A Thought Experiment: Using the ECS Pattern Outside of Game Engines

Over the christmas break I thought I'd write up an idea I've had for a while now. Taking the Entity-Component-System architecture used ubiquitously by the games industry and apply it to other areas, in this case a 2D CAD library.

Let me know what you think!

5 Likes

You did link to a Reddit thread where it’s already mentioned, but maybe you could add DCES and OrbTk by @FloVanGH to your “See also” section for good measure considering its value as an example.

Two other examples that come to mind:

1 Like

Thanks @erlend_sh =)

I know of this one too https://github.com/dakom/todo-shipyard-lit

1 Like

Thanks for all the links! It was nice to read more about how others have used/implemented this pattern.

I was particularly interested to see that orbtk uses an ECS architecture. The shipyard crate's use of custom derives to let you implement a System with minimal boilerplate looks like it'd be really easy to get a prototype up and running quickly.

I've updated the article's See Also section accordingly :slight_smile:

2 Likes

Very cool!

I'd love to see more articles on the subject or even a proper book of "gems / design patterns in ECS".

It's also interesting how different libraries take different approaches that have far-reaching consequences... for example, "how to detect changed components" isn't really fundamental to the core concept of an ECS, but it's an essential ingredient and they all have different ways of doing that (FlaggedStorage, events, ...?)

Fwiw in the work-in-progress todo example linked above - what I did so far for that particular problem was add a "Dirty" component. In Shipyard, adding/removing components is cheap so using these "Tag Components" to create a de-facto event mechanism is totally fine. Don't have to depend on an actual event or notification system for that (but this approach would not be as good in archetype-based ECS's since adding/removing components is more expensive there, and it could explode into needing lots of separate Dirty components, depending on the application)

Sometime soon (within this week) I want to try and use signals as the component data.. then when it changes, anything that is interested can be notified that way. Not sure yet if it'll work out the way I want - but if it does, imho that's kinda cool and would feel pretty elegant since it means a totally different part of the stack, like dom renderer (via dominator) can just listen to signals and not care how they're triggered.

On that note - this problem of tying the ECS to other things (like DOM tree or a separate physics system or a renderer) is something that could also use some articles and/or books :wink:

All in all it feels like early days for ECS actually... I guess it's technically been around for a long time, but with Unity's recent shift and the mantra of "composition over inheritance" becoming more and more popular for web... feels like it's just starting to pick up steam outside of the game studio world. Or maybe I've just not been paying attention enough, hehe.

For Rust in particular I think it can also make things a lot easier since it lets you pass around component data by reference, even with multithreading, without worrying about lifetime issues. That's a pretty big win too imho.

2 Likes

A gripe I have with most of the ECS implementations I've seen in Rust so far is you lose a lot of the strong typing that a lot of people come to Rust for.

Take the DrawingObject from my original article, it has a layer: Entity field but there's absolutely no guarantee the Entity pointed to by layer actually has a Layer component. It could be a Line or Image for all we know. That means all code using the Layer pointed to by a DrawingObject effectively does a dictionary lookup and blows up at runtime if the lookup fails if (for whatever reason) the "layer has a Layer component" invariant is accidentally broken.

This feels a lot closer to the stringly-typed APIs you might see from a novice Python programmer than the strongly-typed APIs I've come to expect from idiomatic in Rust.

This doesn't necessarily mean the ECS pattern is bad. Just that different ways of writing code have different trade-offs that need to be taken into account when designing things.

2 Likes

Good point. Though I'm not so sure it really is a problem in practice due to the strong typing within system runs and component management. But I hear ya - it's definitely possible for things to get out of sync due to the extra step of indirection.

Strikes me as something that can also be addressed by a good book on ECS techniques...

You’re introducing dynamic typing to only a small part of the application with ECS patterns. One very key point of it is to allow entities to have dynamic types.

If you have a few different types of entities in your program and there’s not really any sensible subset of components shared between them, an ECS probably won’t make sense. On the other hand, imagine a magic system in a game which allows a door to become animate and speak to the player, catch on fire, or do any number of wildly-magical things. The entity at runtime necessarily in such a case gathers new properties and straddles the line between clean type divisions. Is it level geometry? Is it an AI actor? Is it a story objective?

If you find yourself questioning whether an ECS is helping or hindering the correctness guarantees, performance, flexibility, or other aspects of your program, then that’s good. You’re just doing engineering. I enjoy discussions on when particular patterns are appropriate so I can learn new things, but in the end you’re doing the right thing by questioning how patterns fit your particular use.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.