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:
@@ -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
|
||||||
|
|||||||
56
engine/include/XCEngine/Resources/ResourceDependencyGraph.h
Normal file
56
engine/include/XCEngine/Resources/ResourceDependencyGraph.h
Normal 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
|
||||||
224
engine/src/Resources/ResourceDependencyGraph.cpp
Normal file
224
engine/src/Resources/ResourceDependencyGraph.cpp
Normal 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
|
||||||
@@ -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})
|
||||||
|
|||||||
131
tests/Resources/test_resource_dependency.cpp
Normal file
131
tests/Resources/test_resource_dependency.cpp
Normal 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
|
||||||
Reference in New Issue
Block a user