S.O.S. - Client timing

Hi,

This is a difficult problem I’ve been struggling with that’s not really about network API’s but about how to recieve server updates on the client using the following architecture:

Client-server framework where clients run asynchronously
the server sends the whole game world to clients who replace their old one
the game world is updated by the clients between server updates using the time elapsed since the last re-draw,
all game worlds have a nano-second time-stamp of when they were last updated
clients and server do not coordinate their times and no ping is measured.

My client code is as follows, but is this the best way to calculate the new time elapsed after a server-update?

long nanoTimeLastUpdated = 0;
long newGameWorldRelativeNanos = 0;
// + means the new gameWorld is ahead of the replaced one, - means it is behind.

public void run(){

nanoTimeLastUpdated = System.nanoTime();
while(shouldRun){
	long currentTime = System.nanoTime();
	long timeSinceLastUpdate = (currentTime - nanoTimeLastUpdated);

	long timeElapsedNanos = timeSinceLastUpdate - newGameWorldRelativeNanos;
	if (timeElapsedNanos > 0){
		gameWorld.update(timeElapsedNanos);
	}
	else{
		// do nothing, can't update the game world with a negative time
	}
	newGameWorldRelativeNanos = 0;
	nanoTimeLastUpdated = currentTime;

	view.reDisplay();	

	doMinSleep();	// Thread.sleep(...) done here

	networkPlayer.recieveAll();
	// calls setGameWorld(...) below if a new gameworld was recieved from server. Otherwise does nothing.
}

}

public void setGameWorld(GameWorld gameWorld, long oldGameWorldTotalNanosElapsed){
newGameWorldRelativeNanos = (long)(gameWorld.getTotalNanosElapsed() - oldGameWorldTotalNanosElapsed);
this.gameWorld = gameWorld;
}

This method seems to work OK in a LAN setup, but on the clients it is much less smooth than on the server and I am wondering whether it could be due to the wrong time being chosen to update the game world when the new game world is recieved from the server. It’s numbing my brain trying to visualise the parallel server and client game worlds and their different timing. :slight_smile:

Many thanks,
Keith

PS: I can’t determine whether the client bumpyness is only the GC or my own faulty code above

I dont think trying to synchronize updates based o ntime wil lwork without your synchronizing the clocks first.

The jitter yo uare experiencing is probably the random variations of latency over the internet.

To synchronize clocks, use sntp.

Thanks Jeff, I nearly gave up looking for an answer to that post.

I’ll look up sntp and try it.

I thought that the method I use above would make clock differences and lag irrelevant since the client’s game world should appear perfectly smooth since it is updated with the actual time that has elapsed in reality except for when timeElapsedNanos is negative (see above code).

When this occurs, the game world will jump since time on the client would not have been continuous, but this should only happen when an update gets to the client quicker than any before (which only happnes a couple of times at the start of the client’s game, and this jump gets smaller and smaller).

SNTP is a bit inaccurate and only works on datagram based protocols if I remember correctly. Especially since you use nano precision… is that really necesary? If you want to sync very high precision clocks you should use NTP (very complex though), which has a precision of ~15 microseconds.

In most scenario’s you can get away with millisecond precision, to implement clock synchronisation i’d recommend to read these two articles:
http://www.mine-control.com/zack/timesync/timesync.html
http://www.codewhore.com/howto1.html

Basically what you do is measure how much the local clocks differ on the client & server and add that difference to your own local clock to get the current clock on the other machine.

Thijs

PS:

public void setGameWorld(GameWorld gameWorld, long oldGameWorldTotalNanosElapsed)
Where is the oldGameWorldTotalNanosElapsed parameter coming from? Local or network?

oldGameWorldTotalNanosElapsed comes from the local machine. It is the addition of all of the timeElapsedNanos from gameWorld.update(timeElapsedNanos).

Basically what you do is measure how much the local clocks differ on the client & server and add that difference to your own local clock to get the current clock on the other machine.

With the above design, wouldn’t clock differences and the network lag become irrelevant?

Thanks for the help.

[quote]With the above design, wouldn’t clock differences and the network lag become irrelevant?
[/quote]
Yes it probably would (if I understand correctly)… I read:

[quote]This method seems to work OK in a LAN setup, but on the clients it is much less smooth than on the server and I am wondering whether it could be due to the wrong time being chosen to update the game world when the new game world is recieved from the server.
[/quote]
And with the reply about SNTP I assumed it was about syncing clocks over a network. Still as you mention it does work fine on a LAN but doesnt work over the internet (where you often have to deal with varaible latencies) it seems to be related to that issue…

It’s a bit hard to see what the problem could be this way, but I’d begin by replacing doMinSleep(); with a Thread.yield() and nanoTimeLastUpdated = currentTime; wouldn’t that need to be placed here instead? :

if (timeElapsedNanos > 0){
gameWorld.update(timeElapsedNanos);
nanoTimeLastUpdated = currentTime;
}

Hope that helps!

Thijs

Yeah, that method sleeps & yields already. That probably isn’t important though.

Yes, that is worth a try. I replace nanoTimeLastUpdated with currentTime always because when timeElapsedNanos is negative the game world is just replaced and the timing should be completely reset (I think…).

Thanks for your help thijs, its a pretty intractable problem and I’m grateful to have someone take a fresh look at it.

SNTP just synchronizes your clocks. What protocol you are using to send your data is orthogonal.

[quote]SNTP just synchronizes your clocks. What protocol you are using to send your data is orthogonal.
[/quote]
Hmmm I remember both pure NTP and SNTP use UDP because they measure connection latency. Retransmits in TCP cause variable and asymetric latencies…

EDIT:
Googled for it a bit and found these explaining it in more detail:
http://www.mine-control.com/zack/timesync/timesync.html
http://coders.meta.net.nz/~perry/rfc/index-1769.html

Hmmm I remember both pure NTP and SNTP use UDP because they measure connection latency. Retransmits in TCP cause variable and asymetric latencies…
[/quote]
Right, so maybe I misunderstood you.

You need to be able to do UDP to use NTP/SNTP to set your clock. But you can then use that properly set clock with whatever protocol and comm scheme you wish.

Is that clearer?

I’m in need to implement clocks and compasate for timing differences… so Keith, what algoritam did you use and do you find it satisfactory?
I’ve read about SNTP and similar ways but it was all logic in few lines… I could have come up with that eventually, what I’m searching is maybe something with simple examples and real problems. Any link to some longer, detailed article will be appriciated, from just implementing clocks in game to synchronizing it. Thank you.

At least on LAN (I haven’t tested it on the net yet) I’ve found that the above code works without having to sync clocks or anything. It seems to auto-sync itself! There’s a lot more to smooth network movement using a variable frame rate than just that game loop code, unfortunately. :stuck_out_tongue:

You need to put time stamps on key & mouse events (the time stamp must be the totalTimeElapsed of the GameWorld at the moment the key/mouse press occurs) & it must be compensated for on the server side. You have to make a decision whether the event is allowed to occur at that time or whether it occurs when the server actually recieved the event. Because it takes time for the event to be sent to the server, you can’t apply it to the server gameWorld at the time it happened on the client unless its possible to ‘go backwards in time’ by updating the server’s gameWorld with a negative time, which I can’t usually do in my case. This means that these non-backwards-in-time events are not smooth but are lagged. Also, there’s some accounting that must be done so client events are not applied twice or not applied at all.

Its also really helpful to have a complete re-sync a couple of times a second so that the client & server gameWorlds are the same, this is where my SuperSerializable streams come into it ! 8)

I haven’t got any neat little examples but PM me with your email and I’ll send you some (bloated & messy) source which shows the above nightmare in action.

Keith

you can see my mail in my profile (warning: have different for MSN account wich I don’t use)
so did you all do it by yourself, and I mean the concept, did you know what is needed and implemented it yourself without researching, reading tutorials and so on? If not, what did you read? :slight_smile:

To be precise about my problem for all, all I need now is to fix choppy movement on my clients… What I think the problem is, is that on server everything gets calculated and it’s fine, but on client the part when package hasn’t arrived yet is simulated (using same engine server uses) but packets with fresh data (position is problem) arrive late and relocates client back some time making it jump “backwards”. This is clearly noticable if I move only forward for example… on server it looks smooth, but on client the players keeps jumping little bit back all the time, so I assume simulation was quicker and when packet arrived (late coz of ping) it corrected the player back. So I’m really getting packets with “old” positon due to latency between server and client. I should implement clock and synchronize it so I can precisly know when I can process packets received, right? :slight_smile:

I’ll mail it when I get home. That teleportation effect can only be fixed by timestamping the events. You have to take the ‘backwards in time’ approach I mentioned above. I haven’t found any material on the topic either, I’ve just stumbled onto the problems as I go.