carb::extras::HandleDatabase

Defined in carb/extras/HandleDatabase.h

template<class Mapped, class Handle, class Allocator = std::allocator<Mapped>>
class HandleDatabase

Provides an OS-style mapping of a Handle to a Resource.

Essentially HandleDatabase is a fast thread-safe reference-counted associative container.

A given Handle value is typically never reused, however, it is possible for a handle value to be reused if billions of handles are requested.

Thread Safety

Unless otherwise mentioned, HandleDatabase functions may be called simultaneously from multiple threads.

The HandleDatabase can store at most (2^31)-1 items, or 2,147,483,647 items simultaneously, provided the memory exists to support it.

Implementation Details:

HandleDatabase achieves its speed and thread-safety by having a fixed-size base array (m_database) where each element is an array of size 2^(base array index). As such, these arrays double the size of the previous index array. These base arrays are never freed, simplifying thread safety and allowing HandleDatabase to be mostly lockless. This array-of-arrays forms a non-contiguous indexable array where the base array and offset can be computed from a 32-bit index value (see indexToBucketAndOffset).

The Handle is a 64-bit value composed of two 32-bit values. The least-significant 32 bits is the index value into the array-of-arrays described above. The most-significant 32 bits is a lifecycle counter. Every time a new Mapped object is constructed, the lifecycle counter is incremented. This value forms a contract between a handle and the Mapped object at the index indicated by the Handle. If the lifecycle counter doesn’t match, the Handle is invalid. As this lifecycle counter is incremented, there is the possibility of rollover after 2^31 handles are generated at a given index. The most significant bit will then be set as a rollover flag so that handleWasValid() continues to operate correctly. The handleWasValid() function returns true for any Handle where the lifecycle counter at the given index is greater-than-or-equal to the Handle’s lifecycle counter, or if the rollover flag is set.

Template Parameters
  • Mapped – The “value” type that is associated with a Handle.

  • Handle – The “key” type. Must be an unsigned integer or pointer type and 64-bit. This is an opaque type that cannot be dereferenced.

  • Allocator – The allocator that will be used to allocate memory. Must be capable of allocating large contiguous blocks of memory.

Public Types

using MappedType = Mapped

An alias to Mapped; the mapped value type.

using HandleType = Handle

An alias to Handle; the key type used to associate a mapped value type.

using AllocatorType = Allocator

An alias to Allocator; the allocator used to allocate memory.

using HandleRefType = HandleRef<Mapped, Handle, Allocator>

An alias to the HandleRef for this HandleDatabase.

using ConstHandleRefType = ConstHandleRef<Mapped, Handle, Allocator>

An alias to the ConstHandleRef for this HandleDatabase.

using scoped_handle_ref_type = HandleRefType

Deprecated: Use HandleRefType instead.

Public Functions

inline constexpr HandleDatabase() noexcept

Constructor.

Thread Safety

The constructor must complete in a single thread before any other functions may be called on *this.

inline ~HandleDatabase()

Destructor.

Thread Safety

All other functions on *this must be finished before the destructor can be called.

inline bool handleWasValid(Handle handle) const

Checks to see if a handle is valid or was ever valid in the past.

Parameters

handle – The Handle to check.

Returns

true if a handle is currently valid or was valid in the past and has since been released; false otherwise.

inline bool handleIsValid(Handle handle) const

Checks to see if a handle is currently valid.

Parameters

handle – The Handle to check.

Returns

true if a handle is currently valid; false otherwise.

template<class ...Args>
inline std::pair<Handle, Mapped*> createHandle(Args&&... args)

Creates a new Mapped type with the given arguments.

Parameters

args – The arguments to pass to the Mapped constructor.

Returns

A std::pair of the Handle that can uniquely identify the new Mapped type, and a pointer to the newly constructed Mapped type.

inline Mapped *getValueFromHandle(Handle handle) const

Attempts to find the Mapped type represented by handle.

Thread Safety

Not thread-safe as the Mapped type could be destroyed if another thread calls release().

Parameters

handle – A handle previously returned from createHandle(). Invalid or previously-valid handles will merely result in a nullptr result without an assert or any other side-effects.

Returns

A pointer to a valid Mapped type if the handle was valid; nullptr otherwise.

inline Handle getHandleFromValue(const Mapped *mapped) const

Retrieves the Handle representing mapped.

Warning

Providing mapped that is no longer valid or was not returned from one of the HandleDatabase functions is undefined.

Parameters

mapped – A Mapped type previously created with createHandle().

Returns

The Handle representing mapped.

inline Mapped *tryAddRef(Handle handle)

Atomically attempts to add a reference for the given Handle.

Parameters

handle – A handle previously returned from createHandle(). Invalid or previously-valid handles will merely result in a nullptr result without an assert or any other side-effects.

Returns

A pointer to a valid Mapped type if a reference could be added; nullptr otherwise.

inline void addRef(Handle handle)

Atomically adds a reference for the given Handle; fatal if handle is invalid.

Parameters

handle – A valid handle previously returned from createHandle(). Invalid or previously-valid handles will result in std::terminate() being called.

inline void addRef(Mapped *mapped)

Atomically adds a reference for the Handle representing mapped.

Warning

Providing mapped that is no longer valid or was not returned from one of the HandleDatabase functions is undefined.

Parameters

mapped – A Mapped type previously created with createHandle().

inline bool release(Handle handle)

Atomically releases a reference for the given Handle, potentially freeing the associated Mapped type.

Parameters

handle – A valid handle previously returned from createHandle(). Invalid or previously-valid handles will result in an assert in Debug builds, but return false with no side effects in Release builds.

Returns

true if the last reference was released and handle is no longer valid; false if Handle is not valid or is previously-valid or a non-final reference was released.

inline bool release(Mapped *mapped)

Atomically releases a reference for the Handle representing mapped.

Warning

Provided mapped that is no longer valid or was not returned from one of the HandleDatabase functions is undefined.

Parameters

mapped – A Mapped type previously created with createHandle().

Returns

true if the last reference was released and mapped is no longer valid; false if a reference other than the last reference was released.

inline bool releaseIfLastRef(Handle handle)

Atomically releases a reference if and only if it’s the last reference.

Parameters

handle – A valid handle previously returned from createHandle(). Invalid or previously-valid handles will result in an assert in Debug builds, but return false with no side effects in Release builds.

Returns

true if the last reference was released and handle is no longer valid; false otherwise.

inline bool releaseIfLastRef(Mapped *mapped)

Atomically releases a reference if and only if it’s the last reference.

Warning

Provided mapped that is no longer valid or was not returned from one of the HandleDatabase functions is undefined.

Parameters

mapped – A Mapped type previously created with createHandle().

Returns

true if the last reference was released and mapped is no longer valid; false otherwise.

inline HandleRefType makeScopedRef(Handle handle)

Attempts to atomically add a reference to handle, and returns a HandleRef to handle.

Parameters

handle – A handle previously returned from createHandle(). Invalid or previously-valid handles will merely result in an empty HandleRef result without an assert or any other side-effects.

Returns

If tryAddRef() would return a valid Mapped type for handle, then a HandleRef that manages the reference is returned; otherwise an empty HandleRef is returned.

inline ConstHandleRefType makeScopedRef(Handle handle) const

Attempts to atomically add a reference to handle, and returns a ConstHandleRef to handle.

Parameters

handle – A handle previously returned from createHandle(). Invalid or previously-valid handles will merely result in an empty ConstHandleRef result without an assert or any other side-effects.

Returns

If tryAddRef() would return a valid Mapped type for handle, then a ConstHandleRef that manages the reference is returned; otherwise an empty ConstHandleRef is returned.

template<class Func>
inline void forEachHandle(Func &&f) const

Calls the provided Func invocable object for each valid handle and its associated mapped type.

Thread Safety

The Mapped* passed to f is not safe to use if any other thread would be calling releaseIfLastRef() or release() to release the last reference of a Handle. In these situations it is instead recommended to try to add a reference to the Handle passed to f in order to assure validity.

Parameters

f – An invocable object with signature void(Handle, Mapped*). Note that there also exists a version of forEachHandle that can accept bool(Handle, Mapped*) and can early out. Technically any non-bool return type is allowed but any return values are ignored. It is highly recommended to ignore the Mapped* unless it is guaranteed that no other threads may be modifying the HandleDatabase, in which case it is recommend to instead try to add a reference to the Handle in order to assure validity.

template<class Func>
inline bool forEachHandle(Func &&f) const

Calls the provided Func invocable object for each valid handle and its associated mapped type until the invocable object returns false.

Thread Safety

The Mapped* passed to f is not safe to use if any other thread would be calling releaseIfLastRef() or release() to release the last reference of a Handle. In these situations it is instead recommended to try to add a reference to the Handle passed to f in order to assure validity.

Parameters

f – An invocable object with signature bool(Handle, Mapped*). If the invocable object returns false, iteration terminates immediately and returns false. It is highly recommended to ignore the Mapped* unless it is guaranteed that no other threads may be modifying the HandleDatabase, in which case it is recommend to instead try to add a reference to the Handle in order to assure validity.

Returns

true if iteration completed over the entirety of *this; false if f returned false causing iteration to be aborted early.

inline size_t clear()

Iterates over all valid handles and sets their reference counts to zero, destroying the associated mapped type.

Thread Safety

This function is NOT safe to call if any other thread is calling ANY other HandleDatabase function except for clear() or handleWasValid().

Returns

The number of handles that were released to zero.

inline void awaitFinalRelease(Handle h) const

Causes the calling thread to wait until the given handle has been fully released.

Parameters

h – The handle to wait for.