Jump to content

  • Log In with Google      Sign In   
  • Create Account


Outboard component-based entity system architecture


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
106 replies to this topic

#41 madeso   Members   -  Reputation: 563

Like
0Likes
Like

Posted 16 September 2007 - 05:45 AM

My design is based somewhat on the gems article, and the links posted earlier. Main design principle is flexible and readable code. My terminology is a little different but that shouldn't be a problem(3 says that it belongs in a 3d world, entity=>object). This is pseudo C++.

An object is built from components:
class Object3 {
map<string, shared_ptr<Component3>> mComponents;
Component3* getComponent(string iName);
};

class Component3 {
virtual MessageResult handleMessage(Message& iMessage);
Object3* mOwner;
};

class Message {
MessageType getMessageType() const;
private:
MessageType mType;
};

class UpdateMessage : Message {
UpdateMessage(real iTime) : Message(MT_UPDATE), time(iTime);
const real time;
};




I tell the objects to render and/or update with messages.

The DisplayComponent3 wants to know where to display the mesh, and the current skeleton pose(among other things). This is done with some ugly, but flexible, code somewhat similar to dataports:

class DataBase {
};

template<class T>
class Data : DataBase {
T t;
T& getReference() { return t; }
// some virtual void setValue(GenericTypeSuchAsStringOrXmlNode)=0;
};

class DataContainer {
map<string, shared_ptr<DataBase> > mData;
DataBase& getData(string iName);
};

shared_ptr<DataBase> BuildData(string& iType); // probably some factory

template<class T>
T& GetDataBase(DataContainer& iContainer, string iName, string iType) {
if( !iContainer.hasData(iName) ) iContainer.addData( iName, BuildData(iType) );
return ((Data<T>& )iContainer.getData(iName)).getReference();
}

// vec3 is a vector in 3d
vec3& GetDataVec3(DataContainer& iContainer, string iName) {
return GetDataBase<vec3>(iContainer, iName, "vec3");
}

// usage
class MyComponent3 : Component3 {
MyComponent3(Object3& iOwner) : Component3(iOwner), mLocation(GetDataVec3(getDataConatiner(), "location")) {}
vec3& mLocation;
};




Since it's a lazy eval Objects only store what the components exchange, so if the object doesn't need the location, it doesn't have it. Since the varaible source is string-based theese can be read from a cfg and with this principle it was simple to apply the RandomMovement (pretty useless but fun), and the WithinRect component to the 2d cursor(location based), from the background-animation(uv based).

I'm thinking of adding some sort of layer system, so I can add components that (temporarily) remove others from some/all messages. Such components may be a Tranqualized, or ResurectAfterSomeTime component.

I'm unsure why subsystems are hard to access with components. If some component want access to some subsystem all it does is something like this:
class SomeComponent3 : Component3 {
SomeComponent3(Object3& iOwner) : Component3(iOwner), mSomeSystem( (dynamic_cast<SomeSystem&>(iOwner.getWorld()
.getSystem("SomeSystem"))) ) {
}
SomeSystem& mSomeSystem;
};


Sponsor:

#42 Sneftel   Senior Moderators   -  Reputation: 1776

Like
0Likes
Like

Posted 18 September 2007 - 12:53 PM

Quote:
Original post by sirGustav
I'm unsure why subsystems are hard to access with components. If some component want access to some subsystem all it does is something like this:
class SomeComponent3 : Component3 {
SomeComponent3(Object3& iOwner) : Component3(iOwner), mSomeSystem( (dynamic_cast<SomeSystem&>(iOwner.getWorld()
.getSystem("SomeSystem"))) ) {
}
SomeSystem& mSomeSystem;
};

Or perhaps iOwner.getOwner().getWorld().getGame().getSomeSystem().getSomeSubSystem()? And even then, only if each level of the tree knows its parent. That won't always be the case; in fact, in a good design it should rarely be the case.

#43 madeso   Members   -  Reputation: 563

Like
0Likes
Like

Posted 20 September 2007 - 08:13 AM

It should probably be worth mentioning that I got two different systems, world and game. World is based in the world file(the pathfinding system as an example). Game is more globally based, as in file, state and window systems.

So we get the pathfinding like this:
mPathfinding( (dynamic_cast<PathfindingSystem&>(
iOwner.getWorld().getSystem("Pathfinding") )))

* so iOwner is the object that hold the datas, this component(and others) and most important
* the world that it belong to. The world can be render()ed tick()ed and frame()d (frame to frame update) and receives events. It also holds the game systems.
* at last you request the system you want. You might prefer .getPathfindingSystem(), I kinda like dynamic_cast<PathfindingSystem&>(.getSystem("Pathfinding"))

besides...

.. why do you need to access some system through another, or need components that use a "low-level" system?
Let's say we have a Render component. It interfaces with the Display system. The DisplaySystem gets some mesh and displays it according to some properties. The mesh is locally created(as in Mesh myMesh;) and loaded with the help of the 3d File System(as in mFileSystem.loadMesh(&myMesh);). The DisplaySystem is dependent on the Window System and the 3d File System is dependent on the File System, but none of the components should care.

#44 Sneftel   Senior Moderators   -  Reputation: 1776

Like
0Likes
Like

Posted 23 September 2007 - 04:03 AM

Quote:
Original post by sirGustav
So we get the pathfinding like this:
mPathfinding( (dynamic_cast<PathfindingSystem&>(
iOwner.getWorld().getSystem("Pathfinding") )))

* so iOwner is the object that hold the datas, this component(and others) and most important
* the world that it belong to. The world can be render()ed tick()ed and frame()d (frame to frame update) and receives events. It also holds the game systems.
* at last you request the system you want. You might prefer .getPathfindingSystem(), I kinda like dynamic_cast<PathfindingSystem&>(.getSystem("Pathfinding"))

That means that the component depends on the owner, the world, and the subsystem. It also means that the world depends on the pathfinding system. It's a reasonable design, but I'm going for something a little more structured and compartmentalized. In particular, in my experience, the above design leads to an explosion in code length.

Quote:
.. why do you need to access some system through another, or need components that use a "low-level" system?

I've given examples of that above.


#45 Sutekh   Members   -  Reputation: 122

Like
0Likes
Like

Posted 22 October 2007 - 05:17 PM

Sneftel,
So in your system, you have each subsystem store a it's own component[s] that get updated as the subsystem see fit. Each component is responsible for one entity and stores the data it requires for manipulating that entity within itself? Or do you some how store the data for each component with in the entity which is open for all other component to alter if they wish?

I have been attempting to wrap my head around a component system, I was going to attempt to use boost::any to allow an extensible system which allowed any data to be stored in a map on the entity, but I wasn't sure what the limitations on boost::any were on what types of data it could actually hold(can it hold class/objects)?

Thanks for all your insightful information.

#46 yahastu   Members   -  Reputation: 152

Like
0Likes
Like

Posted 23 October 2007 - 07:28 AM

Interesting, we have a lot of people using component based systems with a lot of differences. The downsides to components that Sneftel points out that it is a problem that components have trouble communicating with different (intra) components within the same entity (inter) or other entities.

There is another problem with the component based model that nobody has mentioned yet: for components that REQUIRE other components, you have to constantly check to see if you have the component...and what do you do if you dont find the component...do nothing? Also there is such a thing as over-generalizing...writing components that will not produce errors when used in combinations where components somehow conflict or dont work depending on what other components are in the entity

Possible solutions to inter-communication:

vtchill:
Quote:
For my design each component knows its entity parent and each entity has a HasComponent type function for querying on a specific component type which returns either a shared pointer to the component or an empty (NULL) shared pointer. The entities store their components in a map or hash map type structure for quick lookup. To get component specific information I am forced to cast the returned pointer to the type I requested


Pros: relatively fast lookup of components
Problems: requires dynamic casting, indirect lookup of components albeit constant time

Paladine:
Quote:
Essentially an entity is a container for components whose responsibility is to route events between components. The idea is that a component is a chunk of logic with a set of output events and a series of input events to which it responds.
Components then don't care explicitly about other components, they just use the more generic events which other components create and pass across the Entity bus. You could, therefore, throw any component into any entity and gain some/all of it's functionality (depending on what other components are also present).


Pros: modular
Problems: inefficient, requires a lot of virtual "bus snooping"

Sirisian:
Quote:
My event system is rather different in that I use a packet. Event ID followed by parameters serialized. So when an event gets activated it just passes the packet to the event manager which uses a FSM to break apart the packets from front to end.

It's much easier to the think of a tank as a an object that once the player gets in just runs the "get in" event and registers events to the keys and mouse. Very flexible at least.

Not sure how you guys handle the child components. But I give put them in an associative array which allows them to be accessed via their names. Plane2.GetComponent["weapon1"].GetProperty["ammo"]. Get's complex I guess, but for some reason I don't really see any speed decreases so far.


Cons: breaks rules of encapsulation, complicated, and lots of opportunities for failure if "weapon1" does not exist for example...if you depend on it being there, maybe you should not use an indirect referencing scheme?

--------

My view is that a hybrid scheme is best. Don't just make a single entity class that is completely generic. Define your entities based on the types of components they need and how they communicate with those components. This way, you know exactly what components EntityTypeA has. Put the logic for communicating between components into EntityTypeA, but put all the funcionality of the components into specific pointers held by EntityTypeA, so that you dont need complicated busses...hash maps...etc...you get the performance of a non-component design, but most of the modularity of a component based design. If the component cant really be separated, then youre probably not really adding any modularity by trying to write it so that it will work when it IS separated, since it wont really work then anyway!

As for "sub-components"...I dont think they should exist. Integrate into the component.

As for "inter-entity communication", this problem is also solved by the hybrid scheme.

#47 ZealousEngine   Members   -  Reputation: 100

Like
0Likes
Like

Posted 23 October 2007 - 12:34 PM

In regard to the component to component interaction debate...

So lets say you have a Entity that contains a Physical and Visual component. The Physical component needs to interact with a Visual component (ie whenever the physics subsystem updates the position/transform, the Physical component sends a update message to the Visual component). IMO for performance reasons, this communication should be direct (no 3rd party), so the Physical component should have a direct ptr to the Visual component.

The problem is, what happens if you want to use that Physical component in a Entity which has no Visual component (for example a 'invisible' cube that effects the physics simulation). It seems there are two very elegant options...

1.) Have the Physics component check for a null Visual pointer before trying to use it (since during Entity construction one wasnt assigned).

or

2.) Make TWO Physical components. Physical and PhysicalWithVisual. Modify the code accordingly for each.

Sound good or did I miss something?

#48 Sneftel   Senior Moderators   -  Reputation: 1776

Like
0Likes
Like

Posted 24 October 2007 - 03:18 AM

Quote:
Original post by ZealousEngine
So lets say you have a Entity that contains a Physical and Visual component. The Physical component needs to interact with a Visual component (ie whenever the physics subsystem updates the position/transform, the Physical component sends a update message to the Visual component).

The fact that Physical needs to interact with Visual does not imply that Physical needs to be coded to be aware of Visual's existence. This is the basis of signal/slot systems, and of the Observer pattern.

#49 ZealousEngine   Members   -  Reputation: 100

Like
0Likes
Like

Posted 24 October 2007 - 10:37 AM

Ah touche'

I wasnt too familiar with the observer pattern but youre right. A good article can be found here...

http://www.codeproject.com/cpp/observer.asp

So instead...

The Position Component would be a Subject, and as Visual or Physical Components are attached, they register to the Position as Observers. Then when Physical changes the Position, Visual will be notified indirectly.

And like youre already doing, I think its smart to just toss Position into the entity itself (since 99% of entities will have a position). No need for it to be a Component.

*edit

So im curious to hear more about how you implemented this. Are the components/observers polling their subjects? How do you control when this polling takes place? Or are you just having subjects notify all registered observers on a change?

[Edited by - ZealousEngine on October 24, 2007 5:37:29 PM]

#50 vtchill   Members   -  Reputation: 180

Like
0Likes
Like

Posted 24 October 2007 - 01:42 PM

Quote:
Original post by ZealousEngine
Ah touche'

I wasnt too familiar with the observer pattern but youre right. A good article can be found here...

http://www.codeproject.com/cpp/observer.asp

So instead...

The Position Component would be a Subject, and as Visual or Physical Components are attached, they register to the Position as Observers. Then when Physical changes the Position, Visual will be notified indirectly.

And like youre already doing, I think its smart to just toss Position into the entity itself (since 99% of entities will have a position). No need for it to be a Component.

*edit

So im curious to hear more about how you implemented this. Are the components/observers polling their subjects? How do you control when this polling takes place? Or are you just having subjects notify all registered observers on a change?


Subjects notify all registered observers on the change that is the beauty of the observer pattern. It would be a pain if observers had to poll. Usually a subject has register and unregister functions along with a Notify function which just loops through registered observers and calls their action function which can either be a polymorphic function or a function pointer style callback.

#51 Lord_Evil   Members   -  Reputation: 680

Like
2Likes
Like

Posted 29 October 2007 - 02:53 AM

What about this approach:

The entity would be a container for data-components, i.e. a transformation component (position, orientation etc.), a visual component (mesh, textures etc.), ...
Subsystems take the role of behaviour-components but there's only a single instance. Entities are then processed in a loop which works on whatever list may be appropriate for the subsystem. Data that is needed per entity but only within the subsystem could be stored using the decorator pattern.

When you add an entity to the world, it is added to the relevant subsystems. Those are found by
1. explicitly defining where it should be added (e.g. physics, visual, ai, ...). If the entity doesn't have the data required by a subsystem it either responds with some error or - if it can - creates the data-components and adds them to the entity.

2. iterating through each subsystem where each subsystem checks the entity for the required data-components and adds the entity to its list if all required data-components are found.

A data-component may have a list of observing subsystems but subsystems are not required to register to the data-components. Thus, if necessary, when a data-component is changed the registered subsystems are notified and may process the entity as the wish (e.g. add it to a "to-be-processed" list). Subsystems that would process each entity anyway would not have to register to the component but be able to poll it when needed.

Each frame the subsystems could run in order, i.e. ai->physics->visual. As said above each subsystem would process all the relevant entities (e.g. from the "to-be-processed" list), much like a particle system would do.


So, what do you say?

#52 Aressera   Members   -  Reputation: 1384

Like
0Likes
Like

Posted 29 October 2007 - 03:17 AM

Quote:
Original post by Lord_Evil

So, what do you say?


I think I like this approach. So basically, entities are pure containers for components, data structures which only have data, no logic. Then, "subsystems" are the logic portion of the system. They are given entities, and perform operations on those entities, utilizing one or more of the data components in each entity to update the state of the system. A subsystem ignores an entity if it does not contain all components that it requires.


The beauty of this approach is that communication between components is not required at all. The subsystems are responsible for this communication. I.e. the graphics subsystem updates the position and orientation of the graphics component from that of the transformation component.


Logic could therefore be abstracted into a logic subsystem which perhaps has a scripting engine interface. Therefore each entity could have it's own logic component which would contain the script to be executed for logic updates for that entity. The script, since it is executed by the subsystem would have access to all of the components, even itself.


Genious, pure genious.

#53 Emmanuel Deloget   Members   -  Reputation: 1381

Like
0Likes
Like

Posted 29 October 2007 - 04:23 AM

Quote:
Original post by Sneftel
Quote:
If you wanted an entity without a ScriptComponent for example, then how does the AnimationComponent detect this so that it can feed its data into the UserInputComponent instead?
I'm not sure what you're asking here... though I also have no idea what AnimationComponent would have to say to UserInputComponent.

"At this point in the current animation, if the user does a specific action, it triggers a specific response" (for example, clicking at the right time when fighting in The Witcher allows the player to make a combo).

I'm going to read that thread from the beginning to its end - it's quite long, but it's quite interesting too. I hope I'll be able to contribute something of interest [smile].

#54 Lord_Evil   Members   -  Reputation: 680

Like
0Likes
Like

Posted 29 October 2007 - 06:11 AM

Quote:
Original post by Aressera
Genious, pure genious.

Erm ... well, that's too much of praise for me [embarrass]

#55 Sneftel   Senior Moderators   -  Reputation: 1776

Like
0Likes
Like

Posted 29 October 2007 - 07:10 AM

Quote:
Original post by Lord_Evil
Subsystems take the role of behaviour-components but there's only a single instance. Entities are then processed in a loop which works on whatever list may be appropriate for the subsystem.

The primary reason that I kept components separate in my design is the Single Responsibility Principle. This sounds like it would work fine.
Quote:
Data that is needed per entity but only within the subsystem could be stored using the decorator pattern.

What do you mean here? The decorator pattern isn't great at adding heterogeneous data to an object.
Quote:
A data-component may have a list of observing subsystems but subsystems are not required to register to the data-components. Thus, if necessary, when a data-component is changed the registered subsystems are notified and may process the entity as the wish (e.g. add it to a "to-be-processed" list). Subsystems that would process each entity anyway would not have to register to the component but be able to poll it when needed.

This is a very interesting approach. I'd never thought about listening on data-component events, only on entity events.
Quote:
So, what do you say?

It's a very clever approach. I'll have to think more on it.


#56 Sneftel   Senior Moderators   -  Reputation: 1776

Like
0Likes
Like

Posted 29 October 2007 - 07:11 AM

Quote:
Original post by Emmanuel Deloget
Quote:
Original post by Sneftel
Quote:
If you wanted an entity without a ScriptComponent for example, then how does the AnimationComponent detect this so that it can feed its data into the UserInputComponent instead?
I'm not sure what you're asking here... though I also have no idea what AnimationComponent would have to say to UserInputComponent.

"At this point in the current animation, if the user does a specific action, it triggers a specific response" (for example, clicking at the right time when fighting in The Witcher allows the player to make a combo).

Ah, I got it. In that situation, the component would ask the other subsystem whether it had a component for that entity. Or the flow of control would be inverted, and an event would be raised for anyone who cared.

#57 Lord_Evil   Members   -  Reputation: 680

Like
0Likes
Like

Posted 29 October 2007 - 11:21 AM

Quote:
Original post by Sneftel
Quote:
Data that is needed per entity but only within the subsystem could be stored using the decorator pattern.

What do you mean here? The decorator pattern isn't great at adding heterogeneous data to an object.

Well, maybe decorator isn't the correct term. I mean something like:

class PhysicsEntity
{
private:
EntityPtr entity;
Box boundingbox;

public:
...
}


I'd just do this to add data that isn't necessarily stored somewhere but might be used by the subsystem. In the example given you won't store a bounding box within the entity but since it's needed by the physics system you'd calculate the box when you add the entity and store it within the physics subsystem. If multiple subsystems would make use of the bounding box it should be promoted to be part of some data-component. (Maybe a bounding box is not the ultimate example here [smile].)

I could also keep some other data structure along with the entities but that would be error prone (e.g. when removing the entity but not the additional data) and ugly.
Quote:
Original post by Sneftel
Quote:
So, what do you say?

It's a very clever approach. I'll have to think more on it.

I'd appreciate your thoughts on this. I was inspired by your approach and the discussion of it made me think of how I could design my engine's component model for weeks.


#58 Emmanuel Deloget   Members   -  Reputation: 1381

Like
0Likes
Like

Posted 30 October 2007 - 05:39 AM

Quote:
Original post by Lord_Evil
What about this approach:

The entity would be a container for data-components, i.e. a transformation component (position, orientation etc.), a visual component (mesh, textures etc.), ...
Subsystems take the role of behaviour-components but there's only a single instance. Entities are then processed in a loop which works on whatever list may be appropriate for the subsystem. Data that is needed per entity but only within the subsystem could be stored using the decorator pattern.

When you add an entity to the world, it is added to the relevant subsystems. Those are found by
1. explicitly defining where it should be added (e.g. physics, visual, ai, ...). If the entity doesn't have the data required by a subsystem it either responds with some error or - if it can - creates the data-components and adds them to the entity.

2. iterating through each subsystem where each subsystem checks the entity for the required data-components and adds the entity to its list if all required data-components are found.

A data-component may have a list of observing subsystems but subsystems are not required to register to the data-components. Thus, if necessary, when a data-component is changed the registered subsystems are notified and may process the entity as the wish (e.g. add it to a "to-be-processed" list). Subsystems that would process each entity anyway would not have to register to the component but be able to poll it when needed.

Each frame the subsystems could run in order, i.e. ai->physics->visual. As said above each subsystem would process all the relevant entities (e.g. from the "to-be-processed" list), much like a particle system would do.

So, what do you say?

If I understand everything, subsystems are made of a query (to get the entities they can work on) and a procedure (to apply a function on the entities). It seems to be simple and elegant and do not assume any kind of scene structure (do you can use a scene graph or any other world management structure).

In fact, it looks like a database to me - which is a good point imho. I would go without the observer mechanism as it introduces out-of-order processing (something which is difficult to manage when you have a lot of data). If you can cope with that then it's ok.

Question: how do you manage
1) data duplication (the bounding box can be of interest to various components; should all components have an embedded bounding box)?
2) components requirements (because I have this component, I shall also have this other one)?

#59 TwoD   Members   -  Reputation: 122

Like
0Likes
Like

Posted 30 October 2007 - 07:34 AM

Quote:
Original post by Emmanuel Deloget
Question: how do you manage
1) data duplication (the bounding box can be of interest to various components; should all components have an embedded bounding box)?
2) components requirements (because I have this component, I shall also have this other one)?


Maybe these both questions could be answered by the same mechanism, when adding a component to an Entity?
A subsystem which depends on/Observes a different subsystem will most likely know which component(s) the other/Subject subsystem has, say a bounding box. It would not be much point for a subsystem to depend on another one unless it knows what the other one has/does, right? Atleast to some degree...

Thus, if subsystems try to intitialize or add its component to an Entity, before it has another component it depends on, it should know which component needs to be added by the missing component's owning subsystem. So it could simply (maybe recursively) tell that other subsystem to init the Entity before it does so itself.

To avoid duplicated data, a subsystem could also obtain direct references to other components of that Entity, or perhaps even members in those components, during the add-component/init phase. The references would be relayed to (and stored in) the component which needs them to function by its owning subsystem.
There should be little problem as long as subsystems which rely on components from other subsystems make sure they are added before their own.

This hopefully also avoids having to "manually" update all members in a component which relies on other component's members, as they would automatically point to updated data at all times.

To avoid making these "couplings" so hard that there'll be trouble when changing members of a component/subsystem which are relied upon by other subsystems/components, one could use ideas from Game Programming Gems 2, "A Property Class for Generic C++ Member Access" so exposed component or subsystem members could be referenced indirectly by a more descriptive name such as
"Bounding Box" istead of m_aabb.

Of course, this "property lookup" between components should all be done during Entity init to avoid performance drops due to having to go through a bunch of indirect couplingd.

What do you all think?

#60 Sneftel   Senior Moderators   -  Reputation: 1776

Like
0Likes
Like

Posted 30 October 2007 - 07:42 AM

Quote:
Original post by Lord_Evil
I'd just do this to add data that isn't necessarily stored somewhere but might be used by the subsystem. In the example given you won't store a bounding box within the entity but since it's needed by the physics system you'd calculate the box when you add the entity and store it within the physics subsystem.

This starts sounding a lot like my approach, where subsystem-specific data is stored by the outboard component. Rename PhysicsEntity to PhysicsComponent and you'll see what I mean.

I agree with Emmanuel-- it's starting to sound a lot like a database. Which isn't a bad thing. Of the mainstream software development ideas which game development has grudgingly absorbed--OOP, data-driven architecture, scripting integration--I really think that RDBMSes are next. I disagree with him that the observers are a bad thing, though. Just remember your class invariants, and make sure that they're satisfied whenever you invoke an observer. Do that, and the screwed up control flow won't matter.




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