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->loadFlags & ResourceLoadFlags::KeepStorage) != 0) {
337 resource->setKeepStorage();
338 }
339
340 if (loadInfo->resourcePath.size()) {
341 storage->resourcesBySource[code] = resource;
342 }
343
344 resource->setFlags(loadInfo->resourceFlags);
345 resource->unsetFlag(ResourceFlags::Queued);
346 resource->setLoading();
347
348 if (context->watcher && loadInfo->shouldAutoReload() && loadInfo->protocol == ResourceProtocol::File) {
349 auto callback = [this, resource](FileSystemWatcher::Event /*e*/) mutable {
350 handleReload(ResourceHandleBase(resource));
351 };
352
353 if (context->watcher->watchFile(path, callback)) {
354 resource->setFlag(ResourceFlags::Watched);
355 }
356 }
357
358 {
359 LockGuard loadingLock(storage->loadingMutex);
360
361 storage->loading.push(loadInfo);
362 }
363
364 return loadInfo->handle;
365}
366
368{
369 UniqueLock loadingLock(storage->loadingMutex);
370
371 const size_t maxLoaded = 1000;
372
373 size_t loaded = 0;
374
375 while (storage->loading.size() && loaded++ < maxLoaded) {
376 auto loadInfo = std::move(storage->loading.front());
377 storage->loading.pop();
378
379 loadInfo->handle->setLoading();
380
381 // Must unlock to observe locking order
382 loadingLock.unlock();
383 handleLoadInternal(loadInfo);
384 loadingLock.lock();
385 }
386
387 while (storage->queued.size()) {
388 storage->loading.push(std::move(storage->queued.front()));
389 storage->queued.pop();
390 }
391
392 loadingLock.unlock();
393 postProcessLoading();
394}
395
397{
398 auto * resource = loadInfo->handle.get();
399 if (resource) {
400 resource->unsetFlag(ResourceFlags::Loading);
401
402 if (resource->hasFailedLoad()) {
403 handleFailedLoadInternal(loadInfo);
404 context->engine->invokeResourceLoadCallback((int)resource->getType(), loadInfo->resourceId, 1);
405 }
406 else {
407 if (!swapping) {
408 resource->setFlag(ResourceFlags::Loaded);
409 resource->setChanged();
410 }
411 context->engine->invokeResourceLoadCallback((int)resource->getType(), loadInfo->resourceId, 0);
412 }
413 }
414 context->engine->setDirty();
415 destroyLoadInfoInternal(loadInfo);
416}
417
418void Cogs::Core::ResourceManagerBase::activateResources()
419{
420 std::vector<ResourceHandleBase> updated;
421
422 const int quota = getUpdateQuota();
423
424 {
425 LockGuard updatedLock(storage->updatedMutex);
426
427 if (quota>0 && static_cast<size_t>(quota) < storage->updatedResources.size()) {
428 while (storage->updatedResources.size() && updated.size() < static_cast<size_t>(quota)) {
429 auto it = storage->updatedResources.begin();
430 updated.emplace_back(*it);
431 storage->updatedResources.erase(it);
432 }
433 } else {
434 updated.assign(storage->updatedResources.begin(), storage->updatedResources.end());
435
436 storage->updatedResources.clear();
437 }
438 }
439
440 for (auto & r : updated) {
441 auto resource = r.get();
442
443 if (r->hasChanged()) {
444 const auto result = handleActivationInternal(r, resource);
445 resource->unsetFlag(ResourceFlags::Changed);
446
447 switch (result) {
449 resource->setFlag(ResourceFlags::FailedActivation);
450 break;
452 // Setting the state to Changed here will automatically re-queue the resource in
453 // the updatedResources queue.
454 resource->setChanged();
455 break;
457 default:
458 resource->setResident();
459
460 if (resource->isDependency()) {
461 // Run an additional frame to let code waiting for resource activation detect the
462 // active state.
463 context->engine->setDirty();
464 }
465
466 break;
467 }
468 }
469 }
470}
471
472void Cogs::Core::ResourceManagerBase::fillDeletionQueue(std::vector<ResourceBase *> & deletion)
473{
474 const int quota = getUpdateQuota();
475
476 {
477 LockGuard orphanedLock(storage->orphanedMutex);
478
479 if (quota>0 && static_cast<size_t>(quota) < storage->orphanedResources.size()) {
480 deletion.assign(storage->orphanedResources.begin(), storage->orphanedResources.begin() + quota);
481 storage->orphanedResources.erase(storage->orphanedResources.begin(), storage->orphanedResources.begin() + quota);
482 } else {
483 deletion.assign(std::begin(storage->orphanedResources), std::end(storage->orphanedResources));
484 storage->orphanedResources.clear();
485 }
486 }
487}
488
490{
491 std::vector<ResourceBase *> deletion;
492
493 fillDeletionQueue(deletion);
494
495 {
496 LockGuard resourceLock(storage->resourceMutex);
497
498 for (auto & resource : deletion) {
499#ifdef _DEBUG
500 assert(resource->referenceCount() == 0 && "Owned resource may not be destroyed.");
501#else
502 if (resource->referenceCount() > 0) {
503 LOG_WARNING(logger, "Cannot destroy resource with reference count %u.", resource->referenceCount());
504 continue;
505 }
506#endif
507 destroy(resource);
508 }
509 }
510}
511
512void Cogs::Core::ResourceManagerBase::safeDestroy(ResourceBase * resource)
513{
514 LockGuard lock(storage->resourceMutex);
515
516 destroy(resource);
517}
518
520{
521 // If the resource has been resurrected, bail out before destruction.
522 if (resource->referenceCount()) {
523 LOG_WARNING(logger, "Resurrecting resource.");
524 return;
525 }
526
527 storage->allocatedResources.remove(resource->getSlot());
528 resource->setSlot(ResourcePointerMap::NoSlot);
529
530 if (!resource->isProxy()) {
531 handleDeletionInternal(resource);
532 }
533
534 if (!resource->getSource().empty()) {
535 storage->resourcesBySource.erase(Cogs::hash(resource->getSource()));
536 }
537
538 if (context->watcher && resource->isSet(ResourceFlags::Watched)) {
539 context->watcher->unwatchFile(resource->getSource());
540 }
541
542 resource->setOwner(nullptr);
543 resource->unsetFlag((ResourceFlags)~0);
544 resource->setFlags(ResourceFlags::Deleted);
545
546 if (resource->info) {
547 storage->resourceInfos.destroy(resource->info);
548 resource->info = nullptr;
549 }
550
551 destroyInternal(resource);
552}
553
554void Cogs::Core::ResourceManagerBase::destroyLocked(ResourceBase * resource)
555{
556 LockGuard lock(storage->resourceMutex);
557
558 destroyInternalLocked(resource);
559}
560
562{
563 return storage->nextResourceId++;
564}
565
566void Cogs::Core::ResourceManagerBase::resourceChanged(ResourceBase * resource)
567{
568 if (resource->isProxy()) return;
569
570 LockGuard updatedLock(storage->updatedMutex);
571
572 if (resource->referenceCount() == 0) {
573 return;
574 }
575
576 storage->updatedResources.insert(generateHandle(resource));
577}
578
579void Cogs::Core::ResourceManagerBase::resourceDeleted(ResourceBase * resource)
580{
581 LockGuard orphanedLock(storage->orphanedMutex);
582
583 // Resource is free and marked Orphaned.
584 // Still stored in resource lookup tables and must be checked if Orphaned before used.
585 storage->orphanedResources.push_back(resource);
586}
587
589{
590 LockGuard resourceLock(storage->resourceMutex);
591
592 resource->setId(id);
593
594 storage->resourcesById[id] = resource;
595}
596
598{
599 loadInfo->handle->setFlag(ResourceFlags::Queued);
600
601 storage->queued.push(loadInfo);
602}
603
605{
606 LockGuard lock(storage->resourceMutex);
607
608 auto it = storage->resourcesById.find(id);
609 if (id == NoResourceId || it == storage->resourcesById.end()) {
610 LOG_WARNING(logger, "Could not fetch handle for invalid id: %d", id);
612 }
613
614 return it->second;
615}
616
618{
619 LockGuard lock(storage->resourceMutex);
620 return storage->resourcesById.contains(id);
621}
622
623bool Cogs::Core::ResourceManagerBase::shouldAutoReload() const
624{
625 return context->variables->get("resources.autoReload", false);
626}
627
628std::vector<Cogs::Core::ResourceBase *> Cogs::Core::ResourceManagerBase::getAllocatedResources() const
629{
630 LockGuard lock(storage->resourceMutex);
631 auto v = storage->allocatedResources.view();
632 std::vector<Cogs::Core::ResourceBase*> resources;
633 resources.reserve(v.size());
634 for (const auto entry : v) {
635 if (entry.resource) {
636 resources.emplace_back(entry.resource);
637 }
638 }
639
640 return resources;
641}
642
643Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::getOrCreate(ResourceId resourceId)
644{
645 LockGuard resourceLock(storage->resourceMutex);
646
647 return getOrCreateInternal(resourceId);
648}
649
650Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::getOrCreateInternal(ResourceId resourceId)
651{
652 if (resourceId != NoResourceId) {
653 auto existingIt = storage->resourcesById.find(resourceId);
654
655 if (existingIt != storage->resourcesById.end()) {
656 return generateHandle(existingIt->second.get());
657 } else {
658 auto handle = createResourceInternal();
659
660 handle.get()->setId(resourceId);
661 storage->resourcesById[resourceId] = handle;
662
663 return handle;
664 }
665 } else {
666 return createResourceInternal();
667 }
668}
669
670Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::getByName(std::string_view name) const
671{
672 LockGuard resourceLock(storage->resourceMutex);
673
674 for (const auto & r : storage->allocatedResources.view()) {
675 if (r.resource && name == r.resource->getName()) {
676 if (!r.resource->isOrphaned()) {
677 return r.resource;
678 }
679 else {
680 LOG_TRACE(logger, "getByName: Ignoring orphaned resource: %.*s .", StringViewFormat(name));
681 }
682 }
683 }
684
686}
687
688Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::getBySource(std::string_view source) const
689{
690 LockGuard resourceLock(storage->resourceMutex);
691
692 auto foundIt = storage->resourcesBySource.find(Cogs::hash(source));
693
694 if (foundIt != storage->resourcesBySource.end()) {
695 if (!foundIt->second->isOrphaned()) {
696 return foundIt->second;
697 }
698 }
700}
701
702Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::createResource()
703{
704 LockGuard resourceLock(storage->resourceMutex);
705
706 return createResourceInternal();
707}
708
709Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::createResourceInternal()
710{
711 auto resource = createInternal();
712
713 resource->setInfo(storage->resourceInfos.create());
714
715 resource->setType(resourceType);
716 resource->setOwner(this);
717 resource->setInitialized();
718
719 // Track allocated resources to be able to check e.g if any are left when this instance is destructed.
720 resource->setSlot(storage->allocatedResources.insert(resource));
721
722 return ResourceHandleBase(resource);
723}
724
725Cogs::Core::ResourceHandleBase Cogs::Core::ResourceManagerBase::generateHandle(ResourceBase * resource)
726{
727 return ResourceHandleBase(resource);
728}
729
730size_t Cogs::Core::ResourceManagerBase::updatedResourceCount()
731{
732 LockGuard lock(storage->updatedMutex);
733 return storage->updatedResources.size();
734}
735
736size_t Cogs::Core::ResourceManagerBase::orphanedResourceCount()
737{
738 LockGuard lock(storage->orphanedMutex);
739 return storage->orphanedResources.size();
740}
741
742size_t Cogs::Core::ResourceManagerBase::allocatedResourceCount()
743{
744 LockGuard lock(storage->resourceMutex);
745 return storage->allocatedResources.size();
746}
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:140
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.
@ KeepStorage
Do not unload the CPU copy of this resource after uploading to the GPU.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
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)