From d2585f14b34410c2b7bc8c61067d693a2a1bd1a6 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 18 Mar 2026 01:13:02 +0800 Subject: [PATCH] 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 --- engine/CMakeLists.txt | 2 + .../Resources/ResourceDependencyGraph.h | 56 +++++ .../src/Resources/ResourceDependencyGraph.cpp | 224 ++++++++++++++++++ tests/Resources/CMakeLists.txt | 1 + tests/Resources/test_resource_dependency.cpp | 131 ++++++++++ 5 files changed, 414 insertions(+) create mode 100644 engine/include/XCEngine/Resources/ResourceDependencyGraph.h create mode 100644 engine/src/Resources/ResourceDependencyGraph.cpp create mode 100644 tests/Resources/test_resource_dependency.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 58fedf9b..d5a65855 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -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 diff --git a/engine/include/XCEngine/Resources/ResourceDependencyGraph.h b/engine/include/XCEngine/Resources/ResourceDependencyGraph.h new file mode 100644 index 00000000..19be2144 --- /dev/null +++ b/engine/include/XCEngine/Resources/ResourceDependencyGraph.h @@ -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 dependencies; + Containers::Array 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 GetDependencies(ResourceGUID guid) const; + Containers::Array GetDependents(ResourceGUID guid) const; + Containers::Array GetAllDependencies(ResourceGUID guid) const; + + void IncrementRefCount(ResourceGUID guid); + void DecrementRefCount(ResourceGUID guid); + Core::uint32 GetRefCount(ResourceGUID guid) const; + + bool HasCircularDependency(ResourceGUID guid, Containers::Array& outCycle) const; + + Containers::Array TopologicalSort() const; + + bool Unload(ResourceGUID guid); + + void Clear(); + + bool HasNode(ResourceGUID guid) const; + +private: + bool HasCircularDependencyInternal(ResourceGUID guid, Containers::HashMap& visited, + Containers::Array& path, Containers::Array& outCycle) const; + + Containers::HashMap m_nodes; +}; + +} // namespace Resources +} // namespace XCEngine diff --git a/engine/src/Resources/ResourceDependencyGraph.cpp b/engine/src/Resources/ResourceDependencyGraph.cpp new file mode 100644 index 00000000..d713c03d --- /dev/null +++ b/engine/src/Resources/ResourceDependencyGraph.cpp @@ -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 ResourceDependencyGraph::GetDependencies(ResourceGUID guid) const { + Containers::Array result; + + const DependencyNode* node = m_nodes.Find(guid); + if (node) { + result = node->dependencies; + } + + return result; +} + +Containers::Array ResourceDependencyGraph::GetDependents(ResourceGUID guid) const { + Containers::Array result; + + const DependencyNode* node = m_nodes.Find(guid); + if (node) { + result = node->dependents; + } + + return result; +} + +Containers::Array ResourceDependencyGraph::GetAllDependencies(ResourceGUID guid) const { + Containers::Array result; + Containers::Array 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& outCycle) const { + Containers::Array path; + Containers::Array 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 ResourceDependencyGraph::TopologicalSort() const { + Containers::Array 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& visited, + Containers::Array& path, Containers::Array& outCycle) const { + return false; +} + +} // namespace Resources +} // namespace XCEngine diff --git a/tests/Resources/CMakeLists.txt b/tests/Resources/CMakeLists.txt index ca2537af..12526894 100644 --- a/tests/Resources/CMakeLists.txt +++ b/tests/Resources/CMakeLists.txt @@ -22,6 +22,7 @@ set(RESOURCES_TEST_SOURCES test_shader_loader.cpp test_material_loader.cpp test_resource_package.cpp + test_resource_dependency.cpp ) add_executable(xcengine_resources_tests ${RESOURCES_TEST_SOURCES}) diff --git a/tests/Resources/test_resource_dependency.cpp b/tests/Resources/test_resource_dependency.cpp new file mode 100644 index 00000000..d4d527f1 --- /dev/null +++ b/tests/Resources/test_resource_dependency.cpp @@ -0,0 +1,131 @@ +#include +#include +#include + +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