box2d icon indicating copy to clipboard operation
box2d copied to clipboard

Add callback for body activation event?

Open juj opened this issue 6 years ago • 2 comments

It seems that engines using Box2D have a structure where they loop through bodies after each simulation step to propagate the updated simulation positions back to the engine renderer, scene manager, networking, etc. to access:

b2World world(gravity);
...
world.Step(timeStep, velocityIterations, positionIterations);
b2Body *b = world.GetBodyList();
while(b) {
   Sprite *s = (Sprite*)(b->GetUserData());
   // hypothetical actions to take on moved objects:
   renderer.setSpritePosition(s, b->GetPosition(), b->GetAngle()); // perhaps lightweight
   scene.updateSpriteParentChildHierarchy(s, b->GetPosition(), b->GetAngle()); // a bit more costly
   network.replicate(s, b->GetPosition(), b->GetAngle()); // heavy-weight(!)

   b = b->GetNext();
}

This kind of code structure feels nonideal because this loop will also iterate over sleeping bodies. A micro-optimization is then to track which bodies actually changed position:

b2World world(gravity);
std::unordered_map<Sprite, std::pair<b2Vec2, float> > prevPositions;
...
world.Step(timeStep, velocityIterations, positionIterations);
b2Body *b = world.GetBodyList();
while(b) {
   Sprite *s = (Sprite*)(b->GetUserData());
   if (prevPositions[s] != std::make_pair(b->GetPosition(), b->GetAngle()) {
      renderer.setSpritePosition(s, b->GetPosition(), b->GetAngle());
      scene.updateSpriteParentChildHierarchy(s, b->GetPosition(), b->GetAngle());
      network.replicate(s, b->GetPosition(), b->GetAngle());
      prevPositions[s] = std::make_pair(b->GetPosition(), b->GetAngle());
   }

   b = b->GetNext();
}

This is better to avoid redundant engine operations, but still has annoying bookkeeping, especially on float != float comparisons, plus the iteration still goes over bodies that are sleeping, which may be a large fraction of all bodies in the simulation.

Is there anything better that could be done here? It seems that since Box2D already manages sleeping vs active bodies, that latching on this information should be "free" optimization, even if the above redundant iteration would not take up that much of time - on mobile devices every single low hanging CPU cycle is desirable to make achieved battery time longer.

One idea would be to provide callbacks back to caller for body activation. Then one could optimize with the following:

std::set<b2Body*> activeBodies;
b2World world(gravity);
world.setBodyActivationCallback([](b2Body *body, void *b2WorldUserData) {
   activeBodies.insert(body);
});

world.Step(timeStep, velocityIterations, positionIterations);
for(auto it = activeBodies.begin(); it != activeBodies.end();) {
   Sprite *s = (Sprite*)(b->GetUserData());
   scene.updateSpriteParentChildHierarchy(s, b->GetPosition(), b->GetAngle());
   network.replicate(s, b->GetPosition(), b->GetAngle());
   renderer.setSpritePosition(s, b->GetPosition(), b->GetAngle());

   if (!b->IsAwake()) it = activeBodies.erase(it);
   else ++it;
}

This way Box2D would only have to add one stateless callback back to the user whenever a body is woken up, and not have to add any more bookkeeping e.g. in the form of active body linked lists or similar, and users can manage the level of bookkeeping they want to make on active bodies.

Would this kind of feature make sense? Or does there already exist something like this in Box2D? (sorry if I missed something important here!)

juj avatar Apr 12 '18 18:04 juj

Please write the different versions, compile them with the highest level of optimization enabled (should be like O3), run timed tests of a variety of different simulations, and report the results for a few different platforms! Thanks!

louis-langholtz avatar Apr 13 '18 01:04 louis-langholtz

I second this feature request, but would prefer a sleep state callback -- you would get notified on both falling asleep and waking up.

elemel avatar Apr 13 '18 07:04 elemel