Component based entity design example

Started by
1 comment, last by Telastyn 11 years, 7 months ago
So I wanted to follow up on my previous topic about component based entity design since that thread was... not super helpful for me, despite some attempts. Hopefully this can help others who come along looking for similar implementation details. This is for a non-gamedev project, but many of the basics fit.

Anyways, the implementation is broken into 3 distinct pieces:

  1. The entity that is a common aggregation of functionality.
  2. Components that are individual composable units of functionality.
  3. And a definition that is immutable and readonly. It defines what an entity is, and is used by the components to determine if the component is applicable, and if so, what flavor of component to use.

The Entity in this design is very, very thin. It contains a definition, and a collection of components. Since the project is in C#, the component store ends up being a private list of dynamic, with public methods to extract typed instances.

Composition

This project requires the components be deployable independently of each other. They're more of a plugin at this point than conventional components. To make this work, components are separated into parts. There is a public interface that defines the component, and should be stable over time. And then there is concrete implementation(s), including a factory class for component discovery. There may also be component specific data/assets. The consumer of the entity, as well as other components only ever refer to the DLL having the public interface.

At composition time, the core code uses reflection (project requirements prevent our use of MEF, Unity and a few other IoC containers) to hunt for types that inherit from the common factory interface for component discovery. The Definition is then passed into the factories to spin up instances of all the components (where applicable).

Dependencies

To get the components to talk to one another, the design calls for dependency injection. A common attribute exists that components can use to tag their fields/properties as a dependency. Once all of the components are created, a second step goes through all of them looking for the attribute. When one is found, it looks for a component instance that inherits from the tagged field/property. If one is found, reflection is used to assign the instance into the other component. They then talk directly to do their communication.

Reflection is okay here, since we have few actual entities and they're created infrequently. Direct communication is okay too, since our components are pretty tightly coupled (in a business sense) when coupled. They won't live on different threads/machines, so abstracting the communication only leads to unnecessary complexity.


And that's it in the nutshell. We've not actually implemented too much of it yet, so it might be garbage; we'll see. Hopefully that helps. If anything is unclear, please ask and I'll be happy to elaborate as much as I can.

Advertisement
The problem I typically have with the whole "entity/component" concept is that the idea is extremely general, but the specifics of how it can best be implemented will vary widely based on the exact requirements of each particular project. Put another way, what works well for one design may totally fail to scale in even a slightly different situation.

That said, this sounds reasonable, provided you stick to your original design requirements fairly well and don't get carried away with feature bloat. For example, coupling entities and using reflection for entity creation is fine in many cases as is use of "dynamic" for holding a "typeless" set of components for later concretization. For a business logic system this sounds like it could be a very good design basis, depending on what you need to do.

For other things, though, I would caution other adventurers who happen across this thread that one size does not fit all. Entity/component designs are the M theory of software architecture; they're not really "a" design so much as a very, very broad family of possible designs, any number of which might suit the project at hand.

Just as an example, if you write a system that needs to spin up and destroy thousands of entities per second, this probably won't scale. If you need to distribute communication across process boundaries or machine boundaries, the whole thing just may not work. And if you need early-binding type information, you're out of luck entirely. Also, it's worth noting that a lot of this is really only possible as-is in C#; even a similar language like Java would have tremendous difficulty mimicking the way this works.


I don't mean any of this as a negative against your design, mind you; in fact I'd be inclined to believe that it's very well suited for what you want to accomplish. I just worry when people see a well-thought-out architecture based on components and immediately think that it will transfer en masse directly into their totally unrelated problem space.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Absolutely. I perhaps should have made that more clear. I tried to list some of the requirements that impacted design decisions. The main ones off the top of my head are:

  • We only make a few entities.
  • Those entities tend to be long lived.
  • The entities at most talk to one other entity.
  • The definition of an entity is constant over the duration of the program.
  • The mapping from definition to component is constant over the lifetime of the entity.
  • Components aren't doing much constant work.
  • Component communication is infrequent, but blocks operations until it completes (usually).
  • Components cannot for business reasons be distributed.

Really, if any of these change then the design will probably change to better accomodate things. But ideally people can look at this, think about how it doesn't fit with their needs and adapt (or discard!) it as necessary.

This topic is closed to new replies.

Advertisement