I am currently working on my own octree in C. The tree will contain a few billion objects, so memory efficiency is key. To achieve this I currently use one struct with a flag and a union, but I think it is not clean and it is wasting space for the inner node because I only need an 8-bit flag but memory is being reserved for the 64-bit index. My code currently is as follows:
typedef struct _OctreeNode
{
uint64_t location_code;
union
{
uint8_t child_exists;
uint64_t object_index;
} data;
uint8_t type;
} OctreeNode;
I would like to split this up into two different structs. One leaf node and one inner node. As follows:
typedef struct _OctreeInnerNode
{
uint64_t location_code;
uint8_t child_exists;
uint8_t type;
} OctreeInnerNode;
typedef struct _OctreeLeafNode
{
uint64_t location_code;
uint64_t object_index;
uint8_t type;
} OctreeLeafNode;
Now the problem arises with my unordered map based on the hash of the location code. It uses a void pointer, so storing two different structs is not a problem. I know a possibility would be to have the flag be the first element and dereference the pointer to the flag datatype to derive the type, like so:
typedef struct _OctreeLeafNode
{
uint8_t type;
uint64_t location_code;
uint64_t object_index;
} OctreeLeafNode;
void
func(void* node)
{
uint8_t type = *(uint8_t*)node;
if (type == LEAF_NODE) {
OctreeLeafNode* leaf_node = (OctreeLeafNode*)node;
}
}
I was wondering if there is a cleaner way. Or is this not recommended? How would I be supposed to deal with multiple possibilities for structs and void pointers?
Thanks in advance!
This a method that is used commonly in C
.
But just put these field at start of structure (first field) and never change their position. In addition, you need to keep them in all your structures.
A common sample for this approach is the version
field in structures (or type
in your case). You can keep them at start of structure, and then check structure version by similar method. something like this:
struct _base {
uint8_t ver;
};
#define TYPE_OLD 0
struct _a_old {
struct _base info;
uint8_t a;
};
#define TYPE_NEW 1
struct _a_new {
struct _base info;
uint8_t a;
uint8_t b;
};
Now you can identify different types by casting your data to struct _base
and checking ver
field.
unsigned char* buf = ...
switch (((struct _base*)buf)->ver)
{
case TYPE_OLD:
{
struct _a_old* old = (struct _a_old*)buf;
// ...
break;
}
case TYPE_NEW:
{
struct _a_new* old = (struct _a_new*)buf;
// ...
break;
}
default:
// ...
}