vkmEngine 1.0.0
A C++ game engine · vkmengine.com
Loading...
Searching...
No Matches
resource_manager.h
1#pragma once
2
3#include <memory>
4#include <string>
5#include <type_traits>
6#include <unordered_map>
7#include <utility>
8#include <vector>
9
10#include "l_assert.h"
11#include "logger.h"
12
13#include "core/memory/slot_allocator.h"
14#include "core/memory/sparse_set.h"
15#include "core/memory/types.h"
16#include "core/memory/type_registry.h"
17#include "resource/asset_ref.h"
18#include "resource/asset_type.h"
19#include "resource/resource.h"
20#include "resource/resource_handle.h"
21
22namespace Vkm::Engine {
23
24struct FontAsset;
25
33template<typename T>
34inline constexpr bool IS_ENGINE_ASSET = ASSET_TYPE<T> != AssetType::Count || std::is_same_v<T, FontAsset>;
35
46class ResourceManager {
47 public:
48 ResourceManager();
49 ~ResourceManager() = default;
50
51 ResourceManager(const ResourceManager& other) = delete;
52 ResourceManager& operator=(const ResourceManager& other) = delete;
53
54 ResourceManager(ResourceManager && other) = delete;
55 ResourceManager& operator=(ResourceManager && other) = delete;
56
57 public:
71 template<typename ResourceType>
72 auto add(ResourceType && resource) {
73 using T = std::remove_cv_t<std::remove_reference_t<ResourceType>>;
74 static_assert(
75 std::is_base_of_v<Resource, T>,
76 "ResourceManager stores only types deriving from Resource."
77 );
78 auto& slot = getSlot<T>();
79
80 if (resource.m_name.empty()) ensureUniqueName(slot, resource.m_name);
81
82 if (const auto taken = slot.nameIndex.find(resource.m_name); taken != slot.nameIndex.end()) {
83 return replaceAt<T>(slot, taken->second, std::forward<ResourceType>(resource));
84 }
85
86 StorageIndex key = slot.allocator.allocate();
87 std::string indexName = resource.m_name; // non-empty now
88 // Stamped on the stored asset, not the argument: insertion may copy,
89 // and a copy is a duplicate that carries no identity of its own.
90 Resource& stored = storageOf<T>(slot).add(key.index, std::forward<ResourceType>(resource));
91 stored.m_uid = mintUid();
92 stored.m_version = 1;
93 slot.nameIndex.emplace(std::move(indexName), key.index);
94
95 return Handle<T>{key};
96 }
97
106 template<typename ResourceType>
107 auto add(ResourceType && resource, std::string name) {
108 resource.m_name = std::move(name);
109 return add(std::forward<ResourceType>(resource));
110 }
111
120 template<typename ResourceType>
121 auto addPrivate(ResourceType && resource, std::string name) {
122 resource.m_hidden = true;
123 resource.m_name = std::move(name);
124 return add(std::forward<ResourceType>(resource));
125 }
126
137 template<typename HandleType>
138 void remove(const HandleType& handle) {
139 using T = typename HandleType::resource_t;
140 auto& slot = getSlot<T>();
141 VKM_ASSERT(slot.allocator.has(handle.key), "ResourceManager::remove invalid handle");
142 if (!slot.allocator.has(handle.key)) return;
143
144 const T& res = storageOfConst<T>(slot).get(handle.key.index);
145 dropNameIndex(slot, res.m_name, handle.key.index);
146 storageOf<T>(slot).remove(handle.key.index);
147 slot.allocator.free(handle.key);
148 }
149
159 template<typename HandleType>
160 bool isAlive(const HandleType& handle) const {
161 using T = typename HandleType::resource_t;
162 const TypedSlot* slot = trySlot<T>();
163 return slot && slot->allocator.has(handle.key);
164 }
165
173 template<typename HandleType>
174 const auto& get(const HandleType& handle) const {
175 using T = typename HandleType::resource_t;
176 const auto& slot = getSlotConst<T>();
177 VKM_ASSERT(slot.allocator.has(handle.key), "ResourceManager::get invalid handle");
178 return storageOfConst<T>(slot).get(handle.key.index);
179 }
180
191 template<typename HandleType>
192 const auto* tryGet(const HandleType& handle) const {
193 using T = typename HandleType::resource_t;
194 const TypedSlot* slot = trySlot<T>();
195 if (!slot || !slot->allocator.has(handle.key)) return static_cast<const T*>(nullptr);
196 return &storageOfConst<T>(*slot).get(handle.key.index);
197 }
198
208 template<typename HandleType>
209 auto* tryEdit(const HandleType& handle) {
210 using T = typename HandleType::resource_t;
211 TypedSlot* slot = m_slots.find<T>();
212 if (!slot || !slot->allocator.has(handle.key)) return static_cast<T*>(nullptr);
213 return &storageOf<T>(*slot).get(handle.key.index);
214 }
215
226 template<typename HandleType>
227 auto& edit(const HandleType& handle) {
228 using T = typename HandleType::resource_t;
229 auto& slot = getSlot<T>();
230 VKM_ASSERT(slot.allocator.has(handle.key), "ResourceManager::edit invalid handle");
231 return storageOf<T>(slot).get(handle.key.index);
232 }
233
244 template<typename HandleType>
245 void rename(const HandleType& handle, std::string newName) {
246 using T = typename HandleType::resource_t;
247 auto& slot = getSlot<T>();
248 VKM_ASSERT(slot.allocator.has(handle.key), "ResourceManager::rename invalid handle");
249 auto& res = storageOf<T>(slot).get(handle.key.index);
250 // Before the uniqueness check, so renaming to the current name is not a self-collision.
251 dropNameIndex(slot, res.m_name, handle.key.index);
252 ensureUniqueName(slot, newName);
253 res.m_name = std::move(newName);
254 slot.nameIndex[res.m_name] = handle.key.index;
255 }
256
270 template<typename HandleType>
271 void swapValue(const HandleType& handle, typename HandleType::resource_t& value) {
272 using T = typename HandleType::resource_t;
273 static_assert(
274 std::is_base_of_v<Resource, T>,
275 "Resource type must inherit from Resource to use swapValue()."
276 );
277 auto& slot = getSlot<T>();
278 VKM_ASSERT(slot.allocator.has(handle.key), "ResourceManager::swapValue invalid handle");
279 T& target = storageOf<T>(slot).get(handle.key.index);
280 if (&target == &value) return;
281
282 using std::swap;
283 swap(target, value);
284 swap(target.m_version, value.m_version);
285 swap(target.m_uid, value.m_uid);
286 swap(target.m_hidden, value.m_hidden);
287 swap(target.m_name, value.m_name);
288 ++target.m_version;
289 }
290
297 template<typename HandleType>
298 void commit(const HandleType& handle) {
299 using T = typename HandleType::resource_t;
300 static_assert(
301 std::is_base_of_v<Resource, T>,
302 "Resource type must inherit from Resource to use commit()."
303 );
304 auto& slot = getSlot<T>();
305 VKM_ASSERT(slot.allocator.has(handle.key), "ResourceManager::commit invalid handle");
306 ++storageOf<T>(slot).get(handle.key.index).m_version;
307 }
308
316 template<typename T>
317 Handle<T> findByName(const std::string& name) const {
318 const TypedSlot* slot = trySlot<T>();
319 if (!slot) return {};
320
321 auto it = slot->nameIndex.find(name);
322 if (it == slot->nameIndex.end()) return {};
323 return Handle<T>{slot->allocator.handleAt(it->second)};
324 }
325
337 template<typename T>
338 Handle<T> find(const AssetRef<T>& ref) const {
339 return ref.name.empty() ? Handle<T>{} : findByName<T>(ref.name);
340 }
341
351 template<typename T, typename Fn>
352 void forEachOfType(Fn&& fn) const {
353 const TypedSlot* slot = trySlot<T>();
354 if (!slot) return;
355 storageOfConst<T>(*slot).forEach([&](uint32_t index, const T& res) {
356 fn(Handle<T>{slot->allocator.handleAt(index)}, res);
357 });
358 }
359
365 template<typename T>
366 size_t countOfType() const {
367 const TypedSlot* slot = trySlot<T>();
368 return slot ? storageOfConst<T>(*slot).size() : 0;
369 }
370
376 void clear();
377
389 void swap(ResourceManager& other) noexcept;
390
399 uint64_t epoch() const { return m_epoch; }
400
401 private:
402 struct TypedSlot {
403 SlotAllocator allocator;
404 std::unique_ptr<ISparseSet> storage;
406 std::unordered_map<std::string, uint32_t> nameIndex;
407 };
408
409 private:
413 void makeEngineSlots();
414
415 template<typename T>
416 TypedSlot& getSlot() {
417 static_assert(
418 IS_ENGINE_ASSET<T>,
419 "ResourceManager stores the engine's asset types only: a slot for a type a "
420 "gameplay module declares would carry the module's code past its unload."
421 );
422 return m_slots.ensure<T>([] {
423 auto slot = std::make_unique<TypedSlot>();
424 slot->storage = std::make_unique<SparseSet<T>>();
425 return slot;
426 });
427 }
428
435 template<typename T>
436 const TypedSlot* trySlot() const {
437 return m_slots.find<T>();
438 }
439
440 template<typename T>
441 const TypedSlot& getSlotConst() const {
442 const TypedSlot* slot = trySlot<T>();
443 VKM_ASSERT(slot, "ResourceManager: type not registered");
444 return *slot;
445 }
446
447 template<typename T>
448 static SparseSet<T>& storageOf(TypedSlot& slot) {
449 return static_cast<SparseSet<T>&>(*slot.storage);
450 }
451
452 template<typename T>
453 static const SparseSet<T>& storageOfConst(const TypedSlot& slot) {
454 return static_cast<const SparseSet<T>&>(*slot.storage);
455 }
456
464 static void dropNameIndex(TypedSlot& slot, const std::string& name, uint32_t index) {
465 auto it = slot.nameIndex.find(name);
466 if (it != slot.nameIndex.end() && it->second == index) {
467 slot.nameIndex.erase(it);
468 }
469 }
470
485 template <typename T, typename ResourceType>
486 Handle<T> replaceAt(TypedSlot& slot, uint32_t index, ResourceType && resource) {
487 T& target = storageOf<T>(slot).get(index);
488
489 // First: re-declaring an asset with its own contents hands in target itself, and
490 // constructing from it would move the name away.
491 const uint64_t version = target.m_version;
492 std::string name = std::move(target.m_name);
493
494 // Constructed: Resource has no copy assignment.
495 T incoming(std::forward<ResourceType>(resource));
496 using std::swap;
497 swap(target, incoming);
498
499 target.m_name = std::move(name);
500 target.m_uid = mintUid();
501 target.m_version = version + 1;
502 return Handle<T>{slot.allocator.handleAt(index)};
503 }
504
514 static void ensureUniqueName(const TypedSlot& slot, std::string& name) {
515 if (name.empty()) name = "asset";
516 if (slot.nameIndex.find(name) == slot.nameIndex.end()) return;
517 const std::string base = name;
518 for (int n = 2; ; ++n) {
519 std::string candidate = base + " (" + std::to_string(n) + ")";
520 if (slot.nameIndex.find(candidate) == slot.nameIndex.end()) {
521 name = std::move(candidate);
522 return;
523 }
524 }
525 }
526
535 static uint64_t mintUid();
536
537 private:
539
541 uint64_t m_epoch = 1;
542};
543
544} // namespace Vkm::Engine
size_t countOfType() const
How many resources of type T the graph holds.
Definition resource_manager.h:366
Handle< T > find(const AssetRef< T > &ref) const
Resolve an authored reference to the asset it names.
Definition resource_manager.h:338
bool isAlive(const HandleType &handle) const
Whether handle still names a live resource of its type.
Definition resource_manager.h:160
void swapValue(const HandleType &handle, typename HandleType::resource_t &value)
Exchange the contents of the asset at handle with value, each keeping its identity,...
Definition resource_manager.h:271
const auto & get(const HandleType &handle) const
The resource handle names; asserts it is live (see isAlive(), tryGet()).
Definition resource_manager.h:174
void clear()
Drop every resource but the fonts, by swap() with an empty manager.
void rename(const HandleType &handle, std::string newName)
Rename a resource, keeping findByName consistent.
Definition resource_manager.h:245
auto * tryEdit(const HandleType &handle)
The asset handle names for editing, or null when it names none.
Definition resource_manager.h:209
void swap(ResourceManager &other) noexcept
Swap the asset graph with another ResourceManager, fonts excepted.
auto & edit(const HandleType &handle)
Mutable access to the resource handle names.
Definition resource_manager.h:227
const auto * tryGet(const HandleType &handle) const
The asset handle names, or null when it names none.
Definition resource_manager.h:192
auto add(ResourceType &&resource, std::string name)
add() under name.
Definition resource_manager.h:107
uint64_t epoch() const
Identity of the current asset graph, bumped by every swap.
Definition resource_manager.h:399
Handle< T > findByName(const std::string &name) const
Find a resource by the name it was added or renamed under, in O(1).
Definition resource_manager.h:317
void forEachOfType(Fn &&fn) const
Visit every live resource of type T; a linear scan for tooling, not hot paths.
Definition resource_manager.h:352
void remove(const HandleType &handle)
Remove a resource: its name mapping, storage and handle slot.
Definition resource_manager.h:138
void commit(const HandleType &handle)
Bump the asset's version, which GLView::sync keys its GPU re-upload on.
Definition resource_manager.h:298
auto addPrivate(ResourceType &&resource, std::string name)
add() under name, setting Resource::isHidden: kept from the user and scene saves.
Definition resource_manager.h:121
auto add(ResourceType &&resource)
Put resource in the graph under its name, stamping its identity.
Definition resource_manager.h:72
Base class for every asset type, carrying the identity the ResourceManager indexes by.
Definition resource.h:17
Lightweight slot allocator that issues generation-safe handles.
Definition slot_allocator.h:18
StorageIndex handleAt(uint32_t index) const
Rebuild the handle naming a sparse slot, generation included.
Definition slot_allocator.h:93
bool has(StorageIndex id) const
Test whether a handle is still valid.
Definition slot_allocator.h:78
Dense-packed storage indexed by external uint32_t keys.
Definition sparse_set.h:50
One slot per type, indexed by typeId<T>() and filled on first use.
Definition type_registry.h:21
An authored reference to an asset, held as the name it is filed under.
Definition asset_ref.h:23
std::string name
Asset name; empty for none.
Definition asset_ref.h:26
A TrueType font baked to a signed-distance-field atlas.
Definition font_asset.h:48
Opaque, type-safe handle for resources, distinguished by ResourceType.
Definition resource_handle.h:16
Opaque handle pairing a sparse-array index with a generation counter.
Definition types.h:14
uint32_t index
Sparse slot index; 0 is null.
Definition types.h:15