Skip to content

C++ Generational Handles: Add Slot Reuse Only When Needed

The main course never reuses IDs. Generations become necessary only when a recycled slot might otherwise let an old reference resolve to a different body.

Name the New Requirement

A body occupied slot 1. It is deleted; another body takes slot 1. A reference containing only that slot cannot distinguish them.

A reusable handle needs owner, slot, and generation. Resolution checks all three and live membership. A slot alone remains a storage address, not identity.

Read the Metadata Pool

generational_ids.hpp implements a non-copyable, non-movable Pool with slot zero reserved.

Allocation takes a free slot or appends metadata. Release validates the complete handle, marks the slot dead, increments its generation, and links it into a free chain. Release does not allocate.

At the configured generation limit, a slot retires rather than wrapping. Allocation then uses another slot. A finite handle space cannot guarantee unlimited reuse without an exhaustion policy.

The default limit is the largest uint64_t value. Tests use the same templated implementation with limits 1 and 2 to exercise boundaries without billions of operations.

The Pool Is Not a World

It stores no positions, velocities, or lifetime. A caller must remove all component data before releasing a slot. Otherwise a newly allocated identity can inherit stale component data.

In a complete world, construction failures would also need an allocation rollback policy. This isolated experiment does not provide transactional entity construction or a production free-list allocator.

Run the Example

Use the optional build. Its first line is:

reused slot=1 generation=2 old alive=0

The generation tests verify owner collisions, empty handles, stale copies, repeated release, reuse, retirement, storage growth, and a handle surviving its former Pool's lifetime without resolving in another Pool.

Invariants to Review

Rule Failure it prevents
Owner must match A slot from another world resolves
Slot must be in range and live Empty/deleted handle resolves
Generation must match Old handle resolves after reuse
Limit retires rather than wraps Ancient handle becomes valid again
Data cleaned before release New identity inherits old components

No claim follows that this is cheaper than non-reused IDs. It solves a different requirement and adds policies to maintain.

Exercise

With limit 2, allocate, release, allocate, release, allocate. Predict slots and generations: (1,1), (1,2), then (2,1). Explain why retirement is intentional.

Extension overview · Next: Component storage · Main-course identity

Donate