box2c icon indicating copy to clipboard operation
box2c copied to clipboard

Support Client-Side Prediction

Open erincatto opened this issue 2 years ago • 5 comments

  • Determinism
  • Rollback
  • Contact Begin/End events

erincatto avatar May 02 '23 23:05 erincatto

A comment here from our use-case that we've been dealing with in various ways over the past few years... For reference, a project with Box2D Client-Side Prediction implemented here (Also added SDF collision fixtures to this as well): https://rocketbotroyale.winterpixel.io

But for client-side prediction to work, we need to arbitrarily rollback the world to some given state and re-simulate with step() X number of times applying some set of saved inputs per tick. Let's call this process, Rollback & Resim. This rolls the simulation back to some point in time, then fast-forwards back to the real time stream.

This actually works very well in Box2D with a few caveats...

  • The first is that since the engine uses linked lists, the order of the internal lists is important for determinism. So if you're rollback involves say removing and adding bodies to the simulation ( think rockets, missiles, etc ), then after the reset you need to ensure the internal b2 lists (b2body list for example), is in the same order as the server. We can work around this with sequence number on body creation and insertion into world. And a simple re-sort on the reset.

  • Second is that resetting the world via this mechanism usually means rebuilding the contact list. Before we reset anything, we ignore all contact listeners (Begin,Pre,Post,End), and disable any processing there. Then we reset/recreate all bodies and fixtures to known state, sort internal lists, then manually call FindNewContacts() to build the internal contact list. The idea here, is that after FindNewContacts() is called on the client's reset, the client's world state should be the same as the server after it's regular step function(). We've found this generally to be correct with the exception of Begin/End contacts state being contact state that crosses step boundaries. It would be ideal if you could remove this kind of inter state contact dependency as it makes the reset part much easier. Right now Box2D never will emit a BeginContact and EndContact in a single Step even after the solver finishes there is no AABB overlap, i.e. the contact doesn't exist. The EndContact callback is only emitted on the next Step call, which means there is leftover contact state between two world iterations. I think eliminating any type of inter step state would make it easier to reset simulation to arbitrary points in time, in which client-side prediction with rollback & resim becomes a lot easier to do.

jordo avatar May 04 '23 19:05 jordo

Just another comment as this scenario may be helpful... We run our cloud gameservers single threaded because we generally want our server load to be as non-volatile as possible. i.e. I'm not sure we would WANT to solve our physics simulations multithreaded there. For our use-case we schedule each gameserver a core (well less than a core in all honesty), but we definitely limit the cpu on each gameserver to 1vCPU.

So multi-threaded-solver... Very welcome on a client device, but i'm not sure I would want to run the solver on multiple threads on a gameserver. We'd want to pin the cpu through the simulation on a single core so we can balance load across a cloud VM with reasonable cpu scheduling assumptions for each gameserver process.

Would be great if when thinking about determinism and threading, if some different configurations were available.

jordo avatar May 04 '23 19:05 jordo

Will this be targeted for 3.1? Or is it more of an investigation task?

Ughuuu avatar Nov 01 '23 12:11 Ughuuu

I'd like to get this in for v3.1. However Rollback might be v3.2. It is a big feature.

erincatto avatar Nov 01 '23 15:11 erincatto

This has gotten me thinking again... Just a note for any potential rollback feature in b2c, but we recently shipped another game with client reset/rollback, however on this game we had a lot of objects that were stacked and there just wasn't a decent simulation that didn't kill our cpu without warm starting enabled.

So we have it enabled and eventually our client simulations just kinda even out, but this has gotten me thinking about warm starting in general and how it would apply to rollback/reset simulations as well as any other internal 'state' memory that the engine maintains across Step()s. I don't have anything to really contribute here other than recognizing that once you get into the internals of warm starting, determinism and rollback becomes seem very complicated when it involved any internal state shared across world ticks.

jordo avatar Mar 04 '24 17:03 jordo