Advertisement

Rolling back on Prediction

Started by September 11, 2006 02:56 PM
9 comments, last by Ozymandias42 18 years, 5 months ago
So, I've got a client/server setup that uses client prediction. At time t0, the client begins to hold the fire button, launching a stream of bullets and decreasing their ammo count. At time t1, the server is informed that the client began firing t1-t0 times ago, starts creating bullet objects in the right places, and begins notifying the other clients about bullet creation. At time t2, the client stops firing. At time t3, the server discovers the client stopped firing a while ago, un-creates some of the extra bullets, and notifies clients that a client stopped firing. My question is, how can I set up my server's design so that I can easily handle this kind of complicated object creation/destruction? Handling object movement with prediction's pretty easy. You can just store an array of the positions objects were in for the last n states and back up and recalculate as necessary. When it comes to anything other than position, like objects being created at regular intervals or scripting events (a player collides with a landmine, a playey charging up a weapon cancels firing it at the last moment), I'm not sure how to elegantly back out changes to the game state. It seems like the only good way to solve this problem is to have the server running slightly behind the clients, and making it so that once the server has decided a bullet was fired, a bullet damn well was fired. This would seem to double the effective lag, though. How is this normally handled, especially as regards scripted events?
You pick one solution per problem, because what "feels right" varies depending on the problem.

For a few general approaches, see the Forum FAQ.
enum Bool { True, False, FileNotFound };
Advertisement
Is there a particular FAQ answer you're directing me to? Q16 appears to be the only one that might relate to client prediction, but the article it cites is largely concerned with syncing motion. The two problems are related, but separate. Motion syncing is an interesting problem, but an error in position can be quickly corrected by snapping it to a new value.

My question, restated, is this: how should the server store and handle its state in such a way that responding to a back-dated command from the client can be easily handled? If a user flies over a spot in space that triggers an event (perhaps launches missiles or blows up a planet), it's possible that I'll get a message from that user's client 100ms later saying "I changed course." Now the user never hit the trigger, and the missiles were never launched. I can think of a few ways to handle this, but none of them seem particularly elegant.

One possible solution would be to store (refresh rate * maximum allowable latency) previous full game states and handle any game triggers and object spawns all over again every time a new input from the client arrives, but that seems extremely wasteful. Worse, I'd have to do special checks to see if I'd sent any information to the clients that was no longer true (perhaps I told the clients about the new missiles, but they don't exist any more).

Another solution might be to not allow triggers to roll back and make the boundaries as invisible to the clients as possible, with nebulous borders and guns that cannot fire continuously. That seems to be sacrificing too much gameplay, though.

This seems like a pretty common problem someone writing a real-time network game would run into, but I can't find any articles on it, although I can find lots of articles about predicting motion. Perhaps I'm searching for the wrong keywords, or perhaps I'm thinking about it in entirely the wrong way and there's a pretty simple solution?
Hey Viktor,

Thanks for the answer. I think you're right about the no-take-backsies on the server's part. That may be going way too far for prediction, and ignoring it does indeed make things much easier to handle.

Still, I worry about the case of bullets. If the user's lag is 250ms, and he's firing 20 bullets/second, when he stops firing, the server will have created 5 bullets that the original client didn't fire. I could just mark those last ones as ignored and credit the original player's ammo, I guess. Does that seem about right?
Ask yourself: How did the server know the client started firing? How come the server isn't 5 bullets behind when it's starting to count bullets?
Now, can you use the same solution to solve the end-of-firing problem?

Or, alternately, does it matter if some other players hear a few more bullets, if those bullets don't actually injure them? Actual damage taken should only be calculated on the server, so if some players see a few extra bullets/muzzle flashes, does that really matter?
enum Bool { True, False, FileNotFound };
Quote:
Original post by hplus0603
Ask yourself: How did the server know the client started firing? How come the server isn't 5 bullets behind when it's starting to count bullets?
Now, can you use the same solution to solve the end-of-firing problem?


Hey, I think I get it. If I'm using prediction and the game's current state is t0, the client isn't really showing t0, he's showing t1. The server won't find out about the fire message until t1, and when the client STOPS firing, the server's still creating bullets, but it's SUPPOSED to, because if the client's seeing t5 or so when he stops firing, the server will find out he stopped firing at roughly the real t5, so discrepencies will be much smaller than I thought. Does that sound right?


Thanks, hplus!
Advertisement
Still, though, now that I think about it, prediction requires that the server and client are thinking about a world that's t1-t0 different, which means lag is twice as much as it could be. To fix that would introduce a whole new world of computative pain, though, I suppose, where my bullet problem would be the least of the worries.
By the way: Our product at work is a massively-multiplayer scaled distributed simulation, training and gaming platform that has a lot of code to solve this problem, and make it easy for content developers to work in that world (i e, the system takes care of time management, lock-step data distribution, etc). It has cost many millions of dollars and many many man-years to develop. And we have patented some important discoveries made as part of this model :-) Solving the more complex problem you're describing, for real, isn't really doable by a single guy in a reasonable amount of time.

I would recommend the model where the player is sending data to the server, the server takes that as "now" (for everyone), and forwards to the other clients, who display the data when they get it, with position (but not event) interpolation ahead. The only interpolation will then be for position of players who are remote, for each player. Actual events should be arbited by the server.
enum Bool { True, False, FileNotFound };
You've been most helpful. Sounds like very interesting work, too. I just write office productivity software (which is why I need to work on games sometimes).

The one problem I see is that I really, really don't want a stream of bullets to need a full client->server->client step before they start appearing, even if they'll appear as if they'd been traveling, so it'd be great for the client to predict their creation and make some temporary ones. This doesn't seem too troublesome, but now I'm presented with an entirely seperate problem. The client will have created several bullets that the server also created, and it's possible (if there was a collision or something) that the client created items that won't get created at all.

Perhaps I could have a per-player sync'd variable for bullet ID numbers, so that if I get some bullets from the server, I'll know if I've created them locally, but that's got some issues, too. UnrealScript I think can handle this transparently; I wonder how they do it.
Okay, I had an idea for how to handle clients predicting the creation of objects. Basically, when an item spawns, another object is deemed its "parent." Each object knows its parent object, and it also knows the number of objects its parent has previously created. Then, when the server issues the client a spawn message, the client checks for an object with the same parent id and spawn number, and if they match, changes the client object's ID to match the incoming server object's ID. Next, if average latency and a half goes by without the object being officially created by the server, the object's destroyed unless it's marked as clientside only (like an explosion or something).

Does that sort of thing sound like an okay solution?

This topic is closed to new replies.

Advertisement