MADEAPPS / newton-dynamics

Newton Dynamics is an integrated solution for real time simulation of physics environments.
http://www.newtondynamics.com
Other
928 stars 183 forks source link

Use 2 sub-steps in all hello world demos. #292

Closed olitheolix closed 1 year ago

olitheolix commented 1 year ago

This PR increases the number of sub-steps in the hello world demos to 2 as recommended by @JulioJerez in #291

After looking over ndTest once more I noticed calls to world.Sync() in various places, eg after stepping the simulation but also before creating new bodies. What exactly does this do and, more importantly, when do end user need to worry about it?

JulioJerez commented 1 year ago

merged. Thanks

on this "I noticed calls to world.Sync() in various place" that's a good question.

The engine can be build single and multi-threaded.
the option is selectable in cmake. multithreaded in the default.

Single threaded, is good for debugging, and I think I had one or two people, who for some reason have an application that does not support multithreading, but that was long time ago in 3.14. if you select single thread, then function world.Sync() does nothing, and that's that.

It get's more interesting when using it multithread. In multithread builds the engine creates a thread to run the physics asynchronous. The thread function runs in a loop, waiting one a semaphore to execute one step per signal.

when the application call function world.Update(timestep); this function copy timestep to a class variable, signal the loop semaphore and returns immediately. when the thread wakes up, it executes the number of sub steps and all the housekeeping required to complete one step update and goes back to wait for another signal.

This simple naive loop runs the engine async one step at a time but is not safe or stable. the reason is that in the app is very fast, it will go to the begging of the loop before the physics update is completed. and it may make another call to world.Update(timestep) before the previous has completed.

So, to control that, there is the Sync semaphore, the semaphore is the opposite, it blocks the calling thread when the engine is in the executing one update step, and does nothing when the engine in the semaphore. This generates two different ways of operation: sync and async.

Sync is the simplest one, and it is just the way to set it up.

  for (ndInt32 i = 0; i < numSteps; i++)
  {
    world.Update(1.0f / 60.0f);
    world.Sync();
 // do application stuff
  }

This mode is just like any procedural application because even when world.Update(1.0f / 60.0f); returns immediate, world.Sync(); blocks the thread until the update completed.

when the physics thread completes the update, it signals the Sync semaphore and the app thread continues. The effect is that the engine does not runs concurrent with the main thread, because even when it runs on its own thread, the application thread and the engine thread run mutually exclusive.

The Async mode, is the more interesting and it is a loop like this

  for (ndInt32 i = 0; i < numSteps; i++)
  {
    world.Sync();
    world.Update(1.0f / 60.0f);
 // do application stuff
  }

here it syncs first. If the engine was in the wait semaphore them. then world.Sync() and world.Update(1.0f / 60.0f) both returns immediately, leaving the physics thread and all the children threads executing one frame update. Meant time the application thread can also be doing its own stuff concurrent with the physics.

Let us say that the application is very fast and finish its loop before the physics, in that case it will call world.Sync(); again, but since the physics update has not completed yet, the app thread will block until the physics complete the frame and signal the sync semaphore.
if it is the application that is slower, then the physics will signal Sync before the app call world.Sync(), so when the app calls world.Sync(); it just returns immediately. In both cases the slowest loop controls the speed of the application.

It is for this reason that you see, calls to world.Sync(); every time the physic word is modified. imagine that in the main loop some even happens. like body is dead, or the loop exit and want to initialize a new scene, stuff like that. All these things could potentially act of a scene while the engine is execution its frame.

So, it is just good practice to just call world.Sync() before any operation that interact with the physics scene outside of a scene call back. world.Sync() does nothing when the engine is not running a loop.

This is very much similar to the way Open GL glFlush() works. but openFL also has glFinish() which in my opinion is unnecessary. The newton world.Sync() does both glFlush() and glFinish() of openGL.

olitheolix commented 1 year ago

Pretty nifty feature...

Thank you for the detailed explanation. It helped a lot. I will try and incorporate this info into the comments in another PR.