carb/extras/HandleDatabase.h
File members: carb/extras/HandleDatabase.h
// Copyright (c) 2019-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "../Defines.h"
#include "../container/LocklessQueue.h"
#include "../logging/Log.h"
#include "../math/Util.h"
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <memory>
#include <mutex>
#include <stack>
#include <vector>
CARB_IGNOREWARNING_MSC_WITH_PUSH(4201) // nonstandard extension used: nameless struct/union
namespace carb
{
namespace extras
{
template <class Mapped, class Handle, class Allocator>
struct HandleRef;
template <class Mapped, class Handle, class Allocator>
struct ConstHandleRef;
template <class Mapped, class Handle, class Allocator = std::allocator<Mapped>>
class CARB_VIZ HandleDatabase
{
public:
    using MappedType = Mapped;
    using HandleType = Handle;
    using AllocatorType = Allocator;
    using HandleRefType = HandleRef<Mapped, Handle, Allocator>;
    using ConstHandleRefType = ConstHandleRef<Mapped, Handle, Allocator>;
    using scoped_handle_ref_type = HandleRefType;
    constexpr HandleDatabase() noexcept;
    ~HandleDatabase();
    bool handleWasValid(Handle handle) const;
    bool handleIsValid(Handle handle) const;
    template <class... Args>
    std::pair<Handle, Mapped*> createHandle(Args&&... args);
    Mapped* getValueFromHandle(Handle handle) const;
    Handle getHandleFromValue(const Mapped* mapped) const;
    Mapped* tryAddRef(Handle handle);
    void addRef(Handle handle);
    void addRef(Mapped* mapped);
    bool release(Handle handle);
    bool release(Mapped* mapped);
    bool releaseIfLastRef(Handle handle);
    bool releaseIfLastRef(Mapped* mapped);
    HandleRefType makeScopedRef(Handle handle);
    ConstHandleRefType makeScopedRef(Handle handle) const;
    template <class Func CARB_NO_DOC(
        ,
        std::enable_if_t<!std::is_same<bool, decltype(std::declval<Func>()(std::declval<Handle>(), std::declval<Mapped*>()))>::value,
                         bool> = false)>
    void forEachHandle(Func&& f) const
    {
        for (uint32_t i = 0; i < kBuckets; ++i)
        {
            // We are done once we encounter a null pointer
            HandleData* data = getDbIndex(i, std::memory_order_acquire);
            if (!data)
                break;
            uint32_t const bucketSize = 1 << i;
            for (uint32_t j = 0; j != bucketSize; ++j)
            {
                Metadata meta = data[j].metadata.load(std::memory_order_acquire);
                if (meta.refCount != 0)
                {
                    // Valid handle
                    HandleUnion hu((bucketSize - 1) + j, meta.lifecycle);
                    // Call the functor
                    f(hu.handle, std::addressof(data[j].val));
                }
            }
        }
    }
    template <class Func CARB_NO_DOC(
        ,
        std::enable_if_t<std::is_same<bool, decltype(std::declval<Func>()(std::declval<Handle>(), std::declval<Mapped*>()))>::value,
                         bool> = true)>
    bool forEachHandle(Func&& f) const
    {
        for (uint32_t i = 0; i < kBuckets; ++i)
        {
            // We are done once we encounter a null pointer
            HandleData* data = getDbIndex(i, std::memory_order_acquire);
            if (!data)
                break;
            uint32_t const bucketSize = 1 << i;
            for (uint32_t j = 0; j != bucketSize; ++j)
            {
                Metadata meta = data[j].metadata.load(std::memory_order_acquire);
                if (meta.refCount != 0)
                {
                    // Valid handle
                    HandleUnion hu((bucketSize - 1) + j, meta.lifecycle);
                    // Call the functor
                    if (!f(hu.handle, std::addressof(data[j].val)))
                        return false;
                }
            }
        }
        return true;
    }
    size_t clear();
    void awaitFinalRelease(Handle h) const;
private:
    CARB_VIZ static constexpr uint32_t kBuckets = sizeof(uint32_t) * 8 - 1;
    static constexpr uint32_t kMaxSize = (1U << kBuckets) - 1;
    // NOTE: Making *any* change to how rollover works should enable HANDLE_DATABASE_EXTENDED_TESTS in TestExtras.cpp
    static constexpr uint32_t kRolloverFlag = 0x80000000u;
    static constexpr uint32_t kLifecycleMask = 0x7fffffffu;
    // A struct for mapping type Handle into its basic components of index and lifecycle
    struct HandleUnion
    {
        union
        {
            Handle handle;
            struct
            {
                uint32_t index;
                uint32_t lifecycle;
            };
        };
        constexpr HandleUnion(Handle h) noexcept : handle(h)
        {
        }
        constexpr HandleUnion(uint32_t idx, uint32_t life) noexcept : index(idx), lifecycle(life & kLifecycleMask)
        {
        }
    };
    // A struct for tracking the refcount and lifecycle.
    struct alignas(uint64_t) Metadata
    {
        uint32_t refCount{ 0 };
        CARB_VIZ uint32_t lifecycle{ 0 };
        constexpr Metadata() noexcept = default;
    };
    struct HandleData
    {
        // This union will either be the free link and index, or the constructed mapped type
        union
        {
            struct
            {
                container::LocklessQueueLink<HandleData> link;
                uint32_t index;
            };
            // This `val` is only constructed when the HandleData is in use
            Mapped val;
        };
        // Metadata, but the union allows us to access the refCount atomically, separately.
        union
        {
            CARB_VIZ std::atomic<Metadata> metadata;
            std::atomic_uint32_t refCount; // Must share memory address with metadata.refCount
        };
        constexpr HandleData(uint32_t idx) noexcept : index(idx), metadata{}
        {
        }
        // Empty destructor, required because of the union with non-trivial types
        ~HandleData() noexcept
        {
        }
    };
    static_assert(alignof(HandleData) >= alignof(Mapped),
                  "Invalid assumption: HandleData alignment should meet or exceed Mapped alignment");
    static_assert(sizeof(Handle) == sizeof(uint64_t), "Handle should be 64-bit");
    using HandleDataAllocator = typename std::allocator_traits<Allocator>::template rebind_alloc<HandleData>;
    using AllocatorTraits = std::allocator_traits<HandleDataAllocator>;
    static void indexToBucketAndOffset(uint32_t index, uint32_t& bucket, uint32_t& offset);
    HandleData* expandDatabase();
    HandleData* getHandleData(uint32_t index) const;
    HandleData* getHandleData(const Mapped* mapped) const;
    void destroyMapped(HandleData* mapped, uint32_t index);
    HandleData* getDbIndex(size_t index, std::memory_order = std::memory_order_seq_cst) const;
    // m_database is fixed size and written atomically in expandDatabase(). It never contracts and therefore
    // does not require a mutex
    CARB_VIZ std::atomic<HandleData*> m_database[kBuckets] = {};
    struct Members : HandleDataAllocator
    {
        // The "tasking timing" test performs much better with m_free as a queue instead of a stack.
        container::LocklessQueue<HandleData, &HandleData::link> m_free;
    } m_emo;
    // An atomic used as a condition variable of sorts to notify about destruction
    carb::cpp::atomic_size_t m_destructNotifier{ 0 };
};
template <class Mapped, class Handle, class Allocator = std::allocator<Mapped>>
struct ConstHandleRef
{
public:
    using MappedType = const Mapped;
    using ReferenceType = const Mapped&;
    using PointerType = const Mapped*;
    using HandleType = Handle;
    using AllocatorType = Allocator;
    using Database = HandleDatabase<Mapped, Handle, Allocator>;
    using element_type = MappedType;
    using handle_type = HandleType;
    ConstHandleRef() = default;
    ConstHandleRef(Database& database, Handle handle) noexcept : m_mapped(database.tryAddRef(handle))
    {
        if (m_mapped)
        {
            m_database = std::addressof(database);
            m_handle = handle;
        }
    }
    ~ConstHandleRef() noexcept
    {
        reset();
    }
    ConstHandleRef(ConstHandleRef&& other) noexcept
    {
        swap(other);
    }
    ConstHandleRef& operator=(ConstHandleRef&& other) noexcept
    {
        swap(other);
        return *this;
    }
    // Specifically non-Copyable in order to reduce implicit usage.
    // Use the explicit `clone()` function if a copy is desired
    CARB_PREVENT_COPY(ConstHandleRef);
    ConstHandleRef clone() const noexcept
    {
        if (m_mapped)
            m_database->addRef(m_mapped);
        return ConstHandleRef{ m_database, m_handle, m_mapped };
    }
    PointerType operator->() noexcept
    {
        return get();
    }
    PointerType operator->() const noexcept
    {
        return get();
    }
    ReferenceType operator*() noexcept
    {
        CARB_ASSERT(get());
        return *get();
    }
    ReferenceType operator*() const noexcept
    {
        CARB_ASSERT(get());
        return *get();
    }
    PointerType get() noexcept
    {
        return this->m_mapped;
    }
    PointerType get() const noexcept
    {
        return this->m_mapped;
    }
    Handle handle() const noexcept
    {
        return this->m_handle;
    }
    explicit operator bool() const noexcept
    {
        return get() != nullptr;
    }
    void reset()
    {
        if (auto mapped = std::exchange(m_mapped, nullptr))
            m_database->release(mapped);
        m_database = nullptr;
        m_handle = HandleType{};
    }
    void swap(ConstHandleRef& rhs)
    {
        std::swap(m_database, rhs.m_database);
        std::swap(m_handle, rhs.m_handle);
        std::swap(m_mapped, rhs.m_mapped);
    }
protected:
    Database* m_database{};
    Handle m_handle{};
    Mapped* m_mapped{};
    ConstHandleRef(Database* db, Handle h, Mapped* m) noexcept : m_database(db), m_handle(h), m_mapped(m)
    {
    }
};
template <class Mapped, class Handle, class Allocator = std::allocator<Mapped>>
struct HandleRef : public ConstHandleRef<Mapped, Handle, Allocator>
{
    using BaseType = ConstHandleRef<Mapped, Handle, Allocator>;
public:
    using MappedType = Mapped;
    using ReferenceType = Mapped&;
    using PointerType = Mapped*;
    using HandleType = Handle;
    using AllocatorType = Allocator;
    using Database = HandleDatabase<Mapped, Handle, Allocator>;
    using element_type = MappedType;
    using handle_type = HandleType;
    HandleRef() = default;
    HandleRef(Database& database, Handle handle) noexcept : BaseType(database, handle)
    {
    }
    HandleRef clone() const noexcept
    {
        if (this->m_mapped)
            this->m_database->addRef(this->m_mapped);
        return HandleRef{ this->m_database, this->m_handle, this->m_mapped };
    }
    PointerType operator->() noexcept
    {
        return get();
    }
    PointerType operator->() const noexcept
    {
        return get();
    }
    ReferenceType operator*() noexcept
    {
        CARB_ASSERT(get());
        return *get();
    }
    ReferenceType operator*() const noexcept
    {
        CARB_ASSERT(get());
        return *get();
    }
    PointerType get() noexcept
    {
        return this->m_mapped;
    }
    PointerType get() const noexcept
    {
        return this->m_mapped;
    }
    void swap(HandleRef& rhs)
    {
        BaseType::swap(rhs);
    }
private:
    HandleRef(Database* db, Handle h, Mapped* m) noexcept : BaseType(db, h, m)
    {
    }
};
template <class Mapped, class Handle, class Allocator = std::allocator<Mapped>>
using ScopedHandleRef = HandleRef<Mapped, Handle, Allocator>;
template <class Mapped, class Handle, class Allocator>
inline constexpr HandleDatabase<Mapped, Handle, Allocator>::HandleDatabase() noexcept
{
}
template <class Mapped, class Handle, class Allocator>
inline HandleDatabase<Mapped, Handle, Allocator>::~HandleDatabase()
{
    // Make sure everything is removed from the free list before we destroy it
    m_emo.m_free.popAll();
    size_t leaks = 0;
    for (size_t i = 0; i < CARB_COUNTOF(m_database); i++)
    {
        HandleData* handleData = m_database[i].exchange(nullptr, std::memory_order_relaxed);
        if (!handleData)
            break;
        // Go through each entry and destruct any outstanding ones with a non-zero refcount
        size_t const kBucketSize = size_t(1) << i;
        for (size_t j = 0; j != kBucketSize; ++j)
        {
            Metadata meta = handleData[j].metadata.load(std::memory_order_relaxed);
            if (meta.refCount != 0)
            {
                handleData[j].val.~Mapped();
                ++leaks;
            }
            AllocatorTraits::destroy(m_emo, handleData + j);
        }
        AllocatorTraits::deallocate(m_emo, handleData, kBucketSize);
    }
    if (leaks != 0)
    {
        // We don't notify m_destructNotifier here because destructors are inherently non-threadsafe. No other threads
        // should be in any HandleDatabase functions with *this, and if they are then UB will result.
        // If we *did* notify here, then a thread in awaitFinalRelease() will attempt unsafe memory accesses, therefore
        // this is specifically disallowed.
        CARB_LOG_WARN("%s: had %zu outstanding handle(s) at shutdown", CARB_PRETTY_FUNCTION, leaks);
    }
}
template <class Mapped, class Handle, class Allocator>
inline auto HandleDatabase<Mapped, Handle, Allocator>::getHandleData(uint32_t index) const -> HandleData*
{
    uint32_t bucketIndex, offsetInBucket;
    indexToBucketAndOffset(index, bucketIndex, offsetInBucket);
    if (bucketIndex >= CARB_COUNTOF(m_database))
    {
        return nullptr;
    }
    auto bucket = getDbIndex(bucketIndex, std::memory_order_acquire);
    return bucket ? bucket + offsetInBucket : nullptr;
}
template <class Mapped, class Handle, class Allocator>
inline auto HandleDatabase<Mapped, Handle, Allocator>::getHandleData(const Mapped* mapped) const -> HandleData*
{
    // HandleData must have val as the first member. This would prevent us from casting
    // mapped to HandleData without using ugly pointer math otherwise.
    // static assert using offsetof() is not possible due to HandleData having a non-standard-layout type.
    auto pHandleData = reinterpret_cast<HandleData*>(const_cast<Mapped*>(mapped));
    CARB_ASSERT(reinterpret_cast<intptr_t>(&pHandleData->val) - reinterpret_cast<intptr_t>(pHandleData) == 0);
    return pHandleData;
}
template <class Mapped, class Handle, class Allocator>
inline void HandleDatabase<Mapped, Handle, Allocator>::destroyMapped(HandleData* hd, uint32_t index)
{
    // Destroy the mapped type and add it to the free list.
    hd->val.~Mapped();
    hd->index = index;
    m_emo.m_free.push(hd);
    // Just need atomicity, so used relaxed
    m_destructNotifier.fetch_add(1, std::memory_order_relaxed);
    m_destructNotifier.notify_all();
}
template <class Mapped, class Handle, class Allocator>
inline auto HandleDatabase<Mapped, Handle, Allocator>::getDbIndex(size_t index, std::memory_order order) const
    -> HandleData*
{
    static HandleData* const kLocked = reinterpret_cast<HandleData*>(size_t(-1));
    CARB_ASSERT(index < kBuckets);
    // Spin briefly if the entry is locked
    HandleData* bucket = m_database[index].load(order);
    while (CARB_UNLIKELY(bucket == kLocked))
    {
        CARB_HARDWARE_PAUSE();
        bucket = m_database[index].load(order);
    }
    return bucket;
}
template <class Mapped, class Handle, class Allocator>
inline bool HandleDatabase<Mapped, Handle, Allocator>::handleWasValid(Handle handle) const
{
    HandleUnion hu(handle);
    HandleData* pHandleData = getHandleData(hu.index);
    if (pHandleData == nullptr)
    {
        return false;
    }
    Metadata meta = pHandleData->metadata.load(std::memory_order_acquire);
    // The zero lifecycle count isn't used
    if (hu.lifecycle == 0 || meta.lifecycle == 0)
    {
        return false;
    }
    // The kRolloverFlag value is always unset in hu, but possibly present in meta.lifecycle. However, this is still a
    // valid comparison because if the kRolloverFlag is set in meta.lifecycle, it means that every possible value has
    // been a valid handle at one point and the expression will always result in `true`.
    return hu.lifecycle <= meta.lifecycle;
}
template <class Mapped, class Handle, class Allocator>
inline bool HandleDatabase<Mapped, Handle, Allocator>::handleIsValid(Handle handle) const
{
    HandleUnion hu(handle);
    HandleData* pHandleData = getHandleData(hu.index);
    if (pHandleData == nullptr)
    {
        return false;
    }
    // Fence-atomic synchronization since we're not doing any atomic modification, just loading. This fence allows use
    // of the relaxed load though.
    carb::this_thread::atomic_fence_seq_cst();
    Metadata meta = pHandleData->metadata.load(std::memory_order_relaxed);
    // We return true if there is a non-zero refcount on the handle data and the lifecycle matches current.
    return (meta.refCount != 0 && (meta.lifecycle & kLifecycleMask) == hu.lifecycle);
}
template <class Mapped, class Handle, class Allocator>
template <class... Args>
inline std::pair<Handle, Mapped*> HandleDatabase<Mapped, Handle, Allocator>::createHandle(Args&&... args)
{
    HandleData* handleData = m_emo.m_free.pop();
    if (!handleData)
        handleData = expandDatabase();
    CARB_ASSERT(handleData);
    Metadata meta = handleData->metadata.load(std::memory_order_acquire);
    CARB_ASSERT(((void*)std::addressof(handleData->metadata) == (void*)std::addressof(handleData->refCount)) &&
                offsetof(Metadata, refCount) == 0);
    meta.refCount = 1;
    // Don't allow the 0 lifecycle.
    meta.lifecycle++;
    if ((meta.lifecycle & kLifecycleMask) == 0)
    {
        meta.lifecycle = 1 | kRolloverFlag;
    }
    uint32_t indexToAllocateFrom = handleData->index;
    Mapped* p = new (std::addressof(handleData->val)) Mapped(std::forward<Args>(args)...);
    handleData->metadata.store(meta, std::memory_order_release);
    HandleUnion hu(indexToAllocateFrom, meta.lifecycle);
    return std::make_pair(hu.handle, p);
}
template <class Mapped, class Handle, class Allocator>
inline Mapped* HandleDatabase<Mapped, Handle, Allocator>::tryAddRef(Handle handle)
{
    HandleUnion hu(handle);
    HandleData* pHandleData = getHandleData(hu.index);
    if (pHandleData == nullptr)
    {
        return nullptr;
    }
    Metadata meta = pHandleData->metadata.load(std::memory_order_acquire);
    for (;;)
    {
        if (meta.refCount == 0 || (meta.lifecycle & kLifecycleMask) != hu.lifecycle)
        {
            return nullptr;
        }
        Metadata desired = meta;
        desired.refCount++;
        if (pHandleData->metadata.compare_exchange_weak(
                meta, desired, std::memory_order_release, std::memory_order_relaxed))
        {
            return std::addressof(pHandleData->val);
        }
    }
}
template <class Mapped, class Handle, class Allocator>
inline void HandleDatabase<Mapped, Handle, Allocator>::addRef(Handle handle)
{
    Mapped* mapped = tryAddRef(handle);
    CARB_FATAL_UNLESS(mapped != nullptr, "Attempt to addRef released handle");
}
template <class Mapped, class Handle, class Allocator>
inline void HandleDatabase<Mapped, Handle, Allocator>::addRef(Mapped* mapped)
{
    HandleData* pHandleData = getHandleData(mapped);
    // We're working with the Mapped type here, so if it's not valid we're in UB.
    uint32_t prev = pHandleData->refCount.fetch_add(1, std::memory_order_relaxed);
    CARB_CHECK((prev + 1) > 1); // no resurrection and no rollover
}
template <class Mapped, class Handle, class Allocator>
inline bool HandleDatabase<Mapped, Handle, Allocator>::release(Handle handle)
{
    HandleUnion hu(handle);
    HandleData* pHandleData = getHandleData(hu.index);
    CARB_ASSERT(pHandleData);
    if (pHandleData == nullptr)
    {
        return false;
    }
    Metadata meta = pHandleData->metadata.load(std::memory_order_acquire);
    for (;;)
    {
        if (meta.refCount == 0 || (meta.lifecycle & kLifecycleMask) != hu.lifecycle)
        {
            CARB_ASSERT(0);
            return false;
        }
        Metadata desired = meta;
        bool released = --desired.refCount == 0;
        if (CARB_LIKELY(pHandleData->metadata.compare_exchange_weak(
                meta, desired, std::memory_order_release, std::memory_order_relaxed)))
        {
            if (released)
            {
                std::atomic_thread_fence(std::memory_order_acquire);
                destroyMapped(pHandleData, hu.index);
            }
            return released;
        }
    }
}
template <class Mapped, class Handle, class Allocator>
inline bool HandleDatabase<Mapped, Handle, Allocator>::release(Mapped* mapped)
{
    HandleData* pHandleData = getHandleData(mapped);
    // We're working with the Mapped type here, so if it's not valid we're in UB.
    uint32_t prev = pHandleData->refCount.fetch_sub(1, std::memory_order_release);
    CARB_CHECK(prev >= 1); // No underflow
    if (prev == 1)
    {
        // Released
        std::atomic_thread_fence(std::memory_order_acquire);
        destroyMapped(pHandleData, HandleUnion(getHandleFromValue(mapped)).index);
        return true;
    }
    return false;
}
template <class Mapped, class Handle, class Allocator>
inline bool HandleDatabase<Mapped, Handle, Allocator>::releaseIfLastRef(Handle handle)
{
    HandleUnion hu(handle);
    HandleData* pHandleData = getHandleData(hu.index);
    CARB_ASSERT(pHandleData);
    if (pHandleData == nullptr)
    {
        return false;
    }
    Metadata meta = pHandleData->metadata.load(std::memory_order_acquire);
    for (;;)
    {
        if (meta.refCount == 0 || (meta.lifecycle & kLifecycleMask) != hu.lifecycle)
        {
            CARB_ASSERT(0);
            return false;
        }
        if (meta.refCount > 1)
        {
            return false;
        }
        Metadata desired = meta;
        desired.refCount = 0;
        if (CARB_LIKELY(pHandleData->metadata.compare_exchange_weak(
                meta, desired, std::memory_order_release, std::memory_order_relaxed)))
        {
            std::atomic_thread_fence(std::memory_order_acquire);
            destroyMapped(pHandleData, hu.index);
            return true;
        }
    }
}
template <class Mapped, class Handle, class Allocator>
inline bool HandleDatabase<Mapped, Handle, Allocator>::releaseIfLastRef(Mapped* mapped)
{
    HandleData* pHandleData = getHandleData(mapped);
    // We're working with the Mapped type here, so if it's not valid we're in UB.
    uint32_t refCount = pHandleData->refCount.load(std::memory_order_acquire);
    for (;;)
    {
        CARB_CHECK(refCount != 0);
        if (refCount > 1)
        {
            return false;
        }
        if (CARB_LIKELY(pHandleData->refCount.compare_exchange_weak(
                refCount, 0, std::memory_order_release, std::memory_order_relaxed)))
        {
            std::atomic_thread_fence(std::memory_order_acquire);
            destroyMapped(pHandleData, HandleUnion(getHandleFromValue(mapped)).index);
            return true;
        }
    }
}
template <class Mapped, class Handle, class Allocator>
inline Mapped* HandleDatabase<Mapped, Handle, Allocator>::getValueFromHandle(Handle handle) const
{
    HandleUnion hu(handle);
    HandleData* pHandleData = getHandleData(hu.index);
    if (pHandleData == nullptr)
    {
        return nullptr;
    }
    Metadata meta = pHandleData->metadata.load(std::memory_order_acquire);
    return (meta.refCount != 0 && (meta.lifecycle & kLifecycleMask) == hu.lifecycle) ? std::addressof(pHandleData->val) :
                                                                                       nullptr;
}
template <class Mapped, class Handle, class Allocator>
inline Handle HandleDatabase<Mapped, Handle, Allocator>::getHandleFromValue(const Mapped* mapped) const
{
    // Resolve mapped to the handle data
    // We're working with the Mapped type here, so if it's not valid we're in UB.
    HandleData* pHandleData = getHandleData(mapped);
    // Start setting up the handle, but we don't know the index yet.
    uint32_t lifecycle = pHandleData->metadata.load(std::memory_order_acquire).lifecycle;
    // Find the index by searching all of the buckets.
    for (uint32_t i = 0; i < kBuckets; ++i)
    {
        HandleData* p = getDbIndex(i, std::memory_order_acquire);
        if (!p)
            break;
        size_t offset = size_t(pHandleData - p);
        if (offset < (size_t(1) << i))
        {
            // Found the index!
            uint32_t index = (uint32_t(1) << i) - 1 + uint32_t(offset);
            return HandleUnion(index, lifecycle).handle;
        }
    }
    // Given mapped doesn't exist in this HandleDatabase
    CARB_CHECK(0);
    return Handle{};
}
template <class Mapped, class Handle, class Allocator>
inline void HandleDatabase<Mapped, Handle, Allocator>::indexToBucketAndOffset(uint32_t index,
                                                                              uint32_t& bucket,
                                                                              uint32_t& offset)
{
    // Bucket id
    bucket = 31 - math::numLeadingZeroBits(index + 1);
    offset = index + 1 - (1 << CARB_MIN(31u, bucket));
}
template <class Mapped, class Handle, class Allocator>
inline auto HandleDatabase<Mapped, Handle, Allocator>::expandDatabase() -> HandleData*
{
    static HandleData* const kLocked = reinterpret_cast<HandleData*>(size_t(-1));
    // Scan (right now O(n), but this is rare and n is small) for the first empty database.
    size_t bucketToAllocateFrom;
    for (bucketToAllocateFrom = 0; bucketToAllocateFrom != CARB_COUNTOF(m_database); bucketToAllocateFrom++)
    {
        HandleData* mem = m_database[bucketToAllocateFrom].load(std::memory_order_acquire);
        for (;;)
        {
            while (mem == kLocked)
            {
                // Another thread is trying to allocate this. Try to pull from the free list until it stabilizes.
                mem = m_emo.m_free.pop();
                if (mem)
                    return mem;
                mem = m_database[bucketToAllocateFrom].load(std::memory_order_acquire);
            }
            if (!mem)
            {
                // Try to lock it
                if (m_database[bucketToAllocateFrom].compare_exchange_strong(
                        mem, kLocked, std::memory_order_release, std::memory_order_relaxed))
                {
                    // Successfully locked
                    break;
                }
                // Continue if another thread has locked it
                if (mem == kLocked)
                    continue;
                // Failed to lock, but it's not locked anymore, so see if there's a free entry now
                HandleData* hd = m_emo.m_free.pop();
                if (hd)
                    return hd;
            }
            CARB_ASSERT(mem != kLocked);
            break;
        }
        if (!mem)
            break;
    }
    CARB_FATAL_UNLESS(bucketToAllocateFrom < CARB_COUNTOF(m_database), "Out of handles!");
    uint32_t const allocateCount = 1 << bucketToAllocateFrom;
    uint32_t const offset = allocateCount - 1;
    HandleData* handleData = AllocatorTraits::allocate(m_emo, allocateCount);
    // Set the indexes
    for (uint32_t i = 0; i < allocateCount; ++i)
    {
        AllocatorTraits::construct(m_emo, handleData + i, offset + i);
    }
    // Add entries (after the first) to the free list
    m_emo.m_free.push(handleData + 1, handleData + allocateCount);
    // Overwrite the lock with the actual pointer now
    HandleData* expect = m_database[bucketToAllocateFrom].exchange(handleData, std::memory_order_release);
    CARB_ASSERT(expect == kLocked);
    CARB_UNUSED(expect);
    // Return the first entry that we reserved for the caller
    AllocatorTraits::construct(m_emo, handleData, offset);
    return handleData;
}
template <class Mapped, class Handle, class Allocator>
inline size_t HandleDatabase<Mapped, Handle, Allocator>::clear()
{
    size_t count = 0;
    for (uint32_t i = 0; i != kBuckets; ++i)
    {
        HandleData* data = getDbIndex(i, std::memory_order_acquire);
        if (!data)
            break;
        uint32_t const bucketSize = 1u << i;
        for (uint32_t j = 0; j != bucketSize; ++j)
        {
            if (data[j].refCount.exchange(0, std::memory_order_release) != 0)
            {
                // Successfully set the refcount to 0. Destroy and add to the free list
                std::atomic_thread_fence(std::memory_order_acquire);
                ++count;
                data[j].val.~Mapped();
                data[j].index = bucketSize - 1 + j;
                m_emo.m_free.push(std::addressof(data[j]));
            }
        }
    }
    // Notify once at the end
    if (count)
    {
        // Just need atomicity, so use relaxed semantics.
        m_destructNotifier.fetch_add(1, std::memory_order_relaxed);
        m_destructNotifier.notify_all();
    }
    return count;
}
template <class Mapped, class Handle, class Allocator>
inline void HandleDatabase<Mapped, Handle, Allocator>::awaitFinalRelease(Handle h) const
{
    // handleIsValid() performs seq-cst ordering, so we can use relaxed semantics here.
    auto val = m_destructNotifier.load(std::memory_order_relaxed);
    while (handleIsValid(h))
    {
        m_destructNotifier.wait(val, std::memory_order_relaxed);
        val = m_destructNotifier.load(std::memory_order_relaxed);
    }
}
template <class Mapped, class Handle, class Allocator>
inline auto HandleDatabase<Mapped, Handle, Allocator>::makeScopedRef(Handle handle) -> HandleRefType
{
    return HandleRef<Mapped, Handle, Allocator>(*this, handle);
}
template <class Mapped, class Handle, class Allocator>
inline auto HandleDatabase<Mapped, Handle, Allocator>::makeScopedRef(Handle handle) const -> ConstHandleRefType
{
    return ConstHandleRef<Mapped, Handle, Allocator>(const_cast<HandleDatabase&>(*this), handle);
}
// Global operators.
template <class Mapped, class Handle, class Allocator>
inline bool operator==(const HandleRef<Mapped, Handle, Allocator>& ref, std::nullptr_t)
{
    return ref.get() == nullptr;
}
template <class Mapped, class Handle, class Allocator>
inline bool operator==(std::nullptr_t, const HandleRef<Mapped, Handle, Allocator>& ref)
{
    return ref.get() == nullptr;
}
template <class Mapped, class Handle, class Allocator>
inline bool operator!=(const HandleRef<Mapped, Handle, Allocator>& ref, std::nullptr_t)
{
    return ref.get() != nullptr;
}
template <class Mapped, class Handle, class Allocator>
inline bool operator!=(std::nullptr_t, const HandleRef<Mapped, Handle, Allocator>& ref)
{
    return ref.get() != nullptr;
}
} // namespace extras
} // namespace carb
CARB_IGNOREWARNING_MSC_POP