Cogs.Core
ResourceManagerBase.cpp
1#include "ResourceManagerBase.h"
2#include "Context.h"
3#include "Engine.h"
4#include "ResourceStore.h"
5
6#include "Services/Variables.h"
7
8#include "Foundation/Collections/Pool.h"
9#include "Foundation/Logging/Logger.h"
10#include "Foundation/Platform/FileSystemWatcher.h"
11#include "Foundation/Platform/IO.h"
12
13#include <queue>
14#include <unordered_set>
15#include <unordered_map>
16#include <utility>
17
18namespace
19{
20 Cogs::Logging::Log logger = Cogs::Logging::getLogger("ResourceManagerBase");
21}
22
23namespace Cogs::Core
24{
31 {
32 static constexpr uint32_t NoSlot = static_cast<uint32_t>(-1);
33 static constexpr uint32_t NotFree = static_cast<uint32_t>(-2);
34
35 struct PointerEntry {
36 uint32_t next = NoSlot;
37 ResourceBase* resource = nullptr;
38 };
39
42 uint32_t insert(ResourceBase * ptr)
43 {
44 uint32_t slot;
45 if (free != NoSlot) {
46 // Get from free list
47 slot = free;
48 free = slots[free].next;
49 }
50 else {
51 slot = static_cast<uint32_t>(slots.size());
52 slots.emplace_back();
53 }
54
55 numUsed++;
56 slots[slot].next = NotFree;
57 slots[slot].resource = ptr;
58 return slot;
59 }
60
63 void remove(uint32_t slot)
64 {
65 assert(slot != NoSlot && "Invalid slot index Using Freed resource.");
66 assert(slot < slots.size() && "Slot index out of range.");
67 if (slots[slot].resource == nullptr) {
68 assert(false && "Slot index freed.");
69 return;
70 }
71
72 // Add to free-list. Mark freed.
73 slots[slot].next = free;
74 slots[slot].resource = nullptr;
75 free = slot;
76 numUsed--;
77 }
78
79 size_t size() const
80 {
81 return numUsed;
82 }
83
86 std::span<const PointerEntry> view() const
87 {
88 return slots;
89 }
90
91 private:
92 std::vector<PointerEntry> slots;
93 uint32_t free = NoSlot;
94 size_t numUsed = 0u;
95 };
96
98 {
99 ResourceManagerBaseStorage() : resourceInfos(MemBlockType::ResourceManager) {}
100
102
103 std::queue<ResourceLoadInfoBase *> loading;
104 std::queue<ResourceLoadInfoBase *> queued;
105
106 std::unordered_set<ResourceHandleBase> updatedResources;
107 std::vector<ResourceBase *> orphanedResources;
109
110 std::unordered_map<ResourceId, ResourceHandleBase> resourcesById;
111
112 std::unordered_map<size_t, ResourceBase *> resourcesBySource;
113
120
123
124 Atomic<ResourceId> nextResourceId = 1;
125 };
126}
127
128Cogs::Core::ResourceManagerBase::ResourceManagerBase(Context * context) :
129 storage(std::make_unique<ResourceManagerBaseStorage>()),
130 context(context)
131{
132
133}
134
135Cogs::Core::ResourceManagerBase::~ResourceManagerBase()
136{
137 assert(storage->updatedResources.size() == 0 && "Updated resources must be cleared before manager is destructed.");
138}
139
140void Cogs::Core::ResourceManagerBase::initialize()
141{
142 //NOTE: This section is primarily used to guard against some edge case behavior seen in example
143 // code where the engine is reinitialized multiple times in a short timeframe, with potential
144 // background processes being run by extensions etc.
145 LockGuard lock(storage->updatedMutex);
146 LockGuard rlock(storage->resourceMutex);
147
148 // Ensure resources allocated and changed previous to the initialize call are put in the
149 // update queue.
150 for (const auto & entry : storage->allocatedResources.view()) {
151 ResourceBase* a = entry.resource;
152 if (a != nullptr && a->hasChanged() && !a->isProxy() && a->referenceCount()) {
153 storage->updatedResources.insert(generateHandle(a));
154 }
155 }
156}
157
158void Cogs::Core::ResourceManagerBase::clear()
159{
160 storage->nextResourceId = 1;
161
162 clearUpdated();
163}
164
165void Cogs::Core::ResourceManagerBase::reportLeaks(std::string_view typeName)
166{
167 if (context && context->variables->get("resources.reportLeaks", false)) {
168 LockGuard lock(storage->resourceMutex);
169
170 for (const auto & entry : storage->allocatedResources.view()) {
171 const ResourceBase* resource = entry.resource;
172 if (resource && resource->referenceCount()) {
173 LOG_WARNING(logger, "%.*s still alive with reference count: %d.", StringViewFormat(typeName), resource->referenceCount());
174 }
175 }
176 }
177}
178
180{
181 size_t infos, ids, sources, allocated;
182 {
183 LockGuard guard(storage->resourceMutex);
184 infos = storage->resourceInfos.size();
185 ids = storage->resourcesById.size();
186 sources = storage->resourcesById.size();
187 allocated = storage->allocatedResources.size();
188 }
189 size_t loading, queued;
190 {
191 LockGuard guard(storage->loadingMutex);
192 loading = storage->loading.size();
193 queued = storage->queued.size();
194 }
195 size_t updated;
196 {
197 LockGuard guard(storage->updatedMutex);
198 updated = storage->updatedResources.size();
199 }
200 size_t orphaned;
201 {
202 LockGuard guard(storage->orphanedMutex);
203 orphaned = storage->orphanedResources.size();
204 }
205
206 char buf[1024];
207 int rv = std::snprintf(buf, sizeof(buf), "alloc=%3zu, ids=%3zu, sources=%3zu infos=%3zu, load=%3zu, queue=%3zu, update=%3zu, orphan=%3zu", allocated, ids, sources, infos, loading, queued, updated, orphaned);
208 if (0 <= rv && static_cast<size_t>(rv) < sizeof(buf)) {
209 return std::string(buf);
210 }
211 else {
212 return std::string();
213 }
214}
215
216bool Cogs::Core::ResourceManagerBase::checkPreloaded(ResourceLoadInfoBase * loadInfo)
217{
218 if (!context->resourceStore->hasResource(loadInfo->resourcePath)) {
219 if (!loadInfo->preloading) {
220 context->resourceStore->preloadResources({ loadInfo->resourcePath });
221 loadInfo->preloading = true;
222 }
223
224 // Re-queue until all dependencies available.
225 queueResource(loadInfo);
226
227 return false;
228 }
229
230 return true;
231}
232
233void Cogs::Core::ResourceManagerBase::clearUpdated()
234{
235 {
236 LockGuard lock(storage->updatedMutex);
237
238 storage->updatedResources.clear();
239 }
240
241 LockGuard lock(storage->resourceMutex);
242
243 storage->resourcesById.clear();
244}
245
246int Cogs::Core::ResourceManagerBase::getUpdateQuota() const
247{
248 return context->variables->get("resources.globalFrameUpdateQuota", 0);
249}
250
252{
253 LockGuard lock(storage->resourceMutex);
254
255 auto found = storage->resourcesById.find(resourceId);
256
257 if (found == storage->resourcesById.end()) {
258 LOG_ERROR(logger, "Could not release resource %d.", resourceId);
259
261 }
262
263 // Remove from handle map. Lookup wil fail.
264 // Actual delete will be handled when no active refs left.
265 ResourceHandleBase h = found->second;
266 storage->resourcesById.erase(found);
267 return h;
268}
269
271{
272 if (loadInfo->isReload()) {
273 if (!loadInfo->handle) {
274 LOG_ERROR(logger, "Cannot reload invalid (empty) resource.");
275 return loadInfo->handle;
276 }
277
278 loadInfo->handle->unsetFlag(ResourceFlags::Queued);
279 loadInfo->handle->setLoading();
280
281 {
282 LockGuard loadingLock(storage->loadingMutex);
283
284 storage->loading.push(loadInfo);
285 }
286
287 return loadInfo->handle;
288 }
289
290 // Resolve the resource path to a unique path/protocol combination.
291 auto [path, protocol] = context->resourceStore->getResourceLocation(loadInfo->resourcePath);
292 loadInfo->protocol = protocol;
293
294 LockGuard resourceLock(storage->resourceMutex);
295
296 size_t code = Cogs::hash(path);
297
298 if (!path.empty()) {
299 if (shouldMergeBySource() && (loadInfo->loadFlags & ResourceLoadFlags::ForceUnique) == 0) {
300 // Check if resource with the given source path has already been loaded.
301 auto found = storage->resourcesBySource.find(code);
302
303 if (found != storage->resourcesBySource.end()) {
304 if (found->second->referenceCount() > 0) {
305 destroyLoadInfoInternal(loadInfo);
306 return found->second;
307 }
308 }
309 }
310
311 // Use the fixed-up path as resource source/path.
312 loadInfo->resourcePath = path;
313
314 if (loadInfo->resourceName.empty() && !loadInfo->noDefaultName()) {
315 loadInfo->resourceName = IO::fileName(loadInfo->resourcePath);
316 }
317
318 if (shouldAutoReload()) {
319 loadInfo->loadFlags |= ResourceLoadFlags::AutoReload;
320 }
321 }
322
323 if (!loadInfo->handle) {
324 loadInfo->handle = getOrCreateInternal(loadInfo->resourceId);
325 }
326
327 auto resource = loadInfo->handle.get();
328
329 assert(!resource->isDeleted() && "Resource pending deletion not valid destination.");
330
331 resource->setName(loadInfo->resourceName);
332 if ((loadInfo->loadFlags & ResourceLoadFlags::DoNotStoreSource) == 0) {
333 resource->setSource(loadInfo->resourcePath);
334 }
335
336 if (loadInfo->resourcePath.size()) {
337 storage->resourcesBySource[code] = resource;
338 }
339
340 resource->setFlags(loadInfo->resourceFlags);
341 resource->unsetFlag(ResourceFlags::Queued);
342 resource->setLoading();
343
344 if (loadInfo->shouldAutoReload() && loadInfo->protocol == ResourceProtocol::File) {
345 auto callback = [this, resource](FileSystemWatcher::Event /*e*/) mutable {
346 handleReload(ResourceHandleBase(resource));
347 };
348
349 if (context->watcher->watchFile(path, callback)) {
350 resource->setFlag(ResourceFlags::Watched);
351 }
352 }
353
354 {
355 LockGuard loadingLock(storage->loadingMutex);
356
357 storage->loading.push(loadInfo);
358 }
359
360 return loadInfo->handle;
361}
362
364{
365 UniqueLock loadingLock(storage->loadingMutex);
366
367 const size_t maxLoaded = 1000;
368
369 size_t loaded = 0;
370
371 while (storage->loading.size() && loaded++ < maxLoaded) {
372 auto loadInfo = std::move(storage->loading.front());
373 storage->loading.pop();
374
375 loadInfo->handle->setLoading();
376
377 // Must unlock to observe locking order
378 loadingLock.unlock();
379 handleLoadInternal(loadInfo);
380 loadingLock.lock();
381 }
382
383 while (storage->queued.size()) {
384 storage->loading.push(std::move(storage->queued.front()));
385 storage->queued.pop();
386 }
387
388 loadingLock.unlock();
389 postProcessLoading();
390}
391
393{
394 auto * resource = loadInfo->handle.get();
395 if (resource) {
396 resource->unsetFlag(ResourceFlags::Loading);
397
398 if (resource->hasFailedLoad()) {
399 handleFailedLoadInternal(loadInfo);
400 context->engine->invokeResourceLoadCallback((int)resource->getType(), loadInfo->resourceId, 1);
401 }
402 else {
403 if (!swapping) {
404 resource->setFlag(ResourceFlags::Loaded);
405 resource->setChanged();
406 }
407 context->engine->invokeResourceLoadCallback((int)resource->getType(), loadInfo->resourceId, 0);
408 }
409 }
410 context->engine->setDirty();
411 destroyLoadInfoInternal(loadInfo);
412}
413
414void Cogs::Core::ResourceManagerBase::activateResources()
415{
416 std::vector<ResourceHandleBase> updated;
417
418 const int quota = getUpdateQuota();
419
420 {
421 LockGuard updatedLock(storage->updatedMutex);
422
423 if (quota>0 && static_cast<size_t>(quota) < storage->updatedResources.size()) {
424 while (storage->updatedResources.size() && updated.size() < static_cast<size_t>(quota)) {
425 auto it = storage->updatedResources.begin();
426 updated.emplace_back(*it);
427 storage->updatedResources.erase(it);
428 }
429 } else {
430 updated.assign(storage->updatedResources.begin(), storage->updatedResources.end());
431
432 storage->updatedResources.clear();
433 }
434 }
435
436 for (auto & r : updated) {
437 auto resource = r.get();
438
439 if (r->hasChanged()) {
440 const auto result = handleActivationInternal(r, resource);
441 resource->unsetFlag(ResourceFlags::Changed);
442
443 switch (result) {
445 resource->setFlag(ResourceFlags::FailedActivation);
446 break;
448 // Setting the state to Changed here will automatically re-queue the resource in
449 // the updatedResources queue.
450 resource->setChanged();
451 break;
453 default:
454 resource->setResident();
455
456 if (resource->isDependency()) {
457 // Run an additional frame to let code waiting for resource activation detect the
458 // active state.
459 context->engine->setDirty();
460 }
461
462 break;
463 }
464 }
465 }
466}
467
468void Cogs::Core::ResourceManagerBase::fillDeletionQueue(std::vector<ResourceBase *> & deletion)
469{
470 const int quota = getUpdateQuota();
471
472 {
473 LockGuard orphanedLock(storage->orphanedMutex);
474
475 if (quota>0 && static_cast<size_t>(quota) < storage->orphanedResources.size()) {
476 deletion.assign(storage->orphanedResources.begin(), storage->orphanedResources.begin() + quota);
477 storage->orphanedResources.erase(storage->orphanedResources.begin(), storage->orphanedResources.begin() + quota);
478 } else {
479 deletion.assign(std::begin(storage->orphanedResources), std::end(storage->orphanedResources));
480 storage->orphanedResources.clear();
481 }
482 }
483}
484
486{
487 std::vector<ResourceBase *> deletion;
488
489 fillDeletionQueue(deletion);
490
491 {
492 LockGuard resourceLock(storage->resourceMutex);
493
494 for (auto & resource : deletion) {
495#ifdef _DEBUG
496 assert(resource->referenceCount() == 0 && "Owned resource may not be destroyed.");
497#else
498 if (resource->referenceCount() > 0) {
499 LOG_WARNING(logger, "Cannot destroy resource with reference count %u.", resource->referenceCount());
500 continue;
501 }
502#endif
503 destroy(resource);
504 }
505 }
506}
507
508void Cogs::Core::ResourceManagerBase::safeDestroy(ResourceBase * resource)
509{
510 LockGuard lock(storage->resourceMutex);
511
512 destroy(resource);
513}
514
516{
517 // If the resource has been resurrected, bail out before destruction.
518 if (resource->referenceCount()) {
519 LOG_WARNING(logger, "Resurrecting resource.");
520 return;
521 }
522
523 storage->allocatedResources.remove(resource->getSlot());
524 resource->setSlot(ResourcePointerMap::NoSlot);
525
526 if (!resource->isProxy()) {
527 handleDeletionInternal(resource);
528 }
529
530 if (!resource->getSource().empty()) {
531 storage->resourcesBySource.erase(Cogs::hash(resource->getSource()));
532 }
533
534 if (resource->isSet(ResourceFlags::Watched)) {
535 context->watcher->unwatchFile(resource->getSource());
536 }
537
538 resource->setOwner(nullptr);
539 resource->unsetFlag((ResourceFlags)~0);
540 resource->setFlags(ResourceFlags::Deleted);
541
542 if (resource->info) {
543 storage->resourceInfos.destroy(resource->info);
544 resource->info = nullptr;
545 }
546
547 destroyInternal(resource);
548}
549
550void Cogs::Core::ResourceManagerBase::destroyLocked(ResourceBase * resource)
551{
552 LockGuard lock(storage->resourceMutex);
553
554 destroyInternalLocked(resource);
555}
556
558{
559 return storage->nextResourceId++;
560}
561
562void Cogs::Core::ResourceManagerBase::resourceChanged(ResourceBase * resource)
563{
564 if (resource->isProxy()) return;
565
566 LockGuard updatedLock(storage->updatedMutex);
567
568 if (resource->referenceCount() == 0) {
569 return;
570 }
571
572 storage->updatedResources.insert(generateHandle(resource));
573}
574
575void Cogs::Core::ResourceManagerBase::resourceDeleted(ResourceBase * resource)
576{
577 LockGuard orphanedLock(storage->orphanedMutex);
578
579 // Resource is free and marked Orphaned.
580 // Still stored in resource lookup tables and must be checked if Orphaned before used.
581 storage->orphanedResources.push_back(resource);
582}
583
585{
586 LockGuard resourceLock(storage->resourceMutex);
587
588 resource->setId(id);
589
590 storage->resourcesById[id] = resource;
591}
592
594{
595 loadInfo->handle->setFlag(ResourceFlags::Queued);
596
597 storage->queued.push(loadInfo);
598}
599
601{
602 LockGuard lock(storage->resourceMutex);
603
604 auto it = storage->resourcesById.find(id);
605 if (id == NoResourceId || it == storage->resourcesById.end()) {
606 LOG_WARNING(logger, "Could not fetch handle for invalid id: %d", id);
608 }
609
610 return it->second;
611}
612
614{
615 LockGuard lock(storage->resourceMutex);
616 return storage->resourcesById.contains(id);
617}
618
619bool Cogs::Core::ResourceManagerBase::shouldAutoReload() const
620{
621 return context->variables->get("resources.autoReload", false);
622}
623
624std::vector<Cogs::Core::ResourceBase *> Cogs::Core::ResourceManagerBase::getAllocatedResources() const
625{
626 LockGuard lock(storage->resourceMutex);
627 auto v = storage->allocatedResources.view();
628 std::vector<Cogs::Core::ResourceBase*> resources;
629 resources.reserve(v.size());
630 for (const auto entry : v) {
631 if (entry.resource) {
632 resources.emplace_back(entry.resource);
633 }
634 }
635
636 return resources;
637}
638
639Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::getOrCreate(ResourceId resourceId)
640{
641 LockGuard resourceLock(storage->resourceMutex);
642
643 return getOrCreateInternal(resourceId);
644}
645
646Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::getOrCreateInternal(ResourceId resourceId)
647{
648 if (resourceId != NoResourceId) {
649 auto existingIt = storage->resourcesById.find(resourceId);
650
651 if (existingIt != storage->resourcesById.end()) {
652 return generateHandle(existingIt->second.get());
653 } else {
654 auto handle = createResourceInternal();
655
656 handle.get()->setId(resourceId);
657 storage->resourcesById[resourceId] = handle;
658
659 return handle;
660 }
661 } else {
662 return createResourceInternal();
663 }
664}
665
666Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::getByName(std::string_view name) const
667{
668 LockGuard resourceLock(storage->resourceMutex);
669
670 for (const auto & r : storage->allocatedResources.view()) {
671 if (r.resource && name == r.resource->getName()) {
672 if (!r.resource->isOrphaned()) {
673 return r.resource;
674 }
675 else {
676 LOG_TRACE(logger, "getByName: Ignoring orphaned resource: %.*s .", StringViewFormat(name));
677 }
678 }
679 }
680
682}
683
684Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::getBySource(std::string_view source) const
685{
686 LockGuard resourceLock(storage->resourceMutex);
687
688 auto foundIt = storage->resourcesBySource.find(Cogs::hash(source));
689
690 if (foundIt != storage->resourcesBySource.end()) {
691 if (!foundIt->second->isOrphaned()) {
692 return foundIt->second;
693 }
694 }
696}
697
698Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::createResource()
699{
700 LockGuard resourceLock(storage->resourceMutex);
701
702 return createResourceInternal();
703}
704
705Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::createResourceInternal()
706{
707 auto resource = createInternal();
708
709 resource->setInfo(storage->resourceInfos.create());
710
711 resource->setType(resourceType);
712 resource->setOwner(this);
713 resource->setInitialized();
714
715 // Track allocated resources to be able to check e.g if any are left when this instance is destructed.
716 resource->setSlot(storage->allocatedResources.insert(resource));
717
718 return ResourceHandleBase(resource);
719}
720
721Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::generateHandle(ResourceBase * resource)
722{
723 return ResourceHandleBase(resource);
724}
725
726size_t Cogs::Core::ResourceManagerBase::updatedResourceCount()
727{
728 LockGuard lock(storage->updatedMutex);
729 return storage->updatedResources.size();
730}
731
732size_t Cogs::Core::ResourceManagerBase::orphanedResourceCount()
733{
734 LockGuard lock(storage->orphanedMutex);
735 return storage->orphanedResources.size();
736}
737
738size_t Cogs::Core::ResourceManagerBase::allocatedResourceCount()
739{
740 LockGuard lock(storage->resourceMutex);
741 return storage->allocatedResources.size();
742}
void destroy(ResourceBase *resource)
Destroy the given resource, freeing it's data and sub-allocations.
std::string getReport()
Return a string with current resource usage.
void setResourceId(ResourceBase *resource, ResourceId id) override
Assign the given id to a previously created resource.
virtual void processDeletion() override
Process resources pending deallocation.
ResourceHandleBase releaseInternal(ResourceId resourceId)
Releases a resourceId -> ResourceHandle mapping.
ResourceId getNextResourceId() override
Get the next unique resource id.
void processLoading() final
Process loading resources.
bool isValidResource(ResourceId id) const
Check if resource handle points to.
void setProcessed(ResourceLoadInfoBase *loadInfo, bool swapping=false)
Signal the resource being loaded as done loading and ready for activation etc.
ResourceHandleBase loadResourceInternal(ResourceLoadInfoBase *loadInfo)
Load a resource using the given loadInfo.
ResourceHandleBase getResourceHandle(ResourceId id) const
Get existing resource handle.
void queueResource(ResourceLoadInfoBase *loadInfo)
Re-queue the given loadInfo to defer loading to the next time the processLoading() phase is performed...
Log implementation class.
Definition: LogManager.h:139
constexpr bool empty() const noexcept
Check if the string is empty.
Definition: StringView.h:122
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
ResourceFlags
Flags for describing resource attributes.
Definition: ResourceBase.h:27
@ Watched
The resource on disk is being watched for changes.
@ Success
Resource activated successfully.
@ Postponed
Resource activation postponed, retry later.
@ Failure
Resource activation failed.
@ DoNotStoreSource
Don't store the source.
@ ForceUnique
Force unique resource load when source resolves to existing resource.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62
STL namespace.
Pool used to store elements of ElementType.
Definition: Pool.h:17
Base class for engine resources.
Definition: ResourceBase.h:107
void unsetFlag(ResourceFlags flag)
Unset the given flag.
Definition: ResourceBase.h:236
void setFlag(ResourceFlags flags)
Set the given resource flag.
Definition: ResourceBase.h:225
uint32_t getSlot() const
Gets the slot where the resource is tracked internally.
Definition: ResourceBase.h:386
ResourceInfo * info
Additional resource info structure.
Definition: ResourceBase.h:401
bool isSet(ResourceFlags flag) const
Check if the given flag is currently set.
Definition: ResourceBase.h:245
void setId(ResourceId resourceId)
Set the resource id of the resource.
Definition: ResourceBase.h:202
void setSlot(uint32_t slot)
Sets the slot at which the resource is internally tracked.
Definition: ResourceBase.h:383
uint32_t referenceCount() const
Get the current reference count.
Definition: ResourceBase.h:360
void setOwner(IResourceManager *owner)
Sets the owner of this resource instance.
Definition: ResourceBase.h:287
Resource handle base class handling reference counting of resources derived from ResourceBase.
static const ResourceHandleBase NoHandle
Provided as shorthand for empty resource handles.
std::string resourcePath
Resource path. Used to locate resource.
std::string resourceName
Desired resource name. If no name is given, a default name will be chosen.
ResourceId resourceId
Unique resource identifier. Must be unique among resources of the same kind.
ResourceFlags resourceFlags
Desired resource flags. Flags will be validated and, if possible, applied to the loaded resource.
ResourceHandleBase handle
Handle to resource structure for holding actual resource data.
ResourceLoadFlags loadFlags
Desired loading flags. Used to specify how the resource will be loaded.
Mutex loadingMutex
Guard when loading resource. Nesting: resourceMutex -> loadingMutex.
Mutex orphanedMutex
Local lock for orphanedResources. Must not call anything acquiring locks.
Collections::Pool< ResourceInfo > resourceInfos
guarded by resourceMutex
std::unordered_map< size_t, ResourceBase * > resourcesBySource
guarded by resourceMutex
std::queue< ResourceLoadInfoBase * > queued
guarded by loadingMutex
std::unordered_set< ResourceHandleBase > updatedResources
guarded by updatedMutex
ResourcePointerMap allocatedResources
guarded by resourceMutex
std::queue< ResourceLoadInfoBase * > loading
guarded by loadingMutex
std::vector< ResourceBase * > orphanedResources
guarded by orphanedMutex
std::unordered_map< ResourceId, ResourceHandleBase > resourcesById
guarded by resourceMutex
Mutex resourceMutex
nesting: updatedMutex -> resourceMutex
Structure to hold a set of pointers with constant time insertion and removal.
std::span< const PointerEntry > view() const
uint32_t insert(ResourceBase *ptr)