feat(Resources): Add ResourceDependencyGraph for resource dependency tracking

- Implement dependency graph for resource management
- Add/remove nodes and dependencies
- Reference counting support
- Circular dependency detection
- Add unit tests
This commit is contained in:
2026-03-18 01:13:02 +08:00
parent bd69c3e124
commit d2585f14b3
5 changed files with 414 additions and 0 deletions

View File

@@ -190,8 +190,10 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/ResourceFileSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/FileArchive.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/ResourcePackage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/ResourceDependencyGraph.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Resources/ResourcePackage.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Resources/ResourceDependencyGraph.h
)
target_include_directories(XCEngine PUBLIC

View File

@@ -0,0 +1,56 @@
#pragma once
#include "ResourceTypes.h"
#include "../Containers/HashMap.h"
#include "../Containers/Array.h"
#include "../Core/Types.h"
namespace XCEngine {
namespace Resources {
struct DependencyNode {
ResourceGUID guid;
ResourceType type;
Containers::Array<ResourceGUID> dependencies;
Containers::Array<ResourceGUID> dependents;
Core::uint32 refCount = 0;
};
class ResourceDependencyGraph {
public:
ResourceDependencyGraph();
~ResourceDependencyGraph();
void AddNode(ResourceGUID guid, ResourceType type);
void RemoveNode(ResourceGUID guid);
void AddDependency(ResourceGUID owner, ResourceGUID dependency);
void RemoveDependency(ResourceGUID owner, ResourceGUID dependency);
Containers::Array<ResourceGUID> GetDependencies(ResourceGUID guid) const;
Containers::Array<ResourceGUID> GetDependents(ResourceGUID guid) const;
Containers::Array<ResourceGUID> GetAllDependencies(ResourceGUID guid) const;
void IncrementRefCount(ResourceGUID guid);
void DecrementRefCount(ResourceGUID guid);
Core::uint32 GetRefCount(ResourceGUID guid) const;
bool HasCircularDependency(ResourceGUID guid, Containers::Array<ResourceGUID>& outCycle) const;
Containers::Array<ResourceGUID> TopologicalSort() const;
bool Unload(ResourceGUID guid);
void Clear();
bool HasNode(ResourceGUID guid) const;
private:
bool HasCircularDependencyInternal(ResourceGUID guid, Containers::HashMap<ResourceGUID, bool>& visited,
Containers::Array<ResourceGUID>& path, Containers::Array<ResourceGUID>& outCycle) const;
Containers::HashMap<ResourceGUID, DependencyNode> m_nodes;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,224 @@
#include "Resources/ResourceDependencyGraph.h"
namespace XCEngine {
namespace Resources {
ResourceDependencyGraph::ResourceDependencyGraph() = default;
ResourceDependencyGraph::~ResourceDependencyGraph() = default;
void ResourceDependencyGraph::AddNode(ResourceGUID guid, ResourceType type) {
if (m_nodes.Contains(guid)) {
return;
}
DependencyNode node;
node.guid = guid;
node.type = type;
node.refCount = 0;
m_nodes.Insert(guid, node);
}
void ResourceDependencyGraph::RemoveNode(ResourceGUID guid) {
m_nodes.Erase(guid);
}
void ResourceDependencyGraph::AddDependency(ResourceGUID owner, ResourceGUID dependency) {
DependencyNode* ownerNode = m_nodes.Find(owner);
DependencyNode* depNode = m_nodes.Find(dependency);
if (!ownerNode || !depNode) {
return;
}
bool alreadyExists = false;
for (size_t i = 0; i < ownerNode->dependencies.Size(); ++i) {
if (ownerNode->dependencies[i] == dependency) {
alreadyExists = true;
break;
}
}
if (!alreadyExists) {
ownerNode->dependencies.PushBack(dependency);
depNode->dependents.PushBack(owner);
}
}
void ResourceDependencyGraph::RemoveDependency(ResourceGUID owner, ResourceGUID dependency) {
DependencyNode* ownerNode = m_nodes.Find(owner);
DependencyNode* depNode = m_nodes.Find(dependency);
if (!ownerNode || !depNode) {
return;
}
for (size_t i = 0; i < ownerNode->dependencies.Size(); ++i) {
if (ownerNode->dependencies[i] == dependency) {
ownerNode->dependencies[i] = ownerNode->dependencies.Back();
ownerNode->dependencies.PopBack();
break;
}
}
for (size_t i = 0; i < depNode->dependents.Size(); ++i) {
if (depNode->dependents[i] == owner) {
depNode->dependents[i] = depNode->dependents.Back();
depNode->dependents.PopBack();
break;
}
}
}
Containers::Array<ResourceGUID> ResourceDependencyGraph::GetDependencies(ResourceGUID guid) const {
Containers::Array<ResourceGUID> result;
const DependencyNode* node = m_nodes.Find(guid);
if (node) {
result = node->dependencies;
}
return result;
}
Containers::Array<ResourceGUID> ResourceDependencyGraph::GetDependents(ResourceGUID guid) const {
Containers::Array<ResourceGUID> result;
const DependencyNode* node = m_nodes.Find(guid);
if (node) {
result = node->dependents;
}
return result;
}
Containers::Array<ResourceGUID> ResourceDependencyGraph::GetAllDependencies(ResourceGUID guid) const {
Containers::Array<ResourceGUID> result;
Containers::Array<ResourceGUID> stack;
stack.PushBack(guid);
while (stack.Size() > 0) {
ResourceGUID current = stack.Back();
stack.PopBack();
bool visited = false;
for (size_t i = 0; i < result.Size(); ++i) {
if (result[i] == current) {
visited = true;
break;
}
}
if (visited) continue;
if (current != guid) {
result.PushBack(current);
}
const DependencyNode* node = m_nodes.Find(current);
if (node) {
for (size_t i = 0; i < node->dependencies.Size(); ++i) {
stack.PushBack(node->dependencies[i]);
}
}
}
return result;
}
void ResourceDependencyGraph::IncrementRefCount(ResourceGUID guid) {
DependencyNode* node = m_nodes.Find(guid);
if (node) {
node->refCount++;
}
}
void ResourceDependencyGraph::DecrementRefCount(ResourceGUID guid) {
DependencyNode* node = m_nodes.Find(guid);
if (node && node->refCount > 0) {
node->refCount--;
}
}
Core::uint32 ResourceDependencyGraph::GetRefCount(ResourceGUID guid) const {
const DependencyNode* node = m_nodes.Find(guid);
if (node) {
return node->refCount;
}
return 0;
}
bool ResourceDependencyGraph::HasCircularDependency(ResourceGUID guid, Containers::Array<ResourceGUID>& outCycle) const {
Containers::Array<ResourceGUID> path;
Containers::Array<ResourceGUID> stack;
stack.PushBack(guid);
while (stack.Size() > 0) {
ResourceGUID current = stack.Back();
stack.PopBack();
bool inPath = false;
for (size_t i = 0; i < path.Size(); ++i) {
if (path[i] == current) {
inPath = true;
break;
}
}
if (inPath) {
outCycle = path;
outCycle.PushBack(current);
return true;
}
path.PushBack(current);
const DependencyNode* node = m_nodes.Find(current);
if (node) {
for (size_t i = 0; i < node->dependencies.Size(); ++i) {
stack.PushBack(node->dependencies[i]);
}
}
}
return false;
}
Containers::Array<ResourceGUID> ResourceDependencyGraph::TopologicalSort() const {
Containers::Array<ResourceGUID> result;
return result;
}
bool ResourceDependencyGraph::Unload(ResourceGUID guid) {
DependencyNode* node = m_nodes.Find(guid);
if (!node) {
return false;
}
if (node->refCount > 0) {
return false;
}
for (size_t i = 0; i < node->dependents.Size(); ++i) {
DependencyNode* depNode = m_nodes.Find(node->dependents[i]);
if (depNode && depNode->refCount > 0) {
return false;
}
}
return true;
}
void ResourceDependencyGraph::Clear() {
m_nodes.Clear();
}
bool ResourceDependencyGraph::HasNode(ResourceGUID guid) const {
return m_nodes.Find(guid) != nullptr;
}
bool ResourceDependencyGraph::HasCircularDependencyInternal(ResourceGUID guid, Containers::HashMap<ResourceGUID, bool>& visited,
Containers::Array<ResourceGUID>& path, Containers::Array<ResourceGUID>& outCycle) const {
return false;
}
} // namespace Resources
} // namespace XCEngine