Trying to reduce lag

Hello all,



I’ve been messing around with spider monkey and managed to get a client/server set up. Clients connect to the server, send what keys are being pressed, the server runs the physics and updates all the clients.



So far this has worked ok when I’ve been testing it with just two people running around a map. I’ve hit a problem now that i’ve come to add enemies. To keep things simple i’m only having the server get the enemies to spin on the spot, however If I have more than about 10 enemies the game lags so much it becomes unplayable.



I figure I must not be implementing the server in the most optimal way but I’m really not sure how to make it better, below is the update method for the server, I can post more code if people want me to:



[java]

public void simpleUpdate(float tpf)

{

calendar = Calendar.getInstance();



long tmpTime = calendar.getTimeInMillis();



long timeStep = 10;



if((tmpTime - previousTime) > timeStep)

{

if(entities.size() > 0)

{

int count = entities.size();



for(int i = 0; i < count; i++)

{

if(entities.containsKey(i))

{

Entity e = entities.get(i);



//sets walk direction and Geometry rotation

e.rotate();

e.move();



//check if entity has fallen off the map

if(e.getCharacterPhys().getPhysicsLocation().y < -100)

e.getCharacterPhys().setPhysicsLocation(new Vector3f(0, 1, 0));



//custom message class

newMessage.setId(e.getId());

newMessage.setPosition(e.getCharacterPhys().getPhysicsLocation());

newMessage.setRotation(e.getRotation());

newMessage.setType(e.getType());



server.broadcast(newMessage.setReliable(false));

}

}

}



previousTime = calendar.getTimeInMillis();

}

}

[/java]



Both players and enemies are an Entity class and the server stores them all in an ArrayList called entities

While you will definitely end up running into scalability issues with this simple approach, you should also be able to get more than 10 entities at a time.



I’m curious about a few things:



How is your server setup? I see you are using simpleUpdate() so I’m guessing it’s a full up JME application. How often is simpleUpdate() called?



Also, what is your test configuration? Do you have one server stand alone with separate clients on a LAN or is one of your clients running on the same box as the server? Is any player running remote over the internet?



None of those are necessarily problems, of course, I’m just trying to understand the landscape of your performance observations.



…and maybe the most important one, I think we need to see the class for newMessage. I’m curious to get an idea of what spider monkey is trying to serialize 10+ times an update.

How can you find out how often simpleUpdate() is called?



The server and client are separate programs. Normally to test I run the server and the client both from eclipse (connecting over LAN) but I have also tested with another laptop running the game and connecting over the internet.



Here is the newMessage, which is an UpdateMessage class. I’ve omitted the getters and setters.



[java]

public class UpdateMessage extends AbstractMessage

{

private Vector3f position;

private float rotation;

private int id;



//true when player disconnects or enemy is dead

private boolean delete = false;

//is the entity being updated an enemy or a player

private Entity.Type type;



//Getters and setters go here

}

[/java]

public void simpleUpdate(float tpf) implies that you are running the server as a JME SimpleApplication. So simpleUpdate() will be called once per render frame. If you are getting thousands of frames per second then you will be sending 10s of thousands of messages per second. But I don’t know how you have this app setup so I can’t be sure. Ah, I see you are regulating with a time step.



That message should be pretty small so it’s probably not that. Presuming Entity.Type is an enum then the messages should only be around 30 bytes or so.



Sending out full world state 100 times a second could be overkill, though. After all, the clients are only drawing at 60 frames per second anyway.



Can you describe the lag you are seeing? It’s possible it’s not the networking at all and simply the clients bogging down trying to handle 1000+ messages per second.

The server extends simpleApplication but runs it as headless.



When the game has just one player everything is fine, but when there are more than 10 entities on the server the players rotation speed becomes really really slow, and the translation of the player isn’t as smooth.



Just noticed that you said the client should be running at 60 fps.



[java]AppSettings s = new AppSettings(true);

s.setFrameRate(100);[/java]



My client was setting up with this but i’ve changed it to 60 and it runs better. Seems counter intuitive that lowering the frame rate makes the game run smoother, should I also set the frame rate of the server to 60?

You should pick a rate you want the server to run at and run it at that. Maybe you already set the server to 100… though 100 is a little excessive maybe (the every 10 ms thing).



On the client, I just meant that the monitor refresh rate is probably 60 FPS anyway… so anything more than that is wasted. Though if you are also sending client updates to the server at that frequency then that could be a factor.



There are many different strategies for sending state. One of the more common ones would be to bundle messages together and send state chunks 20 times a second or so… then the client interpolates at a slight delay to create smoother movement.



These are good articles if you can make it through them:

http://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

http://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization



Even if you don’t borrow all of the ideas you might get something useful out of them.



Also, even if you don’t bundle messages, one of the benefits of using unreliable messaging is that you can throw away old data. Right now you force every received message to process through the render loop. It might be better to update a shared state that update loop can simply consult to update the entities when it can. For example, the simple approach would be to store entity positions in a ConcurrentHashMap and just pass through that on update to reset entities that have moved. Then all the message listener needs to do is update the position in that map. If you were keeping some window of values and interpolating then you’d probably have a similar structure anyway.



…but this rapidly digresses into a much more complicated conversation. :wink: And that is better served by a thorough understanding of those links.

Sigh, it’s never as easy as you think it would be. Thanks for the links, i’ll have a look through them when I have a chance.



I don’t understand how the ConcurrentHashMap approach you mentioned differs from what I am doing at the moment. The Client and Server store all the entities in a HashMap (I know I said ArrayList in my original post but that was a mistake). The message listener on the client sets temporary values for each entity when it receives an UpdateMessage and the simpleUpdate method then sets position and rotation of the node.



[java]

public void simpleUpdate(float tpf)

{

if(player.getUpdate())

{

player.getCharacterPhys().setPhysicsLocation(player.getTmpPos());

player.setRotation(player.getTmpRotation());

player.setUpdate(false);

}



player.applyRotation();



if(entities.size() > 0)

{

for(Map.Entry<Integer, Entity> entry : entities.entrySet())

{

Entity p = entry.getValue();



if(p.getDelete())

{

rootNode.detachChildNamed(String.valueOf(p.getId()));

entities.remove(p.getId());

continue;

}



if(p.getAdd())

{

p.getNode().setName(String.valueOf(p.getId()));



rootNode.attachChild(p.getNode());

p.setAdd(false);

}



if(p.getUpdate())

{

p.getCharacterPhys().setPhysicsLocation(p.getTmpPos());

p.setRotation(p.getTmpRotation());

p.setUpdate(false);

}



p.applyRotation();

}

}

}

[/java]



[java]

public void messageReceived(Client source, Message m)

{

if(m.getClass().equals(UpdateMessage.class))

{

UpdateMessage current = (UpdateMessage)m;

//ie player has not been assigned an ID

if(player.getId() == -1)

{

player.setId(current.getId());

System.out.println("New ID: " + current.getId());

}

//if the incoming message is updating the player

if(current.getId() == player.getId())

{

player.setTmpPos(current.getPosition());

player.setTmpRotation(current.getRotation());

player.setUpdate(true);



}

//else incoming message is updating en entity in the hashmap

else

{

if(current.getDelete())

{

System.out.println("Player left: " + current.getId());

entities.get(current.getId()).setDelete(true);

}

else

{

//message is for a new entity (one the client hasn’t seen before) ie new player joined

if(!entities.containsKey(current.getId()))

{

if(current.getType() == Entity.Type.player)

System.out.println("Player joined: " + current.getId());

else

System.out.println("Enemy joined: " + current.getId());



Entity p = new Entity(assetManager, current.getPosition(), current.getType());

p.setType(current.getType());

p.setId(current.getId());

p.setAdd(true);

entities.put(current.getId(), p);

}



Entity p = entities.get(current.getId());

p.setTmpPos(current.getPosition());

p.setTmpRotation(current.getRotation());





p.setUpdate(true);

}

}

}

}

[/java]

Sorry… I was thinking about another post where someone was enqueuing callables to set the entity position.



However, neither HashMap nor ArrayList are thread safe so I may have jumped to conclusions. How are you making sure that the received messages aren’t tromping all over the render thread’s view? For example, HashMap.get() can actually loop forever if it hits just at the right time when a HashMap.put() is called from another thread.



You should at least switch your HashMap for ConcurrentHashMap to avoid those issues. Then you will only have to deal with seeing inconsistent state within a single entity… or in a multi-CPU environment you may see state changes very late. I don’t know if your entity class is already dealing with this. There are ways to avoid expensive synchronized calls, though.

pspeed said:
Sorry... I was thinking about another post where someone was enqueuing callables to set the entity position.


Does this mean that your advice: 'Right now you force every received message to process through the render loop. It might be better to update a shared state that update loop can simply consult to update the entities when it can'

Doesn't apply to my situation?

pspeed said:How are you making sure that the received messages aren't tromping all over the render thread's view?


I'm not, i'm new to spider monkey (although i've done network programming in C before) so I imagine there are a lot of things I'm missing or not doing in the best way.

I'll switch to ConcurrentHashMap though as hopefully that will avoid some issues.
baggyn said:
Does this mean that your advice: 'Right now you force every received message to process through the render loop. It might be better to update a shared state that update loop can simply consult to update the entities when it can'

Doesn't apply to my situation?


Correct.