summaryrefslogtreecommitdiff
path: root/asl/handle_pool/dense_handle_pool.hpp
blob: 34cd04f277848d245c800e01ef18db271de4d445 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause

#pragma once

#include "asl/handle_pool/index_pool.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/containers/chunked_buffer.hpp"


namespace asl
{

// @Todo If we want the allocator to be non-copyable, we could
// introduce a reference allocator type that is copyable, and store
// the "main" allocator in the pool.

template<
    is_object T,
    int kIndexBits,
    int kGenBits,
    typename UserType = empty,
    int kUserBits = 0,
    isize_t kChunkSize = 32,
    allocator Allocator = DefaultAllocator>
requires moveable<T> && copyable<Allocator>
class DenseHandlePool
{
    using ThisIndexPool = IndexPool<kIndexBits, kGenBits, UserType, kUserBits, isize_t, Allocator>;

    struct Slot
    {
        ThisIndexPool::handle h;
        T obj;

        template<typename... Args>
        explicit Slot(ThisIndexPool::handle h, Args&&... args)
            : h{h}
            , obj(std::forward<Args>(args)...)
        {}
    };

    using Buffer = chunked_buffer<Slot, kChunkSize, Allocator>;

    ThisIndexPool m_index_pool{};
    Buffer        m_buffer{};

    using config = ThisIndexPool::handle::config;

    template<typename... Args>
    isize_t push(Args&&... args)
        requires constructible_from<T, Args&&...>
    {
        m_buffer.push(typename ThisIndexPool::handle{}, std::forward<Args>(args)...);
        return m_buffer.size() - 1;
    }

public:
    using handle = ThisIndexPool::handle;

    DenseHandlePool() requires default_constructible<Allocator> = default;

    explicit DenseHandlePool(const Allocator& allocator)
        : m_index_pool(allocator)
        , m_buffer(allocator)
    {}

    ASL_DELETE_COPY(DenseHandlePool);
    ASL_DEFAULT_MOVE(DenseHandlePool);
    ~DenseHandlePool() = default;

    [[nodiscard]] bool is_full() const
    {
        return m_index_pool.is_full();
    }

    bool is_valid(handle h) const
    {
        return m_index_pool.is_valid(h);
    }

    template<typename... Args>
    option<handle> acquire(config::UserType user, Args&&... args)
        requires config::kHasUser && constructible_from<T, Args&&...>
    {
        if (is_full()) { return nullopt; }
        const isize_t obj_index = push(std::forward<Args>(args)...);
        const auto handle = m_index_pool.acquire_ensure(user, obj_index);
        m_buffer[obj_index].h = handle;
        return handle;
    }

    template<typename... Args>
    option<handle> acquire(Args&&... args)
        requires (!config::kHasUser) && constructible_from<T, Args&&...>
    {
        if (is_full()) { return nullopt; }
        const isize_t obj_index = push(std::forward<Args>(args)...);
        const auto handle = m_index_pool.acquire_ensure(obj_index);
        m_buffer[obj_index].h = handle;
        return handle;
    }

    template<typename... Args>
    handle acquire_ensure(config::UserType user, Args&&... args)
        requires config::kHasUser && constructible_from<T, Args&&...>
    {
        ASL_ASSERT_RELEASE(!is_full());
        const isize_t obj_index = push(std::forward<Args>(args)...);
        const auto handle = m_index_pool.acquire_ensure(user, obj_index);
        m_buffer[obj_index].h = handle;
        return handle;
    }

    template<typename... Args>
    handle acquire_ensure(Args&&... args)
        requires (!config::kHasUser) && constructible_from<T, Args&&...>
    {
        ASL_ASSERT_RELEASE(!is_full());
        const isize_t obj_index = push(std::forward<Args>(args)...);
        const auto handle = m_index_pool.acquire_ensure(obj_index);
        m_buffer[obj_index].h = handle;
        return handle;
    }

    void release(handle to_release_handle)
    {
        if (!is_valid(to_release_handle)) { return; }

        const auto to_release_index = *m_index_pool.get_payload(to_release_handle);
        if (to_release_index < m_buffer.size() - 1)
        {
            const auto to_swap_index = m_buffer.size() - 1;
            const auto to_swap_handle = m_buffer[to_swap_index].h;

            m_buffer[to_release_index] = std::move(m_buffer[to_swap_index]);
            m_index_pool.exchange_payload(to_swap_handle, to_release_index);
        }

        m_buffer.pop();
        m_index_pool.release(to_release_handle);
    }

    auto get(this auto&& self, handle h)
        -> copy_const_t<un_ref_t<decltype(self)>, T>*
    {
        if (!self.is_valid(h)) { return nullptr; }
        const auto index = *self.m_index_pool.get_payload(h);
        return &self.m_buffer[index].obj;
    }

    auto get_ensure(this auto&& self, handle h)
        -> copy_cref_t<decltype(self), T>
    {
        ASL_ASSERT_RELEASE(self.is_valid(h));
        const auto index = *self.m_index_pool.get_payload(h);
        return std::forward<decltype(self)>(self).m_buffer[index].obj;
    }
};

} // namespace asl