Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Questions about migration to PlayRho! #217

Closed
ninnghazad opened this issue Nov 23, 2017 · 10 comments
Closed

Questions about migration to PlayRho! #217

ninnghazad opened this issue Nov 23, 2017 · 10 comments
Assignees
Labels

Comments

@ninnghazad
Copy link
Contributor

Some questions i have about the project (esp. in relation to Box2D) that @louis-langholtz or someone else might just know the answer to, in order to spare me writing tests and possibly pointless reintegrations.

  • How does the performance of the 32/64/fixed32/fixed64 version compare to it's counterparts? Is using the fixed-point implementation on worlds containing roughly (low)hundreds of bodies at all feasible?

  • How do the float-types impact maximum scale ratios between bodies in this engine? Can this get me beyond Box2D's 0.1m - 10m rule for bodys' sizes and maybe even beyond 0.1m - ~1000m?

  • Apart from namespacing and renaming - what are the most obvious pitfalls one might encounter migrating from Box2D to PlayRho in an existing project?

  • Are there any plans to implement liquidfun's additions to Box2D in PlayRho? (ParticleSystems)

  • I read about 3D in the issues - will this project move away from a pure 2D engine? and if so will the API for the 2D usage as well as the 2D performance of the engine change/suffer due to that change?

My hopes are that i can more or less drop in PlayRho as a replacement for Box2D and get a similar performance but stronger determinism (syncing Box2D over network is... tedious to say the least) on a larger range of scale.

@louis-langholtz
Copy link
Owner

louis-langholtz commented Nov 23, 2017

@ninnghazad Thank you for the questions. I love them and find them helpful too! I will have to get back to you on these but here's a response for what I can tell you right now...

  1. I'm not certain how the performance of the fixed precision types compares to fundamental floating point types. The fixed precision types as I've implemented them seem considerably slower though. I strongly suspect that their implementations could be improved and admit I was focusing more on their semantics than their performance. I will look into a concept like description for Real to identify whatever requirements any alternate types would need to have to satisfy the uses. I'll also look into adding benchmarks for the Fixed types into the Benchmark application.
  2. Since PlayRho's settings are established at run-time, it has more run-time flexibility to deal with the scale ratio rule you mention. I've been looking into this question in terms of how to adapt the settings to deal with different scales. I haven't gotten to the point yet that I want to though in terms of making scaling much easier for the general user. PlayRho does have a Solar System Testbed demo in it now for instance that's to scale — done in terms of yotta-grams and giga-meters etc.. It's setup to allow any velocity expressible by the Real used. I haven't tuned all of the step configuration parameters yet to handle collisions properly and so far have seen some calculations overflow to infinity where Real = float (but not when Real is set to double).
  3. As to biggest pitfalls in a migration from Box2D to PlayRho besides renaming, I'm not clear. I think that depends in part on the human experience of it which will of course vary from person to person. My personal experience with this has been with migrating Testbed tests from Box2D as well as some written by iforce2d. I've tried to be sensitive to pitfalls and mitigate them with changes or additions to the PlayRho code. An overall design goal I have for PlayRho is to make it easier to use correctly, and harder to use incorrectly. So I'm very interested in feedback in this regard.
  4. I am interested in pulling liquidfun into PlayRho and have been looking into this.
  5. As for 3D, my thinking right now is on having 3D simulations separate from 2D ones with the current World class becoming the 2D world class and having a separate 3D world class for 3D simulations. I'd like to minimize name changes needed for this so that the names for 2D PlayRho classes won't have to change after beta release. I've also been working to make this easier with more use of generic code (via templates) for instance for things like axis aligned bounding boxes and vectors and anything else like that. With 2D simulations being kept separate from 3D sims, performance should not suffer for 2D simulations.

I will update this issue as I work on addressing these questions further. Adding a migration guide for instance is something I want to do as is adding engine-engine comparisons so people can more easily recognize what the pros and cons of various engines are.

@louis-langholtz
Copy link
Owner

louis-langholtz commented Nov 25, 2017

I've opened the following issues related to this issue:

I've also created the following projects that are related to this issue:

  • Derive Concept For Real project to address what Real is and/or must be in terms of alternative implementations. This will also help with recognizing what the engines numerical limitations are in terms of what the implementation of Real is and I've begun to push changes for this.
  • Increase Dimensional Scalability to make it easier/simpler to go beyond Box2D tuned ranges.

@ninnghazad
Copy link
Contributor Author

Thanks for the answers!

louis-langholtz added a commit that referenced this issue Nov 28, 2017
Adds configurability to solar system demo for much larger bomb sizes and densities.
louis-langholtz added a commit that referenced this issue Nov 28, 2017
@louis-langholtz
Copy link
Owner

louis-langholtz commented Nov 28, 2017

Just a fun update for me on this... With my latest commit, I believe I've gotten a World working with collision handling (not just movement) for objects that are literally at our solar system's scales. So that's bodies like the Sun that are: 696342000 meters in radius using a DiskShape (the circle shape) with a 696342_km sized radius, and 1988550000.0 x 10^21 kilograms in mass. Kind of excites me comparing that to Box2D's scale of up to 100 meters in size!

To achieve this simulation, the code uses a StepConf per-step configuration with an infinite maximum per-step translation, a delta time initialized to 24 * 3.6e3 seconds, and a linear slop of 200_km! I've opened an issue (issue #225) to devise a function for calculating a StepConf from a World in the hopes that this could make determining those sorts of values easier for the average user. I also built the system with Real set to use double to avoid overflows in cross product calculations and probably other places would have overflowed otherwise too. But it's doable and evidence is out there now in the Solar System Testbed test and in unit test code.

I'm pretty certain that more work needs to be done still for these huge scales but what's available now feels very promising to me.

@ninnghazad
Copy link
Contributor Author

ninnghazad commented Nov 29, 2017

Mhm i like the sound of that. I am eager to see how i can utilize the new possibilities PlayRho seems to offer.
However i am still in migration. While updating debug-rendering for physics-objects i wondered about the rational behind not having a ShapeType anymore. I see there "still" is BodyType and JointType though. I am currently replacing the old "what kind of shape is this so i know how to render it"-part to use dynamic_casts to RTTI me some info on derived. that should work, but well, feels weird. Can you drop a few lines on the thinking behind the difference here?
EDIT: feels weird because my experience is that RTTI cast-to-derived per dynamic_cast is quite a bit slower than thisisoftype-member with static casts.

@louis-langholtz
Copy link
Owner

louis-langholtz commented Nov 29, 2017

@ninnghazad

i wondered about the rational behind not having a ShapeType anymore.

My thinking was to move away from b2Shape::Type and toward a visitor pattern alternative. So playrho::Shape::Accept(ShapeVisitor& visitor) is provided and is what I meant to get used instead of switching based off of a GetType() like method. If you need to do some actions that are specific to the shape subclass, sub-classing playrho::ShapeVisitor and implementing the shape subclass specific code in the Visit method for that shape subclass is the way I meant for things to get done. I would discourage using the RTTI cast-to-derived for the reason you've mentioned and encourage instead the use of sub-classing the visitor base class.

Looking at the sources just now, I see there's not as much documentation/explanation of this as I'd like and I apologize about that.

Admittedly I'm not entirely happy with the current visitor pattern solution either. I like it better than using enumerable types but it does bother me that adding a new shape subclass will still require every switching point to get updated to recognize the new subclass.

A drawback to this pattern, however, is that it makes extensions to the class hierarchy more difficult, as new classes typically require a new visit method to be added to each visitor.

I had looked into using a more dynamic dispatch mechanism like a CRTP augmented solution but was finding myself spending more time on that then it seemed worth. I wonder if a runtime polymorphic solution (like what's described in Sean Parent's "Runtime Polymorphism" talk or as shown in Dyno) could be devised that would be an even better way to mitigate this extension problem.

louis-langholtz added a commit that referenced this issue Nov 29, 2017
- Adds documentation comments regarding use of visitor pattern.
- Updates documentation comments to more thoroughly explain what types can be used for the Real type alias.
louis-langholtz added a commit that referenced this issue Nov 30, 2017
- Adds lambda accepting visitor implementations for JointVisitor and ShapeVisitor interfaces as perhaps a more convenient alternative for visiting joints and/or shapes.
@louis-langholtz
Copy link
Owner

My last merged commit provides two visitor classes that can be directly instantiated and use lambdas for visiting: FunctionalJointVisitor and FunctionalShapeVisitor. I'm hoping they can be an attractive alternative to users sub-classing the pure virtual base visitor classes.

Here's an example of the use of FunctionalJointVisitor from the Testbed application:

template <class T>
inline void ForAll(World& world, const std::function<void(T& e)>& action);

template <>
inline void ForAll(World& world, const std::function<void(RevoluteJoint& e)>& action)
{
    auto visitor = FunctionalJointVisitor{}.Use(action);
    const auto range = world.GetJoints();
    std::for_each(std::begin(range), std::end(range), [&](Joint* j) {
        j->Accept(visitor);
    });
}

void Tumbler()
{
    ...
    ForAll<RevoluteJoint>(m_world, [=](RevoluteJoint& j) { IncMotorSpeed(j, +MotorInc); });
    ...
}

Please let me know what you think of these additions.

@ninnghazad
Copy link
Contributor Author

I for one like lambdas. The syntax in this case i had to take multiple looks at to see whats going on. What strikes me as odd is the for_each: what i see is we get all joints from world into "range", and then for each joint in "range" we pass our lamba to the joint's accept. the lambda and template are typed towards the joint's actual type. now wouldn't this mean that if we had lots of joints of every actual type, each per-type invocation of ForAll would have to loop over all joints, and would try to pass non-fitting visitor to all joints that are not of its type?
the way i have understood the "old" way would be that i loop over the joints/shapes once, and accept "picks" the right typed function from my derived visitor.
i haven't tried but would guess if i passed lambdas for every possible type of joint to a ForAll which then builds a Functional*Visitor using all of then it would save me the additional loops...
hm, maybe i just didn't understand this well enough. anyway - would be cool to have a nice ForAll as a free function included with the lib. so i might do like this:
playrho::ForAllJoints(myWorld, [=](SomeJoint & j){j.doSomeThing(x);}, [=](SomeOtherJoint & j){j.someOtherThing(y);}, .... );
with the .... being a variable amount of N>0 lambdas for *Types i want to have visited by my lambdas.

@louis-langholtz
Copy link
Owner

louis-langholtz commented Jan 18, 2018

Updated details regarding visiting shapes and my thoughts on that...

Pull request #279, changed the initializing construction of Shape instances to being explicit. It's often recommended for single argument constructors to be explicit and this change just heeds that advice though syntactically it created many changes. Perhaps more importantly, this PR demonstrates what should be a more efficient means of visiting on-use polymorphic classes (like Shape has become) than say using the RTTI system and a series of if-else branches (like Testbed/Framework/Test.cpp had been doing).

The way I'm thinking of visitation now is as application defined visitation.

The PlayRho library code should explicitly list the methods of the concept that it uses itself and leave any other access up to the user to setup using the underlying visit mechanism. A user of the PlayRho library achieves that by specializing the playrho::Visit function template for the configuration classes that on-use polymorphic types get constructed by.

With this architecture, it's now possible for a user to devise their own shape configurations and specialize playrho::Visit to handle visiting shapes constructed using those configurations. Inside their template <> playrho::Visit(const UserShapeConf& sc, void* userData) {} function, users can do whatever they want, where UserShapeConf represents their user defined shape configuration, and userData represents any pointer type of data passed in on their call to the shape visiting function (playrho::d2::Visit(shape, &data)). With this design, the user can decide to pass no data at all, or to pass an address that their code can use as they want. Like perhaps to a struct which has a std::function for each of the shape configuration classes the user wants to support visitation for. In the case of the Testbed, I chose to use the passed in data directly in code I put into the template specialization (rather than deferring execution another layer). The unit test code also demonstrates this new visitation design as demonstrated in the UnitTests/UnitTests.hpp file.

Hope this write-up helps with making sense of visiting shapes as it now stands. I'm pretty excited about how this fits together and what it means for the future like for the Reducing Use of Pointers project and the Unifying Contact & Joint Classes project.

@louis-langholtz
Copy link
Owner

I believe this question has been addressed or separated off to other issues so am closing this issue now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants