First something about SimFrames:
In Spring SimFrames only depend on _commands_, means the GameServer sends only commands ("move unit a to b") to the GameClients and those calculate then the unitpositions etc. based just on those commands. The GameServer _never_ sends unit positions, unit states, deaths, ... to the GameClients!
Based on that, for the GameServer a SimFrame is _nothing_ else than sending NET_NEWFRAME to the gameclients, and obv. it can send those even when the GameClients didn't finished their computations on current frames -> the GameServer creates SimFrames ahead (from game and GameClient view).
Now some images:
This what ppl assume how it works:
Code: Select all
v current frame
GamesServer: --------------x
GameClient: ---(x-y)
So GameServer send now the y remaining SimFrames to gameclient, when the client recv them we got:
Code: Select all
GamesServer: --------------x-----------x+y
GameClient: --------------x
SpringPing := time("GameServer gets response that GameClient finished X") - time("GameServer added/created frame x") -> means it's not pure netping!
So the GameClient sends to the GameServer that it recv'ed and processed SimFrame "x":
Code: Select all
GamesServer: --------------x-----------x+y-----------x+2y
GameClient: --------------x-----------x+y
=> SpringPing = 2y/SIM_FRAMES_PER_SEC = 2y/30
Now imagine an ideal world the client is able to process the SimFrames in zero time exactly when it recv them, then SpringPing would be equal to NetPing!
Strangely we _never_ observe anything near that in real world, e.g. we got 20ms netping to the server and Spring still shows 80-130ms ping. Why does this happen?
The answer is simple ...
The GameServer creates SimFrames ahead (from GameServer view)
to compensate fluctuations in netlag.
So instead of the first sketch we get:
Code: Select all
v current frame v most recent `ahead simframe`
GamesServer: --------------(x)ooooooooooooooo(x+n)
GameClient: ---(x-y)
o := `unprocessed/ahead simframes`
Code: Select all
GamesServer: --------------(x)-----------(x+y)ooooooooooooooo(x+y+n)
GameClient: --------------(x)ooooooooooooooo(x+n)
Now we know the Pros, what are the Contras?
Imagine the GameClients wants now to issue a command in SimFrame (x-y):
Code: Select all
GamesServer: --------------(x)ooooooooooooooo(x+n)
GameClient: ---(x-y)
Code: Select all
GamesServer: --------------(x)-----------(x+y)ooooooooooooooo(x+y+n)
GameClient: --------------(x)
So the GameServer must add the GameClient's command in SimFrame (x+y+n).
-> When an user gives a command it will be happen 2y+n SimFrames later!
This also explains why SpringPing is so high, it's the same reason:
SpringPing := (2y+n) / 30 with y:= netping/2 (in simframes) & n := #ahead simframes
So "n" directly influences the responsiveness of the Game, the higher n is the longer it takes till the user sees his commands being issued. This even matters for singleplayer matches cause those do nothing else than running the GameServer locally -> netping is 0 then, but "n" is not! So the user even gets a lag with localhost matches!
How does this matter?
It's a question of balance, setting n too high makes the game unresponsive, making it too low can make it `laggy` when the internet connection is unstable (=ping is fluctuating != not packet drop rate), means that the GameClient runs out of SimFrames and the game stops for a few microseconds till it gets new packets from the server.