6
\$\begingroup\$

I am trying to design a data-oriented ECS engine. The issue I am struggling with is that my rendering system have to rely on two different sets of entities to actually do rendering.

My current idea is to have (usually one, but possibly more) entities representing cameras (e.g. sets of components describing views and viewports). Then, I can have RenderingSystem that iterates through all CameraComponents and renders everything they 'see' to appropriate viewports.

However, this requires RenderingSystem to query for two non-intersecting sets of components: cameras and renderables. My current approach allows systems to select a set of nodes that have one or several required components. It does not allow for more complex queries, like either CameraComponent or RenderableComponent. This is not the first time when more complex system-component dependencies (using unions of component sets insetead of their intersections) seem useful to me.

Plain old data components have crucial advantages:

  • They are trivially serializable
  • Can be changed by game designers without recompilation or coding knowledge
  • Describe behaviors in purely declarative way

That is why I would like to put cameras among other things in the ECS.

Use case: if we are implementing mirrors, we may put Renderable, Position and Camera components on them, then Renderable's texture will also be a render target for Camera. RenderingSystem then will render them in some efficient way.

So, is defining more complex relations with components (like "this system should receive all nodes that have Camera and Position as 'cameras' collection and all nodes with Renderable and Position as 'renderables') the most flexible solution in this case, or is there a better way?

UPDATE(1): I ended up allowing systems to define their dependencies like this (the word "aspect" is borrowed from Artemis ECS framework):

aspects: {
    renderables: Aspect.all(['Transform', 'Sprite']),
    cameras: Aspect.all(['Transform', 'Camera']),
    emitters: Aspect.all(['Transform', 'ParticleEmitter']),
    ...
}

These lists are gathered, passed to a system, cached and rendered in an efficient way. This actually works quite nicely (in other systems too), as well as cameras being entities. Is this a common way to do it?

UPDATE(2): I have spent some time working with ECS-like architectures and added a conclusion as an answer. I hope it makes clear that the approach I have suggested in the first update is just one of available options.

\$\endgroup\$
  • \$\begingroup\$ If you solved your problem using the Aspect approach you describe, then feel free to elaborate on this in an answer and mark it as accepted. That makes it easier for people with similar problems to find your answer later. \$\endgroup\$ – DMGregory Jan 8 '16 at 14:16
  • \$\begingroup\$ @DMGregory: I will wait for a few days and then accept or create an answer. Still, I would like to know whether my approach is common. \$\endgroup\$ – interphx Jan 8 '16 at 14:29
  • \$\begingroup\$ Does being common make it better? If it accomplishes your goals within your performance targets and makes sense to you to use, then would it matter even if it was a one-of-a-kind solution? ;) \$\endgroup\$ – DMGregory Jan 8 '16 at 14:31
9
\$\begingroup\$

First, you should not have a strict 1-1 mapping of Components to Systems. It's unclear to me from your question if that's the case already. You may very well have singular systems that use or interact with numerous components. Rendering, physics, AI, etc. are all Systems (they perform a cohesive set of updates and logic) but interact with many Components.

A System needs to be able to store a list of entities that match some particular Signature. e.g., if physics requires BodyComponent and TransformComponent, then the PhysicsSystem needs a list of all entities that have both of those two components.

Solution 1

A System also likely needs multiple such lists, as you're finding. RenderSystem needs to know about all Renderable+Transform pairs, as well as all Camera+Transform pairs, and that's only for a very simple rendering engine (once you get particles, post-processing, terrain, foliage, skinned meshes, etc. it gets more complex).

Solution 2

The other alternative that I'm rather fond of is to stop trying to use the ECS pattern. If you split all your engines (physics, graphics, AI, audio, etc.) up into separate libraries (which is probably already the case for physics since most people use off-the-shelf libraries there) then you'll note that you need each to exist independently of your ECS framework.

For instance, when you load an entity up and see that it has a PhysicsComponent you call into your physics library's CreateBody() function with all the data from the entity; that will usually return some kind of pointer or id. Store that into the PhysicsComponent. Most of the rest of the data you loaded off disk can possibly even just be thrown away since it'll all be copied into the physics library's internal data structures. Really all you're left with is some glue so when game logic needs to add a force to a game object it can find the appropriate pointer to the physics library's body object.

Your Components end up mostly just being glue that holds your convenient data-driven game objects together with the external handles/objects managed by your engine libraries. You're more likely to find that you don't need Systems at all anymore and possibly (for simpler games) don't even need Components, either.

Depending on your choice of libraries or how you decide to implement Components, this may still even end up looking a lot like an ECS. For instance, instead of storing a PhysicsComponent that holds a pointer to a rigid body object, your library may allow you to attach a unique ID to a body when it's created and then query for bodies by ID; that would allow you to attach the EntityId to the corresponding physics body and then write a ComponentMapper facade that queries your third-party physics library instead of a custom component data structure. I question the utility of such a design myself, but it's certainly something you could do.

\$\endgroup\$
  • \$\begingroup\$ It's probably not useful to post another answer saying to not use ECS for this, so I'll add my thoughts as a comment. If you imagine a scene graph for dealing with rendering, sound, etc., it's very easy to hook it up with an ECS; simply have a component that refers to or owns a node in the graph. The scene graph is the meat and bones of the world, while the ECS is the brain. \$\endgroup\$ – jmegaffin Jan 3 '16 at 18:45
  • \$\begingroup\$ Thank you for your answer! Please, see my edits, I have clarified some details in my question. As system may need multiple lists of components/nodes, is it common or generally useful to implement simple DSL (possibly SQL-like) to handle such relations? Also, is signature a common word to refer to a subset of entity's components? \$\endgroup\$ – interphx Jan 4 '16 at 11:13
  • \$\begingroup\$ @interphx: I'm unsure how common such a DSL is, though it sounds a little over-engineered to me. You perhaps could look around at any of the many open-source Entity Systems and see which one you like. \$\endgroup\$ – Sean Middleditch Jan 4 '16 at 16:46
  • \$\begingroup\$ Using your second solution, how would handle, say, soft body physics ? Where you need your RenderingSystem to update the mesh data at every frame. This feels a bit like having to copy the data back from the physics engine to convert it to your mesh data structure, then send it to the GPU. Isn't there a bit too much data copy ? \$\endgroup\$ – Zouch Jan 18 '17 at 12:32
  • \$\begingroup\$ @Zouch: memcpy is scary fast, and if your data structures aren't memcpyable then you should rethink your code (especially if you're sticking to the ECS variant of component architectures, why aren't you writing heavily data-oriented code?). If you do find there's too much copying going on, you may need to rethink your algorithms: why aren't you generating the meshes on the GPU? \$\endgroup\$ – Sean Middleditch Jan 18 '17 at 19:19
2
\$\begingroup\$

I think you are over-using the component model. While what you are saying could be accomplished and would work, the general way of doing this is making the camera a global thing.

Sometimes it is just much easier to make an exception than to try force everything into a rigid view of "everything must be a component". So I recommend making the camera not a component but rather a property of the RenderSystem. There you could even have a list of cameras, and the RenderSystem would have much more control to optimize the way things get rendered.


In response to your edit:

If you want to keep your original model, and you are willing to sacrifice performance(though that will depend on the implementation), you could do it in 2 passes. The first pass will only enumerate cameras into the system, and the second pass renders all objects from all viewports.

\$\endgroup\$
  • \$\begingroup\$ Please, see my edit. I have clarified why I believe that components are useful in this case. Also, I have some background in functional programming, from which I know that making things uniform brings big benefits for code quality, reusability and size. \$\endgroup\$ – interphx Jan 4 '16 at 11:17
  • \$\begingroup\$ @interphx I still think its overkill, but see my edit. \$\endgroup\$ – akaltar Jan 4 '16 at 13:05
1
\$\begingroup\$

I have spent some time working with ECS-like approaches and can now write the conclusion.

Making complex queries for your entities is alright (and necessary) as long as they are not a bottleneck (which they won't be if you know in advance what you will be querying for).

Let's use name Aspect for a selection filter you use to query for entities. Aspects are created with constructors like Aspect.all([TransformComponent, RenderableComponent, ...]), Aspect.any([...]) and so on, whatever you actually need. Aspect.all instance can be efficiently represented by a bitfield, and we can have a lookup table:

HashTable<Aspect, Set<EntityId>>

It gives us O(1) querying for a set of entities having all the specified components. This table is updated on inserting/removing entities and their components. Aspects we query for should also be known beforehand to be efficiently tracked (I make calls like entityManager.registerAspect(Aspect.all([...])) during initialization stage, before creating any entities). EntityId sets for Aspect.any and other kinds of Aspect can also be cached or constructed on the fly depending on memory and performance requirements.

As for systems and defining their dependencies, this is a matter of preference and convenience. It is mostly unrelated to the rest of your infrastructure, but you can make some handy helpers. Systems are basically anything that fetches your entities and operates on them. If you like the declarative approach and defining dependencies in your class, you can implement a base class for some (not necessarily all) systems and use it like this:

class Renderer extends AutoQueryingSystem {
    aspects: {
        renderables: Aspect.all([
            TransformComponent, 
            Aspect.any([SpriteComponent, SimpleShapeComponent])
        ]),
        cameras: Aspect.all([TransformComponent, CameraComponent])
    },

    // AutoQueryingSystem automatically quieries for its aspects
    // and passes the results into its update function
    update: function(dt, entities) {
        for (camera of entities.cameras) {
            for (renderable of entities.renderables) {
                // Do rendering...
            }
        }
    }
}

But you can do the same thing manually just as well:

var renderableAspect = Aspect.all([
    TransformComponent, 
    Aspect.any([SpriteComponent, SimpleShapeComponent])
]);
var cameraAspect = Aspect.all([TransformComponent, CameraComponent]);
...
update: function(dt) {
    var cameras = entityManager.getEntities(renderableAspect);
    var renderables = entityManager.getEntities(cameraAspect);
    // Do rendering...
}

As Sean Middleditch notes, you may not even have separate System classes for third-party libraries, in which case the abovementioned declarative approach would be harder to implement. Simply calling entityManager.getEntities can be done anywhere, though.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.