dimforge / nphysics

2 and 3-dimensional rigid body physics engine for Rust.

Home Page:https://nphysics.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

nphysics-ecs: simplify integration of nphysics with an ECS.

sebcrozet opened this issue · comments

This issues provides a coarse description of a potential solution for simplifying the integration of nphysics into an ECS. Current solutions can be found on various small games: stacked-worlds and airjump-multi. They are mostly based on synchronizing components added to the ECS world with data stored by the physics world.

The solution discussed here is the creation of a new crate named nphysics-ecs that would define whatever is necessary to easily integrate nphysics into various popular ECS.

See also amethyst/amethyst#942 (comment)
Cc @Rhuagh and @ldesgoui who might be interested in contributing.

Systems: the stepping function

The most critical method of nphysics is the World::step method. This single method will not be good enough for an ECS since it assumes full control over the physics world. Instead, each major parts of the engine should be represented as separate systems. This implies at least:

For the moment, those systems cannot be updated in parallel as they all depend on data generated by the system preceding it on this list.
There are a few steps I omitted here that should be executed by one of the systems described so far, or by a new one:

Components: bodies, colliders, force generators, joint constraints

There can be several approach depending on how much granularity we want. At the coarsest level we could have:

  • A RigidBody component containing a RigidBody structure from nphysics.
  • A Collider component containing the description of a collider, i.e., its shape, transform, etc. See the CollisionObject struct from ncollide, and the specific data ColiderData nphysics uses.
  • A ForceGenerator component that would contain a struct implementing the ForceGenerator trait?
  • One or several components for gravity and integration parameters.

Currently, all the bodies are contained by a BodySet which is part of the world. We might want to abstract various parts of the API of nphysics itself to be able to retrieve bodies from an arbitrary data provider.

I think it is best to ignore multibodies for a first prototype of nphysics-ecs. Multibodies will probably be more difficult to handle and their design (including some magic happening when a multibody link is removed) will be strongly affected by the upcoming work on deformable bodies.

One of the open questions is whether all this will have an impact on ncollide. Should the ncollide world also be split up into components and systems? Or can we achieve our goal on nphysics without requiring creating a ncollide-ecs crate too?

How to proceed steps by steps

The development of the nphysics-ecs crate could start focusing on supporting specs which is quite popular and well maintained.
The different milestones for this ECS integration should be set by increasing order of complexity:

  1. Be able to have something able to simulate one rigid body that falls under gravity. This excludes collision detection, constraint solver, etc.
  2. Add support for forces generators.
  3. Add support for proxies (which are colliders without contact generation) and proximity detection.
  4. Add colliders, without constraints solver.
  5. Add support for the constraints solver, but without handling island computation nor body activation/sleeping.
  6. Add support for kinematic bodies.
  7. Add support for activation/sleeping.
  8. Add support for island computation.
  9. Add support for joint constraints

FWIW, as someone currently using nphysics with an ECS, I find the existing interface ideal since I don't have a 1-to-1 relationship between entities and bodies. Would be interested to see what impact this has on nphysics performance, though!

Sorry for going MIA, had a lot of day job things to take care. I think the proposal looks good!

@Ralith while that's true, I think syncing data between the Worlds also quickly becomes tedious.

I don't sync data between them and I'm not sure why I would. Entities that are associated with a body store that body's handle, and any system that wants to know details about the body looks them up in the usual way.

Hello,

I should be able to begin experimenting "soon", thanks for considering and organising this.

Would ncollide require a similar solution, as far as I'm aware, it also contains a World API

Thanks

ncollide does have a World, but it also allows for the steps to be called separately instead of all together through a World. As far as I can tell, nphysics expects its users to go through the World to get physics things done.

If nothing else, a proof of concept can be assembled for NCollide alone (no NPhysics behavior), just by assembling the building blocks of NCollide in specs in the same order as in the NCollide world.

Is there any idea on the performance impact this would have on nphysics (when using it with Specs, or Pyro, for example)? I don’t see it mentioned anywhere, and I understans that’s something that has to be benchmarked, but I think it makes sense to put performance as one of the top priorities for this integration, so that it can be taken into account – and measured – every step of the way.

What might be helpful for this would be to break out parts of the physics simulation functionality into their own procedures that can be called on disparate parts of the world, wherever they happen to be. ECS architectures generally are opinionated in preferring that data is laid out in the open in a particular place for systems to process them as they need to as opposed to being encapsulated with the methods that predominantly operate on them, which appears to be the architecture of the physics world in nphysics.

commented

Some users in Discord think that decoupling the simulation data from the application of mechanics (which is truly the root of this issue here) is not a good idea (namely @Ralith and @Andlon) so I figured I'd write on the issue tracker here what problems this solves beyond ✨ magical parallelism ✨ or any other performance gains/losses that simply can't be quantified without thoroughly developed implementation work.

A great case brought up by @ldesgoui on discord are the requirements necessary for history, which requires far more granular access to the data in the simulation than is currently possible. While one of the levels of granularity required may be fixed with the current design (field access), another is necessarily held back (ownership). Many problems are solved in the structure of handling that data when the primary owner of it is not the World where stepping occurs, but the program who calls the stepping function. Lending data to the simulation functions is far less problematic in designing the ownership through the program in whole (especially when that program actually has Send and/or Sync requirements) than borrowing the whole World about so that you can then seek for borrows on the data within it.

The first, and greatest, issue that came to mind for me was considering the Uniform Access Principle in the API design of nphysics in an ECS context. Not only does the solution advocated for by Ralith necessitate unnecessary copying in Entity Transform data, but it undermines the API of the ECS it's hoisted onto. More orthogonal implementations (implementing Dynamics information Components and synchronizing them alongside Transform data back and forth between ECS storage and the nphysics World) incur even greater costs. And considering the progress being made in ECS tooling for inspection of Entities and their associated Storages, among many other benefits brought by conserving UAP in this layer, the design of nphysics left as it is now would appear completely bizarre to the average ECS/Amethyst user.

What's more, is that I believe that the current World design could be preserved as a data storage implementation, such that the API for those seeking to continue to use nphysics the same way, would be able to.

I must admit I'm somewhat surprised by being tossed into this discussion after what I believed was idle chatting on Discord. I must say that I'm not particularly invested in this particular issue, and wasn't originally planning to say anything about it on here. That said, now that I've been singled out, I suppose I will need to clarify a few things.

Some users in Discord think that decoupling the simulation data from the application of mechanics (which is truly the root of this issue here) is not a good idea (namely @Ralith and @Andlon)

That is certainly not my position - or at least, not without significant qualifications. I'm also not opposed to this nphysics-ecs crate and the ideas proposed by @sebcrozet in this issue (I'll have some more comments on that later though). When the discussion started on Discord, I was simply curious about what was wrong with simply using nphysics and e.g. storing handles in components of an ECS, much like @Ralith alluded to earlier in this thread.

I'm confused about the real topic of the discussion going on here. On the one hand, I get the impression that @sebcrozet mainly proposes simply to create a layer on top of nphysics which would more easily facilitate integrating nphysics with various ECS architectures. Sure, makes sense.

On the other hand, I have the impression that parts of the discussion is also about modifying nphysics itself to use an ECS-like pattern, or at the very least exposing the way nphysics stores its data to the outside in order to support ECS architectures. This part worries me somewhat. Yes, it is true that doing so would allow you to avoid more copies in ECS-like scenarios, but it potentially comes at great cost, namely the exposure of the way nphysics internally organizes its data. Unless I misunderstand, this would significantly increase the surface area of the API of nphysics, which typically makes maintenance harder and making changes to the internals more difficult. Moreover, it would mean that one could not rearrange how data is stored to facilitate better performance for the solvers.

Another worry that I have, is the idea of trying to structure the physics engine itself in terms of "systems". My claim is that a general purpose physics simulator simply does not fit inside of a traditional ECS architecture. Let's take an example, such as ``force generation" (i.e. given a set of bodies, return the forces acting on these bodies). One might be tempted to make these force generators systems. However, consider e.g. a Runge-Kutta 4 time integrator. Here one would need to evaluate forces multiple times within a single time step, and so one cannot simply organize "force computation" and "time integration" as steps in a sequence of systems. Instead, the "time integration" would need to call the "force computation" at selected points in time. Another example is the fully implicit time integration of finite element soft bodies. In this case, one would need to evaluate forces at every step of a loop of a non-linear system solver.

What both of these two examples have in common, is that they exhibit something that tends to come up a lot when designing physics simulators, namely that ``systems" call other systems. Obviously, this breaks with the traditional ECS architecture in which systems are called in a linear sequence.

I'll finish this part by saying that there may very well be many modifications to nphysics that would make it integrate more easily into various architectures, and I'm all in favor of that. I am only cautioning against going too far in trying to accommodate ECS architectures.

A great case brought up by @ldesgoui on discord are the requirements necessary for history, which requires far more granular access to the data in the simulation than is currently possible

Can you clarify? What is missing? Is he not able to simply copy out the data he needs after every time steps, thus recording a history of past events? (arguably any recording of historical values is beyond the responsibilities of a physics engine, and would always require copying data).

Many problems are solved in the structure of handling that data when the primary owner of it is not the World where stepping occurs, but the program who calls the stepping function.

Can you support this claim? What specific otherwise-difficult problems are solved?

Not only does the solution advocated for by Ralith necessitate unnecessary copying in Entity Transform data, but it undermines the API of the ECS it's hoisted onto.

I'm not trying to advocate any particular design here, but this is false. There is no need to copy significant quantities of data to/from an nphysics World in the current API. If you want to know the current transform of a rigid body, you look it up when in the World when needed.

among many other benefits brought by conserving UAP in this layer, the design of nphysics left as it is now would appear completely bizarre to the average ECS/Amethyst user.

I don't think nphysics' current API is bizarre. It looks like the uniform access principle is about providing an opaque interface that doesn't expose whether a value was cached or computed, which I don't think the current API is in violation of, either--though I'd think exposing internal state for storage in ECS columns might constitute a violation. Can you provide some specific examples of the many benefits you allude to?

commented

@Andlon

I must admit I'm somewhat surprised by being tossed into this discussion after what I believed was idle chatting on Discord.

My honest apologies! These things can be time consuming. I will respond to your message below for posterity but don't find it necessary to respond to this message if you don't find the time or interest to do so.

On the other hand, I have the impression that parts of the discussion is also about modifying nphysics itself to use an ECS-like pattern, or at the very least exposing the way nphysics stores its data to the outside in order to support ECS architectures.

To be clear I only personally hope for the latter. I don't think there are plans on forcing users to use an ECS with nphysics :P

Obviously, this breaks with the traditional ECS architecture in which systems are called in a linear sequence.

I'm not sure how "traditional" specs is but this is definitely not the case for how its default dispatcher operates. Also, hopefully, the upcoming nitric data processing crate should solve some of the dispatch issues here.

Inversion of control within systems using trait-based type parameters (my proposed solution for something inline with the integrator's normal operation), imo is not so much a "violation" of ECS but your mileage may vary on that. Other languages don't necessarily have the features to make that possible without a nightmarish or confusing API, while for Rust it is obvious to the user, and the state of the program, along with how that state is stored and accessed, is still reaping the UAP and performance benefits of ECS (although I honestly don't think it's possible to parallelize the physics system in ECS itself, Seb mentioned above the issues in that each calculation relies on the results of the previous, in the methods he considered splitting off)

Can you clarify? What is missing? Is he not able to simply copy out the data he needs after every time steps, thus recording a history of past events? (arguably any recording of historical values is beyond the responsibilities of a physics engine, and would always require copying data).

Yes, History is always going to requiring copying. This does not mean that reducing the number of copies performed isn't beneficial.


@Ralith

Can you support this claim? What specific otherwise-difficult problems are solved?

A great example is the case of a generic-based trait implementation of force generators over the type parameters of the physics system. It is impossible to execute such a design because of the ownership of the data.

I'm not trying to advocate any particular design here, but this is false. There is no need to copy significant quantities of data to/from an nphysics World in the current API. If you want to know the current transform of a rigid body, you look it up when in the World when needed.

There is no point in using an ECS if you're just going to ignore the Entity+Component part and leave it just for handle lookups. Most average implementations at the very least utilize a Transform component to aid in rendering. The reason why it is implemented as so is to make iterating over the storages cache efficient. Actually making effort to expose at the very least Transform information for users who probably are already using a Transform, who probably already have systems that interact with that information, will cause unnecessary copies. Which brings me to the case of UAP, which you completely misunderstand.

UAP is about the uniformity of accessing information in an API. What this means, is that data, regardless of kind, may be accessed through the same notation as all other data within that api. When using nphysics in an ECS as you suggest, there is literally no uniformity, which is why I suggested it would appear bizarre to many users to do what you suggest. I assume I need to address why having a uniform API would be beneficial in the first place.

You'll find that discussion about UAP on places such as wikiwikiweb focus on the uniformity of notation within programming languages. However, we are not considering changing Rust, we are discussing the API of stateful libraries (nphysics and things that otherwise fit into specs in a way that is standard for the architecture). So we must consider the notation here as the API through which information is accessed and mutated. For many specs libraries, what you must do is create a Storage for that information, add the type for that information to the Data of whatever System you want to access or mutate it, and then operate on the received Storage, which should be plain data in almost all cases, that contains all relevant state for that Storage.

In the nphysics implementation within specs that you suggest, you must do all of this, obviously, storing the whole nphysics world within a Resource, instead of placing the individual properties as Storages, and instead creating a Storage with handles to the nphysics World Resource, where the state of the program is essentially hidden, causing users to have to iterate over the Storage of Handles, using nphysic's specific api's for dereferencing handles in a World, dereferencing piecemeal over each handle in the Storage with your World Resource in hand, any time you wish to operate on the data within nphysics through your Systems.

And this is just to access and mutate the data within a system, involving any sort of Storage.

I don't think nphysics' current API is bizarre

Neither do I. But doing the above would appear bizarre to any average user coming to use ECS with nphysics. Just the act of accessing and mutating data is completely irregular from any normally Storage-based api.

Can you provide some specific examples of the many benefits you allude to?

  • Uniform drop-in serialization for networking
  • Uniform interface for handling lag correction and synchronization
  • Compatibility with inspection tools for Entities and their Storages
  • Drop-in Save and Load functionality
  • Better discoverability for ECS users (UAP)
  • Improved learning curve for ECS users (UAP)
  • Only having to maintain, for the most part in the ECS case, the api of data used in the simulation, along with the obvious simulation method and integration parameters

Apologies for any typos or poor articulations, this message ended up quite long.