HandleDatabase#
Fully qualified name: 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_bucket
) where the first index is a initial size, and each subsequent entry doubles the previous entry. 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 (seeindexToBucketAndOffset
).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.
- Handles and sharing
Handles are essentially a non-counted weak referencing system where only the number of strong references are known. The number of weak references are not tracked. Once the number of strong references drops to zero, the handle is destroyed and will no longer be able to be used to access a mapped object.
The number of strong references belonging to a handle or mapped object can be obtained with HandleDatabase::debugGetRefCount().
However, HandleDatabase is thread-safe. In a multi-threaded environment the number of references can be changed by other threads at any time. This makes the reference count stale by the time it is read by the calling thread. For this reason it is not advised to use the number of reference counts in a multithreaded environment.
It can still be useful to know what the shared-state of a handle is. To determine this, call HandleDatabase::getSharedState() which returns a bitwise value of SharedState. When created, handles are considered to be SharedState::eNotShared and have a strong reference count of one. Whenever the reference count is above one, HandleDatabase::getSharedState() will report SharedState::eReferenced. If the handle was ever used to increment the reference count, or if the handle is explicitly marked as ‘shared’ with HandleDatabase::markShared(), then HandleDatabase::getSharedState() will include the SharedState::eMarkedShared bit as well. This bit means that the handle is valid and in use, so the number of weak references is not known. The SharedState::eMarkedShared bit remains for the lifetime of the handle.
- 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 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 ahandle
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 ahandle
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().
Warning
This function should not be used unless the thread calling it holds a reference count. It is recommended instead to use makeScopedRef() to validate the handle and add a reference.
- 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
.- Parameters:
mapped – A pointer to the mapped type. If
nullptr
,mapped
has been fully released, ormapped
is not owned by*this
, behavior is undefined.- Returns:
The Handle representing
mapped
.
-
inline Mapped *tryAddRef(Handle handle, size_t *outRefCount = nullptr)#
Atomically attempts to add a reference for the given Handle.
Note
This operation, if successful, implicitly marks a handle as shared as via markShared().
- 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.outRefCount – (Optional) If non-
nullptr
, will receive the new reference count provided that the function returns a non-nullptr
value. If the function returnsnullptr
, this value is unchanged. Note however that reference counts can be changed by other threads, so this value may be stale before it is read by the calling thread.
- Returns:
A pointer to a valid Mapped type if a reference could be added;
nullptr
otherwise.
-
inline size_t addRef(Handle handle)#
Atomically adds a reference for the given Handle; fatal if
handle
is invalid.Note
This operation implicitly marks a handle as shared as via markShared().
- Parameters:
handle – A valid handle previously returned from createHandle(). Invalid or previously-valid handles will result in
std::terminate()
being called.- Returns:
The new reference count. Note however that reference counts can be changed by other threads, so this value may be stale before it is read by the calling thread.
-
inline size_t addRef(Mapped *mapped)#
Atomically adds a reference for the Handle representing
mapped
.Note
This operation does not mark the handle representing
mapped
as shared per markShared().- Parameters:
mapped – A pointer to the mapped type. If
nullptr
,mapped
has been fully released, ormapped
is not owned by*this
, behavior is undefined.- Returns:
The new reference count. Note however that reference counts can be changed by other threads, so this value may be stale before it is read by the calling thread.
-
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 andhandle
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
.- Parameters:
mapped – A pointer to the mapped type. If
nullptr
,mapped
has been fully released, ormapped
is not owned by*this
, behavior is undefined.- Returns:
true
if the last reference was released andmapped
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 andhandle
is no longer valid;false
otherwise.
-
inline bool releaseIfLastRef(Mapped *mapped)#
Atomically releases a reference if and only if it’s the last reference.
- Parameters:
mapped – A pointer to the mapped type. If
nullptr
,mapped
has been fully released, ormapped
is not owned by*this
, behavior is undefined.- Returns:
true
if the last reference was released andmapped
is no longer valid;false
otherwise.
-
inline HandleRefType makeScopedRef(Handle handle)#
Attempts to atomically add a reference to
handle
, and returns a HandleRef tohandle
.- 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 tohandle
.- 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 tof
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 theHandle
passed tof
in order to assure validity.
- Parameters:
f – An invocable object with signature
void(Handle, Mapped*)
. Note that there also exists a version offorEachHandle
that can acceptbool(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 theMapped*
unless it is guaranteed that no other threads may be modifying theHandleDatabase
, in which case it is recommend to instead try to add a reference to theHandle
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 returnsfalse
.- Thread Safety
The
Mapped*
passed tof
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 theHandle
passed tof
in order to assure validity.
- Parameters:
f – An invocable object with signature
bool(Handle, Mapped*)
. If the invocable object returnsfalse
, iteration terminates immediately and returnsfalse
. It is highly recommended to ignore theMapped*
unless it is guaranteed that no other threads may be modifying theHandleDatabase
, in which case it is recommend to instead try to add a reference to theHandle
in order to assure validity.- Returns:
true
if iteration completed over the entirety of*this
;false
iff
returnedfalse
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.
- inline cpp::optional<size_t> debugGetRefCount(
- Handle handle,
Retrieves the reference count (debugging only).
Obtains the reference count observed by the calling thread at the time of function call. Since handles are effectively weak references, even a returned reference count of 1 does not guarantee that the object is owned only by the calling thread. To safely determine if an entry is shared, use getSharedState().
See also
Warning
This function should be used for debugging only. Other threads may change the reference count by handle which makes the return value of this function possibly stale at the time the calling thread reads it.
- Parameters:
handle – The handle.
- Returns:
A cpp::optional containing the reference count observed. If
handle
is not valid,cpp::nullopt
is returned.
-
inline size_t debugGetRefCount(const Mapped *mapped) const noexcept#
Retrieves the reference count (debugging only).
Obtains the reference count observed by the calling thread at the time of function call. Since handles are effectively weak references, even a returned reference count of 1 does not guarantee that the object is owned only by the calling thread. To safely determine if an entry is shared, use getSharedState().
See also
Warning
This function should be used for debugging only. Other threads may change the reference count by handle which makes the return value of this function possibly stale at the time the calling thread reads it.
- Parameters:
mapped – A pointer to the mapped type. If
nullptr
,mapped
has been fully released, ormapped
is not owned by*this
, behavior is undefined.- Returns:
The reference count observed.
- Handle handle,
- bool *previouslyShared = nullptr,
Explicitly and atomically marks a handle as shared.
A shared handle means that it has been returned outside of the system that manages the handle and the reference count of the handle may be changed at any point by another thread. Once a handle is marked shared by this function, it will remain shared for its entire lifetime.
- Parameters:
handle – The handle to validate and mark as shared.
previouslyShared – (Optional) If provided, receives the previous shared state.
- Return values:
true – If
*previouslyShared
was false, thehandle
is valid and has been marked as shared. If*previouslyShared
was true,handle
appears to be valid at the time this function was called.false – The
handle
is not valid.previouslyShared
(if provided) is unchanged.
- Mapped *mapped,
- bool *previouslyShared = nullptr,
Explicitly and atomically marks the handle referring to a mapped object as shared.
A shared handle means that it has been returned outside of the system that manages the handle and the reference count of the handle may be changed at any point by another thread. Once a handle is marked shared by this function, it will remain shared for its entire lifetime.
- Parameters:
mapped – A pointer to the mapped type. If
nullptr
,mapped
has been fully released, ormapped
is not owned by*this
, behavior is undefined.previouslyShared – (Optional) If provided, receives the previous shared state.
- Handle handle,
Tests a handle for validity and whether that handle is shared.
See also
- Parameters:
handle – The handle to validate and check.
- Returns:
cpp::nullopt
if thehandle
is not valid. Otherwise a SharedState value indicating how the handle is shared.
- const Mapped *mapped,
Tests whether a mapped entry is shared.
See also
- Parameters:
mapped – A pointer to the mapped type. If
nullptr
,mapped
has been fully released, ormapped
is not owned by*this
, behavior is undefined.- Returns:
A SharedState value indicating how the handle is shared.