Jump to content

  • Log In with Google      Sign In   
  • Create Account

Composition heavy OOP vs pure entity component systems?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
31 replies to this topic

#1 Midori Ryuu   Members   -  Reputation: 177

Like
5Likes
Like

Posted 20 August 2012 - 12:00 AM

I admit, I have made the sin of overusing, and even abusing inheritance. The first (text) game project that I made when I was taking my OOP course went as far as "Locked door" and "unlocked door" from "Door" and "Room with one door", "Room with two doors", and so on from "Room".

With the (graphical) game I worked on recently, I thought I had learned my lesson and put a limit on using inheritance. However I noticed the problems soon beginning to appear. My root class was beginning to bloat more and more, and my leaf classes were full of duplicate codes.

I thought I was still doing things wrong, and after looking it up online I discovered that I wasn't the only one with this problem. It's how I ended up discovering Entity systems after some thorough research (read: googlefu)

When I began reading on it, I was able to see how clearly it was able to solve the problems I was having with the traditional OOP hierarchy with components. These were in the first readings however. When I stumbled upon more… “radical” ES approaches, such as the one at T-machine.

I began disagreeing with the methods they were using. A pure component system seemed either overkill, or rather, unintuitive, which is probably the strength of OOP. The author goes so far as to say that ES system is the opposite of OOP, and while it may be usable along OOP, it really shouldn’t. I'm not saying it's wrong, but I just didn't feel like a solution I would like to implement.

It was then that I stumbled on Hodgman post, and it really made sense to me. I really didn’t see any conflict between OOP and composition; rather, the two seemed as one.

So to me, and to solve the problems I was having at the beginning of the post, without going against my intuitions, is to still use a hierarchy, however it won’t be a monolithic hierarchy like the ones I used before, but rather a polylithic one (I couldn’t find a word opposite to monolithic), which consists of several, smaller trees.

The following example shows what I mean (this is insipied by an example I found in Game Engine Architecture, Chapter 14).

I would have a small tree for vehicles. The root vehicle class would have a rendering component, a collision component, position component etc..

Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.

The same goes for the Characters. A character would have it's own components, then the Player Class would inherit it, and be given an Input controller, while other enemy classes would inherit from the Character class and be given an AI controller.



I don't really see any problems with this design. Despite not using a pure Entity Controller System, the problem with the bubbling up effect, and the large root class is solved by using a multi-tree hierarchy, and the problem of the heavy, code duplicating leafs is gone since the leafs don't have any code to begin with, just components. If a change needs to be done to the leaf level, then it's as simple as changing a single component, instead of copy pasting the code everwhere.

Of course, being as inexperienced as I am, I did not see any problems when I first started using the single hierarchy, inheritence heavy model, so if there are problems with the model that I'm currently thinking of implementing, I wouldn't be able to see it.

Your opinions?

P.S: I am using Java, so using multiple inheritance to implement this instead of using normal components is not possible.

P.P.S: Intercomponent communications will be done by linking dependent components to each other. This will lead to coupling, but I think it's an ok trade off.

Sponsor:

#2 Hodgman   Moderators   -  Reputation: 30388

Like
6Likes
Like

Posted 20 August 2012 - 12:48 AM

I would have a small tree for vehicles. The root vehicle class would have a rendering component, a collision component, position component etc..

Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.

This is still an abuse of the OO concept of inheritance. You could compose your tank out of a vehicle and a cannon -- e.g.
struct Tank {
  Vehicle vehicle;
  Cannon cannon;
};

The same goes for the Characters. A character would have it's own components, then the Player Class would inherit it, and be given an Input controller, while other enemy classes would inherit from the Character class and be given an AI controller.

As an example of how inheritance is causing unnecessary restrictions here -- what if you want the ability for the player to take over an AI character, and let the AI take control of their previous character? Decoupled composition allows for this:
struct BaseController
{
  Character* controlled;
}
struct PlayerController : BaseController
{
  InputDevice inputs;
  void ReadInputsAndControlCharacter();
};
struct AiController : BaseController
{
  CharacterAi brains;
  void UpdateBrainsAndControlCharacter();
};
void SwapControl( BaseController& a, BaseController& b ) { std::swap( a.controlled, b.controlled ); }
N.B. in the above, CharacterAi, InputDevice and Character are completely decoupled - they don't know about each other at all. The player and AI controller classes form a 'bridge' between these decoupled components -- reading intentions from inputs or AI and passing them on to the character interface (and reading character state ans passing it to the 'brain').
[edit]Sorry for the C++ examples Posted Image

Edited by Hodgman, 20 August 2012 - 12:52 AM.


#3 Midori Ryuu   Members   -  Reputation: 177

Like
2Likes
Like

Posted 20 August 2012 - 01:47 AM


I would have a small tree for vehicles. The root vehicle class would have a rendering component, a collision component, position component etc..

Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.

This is still an abuse of the OO concept of inheritance. You could compose your tank out of a vehicle and a cannon -- e.g.
struct Tank {
  Vehicle vehicle;
  Cannon cannon;
};


How is this different from adding the vehicle's components inside the tank through inheritance and then adding a cannon component? They really seem the same to me. Will it cause the same problem as the one in your second example?

The same goes for the Characters. A character would have it's own components, then the Player Class would inherit it, and be given an Input controller, while other enemy classes would inherit from the Character class and be given an AI controller.

As an example of how inheritance is causing unnecessary restrictions here -- what if you want the ability for the player to take over an AI character, and let the AI take control of their previous character? Decoupled composition allows for this:
struct BaseController
{
  Character* controlled;
}
struct PlayerController : BaseController
{
  InputDevice inputs;
  void ReadInputsAndControlCharacter();
};
struct AiController : BaseController
{
  CharacterAi brains;
  void UpdateBrainsAndControlCharacter();
};
void SwapControl( BaseController& a, BaseController& b ) { std::swap( a.controlled, b.controlled ); }
N.B. in the above, CharacterAi, InputDevice and Character are completely decoupled - they don't know about each other at all. The player and AI controller classes form a 'bridge' between these decoupled components -- reading intentions from inputs or AI and passing them on to the character interface (and reading character state ans passing it to the 'brain').


I was actually thinking about similar problems when I posted. I thought of doing some sort of duck-typing in a scripting language to solve this, but I didn't have an actual idea how to. The way you implemented the controllers is much more elegant. I assume this removes the need for a player and an enemy class and the distinction will be made at runtime when the components are assigned?

If so, then the will the character class have a polymorphic reference to the BaseController which can be either an instance of PlayerController or AIController?

[edit]Sorry for the C++ examples

It's perfectly fine as long as you excuse my inexperience with C++, as I may have understood the example you gave a bit wrong.. >_<

Edited by Midori Ryuu, 20 August 2012 - 01:48 AM.


#4 Hodgman   Moderators   -  Reputation: 30388

Like
8Likes
Like

Posted 20 August 2012 - 02:16 AM

How is this different from adding the vehicle's components inside the tank through inheritance and then adding a cannon component? They really seem the same to me. Will it cause the same problem as the one in your second example?

In practice, in effect, there's no apparent difference... but you need to distinguish between OOP and OOD.
Lots of people use inheritance when writing OOP code out of convenience -- "I want all this stuff in my new class, so I'll inherit it" -- n.b. this same goal can be achieved via composition or inheritance.
However, when designing in OOD, the concepts implemented in OOP languages have very specific meanings and using these concepts implies that you're promising to follow certain rules -- e.g. making a class implies you'll follow the SRP, and inheriting a class implies you follow the LSP.

All these inflexible, monolithic, inheritance-based designs of the past have be born out of teams who understood OOP, but not OOD. That's fine -- the majority of code written in OOP languages probably isn't following OO rules at all, and at the same time, OO designs can be implemented in non-OOP languages, such as C... but it's important to note the destiction between the OO theory of design, and the OOP features in certain languages. They're meant to go together, but don't have to.

Back to the choice of inheritance vs composition here though --
1) The main difference in this case is that with composition, Tank only gains access to Vehicle's public interface, whereas with inheritance, Tank gains access to it's protected interface. This is probably bad as it increases the potential to extra unnecessary coupling.
2) If using inheritance, it would also be valid for Tank to inherit from Cannon, and then compose in a Vehicle... so how do you choose which one is right? If it's an arbitrary choice between the two, then it seem that inheritance is unnecessary, and should probably be avoided. If the answer is to inherit from both, then what happens when you want a tank with 2 cannons, or if you make a transforming super-tank made up of 2 vehicles?

Common wisdom is that you should default to composition for code re-use, and only use inheritance where necessary.

If so, then the will the character class have a polymorphic reference to the BaseController which can be either an instance of PlayerController or AIController?

I had it the other way around, where Character is unaware of the controller classes at all, and the controllers have a polymorphic reference to the character.

You should design your code in layers, where each layer only uses classes in layer below it, or the same layer. You should also generally avoid designs where two classes both know about each other, i.e. dependencies should almost always be 1-way, not cyclic.
For example, C is in the top layer and knows about the public interfaces of A and B. A+B are in the layer below, and each have some complex private details that are wrapped up in a simpler public interface. Also, A and B don't know about each other at all - they're decoupled, which is good; they rely on the next layer of code to glue them together in some useful way. See also the Law of Demeter on principles about loose coupling - you generally want each class to have knowledge of as few other classes as possible, and use sensible layering to glue different bits together into a useful whole.
In this case, the controller is in the top layer ©, and AI and characters are in the bottom layer (A & B). C makes use of A&B's public interfaces, inside it's private details, and then wraps this behaviour up in a new public interface (which might be used by another layer above this).
+-----------+
        |  public   |
        | interface |
        |-----------|
        |Component C|
        |-----------|
        |  private  |
        |  details  |
        +-----------+
          |       |
          |       |
         \|/     \|/
+-----------+   +-----------+
|  public   |   |  public   |   
| interface |   | interface |   
|-----------|   |-----------|   
|Component A|   |Component B|   
|-----------|   |-----------|   
|  private  |   |  private  |   
|  details  |   |  details  |   
+-----------+   +-----------+

Edited by Hodgman, 20 August 2012 - 03:22 AM.


#5 Midori Ryuu   Members   -  Reputation: 177

Like
2Likes
Like

Posted 20 August 2012 - 04:43 AM


How is this different from adding the vehicle's components inside the tank through inheritance and then adding a cannon component? They really seem the same to me. Will it cause the same problem as the one in your second example?

In practice, in effect, there's no apparent difference... but you need to distinguish between OOP and OOD.
Lots of people use inheritance when writing OOP code out of convenience -- "I want all this stuff in my new class, so I'll inherit it" -- n.b. this same goal can be achieved via composition or inheritance.
However, when designing in OOD, the concepts implemented in OOP languages have very specific meanings and using these concepts implies that you're promising to follow certain rules -- e.g. making a class implies you'll follow the SRP, and inheriting a class implies you follow the LSP.

All these inflexible, monolithic, inheritance-based designs of the past have be born out of teams who understood OOP, but not OOD. That's fine -- the majority of code written in OOP languages probably isn't following OO rules at all, and at the same time, OO designs can be implemented in non-OOP languages, such as C... but it's important to note the destiction between the OO theory of design, and the OOP features in certain languages. They're meant to go together, but don't have to.

Back to the choice of inheritance vs composition here though --
1) The main difference in this case is that with composition, Tank only gains access to Vehicle's public interface, whereas with inheritance, Tank gains access to it's protected interface. This is probably bad as it increases the potential to extra unnecessary coupling.
2) If using inheritance, it would also be valid for Tank to inherit from Cannon, and then compose in a Vehicle... so how do you choose which one is right? If it's an arbitrary choice between the two, then it seem that inheritance is unnecessary, and should probably be avoided. If the answer is to inherit from both, then what happens when you want a tank with 2 cannons, or if you make a transforming super-tank made up of 2 vehicles?

Common wisdom is that you should default to composition for code re-use, and only use inheritance where necessary.


The problem is I am trying to keep the layout of my code intuitive (to me at least). As in, a tank "Is a" vehicle but "has a" cannon. So that is why I opted to go with limited inheritance in some places, despite composition being a better solution, since the damage that it might cause will be rather small in comparison to large inheritance chains.

I do agree with you that it's better to compose here, but I kind of feel that as long as I don't allow the inheritance to expand more than 2-3 levels deep it's an acceptable tradeoff to the advantages given by composition. Then again, it is the (bad) OOP thinking that's immersed in my head thinking that, and the examples you keep giving are really helping to dispel that kind of thinking (Transforming super-tank made up of two vehicles! Posted Image)

It will be a pain, and probably cost me more development, but I will try to do things the correct way this time, since I know that once I do things the right way, it will be much easier doing it the right way the second time, and third time and so on.


If so, then the will the character class have a polymorphic reference to the BaseController which can be either an instance of PlayerController or AIController?

I had it the other way around, where Character is unaware of the controller classes at all, and the controllers have a polymorphic reference to the character.

You should design your code in layers, where each layer only uses classes in layer below it, or the same layer. You should also generally avoid designs where two classes both know about each other, i.e. dependencies should almost always be 1-way, not cyclic.
For example, C is in the top layer and knows about the public interfaces of A and B. A+B are in the layer below, and each have some complex private details that are wrapped up in a simpler public interface. Also, A and B don't know about each other at all - they're decoupled, which is good; they rely on the next layer of code to glue them together in some useful way. See also the Law of Demeter on principles about loose coupling - you generally want each class to have knowledge of as few other classes as possible, and use sensible layering to glue different bits together into a useful whole.
In this case, the controller is in the top layer ©, and AI and characters are in the bottom layer (A & B). C makes use of A&B's public interfaces, inside it's private details, and then wraps this behaviour up in a new public interface (which might be used by another layer above this).
+-----------+
		|  public   |
		| interface |
		|-----------|
		|Component C|
		|-----------|
		|  private  |
		|  details  |
		+-----------+
		  |	   |
		  |	   |
		 \|/	 \|/
+-----------+   +-----------+
|  public   |   |  public   |  
| interface |   | interface |  
|-----------|   |-----------|  
|Component A|   |Component B|  
|-----------|   |-----------|  
|  private  |   |  private  |  
|  details  |   |  details  |  
+-----------+   +-----------+


Ah yes, that makes a lot more sense, since the player might be in control of many characters, and it also enables the AI to reason in term of groups (cooperation, etc..) instead of just simple characters. That and once the lower layers are taken care of, I assume programming the game and scripting will be a breeze!

On another forum where I asked this question it was also pointed how I the setup I made would have caused, for example, the coupling of the characters and rendering to be coupled, which will come in the way in case of a server-client architecture is done for multiplayer.

By following your way the rendering will be decoupled from the characters and allows for client side rendering only.

Thank you for the time you're taking to answer my questions and all the free tips you're giving us!



Just a small question though, (I know it's example and implementation specific and doesn't have much to do with the thread but) why is the AI in the layer below the controller? Is it because it will ask C to provide the public interface of B?
You say that I should talk to the below layers, and the dependency should be 1 way, but won't this be better achieved if the A.I is above the controller for it to be able to control the characters, tanks,etc.. through the Controller which provides a public interface wrapping the lower layer?

Thank you again for your patience to my noobiness!

Edited by Midori Ryuu, 20 August 2012 - 05:37 AM.


#6 Telastyn   Crossbones+   -  Reputation: 3726

Like
3Likes
Like

Posted 20 August 2012 - 06:27 AM

Thank you again for your patience to my noobiness!


Just to intrude for one moment: your posts are well formed and ask good questions. Frankly, most professional programmers I've worked with (albeit in non-gamedev) wouldn't even think to look into how to design things better; let alone speak intelligently about how/why they did their design a certain way.

Being humble is good, but make sure your self-evaluation is accurate too.

#7 dmatter   Crossbones+   -  Reputation: 3101

Like
4Likes
Like

Posted 20 August 2012 - 07:38 AM

I do agree with you that it's better to compose here, but I kind of feel that as long as I don't allow the inheritance to expand more than 2-3 levels deep it's an acceptable tradeoff to the advantages given by composition.

I tend to think that more than 1-2 levels is questionable. Where 1 level obviously means you're extending an interface or abstract base class and 2 levels typically means you've introduced an abstract helper class that implements some or all of an interface and maybe applies the Template Pattern to simplify things for subclasses. Any deeper and it starts to smell and I begin to wonder whether something has gone wrong.

Obviously "is-a" is itself a multi-faceted concept and I have long since decided that using inheritance to model the structural or logical is-a relationships is not the way to go. These days things like LSP teach us that you should really just use it for just the behavioural is-a relationships, in other words whenever you need polymorphism.

Some concrete examples:

Tank is-a Vehicle (logical)
Duck is-a Bird is-a Animal (logical)

Square is-a Rectangle (structural)
RoomWithTwoDoors is-a RoomWithOneDoor is-a Door (structural)

Tank is-a GameEntityEventListener (behavioural - tank will polymorphically respond to game events such as onUpdate)
XMLParser is-a TextParser (behavioural - subclass implements parse(String text))

(Note: Please don't think that my distinction between structural and logical is-a relationships is a formal one, it's more intuitive than anything).


So I would say there are two okay reasons to use inheritance, first and foremost is this:

1) Use inheritance to gain polymorphic behaviour (but if you need polymorphic behaviour, otherwise you're over-engineering).

The second I permit for pragmatic reasons in languages that don't support mixins as a first-class concept:

2) May use inheritance as a way to mix-in or reuse an implementation of a public class interface.

The key part to point 2 is the word "public", if you're using inheritance just to gain reuse of private code then it's likely you've modelled a structural is-a relationship (which is bad) and is also subject to Hodgman's explanation given earlier about whether Tank should inherit or compose Vehicle.
Point 2 is easily abused though, think of it simply as a get-out-clause for when it's pragmatic :-)

The logical relationships in particular may well fall out naturally anyway - you could imagine renaming GameEntityEventListener into simply Entity and then you have achieved both logical and behavioural subclassing, in this case it is okay since it was the behavioural aspect that you designed for and the logical aspect is purely cosmetic but aids in your comprehension.

The real point is that inheritance is not being used to model real-life, or mimic some scientific classification of types - in software it is merely a tool that allows us to separate specialised implementations from generalised implementations from interfaces.

Edited by dmatter, 20 August 2012 - 07:57 AM.


#8 Hodgman   Moderators   -  Reputation: 30388

Like
2Likes
Like

Posted 20 August 2012 - 07:58 AM

Square is-a Rectangle

Sorry to use this as a good example of how easy it is to violate the LSP Posted Image
If the Rectangle interface is as below, the it's safe to say that "Square is-a Rectangle":
class Rectangle {
  virtual int Width();
  virtual int Height();
};
...but, if the Rectangle interface looks like this:
class Rectangle {
  virtual int GetWidth();
  virtual int GetHeight();
  virtual void SetWidth(int w);
  virtual void SetHeight(int h);
};
...then now "Square is-a Rectangle" is no longer valid! This is unintuitive, because logically/mathematically "a square is a rectangle" is true!
To explain: LSP basically says that any operation that works on the base class, must continue to work as expected if you substitute in a derived class -- and the following function can be expect to work on Rectangles, but will fail for Squares:
void TestRectangle( Rectangle& r )
{
  r.SetWidth(5);
  r.SetHeight(10);
  int area = r.GetWidth() * r.GetHeight();
  ASSERT( area == 5*10 );
}

Just a small question though, (I know it's example and implementation specific and doesn't have much to do with the thread but) why is the AI in the layer below the controller? Is it because it will ask C to provide the public interface of B?

Yeah any specific example is going to have to come with specific requirements, etc... In my example, I was assuming that:
* the AI code can all be self-contained, with it's user/owner passing it information about where it is, etc, and retrieving information such as what it wants to do.
* the character code can all be self-contained, with it's user/owner passing it information about how it should move, etc, and retrieving infomation such as where it currently is.
* The controller is then a higher-level 'glue' class that plugs these two components into each other (N.B. the inputs/outputs of each class are a close match).
* A different controller could replace the AI class with an InputDevice class, to instead glue together an InputDevice to a Character.

In this design, the InputDevice, AI and Character are all completely decoupled -- they don't directly depend on each other.
However, yes, it would also be valid for the Character to not be aware of the AI, but have the AI directly use the Character interface (without the need for a 'glue' controller in the middle).

#9 nox_pp   Members   -  Reputation: 490

Like
0Likes
Like

Posted 20 August 2012 - 09:30 AM

I'm not much of a proponent of OO in general anymore, so take what I say in light of that fact.

I wrote this little series on component based entities systems a few years ago. Since then, I've extensively toyed with the idea, cleaned it up, and taken it much further.

A pure component system seemed either overkill, or rather, unintuitive, which is probably the strength of OOP.


Why does a pure component system seem like overkill? I understand that the idea can be repulsive at first, but only given a prior history in adherence to strict OOP principles. It's a perfect fit for the inherently dynamic nature of video games.

Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.


Isn't the definition of tank rather arbitrary though? What if, without recompiling, you could compose a completely new sort of vehicle at runtime? Is your base vehicle class compatible with a flying vehicle? Or do you have to derive a flying and non-flying vehicle from it? And then, what if you have something that occupies a middle ground...like a "hover tank?" As you can see, a class hierarchy still causes a combinatoric explosion of rigid classes.

I'm retreading ground that other posters have covered, but my main point is to cause you to rethink your attachment to OOP with regard to entity systems. It's just not a good fit.

Between Scylla and Charybdis: First Look <-- The game I'm working on

 

Object-Oriented Programming Sucks <-- The kind of thing I say


#10 Hodgman   Moderators   -  Reputation: 30388

Like
2Likes
Like

Posted 20 August 2012 - 10:12 AM

I'm retreading ground that other posters have covered, but my main point is to cause you to rethink your attachment to OOP with regard to entity systems. It's just not a good fit.

Note that any good "component system" is basically just a DDL or scripting-language or serialization-library for OO structures.
i.e. good OO code is component based -- you don't need a "component framework" to allow you to write good component-based OO code (inheriting everything from Component is another abuse of inheritance, IMHO). If you want the game to be data-driven, you just need a good serialization library, or a scripting language.

#11 CC Ricers   Members   -  Reputation: 623

Like
0Likes
Like

Posted 20 August 2012 - 01:16 PM

This topic's been real helpful for me in knowing when and where to add extra functionality in objects being handled by game systems. I'm using a screen system in a program, where Screen objects may have different logic in its methods for updating, drawing and (if needed) handling input. The system loops through a list of Screens to do the updating and drawing.

Currently, the Screen class has virtual functions for draw, update and handleInput, and I have several objects that derive from Screen to replace these functions with. This is how I'm giving each Screen its own behavior. A plain old Screen can be used too, if you just need to render a background or some splash image. But this topic got me to think if I'm overusing inheritance, because I had come up with special screens that have more specialized functions:

class Screen
{
  protected virtual void Update();
  protected virtual void Draw(GraphicsStuff graphics);
}

class InteractiveScreen : Screen
{
  protected virtual void HandleInput(PlayerInput input);
}

class MenuScreen : InteractiveScreen
{
  Menu menu;
}

Then I got to thinking, I don't need that added level of inheritance, if I can just add whatever classes I need inside the Screens to provide the behavior and functions. So I consider refactoring the code to this:

// Screen class is the same as before

class InteractiveScreen : Screen
{
  PlayerInput input;
}

class MenuScreen : Screen
{
  PlayerInput input;
  Menu menu;
}

Now I have just one level of inheritance, and the derived objects differ in the components that they need. Is this a good step to move to a more component-based design and away from the more rigid hierarchy-like design? Also, GraphicsStuff could be considered a component. Still, would you consider putting GraphicsStuff as a protected member in the base screen object if all Screens that inherit the class are required to use it?
My development blog: Electronic Meteor

#12 Inferiarum   Members   -  Reputation: 732

Like
0Likes
Like

Posted 20 August 2012 - 05:12 PM

There is one idea I had recently. Namely to build an entity system without the components. I have to say with a lot of the designs I do not see the need for having components probably inherited from a Component class etc. .

What I am doing is to implement Systems just as you would with the component based approach, but leave the data locally in the system. For example, we have a physics system which operates on several entities, so it has a list of the physics data for those entities. Then we introduce dependencies between systems, e.g. the render system needs data from the physic systems etc. . With the information about the dependencies the update order can be determined in some way (if you want to add systems after compilation that is).

This approach should be fairly easy to parallelize, since you have the data locally with a well defined communication interface. Initially the idea came from designing FPGAs with hardware description languages, where everything runs in parallel.

#13 phil_t   Crossbones+   -  Reputation: 3926

Like
0Likes
Like

Posted 20 August 2012 - 08:39 PM

Then we introduce dependencies between systems, e.g. the render system needs data from the physic systems etc. . With the information about the dependencies the update order can be determined in some way (if you want to add systems after compilation that is).


Hmm... maybe I'm not understanding you correctly, but that doesn't sound like good design. Why would the render system need data from the physics systems? You should be able to render objects that have no physics element to them.

If the data is in the systems... where would you put something like position/transform?

#14 Hodgman   Moderators   -  Reputation: 30388

Like
0Likes
Like

Posted 20 August 2012 - 09:11 PM

What I am doing is to implement Systems just as you would with the component based approach, but leave the data locally in the system.

AFAIK, this is the standard way to implement "entity/component frameworks". One of the major attraction of this style of "entity" is the locality of reference improvements by pool-allocating your components, rather than embedding them inside entities.

Why would the render system need data from the physics systems? You should be able to render objects that have no physics element to them.
If the data is in the systems... where would you put something like position/transform?

Generally your physics engine wants to embed transforms somewhere in it's rigid-bodies (details on how/where it stores this data is rightly encapsulated), but your rendering system needs also transforms in a cbuffer/UBO in order to render a representation of those rigid-bodies. Physical entities thus require the data to be copied out of the rigid-bodies and into the cbuffers. Non-physical entities can update their cbuffers by some other mechanism, or use read-only cbuffers if fully static.
This would normally be done either by: linking individual rigid-body / cbuffer components together so that the individual data can flow, or linking the physics system to the render system so that the data can flow en masse.

#15 Midori Ryuu   Members   -  Reputation: 177

Like
0Likes
Like

Posted 20 August 2012 - 11:28 PM

Wow a lot of replies since yesterday! I didn't think this topic would get that much attention! Posted Image

Thank you all for your replies. Posted Image

My reply may or may not make much sense. I'm quite feverish at the moment.


Thank you again for your patience to my noobiness!


Just to intrude for one moment: your posts are well formed and ask good questions. Frankly, most professional programmers I've worked with (albeit in non-gamedev) wouldn't even think to look into how to design things better; let alone speak intelligently about how/why they did their design a certain way.

Being humble is good, but make sure your self-evaluation is accurate too.


Thank you for the compliment! Maintaining the balance between humble and cocky is a bit difficult for me, so I prefer to stay on the safe side. Posted Image


I do agree with you that it's better to compose here, but I kind of feel that as long as I don't allow the inheritance to expand more than 2-3 levels deep it's an acceptable tradeoff to the advantages given by composition.

I tend to think that more than 1-2 levels is questionable. Where 1 level obviously means you're extending an interface or abstract base class and 2 levels typically means you've introduced an abstract helper class that implements some or all of an interface and maybe applies the Template Pattern to simplify things for subclasses. Any deeper and it starts to smell and I begin to wonder whether something has gone wrong.

Obviously "is-a" is itself a multi-faceted concept and I have long since decided that using inheritance to model the structural or logical is-a relationships is not the way to go. These days things like LSP teach us that you should really just use it for just the behavioural is-a relationships, in other words whenever you need polymorphism.

Some concrete examples:

Tank is-a Vehicle (logical)
Duck is-a Bird is-a Animal (logical)

Square is-a Rectangle (structural)
RoomWithTwoDoors is-a RoomWithOneDoor is-a Door (structural)

Tank is-a GameEntityEventListener (behavioural - tank will polymorphically respond to game events such as onUpdate)
XMLParser is-a TextParser (behavioural - subclass implements parse(String text))

(Note: Please don't think that my distinction between structural and logical is-a relationships is a formal one, it's more intuitive than anything).


So I would say there are two okay reasons to use inheritance, first and foremost is this:

1) Use inheritance to gain polymorphic behaviour (but if you need polymorphic behaviour, otherwise you're over-engineering).

The second I permit for pragmatic reasons in languages that don't support mixins as a first-class concept:

2) May use inheritance as a way to mix-in or reuse an implementation of a public class interface.

The key part to point 2 is the word "public", if you're using inheritance just to gain reuse of private code then it's likely you've modelled a structural is-a relationship (which is bad) and is also subject to Hodgman's explanation given earlier about whether Tank should inherit or compose Vehicle.
Point 2 is easily abused though, think of it simply as a get-out-clause for when it's pragmatic :-)

The logical relationships in particular may well fall out naturally anyway - you could imagine renaming GameEntityEventListener into simply Entity and then you have achieved both logical and behavioural subclassing, in this case it is okay since it was the behavioural aspect that you designed for and the logical aspect is purely cosmetic but aids in your comprehension.

The real point is that inheritance is not being used to model real-life, or mimic some scientific classification of types - in software it is merely a tool that allows us to separate specialised implementations from generalised implementations from interfaces.


Yeah, I am starting to see how just because something -seems- "logically" correct, doesn't necessary mean it -is- correct. OOP does seem like a double edged sword from that point.

I am guilty of using inheritance to gain reuse of private code too.. Being fresh out of college and having worked only for a month now, I still have the whole "Inheritance, inheritance, inheritance" thing glued to me, along with the use OOP to model real life. I didn't even know about composition until a couple of days ago!

Thank you again for your great reply! Posted Image

I'm not much of a proponent of OO in general anymore, so take what I say in light of that fact.

I wrote this little series on component based entities systems a few years ago. Since then, I've extensively toyed with the idea, cleaned it up, and taken it much further.


A pure component system seemed either overkill, or rather, unintuitive, which is probably the strength of OOP.


Why does a pure component system seem like overkill? I understand that the idea can be repulsive at first, but only given a prior history in adherence to strict OOP principles. It's a perfect fit for the inherently dynamic nature of video games.

I actually stumbled upon your website when searching about entity systems. I followed the links you gave as well. Lovely reads!
Yes it does seem a bit "repulsive" to me, especially the pure component (no entity) style. It's why I was trying to mix in a "flavor" of inheritence to sweeten the taste.
It seemed overkill though, because I will be mostly working on small games, and I already have made a game using the traditional system, so as Hodgman said, it "worked". But on the other hand I know that if I want to move on to larger games, I will have to learn to do things the right way in small games first.
Mostly, a lack of experience is making me tread these things lightly.


Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.


Isn't the definition of tank rather arbitrary though? What if, without recompiling, you could compose a completely new sort of vehicle at runtime? Is your base vehicle class compatible with a flying vehicle? Or do you have to derive a flying and non-flying vehicle from it? And then, what if you have something that occupies a middle ground...like a "hover tank?" As you can see, a class hierarchy still causes a combinatoric explosion of rigid classes.

I'm retreading ground that other posters have covered, but my main point is to cause you to rethink your attachment to OOP with regard to entity systems. It's just not a good fit.


I was thinking of making things rather static, and try to think of all possible combinations and hard-code them. Though with this thread, I see how things would have gone wrong. (Which was actually my question! Posted Image )

-------------------------------------------------------------------

I really wanted to jump straight to programming and start to get familiar with LibGDX after moving to it from Slick2D, but it's probably better now to take some time to think thoroughly how I will implement all this..

I will just have to ignore this programmer's itch for the moment..

Thank you all again for your great replies!

Edited by Midori Ryuu, 20 August 2012 - 11:38 PM.


#16 LorenzoGatti   Crossbones+   -  Reputation: 2705

Like
1Likes
Like

Posted 21 August 2012 - 03:55 AM

I'd like to pick on the Tank example because it is practical and rooted in actual requirements.

Then a tank, a subclass of vehicle would inherit those components from it, and be given it's own "cannon" component.

  • A Tank class is unlikely to have a purpose.
    Is a tank fundamentally different from a jeep with a machine gun, from a truck-mounted AA rocket platform, and from an endless variety of other armed vehicles? Presumably not: all are vehicles, and the AI or player has the same task of moving and shooting appropriately.
  • Moreover, why should a tank be armed with only one weapon? And why has that weapon to be a Cannon? By the way, why should a specialized Cannon class exist?
  • Unless you do something very special with certain kinds of weapon or vehicle, an ArmedVehicle class, inheriting from Vehicle and adding a list of Weapon components, should be all the inheritance you need; the rest can be data-driven object creation.
  • Even the need for distinguishing Vehicle and ArmedVehicle classes is questionable: an unarmed Vehicle can simply have an empty weapon list, getting rid of all remaining pointless inheritance and pointless classes.
  • Collapsing hierarchies doesn't imply losing or wasting type information. You can keep collections of objects according to some attribute (e.g. all vehicles that have one or more weapons and need the appropriate extra method calls to shoot in the appropriate part of a turn) and use meaningful and principled flags, fields and methods instead of slippery RTTI for "duck typing" (e.g. when you need to know if vehicles can tread on frozen rivers in the winter campaign, instead of a clumsy HeavyVehicle class you should implement an ad hoc method: bool Vehicle.wouldCollapseIce(const IceTypeEnum&), taking into account vehicle weight and size).

The problem is I am trying to keep the layout of my code intuitive (to me at least). As in, a tank "Is a" vehicle but "has a" cannon. So that is why I opted to go with limited inheritance in some places, despite composition being a better solution, since the damage that it might cause will be rather small in comparison to large inheritance chains.

I do agree with you that it's better to compose here, but I kind of feel that as long as I don't allow the inheritance to expand more than 2-3 levels deep it's an acceptable tradeoff to the advantages given by composition. Then again, it is the (bad) OOP thinking that's immersed in my head thinking that, and the examples you keep giving are really helping to dispel that kind of thinking (Transforming super-tank made up of two vehicles!

You sound like you have an urge to hurt yourself with inheritance that you are unable to overcome, like an addiction that you can only repress enough to avoid disastrous consequences.
Unfortunately, damage control doesn't apply to software design as it does to vices: any bad choice, however small, is permanent, and it affects your ability to progress until it is corrected. If your first draft contains too much inheritance, make sure it remains only a first draft.

Edited by LorenzoGatti, 21 August 2012 - 03:57 AM.

Produci, consuma, crepa

#17 slayemin   Members   -  Reputation: 2609

Like
1Likes
Like

Posted 21 August 2012 - 04:42 AM

One other advantage to the Entity-component system is that it lends itself very well towards parallelism. When you've got processors with multiple cores, multiple processors, or multiple networked computers each with their own processors, it only makes sense to distribute the processor work load to make use of the available resources (if it provides a performance boost). I've successfully implemented an entity-component system which distributes the components across multiple cores, but haven't spent a lot of time and effort to see if it works over networked computers. It's mostly been a proof of concept and I've been meaning to flesh it out and do a thorough write up with code samples.

One downside of the entity-component system is that it incurrs a significant overhead cost in terms of programmer time (at least it does in mine since I'm using a message passing system). Every get/set accessor function has to be a message which can be passed between entities and components. This can start to cost 70% of your time every time you create new component classes, and adding this extra code can become rather tedious after you've done it a lot.

The big win with the entity-component systems though is that NO game objects are defined in your code. I define every entity within an XML file. When the game loads, it parses the XML file for entities, reads in the templated components which compose the entity, and then instantiates the entity based off of a "blue print" (or whatever term you want to use). The dynamicism for game objects depends entirely on how flexible the components are and how capable they are of supporting whatever you want to build. Creating new objects in a game is thus, much like combining lego bricks together in new and interesting ways.

Eric Nevala

Indie Developer | Dev blog


#18 Hodgman   Moderators   -  Reputation: 30388

Like
0Likes
Like

Posted 21 August 2012 - 05:08 AM

One downside of [using a message passing system] is that it incurrs a significant overhead cost in terms of programmer time. Every get/set accessor function has to be a message which can be passed between entities and components. This can start to cost 70% of your time every time you create new component classes, and adding this extra code can become rather tedious after you've done it a lot.

If you're writing a lot of repetitive boilerplate code, you can probably automate it with macros and templates. For example, below is a "mover component" (designed to move a scene-graph node's transform according to the data in a spline component) from my previous hobby-engine, which used message-passing to automatically and efficiently parallelize everything:
[source lang=cpp]//movercomponent.h class CMoverComponent : public CComponent { public: seComponent( CMoverComponent, CComponent, CDemoSystem ) seNoInterfaces seNoProperties seConstructors seConstructor3( CSplineComponent&, spline, CNode&, node, float, wl ) seConstructor4( CSplineComponent&, spline, CNode&, node, float, wl, float, offs ) seConstructorsEnd seMessage1( void, Update, float, deltaTime ); private: seMessage1( void, Move, const PositionRotation&, sample ); CMoverComponent( CDemoSystem&, CEntity&, CSplineComponent&, CNode&, float wl, float offs=0 ); WeakSpline m_pSpline; WeakNode m_pNode; float m_Wavelength, m_Fraction; };//movercomponent.cppseImplementComponent( CMoverComponent )CMoverComponent::CMoverComponent( CDemoSystem& s, CEntity& p, CSplineComponent& spline, CNode& node, float wl, float offs ) : CComponent(s,p), m_pSpline(&spline), m_pNode(&node) , m_Wavelength(wl), m_Fraction(offs){ s.AddMover( Ref(*this) );}seFunction1( 0, void, CMoverComponent, Update, float, deltaTime ){ m_Fraction += deltaTime * m_Wavelength; m_Fraction -= floorf( m_Fraction ); if( m_pSpline && m_pNode ) Move( m_pSpline->GetSample( m_Fraction ) );}seFunction1( 0, void, CMoverComponent, Move, const PositionRotation&, sample ){ ASSERT( m_pNode ) CVector3 pos(sample.first); CVector3 rot = CVector3( sample.second.x, sample.second.y, 0 ); CVector3 scale(1,1,1); m_pNode->SetTransform( pos, rot, scale );}[/source]N.B. since throwing out this engine and starting again, my macros have got even easier to use, I've stopped inheriting everything from Component, and the binding system is less intrusive (classes are authored as usual, then have a block of macros in the CPP to bind them to the data-driven magic) ;)

Edited by Hodgman, 21 August 2012 - 05:11 AM.


#19 Mito   Members   -  Reputation: 855

Like
0Likes
Like

Posted 21 August 2012 - 05:30 AM

This topic's been real helpful for me in knowing when and where to add extra functionality in objects being handled by game systems. I'm using a screen system in a program, where Screen objects may have different logic in its methods for updating, drawing and (if needed) handling input. The system loops through a list of Screens to do the updating and drawing.

Currently, the Screen class has virtual functions for draw, update and handleInput, and I have several objects that derive from Screen to replace these functions with. This is how I'm giving each Screen its own behavior. A plain old Screen can be used too, if you just need to render a background or some splash image. But this topic got me to think if I'm overusing inheritance, because I had come up with special screens that have more specialized functions:

class Screen
{
  protected virtual void Update();
  protected virtual void Draw(GraphicsStuff graphics);
}

class InteractiveScreen : Screen
{
  protected virtual void HandleInput(PlayerInput input);
}

class MenuScreen : InteractiveScreen
{
  Menu menu;
}

Then I got to thinking, I don't need that added level of inheritance, if I can just add whatever classes I need inside the Screens to provide the behavior and functions. So I consider refactoring the code to this:

// Screen class is the same as before

class InteractiveScreen : Screen
{
  PlayerInput input;
}

class MenuScreen : Screen
{
  PlayerInput input;
  Menu menu;
}

Now I have just one level of inheritance, and the derived objects differ in the components that they need. Is this a good step to move to a more component-based design and away from the more rigid hierarchy-like design? Also, GraphicsStuff could be considered a component. Still, would you consider putting GraphicsStuff as a protected member in the base screen object if all Screens that inherit the class are required to use it?


Well, in your case, the inheritance is behavioral, so i don't think you gain to much by eliminating it. maybe you can do something like this:

// Screen class is the same as before
class PlayerInput
{
  Screen controlledScreen;
}

class MenuScreen : Screen
{
  Menu menu;
}

this way you can controll prety much any screen with your controller (a proper name would be better, but i could not think of one at this moment...)

#20 CC Ricers   Members   -  Reputation: 623

Like
0Likes
Like

Posted 21 August 2012 - 08:49 AM

Well, in your case, the inheritance is behavioral, so i don't think you gain to much by eliminating it. maybe you can do something like this:

// Screen class is the same as before
class PlayerInput
{
  Screen controlledScreen;
}

class MenuScreen : Screen
{
  Menu menu;
}

this way you can controll prety much any screen with your controller (a proper name would be better, but i could not think of one at this moment...)


Hey, thanks for being the one to answer me. You are right in that in my usage, the inheritance is behavioral. From what I've read on proper use of inheritance, this usage is fine, as the behaviors are different enough to replace original ones declared in the base class. I just didn't know if I was taking it too far with the possibility of having different menus.

Also, I cannot see how putting the screen inside the Input would be an improvement- to me it's turning the screen setup on its head. Aside from that I decided on to just leave PlayerInput out of the screens entirely, and just passing on PlayerInput to the screen's HandleInput method.

I think part of the reason inheritance is overused to add on so many features on top of existing ones can in one way be blamed for Java's use of the "extends" keyword. People then see it as a means to very literally extend to the functionality of an existing class and not just replace what was in it.

But I guess that is the process- you usually start with several classes, find similarities and decompose the classes until they become generic and then you will only need one generic, reusable class that depends on outside data for its behavior.

That's what I've been noticing lately with my screen system. The screens have the ability to transition in and out of view, with user-defined times (as far as user-defined animations I'm working on it). This added a lot of code bloat to the Screen class, so I isolated all this transition stuff into its own Transition class. Now every screen just has a Transition that it can use internally.

Another thing is the menus and splash screens. Menus have a list of MenuEntries being clickable text entries with events. Splash screens are non-interactive, stay on for a fixed amount of time on the center of the screen, and leave. They can have text or an image. In the essence of time, I reused the MenuEntry class to display text on the splash screen, ignoring its clickable functionality. Having that unused baggage is a waste of code, and also potentially a waste of memory (though in practice I don't think it's much of an inconvenience to modern computers). At that point I realize it would then be necessary to make MenuEntry more generic, maybe call it TextEntry. And then it can be decomposed further, so an actual MenuEntry class would just be a container for a TextEntry and a Button.

Going back to the Transition class, I realize I have something reusable here. Menu entries can use this, so it would be possible for them to transition independently of the screen they're in. All this so far has been my speculation and not implemented in code yet, but it has gotten my mind already running on how I will carry it out. I don't think I will go as far as to add a messaging system (at least not yet), as I haven't encountered too many cases where screens and other components need to talk to each other. I'm just passing references

Speaking of that, if I was looping through my screens or other list of objects to update (as I am now) if some objects' updates depend on communicating with other objects, does parallelism get thrown out the window? And I don't just mean in CPU terms, but the idea that updates of the objects can be order-independent.
My development blog: Electronic Meteor




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS