1#include "ResourceManagerBase.h"
4#include "ResourceStore.h"
6#include "Services/Variables.h"
8#include "Foundation/Collections/Pool.h"
9#include "Foundation/Logging/Logger.h"
10#include "Foundation/Platform/FileSystemWatcher.h"
11#include "Foundation/Platform/IO.h"
14#include <unordered_set>
15#include <unordered_map>
32 static constexpr uint32_t NoSlot =
static_cast<uint32_t
>(-1);
33 static constexpr uint32_t NotFree =
static_cast<uint32_t
>(-2);
36 uint32_t next = NoSlot;
48 free = slots[free].next;
51 slot =
static_cast<uint32_t
>(slots.size());
56 slots[slot].next = NotFree;
57 slots[slot].resource = ptr;
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.");
73 slots[slot].next = free;
74 slots[slot].resource =
nullptr;
86 std::span<const PointerEntry>
view()
const
92 std::vector<PointerEntry> slots;
93 uint32_t free = NoSlot;
104 std::queue<ResourceLoadInfoBase *>
queued;
124 Atomic<ResourceId> nextResourceId = 1;
128Cogs::Core::ResourceManagerBase::ResourceManagerBase(Context * context) :
129 storage(
std::make_unique<ResourceManagerBaseStorage>()),
135Cogs::Core::ResourceManagerBase::~ResourceManagerBase()
137 assert(storage->updatedResources.size() == 0 &&
"Updated resources must be cleared before manager is destructed.");
140void Cogs::Core::ResourceManagerBase::initialize()
145 LockGuard lock(storage->updatedMutex);
146 LockGuard rlock(storage->resourceMutex);
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));
158void Cogs::Core::ResourceManagerBase::clear()
160 storage->nextResourceId = 1;
165void Cogs::Core::ResourceManagerBase::reportLeaks(std::string_view typeName)
167 if (context && context->variables->get(
"resources.reportLeaks",
false)) {
168 LockGuard lock(storage->resourceMutex);
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());
181 size_t infos, ids, sources, allocated;
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();
189 size_t loading, queued;
191 LockGuard guard(storage->loadingMutex);
192 loading = storage->loading.size();
193 queued = storage->queued.size();
197 LockGuard guard(storage->updatedMutex);
198 updated = storage->updatedResources.size();
202 LockGuard guard(storage->orphanedMutex);
203 orphaned = storage->orphanedResources.size();
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);
212 return std::string();
218 if (!context->resourceStore->hasResource(loadInfo->
resourcePath)) {
220 context->resourceStore->preloadResources({ loadInfo->
resourcePath });
225 queueResource(loadInfo);
233void Cogs::Core::ResourceManagerBase::clearUpdated()
236 LockGuard lock(storage->updatedMutex);
238 storage->updatedResources.clear();
241 LockGuard lock(storage->resourceMutex);
243 storage->resourcesById.clear();
246int Cogs::Core::ResourceManagerBase::getUpdateQuota()
const
248 return context->variables->get(
"resources.globalFrameUpdateQuota", 0);
253 LockGuard lock(storage->resourceMutex);
255 auto found = storage->resourcesById.find(resourceId);
257 if (found == storage->resourcesById.end()) {
258 LOG_ERROR(logger,
"Could not release resource %d.", resourceId);
266 storage->resourcesById.erase(found);
272 if (loadInfo->isReload()) {
274 LOG_ERROR(logger,
"Cannot reload invalid (empty) resource.");
279 loadInfo->
handle->setLoading();
282 LockGuard loadingLock(storage->loadingMutex);
284 storage->loading.push(loadInfo);
291 auto [path, protocol] = context->resourceStore->getResourceLocation(loadInfo->
resourcePath);
292 loadInfo->protocol = protocol;
294 LockGuard resourceLock(storage->resourceMutex);
301 auto found = storage->resourcesBySource.find(code);
303 if (found != storage->resourcesBySource.end()) {
304 if (found->second->referenceCount() > 0) {
305 destroyLoadInfoInternal(loadInfo);
306 return found->second;
314 if (loadInfo->
resourceName.empty() && !loadInfo->noDefaultName()) {
318 if (shouldAutoReload()) {
319 loadInfo->
loadFlags |= ResourceLoadFlags::AutoReload;
327 auto resource = loadInfo->
handle.get();
329 assert(!resource->isDeleted() &&
"Resource pending deletion not valid destination.");
337 resource->setKeepStorage();
341 storage->resourcesBySource[code] = resource;
345 resource->unsetFlag(ResourceFlags::Queued);
346 resource->setLoading();
348 if (context->watcher && loadInfo->shouldAutoReload() && loadInfo->protocol == ResourceProtocol::File) {
353 if (context->watcher->watchFile(path, callback)) {
359 LockGuard loadingLock(storage->loadingMutex);
361 storage->loading.push(loadInfo);
369 UniqueLock loadingLock(storage->loadingMutex);
371 const size_t maxLoaded = 1000;
375 while (storage->loading.size() && loaded++ < maxLoaded) {
376 auto loadInfo = std::move(storage->loading.front());
377 storage->loading.pop();
379 loadInfo->
handle->setLoading();
382 loadingLock.unlock();
383 handleLoadInternal(loadInfo);
387 while (storage->queued.size()) {
388 storage->loading.push(std::move(storage->queued.front()));
389 storage->queued.pop();
392 loadingLock.unlock();
393 postProcessLoading();
398 auto * resource = loadInfo->
handle.get();
400 resource->
unsetFlag(ResourceFlags::Loading);
402 if (resource->hasFailedLoad()) {
403 handleFailedLoadInternal(loadInfo);
404 context->engine->invokeResourceLoadCallback((
int)resource->getType(), loadInfo->
resourceId, 1);
408 resource->setFlag(ResourceFlags::Loaded);
409 resource->setChanged();
411 context->engine->invokeResourceLoadCallback((
int)resource->getType(), loadInfo->
resourceId, 0);
414 context->engine->setDirty();
415 destroyLoadInfoInternal(loadInfo);
418void Cogs::Core::ResourceManagerBase::activateResources()
420 std::vector<ResourceHandleBase> updated;
422 const int quota = getUpdateQuota();
425 LockGuard updatedLock(storage->updatedMutex);
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);
434 updated.assign(storage->updatedResources.begin(), storage->updatedResources.end());
436 storage->updatedResources.clear();
440 for (
auto & r : updated) {
441 auto resource = r.get();
443 if (r->hasChanged()) {
444 const auto result = handleActivationInternal(r, resource);
445 resource->unsetFlag(ResourceFlags::Changed);
449 resource->setFlag(ResourceFlags::FailedActivation);
454 resource->setChanged();
458 resource->setResident();
460 if (resource->isDependency()) {
463 context->engine->setDirty();
472void Cogs::Core::ResourceManagerBase::fillDeletionQueue(std::vector<ResourceBase *> & deletion)
474 const int quota = getUpdateQuota();
477 LockGuard orphanedLock(storage->orphanedMutex);
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);
483 deletion.assign(std::begin(storage->orphanedResources), std::end(storage->orphanedResources));
484 storage->orphanedResources.clear();
491 std::vector<ResourceBase *> deletion;
493 fillDeletionQueue(deletion);
496 LockGuard resourceLock(storage->resourceMutex);
498 for (
auto & resource : deletion) {
500 assert(resource->referenceCount() == 0 &&
"Owned resource may not be destroyed.");
502 if (resource->referenceCount() > 0) {
503 LOG_WARNING(logger,
"Cannot destroy resource with reference count %u.", resource->referenceCount());
512void Cogs::Core::ResourceManagerBase::safeDestroy(
ResourceBase * resource)
514 LockGuard lock(storage->resourceMutex);
523 LOG_WARNING(logger,
"Resurrecting resource.");
527 storage->allocatedResources.remove(resource->
getSlot());
528 resource->
setSlot(ResourcePointerMap::NoSlot);
530 if (!resource->isProxy()) {
531 handleDeletionInternal(resource);
534 if (!resource->getSource().
empty()) {
535 storage->resourcesBySource.erase(
Cogs::hash(resource->getSource()));
539 context->watcher->unwatchFile(resource->getSource());
544 resource->setFlags(ResourceFlags::Deleted);
546 if (resource->
info) {
547 storage->resourceInfos.destroy(resource->
info);
548 resource->
info =
nullptr;
551 destroyInternal(resource);
554void Cogs::Core::ResourceManagerBase::destroyLocked(
ResourceBase * resource)
556 LockGuard lock(storage->resourceMutex);
558 destroyInternalLocked(resource);
563 return storage->nextResourceId++;
566void Cogs::Core::ResourceManagerBase::resourceChanged(
ResourceBase * resource)
568 if (resource->isProxy())
return;
570 LockGuard updatedLock(storage->updatedMutex);
576 storage->updatedResources.insert(generateHandle(resource));
579void Cogs::Core::ResourceManagerBase::resourceDeleted(ResourceBase * resource)
581 LockGuard orphanedLock(storage->orphanedMutex);
585 storage->orphanedResources.push_back(resource);
590 LockGuard resourceLock(storage->resourceMutex);
594 storage->resourcesById[id] = resource;
601 storage->queued.push(loadInfo);
606 LockGuard lock(storage->resourceMutex);
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);
619 LockGuard lock(storage->resourceMutex);
620 return storage->resourcesById.contains(
id);
623bool Cogs::Core::ResourceManagerBase::shouldAutoReload()
const
625 return context->variables->get(
"resources.autoReload",
false);
628std::vector<Cogs::Core::ResourceBase *> Cogs::Core::ResourceManagerBase::getAllocatedResources()
const
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);
645 LockGuard resourceLock(storage->resourceMutex);
647 return getOrCreateInternal(resourceId);
652 if (resourceId != NoResourceId) {
653 auto existingIt = storage->resourcesById.find(resourceId);
655 if (existingIt != storage->resourcesById.end()) {
656 return generateHandle(existingIt->second.get());
658 auto handle = createResourceInternal();
660 handle.get()->setId(resourceId);
661 storage->resourcesById[resourceId] = handle;
666 return createResourceInternal();
672 LockGuard resourceLock(storage->resourceMutex);
674 for (
const auto & r : storage->allocatedResources.view()) {
675 if (r.resource && name == r.resource->getName()) {
676 if (!r.resource->isOrphaned()) {
680 LOG_TRACE(logger,
"getByName: Ignoring orphaned resource: %.*s .", StringViewFormat(name));
690 LockGuard resourceLock(storage->resourceMutex);
692 auto foundIt = storage->resourcesBySource.find(
Cogs::hash(source));
694 if (foundIt != storage->resourcesBySource.end()) {
695 if (!foundIt->second->isOrphaned()) {
696 return foundIt->second;
704 LockGuard resourceLock(storage->resourceMutex);
706 return createResourceInternal();
711 auto resource = createInternal();
713 resource->setInfo(storage->resourceInfos.create());
715 resource->setType(resourceType);
716 resource->setOwner(
this);
717 resource->setInitialized();
720 resource->setSlot(storage->allocatedResources.insert(resource));
722 return ResourceHandleBase(resource);
727 return ResourceHandleBase(resource);
730size_t Cogs::Core::ResourceManagerBase::updatedResourceCount()
732 LockGuard lock(storage->updatedMutex);
733 return storage->updatedResources.size();
736size_t Cogs::Core::ResourceManagerBase::orphanedResourceCount()
738 LockGuard lock(storage->orphanedMutex);
739 return storage->orphanedResources.size();
742size_t Cogs::Core::ResourceManagerBase::allocatedResourceCount()
744 LockGuard lock(storage->resourceMutex);
745 return storage->allocatedResources.size();
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.
constexpr bool empty() const noexcept
Check if the string is empty.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
ResourceFlags
Flags for describing resource attributes.
@ 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
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Pool used to store elements of ElementType.
Base class for engine resources.
void unsetFlag(ResourceFlags flag)
Unset the given flag.
void setFlag(ResourceFlags flags)
Set the given resource flag.
uint32_t getSlot() const
Gets the slot where the resource is tracked internally.
ResourceInfo * info
Additional resource info structure.
bool isSet(ResourceFlags flag) const
Check if the given flag is currently set.
void setId(ResourceId resourceId)
Set the resource id of the resource.
void setSlot(uint32_t slot)
Sets the slot at which the resource is internally tracked.
uint32_t referenceCount() const
Get the current reference count.
void setOwner(IResourceManager *owner)
Sets the owner of this resource instance.
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
Mutex updatedMutex
Outer lock.
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.
void remove(uint32_t slot)
std::span< const PointerEntry > view() const
uint32_t insert(ResourceBase *ptr)