I'm trying to get client-side prediction / reconciliation working for a client/server model. I have been using Scenekit for rendering the 3D world and handling physics calculations.
In order to get this working, I need the ability to force the physics simulation to "calculate" a given number of frames (apply forces, check for collisions, update node positions / orientations), without rendering these updates. This way, when I determine that the client has had a misprediction with what the server is sending back, I can re-orient all nodes in the physics simulation to match what the server sent (including setting physics properties like velocity / angular velocity), and then fast forward through the "events" that the server has not yet processed to get back to the client's current point in time.
I understand SceneKit does not use a deterministic physics engine, but I'm hoping I can get something that looks "good enough" (my misprediction system has a threshold for determining if a given prediction doesn't align with the server's).
The process looks roughly like this:
- Client runs physics simulation locally based on local inputs / current local simulation state
- The client generates it's own host packets each time the physics simulation runs, caching them for the current frame date.
- Client receives a host packet from server stating what the physics simulation should look like. This packet is based on a packet the client sent to the server in the past. The host packet contains an identifier (a date) that the client bundled with it's packet it sent to the server previously.
- The client looks up it's previous cached version of how it saw the physics world in the past for this date. If it differs too much from what the host has sent, we determine that we've had a misprediction and need to correct.
- The client re-orients all nodes in the scene to match what the host packet states (setting position, rotation, velocity, angular velocity, etc)
- The client then iterates over all of the packets it has sent to the server AFTER the host packet's date (client packets that the server has not processed yet as part of this host packet), and re-applies them to their corresponding nodes.
- The part I'm struggling with: Each time the client applies a set of historic packets to the nodes in part 6, I need to force the physics simulation to "process" these changes (apply the forces generated from these packets, check for collisions, update node positions) before moving on to the next set of packets that the server has not yet processed.
I have tried playing with the physics world's timeStep but it appears increasing this property also decreases the applied forces per physics "cycle" (we get more physics simulations, but the end result is just a more "accurate" simulation by moving physics bodies shorter distances before checking for collisions).
I have tried playing with the physics world's speed but a) increasing this value reduces the physics simulation's accuracy, b) for some reason scenes created in the scene editor have a default speed of 6 instead of 1 so determining the appropriate value here is a bit confusing, and c) it doesn't seem to have any affect until the next time the simulation attempts to run.
I have tried playing with the scene view's sceneTime, incrementing it by 1 each time I process a set of historical inputs, which I thought was working but upon closer investigation it seems this does nothing.
I have tried pausing the scene, applying my changes and then playing the scene, but pausing the scene pauses the physics simulation as well.
Is there any way to just have the SCNPhysicsWorld update it's physics simulation manually/repeatedly in a loop, without triggering any rendering calls?