carb::extras::HandleDatabase

Defined in carb/extras/HandleDatabase.h

template<class Mapped, class Handle, class Allocator = std::allocator<Mapped>>
class carb::extras::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.

tparam Mapped

The “value” type that is associated with a Handle.

tparam Handle

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

tparam 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 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.

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 uniquly 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 const HandleRefType makeScopedRef(Handle handle) const

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.

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

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

Thread Safety

This function is not safe to call if any other thread would be calling releaseIfLastRef() or release() to release the last reference of a Handle. Other threads calling createHandle() when this function is called may result in handles being skipped.

Parameters

f – An invokeable object with signature void(Handle, Mapped*).

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.