Outboard component-based entity system architecture

Started by
105 comments, last by qingrui 16 years, 3 months ago
Well, I don´t really know what you mean, but I can tell you I´m using couritines for my entitnes (each thread is a entity). I Have some functions that define the behaviour of the entity, I mean, Events, like OnHit(), OnDie(), OnCreate(), etc...

From my entity system I create a lua entity (it is a C++ class) that calls the OnCreate() in lua and this sets everything the entity needs to init (mesh used, speed, etc...), after it comes back from OnCreate(), I finish the initialization in C++.

Sorry if I´m making silly questions, but I really afraid to move my design to component system and having lot of undercovered problems.

By the way, what are this events system used for? I read something about communication between components? I think there's a more direct way to do this, don´t you think?.

The other problem I see with component system is the granurality of it. I think we will end up with hundreds of components for just everthing.

Thanks in advance,
HexDump.
Advertisement
Well, I have been doing some drawings and thinking about my design :). Here is where I am so far, I hope could give his opinion about it:

1) I will go with a component system where entities will be a container for the components.
2) The components will be stored in a map/hash, with key "component name", instance.
3) When a component is added to an entity, it will publish to the entity callbacks
to handle the component, example:

====> $this->Owner->RegisterAction("UPDATE",UpdateCallback);
====> $this->Owner->RegisterAction("GOTO",GotoCallback);
====> ....

This registrations could be mantained in a map/hash of type <String,CallBack>
A call for example to get the Position of a entity (if we have decided to to put this in the entity itself because of its use in almost all entities) to a Frame componenet would be something like:

====> $this->Owner->DoAction("GETPOSITION");

and this will give us position of the entity.

This provides me with a mecahnism to not having hundreds of simple methods in entity that the only thing that do is delegating to the real method of the component.


4) In order to handle parameteres, returns, we could use a ParamList that will be passed, or returned by the Action.

What do you think about it?.

Thanks in advance,
HexDump.
This is a great thread, I've learned a lot. Inverting the component system is so beautifully simple I can't believe more people haven't thought of it and been advocating it.

One thing though, I'm not sure this system lends itself well to threading across many, many cores. It would be easy enough to throw each subsystem into it's own thread and let them run asynchronously, but eventually I can't see you coming up with enough subsystems for all the cores you will have. And since objects are just data containers in this case, with no reference back to their components except loosely through listeners, it would be difficult to group spatially close objects and throw those at threads instead.

Any thoughts?
Quote:Original post by Thergothon
One thing though, I'm not sure this system lends itself well to threading across many, many cores. It would be easy enough to throw each subsystem into it's own thread and let them run asynchronously, but eventually I can't see you coming up with enough subsystems for all the cores you will have. And since objects are just data containers in this case, with no reference back to their components except loosely through listeners, it would be difficult to group spatially close objects and throw those at threads instead.


Actually, I think this system is probably more amenable to parallelization than the standard component design. If you can't come up with enough subsystems to give all your cores something to do, just split a subsystem update across multiple cores. That's probably a good idea regardless, since different subsystems will eat up different-sized CPU slices. (This is assuming your subsystem updates can be parallelized; if they can't, you're screwed regardless of which design you go with). The key here, I think, is a job-based as opposed to a thread-based model, which Sony's been trying to convince people to go with anyway.
Makes sense, looks like I'm going to have to hit up google for some job-based v. thread-based articles.

Cheers.
Quote:Original post by Sneftel
Actually, I think this system is probably more amenable to parallelization than the standard component design. If you can't come up with enough subsystems to give all your cores something to do, just split a subsystem update across multiple cores. That's probably a good idea regardless, since different subsystems will eat up different-sized CPU slices. (This is assuming your subsystem updates can be parallelized; if they can't, you're screwed regardless of which design you go with). The key here, I think, is a job-based as opposed to a thread-based model, which Sony's been trying to convince people to go with anyway.


Creating a good parallelization design doesn't mean that you design sub systems to live on a thread/cpu. I don't really feel the component model will ever lend itself well to working on multiple threads. If there are any links to the outside world even if its loosely coupled then, in my opinion, the design of the system is flawed for multiple cpus/threads. Parallelization works best when you work in a functional model. Take input, transform input, create output. You can then take a bunch of these functions queue them up and send them off to do work. This allows one subsystem to use multiple CPU's/threads. The real work being done by functions and the subsystem handles setting up the data to be processed and any interaction with any other systems.

For example, if you have an AI subsystem then a good way to parallelize it is to create functions that perform work (i.e) chunk the world data into a buffer and send it off to a thread/cpu to find a path through it. You could have 10 of these working at once. As long as you can design your AI subsystem can defer looking for the best path until the threads are finished processing it.

It's kind of like programming in a purely functional language. If you know a function has no side effects then you know its ripe for parallelization.

I love all the different forms of component design but the beauty of their design is how well they can glue into different systems. I don't think trying to force them to be parallel is going to be a good clean solution. I feel it's better to separate the communication/interaction with the components/systems and the real grunt processing into functions.

This reminds me of the Tim Sweeney - The next generation of programming languages Where he shows statistics on how much code/time is spent in game play systems and how much code/time is spent on hard numerical crunching. The numerical crunching part is where you want to multi-thread the game play systems should be able to have references to shared data or events wherever they want.

-= Dave
Graphics Programmer - Ready At Dawn Studios
Well i had a quick look at the thread post and not at the rest of it so objectively here is my idea of a component based architecture for systems:

// System base class (used for Video or Audio subsystems etc)class System { };struct NamedIndex{    int index;    string name;};// Holds a database of named and indexed systemsclass SystemDatabase{private:    map <NamedIndex, System> mSystems;public:    // registers a system with the database    void SetSystem (string name, int index, System * system) { }    // gets a system    template <class T    T * GetSystem (string name, int index = 0) {        Entry e = { index, name };        map <Entry, System>::iterator i = mSystems.find (e);        if (i == mSystems.end ())            return 0;        return dynamic_cast <T *> i->second;    }};// Sub class of Systemclass SceneGraph : public System{public:    SceneGraph (SystemDatabase * db) {        db->SetSystem ("scene", 0, this);    }};// to access a systemint main (){     SystemDatabase db;     SceneGraph * graph = new SceneGraph (db);     if (db->GetSystem ("scene", 0)) {         printf ("Yay it worked");     }}


While this still allows objects to depend on other systems, it separates them from the object itself by having to ask the database for a type of system and it giving it whatevers there.

As for components and entities:

class Component;// This collection of components abstracts it away from what object it is actually in (which may be a bad thing i dunno)class ComponentCollection{private:    map <NamedIndex, Component *> mComponents;public:    // sets a component (in implementation)    void SetComponent (string, int idx, Component * c)    {         // ... add component         c->mParent = this;    }    void Update (double dt);};class Component{private:    ComponentCollection * mParent;public:    Component (ComponentCollection * parent);};// entityclass Entity : public ComponentCollection { };


Having a parent component collection member variable allows them to have a look at the other components by asking the collection for the object under the desired name such as
mParent->GetComponent <SceneNode> ("scene_node", 0)->Move (x, y, z);
Quote:Original post by David Neubelt
Creating a good parallelization design doesn't mean that you design sub systems to live on a thread/cpu. I don't really feel the component model will ever lend itself well to working on multiple threads. If there are any links to the outside world even if its loosely coupled then, in my opinion, the design of the system is flawed for multiple cpus/threads. Parallelization works best when you work in a functional model. Take input, transform input, create output. You can then take a bunch of these functions queue them up and send them off to do work. This allows one subsystem to use multiple CPU's/threads. The real work being done by functions and the subsystem handles setting up the data to be processed and any interaction with any other systems.

Yes, this is what I was talking about WRT a job-based instead of thread-based system.
using the observer pattern, just wondering how people are handling this notify subject changed callback.

eg.

class Component{}class ComponentA : public Component{   void SomeFunction()   {     NotifyObservers(?);   }  void SomeOtherFunction()   {     NotifyObservers(?);   }}class ComponentB : public Component{   void NotifySubjectChanged(Component* component, ?)}


supposed ComponetB is an observer of ComponentA, but component B want to know different information based on if SomeFunction or SomeOtherFunction was called on ComponentA.

hence, some additional event info needs to be sent, i guess this should be an event id. It would be nice if i could just send through the pointer of the function that was called but c++ you cant just cast function pointers to void* unfortunately.

any one have any good ideas what to do here?
Quote:Original post by supagu
using the observer pattern, just wondering how people are handling this notify subject changed callback.

eg.

*** Source Snippet Removed ***

supposed ComponetB is an observer of ComponentA, but component B want to know different information based on if SomeFunction or SomeOtherFunction was called on ComponentA.

hence, some additional event info needs to be sent, i guess this should be an event id. It would be nice if i could just send through the pointer of the function that was called but c++ you cant just cast function pointers to void* unfortunately.

any one have any good ideas what to do here?



Well my quickest solution would be to create event classes inherited from a base event class and pass the base event class around to Notify objects of changes. I don't think you should have to pass the entire component for every change.

class BaseEvent{   BaseEvent(int type, int source, int dest);   int eventType;   int senderId;   int receiverId;};class DerivedEvent1 : public BaseEvent{   DerivedEvent(double information);   double derivedInformation;};class DerivedEvent2 : public BaseEvent{   DerivedEvent(double information1, double information2);   double information1;   double information2;};class ComponentA{   void SomeFunction()   {      BaseEvent *event = new DerivedEvent1(some_info);      NotifyListeners(event);   }   void SomeOtherFunction()   {      BaseEvent *event = new DerivedEvent2(some_info, some_other_info);      NotifyListeners(event);   }};class ComponentB{   void NotifySubjectChanged(BaseEvent *event)   {      switch(event->eventType)      case EVENT_FLY:      {         // cast to specific event here         DerivedEvent1 devent = static_cast<DerivedEvent1*>(event);         // use information contained in derived event here                  break;      }      ...   }};

This topic is closed to new replies.

Advertisement