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 storage->resourcesBySource[code] = resource;
341 resource->unsetFlag(ResourceFlags::Queued);
342 resource->setLoading();
344 if (loadInfo->shouldAutoReload() && loadInfo->protocol == ResourceProtocol::File) {
349 if (context->watcher->watchFile(path, callback)) {
355 LockGuard loadingLock(storage->loadingMutex);
357 storage->loading.push(loadInfo);
365 UniqueLock loadingLock(storage->loadingMutex);
367 const size_t maxLoaded = 1000;
371 while (storage->loading.size() && loaded++ < maxLoaded) {
372 auto loadInfo = std::move(storage->loading.front());
373 storage->loading.pop();
375 loadInfo->
handle->setLoading();
378 loadingLock.unlock();
379 handleLoadInternal(loadInfo);
383 while (storage->queued.size()) {
384 storage->loading.push(std::move(storage->queued.front()));
385 storage->queued.pop();
388 loadingLock.unlock();
389 postProcessLoading();
394 auto * resource = loadInfo->
handle.get();
396 resource->
unsetFlag(ResourceFlags::Loading);
398 if (resource->hasFailedLoad()) {
399 handleFailedLoadInternal(loadInfo);
400 context->engine->invokeResourceLoadCallback((
int)resource->getType(), loadInfo->
resourceId, 1);
404 resource->setFlag(ResourceFlags::Loaded);
405 resource->setChanged();
407 context->engine->invokeResourceLoadCallback((
int)resource->getType(), loadInfo->
resourceId, 0);
410 context->engine->setDirty();
411 destroyLoadInfoInternal(loadInfo);
414void Cogs::Core::ResourceManagerBase::activateResources()
416 std::vector<ResourceHandleBase> updated;
418 const int quota = getUpdateQuota();
421 LockGuard updatedLock(storage->updatedMutex);
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);
430 updated.assign(storage->updatedResources.begin(), storage->updatedResources.end());
432 storage->updatedResources.clear();
436 for (
auto & r : updated) {
437 auto resource = r.get();
439 if (r->hasChanged()) {
440 const auto result = handleActivationInternal(r, resource);
441 resource->unsetFlag(ResourceFlags::Changed);
445 resource->setFlag(ResourceFlags::FailedActivation);
450 resource->setChanged();
454 resource->setResident();
456 if (resource->isDependency()) {
459 context->engine->setDirty();
468void Cogs::Core::ResourceManagerBase::fillDeletionQueue(std::vector<ResourceBase *> & deletion)
470 const int quota = getUpdateQuota();
473 LockGuard orphanedLock(storage->orphanedMutex);
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);
479 deletion.assign(std::begin(storage->orphanedResources), std::end(storage->orphanedResources));
480 storage->orphanedResources.clear();
487 std::vector<ResourceBase *> deletion;
489 fillDeletionQueue(deletion);
492 LockGuard resourceLock(storage->resourceMutex);
494 for (
auto & resource : deletion) {
496 assert(resource->referenceCount() == 0 &&
"Owned resource may not be destroyed.");
498 if (resource->referenceCount() > 0) {
499 LOG_WARNING(logger,
"Cannot destroy resource with reference count %u.", resource->referenceCount());
508void Cogs::Core::ResourceManagerBase::safeDestroy(
ResourceBase * resource)
510 LockGuard lock(storage->resourceMutex);
519 LOG_WARNING(logger,
"Resurrecting resource.");
523 storage->allocatedResources.remove(resource->
getSlot());
524 resource->
setSlot(ResourcePointerMap::NoSlot);
526 if (!resource->isProxy()) {
527 handleDeletionInternal(resource);
530 if (!resource->getSource().
empty()) {
531 storage->resourcesBySource.erase(
Cogs::hash(resource->getSource()));
535 context->watcher->unwatchFile(resource->getSource());
540 resource->setFlags(ResourceFlags::Deleted);
542 if (resource->
info) {
543 storage->resourceInfos.destroy(resource->
info);
544 resource->
info =
nullptr;
547 destroyInternal(resource);
550void Cogs::Core::ResourceManagerBase::destroyLocked(
ResourceBase * resource)
552 LockGuard lock(storage->resourceMutex);
554 destroyInternalLocked(resource);
559 return storage->nextResourceId++;
562void Cogs::Core::ResourceManagerBase::resourceChanged(
ResourceBase * resource)
564 if (resource->isProxy())
return;
566 LockGuard updatedLock(storage->updatedMutex);
572 storage->updatedResources.insert(generateHandle(resource));
575void Cogs::Core::ResourceManagerBase::resourceDeleted(ResourceBase * resource)
577 LockGuard orphanedLock(storage->orphanedMutex);
581 storage->orphanedResources.push_back(resource);
586 LockGuard resourceLock(storage->resourceMutex);
590 storage->resourcesById[id] = resource;
597 storage->queued.push(loadInfo);
602 LockGuard lock(storage->resourceMutex);
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);
615 LockGuard lock(storage->resourceMutex);
616 return storage->resourcesById.contains(
id);
619bool Cogs::Core::ResourceManagerBase::shouldAutoReload()
const
621 return context->variables->get(
"resources.autoReload",
false);
624std::vector<Cogs::Core::ResourceBase *> Cogs::Core::ResourceManagerBase::getAllocatedResources()
const
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);
641 LockGuard resourceLock(storage->resourceMutex);
643 return getOrCreateInternal(resourceId);
648 if (resourceId != NoResourceId) {
649 auto existingIt = storage->resourcesById.find(resourceId);
651 if (existingIt != storage->resourcesById.end()) {
652 return generateHandle(existingIt->second.get());
654 auto handle = createResourceInternal();
656 handle.get()->setId(resourceId);
657 storage->resourcesById[resourceId] = handle;
662 return createResourceInternal();
668 LockGuard resourceLock(storage->resourceMutex);
670 for (
const auto & r : storage->allocatedResources.view()) {
671 if (r.resource && name == r.resource->getName()) {
672 if (!r.resource->isOrphaned()) {
676 LOG_TRACE(logger,
"getByName: Ignoring orphaned resource: %.*s .", StringViewFormat(name));
686 LockGuard resourceLock(storage->resourceMutex);
688 auto foundIt = storage->resourcesBySource.find(
Cogs::hash(source));
690 if (foundIt != storage->resourcesBySource.end()) {
691 if (!foundIt->second->isOrphaned()) {
692 return foundIt->second;
700 LockGuard resourceLock(storage->resourceMutex);
702 return createResourceInternal();
707 auto resource = createInternal();
709 resource->setInfo(storage->resourceInfos.create());
711 resource->setType(resourceType);
712 resource->setOwner(
this);
713 resource->setInitialized();
716 resource->setSlot(storage->allocatedResources.insert(resource));
718 return ResourceHandleBase(resource);
723 return ResourceHandleBase(resource);
726size_t Cogs::Core::ResourceManagerBase::updatedResourceCount()
728 LockGuard lock(storage->updatedMutex);
729 return storage->updatedResources.size();
732size_t Cogs::Core::ResourceManagerBase::orphanedResourceCount()
734 LockGuard lock(storage->orphanedMutex);
735 return storage->orphanedResources.size();
738size_t Cogs::Core::ResourceManagerBase::allocatedResourceCount()
740 LockGuard lock(storage->resourceMutex);
741 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.
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)