Day64: Why store entity_reference as a union and not a struct?

Hello Simon,

I know you are going to be answering this :P I don't know why you help so much but thankyou.

I don't get why when moving entities to simulation regions the entity_reference was defined as a union. To me it seems scary because I will end up calling that StoreEntityReference function with the index set inside the reference (instead of a pointer) and it will just treat that as a non null pointer. Then it will do something weird right?

So why not just define a struct for the reference with both the pointer and index inside it?

Here is the function I am talking about:

entityunion.png

And here is how its used to convert a pointer to a low entity to an index unionusage.png

At this point, all indication of what is inside the union (i.e. a pointer or index) is lost. Is this not supposed to make me uncomfortable?

My understanding of unions is that regardless of whether the union contains the pointer or the index, I can access it as ref.Index or ref.Ptr and it will unsafe cast whatever data happens to be inside it.

There is also a sim_entity_hash which is a struct of the same thing, so why not use this everywhere? simhash.png


Edited by Gaurav Gautam on

You're correct.

In practice we use the pointer reference while the entity is high and the index when it's low (if I remember correctly, it's been a long time). There isn't a reason for calling StoreEntityReference on a low entity and it would probably be quickly obvious that there is a problem if you did. So while the data and data type don't have indication of what's inside the union, the "context" implies it.

Another possible reason for the union is maybe to reduce the size because in the game state there is an array of 100000 references and using a struct would take twice the size (8 bytes pointer + 4 bytes index + 4 bytes padding). We are talking about 0.8MB vs 1.6MB so it's not much memory, but if you need to iterate over all element quickly it might matter.

The hash requires both values since it's used to map between low and high entities. It also only contains 4096 entries.

If you prefer to have both fields, you may need to make sure you don't have value in both field or that both value express the correct relation.

You could also use a discriminated union:

typedef enum reference_type_t {
    reference_none,
    reference_high,
    reference_low,
} reference_type_t;

typedef struct reference_t {
    reference_type_t type;
    union {
        void* pointer;
        u32 index;
    };
} reference_t;

inline void
StoreEntityReference( reference_t* ref ) {
    assert( ref->type != reference_low );
    if ( ref->type == reference_high && ref->pointer ) {
        ref->index = ref->pointer->StorageIndex;
    }
}

Edited by Simon Anciaux on

Ah yes, I didn't think about size of the array of references being huge.


Replying to mrmixer (#26646)