| 1 | /**************************************************************************/ |
| 2 | /* pooled_list.h */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #ifndef POOLED_LIST_H |
| 32 | #define POOLED_LIST_H |
| 33 | |
| 34 | // Simple template to provide a pool with O(1) allocate and free. |
| 35 | // The freelist could alternatively be a linked list placed within the unused elements |
| 36 | // to use less memory, however a separate freelist is probably more cache friendly. |
| 37 | |
| 38 | // NOTE : Take great care when using this with non POD types. The construction and destruction |
| 39 | // is done in the LocalVector, NOT as part of the pool. So requesting a new item does not guarantee |
| 40 | // a constructor is run, and free does not guarantee a destructor. |
| 41 | // You should generally handle clearing |
| 42 | // an item explicitly after a request, as it may contain 'leftovers'. |
| 43 | // This is by design for fastest use in the BVH. If you want a more general pool |
| 44 | // that does call constructors / destructors on request / free, this should probably be |
| 45 | // a separate template. |
| 46 | |
| 47 | // The zero_on_first_request feature is optional and is useful for e.g. pools of handles, |
| 48 | // which may use a ref count which we want to be initialized to zero the first time a handle is created, |
| 49 | // but left alone on subsequent allocations (as will typically be incremented). |
| 50 | |
| 51 | // Note that there is no function to compact the pool - this would |
| 52 | // invalidate any existing pool IDs held externally. |
| 53 | // Compaction can be done but would rely on a more complex method |
| 54 | // of preferentially giving out lower IDs in the freelist first. |
| 55 | |
| 56 | #include "core/templates/local_vector.h" |
| 57 | |
| 58 | template <class T, class U = uint32_t, bool force_trivial = false, bool zero_on_first_request = false> |
| 59 | class PooledList { |
| 60 | LocalVector<T, U, force_trivial> list; |
| 61 | LocalVector<U, U, true> freelist; |
| 62 | |
| 63 | // not all list members are necessarily used |
| 64 | U _used_size; |
| 65 | |
| 66 | public: |
| 67 | PooledList() { |
| 68 | _used_size = 0; |
| 69 | } |
| 70 | |
| 71 | // Use with care, in most cases you should make sure to |
| 72 | // free all elements first (i.e. _used_size would be zero), |
| 73 | // although it could also be used without this as an optimization |
| 74 | // in some cases. |
| 75 | void clear() { |
| 76 | list.clear(); |
| 77 | freelist.clear(); |
| 78 | _used_size = 0; |
| 79 | } |
| 80 | |
| 81 | uint64_t estimate_memory_use() const { |
| 82 | return ((uint64_t)list.size() * sizeof(T)) + ((uint64_t)freelist.size() * sizeof(U)); |
| 83 | } |
| 84 | |
| 85 | const T &operator[](U p_index) const { |
| 86 | return list[p_index]; |
| 87 | } |
| 88 | T &operator[](U p_index) { |
| 89 | return list[p_index]; |
| 90 | } |
| 91 | |
| 92 | // To be explicit in a pool there is a distinction |
| 93 | // between the number of elements that are currently |
| 94 | // in use, and the number of elements that have been reserved. |
| 95 | // Using size() would be vague. |
| 96 | U used_size() const { return _used_size; } |
| 97 | U reserved_size() const { return list.size(); } |
| 98 | |
| 99 | T *request(U &r_id) { |
| 100 | _used_size++; |
| 101 | |
| 102 | if (freelist.size()) { |
| 103 | // pop from freelist |
| 104 | int new_size = freelist.size() - 1; |
| 105 | r_id = freelist[new_size]; |
| 106 | freelist.resize(new_size); |
| 107 | |
| 108 | return &list[r_id]; |
| 109 | } |
| 110 | |
| 111 | r_id = list.size(); |
| 112 | list.resize(r_id + 1); |
| 113 | |
| 114 | static_assert((!zero_on_first_request) || (__is_pod(T)), "zero_on_first_request requires trivial type" ); |
| 115 | if constexpr (zero_on_first_request && __is_pod(T)) { |
| 116 | list[r_id] = {}; |
| 117 | } |
| 118 | |
| 119 | return &list[r_id]; |
| 120 | } |
| 121 | void free(const U &p_id) { |
| 122 | // should not be on free list already |
| 123 | ERR_FAIL_UNSIGNED_INDEX(p_id, list.size()); |
| 124 | freelist.push_back(p_id); |
| 125 | ERR_FAIL_COND_MSG(!_used_size, "_used_size has become out of sync, have you double freed an item?" ); |
| 126 | _used_size--; |
| 127 | } |
| 128 | }; |
| 129 | |
| 130 | // a pooled list which automatically keeps a list of the active members |
| 131 | template <class T, class U = uint32_t, bool force_trivial = false, bool zero_on_first_request = false> |
| 132 | class TrackedPooledList { |
| 133 | public: |
| 134 | U pool_used_size() const { return _pool.used_size(); } |
| 135 | U pool_reserved_size() const { return _pool.reserved_size(); } |
| 136 | U active_size() const { return _active_list.size(); } |
| 137 | |
| 138 | // use with care, see the earlier notes in the PooledList clear() |
| 139 | void clear() { |
| 140 | _pool.clear(); |
| 141 | _active_list.clear(); |
| 142 | _active_map.clear(); |
| 143 | } |
| 144 | |
| 145 | U get_active_id(U p_index) const { |
| 146 | return _active_list[p_index]; |
| 147 | } |
| 148 | |
| 149 | const T &get_active(U p_index) const { |
| 150 | return _pool[get_active_id(p_index)]; |
| 151 | } |
| 152 | |
| 153 | T &get_active(U p_index) { |
| 154 | return _pool[get_active_id(p_index)]; |
| 155 | } |
| 156 | |
| 157 | const T &operator[](U p_index) const { |
| 158 | return _pool[p_index]; |
| 159 | } |
| 160 | T &operator[](U p_index) { |
| 161 | return _pool[p_index]; |
| 162 | } |
| 163 | |
| 164 | T *request(U &r_id) { |
| 165 | T *item = _pool.request(r_id); |
| 166 | |
| 167 | // add to the active list |
| 168 | U active_list_id = _active_list.size(); |
| 169 | _active_list.push_back(r_id); |
| 170 | |
| 171 | // expand the active map (this should be in sync with the pool list |
| 172 | if (_pool.used_size() > _active_map.size()) { |
| 173 | _active_map.resize(_pool.used_size()); |
| 174 | } |
| 175 | |
| 176 | // store in the active map |
| 177 | _active_map[r_id] = active_list_id; |
| 178 | |
| 179 | return item; |
| 180 | } |
| 181 | |
| 182 | void free(const U &p_id) { |
| 183 | _pool.free(p_id); |
| 184 | |
| 185 | // remove from the active list. |
| 186 | U list_id = _active_map[p_id]; |
| 187 | |
| 188 | // zero the _active map to detect bugs (only in debug?) |
| 189 | _active_map[p_id] = -1; |
| 190 | |
| 191 | _active_list.remove_unordered(list_id); |
| 192 | |
| 193 | // keep the replacement in sync with the correct list Id |
| 194 | if (list_id < _active_list.size()) { |
| 195 | // which pool id has been replaced in the active list |
| 196 | U replacement_id = _active_list[list_id]; |
| 197 | |
| 198 | // keep that replacements map up to date with the new position |
| 199 | _active_map[replacement_id] = list_id; |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | const LocalVector<U, U> &get_active_list() const { return _active_list; } |
| 204 | |
| 205 | private: |
| 206 | PooledList<T, U, force_trivial, zero_on_first_request> _pool; |
| 207 | LocalVector<U, U> _active_map; |
| 208 | LocalVector<U, U> _active_list; |
| 209 | }; |
| 210 | |
| 211 | #endif // POOLED_LIST_H |
| 212 | |