Sign in to follow this  

Hoping for second opinion on RTS/RPG networking model

This topic is 2621 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Okay, I'm thinking about the following situation:
- Client/Server topology
- 1 to 10 clients per server
- All data sent over TCP/IP in binary format
- Up to roughly 200 entities of interest to any given client at any given time, typical figure is 1/3rd to 1/2 of that
- Entity movement is generally slow, linear, and predictable (eg. direction and velocity only changes once per second or two)
- No twitch gameplay (RPG or RTS, not FPS, no significant physics)

I'm hoping to get that working with minimal popping/snapping but with reasonable synchronisation across all machines, probably running slightly in the past (100-500ms?) on everybody's machine to facilitate this.

I don't want a lock-step system, mostly so that each client only knows about the entities I want it to know about. (Also discussed here recently.)

I'm thinking of broadcasting messages consisting of entity-position/direction/velocity/server-timestamp, plus maybe some forward-dated messages with zero velocity acting as 'stop here' commands to reduce overshoot. This would be done maybe 1 or 2 times per second per entity. Does that sound reasonable? I'm hoping that by sending velocities and running everything in the past I can get smooth-looking motion, since the direction of movement is quite predictable, and can avoid needing to do good forward prediction.

Regarding the approach of simulating the past, what sort of delay is reasonable for this? I was probably going to start with about 250ms, hopefully more than allowing for the various obvious causes of latency (eg. per-connection polling frequency on the server, actual net latency, and per-connection polling frequency on the client). I'm happy for people to see occasional position corrections, as long as the vast majority of players get a smooth game the vast majority of the time.

Any obvious mistakes I'd be making? Anything I need to consider that I've forgotten? I've "only" worked on MMOs before, so this is slightly different. ;)

Share this post


Link to post
Share on other sites
Yes, that sounds reasonable. You don't need to post-date "stop" messages; just synthesize them on each client. When you send a "entity, position, velocity" message, include a duration for how long that is valid. Or even just make that duration a constant per object type, to avoid sending anything at all.

The amount of time in the past you want to run at is easy: It's the one-trip transmission latency from the server to the client. Input commands for players will additionally be delayed by the reverse transmission latency, so gap from "action" to "response" will be your ping time.

Share this post


Link to post
Share on other sites
Ah, of course. I was thinking that I could send start and end locations with start and end times and linearly interpolate across that, but obviously a duration carries the same information in these circumstances.

Estimating the round trip time for a given client is easy enough, but what about when that varies over the session? I'm trying to work out if I'd need to contract or dilate time locally, which sounds a bit tricky. If someone has a good ping at the start but it gets worse, they'll have messages coming in late throughout. Am I overthinking it?

Share this post


Link to post
Share on other sites
Well, you can either stop a person from all forward prediction once they reach some threshold, or you can treat them like they are not lagging. Either way, you will get rubber banding. In one case, you will get rubber banding on the server to all other clients, and the other you will get it on the client who is lagging.

Remember, most users on the internet now have broadband connections and do not lag much.

After a user sends no packets, you should disconnect them. Other than those options, there isn't really anything you can do.

The funny thing is, all of your clients will lag to some degree: 10 ms ping, or 300 ms ping. You have to decide how much is too much through testing. So, dealing with internet lag is a complicated question. What about packet loss? Do you disconnect someone if they reach 20% packet loss over a 10 second period or over a 30 second period.

Most of these problems can be answered by seeing how important your internet connection is to gameplay. Some games you can play with a 700 ms ping and there is no noticable lag, others need 100 and below.

I am giving you more questions than answers, but I hope that you may form some answers through my questions :P

Share this post


Link to post
Share on other sites
Quote:
Original post by smasherprog
Well, you can either stop a person from all forward prediction once they reach some threshold, or you can treat them like they are not lagging.

But I explained above that I am pretty much avoiding doing forward prediction. The idea of showing the player's past position is that I can move players from one authoritative position to another. I'm not averse to doing a small amount of extrapolation on the occasions when a message is late to arrive, but that's all.

Quote:
Remember, most users on the internet now have broadband connections and do not lag much.

It's not really about lag but about jitter. Ping times will vary over the course of a session, which means the gap between remote time and local time will grow and shrink. It's dealing with that which I am trying to work out.

Quote:
I am giving you more questions than answers, but I hope that you may form some answers through my questions :P

I'm not sure how much of the rest of what you said is relevant to my problem, sorry!

Share this post


Link to post
Share on other sites
I suggest you make the client-calculated delay something like:

"X milliseconds plus a leaky integrator based estimate of the calculated round trip time."

X might be in the 10 .. 100 range depending on a robustness-vs-responsivness trade-off. This lets you make the response time reasonably responsive, while still having some buffer for when the delay jumps.

The leaky integrator probably also wants to be more sensitive to higher times than lower. It might look something like:

float update_estimate(float estimate, float new_sample) {
if (new_sample > estimate) {
return estimate * 0.5f + new_sample * 0.5f;
}
return estimate * 0.95f + new_sample * 0.05f;
}

Share this post


Link to post
Share on other sites
Yeah, that seems like a sensible way to adjust the time, but it introduces discontinuities into the simulated timeline, meaning I need another layer of interpolation on the top to smooth it out, no?

For example: I send an entity,position,velocity,duration message where the duration is 1 second. Then, during that second, the detected latency changes and the clock moves back or forwards a bit. Moving that entity naively for that velocity and duration is now going to undershoot or overshoot. Right? That's the bit that I can't quite get my head around.

I suppose I can multiply out the positions + velocities into expected positions at certain times, but I'll still need more corrective interpolation on top of that to deal with the aforementioned discontinuities.

I'm sure there is a simple solution to this, probably just involving 1 more blend operation, but I just can't quite put my finger on what it is.

Share this post


Link to post
Share on other sites
What's going to happen is that your delta-time for a time step will be smaller or bigger (if you use variable time step), or the delay before the next fixed-size time-step is taken will be smaller or bigger. You have to separate wallclock time ("time") from simulation time ("steps," for example). The server offset is the value that correlates the two, but it doesn't affect the steps directly.

So, yes, a time adjustment will cause a momentary time dilation artifact, which will likely be seen as a small momentary twitch or jump in velocity. That's why you want to adjust the clock softly, not in a big jump (amortizing the jump over (wallclock) time).

Share this post


Link to post
Share on other sites
Ok, I wasn't really thinking of timesteps at this point. After all, if I have the position and velocity as a vector, and I know acceleration is zero, then it doesn't matter whether I have a fixed timestep or not as it's going to integrate to the same value. But I guess that if I expand out the position + velocity + duration to state where an entity will arrive at a certain time, interpolation of its position up to that point will be smooth and continuous as long as local time is continuous (well, C1 continuous, if I've got my terminology right), and the leaky integrator should approximate that. Am I understanding it correctly?

Share this post


Link to post
Share on other sites
The leaky integrator makes it C0 continuous (there are no "gaps") but you still get a velocity disruption for each adjustment (so velocity, or first derivative, is not continuous).
But, yes, I think you're thinking about it right.
I prefer time steps, because you don't get as deep into integrator problems, like how high you can jump given fixed gravity, as you do with variable time step.

Share this post


Link to post
Share on other sites
To me, modeling your locomotion this way seems fairly direct and low-level for an RTS-style game with slow, linear, predictable, non-twitch movement. Couldn't you use a pathfinding solution here and interpolate? When an entity wants to move, it calculates a predicated path on-the-fly and starts moving1. It sends the request to the server, which does the authoritative pathing, and sends the results back to the client who then reconciles the two. It should also help the time dilation issue since your motion isn't based on continued updates from the server.

1Instead of moving immediately, it can even wait the estimated one-trip time.

Share this post


Link to post
Share on other sites
I would essentially be doing that, but I'll basically only be sending each step of the path as it happens, so that clients can't cheat by being able to see a whole path in advance. If the path has someone walking in a straight line for 5 seconds then I'll probably only send 1 message per recipient for that unit in that time span.

Share this post


Link to post
Share on other sites
The part I still don't get is the nature of the delay. For example, say you have a player with a 230ms ping (115ms one-way). Do you slow down the entire simulation on the server so that each update (fixed-rate timestep) is at ~150ms intervals, to make sure the laggy player sees it and can respond before the next update? Or do you sent out updates at a static rate (say, 50ms), and just force all players to future-date their actions so that the laggy player has time to receive them before it has to apply them?

The first one, varying the interval between timesteps, sounds very messy. I would imagine the players would end up doing a lot of extrapolation when they discover the next server update is delayed due to increased lag. But the second could lead to cheating if players receive actions so far future-dated that they could act on the knowledge.

Have you decided how you will handle this?

Share this post


Link to post
Share on other sites
I'm not doing anything on the server for this at all. The server runs along merrily, simulating at a constant rate, and the clients are always seeing what happened some time in the very recent past. (Just like real life.) If a client lags, they will presumably see some jerky motion as messages start coming in late. There is deliberately some buffer time of slightly more than the typical latency time to try and ensure that players get a chance to see things broadly in sync, but there's no guarantee (and I'm not interested in making such a guarantee).

There won't be any notion of responding 'before' the 'next' update or anything like that. You send requests to the server whenever you issue an order. The server then judges whether it can act on them, and if it can, the state changes accordingly and any necessary notification will be sent. This isn't a lock-step system and there are no deadlines, nor is there any need for network updates to be synchronised to simulation updates or anything like that.

Share this post


Link to post
Share on other sites
Quote:
Original post by Nairou
The part I still don't get is the nature of the delay. For example, say you have a player with a 230ms ping (115ms one-way). Do you slow down the entire simulation on the server so that each update (fixed-rate timestep) is at ~150ms intervals, to make sure the laggy player sees it and can respond before the next update?


That would make it a turn-based game!

Any real-time networked game has to pipeline/overlap input, transmission, simulation and display. Exactly how you mix and match these components and present it to the user is what makes certain network models "feel" certain ways.

Share this post


Link to post
Share on other sites
Don't send durations. Send absolute targets for things. Why? Because you can out-of-order things, repeat things, stack things and then they still work.

Your object in the world has a destination X, Y (possibly Z) and time. Just send those tuples.

You can add adverbs to them -- "run", "walk", "fly", "describe ballistic arc from ORIGIN-TUPLE", but the crunchy point here is that the transactions are now idempotent.

Idempotent means you can send the same transaction again and the result won't change. If you send a tuple including a timestamp, even if the message is delayed in transmission (either in a TCP resend window or because the UDP got dropped or just somewhere in your message stack) it'll be valid when it arrives; you may just need to either warp the thing or just move it slightly faster.

{You'd probably rather characters skated slightly but ended up in the right place and in sync. If it's a spacecraft, no-one will ever notice}

The crucial thing is that even if the motion starts slightly late it will complete at the correct time.

Idempotency is useful in another way; you can just have a single state for the object. You no longer need to track what it's *doing*, you can just dump it's whole state as an update to each client; there's just a note on each one which says stuff like "RUNNING TOWARDS X,Y. ARRIVAL AT TICK 959879"

You can compile several steps and send them all; actions which complete "in the past" just get skipped. This way you don't need to keep sending new operations (this is useful for sending a whole section of a planned route, for example).

You then just need to get your two systems to agree on a common concept of time, but that's a lot easier; you can just tick up in 20ths of a second or some useful interval like that (small enough that the universe looks smooth to a user, but large enough that clock jitter isn't noticeable) from the connection time and on the server remember the constant offset from universe start.

Fixing any drift is then relatively easy -- you just bounce time pings back and forth every once in a while.

Share this post


Link to post
Share on other sites
Katie, I was going to send durations along with start times in the message, which is equivalent to start and end times as far as I can tell. (For linear movement at constant velocity, at least.) Looking back through the previous posts I can see that I didn't make that clear.

Share this post


Link to post
Share on other sites

This topic is 2621 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this