How to interface with renderer/physics?

I’ve been looking into various ECS’s and something that I still don’t quite understand is how to tie the ECS to a separate renderer or physics engine, without having it all bound to the ECS itself.

For example - if components hold raw data like an array of floats for position, but a rendering engine has the notion of a GameObject.Transform, how to sync the ECS with the transform at the end of each tick so that it will be rendered correctly?

If the ECS owns the rendering pipeline, I suppose this isn’t a problem (though dealing with “dirty” updates and optimization could be tricky) - though I guess that’s really a different topic.

The same issue can happen from the other side, if there’s a physics that owns some RigidBody.Transform, it’d need to sync its position to the ECS Position components, based on… some shared entity id?

I see there is a three.js example here but I couldn’t quite follow what’s happening at a high level. Insight into that as a point of reference would be great.

Thanks!

Kind of a related question. My game engine has a fixed time step update function for physics and a seperate variable rate render function. From the docs I can see that the usual way to run a tick through the ECSY’s systems is to call World.execute(dt, time) but that doesn’t seem compatible with the way my engine works. Some of the systems need to be run in the update function and some in the render function. Is there a preferred way to deal with this?

@dakom

I’ve been thinking about this issue myself. (Note I’m not a game developer so take this with a grain of salt). My current solution is to use System State Components. You can then ‘save’ the physicsBody into the component (like they do with the mesh in the example). This is really handy as you can clean up the resource once you’re done (remove it from the world in the case of the physics engine, or detach and dispose of a node in the case of web audio). The Unity docs have a bit of info on these too.

Another possible ‘solution’ is to use a WeakMap to allow you to lookup an object (web audio node, three object3d, a cannon.js body, etc.) keyed by either the entity or the component (I’m still working on this bit to see what effect object pooling has on lookups in WeakMaps). This could look something like the following (note some of this is psudo code, you’ll need to fill it in based on libraries you use and your own style, etc):

class CollidableSystem extends System {
  constructor(world, attributes) {
    super(world, attributes);
    // you can either build the world outside the system 
    // (if you have multiple systems working on the same 
    // world graph) or you could build it here if this is the 
    // only system that needs to deal with it
    this.physicsWorld = attributes.physicsWorld;
    this.physicsBodiesKeyedByEntity = new WeakMap();
  }

  execute(delta, time) {
    this.queries.toBeAdded.results.forEach(entity => {
      // you can take the components you need off the entity
      const mesh = entity.getComponent(MeshComponent);
      const transform = entity.getComponent(Transform);
      // etc..

      const physicsBody = generateHullFromMesh(mesh);
      physicsBody.copyTransform(transform);

      this.physicsWorld.add(physicsBody);
      entity.addComponent(CollidableManaged);
      this.physicsBodiesKeyedByEntity.set(entity, physicsBody);
    });
  }
}

These are the two solutions that I’ve come up with. I prefer the system state component approach because you can use queries to fetch items to be disposed of. Its a really nice feature. This is especially true for web audio where you can’t walk the graph to find nodes by name or id like you can in three.js and cannon.js.

Anyway, I hope this helps a bit. If you can find another way to handle these let me know. :+1:

1 Like

@Snowfrogdev

If youre doing something like this: https://gafferongames.com/post/fix_your_timestep/

You can break the calculation out and handle skipping in the physics system. If you pass in a delta time that is less then the fixed step then return early. If you’re over the fixed time run the physics update as many times you need to and save the leftover time away in the system. The rest of the systems can run away as needed.

Hope this helps.

@James_Drew Hum, I think this would require some pretty significant changes to my current game loop but it’s not undoable. This is what I’m working with at the moment:

private frame_(): void {
    this.rafId_ = requestAnimationFrame(() => this.frame_());
    this.input_();
    this.now_ = performance.now();
    this.delta_ = this.now_ - this.then_;
    this.then_ = this.now_;
    this.lag_ += Math.min(this.sec_, this.delta_);
    while (this.lag_ >= this.slowStep_) {
      this.lag_ -= this.slowStep_;
      this.update_(this.updateTimeStep_);
    }
    this.frameTime_ += (this.delta_ - this.frameTime_) / this.fpsFilterStrength_;
    this.render_(this.lag_ / this.slowStep_);
  }

@Snowfrogdev I think it should be fine for what you have there. So a few things: raf passes in a high resolution timer. You should probably use that instead of a call to performance.now(). They used to be the same but there was an issue with timing attacks so they nuked the floating point precision of performance.now().

Anyway on to the core of the issue. :slightly_smiling_face: You can more or less take this bit and run it inside the physics system:

class PhysicsSystem extends System {
  constructor(world, attributes) {
    super(world, attributes);
    this.slowStep_ = // ?
    this.sec_ = // ?
    this.lag_ = 0;
  }

  execute(delta, time) {
    this.lag += Math.min(this.sec_, this.delta_);
    while (this.lag_ >= this.slowStep_) {
      this.lag -= this.slowStep;
      // run your physics step here
    }
  }
}

Then your outside loop gets reduced to:

private frame_(timestamp): void {
    this.rafId_ = requestAnimationFrame(this.frame_.bind(this));
    this.input_(); // this can go into a system to be executed by the ecsy world
    this.now_ = timestamp; 
    this.delta_ = this.now_ - this.then_;
    this.then_ = this.now_;

    // can probably be moved to the rendering system?
    this.frameTime_ += (this.delta_ - this.frameTime_) / this.fpsFilterStrength_;
    
    world.execute(this.delta, timestamp);
}

You can then just run it as normal. If it doesn’t need to update it will just skip. It’s also nice that the code for the physics is nicely encapsulated into the physics system.

Oh, ok. I think I get it. So we are essentially imbedding the game loop related computations inside systems. I guess this would work and it looks neat if all you have is one physics related system and one render system but if you have alot of them, doesn’t this lead to a lot of duplication, needing every single system to essentially deal with these timing issues. It seems like a cross cutting concern.

Still this might be better than my current solution which is to manually call execute on each system.

Yup. Just the bit for the sub loop; the main loop stays the same. Its not that much. I generally only have one physics system so it doesn’t become too much of a problem. A lot of the loop functionality can be extracted out into a function and just mixed into the system classes if you need it in a few places.

1 Like

@James_Drew Cool. Thanks for the help. This kind of leads me to another question that has been brewing in my mind for the pas few days. I made a separate post of it: How to handle global engine state

No probs. I’ve take a shot at answering the other one too. Again though, I’m not a game dev so take my advice with a grain of salt. I haven’t had to use any of these things in anger :stuck_out_tongue:

1 Like