Skip to main content
gamedev.net
🔒 Locked

Component dependency

Started by M4573R 22 replies 2.5k views
Original Post
M4573R
M4573R
I'm curious about how someone goes about solving the problem of component dependency inside a game object. Say you have a position component. Then another couple component that depend on it such as physics, graphics, etc. They need a pointer to the position component for modifying it and accessing it. So how would you hook all of it up when you're creating an object? What if you create a physics component before the position component? What if you had circular dependency?
swiftcoder
swiftcoder
I side-step the issue entirely by separating out 'attributes' from 'components'. Components contain the logic, and any state which is useful only to themselves. Attributes are pure state, and are accessible to all components. Attribute pointers can be created, tested for existence, retrieved or removed by name.

So lets say we attach a graphics component first: the graphics component test to see if the entity has a 'position' attribute, and since it doesn't, it creates the attribute and initialises it to Vector3(0, 0, 0). Then we attach a physics component to the same entity, which checks to see if it has the 'position' attribute, and since the graphics component already created it, the physics component just retrieves the pointer.

This system has worked extremely well so far, and makes serialisation very simple, as we have state fully separated from logic.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
M4573R
M4573R
Well couldn't that still be done if with data/logic in the same components? Could you explain what you mean by 'states' too please?
swiftcoder
swiftcoder
Quote:
Original post by M4573R
Could you explain what you mean by 'states' too please?
Not 'states', state - as in, the data which defines the object.
Quote:
Well couldn't that still be done if with data/logic in the same components?
It could, but that way components would need to know about each other. In this setup, the entity itself holds all the data, so each component only needs to know about the entity itself.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
M4573R
M4573R
If the components know about the entity itself, couldn't it readily access any other component held in the entity?
swiftcoder
swiftcoder
Quote:
Original post by M4573R
If the components know about the entity itself, couldn't it readily access any other component held in the entity?
Ah, I forgot to mention that this is an outboard component-entity system: components are not held in the entity at all, and are instead managed separately by their owning subsystem. But this is all largely immaterial. The important part of the process is not how the data is accessed, but instead the fact that data and logic are entirely separate.

It isn't straightforward to explain the rationale behind this, as the system grew out of a fair amount of trial and error. The basic theory is that no component dependencies are required, as attributes are just data. It also allows simple serialisation (for load and save), where you just serialise the entity pool.

You certainly don't have to do this, but using just components, and then having to differentiate between data components and logic components, was a decided pain.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
M4573R
M4573R
I agree with the sub-systems dishing out and managing their corresponding components, but will the entity still need to keep a list of these? Who dishes out the and maintains the data??
bzroom
bzroom
I follow the same pattern of managers giving out the components, but the entity holds the list of these.

Aside from that I also use the "properties" model for the component data, with two distinct kinds of properties "input" and "output." An ouput is just a regular variable, holding the data which is known as a property (such as position). The inputs are essentially weakptrs to the outputs. When bound, the output property can be accessed via the input property. When not bound the input property will return null or assert depending on the accessor called.

When the components are loaded and added, their inputs and outputs are connected from a manifest. From that point, no searching or rtti action happens when accessing the data that you use most frequently.

For instance your vehicle animation component may bind an input to the vehicles physical component's transform output.. or something.

You can thank jpetrie for this idea..
swiftcoder
swiftcoder
Quote:
Original post by M4573R
will the entity still need to keep a list of these? Who dishes out the and maintains the data??
The entity keeps a list of the names of the active components, merely so they can be reattached after serialisation. The data is not explicitly maintained by anyone - any component is free to read or write any attribute, and constraints (such as only Physics can update the position) are only loosely enforced at a higher level.
Quote:
Original post by bzroom
When the components are loaded and added, their inputs and outputs are connected from a manifest. From that point, no searching or rtti action happens when accessing the data that you use most frequently.
It equates to roughly the same thing, although I didn't want the complication of inputs vs outputs - your system is probably more robust as a result.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
M4573R
M4573R
There was still one thing. There's the components (logic only as you stated before) and then there's data. Should the entity still hold the data?
yahastu
yahastu
You can solve this problem by using a component manager that allows individual components to register and request shared variables by name. In this way an infinite number of components can interoperate efficiently without any compiler dependencies.
M4573R
M4573R
Couldn't that get incredibly expansive if thousands of components all needs thousands of different pieces of data in thousands of different objects? Or is there a name for each "type" of data and it retrieves the data of that type from the parent entity?
yahastu
yahastu
Quote:
Original post by M4573R
Couldn't that get incredibly expansive if thousands of components all needs thousands of different pieces of data in thousands of different objects? Or is there a name for each "type" of data and it retrieves the data of that type from the parent entity?


I'm not sure if you've directed this question at me and the method I proposed, or the method that was proposed by swiftcoder and bzroom.

If you are addressing me, then no -- the performance is independent of the number of components and shared variables. The way I have implemented it, each shared variable is declared in an enum so that it has a unique index. Thus whenever a variable is requested "by name" (which is actually "by index") the value can be looked up by an array index. Alternatively, one could implement this functionality using a hash table, although that would be slower.

That said, if you have thousands of components with thousands of shared variables, that sounds like an unrealistically high amount of components and communication between them. I can't think of any reason why a game would need quite that many components, or why they would all be dependent on one another.
CrazyCdn
CrazyCdn
I have components as pure data and systems like AI, Renderer that know how to work with them. Then, its just a map which is basically the entities. Though that is wrapped in a class so my GUI and game objects can all use components.

I use a messaging system to pass on needed requests for information like position, etc. I've found you don't have thousands of components needing updates all that often.
"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin
M4573R
M4573R
Sounds good. I only have questions like this because I haven't actually tried implementing it and seen how well it performs. I'm trying to see if there will be any pitfalls I should look out for. This has all been incredibly useful. I really wish I could take an engine architecture class at my school.

Thanks
swiftcoder
swiftcoder
Quote:
Original post by M4573R
Sounds good. I only have questions like this because I haven't actually tried implementing it and seen how well it performs. I'm trying to see if there will be any pitfalls I should look out for. This has all been incredibly useful. I really wish I could take an engine architecture class at my school.
Just to set your fears at rest, my component/entity system is implemented in Python (which allows attributes to just be python variables), so I have already a performance hit due to interpreted code. The entity system has never showed up as a bottleneck in the profiler, despite 4-8 components per entity, and several hundred entities in play at the same time.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
Zipster
Zipster
To be honest, I don't really see the benefit of using 'attributes' over storing data directly in 'components'. By virtue of the fact that you need to lazy create an attribute if it doesn't exist, you're exposing a dependency, so it's not like that issue has been resolved. Except now each and every component that needs a given attribute has to worry about checking for its existence, then lazy creating it if it doesn't exist. And from what I understand, since attributes are essentially global data, each and every component is free to just read and write them at will. What about access control? What about encapsulation? What if setting/getting a component requires more logic -- or rather more custom logic -- than just A = a; and return A;? From what I see, all attributes do is force more responsibility on every component that accesses an attribute to do everything that's required when touching said attribute, as opposed to putting the data into its own component that can provide the proper access control and encapsulation that make dealing with the data easy for external components. I imagine there's also some amount of overhead in retrieving an attribute, whether it be a hashed name look-up or an array offset.

As for serialization, we tried a special system that could parse the components, see what data they wanted serialized, and automatically generate the code to serialize those components. Serialization was simple, so I imagine it ended up being roughly similar to attribute serialization. The problem was that there simply ended up being too many cases where this method of serialization wasn't appropriate. In the same way that components had to control their data, we found that there were too many special cases where components needed control over how their data was serialized. This means that now every component needs to implement its own serialization method, however it ends up being a lot cleaner for us than coming up with a hybrid approach that auto-serializes some components and allows others to perform their own serialization. It also happens that our current project doesn't do much serialization, so that helped us come to this decision.

Component dependency in itself isn't a Bad Thing™. As a matter of fact dependencies are just a way of life. If you have a physics component, it needs to know the position of things. That's the dependency. It doesn't matter if that data is in a 'component' or in an 'attribute' or on the moon. Once you can do physics without needing to know positions, then you've truly eliminated the dependency. But how are you going to manage that?

The trick isn't figuring out how to eliminate dependencies completely. Of course, it's smart not to introduce any unnecessary dependencies, and those should certainly be removed, but there are plenty of necessary dependencies that are going to exist. So assuming you have the bare-minimum dependencies, the trick is figuring out how to make those dependencies a non-issue. Identify the places where component dependency can be a problem, and attack those problems as opposed to the dependencies. Let's stand on the shoulders of giants for a moment. How does Visual Studio handle project dependencies? Certainly not by forcing you to make your projects independent, but rather by allowing each project to specify which other projects it depends on. It can then easily figure out the proper build order. Do the same for components. At design-time, have a special XML file (or whatever your data format is) that outlines component dependencies. You know (because the code "knows") that a physics component and a graphics component will depend on a position component, so for the physics component and graphics component dependencies, specify a position component. If any object that contains a physics/graphics component doesn't also contain a position component, at that time it can be created based on the dependency rules by the system responsible for component creation (or it can just be flagged as an error and reported, whatever), and let the components happily assume their dependent components will exist when they need them. These dependency rules can also be used by the system to perform the proper initialization order. The bottom line is that having the component dependencies specified somewhere helps at every stage in development, and pretty much makes dependencies a non-issue.

If you have circular dependencies, then you just fail. Those should be considered errors, and your system shouldn't try to support them. What happens if you have header file A include header file B, and header file B include header file A? Fail! Granted you don't really get a clear error message from VS when you do that, but the bottom line is that it doesn't work. What happens if you call function A, which calls function B, which in turn calls function A again? Infinite loop! Not because the language is flawed, but because your code is poorly designed. Likewise, if you have two components that depend on each other, should they really be separate components?

I'm not saying that using attributes or similar systems doesn't work, because clearly there are those who have implemented such systems and they work great for their needs. I just don't believe that the dependencies in and of themselves are the problem, just the issues that make dependencies a problem.
swiftcoder
swiftcoder
Quote:
Original post by Zipster
From what I see, all attributes do is force more responsibility on every component that accesses an attribute to do everything that's required when touching said attribute, as opposed to putting the data into its own component that can provide the proper access control and encapsulation that make dealing with the data easy for external components. I imagine there's also some amount of overhead in retrieving an attribute, whether it be a hashed name look-up or an array offset.
In my system, only entity data is stored in attributes - component's store any private data, which is not relevant to the game state. Attributes are lazily created once, when the component is attached, and are retrieved once, at the same time, and held by reference.
Quote:
In the same way that components had to control their data, we found that there were too many special cases where components needed control over how their data was serialized.
It sounds like you are suffering from a poorly thought out separation of logic and state, which is precisely the rationale behind attributes. My entities are just the minimal state necessary for simulation - all the presentation, etc. is data driven, and can be recreated at will.
Quote:
You know (because the code "knows") that a physics component and a graphics component will depend on a position component, so for the physics component and graphics component dependencies, specify a position component.
State and logic are fundamentally different - why the desire to represent both as components? Would you wrap all your local variables in unary functions, just to have a uniform interface?
Quote:
If you have circular dependencies, then you just fail. Those should be considered errors, and your system shouldn't try to support them.
Luckily games tend to have a fairly linear flow of data: AI tells Physics when to accelerate or torque, Physics updates position, rotation, velocity, Graphics uses output from both, etc.
Quote:
I'm not saying that using attributes or similar systems doesn't work, because clearly there are those who have implemented such systems and they work great for their needs. I just don't believe that the dependencies in and of themselves are the problem, just the issues that make dependencies a problem.
I am not sure what attributes vs components has to do with dependencies - it is a separation of state from logic, nothing more, nothing less.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
gekko
gekko
Quote:
Original post by M4573RWhat if you create a physics component before the position component? What if you had circular dependency?


Delay the initialization of each component until all components have been added. This solves the problem of adding components in order and having circular dependencies, but it also allows initialization to fail, something you can't (read: shouldn't) have happen in a constructor. This way you can add components which require other components, and handle the errors any way you desire.
-- gekko
Zipster
Zipster
What you call "separation of logic and state", I just see as storing all the public state outside of a component. Maybe that's all it really is, in which case I don't see the reasons for or benefits of doing so. Components are still responsible for maintaining that state. They create it, access it, and destroy it. But instead of having one component that can localize the logic involved with maintaining a particular piece of state, you instead put that responsibility on all components that could touch that state because there is no single component responsible for knowing how this state should be treated. I like to think of it as a component "owning" a particular piece of state, like having a position component "own" the position state. The combination of all state owned by the components inside all objects is your "gamestate". The object itself is just a container for components and has no game-relevant state or logic.

Serialization isn't free from issues either. If a component is completely rewritten between versions, then your serialization can break down regardless of how it handles versioning. Adding or removing attributes is easy, but changing the meaning of existing attributes is a different beast entirely. Let's say that you used to store position as three floating-point values/attributes. But for the next version someone on your engineering team figured out how to compress that down to a single 64-bit integer. If you try to load state from a file that was serialized with the old version, will those three floating-point values even be loaded, or does the serialization system ignore them because it's now expecting a 64-bit integer? If they are loaded, logic is needed to transform them into the compressed 64-bit form that is now being used by the rewritten component(s). Where is this logic? How do you endow such knowledge onto a serialization system that only handles pure state?

Going even further, why should components even be aware of the difference between logic and state unless it's their own? Why should the physics component know position is a single attribute? What if it isn't? What if it's the agglomeration of 50 attributes interacting in strange ways to somehow produce "position"? What if, like the example above, position isn't really three floating-points but a single 64-bit integer? Ironically, I could argue that you really haven't achieved separation of logic and state, because now your logic is tightly coupled to a particular implementation of your state, where your "implementation" is 'attributes' and how you use them. Without providing a consistent interface that's independent of your implementation of state, you couple your components and their logic to the state in a way that doesn't easily allow you to change it in one place.

When I think of separation of logic and state, it means that as a component, I only have to worry about my own logic and my own state, not that all logic and all state has to be separated. In other words, my interpretation in a component-based architecture is "separate the logic and state of each component from other components", not "separate logic from state in all components". If I need to do something that doesn't involve logic or state that I own, then I just grab the component that is responsible for whatever is I need and let it know. If I need position, I know that I will always get it as a 3D vector regardless of how the position component wants to implement its state and its logic. Everything is nice an encapsulated. It requires all state to reside in components, sure, but it's not often that state just exists without there being logic required to support its use. And if it does, then it might not even be necessary in the first place.

(For some reason I keep expecting to get a "tl;dr" from someone :-p)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.