Outboard component-based entity system architecture

Started by
105 comments, last by qingrui 16 years, 3 months ago
Quote:Original post by dmatter
Quote:Original post by Tesshu
Quote:Original post by dmatter
Quote:Original post by Tesshu
In my system I handle these problems at construction time. All components are added to the object then coms can do discovery when onConstruct() is called on the object. For example, I might have a ComCamera1st that follows the head of the ComSkinMesh3d, if ComSkinMesh3d isn't found then it looks for a ComPhysics and if that isn't found it looks for a ComStaticFrame etc... I really can't see how else to do this sort of thing. Coms that do anything non-trivial pretty much have to talk to other coms.

Perhaps that behaviour belongs elsewhere, such as the scene-graph, rather than the component system. You would have a camera node and attach to it an entity.


I don't see that as much of a solution since you still need to follow the head position.


Well it depends on exactly what you need, but a simple solution might be:
ROOT | \---Group Transform Node            |            \---Camera            |            \---SkinnedMesh

The camera is set up to hover about 4 meters above ground and to look-at the point where the mesh's head is known to be. If the transform node changes then the camera and the mesh both move together thus the camera follows the players head.

IMO, a camera isn't really a good candidate for the component system.


I guess this is a perfect example of how it's not always easy to determine what should be in the com system or not :)

I have a Sim_ComFollowBone that, well follows a bone on a skinned model. So I can use this com to put a weapon in a hand, make a halo appear over a head or make a camera follow a bone. Keep in mind that some of my coms, such as Sim_ComCamera, are really wrappers for more lower level systems. I really can't see why you wouldn't want it to be com. One of the features of the system is sharing code. Using this method I can create many different types of objects that can be used as a camera.


Advertisement
Quote:Original post by Tesshu
I guess this is a perfect example of how it's not always easy to determine what should be in the com system or not :)

I have a Sim_ComFollowBone that, well follows a bone on a skinned model. So I can use this com to put a weapon in a hand, make a halo appear over a head or make a camera follow a bone. Keep in mind that some of my coms, such as Sim_ComCamera, are really wrappers for more lower level systems. I really can't see why you wouldn't want it to be com. One of the features of the system is sharing code. Using this method I can create many different types of objects that can be used as a camera.


It is certainly possible to have such things in either the component system or the scene-graph system and I'm sure there are pros and cons to both.

The way I tackle it is to have BoneSockets in the scenegraph that reference a specific bone in a skeleton so that other scene-graph entities can be attached to it:

ROOT | \---Group Transform Node            |            \---Camera            |            \---SkinnedMesh            |            \---RHandBoneSocket            |         |            |         \---SkinnedMesh (Weapon)            |         |            |         \---ParticleSystem            |            \---LAnkleBoneSocket                      |                      \---SkinnedMesh (Annoying clingy person that won't let go)


It's been on my todo list for some time to ponder whether it's possible to combine the scene-graph and component system together into a single concept, clearly there is cross-over between them. Failing that, I'd atleast like to change the paradigm of my current scene-graph, those BoneSockets were just an ad-hoc means to an end, I'd like to see a more elegant solution in my next code iteration.
I realize this was said a while ago:
Quote:Original post by Sneftel
One limitation of the design is that components are limited in the amount of state they can hold. Since they persist only when the Entity is in the World, they can't be seeded with any state as part of the creation process. Any data that the Component starts out with has to come from the Entity's preexisting state.

Not sure what you mean there. Could you explain that?

I am really curious how you guys do your event systems. I go for a soft coded method that's very flexible at run-time (to the point scripts could be changed and loaded in at run-time to add events). I mean my event system revolves around simple input to output. Every form of input has a way to be linked to an event to produce any output.

A good example is that having a packet allows you to produce extremely dynamic effects. Like clicking on an object can run a packet ID that spawns a GUI window with choices and fills in event ID's and information dynamically. Good for interacting with the world.

I figure you guys do something similar would I be correct in saying that?

A big part is passing down the events to the registered componenents that have focus over the input. (Again thinking of the game entities like a GUI is a perfect way to look at it).
Quote:Original post by Sirisian
I realize this was said a while ago:
Quote:Original post by Sneftel
One limitation of the design is that components are limited in the amount of state they can hold. Since they persist only when the Entity is in the World, they can't be seeded with any state as part of the creation process. Any data that the Component starts out with has to come from the Entity's preexisting state.

Not sure what you mean there. Could you explain that?

Consider that a few months down the line, I decide that every Entity should have a WeightComponent, which tracks the entity's weight, increasing as it overeats and decreasing as it diets and exercises regularly. Suppose that previous to this, the Entity had no concept of weight or mass at all. The difficulty I would run into (assuming that I stored the weight in the WeightComponent) is that since Entity has no member called "weight", I would have no way of knowing what its initial weight should be. Likewise, if for some reason I temporarily removed a particular Entity from the simulation and then added it back in, the weight would no longer be the same; the original WeightComponent would have been destroyed and a new one created. What this all means is that outboard components aren't useful for adding state, only for adding behavior.
The way I have mine set up is that if I wanted I can use any types in the properties. I plan to revise my system more so that it will allow most properties, but under the circumstance that a component needs another property then it could be added dynamically to the object. Albeit the access of that property would have a little more overhead, but not enough that it wouldn't be useful.

I'd end up accessing it in my events. So like the onload event I might have to go through each object that needs it and edit it so that the new variable is created and able to be accessed (via the map of properties).

For a large upgrade where it would require all entities to have the update then I'd just rebuild the engine with it.

Currently I just list out each component and event such to allow for the dynamic creation of "any" object. Breaking everything up into the simplest ideas is the hard part. I mean the actual actions aren't very important and are pretty much "scripted" via event packets. It's the variables and rendering/boundary/data type info that tends to change. I work in 2D, so the implementation is probably different for 3D.

Like say I was making a tank. You'd break it down into the base, the two treads, 4 particle emitters for the front and back treads, the turret with the barrel, and the particle emitter at the end of the barrel.

So you have 5 particle emitter components, 2 animations (treads, the barrel is a translation based on the firerate, but is just a texture), 1 vehicle component(base) and a rotating component (turret) and finally a texture outside of the tank where the player pushes g to get into the tank so 1 texture. So like 11 components. Not bad, but the interactivity between them is high. Like textures are basically 1 frame animations with a rotation point and beginning translation. The vehicle base has tons of event handlers that can register keybinded input to events as said before. The animations register with the main container since they may only update every 50-200 ms and such. Events tell the tread animations whether or not to play and the list goes on.

Also, for clarification when I said, "multiGameEntity" I mean a container. It's the root of the object to which all components register events. When events are handled they are passed to it for a quick cull of events so components don't worry about what they don't need.

Due to my insane FSM system though the basic premise is again a flexible dynamic entity creation system. (I should probably make an editor...).

Still wondering how you guys handle events for your system. Maybe there's a more flexible way than mine. (I am hardcoding after all).
Quote:Original post by Sneftel

Quote:
Quote:
Quote:Presumably there is still a factory of some description for informing (or firing off the adequate events to) each relevant subsystem that an entity requires a representitive for?
Sort of. That happens when the entity is added to the World, not when it is created; so the creation of Components is separated from the creation of Entities. The World contains an event for Entity adding, which each subsystem hooks to.

If subsystems just respond to an EntityAdded event then won't each entity will end up with every type of component whether it wanted it or not?

The entity certainly doesn't decide whether it gets these components or not. It's not responsible for them. The subsystem can choose to create a component or to ignore the addition event completely, based on its appraisal of whether that particular entity is interesting to it or not.


Is that done with a dynamic_cast<> within the given subsysten? How would the subsystem determine which entity needs what?
Quote:Original post by dingojohn
Is that done with a dynamic_cast<> within the given subsysten? How would the subsystem determine which entity needs what?

What would you do with dynamic_cast?

A data-driven approach might see each subsystem with a string of meta-tags that it is interested in. When an entity is added to the world it too is given a string of meta-tags by the user.
Upon receiving an EntityAdded event, the subsystem queries the entity for it's meta-tags and searches it to see whether it's interested creating a representitive for this entity.

The meta-tags can be explicit: std::string("Physical, Spatial, Visual");
They can be implicit: std::string("Antelope");
Or a combination of both: std::string("Vehicle, AI"); -or- std::string("Vechicle, UserControl")
Quote:Original post by dmatter
Quote:Original post by dingojohn
Is that done with a dynamic_cast<> within the given subsysten? How would the subsystem determine which entity needs what?

What would you do with dynamic_cast?

A data-driven approach might see each subsystem with a string of meta-tags that it is interested in. When an entity is added to the world it too is given a string of meta-tags by the user.
Upon receiving an EntityAdded event, the subsystem queries the entity for it's meta-tags and searches it to see whether it's interested creating a representitive for this entity.

The meta-tags can be explicit: std::string("Physical, Spatial, Visual");
They can be implicit: std::string("Antelope");
Or a combination of both: std::string("Vehicle, AI"); -or- std::string("Vechicle, UserControl")


I cannot believe I did not think of that. Thanks.
Actually, dingo's hit on one of the other aspects of my Entity system: I am using inheritance to some degree. That is, there is a small but significant inheritance tree hanging off of Entity, and one of the main criteria subsystems use to decide whether they care about an Entity is that Entity's dynamic type (determined via dynamic_cast). This allows me to add extra properties to certain types of objects, at the cost of some of the flexibility that the standard component-based system allows.
It sounds like I'm doing something that is somewhat different.

In data file for each different entity type that can be created I have a GameObjectType definition that contains a list of components and for each one a bunch of things that set variables in the component. A small example:

Monster
{
Physical
{
size = 4
}
Moving
{
speed = 10
}
}

Any variables you don't set get default values.
You can also derive from one of these classes

FastMonster : Monster
{
Moving { speed = 30 }
}


For each component I have something deriving from ComponentStatic and Component.

At load time when you parse these files for each type we create a list of ComponentStatic objects that belong to that type. A component static has functions for accepting variable definitions and a list of components that it requires services from. Moving for example requires Physical to be present (You need to be able to update the position of the object in order to move it). If you list a component and you don't include its dependency then the Type will fail to load and an error gets reported.

For each dependency you store a ComponentRef which is really just the index of the component for easy look up later.

A GameObjectType also remembers the sizeof for the non-static components corresponding to the static ones it contains and the sum of all of these.

So now each type of object we want has a ComponentStatic that contains dependancy indices and all of the type specific data that each component needs.

When we instantiate a GameObjectType as a GameObject we allocate all of the memory for all of the non static components in one call and use placement new to call of of the constructors for each game object. Each non static component has a reference to its static cousin so that it can easily access all of the object type specific data it needs.

The GameObject has a function that takes a ComponentRef and returns a pointer to the game object that you want. It does this by using a lookup table in the GameObjectType that contains the offsets of each Component for this type.

This may sound a little complex but it means that creating new objects is now pretty fast. You allocate all of the components in one go and this has the additional benefit that all of the components are located next to each other in memory to improve caching performance.

We also have a quick way of getting references to other components. All of the dependencies the component needs are stored as ComponentRef's so getting a pointer is just a constant time array lookup.

There is one other thing needed here though, and that is a way to publish messages to components that require info back from you. For example, you need a way of notifying any components that need to the fact that the object has stopped moving for example (required for animation).

I considered some kind of callback system and implemented some of it but it was fairly complex. So I settled with a much simpler solution. Each component has a ReceiveMessage virtual function and when a ComponentStatic is instantiated all of the components that it requires get an entry added to say that it is interested in messages from those components.

When a component wants to send a message it calls a function that will send messages to all of the other components that registered as listeners. So our animation controller lists the moving component as a dependancy and now it gets notified of any events that it needs to.

There are a few other issues I have not discussed here. The first one that is not yet implemented fully the ability to be able to load GameObjectTypes faster using a binary game object type format that contains a blob of binary data that each component can memcpy directly into a little inline struct during loading. You can compile all of the game object type files down into this format, number all of the components and then very quickly load a game object type. This is for distribution as it requires a locking down of the available components and you can't really edit the type files after that.

The other issue is network serialisation. I have a system that allows each component to be serialised and put into a network stream and come out whole on the other end, and a "dirty component" thing that allows components to say that they have changed and then only components that require changes to be serialised get sent (with a bitfield at the start to say which components the client needs to load.

That mechanism has a whole other list of strange things to do with Components being marked as Client, Server or Shared. Only Shared components get state serialisation and Client / Server components only actually exist on the sides that they need to.

All of the Shared components come first in the physical object to preserve that the component indices are always the same on both the client and server. (We don't send the indices in network traffic but its easier to get your head around sometimes)

I'm not totally happy with the networking part of the system honestly. It can be hard to say sometimes what should be serialised as part of the object state and what should be sent as a standard message packet. I've been considering some kind of Object -> Object network message thing but haven't implemented it yet. That would be so that the server can notify the client of events without actually sending it as object state.

Anyway, this ended up being a pretty long post. Any suggestions and comments welcome.

This topic is closed to new replies.

Advertisement