IceShard 1
A personal game engine project, with development focused on 2D/2.5D games.
Loading...
Searching...
No Matches
hashmap.hxx
Go to the documentation of this file.
1
3
4#pragma once
8
9namespace ice
10{
11
22 template<typename Type, ice::ContainerLogic Logic = ice::Constant_DefaultContainerLogic<Type>>
23 struct HashMap
26 {
27 static constexpr ContainerLogic OperationLogic = Logic;
28 static_assert(
30 "Collection element type is not allowed with 'Trivial' logic!"
31 );
32
33 struct ConstIterator;
34
36 using ValueType = Type;
37 using ConstContainerValueType = Type const;
40
46
50
54
55 inline explicit HashMap(ice::Allocator& alloc) noexcept;
56 inline ~HashMap() noexcept;
57
58 inline HashMap(HashMap&& other) noexcept;
59 inline HashMap(HashMap const& other) noexcept
60 requires std::copy_constructible<Type>;
61
62 inline auto operator=(HashMap&& other) noexcept -> HashMap&;
63 inline auto operator=(HashMap const& other) noexcept -> HashMap&
64 requires std::copy_constructible<Type>;
65
66 // API Requirements Of: Associative(Resizable)Container
67 constexpr auto size() const noexcept -> SizeType { return { _count, sizeof(ValueType) }; }
68 template<typename Self>
69 constexpr auto find(this Self&& self, KeyType key) noexcept -> ice::container::ValuePtr<Self>;
70 template<typename Self>
71 constexpr bool remove(this Self& self, KeyType key) noexcept;
72
73 template<typename Self, typename InValueType = Type>
74 constexpr auto store(
75 this Self& self, KeyType key, InValueType&& in_value
77
78 // Take some of the extended methods from the container mixin.
79 using AssociativeResizableContainer::remove;
80
81 // Additional functionality
82 template<typename Self>
83 constexpr auto values(this Self&& self) noexcept -> ice::container::SpanType<Self>;
84
85 // API Requirements Of: ResizableContainer
86 constexpr auto capacity() const noexcept -> SizeType { return { _capacity, sizeof(ValueType) }; }
87 constexpr void set_capacity(ice::ncount new_capacity) noexcept;
88 constexpr void clear() noexcept;
89
90 // API Requirements Of: IterableContainer
91 template<typename Self>
92 constexpr auto begin(this Self&& self) noexcept -> ice::container::Iterator<Self>;
93 template<typename Self>
94 constexpr auto end(this Self&& self) noexcept -> ice::container::Iterator<Self>;
95
96 // API Requriements Of: Memory and Data
97 constexpr auto memory_view() noexcept -> ice::Memory;
98 constexpr auto entries_memory_view() noexcept -> ice::Memory;
99
100 // Data Helpers
101 constexpr auto values_data_view() const noexcept -> ice::Data;
102 constexpr auto entries_data_view() const noexcept -> ice::Data;
103 };
104
105 template<typename Type, ice::ContainerLogic Logic>
106 struct HashMap<Type, Logic>::ConstIterator
107 {
110
111 constexpr ConstIterator(std::nullptr_t) noexcept
112 : _entry{ nullptr }
113 , _value{ nullptr }
114 { }
115
116 constexpr ConstIterator(EntryType const* entry, Type const* value) noexcept
117 : _entry{ entry }
118 , _value{ value }
119 { }
120
121 constexpr auto key() const noexcept -> ice::u64 const& { return _entry->key; }
122 constexpr auto value() const noexcept -> Type const& { return *_value; }
123
124 constexpr auto operator==(ConstIterator const& other) const noexcept { return _entry == other._entry; }
125 constexpr auto operator!=(ConstIterator const& other) const noexcept { return !(*this == other); }
126
127 constexpr void operator++() noexcept { _entry += 1; _value += 1; }
128 constexpr auto operator*() const noexcept -> Type const& { return value(); }
129 };
130
131 template<typename Type, ice::ContainerLogic Logic>
133 : _allocator{ &alloc }
134 , _capacity{ 0 }
135 , _count{ 0 }
136 , _hashes{ nullptr }
137 , _entries { nullptr }
138 , _data{ nullptr }
139 { }
140
141 template<typename Type, ice::ContainerLogic Logic>
143 {
144 clear();
145 set_capacity(0);
146 }
147
148 template<typename Type, ice::ContainerLogic Logic>
149 inline HashMap<Type, Logic>::HashMap(HashMap&& other) noexcept
150 : _allocator{ other._allocator }
151 , _capacity{ ice::exchange(other._capacity, 0) }
152 , _count{ ice::exchange(other._count, 0) }
153 , _hashes{ ice::exchange(other._hashes, nullptr) }
154 , _entries{ ice::exchange(other._entries, nullptr) }
155 , _data{ ice::exchange(other._data, nullptr) }
156 { }
157
158 template<typename Type, ice::ContainerLogic Logic>
159 inline HashMap<Type, Logic>::HashMap(HashMap const& other) noexcept
160 requires std::copy_constructible<Type>
161 : _allocator{ other._allocator }
162 , _capacity{ 0 }
163 , _count{ 0 }
164 , _hashes{ 0 }
165 , _entries{ 0 }
166 , _data{ 0 }
167 {
168 if (other._count > 0)
169 {
170 set_capacity(other.capacity());
171
172 // NOTE: We keep the original entry + data indices, they don't need to change.
173 // Copy all the entries, this is always a POD type.
174 static_assert(std::is_pod_v<EntryType>, "HashMap::Entry should not be changed!");
176 Memory{ .location = _entries, .size = ice::size_of<EntryType> * other._count, .alignment = ice::align_of<EntryType> },
177 Data{ .location = other._entries, .size = ice::size_of<EntryType> * other._count, .alignment = ice::align_of<EntryType> }
178 );
179
180 // If the value is a complex type, properly move construct it in the new location + destroy in the old one.
181 ice::Memory const self_values_memory{ _data, this->size(), ice::align_of<ValueType> };
182 if constexpr (Logic == ContainerLogic::Complex)
183 {
185 self_values_memory,
186 other._data,
187 other._count
188 );
189 }
190 else
191 {
193 self_values_memory,
194 other.values_data_view()
195 );
196 }
197
198 ice::u32 idx = 0;
199 for (EntryType const& entry : ice::detail::hashmap::entries(*this))
200 {
201 // First remember the previous set index...
202 _entries[idx].next = _hashes[entry.key % _capacity];
203
204 // ... then save the current index in the hashed array.
205 _hashes[entry.key % _capacity] = idx;
206 idx += 1;
207 }
208
209 _count = other._count;
210 }
211 }
212
213 template<typename Type, ice::ContainerLogic Logic>
214 inline auto HashMap<Type, Logic>::operator=(HashMap&& other) noexcept -> HashMap&
215 {
216 if (this != &other)
217 {
218 clear();
219 set_capacity(other.capacity());
220
221 _allocator = other._allocator;
222 _capacity = std::exchange(other._capacity, 0);
223 _count = std::exchange(other._count, 0);
224 _hashes = std::exchange(other._hashes, nullptr);
225 _entries = std::exchange(other._entries, nullptr);
226 _data = std::exchange(other._data, nullptr);
227 }
228 return *this;
229 }
230
231 template<typename Type, ice::ContainerLogic Logic>
232 inline auto HashMap<Type, Logic>::operator=(HashMap const& other) noexcept -> HashMap&
233 requires std::copy_constructible<Type>
234 {
235 if (this != &other)
236 {
237 this->clear();
238
239 // Grows the internal data structure to the required size.
240 this->reserve(other.size());
241
242 // NOTE: We keep the original entry + data indices, they don't need to change.
243 // Copy all the entries, this is always a POD type.
244 static_assert(std::is_pod_v<EntryType>, "HashMap::Entry should not be changed!");
246 this->entries_memory_view(),
247 other.entries_data_view()
248 );
249
250 // If the value is a complex type, properly move construct it in the new location + destroy in the old one.
251 ice::Memory const self_values_memory{ _data, this->size(), ice::align_of<ValueType> };
252 if constexpr (Logic == ContainerLogic::Complex)
253 {
255 self_values_memory,
256 other._data,
257 other._count
258 );
259 }
260 else
261 {
263 self_values_memory,
264 other.values_data_view()
265 );
266 }
267
268 ice::u32 idx = 0;
269 for (EntryType const& entry : ice::detail::hashmap::entries(*this))
270 {
271 // First remember the previous set index...
272 _entries[idx].next = _hashes[entry.key % _capacity];
273
274 // ... then save the current index in the hashed array.
275 _hashes[entry.key % _capacity] = idx;
276 idx += 1;
277 }
278
279 _count = other._count;
280 }
281 return this;
282 }
283
284 template<typename Type, ice::ContainerLogic Logic>
285 template<typename Self, typename InValueType>
286 inline constexpr auto ice::HashMap<Type, Logic>::store(
287 this Self& self, KeyType key, InValueType&& in_value
289 {
290 if (self.is_full())
291 {
292 self.grow();
293 }
294
295 bool found = false;
296 ice::u32 const index = ice::detail::hashmap::find_or_make(self, key, found);
298 {
299 // If the index was found we need to destroy the previous value.
300 if (found)
301 {
302 ice::mem_destruct_at(self._data + index);
303 }
304
306 Memory{ self._data + index, ice::size_of<Type>, ice::align_of<Type> },
307 std::forward<InValueType>(in_value)
308 );
309 }
310 else
311 {
312 self._data[index] = in_value;
313 }
314
315 return self._data[index];
316 }
317
318 template<typename Type, ice::ContainerLogic Logic>
319 template<typename Self>
320 inline constexpr bool ice::HashMap<Type, Logic>::remove(this Self& self, KeyType key) noexcept
321 {
322 return ice::detail::hashmap::find_and_erase(self, key);
323 }
324
325 template<typename Type, ice::ContainerLogic Logic>
326 template<typename Self>
327 inline constexpr auto ice::HashMap<Type, Logic>::find(
328 this Self&& self,
329 KeyType key
331 {
332 ice::u32 const entry_index = ice::detail::hashmap::find_or_fail(self, key);
333 return entry_index != ice::detail::hashmap::Constant_EndOfList
334 ? ice::addressof(self._data[entry_index])
335 : nullptr;
336 }
337
338 template<typename Type, ice::ContainerLogic Logic>
339 template<typename Self>
340 inline constexpr auto ice::HashMap<Type, Logic>::values(this Self&& self) noexcept -> ice::container::SpanType<Self>
341 {
342 return { self._data, self._count };
343 }
344
345 template<typename Type, ice::ContainerLogic Logic>
346 inline constexpr void ice::HashMap<Type, Logic>::set_capacity(ice::ncount new_capacity) noexcept
347 {
348 ice::u32* new_hashes_ptr = nullptr;
349 EntryType* new_entries_ptr = nullptr;
350 ValueType* new_value_ptr = nullptr;
351
352 if (new_capacity > 0)
353 {
354 ICE_ASSERT_CORE(new_capacity >= this->size()); // We don't support support reduction below current item count!
355 ice::ncount const new_internal_capacity = ice::detail::hashmap::capacity_with_overhead(new_capacity);
356
357 ice::ChunkedAllocRequest alloc_reqest;
358 alloc_reqest.include(new_hashes_ptr, new_internal_capacity);
359 alloc_reqest.include(new_entries_ptr, new_capacity);
360 alloc_reqest.include(new_value_ptr, new_capacity);
361 _allocator->allocate(alloc_reqest);
362
363 // Prepare hashes memory
364 std::memset(new_hashes_ptr, 0xffffffff, new_internal_capacity * sizeof(u32));
365
366 if (_count > 0)
367 {
368 // NOTE: We keep the original entry + data indices, they don't need to change.
369
370 // Copy all the entries, this is always a POD type.
371 static_assert(std::is_pod_v<EntryType>, "HashMap::EntryType should not be changed!");
372 std::memcpy(new_entries_ptr, this->_entries, sizeof(EntryType) * _count);
373
374 // If the value is a complex type, properly move construct it in the new location + destroy in the old one.
375 ice::Memory const new_values_memory{ new_value_ptr, this->size(), ice::align_of<ValueType> };
377 {
378 ice::mem_move_construct_n_at(new_values_memory, _data, _count);
379 }
380 else
381 {
382 ice::memcpy(new_values_memory, this->values_data_view());
383 }
384
385 ice::u32 idx = 0;
386 ice::u32 const new_capacity_u32 = new_capacity.u32();
387 for (EntryType const& entry : ice::detail::hashmap::entries(*this))
388 {
389 // First remember the previous set index...
390 new_entries_ptr[idx].next = new_hashes_ptr[entry.key % new_capacity_u32];
391
392 // ... then save the current index in the hashed array.
393 new_hashes_ptr[entry.key % new_capacity_u32] = idx;
394 idx += 1;
395 }
396 }
397 }
398
399 _allocator->deallocate(this->memory_view());
400 _capacity = new_capacity.u32();
401 _hashes = new_hashes_ptr;
402 _entries = new_entries_ptr;
403 _data = new_value_ptr;
404 }
405
406 template<typename Type, ice::ContainerLogic Logic>
407 inline constexpr void ice::HashMap<Type, Logic>::clear() noexcept
408 {
409 if (_count == 0)
410 {
411 return;
412 }
413
414 if constexpr (Logic == ContainerLogic::Complex)
415 {
417 }
418
420 std::memset(_hashes, ice::detail::hashmap::Constant_EndOfList, sizeof(u32) * internal_capacity);
421
422 _count = 0;
423 }
424
425 template<typename Type, ice::ContainerLogic Logic>
426 template<typename Self>
427 inline constexpr auto HashMap<Type, Logic>::begin(this Self&& self) noexcept -> ice::container::Iterator<Self>
428 {
429 return { self._entries, self._data };
430 }
431
432 template<typename Type, ice::ContainerLogic Logic>
433 template<typename Self>
434 inline constexpr auto HashMap<Type, Logic>::end(this Self&& self) noexcept -> ice::container::Iterator<Self>
435 {
436 return { self._entries + self._count, self._data + self._count };
437 }
438
439 template<typename Type, ice::ContainerLogic Logic>
440 inline constexpr auto ice::HashMap<Type, Logic>::memory_view() noexcept -> ice::Memory
441 {
443 return ice::Memory{
444 .location = _hashes,
445 .size = info.size,
446 .alignment = ice::align_of<ValueType>
447 };
448 }
449
450 template<typename Type, ice::ContainerLogic Logic>
451 inline constexpr auto ice::HashMap<Type, Logic>::entries_memory_view() noexcept -> ice::Memory
452 {
453 return ice::Memory{
454 .location = _entries,
456 .alignment = ice::align_of<EntryType>
457 };
458 }
459
460 template<typename Type, ice::ContainerLogic Logic>
461 inline constexpr auto ice::HashMap<Type, Logic>::values_data_view() const noexcept -> ice::Data
462 {
463 return Data{
464 .location = _data,
465 .size = this->size(),
466 .alignment = ice::align_of<ValueType>
467 };
468 }
469
470 template<typename Type, ice::ContainerLogic Logic>
471 inline constexpr auto ice::HashMap<Type, Logic>::entries_data_view() const noexcept -> ice::Data
472 {
473 return Data{
474 .location = _entries,
476 .alignment = ice::align_of<EntryType>
477 };
478 }
479
480} // namespace ice
#define ICE_ASSERT_CORE(expression)
Definition assert_core.hxx:43
A concept that ensures only types that can be trivially copyable can be 'forced' to use trifial Logic...
Definition container_logic.hxx:25
Definition associative_container.hxx:8
ice::Span< ice::container::ConstCorrectContainerValueType< ContainerT > > SpanType
Definition container_concepts.hxx:167
ValueType< ContainerT > * ValuePtr
Definition container_concepts.hxx:155
ConstCorrectContainerIterator< ContainerT > Iterator
Definition container_concepts.hxx:158
ValueType< ContainerT > & ValueRef
Definition container_concepts.hxx:149
constexpr auto calc_meminfo(ice::ncount capacity) noexcept -> ice::meminfo
Definition hashmap_details.hxx:53
bool find_and_erase(ContainerT &map, ice::u64 key) noexcept
Definition hashmap_details.hxx:242
constexpr auto capacity_with_overhead(ice::ncount max_count) noexcept -> ice::ncount
Definition hashmap_details.hxx:47
auto find_or_make(ContainerT &map, ice::u64 key, bool &found) noexcept -> ice::u32
Definition hashmap_details.hxx:207
auto entries(ContainerT const &map) noexcept -> ice::Span< typename ContainerT::EntryType const >
Definition hashmap_details.hxx:64
auto find_or_fail(ContainerT const &map, ice::container::KeyType< ContainerT > key) noexcept -> ice::u32
Definition hashmap_details.hxx:201
static constexpr ice::u32 Constant_EndOfList
Definition hashmap_details.hxx:31
Definition info.hxx:8
SPDX-License-Identifier: MIT.
Definition array.hxx:12
constexpr ice::usize size_of
Definition mem_info.hxx:12
auto mem_move_construct_n_at(ice::Memory memory, T *objects, ice::u64 count) noexcept -> T *
Definition mem_initializers.hxx:58
ContainerLogic
The logic implemented by a collectiont type when working with data. (Copying, Moving,...
Definition container_logic.hxx:13
@ Complex
The collection handles complex data types and properly implements copy and move semantics.
Definition container_logic.hxx:19
void mem_destruct_n_at(T *location, ice::u64 count) noexcept
Definition mem_initializers.hxx:113
auto alloc(ice::usize size) noexcept -> ice::AllocResult
std::uint64_t u64
Definition types.hxx:27
constexpr ice::ualign align_of
Definition mem_info.hxx:15
auto mem_copy_construct_n_at(ice::Memory memory, T const *objects, ice::u64 count) noexcept -> T *
Definition mem_initializers.hxx:80
std::uint32_t u32
Definition types.hxx:26
void mem_destruct_at(T *location) noexcept
Definition mem_initializers.hxx:107
ice::AllocatorBase< ice::build::is_debug||ice::build::is_develop > Allocator
Definition mem_types.hxx:25
auto mem_construct_at(void *memory_ptr, Args &&... args) noexcept -> T *
Definition mem_initializers.hxx:12
auto memcpy(void *dest, void const *source, ice::usize size) noexcept -> void *
Definition mem.hxx:29
constexpr auto include(T *&ptrref, ice::u64 count) noexcept
Definition mem.hxx:91
Definition mem_data.hxx:17
Definition hashmap.hxx:107
constexpr auto operator==(ConstIterator const &other) const noexcept
Definition hashmap.hxx:124
constexpr ConstIterator(std::nullptr_t) noexcept
Definition hashmap.hxx:111
EntryType const * _entry
Definition hashmap.hxx:108
constexpr auto operator!=(ConstIterator const &other) const noexcept
Definition hashmap.hxx:125
constexpr auto value() const noexcept -> Type const &
Definition hashmap.hxx:122
constexpr auto key() const noexcept -> ice::u64 const &
Definition hashmap.hxx:121
constexpr auto operator*() const noexcept -> Type const &
Definition hashmap.hxx:128
constexpr ConstIterator(EntryType const *entry, Type const *value) noexcept
Definition hashmap.hxx:116
ValueType const * _value
Definition hashmap.hxx:109
constexpr void operator++() noexcept
Definition hashmap.hxx:127
Definition hashmap.hxx:42
KeyType key
Definition hashmap.hxx:43
ice::u32 next
Definition hashmap.hxx:44
constexpr auto size() const noexcept -> SizeType
Definition hashmap.hxx:67
constexpr auto store(this Self &self, KeyType key, InValueType &&in_value) noexcept -> ice::container::ValueRef< Self >
Definition hashmap.hxx:286
constexpr auto begin(this Self &&self) noexcept -> ice::container::Iterator< Self >
Definition hashmap.hxx:427
constexpr void clear() noexcept
Definition hashmap.hxx:407
constexpr auto values(this Self &&self) noexcept -> ice::container::SpanType< Self >
Definition hashmap.hxx:340
Type ValueType
Definition hashmap.hxx:36
ConstIterator Iterator
Definition hashmap.hxx:38
ValueType * _data
Definition hashmap.hxx:53
constexpr auto find(this Self &&self, KeyType key) noexcept -> ice::container::ValuePtr< Self >
Definition hashmap.hxx:327
ice::Allocator * _allocator
Definition hashmap.hxx:47
ice::u32 * _hashes
Definition hashmap.hxx:51
constexpr void set_capacity(ice::ncount new_capacity) noexcept
Definition hashmap.hxx:346
constexpr auto entries_memory_view() noexcept -> ice::Memory
Definition hashmap.hxx:451
ice::ncount SizeType
Definition hashmap.hxx:39
ice::u32 _count
Definition hashmap.hxx:49
static constexpr ContainerLogic OperationLogic
Definition hashmap.hxx:27
ice::u64 KeyType
Definition hashmap.hxx:35
constexpr auto end(this Self &&self) noexcept -> ice::container::Iterator< Self >
Definition hashmap.hxx:434
auto operator=(HashMap &&other) noexcept -> HashMap &
Definition hashmap.hxx:214
~HashMap() noexcept
Definition hashmap.hxx:142
EntryType * _entries
Definition hashmap.hxx:52
constexpr auto memory_view() noexcept -> ice::Memory
Definition hashmap.hxx:440
constexpr auto entries_data_view() const noexcept -> ice::Data
Definition hashmap.hxx:471
constexpr auto values_data_view() const noexcept -> ice::Data
Definition hashmap.hxx:461
ice::u32 _capacity
Definition hashmap.hxx:48
HashMap(ice::Allocator &alloc) noexcept
Definition hashmap.hxx:132
constexpr auto capacity() const noexcept -> SizeType
Definition hashmap.hxx:86
constexpr bool remove(this Self &self, KeyType key) noexcept
Definition hashmap.hxx:320
Type const ConstContainerValueType
Definition hashmap.hxx:37
Definition mem_memory.hxx:13
Definition associative_container.hxx:55
Definition resizable_container.hxx:11
constexpr void reserve(this Self &self, ice::ncount min_capacity) noexcept
Definition resizable_container.hxx:25
Definition mem_size_types.hxx:59
Definition ncount.hxx:14