This is a follow-up post to Entity frameworks.
Adam, over at T=Machine, wrote a great series on Entity Frameworks based on his experience. The first part in that series is entitled Entity Systems are the future of MMOG development – Part 1. He posted a sample Java Entity System on Github which I forked into my own EntityProject. In fact, he created an entire wiki, http://entity-systems.wikidot.com/, dedicated to this topic.
Adam noticed that I forked his project and was kind enough to provide a brief code review and some feedback. That kicked off a chain of e-mails between us where we were discussing various concepts around Entity Systems. The largest thing I was struggling with was how to manage the relationship between components of the same entity at runtime. I believe that there is room for a relationship scaffolding, while Adam’s vast practical experience in this area indicated that that was actually entirely unnecessary when it came to real games. Paraphrasing his argument: any sort of complex relationship amongst components generally requires custom code anyway, and that a modern game has to define those relationships concretely simply due to the fact that you need to implement something at the end of the day. Anyway, for those interested in the gory details, the full e-mail chain is provided below. I apologize for the complete lack of editing
.
Reshen -> Adam:
I’ve spent a great deal of time thinking about your blog posts on the topic recently; and as you have seen, I forked and made some very minor changes to your example implementation. I am planning on iterating my fork until I reach something I’m happy with.
I agree that traditional OO game object complexity scales very, very poorly. That’s just a fact of OO – the more derived classes you have, the easier it is to fall prey to promoting data and members up the tree until you have unwieldy and inefficient base classes.
Conversely, I think that ES are poor at managing the run time relationships between objects. This is an inherent problem to relational data design. Consider the following trivial (2D) example:
Rectangle component (width, height)
Circle component (radius)
Position component (x, y)
Game entity 1 (Alice): Rectangle, Position
Game entity 2 (Bob): Circle, Position
Game entity 3 (Charlie): Rectangle, Circle, Position
Alice and Bob are easy. Charlie introduces some subtle complexity. Specifically, ES are poor at describing the relationship between Charlie’s Rectangle and Circle. In OO, you would have one take ownership of the other and define custom behavior for how one treats the other. In ES, the analogy is that you would have some kind of foreign key constraint between the data – which would pollute one of the base components with an unnecessary member. Alternatively, you could have a RectCirc component that has foreign GUID keys to the two children; but that seems silly to me as well.
I guess my point is that I’m coming to the conclusion that there are really 3 pieces to my ideal game object framework:
1. Pure data objects (as your components are defined)
2. Pure behavior objects (as your subsystems are defined)
3. Pure relationships between data and/or behaviors (not yet defined)
I’m trying to come up with an elegant way to define Charlie dynamically. That is, (#1) create pure data objects, (#2) leverage subsystems to do the reusable behavior, but (#3) allow any of the following relationships to be defined when creating Charlie : rectangle is inside of his circle, circle inside of his rectangle, or the two render next to each other. I think the solution lies in creating a separate ComponentRelation framework – I’d like to hear your thoughts.
Sorry for ranting so long =). I really do appreciate your time and any cycles you put into thinking about this.
Adam -> Reshen:
Ah, OK, I see. It’s giving you a seamless, fast aliasing / shadowing
at the Component level, right?
When talking to beginners, I tell them to stop worrying and ignore it.
I tell them to make (say) a CameraHolder component that contains a var
of type UUID, which you assign the UUID of the entity the camera is
currently “attached” to. It works, and requires no changes to the ES.
There’s an obvious problem to this feature (no matter how you do it)
which I think is why I tended to avoid this approach in recent years:
you’re (implicitly) saying that writing to a Component is no longer
side-effect-free. If you attempt to write to that Camera’s version of
the Component, what happens?
At a more advanced level, there’s a definite problem in the basic
approach, which I guess is how you got interested in this? You can no
longer simply stream data direct into a subsystem and say “here’s all
the entity/component mappings you asked for; process one frame’s
worth”. Instead, you have to resolve some sub-lookups. A few of those
is fine; OTOH, if you have 1,000′s per frame, that’ll undo the
performance benefits you had from streaming/cache-efficiently
processing the components.
IIRC in C++ we (ab)used custom pointers and had the overarching
EntityManager intelligently scan the component lists and de-ref any
special pointers once per frame, just before sending the
lists-of-components/entities to the subsystems for processing.
Off the top of my head, if I were doing a lot of aliasing, I’d
probably start with a similar approach again: i.e.:
- EntityManager is responsible for this
- it’s invisible to the main ES/framework/thing – you can only tell
the existence of these “linked” components by querying special methods
in the EntityManager class
- when a subsystem receives it’s once-per-frame list of components,
the EntityManager guarantees to have already pre-resolved/unified any
linked components, so that they *appear* to be ordinary
- in Debug/Development mode, you run a check after each frame just to
make sure no subsystem wrote to any fields of a linked component,
except in the master version of that component
- …or, of course, if the language supports it: you’d pass in the
“linked” components as read-only data structures, and let the
compiler/runtime throw an exception if writing was done when
disallowed
…but I’d first try to avoid the issue altogether, I really like
having writes to components be side-effect-free
.
Reshen -> Adam:
Concerning the formatting, I don’t mind making the small fixes to your example implementation. It’s up to you – if you want me to take that on, feel free to add me as a collaborator to your git project and I’ll hand patch my minor fixes over to a dev branch in your project. You can take a look, and when happy, merge them nicely into your master (aka trunk) branch.
I’ll definitely update the two wiki pages with my thoughts: first to suggest the UUID usage in java, and second to have a more general experience/discussion page after using the example framework.
Concerning the relational framework item below: what I’m really trying to drive for is the analogy in ES to the publisher/subscriber design pattern – components are loosely coupled. I’m specifically talking here *not* about their behaviors – I understand behaviors are kept strictly in the subsystems. What I’m talking about is replacing the RDBMS foreign key constraint idea with a dynamic foreign relation. This is analogous to an RDBMS’s Junction Table – http://en.wikipedia.org/wiki/Junction_table.
Allow me to illustrate with, hopefully, a more concrete example:
- Game components with GUID #1 : Camera, Position
- Game components with GUID #2 : Player, Position
- Game components with GUID #3 : Bullet, Position
Now suppose I have a subsystem called “RelationalBehaviorSubsystem”. One method thats part of RelationalBehaviorSubsystem is copy(Component src, Component dest). As the name implies, copy simply copies the contents of src to dest.
Now suppose I have an EntityRelation framework, for now – its a single class with the following constructor: EntityRelation(Component A, Component B).
You can probably see where I’m going with this: I can now create an EntityRelation component :
- Game component with GUID #4 : EntityRelation(GUID#1 Position, GUID#2 Position)
Now, when the RelationalBehaviorSubsystem udpates, its going to query for all EntityRelation components and perform some behavior on them. For now, the only method thats available is copy – so copy is applied to all EntityRelation components.
I now have a dynamic and easy way to attach the Camera’s position to either the player’s position or the bullet’s position – and best of all, I can do this dynamically by updating GUID #4.
Here is where my argument falters : Without allowing a type explosion in EntityRelation (CopyEntityRelation / FooEntityRelation / etc); how do I easily tie EntityRelation to a specific subsystem behavior? (This question is rhetorical, but feel free to chime in with an answer =).
Anyways, I think it might be best to defer to your experience here and just stick strictly to what you’ve recommended. Perhaps at the end of the day, it turns out that for all practical implementations, the complexity of introducing junction tables (aka EntityRelation framework) don’t really add value. The elephant in the room is that real game objects inevitably need subsystem specific code to handle them. Games rarely do the infinite recomposition of components.
Again, I’m grateful for your time and thoughts.
Adam -> Reshen:
PS: I’d advise putting some of your ideas/thoughts on this on the Wiki
- it’s low traffic at the moment, but it’s a great place to write down
what you think you’re doing and why, and get other people to critique
and modify it.
(I started it largely because the blog posts are too static – they
don’t give enough power for other people to come along and correct my
mistakes. e.g. some of the naming I originally used … people have
suggested some much better names. Or e.g. your use of UUID (I didn’t
know that existed
) – the description of *all* java implementations
on the wiki ought to be updated to say “use UUID for entity id” IMHO)
>
> I’ve spent a great deal of time thinking about your blog posts on the topic recently; and as you have seen, I forked and made some very minor changes to your example implementation. I am planning on iterating my fork until I reach something I’m happy with.
>
Yeah, I’d like to merge some of the changes, but the re-formatting
checkin has upset github. It’s saying all future merges will probably
fail. If formatting is a big issue for you, you can setup eclipse (and
git, if you want) to auto-format code however you like (it’s been a
while since I’ve done it, but all the big projects I’ve worked on, we
had an SCM-formatter for Eclipse, and a pre-checkin-hook that would
reformat code with it. That way, everyone could view code however they
wanted, and yet merging worked OK).
I’ll just have to do copy/paste merges by hand when I get the chance.
> Conversely, I think that ES are poor at managing the run time relationships between objects. This is an inherent problem to relational data design. Consider the following trivial (2D) example:
In practice, I see nothing wrong with your example. The ES works
perfectly – the situation you describe is common in live games, and
typically presents *no* problems. If something is both a Circ and a
Rect that’s 99% likely to be because “in one context, it’s a Circ”,
“in a different, unrelated, context it’s a Rect”. The ES supports that
behaviour beautifully (and handles it a lot better than multiple
inheritance would
).
In the rare cases where you genuinely need an entity to have two
conflicting sets of data within the *same* context, you’ve got an
interesting problem, one that often means you’ve designed your code
poorly (and should re-think it), or that you genuinely need a
“RectCirc” component, because your algorithms are going to have to
special-case this weird “Circ + Rect thing” anyway.
> 3. Pure relationships between data and/or behaviors (not yet defined)
I strongly strongly advise: DO NOT GO THERE! (ALL CAPS!!!!!!111!!
)
Think about it. With 3 above, you just described the core concept of
OOP: “Objects are: data + methods that act on that data”. It’s an
awesome and powerful tool – but do you need it?
My experience from game teams suggests that with games the answer is:
“No; we’re just so accustomed to using it that we sub-consciously keep
trying to use it, even when we’re consciously trying not to”.
> allow any of the following relationships to be defined when creating Charlie
> : rectangle is inside of his circle, circle inside of his rectangle, or the two
> render next to each other. I think the solution lies in creating a separate
> ComponentRelation framework – I’d like to hear your thoughts.
Simply put: why do you *need* this?