Examples » Core » Reflection » Nullable Trait

Handling null type representation.

The NullableTrait is a reflection trait designed to provide a mechanism for representing a null state for a specific type. This is particularly useful when working with types that are not literally null but can be conceptually treated as null in certain scenarios.

Consider a scenario where you have a data structure, such as the Entity struct, which represents an entity with an index and a generation. In some cases, you may want to define a special state that indicates the absence of a valid entity instead of checking if both fields are UINT32_MAX. So, instead of using a separate boolean flag to represent this state, the NullableTrait allows you to define a custom condition for considering an instance of the type as "null".

So, let's see how we can use this. First, we create a reflectable structure:

#include <cubos/core/reflection/reflect.hpp>

struct MyEntity
{
    CUBOS_REFLECT;
    uint32_t idx;
    uint32_t generation;
};

In the reflection definition, we use the NullableTrait to define the conditions under which an instance of MyEntity is considered null, and also another to set it to null.

#include <cubos/core/reflection/traits/nullable.hpp>
#include <cubos/core/reflection/type.hpp>

// Since we're exposing fields of primitive types (uint32_t), its important to
// include the header which defines their reflection.
#include <cubos/core/reflection/external/primitives.hpp>

using cubos::core::reflection::NullableTrait;
using cubos::core::reflection::Type;

CUBOS_REFLECT_IMPL(MyEntity)
{
    return Type::create("MyEntity")
        .with(NullableTrait{[](const void* instance) {
                                const auto* ent = static_cast<const MyEntity*>(instance);
                                return ent->idx == UINT32_MAX && ent->generation == UINT32_MAX;
                            },
                            [](void* instance) {
                                auto* ent = static_cast<MyEntity*>(instance);
                                ent->idx = UINT32_MAX;
                                ent->generation = UINT32_MAX;
                            }});
}

Now, we can simply use it:

int main()
{
    using cubos::core::reflection::reflect;

    // Retrieve the reflection information for MyEntity
    const auto& entityType = reflect<MyEntity>();

    // Check if MyEntity has the NullableTrait
    CUBOS_ASSERT(entityType.has<NullableTrait>());

    // Retrieve the NullableTrait for MyEntity
    const auto& nullableTrait = entityType.get<NullableTrait>();

    // Create an instance of MyEntity
    MyEntity ent{1, 1};

    // Check if the instance is not null
    CUBOS_ASSERT(!nullableTrait.isNull(&ent));

    // Set the instance to the null state
    nullableTrait.setToNull(&ent);

    // Check if the instance is now null
    CUBOS_ASSERT(nullableTrait.isNull(&ent));
}