Component-driven design
CGamesPlay

Today I read about PushButton Engine, which is an open source Flash-based game engine (if you want to discuss it, please start a new thread). What interested me was the idea of Components, which, as far as I can tell, have only really been discussed here[1].

Component-driven design is an alternative to inheritance-driven design. Both designs are object oriented, but with components, all game objects are formless entities with no properties of their own. The game developer can take these entities and apply components to them. Each component adds a set of properties and behaviors to the entity, and can interact with other components that have been applied. By selecting which components are applied to each entity and by adjusting the properties, you add form to the entity. To accomplish this, you can use templates to provide a default set of components and properties to apply to entities. Templates can specialize one another in a traditional inheritance pattern, and then an entity is an instantiation of a particular template.

Before I clarify this, let me show the example that the slides use. Slide 27 shows a subset of the template forest. On the left is a list of root templates, or those that don't specialize any others. The slide shows a list of specialized templates descending in a hierarchy to an example chicken template. Slide 28 shows the details of a template, including how it defines values for the properties. On slide 33, we see the chicken instantiated with a few instance-specific property values specified.

To reiterate, components define what properties exist, but set no values for them. Templates are parallel to the inheritance pattern's classes, in that they specialize one another in a hierarchy. Templates may set or change values for properties but are not required to. Finally, a template can be instantiated. Instances may additionally change the values for properties.

This concept for structuring wildly different types of objects is interesting to me. It's really only a small modification to the inheritance pattern. What are your thoughts/opinions?

References

  1. I am referring to the GDC 2002 paper. For your convenience, I have attached a PDF version of the slides. Note: the HTML version of the slides doesn't appear to work.
SiegeLord

This appears to be the same as multiple inheritance. Components = base classes. Entities = child classes. Templates = also classes. Instances = instances of classes.

The only thing I note is the different language used... Or am I missing something here?

Thomas Fjellstrom

I think its more like interfaces, or ObjC's interfaces+protocols.

SiegeLord

Each component adds a set of properties and behaviors to the entity, and can interact with other components that have been applied.

This seems to be a bit more than just an interface though...

Thomas Fjellstrom

Yeah, its like being able to compose or "mixin" behavioral objects at runtime.

Intriguing idea, but how does it scale? (dynamic properties aren't exactly "fast", especially if you're talking about thousands or tens of thousands of game objects).

CGamesPlay
SiegeLord said:

This appears to be the same as multiple inheritance.

Starting at slide 7, the presentation illustrates why multiple inheritance wasn't used.

One of the big paradigm shifts is moving from is-a to has-a: from "an actor is drawable and collidable" to "an actor has a drawing behavior and a collision behavior". The reason for doing this is to minimize the amount of C++ code used: by using modular behavioral blocks, they can be rearranged without refactoring the code.

Intriguing idea, but how does it scale? (dynamic properties aren't exactly "fast", especially if you're talking about thousands or tens of thousands of game objects).

The properties used in this system aren't dynamic. A component defines some properties, and those don't change. Their value comes from various places, though (the value could be modified by a template, then again by a further-specialized template, then again by the actual instance and only the instance's value would be used).

Thomas Fjellstrom

The properties used in this system aren't dynamic.

What it looked like to me is theres a data store that stores the game objects which are blank, and the components add (mixin) properties.. If this happens using regular C++ inheritance, its not special at all.

CGamesPlay

I tried to avoid using implementation details, but the implementation from the presentation had the components existing as C++ classes, and components existing as script classes. Components have no inheritance, and each is separate and independent. Templates are not defined in C++ code at all. In the presentation, the data store contains all of the templates in compiled form.

Components don't change at run time, but since the templates are not C++ code, the components used can be changed around without recompiling. Each instance has a set of the values that the components define. They aren't dynamic because the properties themselves won't change at run time.

ReyBrujo

It is not really model friendly, if you only have separated classes with no relation.

The closest I can think of is the composite pattern, although it is not exactly the same. For starters, the objects don't provide properties.

SiegeLord

Starting at slide 7, the presentation illustrates why multiple inheritance wasn't used.

Do they? They don't ever mention the concept. Considering the FUD in the first few slides of the presentation, the author might not even be aware of it existing. The thing on page 7 is a single inheritance tree. The thing on page 8 is a multiple inheritance tree. Just use a dynamic cast to determine which components the class has, and go from there. I guess their idea bypasses the need for the dynamic cast though... that could be a plus.

Quote:

One of the big paradigm shifts is moving from is-a to has-a: from "an actor is drawable and collidable" to "an actor has a drawing behavior and a collision behavior". The reason for doing this is to minimize the amount of C++ code used: by using modular behavioral blocks, they can be rearranged without refactoring the code.

So yes, they created a new language to speed up/automate the multiple inheritance process. It appears to me that they just altered the implementation of multiple inheritance, since C++'s one is so clunky. Doesn't change the fact that that is what it is though.

Tobias Dammers

Isn't it just another way of implementing the "composite object" approach? If you ask me, this is not a very new nor a very special thought (though often a good idea); it's just that this kind of thing isn't taught as heavily in typical OO courses (maybe because inheritance is harder to grasp, or maybe because it feels more "elegant" or more "clever" at first glance). This kind of thing actually helps AVOIDING multiple inheritance and all the crap you get from over-using it: GameEntity HAS A EntityBehaviour, and GameEntity HAS A EntityAppearance instead of GameEntity IMPLEMENTS IEntityBehaviour and GameEntity IMPLEMENTS IEntityAppearance. Composing objects means you can override the components individually, with independent levels of specialization.

Thomas Harte

I think its more like interfaces, or ObjC's interfaces+protocols.

I was thinking the exact same thing. Possibly I'm missing the point, but how is this different from implementing the protocols you intend to support and then registering yourself as a delegate in the appropriate places?

Ummm, to quickly help make sense of that for C++ folk: implementing a protocol is not inheritance, it's just implementing methods with the correct names and arguments, setting yourself as a delegate is nominating an instance of a class (any class, inheriting from anything at all or nothing) to be issued with certain method calls; if the methods are implemented by the class then that's great, if they're not then the ObjC runtime deals with problem transparently.

EDIT: Oh, and NextStep/OpenStep/GNUStep/Cocoa also has a thing called an NSNotification; all objects that are interested in a particular event (identified by a string) register themselves with an NSNotificationCenter (or the default NSNotificationCenter if you really only want one) and provide a method to invoke if the event occurs. Subsequently, any other object can trigger the event by sending a message to the notification centre, thereby causing all interested objects to be notified.

EDIT2: Plus, a neat one I learnt recently: you can use an NSInvocation to create a plain selector (ie, method identifier — not a pointer, just something that can be compared at runtime more quickly than a string, though the two are interchangeable at runtime) from any method call by supplying default arguments for the other parameters.

Steve++

The discussion here seems to be leaning towards the negative. Sure, this kind of design can be done with C++. But C++ doesn't give you this framework out of the box. This may not be the best architecture in the world, but it's certainly a good foundation for game development. I, for one, will be implementing something like this as the foundation of my next Allegro game.

I really like the idea of composing game entities out of components, registering those components with the engine and just watching all the pieces work together in all sorts of configurations.

axilmar

So, instead of multiple inheritance, use composition? multiple inheritance is composition. The only difference is namespace merging. And I don't think managing callbacks is any different from managing virtual methods.

Steve++

Multiple inheritance and composition are completely different. Multiple inheritence is used to show that an object is a specialisation of more than one object type. Composition is used to indicate than an object contains a number of components, each one providing a distinct service. In a composition design, it's easy to plug in any combination of components into container objects without necessarily having to specialise those components.

Sure, with multiple inheritence you could make your game objects inherit from the components that you want to use and sure, you can use RTTI to figure out which of the components are inherited, but then you are limited to composing the functionality of your game objects in code and modifying them at runtime is definitely out of the question.

The one thing MI has going for it in this scenario is that you can easily integrate the objects within each game object. But in reality, you wouldn't want to do that too much because the various components are unlikely to have much to do with each other even when combined into the same game object. I envisage that each component would implement common behviour and lifecycle interfaces so that any gamestate changes can be communicated to them.

As an excercise, I may rewrite Trek Invaders using this type of design. It will be interesting to see how well it lends itself to customisation.

axilmar
Steve++ said:

Multiple inheritance and composition are completely different. Multiple inheritence is used to show that an object is a specialisation of more than one object type. Composition is used to indicate than an object contains a number of components, each one providing a distinct service. In a composition design, it's easy to plug in any combination of components into container objects without necessarily having to specialise those components.

How can you not do that with multiple inheritance? by inheriting from a class, each object can be made to provide a distinct service. You don't need to specialize it if specialization is not required.

Quote:

Sure, with multiple inheritence you could make your game objects inherit from the components that you want to use and sure, you can use RTTI to figure out which of the components are inherited, but then you are limited to composing the functionality of your game objects in code and modifying them at runtime is definitely out of the question.

Aha, you are talking about run-time composition of objects. Indeed, that's something that MI can not do, but it is not because MI can not do it, it is because MI is completely unrelated to run-time object composition.

Comparing apples and oranges is not a good thing.

Quote:

The one thing MI has going for it in this scenario is that you can easily integrate the objects within each game object. But in reality, you wouldn't want to do that too much because the various components are unlikely to have much to do with each other even when combined into the same game object. I envisage that each component would implement common behviour and lifecycle interfaces so that any gamestate changes can be communicated to them.

As an excercise, I may rewrite Trek Invaders using this type of design. It will be interesting to see how well it lends itself to customisation.

How will you handle polymorphism?

Using a series of 'ifs' onto the runtime type of an object is a bad approach that does not scale well.

ReyBrujo

Oh, and NextStep/OpenStep/GNUStep/Cocoa also has a thing called an NSNotification; all objects that are interested in a particular event (identified by a string) register themselves with an NSNotificationCenter (or the default NSNotificationCenter if you really only want one) and provide a method to invoke if the event occurs. Subsequently, any other object can trigger the event by sending a message to the notification centre, thereby causing all interested objects to be notified.

Now you are talking about the observer pattern.

CGamesPlay

I believe I misunderstood one of the core facets of this design: templates cannot register event handlers. Templates provide no code themselves, they only provide values for property fields. In this way, templates are analogous to a record in a database. A template can import a component to provide behaviors (and a specialized template imports its parent's components in addition to those it includes itself), but does not include any code. This makes the design significantly different from the inheritance pattern, and makes the game objects a database (as the slides mention).

Steve++
axilmar said:

Aha, you are talking about run-time composition of objects. Indeed, that's something that MI can not do, but it is not because MI can not do it, it is because MI is completely unrelated to run-time object composition.

Comparing apples and oranges is not a good thing.

Actually, I'm not the one comparing apples and oranges. And this time you're lowering the bar to win the debate. The documents referenced above clearly state that one of the motivations behind the architecture is runtime composition of game objects.

Quote:

How will you handle polymorphism?

Using a series of 'ifs' onto the runtime type of an object is a bad approach that does not scale well.

I imagine each type of component will have its own global (within the game manager) collection with which each component instant registers. As events occur that are relevant to a specific component type, the game manager will simply process the collection for that type. I can't see a series of ifs being used here.

Axilmar, you seem to be very resistant to paradigm shifts, so much that you put forward arguments that just make us all scratch our heads. We've both been here long enough for me to know you weren't always like this. What gives? Is everything going alright with you?

axilmar
Steve++ said:

Actually, I'm not the one comparing apples and oranges. And this time you're lowering the bar to win the debate.

I am not here to win anything.

Quote:

Actually, I'm not the one comparing apples and oranges.

I never said that you did.

Quote:

The documents referenced above clearly state that one of the motivations behind the architecture is runtime composition of game objects.

But the documents also talk about why multiple inheritance is not appropriate for this job. So, the person who wrote that compares apples and oranges.

Quote:

I imagine each type of component will have its own global (within the game manager) collection with which each component instant registers. As events occur that are relevant to a specific component type, the game manager will simply process the collection for that type. I can't see a series of ifs being used here.

What you describe above is not polymorphism, unless those components are polymorphic themselves.

Quote:

Axilmar, you seem to be very resistant to paradigm shifts, so much that you put forward arguments that just make us all scratch our heads. We've both been here long enough for me to know you weren't always like this. What gives? Is everything going alright with you?

I am ok. I am not resistant to real paradigm shifts. I just don't see any paradigm shift here. I only see confusion.

Take the so called polymorphism mentioned above, for example: dispatching events to a component type is not polymorphism. A type is polymorphic if it has subtypes with overloaded methods.

As a (sort of) computer scientist with an interest in programming languages and software engineering, these things bother me.

Tobias Dammers
axilmar said:

How can you not do that with multiple inheritance? by inheriting from a class, each object can be made to provide a distinct service. You don't need to specialize it if specialization is not required.

Object composition allows you to de-specialize. Consider the following scenario: You have a number of entities in your game. Each entity has a draw() method, and an update_logic() method. Most of the game objects draw themselves by blitting a single sprite to the back buffer; some, however, require an extra sprite (say, a turret that moves independently); others draw themselves procedurally. It makes sense to have all objects that draw themselves the same way from a common base class. The same goes for the logic: Most game object act like solid bodies: they move according to Newton's laws, they bounce when colliding, etc.; others, however, follow a different movement pattern. Again, it makes sense to have a common base class for each type of movement.
But now we have a problem: Multiple inheritance doesn't allow us to freely combine different behaviours and appearances - we can't derive from SPRITE_DRAWING_ENTITY and SOLID_ENTITY at the same time, because they share a common base class (ENTITY) and thus both provide the same methods. MI, however, only allows combining base classes with mutually exclusive sets of members.
This is where composition comes into play - instead of defining the behaviour and appearance members in the base class directly, we put them into dedicated classes, each with its own polymorphic hierarchy, and give the base class a member variable for each. Thus, ENTITY HAS A BEHAVIOUR; ENTITY HAS AN APPEARANCE; SPRITE_DRAWING_APPEARANCE IS AN APPEARANCE; SOLID_MOVEMENT_BEHAVIOUR IS A BEHAVIOUR. Since both components are polymorphic, this also allows run-time composition of objects.

The word 'paradigm shift' however is out of line here, since composition is about as old as polymorphism when it comes to object-oriented design. It's just that because it's easier to implement and looks less 'clever', people tend to forget that it is a useful tool (often the best), and favour multiple inheritance.

axilmar

But now we have a problem: Multiple inheritance doesn't allow us to freely combine different behaviours and appearances - we can't derive from SPRITE_DRAWING_ENTITY and SOLID_ENTITY at the same time, because they share a common base class (ENTITY) and thus both provide the same methods. MI, however, only allows combining base classes with mutually exclusive sets of members.

What about virtual base classes? you can combine SPRITE_DRAWING_ENTITY and SOLID_ENTITY if they both inherit from ENTITY as a virtual base class.

Quote:

This is where composition comes into play - instead of defining the behaviour and appearance members in the base class directly, we put them into dedicated classes, each with its own polymorphic hierarchy, and give the base class a member variable for each.

Aha. So components ARE polymorphic after all. They need not be put in component lists.

Quote:

Thus, ENTITY HAS A BEHAVIOUR; ENTITY HAS AN APPEARANCE; SPRITE_DRAWING_APPEARANCE IS AN APPEARANCE; SOLID_MOVEMENT_BEHAVIOUR IS A BEHAVIOUR. Since both components are polymorphic, this also allows run-time composition of objects.

That's good design. I would do it like that, except when a derived behavior depends from the derived appearance or vice versa. Then multiple inheritance is better than composition.

Quote:

The word 'paradigm shift' however is out of line here, since composition is about as old as polymorphism when it comes to object-oriented design. It's just that because it's easier to implement and looks less 'clever', people tend to forget that it is a useful tool (often the best), and favour multiple inheritance.

That's my opinion as well. Renaming something and then marketing it as new is silly.

CGamesPlay
axilmar said:

Aha. So components ARE polymorphic after all. They need not be put in component lists.

The system used in Dungeon Siege does not have any polymorphic components.

I think the core difference between this pattern and object composition is that objects have no code and are only the sum of their property sheet in the editor. In object composition, you can add components to an object, but the object also has its own code base.

SiegeLord

I think the core difference between this pattern and object composition is that objects have no code and are only the sum of their property sheet in the editor. In object composition, you can add components to an object, but the object also has its own code base.

So it's a subset of multiple inheritance idea that disallows the final object to add any code. As axilmar said, this may be better implemented (allows for runtime composition), or looks a little different, but it's still multiple inheritance called a different name.

CGamesPlay
SiegeLord said:

So it's a subset of multiple inheritance idea that disallows the final object to add any code.

Except for the fact that it's composition, and not inheritance, yes.

Indeterminatus

I read about this in one of the Game Programming Gems books, but I don't know which one from the top of my head (although I want to say the fifth volume).

CGamesPlay

Now that we have established (I hope) that there is nothing that can be accomplished with MI but cannot be accomplished with Component-driven design (CDD) and vice versa, what are the relative benefits of using one system over the other, in concrete terms? Specifically, since there are no technical advantages when compared solely based on capacity, the advantages of one system over another are organization and ease of modification.

So, help me compare the two designs.

  • MI keeps things in a structured heirarchy. CDD offers a soup of components with no intrinsic structure.

  • CDD allows one to quickly change behaviors by assigning additional components to individual entities or to specialized templates. MI requires one create a specialized class to add additional behavior.

  • CDD features components that can be reused. MI generally requires code duplication[1].

Can anyone add to this list?

References

  1. MI allows this, but having multiple inheritance past the first level of ancestry creates complex inheritance patterns and is generally frowned upon. Example: creating a 'Boss AI' base class and inheriting that amongst several unrelated classes creates a complicated inheritance scheme.
Steve++
axilmar said:

That's my opinion as well. Renaming something and then marketing it as new is silly.

This isn't just about composition. It's about providing a component framework that happens to use composition.

CDD allows one to quickly change behaviors by assigning additional components to individual entities or to specialized templates. MI requires one create a specialized class to add additional behavior.

Not just "quickly", but also at runtime. That's one of the big differences.

Inheritance is nice to have in the toolkit and I'm sure that even among CDD components there would be a lot of inheritance happening, especially among components that implement the same interfaces. One of the biggest benefits of CDD is that it favours service driven architecture over multiple inheritance spaghetti. I personally favour loosely coupled code.

axilmar
Steve++ said:

This isn't just about composition. It's about providing a component framework that happens to use composition.

I am ok with the framework, but I am not ok with presenting composition as something new.

Quote:

I personally favour loosely coupled code.

I do as well.

Here is something useful to read regarding multiple inheritance:

C++ FQA Lite: Inheritance -- multiple and virtual inheritance

Tobias Dammers

Personally, I avoid all occurrences of MI that do not follow the "one normal base class plus an arbitrary number of interfaces" pattern. In other words, when a class inherits from multiple base classes, then all but one must be interfaces (that is, they contain nothing but pure virtual methods).
A nice problem to illustrate the strengths and weaknesses of both methods: The classic species hierarchy. Let's consider a Dog and an Emu.

A Dog IS A Mammal.
A Dog IS A LandAnimal.

An Emu IS A Bird.
An Emu IS A LandAnimal.

A Mammal IS AN Animal.
A Bird IS AN Animal.
A LandAnimal IS AN Animal.

This doesn't work, obviously, because Dog tries to inherit from two classes that share a common base class.
Using composition, the 'Animal' class's members would be distributed over two components (say, AnimalClass, from which Mammal and Bird derive; and AnimalHabitat, from which LandAnimal, SeaAnimal, etc. derive), both of which with their own independent inheritance tree; class Animal would consist of nothing but instances of the two components.
Using multiple inheritance, Mammal and Bird would still derive from Animal, but LandAnimal would not. They could, however, both implement IAnimal.

Thread #599753. Printed from Allegro.cc