C++ saving this ptr offset

Started by
7 comments, last by CrazyCdn 6 years, 3 months ago

Hi, I'm creating a component based entity model for my game(not to confuse with an ECS). Because I decided that I won't need dynamic addition and removal of components, I got the idea to implement components with multiple inheritance: I have an Entity Base class which only has a few functions for entity lifetime etc. Every component virtually inherits from that class. At the end an entity inherits from all the components it needs. Now from time to time a component has to access another component like this:


dynamic_cast<OtherComponent*>(this)->doOtherComponentsStuff() 

The Movement component for example has to access the velocity component every frame. This results in a lot of dynamic_casts and poor performance. But because the components of an entity never change I would call dynamic_cast only once(in the ctor), save the offset of the resulting address and use it later to access the other component without the performance penalty of dynamic_cast. Here's what I mean:


class Entity
{
public:
	virtual ~Entity() = default;
};

class Derived2 : public virtual Entity
{
public:
	void doThat()
	{
		std::cout << "Did that\n";
	}
};

class Derived1 : public virtual Entity
{
public:
	Derived1()
	{
		m_derived2Off = reinterpret_cast<intptr_t>(dynamic_cast<Derived2*>(static_cast<Entity*>(this))) - reinterpret_cast<intptr_t>(this);
	}
	void doIt()
	{
		getDerived2()->doThat();
	}
private:
	intptr_t m_derived2Off;
	Derived2 *getDerived2()const
	{
		return reinterpret_cast<Derived2*>(m_derived2Off + reinterpret_cast<intptr_t>(this));
	}
};

class Player final:
public Derived1,
public Derived2
{
};

int main()
{
	Player obj;

	obj.doIt();
}

It worked for me, but I'm not sure if there might be some undefined behavior involved.

Advertisement

It looks like you're doing inheritance wrong.  

Done correctly you don't need to know what the concrete type is.  If you find yourself doing a dynamic cast to a derived type, you are likely doing something wrong.

The general principles have the acronym SOLID.   I suggest you start reading from that article.

You should have a well-designed abstract base interface that other code is derived from.  All the derived versions should operate correctly on that interface.  All objects should be replaceable with similar objects and still be correct.  This is called Liskov substitution principle named after a famous person who described it. It is also called the Template Method pattern.  This is also used in ECS systems and is a major reason they are popular, all components follow the well-defined interface.

Then you should always work with the abstract type, not the derived classes.  This is called the dependency inversion principle. This is used in many systems, including ECS systems, to prevent brittleness.  In your example you would need to modify Derived1 any time you added any new functionality. It would quickly go to a long list of redirections detecting if it is Derived2, or Derived3, or Derived4, or Derived103. But if you always work with the abstract types the problem goes away.

After that, I suggest you read this article. That's an example of the right way to implement virtual functions.

And after that, recognize that most code in games is not about individual actions but about bulk actions. Many times people build objects and systems that work on single items, single data structures, single instances. But the code rarely operates on a single thing, instead working on bulk items, bulk data structures and array, collections of instances. Take whatever reasonable steps you can to convert to bulk actions rather than individual actions, which avoids considerable overhead and can be leveraged for cache benefits.

15 hours ago, frob said:

And after that, recognize that most code in games is not about individual actions but about bulk actions. Many times people build objects and systems that work on single items, single data structures, single instances. But the code rarely operates on a single thing, instead working on bulk items, bulk data structures and array, collections of instances. Take whatever reasonable steps you can to convert to bulk actions rather than individual actions, which avoids considerable overhead and can be leveraged for cache benefits.

This also means that optimizing access from a component of an entity to other components of the same entity has limited usefulness. Most computations are going to involve components of different entities, and often the same type of component for many entities (e.g. collision detection and response using the respective geometric shapes of entities).

Omae Wa Mou Shindeiru

This seems like a large misunderstanding of the use of both inheritance and the component entity model. I think it would be beneficial for you to look up and develop your understanding of both paradigms. 

 

From an inheritance standpoint , You should never find yourself in an instance in which you’re deriving from two different classes with the same base class. From an entity component standpoint, the purpose of the entity is to be a container for the components, not a subclass... and there shouldn’t be any manipulation of “self” using reinterpret cast and etc to access a required component, among other things.

Even if you did need to do something like that, why jump through hoops to calculate offsets and adjust the "this" pointer? It looks like you could just store the value of the dynamic_cast<>ed pointer as a member instead of the offset.

Sorry for replying so late. My goal is not to create a class hierarchy. I'm trying to create entities from components with the help of mixins. As for just storing the value from dynamic_cast: the pointer would invalidate after copying an entity.

42 minutes ago, BlackDE said:

My goal is not to create a class hierarchy. I'm trying to create entities from components with the help of mixins.

Mixin classes imply inheritance, and inheritance defines a class hierarchy whether you want it or not. You don't have the privilege of "avoiding" inheritance by hacking around with reinterpret_cast and the like instead of using polymorphic pointers properly.

Omae Wa Mou Shindeiru

6 hours ago, BlackDE said:

Sorry for replying so late. My goal is not to create a class hierarchy. I'm trying to create entities from components with the help of mixins. As for just storing the value from dynamic_cast: the pointer would invalidate after copying an entity.

This does not look like any ECS I've ever seen and I've read a lot of them and reviewed a lot of code.  This looks like a God class in disguise.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

This topic is closed to new replies.

Advertisement