Storing position of member variable

Started by
12 comments, last by Juliean 9 years, 9 months ago

Hello,

I know that there is no such thing like reflection in C++, but I need something a tad bit in that direction. I want to refactor and improve my component system, by automating most loading/serialization/editor-related stuff. For this, I plan on adding a component declaration class, where attributes with a certain known type (int, float, string, ...) are specified. This would look something like that:


class Transform : public ecs::Component<Transform>
{
	math::Vector3 vPosition;
	
	static const ComponentDeclaration& Declaration(void)
	{
		const ComponentDeclaration::AttributeVector vAttributes = 
		{
			{ AttributeType::FLOAT, "x" },
			{ AttributeType::FLOAT, "y" },
			{ AttributeType::FLOAT, "z" },
		}
		
		static const ComponentDeclaration decl(vAttributes);
		
		return decl;
	}
}

Now, I just could have a "serialize/deserialize"-method, but I want to ultimately get rid of those, to minimize work required for each component. Is there any way to calculate/store the "offset" of a member variable and use this to access/set that variable? Something like:


// in the declaration

{ AttributeType::FLOAT, "x", OFFSET(Component::vPosition::x) },

// later on

auto& component = ComponentFactory::CreateComponent(type);

const auto& declaration = component.GetDeclaration();

for(auto& attribute : declaration.GetAttributes())
{
	switch(type)
	{
	case AttributeType::FLOAT:
	{
		float* pAttribute = (float*)(((char*)(void*)(component)) + attribute.offset);
	}
	}
}

How would you do so?

PS: Though I don't care about 100% (type)safety and cross-compiler right now, I'm also open for all other, probably better suggestions for that problem you might have.

Advertisement

by automating most loading/serialization/editor-related stuff.

To be honest, I would use only a very simple system, that is , just a de-/serialize (post-deserialization) method which read/write your (primitive) attributes from/to a stream. The overhead to code and maintain a more complex system is not necessary in my opinion and often more error prone than KISS.

Here is a simple example of a more complex scenario, continaing arrays, chunks, complex sub objects etc:


serialize(stream) {
  // handle inventory
  if(inventory.size()>0) {
    stream.write(INVENTORY_CHUNK);
    stream.write(inventory.size());
    for(i=0;i<inventory.size();i++) {
       bool item_present = inventory.get(i);
       stream.write(item_present);
       if(item_present) {
          inventory.get(i).serialize(stream);
       } 
  }

  // handle equipment
  if(equipement.size()>0) {
    stream.write(EQUIPMENT_CHUNK);
    ...
  }

  stream.write(END_CHUNK);
}



deserialize(stream) {
   for( int chunk_id = stream.read();chunk_id!=END_CHUNK; chunk_id = stream.read()) {
      switch(chunk_id) {
        case INVENTORY_CHUNK:
           int size = stream.readInt();
           createNewInventory(size);
           ... continue to read rest ... 

        case EQUIPMENT_CHUNK: 
           // read, but ignore, no longer supported ....
           break;
     }

   }

The benefit is, that you have more control over sub-sequent save game versions and you are able to read and process older file versions more easily without bloating the whole code. The latter has the benefit, that you can add new attributes more easily or ignore obsolete one, without forcing a new game file version. It is really annoying if almost every single bug-fix will make older save-games obsolete.


To be honest, I would use only a very simple system, that is , just a de-/serialize (post-deserialization) method which read/write your (primitive) attributes from/to a stream. The overhead to code and maintain a more complex system is not necessary in my opinion and often more error prone than KISS.

While I'm grateful for the feedback, I have to disagree. This system is in use now for almost two years, and the need for a more complex system arouse just lately after I found that my first, simple system didn't work out so well after a long time - the overhead for adding one single component is way too much.

I must admit that I left some information out in order to explain the problem simplier, but its not only serialization. There is also a few other key points that make a system like I proposed pay off:

- Editor functionality. Right now, I have to create at least 3 different "controller"-classes for each new component, basically just copying & modifying from any other component. Such a "reflection"-system would automate that, and allow me to create even more "complex" editor gui controls for the component without N-overhead (N = number of components).

- Cross-Plugin-Interaction. My engines actual game code is made out of plugins, and components are mutually declared in those plugins. Some plugins might want to use certain aspects (read: attributes) of any other plugins components, or instantiate it, w/o having the code. Right now thats a free-for-all, that attribute-system would give a much safer interface for that interaction.

- There is probably more which I can't just remember right now.

So again, I much appreciate the input, but trust me, I know very well what I want to do and why I want it ;)

Any solution for the original problem though?

EDIT: If found something that appearently does what I want to do, offsetof. Seems not to be designed to work with non-POD-classes, but appearently works on MSVC, thats fine, portability is not a real requirement yet. I'm still looking for a way to make its usage a bit easier. Right now I'd have to do:


class Transform : public Component<Transform>
{
    math::Vector3 vTest;

    void Test(void)
    {
           const size_t offset = offsetof(Transform, vTest.x);
    }
}

I basically want to get rid of the need for specifying the class name everytime. I though about creating a templated protected member-variable in the base-class, which already has the derived class as template-argument:


template<typename Derived>
class Component
{
protected:

	template<typename Type>
	size_t Offset(Type Derived::* pMember) const
	{
		return offsetof(Derived, pMember);
	}
}

But this won't work for the case of the vector, where the actual variable is stored inside another struct in the component, whereas the pure "offsetof"-macro works. Any ideas if this can be fixed? Ok, this actually doesn't work at all, any ideas for a working solution?

Recently i have found interesting article where the author is using "pointer to a member" (with magic template meta-programming) to get that offset.

I think it is somewhat related to what you are asking, but i am still trying to grasp this myself how all this works so i am not 100% sure if it fits your needs.


Recently i have found interesting article where the author is using "pointer to a member" (with magic template meta-programming) to get that offset.

I think it is somewhat related to what you are asking, but i am still trying to grasp this myself how all this works so i am not 100% sure if it fits your needs.

Actually, pointer to member seems quite right, as you can see I've already been trying to use it in my example above, but didn't quite get it totally. Since I have both the class and member type, I can actually store the pointer-to-member as a void* alongside the declaration data, and cast it back when I want to use it... at least I hope this works the way I think about it, I'll at least give it a try.

EDIT: Unfortunately, pointer-to-member seems to also have a problem with the child-POD-struct:


class Sprite : 
	public ecs::Component<Sprite>
{
public:
	math::Vector2f vPos;
	float z;
	
	void Test(void)
	{
		float Sprite::* pZ = &Sprite::z; // works
		float Sprite::* pX = &Sprite::vPos::z; // doesn't even compile... I don't even know how that should work
	}
};

I don't know... is there even a solution to this? offsetof would work, though I like pointer-to-member better... I don't want to replace the vector with pure floats eighter... any ideas?

You might want to look at doing this in a more C++ like manner without the potentially difficult to deal with side effects of pointers into protected/private data. The way I went recently was to implement a system like this using std::function. Basically using accessors I could bind up access in nice little objects which worked with my serialization. So, for instance, in your given example, I wouldn't expose x, y, z separately I'd simply bind the accessor to the vector as:

std::function< Vector& () > accessor( std::bind( &MyComponent::Position, std::placeholders::_1 ) );

With that, pop it in a map of named accessors and you can get/set the component data without breaking encapsulation of the class with evil memory hackery. Obviously there is more work involved in cleaning this up to allow multiple types, separate get/set functions and a bunch of other things I wanted supported but it is considerably better behaved than direct memory access. The primary reason I avoided the direct memory access was because I have a number of items which need to serialize but work within a lazy update system, if I bypass the accessor, the data in memory can be in completely invalid states. With a bound member (or internal lambda), everything can be runtime type safe (unlikely to get compile time type safety), works with lazy update systems and generally a much more robust and less hacky solution.


Since I have both the class and member type, I can actually store the pointer-to-member as a void* alongside the declaration data, and cast it back when I want to use it... at least I hope this works the way I think about it, I'll at least give it a try.

This is not safe.

There is no guarantee that a pointer-to-member is the same size as a regular pointer, which will lead to some interesting results if you cast between them.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

EDIT: Unfortunately, pointer-to-member seems to also have a problem with the child-POD-struct:


class Sprite : 
	public ecs::Component<Sprite>
{
public:
	math::Vector2f vPos;
	float z;
	
	void Test(void)
	{
		float Sprite::* pZ = &Sprite::z; // works
		float Sprite::* pX = &Sprite::vPos::z; // doesn't even compile... I don't even know how that should work
	}
};

I don't know... is there even a solution to this? offsetof would work, though I like pointer-to-member better... I don't want to replace the vector with pure floats eighter... any ideas?

The problem is that &Sprite::vPos::z is not a pointer-to-member of class Sprite, but rather a pointer-to-member of math::Vector2f, which is entirely unrelated to Sprite.
One solution to your problem would be to implement a generic (but typesafe!) pointer-to-member, something using inheritance and type erasure should do the trick.


You might want to look at doing this in a more C++ like manner without the potentially difficult to deal with side effects of pointers into protected/private data. The way I went recently was to implement a system like this using std::function. Basically using accessors I could bind up access in nice little objects which worked with my serialization. So, for instance, in your given example, I wouldn't expose x, y, z separately I'd simply bind the accessor to the vector as:

Hm, definately interesting, haven't looked at it that way before. There is at least no problem with private data here, because components are required to have all their data public for the systems to work with them. They are in fact just "POD"-data containers with a few helper functions brought in by the base classes. So there wouldn't be any substantial problem with direct memory exposure, at least none I can think of. I actually do have a few attributes that use a "dirty"-flag when they are changed, this would actually be a good point for an accessor. I don't really like the std::function-systax, but I quess I can hide that behind some overloaded methods. I'll still go with seperate attributes, mainly because they need to be named in the editor interface, which could range from x/y/z to yaw/pitch/roll, to almost everything one can think of. Thanks for the suggestion, I'll definately try it out and compare it to the direct-memory-access, which one works easier/better I'll take.


There is no guarantee that a pointer-to-member is the same size as a regular pointer, which will lead to some interesting results if you cast between them.

Alright, so I can forget that then. Don't know how else I'd get that to work for multiple types


The problem is that &Sprite::vPos::z is not a pointer-to-member of class Sprite, but rather a pointer-to-member of math::Vector2f, which is entirely unrelated to Sprite.
One solution to your problem would be to implement a generic (but typesafe!) pointer-to-member, something using inheritance and type erasure should do the trick.
.

Hm alright, thats another option. I'm tempted to try it out, lets see what happens.

Thanks all so far, I'll get back when I got any more problems.

Typically you need a layer of indirection to be present. Something like an IMemberInfo class that can then be specialized for the different types of pointer-to-member, getter/setter, etc. You also need to be careful with pointer adjustments for base classes when multiple inheritance is in play, including multiple inheritance of interfaces. This means you also need some kind of list of ClassInfo to that maps a particular class to its base including pointer adjustments. Something like:

class ClassInfo;
class IMemberInfo;

struct BaseInfo {
  ClassInfo* classInfo = nullptr;
  ptrdiff_t offset = 0;
};

class ClassInfo {
  flat_map<string, unique_ptr<IMemberInfo>> _members;
  vector<BaseInfo> _bases;

public:
  bool Set(string const& memberName, void* object, const void* data);
  bool Get(string const& memberName, const void* object, void* outData);

  void AddMember(string memberName, unique_ptr<IMemberInfo> member);
  void AddBase(ClassInfo* classInfo, ptrdiff_t offset);
};

class IMemberInfo {
public:
  virtual ~IMemberInfo() = default;
  virtual void Set(void* object, const void* data) const = 0;
  virtual void Get(const void* object, void* outData) const = 0;
};

template <typename C, typename M>
class DirectMemberInfo {
  C::*M _member = nullptr;

public:
  DirectMemberInfo(C::*M member) : _member(member) {}

  void Set(void* object, const void* data) const override {
    static_cast<C*>(object)->*_member = *static_cast<const M*>(data);
  }

  void Get(const void* object, void* outData) const override {
    *static_cast<M*>(outData) = static_cast<C*>(const object)->*_member;
  }
};

bool ClassInfo::Set(string const& memberName, void* object, const void* data)
{
  auto member = _members.find(memberName);
  if (member != _members.end()) {
    member->second->Set(object, data);

  for (auto base : _bases) {
    auto adjusted = static_cast<char*>(object) + base.offset;
    if (base.classInfo->Set(memberName, adjusted, data))
      return true;
  }

  return false;
}

bool ClassInfo::Get(string const& memberName, const void* object, void* outData) {
  // pretty damn similar to the above
}

void ClassInfo::AddMember(string memberName, unique_ptr<IMemberInfo> member) {
  _members.insert(make_pair(move(memberName), move(member)));
}

void ClassInfo::AddBase(ClassInfo* classInfo, ptrdiff_t offset) {
  BaseInfo base;
  base.classInfo = classInfo;
  base.offset = offset;
  _bases.push_back(base);
}
That's the gist. You can add some templates to make it easy to add new entries, something like the following (or any of a bazillion possible variations):

void initializeReflection() {
  reflect_class(MyClass)
    .add_base<MyBase>()
    .add_member("position", &MyBase::_position)
  ;
}
You can also add other data like the names of the classes, names of the members, specific owner of each member, lots of error-checking, explicit serialization support, etc.

You can recurse through the types so that you can have a member variable be another reflected class (so you can have a class that has members of Vector3f) or even reflected members of unique_ptr or shared_ptr or container wrappers around other primitive or reflected types. It can get pretty intricate but you can support rather complex types with full I/O serialization as well as property GUIs.

Sean Middleditch – Game Systems Engineer – Join my team!

This topic is closed to new replies.

Advertisement