Base JME Networking example

I looked briefly through game and sources. And good things are comments. It just helps to get (and implement) good ideas. At least when I have free time, I will able to research how services works. Actually, when new networking system was released, I failed with SerializationService (or something like this) and returned to old version.

I could send generated normal, spec and height maps if no one going to improve it. It’s almost nothing but usually even low quality textures looks more beautiful with it.

Quite offtopic. But is it good solution to terraform combining simple terrain generator (aka with Perlin Noise) and Constructive Solid Geometry?

PS. When i tried to exit game (client), I caught this exception:

Well… I would need more info than that to diagnose. There was bound to be a stack trace somewhere.

Note that game writes an application.log… it even keeps backups of previous runs. (Because that’s how I roll…)

If I had the time, what I’d do personally is generate an AO map, repaint the texture in slightly higher resolution, and separate out the red parts into a separate mask texture to make it easier to recolor the ships at runtime. (Bonus if the separate mask has three different color channels for different kinds of trim color.) Though the mask texture would require a custom shader, that part is easy. :slight_smile:

But for now, I’d settle for baked AO and a higher res texture.

This game doesn’t even have lighting right now (and may not ever) so the normal maps and stuff might not be relevant.

For those trying to follow along at home, here is the commit that adds the server side SimEthereal support:

And here is the commit that adds the client side support:

Sometimes it’s nice to see the diffs of specific sets of changes like that to see what actually had to be done.

Note that if you try to build this app from source now that you will also need to checkout and gradle install SimEthereal since I added a convenience method over there. Real releases of this stuff is pending getting the example fully working.

Next step is to add visualization interpolation as right now of the LAN it’s noticeably jerky up close… bound to be worse over the WAN even. And anyway, interpolation is easy to add because the code is already there. :slight_smile:

And finally, object interpolation:

And with that, we have a fully working SimEthereal example… that in its current state should support about 80 connected players before the message split bug starts to show up.

Next is to allow disabling that split code, fix the bug, add some diagnostic stuff, write it up… ugh. Hahah.

Edit: one other point to mention is that currently the space grid is finite because I let the player fly in any direction. A real game would want to keep the camera in one place and move the world, make model’s relative to the camera, etc… I’ll probably do that at some point also.

2 Likes

And because spamming my thread is easier than doing real work…

Screen shot of me logged in four times on two different computers:

5 Likes

00:42:04,614 ERROR [LegacyApplication] Uncaught exception thrown in Thread[jME3 Main,5,main]
java.lang.RuntimeException: Error invoking action method:stop
at com.simsilica.lemur.CallMethodAction.execute(CallMethodAction.java:180) ~[lemur-proto-1.6.2-SNAPSHOT.jar:?]
at com.simsilica.lemur.ActionButton$ClickCommand.execute(ActionButton.java:132) ~[lemur-proto-1.6.2-SNAPSHOT.jar:?]
at com.simsilica.lemur.ActionButton$ClickCommand.execute(ActionButton.java:127) ~[lemur-proto-1.6.2-SNAPSHOT.jar:?]
at com.simsilica.lemur.core.CommandMap.runCommands(CommandMap.java:61) ~[lemur-1.8.2-SNAPSHOT.jar:?]
at com.simsilica.lemur.Button$ButtonMouseHandler.click(Button.java:235) ~[lemur-1.8.2-SNAPSHOT.jar:?]
at com.simsilica.lemur.Button$ButtonMouseHandler.mouseButtonEvent(Button.java:258) ~[lemur-1.8.2-SNAPSHOT.jar:?]
at com.simsilica.lemur.event.MouseEventControl.mouseButtonEvent(MouseEventControl.java:122) ~[lemur-1.8.2-SNAPSHOT.jar:?]
at com.simsilica.lemur.event.PickEventSession.buttonEvent(PickEventSession.java:553) ~[lemur-1.8.2-SNAPSHOT.jar:?]
at com.simsilica.lemur.event.MouseAppState.dispatch(MouseAppState.java:97) ~[lemur-1.8.2-SNAPSHOT.jar:?]
at com.simsilica.lemur.event.MouseAppState$MouseObserver.onMouseButtonEvent(MouseAppState.java:112) ~[lemur-1.8.2-SNAPSHOT.jar:?]
at com.jme3.input.InputManager.processQueue(InputManager.java:831) ~[jme3-core-3.1.0-beta1.jar:3.1-beta1]
at com.jme3.input.InputManager.update(InputManager.java:907) ~[jme3-core-3.1.0-beta1.jar:3.1-beta1]
at com.jme3.app.LegacyApplication.update(LegacyApplication.java:725) ~[jme3-core-3.1.0-beta1.jar:3.1-beta1]
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:227) ~[jme3-core-3.1.0-beta1.jar:3.1-beta1]
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151) ~[jme3-lwjgl-3.1.0-beta1.jar:3.1-beta1]
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:193) ~[jme3-lwjgl-3.1.0-beta1.jar:3.1-beta1]
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:232) ~[jme3-lwjgl-3.1.0-beta1.jar:3.1-beta1]
at java.lang.Thread.run(Unknown Source) [?:1.8.0_91]
Caused by: java.lang.IllegalArgumentException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_91]
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_91]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_91]
at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_91]
at com.simsilica.lemur.CallMethodAction.execute(CallMethodAction.java:178) ~[lemur-proto-1.6.2-SNAPSHOT.jar:?]
… 17 more
00:42:04,679 INFO [GameClient] close()

That’s weird.

Is that from the log file or from the console?

From the log file. Anyway, It happened only once.

Paul can you please explain more about concept behinf EventBus in package com.simsilica.event;
Looking to example code i could not understand much.
What events should we dispatch using this ?
Is there use case for it in ES design ?
Is it for communication between client states or can be used for server-client too ?

Thanks

1 Like

Whatever you like.

It’s a way of decoupling event publishing from event subscribers. I often use it for things like player events like joined/left because many different unrelated sets of code might want to know about those things. I also use it for general error reporting with the main Application registering a listener that will display an error and shut the app down if a “fatal” error is sent.

In Mythruna, I even use it for game started, game ended, and different game-related life cycle events because I have a lot of decoupled plugins. When they are initialized, they register for the events in which they are interested.

The event bus supports standard event listener type of notification but you can also let it call methods directly based on the event name and type.

2 Likes

Ah, so you are using for global game events.

I was some how confused because it has similar concept with EntitySets in Zay-ES. Because we can treat events as entities which have event component.
But as i see you are using Event bus for more high level and global game events which should have affect on whole game systems (states) not just on one specific systems.

1 Like

Yeah, it’s for stuff outside the ES, basically.

1 Like

Do you use any specific framework for implementing decoupled plugins? I’m new to it - but it makes total sense, so I’d like to know how this is done. Any material I can read up on or framework I can research?

I basically rolled my own.

I’ve been dynamically loading classes at runtime since my very first use of Java pre-1998… so it’s old hat and I don’t even think about it.

Even still, most of Mythruna’s plugins are loaded by code… it’s just a nice separation that they are decoupled. And in that case, there is no magic. A “pluging” is just a class that has a lifecycle that instantiates other classes… that implements a PlugIn interface with said lifecycle methods on them.

So TerrainPlugIn.initialize() creates and registers all of the related classes for that. I have a light-weight IoC container that everything is put into as the service model. So TerrainPlugIn.initialize() registers the apprpriate terrain database classes, terrain generators, etc. with that service container. Other services can then ask for “WorldDatabase” and get the implementation that was registered. Typical IoC dependency stuff.

1 Like

So… let me ask my question in a bit more professional way :slight_smile:
Every Mob entity has a BodyPosition component which contains a history of TransitionBuffer for that entity. this buffer is being fed by server for each entity.
And ModelViewState updates positions for all Mobs using this buffer by providing an specific time. this time is alway back from the current time (This trick is used to have smooth interpolation for entities movement because server can not send position data for all mobs each frame). So rendering happens always back in time (As mentioned in docs this time is 100 ms, so player always see rendered scene with 100 ms delay), Am i right until now ??

OK, If i am right then the question is :
Do we need to interpolate position for player entity too ? I mean why player should get his position from server again ?
It seems we do not need because Player has no delay for himself on his computer . :slight_smile:
So Paul what is you idea about this ?

and second question is, why these fields are transient in BodyPosition if you are going to read them again from server using SharedObjectUpdater ?

private transient int size;
private transient TransitionBuffer<PositionTransition> position;
1 Like

Yep, just like in the Valve articles linked all over the place.

If you don’t delay the player’s position then they will think they are somewhere that they are not in relation to all of the other objects. Imagine he is flying in formation. All of his team mates will think he is an idiot for flying on some strange place… but so will all the other team mates think the same of them.

More importantly, what if you’ve landed on something that is moving… if you do not put the player’s position in the same time frame as everything else then it will seem like you are moving (possibly quite jittery) with respect to what you’ve landed on.

We make a concession only for rotation in this case… but there are certain kinds of games where even that wouldn’t work. (Anything where the rotation is actually controlled by physics instead of directly a result of mouse look… driving game, etc., we’d have to look at rotation back in time also.)

1 Like

They are transient so that we don’t transfer that data from the server. It would be wasteful since we are just going to replace them on the client with our own shared instance.

2 Likes

Considering what Valve article says :

Input prediction
Lets assume a player has a network latency of 150 milliseconds and starts to move forward. The information that the +FORWARD
key is pressed is stored in a user command and send to the server.
There the user command is processed by the movement code and the
player’s character is moved forward in the game world. This world state
change is transmitted to all clients with the next snapshot update. So
the player would see his own change of movement with a 150 milliseconds
delay after he started walking. This delay applies to all players
actions like movement, shooting weapons, etc. and becomes worse with
higher latencies.
A delay between player input and corresponding visual feedback
creates a strange, unnatural feeling and makes it hard to move or aim
precisely. Client-side input prediction (cl_predict 1) is a
way to remove this delay and let the player’s actions feel more
instant. Instead of waiting for the server to update your own position,
the local client just predicts the results of its own user commands.
Therefore, the client runs exactly the same code and rules the server
will use to process the user commands. After the prediction is finished,
the local player will move instantly to the new location while the
server still sees him at the old place.
After 150 milliseconds, the client will receive the server
snapshot that contains the changes based on the user command he
predicted earlier. Then the client compares the server position with his
predicted position. If they are different, a prediction error has
occurred. This indicates that the client didn’t have the correct
information about other entities and the environment when it processed
the user command. Then the client has to correct its own position, since
the server has final authority over client-side prediction. If cl_showerror 1
is turned on, clients can see when prediction errors happen. Prediction
error correction can be quite noticeable and may cause the client’s
view to jump erratically. By gradually correcting this error over a
short amount of time (cl_smoothtime), errors can be smoothly corrected. Prediction error smoothing can be turned off with cl_smooth 0.
Prediction is only possible for the local player and entities
affected only by him, since prediction works by using the client’s
keypresses to make a “best guess” of where the player will end up.
Predicting other players would require literally predicting the future
with no data, since there’s no way to instantaneously get keypresses
from them.

Player always checks his result of prediction with prediction result from server and if they are different player should fix his position with what provided by server.

1 Like

The network example doesn’t use client-side input prediction and SimEthereal doesn’t provide any support for it (nor could it, really).

As long as the view direction moves when the player moves the mouse the motion feels ok, ie: won’t make people like me sick. After all, movement is done with physics and acceleration so it doesn’t happen right away anyway. Even though the player feels/sees a delay, it’s somewhat mitigated by the fact that the server has been moving you since it first got your key-press… even if you don’t see that position right away.

Because of latency, you get a little acceleration bump on your client when you first see the movement. (Especially if we were to insert physics events back in time by network latency… which I don’t.)

Anyway, the other approach is considerably more difficult and comes with a whole bunch of its own odd behavior and edge cases. It’s definitely not suited for a simple network example.

2 Likes