Entt: A null entity ID

Created on 30 May 2018  Â·  20Comments  Â·  Source: skypjack/entt

For almost as long as I've been using this library, I've been using a null entity ID constant. This constant serves the same purpose as nullptr. A null pointer represents "no object", a null entity ID represents "no entity". Dereferencing a null pointer usually results in a crash. Getting the components of a null entity ID should result in an assertion via assert(valid(entity)) or a crash. I've been using this:

constexpr uint32_t null_entity = std::numeric_limits<uint32_t>::max();

For all practical purposes, this ID is never valid.

This is what I propose:

// perhaps in the entt_traits header
namespace entt {

constexpr uint16_t null16 = std::numeric_limits<uint16_t>::max();
constexpr uint32_t null32 = std::numeric_limits<uint32_t>::max();
constexpr uint64_t null64 = std::numeric_limits<uint64_t>::max();
// for default entity ID
constexpr uint32_t null = null32;

}

I use null entity IDs quite extensively and I hope that others find them just as useful as I do.

documentation

Most helpful comment

@Kerndog73

namespace entt { struct Null { /*...*/ }; }

Maybe this is better: namespace entt { struct null_t { /*...*/ }; }?

All 20 comments

For what do you use null entities? I've never had the necessity actually. Can you make some examples?

Technically speaking, if we consider std::uint_32, all the values from 0 up to 0xFFFFFFFF are valid entity identifiers.
We cannot _reserve_ one of them without some changes in the code so as to prevent users from reaching it.

0xFFFFFFFF is the last entity ID to become valid. By “valid” I mean entt::Registry::valid returns true. You would have to create a ridiculous number of entities for the null ID to refer to a real entity.

I’m not at my computer right now so I can’t look through my code and find all the usages but the usages are very application specific (like usages of nullptr). I do recall one usage though.

In my tower defence game, towers have a TowerTarget component. This component stores the ID of the unit that the tower is aimed at. If the tower is not currently aiming at a unit (all units are out of range), the unit ID is null.

When I get home I can find more examples if you like.

When I get home I can find more examples if you like.

Don't worry, I got what you meant.

0xFFFFFFFF is the last entity ID to become valid.

Yeah, more or less. It depends on the application and I'm tempted to say you won't reach the max any time soon if you use std::uint32_t. I cannot imagine an application that uses 1M entities indeed.
All what is needed is some checks around to assert in debug mode. It could help if you use std::uint16_t at least. In this case reaching the max is quite easy.

I don't see any problem with your request. In release mode it won't affect the performance because we get rid of assertions. It introduces a new potential undefined behavior, of course, but it looks like a corner case that is far away from the normal use and it sounds like a good compromise for such a feature.

Let's see if others come with a good reason not to introduce it. Otherwise we can work on the _null entity_.
I'd suggest to leave the issue open for 24 hours before to decide. Is it fine for you?

All what is needed is some checks around to assert in debug mode

By that did you mean separate assertions for null and invalid entities?

assert(notnull(entity));
assert(valid(entity));

That’s brilliant!

Well, yes, I would add a dedicated public member function like null so that users can know if the entity they are working with is the null one. Then I would put assertions around based on that function.
Still to think a bit about it anyway, but it's a viable solution.

Ironically, your _null entity_ is already reserved because of an error.
It's a matter of documenting it and putting a line in the entity traits. Right?

That’s awesome!

I would call it entity_null and I would made it available through the registry as registry.entity_null. What about? I'm tempted to call it just null, but registry.null doesn't speak much.

I don’t think that an instance of a registry should be required to use null entity. A null entity is a just an integer. If we want to know if an entity is null, we compare it to a constant. If we want to create a null entity, we assign a constant. The registry doesn’t need to be involved as this has nothing to do with components or views.

Well, it makes sense, but it's useful to expose the null entity through the registry.
The idea was to make something like this:

template<>
struct entt_traits<std::uint32_t> {
    // ...
    static constexpr auto entity_null = 0xFFFFFFFF;
};

template<typename Entity>
class Registry {
    // ...

    /*! @brief Null entity identifier. */
    static auto constexpr entity_null = traits_type::entity_null;

    // ...
};

Nothing prevents you from declaring your null entity as entt::entt_traits<std::uin32_t>::entity_null or just as 0xFFFFFFFF. Putting it in the entity traits and in the registry is just convenient. Why not? :-)

It definitely belongs in the entt_traits struct (completely agree). But I think accessing the constants through the entt namespace like entt::null32 will be more convenient than entt::DefaultRegistry::null or entt::entt_traits::null.

And about the name. I’m not really fused. null, entity_null, null_entity, doesn’t matter much to me.

Type of entities is decided by users. If it's E, it's a matter of doing something like this: const auto null = ~E{}. Documented it, nothing more. The feature you requested was already available after all. ;-)

I just had this idea. What do you think?

namespace entt {

struct Null {
  template <typename Entity>
  constexpr operator Entity() const {
    /*
    entt_traits is only specialized for valid entity types so it makes sense to
    use it rather than this:
    static_assert(std::is_unsigned_v<Entity>);
    return ~Entity{};
    */
    return entt_traits<Entity>::null;
  }
};

constexpr Null null {};

// bit of a shame the compiler doesn't just implicitly convert Null to Entity and then compare
template <typename Entity>
constexpr bool operator==(Null, const Entity e) {
  return e == entt_traits<Entity>::null;
}
template <typename Entity>
constexpr bool operator!=(Null, const Entity e) {
  return e != entt_traits<Entity>::null;
}
template <typename Entity>
constexpr bool operator==(const Entity e, Null) {
  return e == entt_traits<Entity>::null;
}
template <typename Entity>
constexpr bool operator!=(const Entity e, Null) {
  return e != entt_traits<Entity>::null;
}

}

void isNull(const uint32_t entity) {
  if (entity == entt::null) {
    std::cout << "Null\n";
  } else {
    std::cout << "Not null\n";
  }
}

int main() {
  isNull(entt::null); // "Null"
  isNull(5);          // "Not null"
  return 0;
}

Why do you want a class type as null instead of a plain entity identifier? I don't see the advantages.

entt::null is a name for the “null entity identifier” concept. entt::null can be treated like an entity identifier of _any_ type.

nullptr is an instance of std::nullptr_t which is not a pointer. std::nullopt is an instance of std::nullopt_t which is not an alias of std::optional. entt::null is an instance of entt::Null which is not an integer.

@Kerndog73

namespace entt { struct Null { /*...*/ }; }

Maybe this is better: namespace entt { struct null_t { /*...*/ }; }?

@ArnCarveris Sure!

@Kerndog73 @ArnCarveris

I put everything in the internal namespace and only entt::null is made available.
See branch experimental for the details. Is that what you were asking for?

@skypjack Looks good to me

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bilek993 picture bilek993  Â·  3Comments

Qix- picture Qix-  Â·  6Comments

xoorath picture xoorath  Â·  3Comments

skypjack picture skypjack  Â·  6Comments

Kerndog73 picture Kerndog73  Â·  5Comments