The important aspect about Hexlo is that it mainly serves as my highly experimental projects in which I inject a lot of my new experimental methods into. I find it a little easier to inject new methodologies into projects like this because of their scale. Anyways, in Hexlo I wanted to rethink about my component system again.
A component system is one where entities in the world has a list of components which then define how that entities looks like, sounds like, behaves like and so forth. For example, for a rocket entity you may have a mesh component which renders a rocket projectile mesh onto the screen, a sound component which plays back a “
fssssssssh!” sound during the rocket’s flight and perhaps a script component which contains the logic for when the rocket contacts something. Component systems like this are designed for the end user in mind. It also helps code reusability and extending current classes rather than having to copy/paste sections of code for different classes that aren’t able to extend similar classes.
My first initial stab at a component system involved using a lot of RTTI (Run Time Type Information) which was functional at the time but certainly an unsafe method of doing things. It is unsafe to me, because you assume a few things. For example, consider:
if (component_ptr->GetType() == Enum_Mesh_Component)
((MeshComponent*)component_ptr)->RenderMesh();
You assume a few things with this sort of code. First, you assume that GetType() will always return the correct value for each class definition you write.
- What happens if you forget to override the virtual function in your child class? Make it abstract you say?
- What happens if you return a conflicting identifier to another class?
- What happens if component_ptr is a child class of a child class of Component?
And lastly, your code will be littered with gigantic switch and if statements that may get out of control. Lastly, you simply assume that component_ptr is going to be MeshComponent. If it isn’t, then it is going to explode as you try to access a function from a class that doesn’t actually have it. There is simply no real fail-safe methods here. I suppose you may interject and suggest that I could have used
dynamic_cast<> to provide fail safe methods. The main problem with using
dynamic_cast<> is that it is rather slow in relative measures.
Certainly when everything is written correctly, everything would be fine. But that’s like saying that a program will never have bugs in it because everything will be written correctly.
My second attempt at a component system involved interfaces. This assumed that every component is just a component and that typing was unnecessary. The base class, Component, would essentially have a large list of virtual functions that were setters and getters for everything imaginable. While this was very fail-safe and anything error related was able to be caught during compile time, the base class became very bloated with one off virtual functions that would eventually create such a large virtual table for these classes that the amount of memory used per class would be huge. For example:
class Component
{
virtual void Set(const unsigned int &id, const int &in) { };
virtual void Set(const unsigned int &id, const float &in) { };
virtual void Set(const unsigned int &id, const short &in) { };
};
As you can imagine, not every component would require all of these virtual functions but they would still incur the penalty of having them within their virtual tables. I couldn’t make these into abstract functions because then every component would need to implement them anyways making every child class simple massive.
So with Hexlo, I wanted to avoid that sort of a setup because I knew it would be a horrible system to work with. While it was very safe, it was memory hogging and too bloated to be usable. So when I thought about it some more, I realized that most of the time it was the entities themselves that often needed to alter the properties of their own components. So with that in mind, since the owners of these class instances were the entities themselves, keeping a private pointer reference to the component would be fine. End users would also not have to care too much about the memory management of these pointers, since a pointer list would manage these class instances.
class Entity
{
public:
void AddMesh()
{
m_mesh_component_ptr = new MeshComponent();
m_components_ptr.push_back(m_mesh_component_ptr)
};
private:
std::vector<Component*> m_components;
MeshComponent *m_mesh_component_ptr;
};
So, with that using m_mesh_component_ptr is used when the class itself wants to change any parameters within it, but I would suspect that the majority of the time this isn’t really required at all. So that’s nothing special just yet, but in this particular case, let’s use this within a rendering scenario.
class Viewport()
{
public:
void Render(MeshComponent &mesh_component);
};
class Component()
{
public:
void Render()
{
InternalRender();
};
private:
virtual void InternalRender() = 0;
};
class MeshComponent : public Component()
{
virtual void InternalRender()
{
GetViewport().Render(*this);
};
};
*GetViewport() returns the Viewport class instance as a referenceThis sort of structure pretty much resolves most of the issues I came across with the top two methods. I don’t have huge banks of virtual functions that hardly ever get used by many child classes, I don’t require RTTI since C++ itself handles typing and it’s pretty darn fail safe at most areas. The only potential problem that could possible happen is that the amount of function calls that is required could be a little on the excessive side causing potential problems but I haven’t come across this yet in Hexlo. The only other ‘problem’ is that you have to write quite a lot of virtual functions within Viewport for all of the different component types you have. However, I don’t think that’s any different to having to write the actual rendering functions for handling the component anyways.
I’m feeling pretty good about this method right now. But perhaps in a few weeks, I might come up with an even better solution!