Local tickrate/hearbeat & server snapshot update frequency

Started by
6 comments, last by Androphin 5 years ago

Hello,

what is a good way of implementing a tickrate/heartbeat in a RTS game (targeting mobile devices) and when to trigger an update request to the server?

My current implementation is based on snapshots with timestamps to avoid persistent connection. That has some weakness I didn't solved yet and works basically like this:
a) Player A requests gamedata from the server
b) Server sends player A the gamedata snapshot with its last commit timestamp and the servers current one (timestamp is used to calculate from the last left state, e.g. if no player is active at real world time 15:00 o'clock and a farmhouse would produce 2 bananas in 1minute and a player joins back at 15:15 o'clock, 30 bananas would be produced)
c) Player A does some actions. Maybe he cheats. Sends his calculated snapshot back to the server.
d) Server compares his calculated snapshot with player A's one and A's actions and sends back a response, what is allowed or has to be reverted.
Weakness: Player B and C, who join later/to different times and request the gamedata before player A commits, are seeing only the changes that can be calculated from the timestamp the server sends, when their request is made. If player A took the bananas out of the farmhouse. Player B and C would still see the bananas and may take them out, although the bananas aren't there anymore.


snapshotinterpolation.thumb.png.c21301520a63c206bec660fb934f7de6.png

1) I don't know what events should occur, for a new request to the server is made or if I simply should say, every 2-5min fire a new request? When is a good time for that? Or after each user action? Or if there are other good mechanics I don't know of. Like giving up the above snapshot-thing and use some kind of server-push/pooling? (server uses currently HTTP protocol and exchanges JSON data with the clients)
2) How to choose a tickrate for the game and does it live in the render-loop or has it's own thread?
 

Help highly appreciated.

Thank you very much.

Advertisement

In an RTS the snapshot can be pretty big, for some games maybe even megabytes. Also what do you do when 2 clients have changes and thus their snapshots conflict? So it mostly only works as a server->client sync or in a more turn based situation (some games you would literally email or otherwise transfer the save file between turns).

 

Anything I have done involved in making sure all clients stayed in sync, and only sending "commands" and doing like a CRC check to catch desyncs. In an RTS type game its OK to have a bit of latency for inputs, so you can enact the command on all clients on the same update tick.

This makes most direct cheating impossible, it will simply desync and drop that client, although the client can still cheat in regards to information or sending fake commands (aimbot type thing, seeing in fog of war, etc.).

Using fixed update steps is absolutely essential, but doing that is a good idea anyway to avoid FPS impacting actual game logic in unexpected ways.

 

If you want lower latency you can mix it, local commands are enacted immediately, normally player avatar movement etc. as well as sending the commands to the server. The server then sends the clients it's state and there is some interpolation from the clients to the servers state in the event that the state does not match. Not sure on all the details here, I never implemented it at a low level.

 

3 hours ago, Androphin said:

My current implementation is based on snapshots with timestamps to avoid persistent connection.

Are you actually aiming for something a lot less real time than the average RTS, or just the ability to recover from connection loss (e.g. due to switching networks or periods of complete internet loss)?

Thank you.

12 hours ago, SyncViews said:

Also what do you do when 2 clients have changes and thus their snapshots conflict?

Good point. I can think of a way like merging each change (has its own timestamp), so that a time-sequence of changes emerge. Then server calculates on that and responds to both clients what was possible, what not and why?

11 hours ago, SyncViews said:

Are you actually aiming for something a lot less real time than the average RTS, or just the ability to recover from connection loss (e.g. due to switching networks or periods of complete internet loss)?

Exactly. It should feel realtime, but isn't really realtime.
There are no units yet, like 2 controllable armies face each other like in AgeOfEmpires. But maybe added later and I guess, a persistent connection is mandatory for this kind of "2 players meet to a battle", where both need to be synced.
Hope this image helps better understanding what's going on in the game:
chunks.png.181d05ffba10e60cee05c45a28dda1cb.png
Players join, leave, come back. They get only this map with the changes they made over time.
I'm trying to do this with snapshot interpolation, so that the server can serve gamedata to a lot more players and not have to give up after - for e.g. - 1000 persistent connections.
The idea is, that the clients themselves calculate (with fixed update step) on the received servers snapshot gamedata timestamp and send it back with what they changed. Meanwhile, the server can serve other requests and just have to compare, if the sent back snapshot and its player-commands are legit.

12 hours ago, SyncViews said:

Using fixed update steps is absolutely essential, but doing that is a good idea anyway to avoid FPS impacting actual game logic in unexpected ways.

Ok, so I would wrap a while-loop around my render function? I currently have a deltaTime (LibGDX deltaTime) as parameter in render() function for the use with animations. As far as I understand, exchange that with a another time-based (fixed) value wouldn't have an impact? Because I thought this are two different calculations. One on graphics/animations on how smooth they are displayed (FPS), and the other updating the model/data (ticks)?


//deWITTERS example
while( game_is_running ) {
    while( GetTickCount() > next_game_tick ) {
        update();
        next_game_tick += SKIP_TICKS;
    }
    interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick )
                    / float( SKIP_TICKS );
    render( interpolation );
}

Thanks for your help in advance, guys.

2 hours ago, Androphin said:
15 hours ago, SyncViews said:

Also what do you do when 2 clients have changes and thus their snapshots conflict?

Good point. I can think of a way like merging each change (has its own timestamp), so that a time-sequence of changes emerge. Then server calculates on that and responds to both clients what was possible, what not and why?

What if they both change the same object? I think commands are easier, as you can play them in order, and the command set can limit cheating.

  • Player 1 tick 1000: Order unit A to attack unit C
  • Player 2 tick 1000: Order unit B to attack unit C

As opposed to both changing the state (maybe health, etc.) of C.

 

2 hours ago, Androphin said:

Exactly. It should feel realtime, but isn't really realtime.

Ah OK. One way to do this part might to take a more web-browser/site/app type approach. When the player loads in they get the relevant partial state sent to them. They can then get updates, either poll or push, but no requirement to be doing it at specific intervals, the server runs the entire simulation, anything the client does is just for prettiness (and with incomplete state, and possible network drops, likely incomplete anyway).

Anything they do is command-response (like HTTP/AJAX). With lower latency concerns, a lot of the logic can be entirely server side, making the partial states possible (e.g. the client doesn't have to know it is about to move an army into another players ambush, the server can simply reply "ambush" when it happens).

The server can probably run this at a very low tick rate. e.g. EVE Online is 1 tick per second IIRC, even for battles/combat. All the prettiness with ships spinning, turrets aiming, projectiles, etc. is just the client. Or even entirely event driven (set a timer for say "army arrives", "unit built", etc.).

 

2 hours ago, Androphin said:

Ok, so I would wrap a while-loop around my render function? I currently have a deltaTime (LibGDX deltaTime) as parameter in render() function for the use with animations. As far as I understand, exchange that with a another time-based (fixed) value wouldn't have an impact? Because I thought this are two different calculations. One on graphics/animations on how smooth they are displayed (FPS), and the other updating the model/data (ticks)?



//deWITTERS example
while( game_is_running ) {
    while( GetTickCount() > next_game_tick ) {
        update();
        next_game_tick += SKIP_TICKS;
    }
    interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick )
                    / float( SKIP_TICKS );
    render( interpolation );
}

Thanks for your help in advance, guys.

Yes, something along those lines is the right idea if you want deterministic/in-sync logic. You also need to decide what to do if a client is too slow. Right now it probably gets stuck in the inner while loop.

  • Some games will simply drop the slow client as "not good enough".
  • You might run the entire simulation in "slow motion" so the slowest client keeps up.

If the server is completely authoritative, and maybe the client doesn't even run the "same simulation" the variable timestep works (slow client makes bigger steps) but for that design you are basically in a "the client is never in sync" model. That also means when the variable timestep causes things to be different it doesn't matter because you just take the servers outcome (there are lots of cases, from shooting code that goes like simple "is it loaded yet? Shoot 1 bullet." type logic having slightly different rounds-per-minute dependent on update rate, to physics code making an artillery/catapult projectile land in a different tile/location, causing ever greater divergence if left alone).

What I don't understand are the different time-vectors. The server has one, and the client needs one/the same as the server. (A fixed update step like the server has I presume?)
To address your last paragraph first: Do I get this right, that I can't let the client run and calculate its gamespeed by just it's fixed FPS of 60, even though he has sees only a simulation and only sends commands to the server who's holding the actual gamestate, because a farmhouse might produce on a fast device 20 bananas, where on a slow device with varying framedrops only produce 10 bananas, while the server calculates only 15 bananas can be really produced.
The player/client would take either 20 or 10 bananas out of the farm and wondering, why he has 15 bananas after the update.

7 hours ago, SyncViews said:

I think commands are easier, as you can play them in order, and the command set can limit cheating.

...

When the player loads in they get the relevant partial state sent to them. They can then get updates, either poll or push, but no requirement to be doing it at specific intervals, the server runs the entire simulation, anything the client does is just for prettiness

Sounds good. But without intervals, what would trigger a poll? After a client fires a command? A bunch of commands are collected over time and after a certain amount of commands or time a request to the server is made?
Server push could be a good way, if one player commits his commands, server pushes updates to the others?
Server push would work with this https://en.wikipedia.org/wiki/HTTP/2_Server_Push, right?

8 hours ago, SyncViews said:

(e.g. the client doesn't have to know it is about to move an army into another players ambush, the server can simply reply "ambush" when it happens).

The server can probably run this at a very low tick rate. e.g. EVE Online is 1 tick per second IIRC, even for battles/combat. All the prettiness with ships spinning, turrets aiming, projectiles, etc. is just the client. Or even entirely event driven (set a timer for say "army arrives", "unit built", etc.).

Sounds good too! The part with the "ambush" isn't entirely clear to me. If the server reply with "ambush", the client would than need to react to it in form of prettiness, right?

Thanks!

54 minutes ago, Androphin said:
9 hours ago, SyncViews said:

I think commands are easier, as you can play them in order, and the command set can limit cheating.

...

When the player loads in they get the relevant partial state sent to them. They can then get updates, either poll or push, but no requirement to be doing it at specific intervals, the server runs the entire simulation, anything the client does is just for prettiness

Sounds good. But without intervals, what would trigger a poll? After a client fires a command? A bunch of commands are collected over time and after a certain amount of commands or time a request to the server is made?

You would under normal conditions expect a realtime and probably fixed interval. Either the server pushes change events, or you push/pull states. But you can allow for it to be OK for an interruption to occur and for it to quickly continue, if the client state is small enough.

I never tried something like this though, and its just a thought. Even MMORPG I have personally played relied on a consistent connection and would drop the player to the login screen if connection was lost.

As I said, more like a webpage/app. An instant messenger for example doesn't care about "intervals" it cares about a "post message" command, the ability to give a client when they load all the previous messages (the state) and a way to inform the currently connected clients of a new message (probably some sort of push event, but if connection is lost it might go back to loading the full state when it connects again). This is a lot less realtime simulation, so it depends on what you are doing exactly, if you want to move an army from one tile to another, and it says it will take minutes, even hours, you probably don't need or want to actually process that at 60Hz or even 1Hz.

I believe for example in EVE Online, features like skill training and industry are a lot more like this. You tell the game to add/cancel/etc. an industry job, change your skill queue, etc. and then there server just schedules an event (probably backed by a database) saying that its complete. It doesn't process every skill, job, etc. every tick.

54 minutes ago, Androphin said:

Server push could be a good way, if one player commits his commands, server pushes updates to the others?
Server push would work with this https://en.wikipedia.org/wiki/HTTP/2_Server_Push, right?

For push events over HTTP I would use a WebSocket which is pretty much like a normal TCP socket with some extra's. HTTP2 push is still part of the request, its more like if there is a  "GET /blog-post", the server can respond with the document, but also be like "you probably want this image at the start of the post", to save a second request (for that <img> tag) and speed up loading.

 

54 minutes ago, Androphin said:

To address your last paragraph first: Do I get this right, that I can't let the client run and calculate its gamespeed by just it's fixed FPS of 60, even though he has sees only a simulation and only sends commands to the server who's holding the actual gamestate, because a farmhouse might produce on a fast device 20 bananas, where on a slow device with varying framedrops only produce 10 bananas, while the server calculates only 15 bananas can be really produced.
The player/client would take either 20 or 10 bananas out of the farm and wondering, why he has 15 bananas after the update.

If your relying on lockstep determistic simulation to keep states in sync, that is just a flat out desync. You can see it in various RTS type games, it normally drops the player or even the entire match. If it can recover at all, its normally by effectively "rejoining" and transferring the entire "save file".

If your relying on the server to tell the client its wrong, you get the "why he has 15 bananas after the update.". I have definitely seen it in say Minecraft (doing something like removing a minecart track just ahead of a fast moving cart, stopping it, the server gets your block change only after its copy of the minecart entity passed. After a few moments when the server syncs the nearby entities, your minecart teleports to the servers location).

 

54 minutes ago, Androphin said:

Sounds good too! The part with the "ambush" isn't entirely clear to me. If the server reply with "ambush", the client would than need to react to it in form of prettiness, right?

Yes, the "ambush" event would need to tell the client all the information it will need to display to the user. The point is the client had no way of knowing it would happen ahead of time. The client isn't running that part of the simulation and doesn't have that data. Again to use a website example, consider that the server knows every page on the site, but the client only knows the open one. When you "click a link" you have an idea of what you expect to get, but until you actually click it you don't know for sure.

 

 

 

EDIT:

To be clear, I am thinking your overall goal maybe has two parts.

  1. A strategic part with a large number of players and the need to let players come and go as they want or as connection issues occur. Actions take a long time (minutes-hours) and there is a lot of things going on in total. But a single player can only see a small amount, and can only directly control a small number of things.

    It seems to me for this the server can run the simulation only, and players can just see and interact with their individual "pages".
     
  2. A more traditional RTS "match" for combat, with a small fixed number of players and a lot of moving objects (100's, maybe 1000's if you include projectiles, etc.). Transferring the state for so many objects frequently requires a lot of bandwidth, which is why many such games use a deterministic simulation and keep it fully syncronised.

Probably practically two games code wise.

Thanks @SyncViews for your input. Very good points. I think you nailed it with "Probably practically two games code wise."
Like two systems: A dynamic one (Combat and other Live-Interactions) that needs updates/a connection in realtime and a more "static" one that isn't (time-)critical to other players perception of the gameworld.

This topic is closed to new replies.

Advertisement