schteppe / p2.js

JavaScript 2D physics library
Other
2.64k stars 329 forks source link

Determinism of the system #155

Closed sowee15 closed 9 years ago

sowee15 commented 9 years ago

Hi,

I am using p2.js version v0.6.1 for a multiplayer game which runs in node.js on the server and in a browser on the client. I am running a physic simulation on the server and on the client.

I was trying to see the extent of the differences of running the "same" simulation on the server and on the client. I setup identical worlds (as far as I can tell) and I apply the same force to a circular body that bounces around on planes (limits of the world) and on other circular bodies inside the planes. On collisions, sometimes server and client are getting the exact same results. Some other times, the angle of the collisions varies roughly by as much as approximately 15 degrees (I didn't measure it, it is a visual approximation). I thought the differences would be smaller than that so that I would be able to make small corrections on the clients that would go unnoticed. That is not the case.

Is is expected behavior or am I doing something wrong (or my worlds are probably not as identical as I think - apart from the environment in which javascript is running that is)?

Thanks a lot for your time.

schteppe commented 9 years ago

p2.js should be fully deterministic, and work equally in different javascript VMs. (There are small differences, for example precision in the trigonometric functions, but it should not be noticeable).

When do you apply the force? If you do it in the fixed step loop (in the postStep event handler for example), you should be fine. If you do it in the render loop, or setInterval, then you might get a timing mismatch for your forces. Also, if your client and server don't apply the exact same force at the exact same time step you'll not get the exact same result. How do you solve this? Do you keep a history of the world state, rewind, and apply forces from back in time?

I would love to see a working networked physics example for p2 :D

Thanks for using the lib!

sowee15 commented 9 years ago

Then it's me doing something wrong, this is very good news ;)

I am rewinding by updating the client's state to match the server's state at a certain time if my position at that time did not match the server's position, and then fast forwarding to where I was, but I am not ensuring that the forces are applied at the same time step. And although I am correcting, not applying the force at the same timestep could cause too big corrections, hence the big jumps I get. I'll have a look into that.

Thanks for your input, when I have put something together that makes sense, I'll let you know.

Thanks for making the lib, it is great!

On Thu, Apr 23, 2015 at 10:34 AM, Stefan Hedman notifications@github.com wrote:

p2.js should be fully deterministic, and work equally in different javascript VMs. (There are small differences, for example precision in the trigonometric functions, but it should not be noticeable).

When do you apply the force? If you do it in the fixed step loop (in the postStep event handler for example), you should be fine. If you do it in the render loop, or setInterval, then you might get a timing mismatch for your forces. Also, if your client and server don't apply the exact same force at the exact same time step you'll not get the exact same result. How do you solve this? Do you keep a history of the world state, rewind, and apply forces from back in time?

I would love to see a working networked physics example for p2 :D

Thanks for using the lib!

— Reply to this email directly or view it on GitHub https://github.com/schteppe/p2.js/issues/155#issuecomment-95607121.

sowee15 commented 9 years ago

Hi Stefan,

I simplified my simulation so that the physic engine only starts (the setInterval) when I receive the force to apply (I store the force, set the interval and in the postStep event I apply the force, so at step 0, there are still no forces involved). In the post step I was also applying custom ground and air friction - it is a top down game) but I removed that to be sure it was not the cause of my problem, I just replaced it with setting a higher damping. Roughly I have a body with a circle shape being sent to collide with a plane shape. Both shapes have a different material and I created a contact material to define the collision between the two. I fixed the angle and magnitude and compute the force vector using cos and sin (on client and server, I know this could cause slight difference but unnoticeable like you said). As far as I can tell, the client and server environment are identical.

Yet, what I observe is that the ball being sent on the client seems to be bouncing less on the plane than on the server, and it does so with a wider angle. I attached a beginContact event handler to inspect all data when both bodies collide. I inspected bodyA, bodyB and contactEquation on the event: everything is the same on server and client, positions, forces, bounding radiuses, aabbs, velocities, etc. Yet the resulting collision is different. Any idea what I should be looking for?

Thanks a lot.

PS. I also tried with and without ccdSpeedThreshold. PPS. In both environment I set world.solver.iterations = 20 and world.applyGravity = false.

On Thu, Apr 23, 2015 at 12:07 PM, Alexandre Soares asoares1@gmail.com wrote:

Then it's me doing something wrong, this is very good news ;)

I am rewinding by updating the client's state to match the server's state at a certain time if my position at that time did not match the server's position, and then fast forwarding to where I was, but I am not ensuring that the forces are applied at the same time step. And although I am correcting, not applying the force at the same timestep could cause too big corrections, hence the big jumps I get. I'll have a look into that.

Thanks for your input, when I have put something together that makes sense, I'll let you know.

Thanks for making the lib, it is great!

On Thu, Apr 23, 2015 at 10:34 AM, Stefan Hedman notifications@github.com wrote:

p2.js should be fully deterministic, and work equally in different javascript VMs. (There are small differences, for example precision in the trigonometric functions, but it should not be noticeable).

When do you apply the force? If you do it in the fixed step loop (in the postStep event handler for example), you should be fine. If you do it in the render loop, or setInterval, then you might get a timing mismatch for your forces. Also, if your client and server don't apply the exact same force at the exact same time step you'll not get the exact same result. How do you solve this? Do you keep a history of the world state, rewind, and apply forces from back in time?

I would love to see a working networked physics example for p2 :D

Thanks for using the lib!

— Reply to this email directly or view it on GitHub https://github.com/schteppe/p2.js/issues/155#issuecomment-95607121.

schteppe commented 9 years ago

Very difficult to guess from here.. Maybe compare the ContactEquation objects on client/server via world.narrowphase.contactEquations? Make sure no properties differ.

sowee15 commented 9 years ago

I'll try to compare everything in the postBroadphase, endContact and impact events and see if I can spot a difference in the values. Is there an event for a solver iteration (to make sure both the server and client are doing the same number of iterations in the narrowphase?)

schteppe commented 9 years ago

Not really. But you can check world.solver.usedIterations in the postStep event to check the number of iteration. Alternatively, just set solver.tolerance=0 and you'll always max out the iterations. Not performant, but you'll assure the number of iterations during testing.

sowee15 commented 9 years ago

Thanks for your directions, I'll let you know when I find out what's causing the difference.

On Tue, Apr 28, 2015 at 9:50 AM, Stefan Hedman notifications@github.com wrote:

Not really. But you can check world.solver.usedIterations in the postStep event to check the number of iteration. Alternatively, just set solver.tolerance=0 and you'll always max out the iterations. Not performant, but you'll assure the number of iterations during testing.

— Reply to this email directly or view it on GitHub https://github.com/schteppe/p2.js/issues/155#issuecomment-97070604.

sowee15 commented 9 years ago

Finally found it by logging all data in the impact event and comparing it between client and server. I noticed some shapes did not have their material set up on the client... I double checked many times that both server and client were setting up the ContactMaterials with the same settings, but I never checked that I was actually setting up the materials on the shape...(facepalm). Once I fixed it, it worked like a charm. Thanks for your support!

schteppe commented 9 years ago

Awesome!

wighawag commented 6 years ago

Hi, I don't want to revive the issue but I would like to make sure p2 is deterministic. You say there could be variation with trigo function. Could these not add up to result in non-determinsitic behavior?

I experimented such drifting myself in my own game in the past (not using p2.js) and I solved it by applying rounding at the correct place so the floating calculation remain the same on every machine.

Is p2 doing something similar ? If not what make you sure p2 is deterministic ?

Thanks

By the way, what is the best place to ask question about p2.js. Is it through github issue like this one or is there an active community somewhere ?

schteppe commented 6 years ago

@wighawag Since p2 doesn’t rely on system timers or random number generators, a given input will yield the same output.

p2 relies on the built in math functions like Math.sin. p2 is not trying to fix them. If you don’t trust these functions, it’s up to you to fix them. You could patch them if you like: Math.sin=function(x){ ... };. I don’t have any proof that the trig functions behave differently on different browsers so maybe you should start there, check how bad this issue is and which browsers it concerns.

I’m kind of surprised about this discussion. Usually when doing networked physics simulated games, the assumption is that every client is slightly out of sync - when it’s too much out of sync it has to be corrected. In this approach, the precision of a math function will not matter much, it will just impact the amount of correction that needs to be applied. See this talk about Rocket League network code: https://youtu.be/ueEmiDM94IE

Yes, I’d say the best p2.js forum is right here.

wighawag commented 6 years ago

Thanks for the answer.

I did not figure out what was the exact cause of the calculation drifting in my past game. Might well have been trig functions but it could simply be some difference in float calculation.

Rocket league is indeed using an authoritative server that send correction regularly.

But for some game, it is better to have the client perform the full game physics deterministically with the only thing sent from server being inputs from other players (no correction need to be send ever). This allow the state to scale. You can have a gigantic state with milions of entities without having to send their data/delta over the wire except at player joining time. The trade off is usually to set a fixed input delay for all client, like 200ms so when a player does an action, its action is only performed 200ms later when server acknowledge it. This works for some game, like strategy game where input does not need to be instant.

While I am not sure my game would need this, I want to explore my possibilities.

If the system is fully deterministic and the client game state will be the exact same as the server. In that case minor difference in floating point calculation could be fatal though.

I understand the p2.js is logically deterministic but since I have seen few claim that different javascript implementation or even the same implementation running on different architecture might have floating point calculation differences due to the order in which operation can be performed, I was wondering if p2 were taking some precaution to keep determinism even in these cases.

How hard would it be to use integer instead of float for p2.js ? javascript having 53 usable bit for integers, we could have enough precision still.

schteppe commented 6 years ago

Thanks for the clarification!

It’s probably a lot of work to use fixed point math. JS doesn’t have operator overloading and other language features required to just switch without changing all of the code. Not to talk about the performance impact it would have.

Even if it was possible, I don’t know if the simulation quality would be stable enough using reduced precision. I’ve tried switching from float32 to float16 once in another rigid body simulation project, and didn’t consider it good enough.

Are there libraries for fixed point math in JS? If this really is a problem, I’d expect a few libraries to pop up..

wighawag commented 6 years ago

The thing is that with js integer we have actually 53 bit precision so we should get better precision than float32. So we might just get away by scaling every number up by a factor of 10^8 or even more (depending on the world size, max speed...) and round them when necessary (division). This should not impact performance much if at all.

I might be completely wrong though :) I should have a deeper look at p2.js myself and see if it would be an achievable task.