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/ResourceFileSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/FileArchive.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/FileArchive.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/ResourcePackage.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/ResourcePackage.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Resources/ResourceDependencyGraph.h
) )
target_include_directories(XCEngine PUBLIC 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

View File

@@ -22,6 +22,7 @@ set(RESOURCES_TEST_SOURCES
test_shader_loader.cpp test_shader_loader.cpp
test_material_loader.cpp test_material_loader.cpp
test_resource_package.cpp test_resource_package.cpp
test_resource_dependency.cpp
) )
add_executable(xcengine_resources_tests ${RESOURCES_TEST_SOURCES}) add_executable(xcengine_resources_tests ${RESOURCES_TEST_SOURCES})

View File

@@ -0,0 +1,131 @@
#include <gtest/gtest.h>
#include <XCEngine/Resources/ResourceDependencyGraph.h>
#include <XCEngine/Resources/ResourceTypes.h>
using namespace XCEngine::Resources;
using namespace XCEngine::Containers;
namespace {
TEST(ResourceDependencyGraph, DefaultConstructor) {
ResourceDependencyGraph graph;
}
TEST(ResourceDependencyGraph, AddNode) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Texture);
EXPECT_TRUE(graph.HasNode(ResourceGUID(1)));
}
TEST(ResourceDependencyGraph, AddMultipleNodes) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Texture);
graph.AddNode(ResourceGUID(2), ResourceType::Mesh);
graph.AddNode(ResourceGUID(3), ResourceType::Material);
EXPECT_TRUE(graph.HasNode(ResourceGUID(1)));
EXPECT_TRUE(graph.HasNode(ResourceGUID(2)));
EXPECT_TRUE(graph.HasNode(ResourceGUID(3)));
}
TEST(ResourceDependencyGraph, RemoveNode) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Texture);
EXPECT_TRUE(graph.HasNode(ResourceGUID(1)));
graph.RemoveNode(ResourceGUID(1));
EXPECT_FALSE(graph.HasNode(ResourceGUID(1)));
}
TEST(ResourceDependencyGraph, AddDependency) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Material);
graph.AddNode(ResourceGUID(2), ResourceType::Texture);
graph.AddDependency(ResourceGUID(1), ResourceGUID(2));
auto deps = graph.GetDependencies(ResourceGUID(1));
EXPECT_EQ(deps.Size(), 1u);
EXPECT_EQ(deps[0], ResourceGUID(2));
}
TEST(ResourceDependencyGraph, GetDependents) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Material);
graph.AddNode(ResourceGUID(2), ResourceType::Texture);
graph.AddDependency(ResourceGUID(1), ResourceGUID(2));
auto dependents = graph.GetDependents(ResourceGUID(2));
EXPECT_EQ(dependents.Size(), 1u);
EXPECT_EQ(dependents[0], ResourceGUID(1));
}
TEST(ResourceDependencyGraph, GetAllDependencies) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Material);
graph.AddNode(ResourceGUID(2), ResourceType::Texture);
graph.AddNode(ResourceGUID(3), ResourceType::Shader);
graph.AddDependency(ResourceGUID(1), ResourceGUID(2));
graph.AddDependency(ResourceGUID(1), ResourceGUID(3));
auto allDeps = graph.GetAllDependencies(ResourceGUID(1));
EXPECT_EQ(allDeps.Size(), 2u);
}
TEST(ResourceDependencyGraph, RefCount) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Texture);
EXPECT_EQ(graph.GetRefCount(ResourceGUID(1)), 0u);
graph.IncrementRefCount(ResourceGUID(1));
EXPECT_EQ(graph.GetRefCount(ResourceGUID(1)), 1u);
graph.IncrementRefCount(ResourceGUID(1));
EXPECT_EQ(graph.GetRefCount(ResourceGUID(1)), 2u);
graph.DecrementRefCount(ResourceGUID(1));
EXPECT_EQ(graph.GetRefCount(ResourceGUID(1)), 1u);
}
TEST(ResourceDependencyGraph, Unload) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Texture);
EXPECT_TRUE(graph.Unload(ResourceGUID(1)));
graph.IncrementRefCount(ResourceGUID(1));
EXPECT_FALSE(graph.Unload(ResourceGUID(1)));
}
TEST(ResourceDependencyGraph, Clear) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Texture);
graph.AddNode(ResourceGUID(2), ResourceType::Mesh);
EXPECT_TRUE(graph.HasNode(ResourceGUID(1)));
EXPECT_TRUE(graph.HasNode(ResourceGUID(2)));
graph.Clear();
EXPECT_FALSE(graph.HasNode(ResourceGUID(1)));
EXPECT_FALSE(graph.HasNode(ResourceGUID(2)));
}
TEST(ResourceDependencyGraph, RemoveDependency) {
ResourceDependencyGraph graph;
graph.AddNode(ResourceGUID(1), ResourceType::Material);
graph.AddNode(ResourceGUID(2), ResourceType::Texture);
graph.AddDependency(ResourceGUID(1), ResourceGUID(2));
auto deps = graph.GetDependencies(ResourceGUID(1));
EXPECT_EQ(deps.Size(), 1u);
graph.RemoveDependency(ResourceGUID(1), ResourceGUID(2));
deps = graph.GetDependencies(ResourceGUID(1));
EXPECT_EQ(deps.Size(), 0u);
}
} // namespace