How to handle global engine/game state

So I’m new to ECS architecture but I’m wondering how rigid we are supposed to be with the whole everything is an entity and data is in component classes that have no behavior… thing.

Specifically when it comes to global singleton-y things. For instance, if I’m using PIXI.js to render my game and let’s say I have various different systems that need to communicate with the renderer to, obviously, render, but also maybe, deal with inputs and other things. Do I need to wrap the instance of the PIXI.js renderer in a component and create one entity with that component and then have all systems that need read/write to the renderer query for the entity that has the Renderer component? Or can I just have systems that use the renderer hold a reference to it?

In other words, must all of the engine/game’s data be held in entities/components or is it Ok to have singleton-like classes that hold some global data and some of its related behaviors and have systems hold a reference to those class instances?

From my understanding you want to keep the source of truth as the data in the components. It should then be ok to derive state from here. An example of this would be something like the following:

I have an entity with a sprite component, a transform component and a renderable tag. We then get our render system to to query for these, load up our sprite and set its position and add it to the app stage. You then have two options you can save the sprite into a WeakMap, using the entity as the key, or you can create a system state component. You then do something similar if the item needs to be added to the physics system (as discussed in the previous thread). Some of these libraries allow you to tag the object with a name and do a lookup (see below). Pixi.js, Three.js and Cannon.js can do this.

The entity itself is still just data but we have different derived states in the respective systems (audio, ai, physics, rendering, etc). Were we to save the entities into a db and then deserialise you should be able to recreate the world again from it.

So for something like pixi you could do something like:

class RenderSystem extends System {
  constructor(world, attributes){
    super(world, attributes);
    this.pixiApp = new PIXI.Application();
  }

  execute(delta, time){
    this.spritesToBeRendered.results.forEach((entities) => {
       const spriteComponent = entity.getComponent(SpriteComponent);
       this.pixiApp.loader.add(spriteComponent.name, spriteComponent.file).load((loader, resources) => {
         const sprite = new PIXI.Sprite(resources[spriteComponent.name].texture);

         // Setup the position of the sprite
         const {x, y} = entity.getComponent(TransformComponent);
         sprite.x = x;
         sprite.y = y;

         // we give the entity a name that we can use for lookup later on
         // this will allow us to fetch the sprite like so: this.pixiApp.stage.getChildByName(entity.id)
         sprite.name = entity.id
         this.pixiApp.stage.addChild(sprite);
      });
    })
  }
}

You might want to make liberal use of tags and components to save the entity state into. Even Unity3D has a monolithic physics engine and the same for the dsp audio engine. You can wrap those worlds in a system and give an external API that would be component based. The hardest part of this is managing resources, making sure that you add, update and cleanup resources in each of these ‘worlds’. This is where system state components are probably your go to for anything that touches these worlds. At least then you can check for their removal and cleanup.

Also in the above example I’m not sure what effect doing an async call has on the game loop. It’s something I haven’t had to deal with too much yet (a lot of my assets are procedurally generated before the game begins and kept in memory).

2 Likes

@James_Drew So in your example, you are instantiating the PIXI app in the constructor of your RenderSystem and saving it in the pixiApp property. But what if another system also needs access to the instance of PIXI app?

In that case you can create it outside the system and pass it in through the ‘attributes’ property. Like so:

const pixiApp = new PIXI.Application();
world.registerSystem(RendererSystem, {app : pixiApp}).registerSystem(OtherRendererSystem, {app : pixiApp});

It’s funny, when I wrote the original post I had it being passed in and then I removed it because I thought it cluttered up the example :stuck_out_tongue_winking_eye:

2 Likes

Fairly certain the system attributes thing isn’t working right now.
Have stepped through the code and I don’t think much is done with attributes.

Is attributes supposed to be an object? Are they supposed to be stored on the system? e.g under system.attributes?

Happy to look at a PR for this if it would help.

Actually, maybe a nice way to handle this would be to change line 18 of SystemManager.js from:
if (system.init) system.init();
to
if (system.init) system.init(attributes);

Then it’s up to the system coder to decide what to do with attributes?

Added 2 speculative PRs to handle this:

  1. https://github.com/MozillaReality/ecsy/pull/151

  2. https://github.com/MozillaReality/ecsy/pull/152

  3. Passes attributes arg to system.init so you can handle as is.

  4. Copies attributes onto the system class.

Second one is in-keeping with how components handle attributes.
First one is maybe a touch more versatile. I generally prefer to avoid pushing people down the route of classes where possible (in the hope they won’s use them :slight_smile: ) although you can unravel the class either way if needed.

Appreciate any feedback on these. If either are viable I can finish them with docs / defs etc.

1 Like