feat(resource-manager): added working singleton resource manager

all external data should now be handled through the resource manager. This will take care of location on disk as well as ownership
This commit is contained in:
2025-03-20 14:26:44 -04:00
parent 1cc21a368b
commit 08075f5108
7 changed files with 303 additions and 0 deletions

40
src/resource/meson.build Normal file
View File

@@ -0,0 +1,40 @@
# Define the library
resourceManager_sources = files(
'private/resourceManager.cpp',
'private/resourceManagerTypes.cpp'
)
resourceManager_headers = files(
'public/resourceManager.h',
'public/resourceManagerTypes.h'
)
dependencies = [
yaml_cpp_dep,
opatio_dep,
eos_dep,
quill_dep,
config_dep,
probe_dep,
mfem_dep,
macros_dep,
meshio_dep
]
libResourceHeader_dep = declare_dependency(include_directories: include_directories('public'))
# Define the libresourceManager library so it can be linked against by other parts of the build system
libresourceManager = static_library('resourceManager',
resourceManager_sources,
include_directories: include_directories('public'),
cpp_args: ['-fvisibility=default'],
dependencies: dependencies,
install : true)
resourceManager_dep = declare_dependency(
include_directories: include_directories('public'),
link_with: libresourceManager,
dependencies: dependencies
)
# Make headers accessible
install_headers(resourceManager_headers, subdir : '4DSSE/resource')

View File

@@ -0,0 +1,68 @@
#include <iostream>
#include <vector>
#include <string>
#include <filesystem>
#include "quill/LogMacros.h"
#include "resourceManager.h"
#include "resourceManagerTypes.h"
#include "debug.h"
#include "config.h"
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
ResourceManager::ResourceManager() {
std::string defaultDataDir = TOSTRING(DATA_DIR);
m_dataDir = m_config.get<std::string>("Data:Dir", defaultDataDir);
// -- Get the index file path using filesytem to make it a system safe path
std::string indexFilePath = m_dataDir + "/index.yaml";
std::filesystem::path indexFile(indexFilePath);
m_resourceConfig.loadConfig(indexFile.string());
std::vector<std::string> assets = m_resourceConfig.keys();
for (auto key : assets ) {
load(key);
}
}
std::vector<std::string> ResourceManager::getAvaliableResources() {
std::vector<std::string> resources;
resources = m_resourceConfig.keys();
return resources;
}
const Resource& ResourceManager::getResource(const std::string &name) const {
auto it = m_resources.find(name);
if (it != m_resources.end()) {
return it->second;
}
throw std::runtime_error("Resource " + name + " not found");
}
bool ResourceManager::loadResource(std::string& name) {
return load(name);
}
bool ResourceManager::load(const std::string& name) {
const std::string resourcePath = m_dataDir + "/" + m_resourceConfig.get<std::string>(name);
std::filesystem::path resourceFile(resourcePath);
if (!std::filesystem::exists(resourceFile)) {
LOG_ERROR(m_logger, "Resource file not found: {}", resourceFile.string());
return false;
}
LOG_INFO(m_logger, "Loading resource: {}", resourceFile.string());
if (m_resources.find(name) != m_resources.end()) {
LOG_INFO(m_logger, "Resource already loaded: {}", name);
return true;
}
Resource resource = createResource(name, resourcePath);
m_resources[name] = std::move(resource);
// -- Check if the resource is already in the map
return true;
}

View File

@@ -0,0 +1,41 @@
#include <string>
#include "resourceManagerTypes.h"
#include "opatIO.h"
#include "meshIO.h"
#include "eosIO.h"
#include "debug.h"
std::string getFirstSegment(const std::string& input) {
size_t pos = input.find(':');
if (pos == std::string::npos) {
// No colon found, return the entire string
return input;
} else {
// Return substring from start to the position of the first colon
return input.substr(0, pos);
}
}
Resource createResource(const std::string& type, const std::string& path) {
static const std::unordered_map<std::string, std::function<Resource(const std::string&)>> factoryMap = {
{"opac", [](const std::string& p) { return Resource(
std::make_unique<OpatIO>(p));
}},
{"mesh", [](const std::string& p) { return Resource(
std::make_unique<MeshIO>(p));
}},
{"eos", [](const std::string& p) { return Resource(
std::make_unique<EosIO>(p));
}}
// Add more mappings as needed
};
auto it = factoryMap.find(getFirstSegment(type));
if (it != factoryMap.end()) {
return it->second(path);
} else {
throw std::invalid_argument("Unknown resource type: " + type);
}
}

View File

@@ -0,0 +1,47 @@
#ifndef RESOURCE_MANAGER_H
#define RESOURCE_MANAGER_H
#include <vector>
#include <string>
#include <stdexcept>
#include <unordered_map>
#include "resourceManagerTypes.h"
#include "config.h"
#include "probe.h"
#include "quill/LogMacros.h"
class ResourceManager {
private:
ResourceManager();
ResourceManager(const ResourceManager&) = delete;
ResourceManager& operator=(const ResourceManager&) = delete;
Config& m_config = Config::getInstance();
Probe::LogManager& m_logManager = Probe::LogManager::getInstance();
quill::Logger* m_logger = m_logManager.getLogger("log");
Config m_resourceConfig;
std::string m_dataDir;
std::unordered_map<std::string, Resource> m_resources;
bool load(const std::string& name);
public:
static ResourceManager& getInstance() {
static ResourceManager instance;
return instance;
}
std::vector<std::string> getAvaliableResources();
const Resource& getResource(const std::string &name) const;
bool loadResource(std::string& name);
std::unordered_map<std::string, bool> loadAllResources();
};
#endif // RESOURCE_MANAGER_H

View File

@@ -0,0 +1,22 @@
#ifndef RESOURCE_MANAGER_TYPES_H
#define RESOURCE_MANAGER_TYPES_H
#include <memory>
#include <variant>
#include "opatIO.h"
#include "helm.h"
#include "meshIO.h"
#include "eosIO.h"
// -- Valid resource types
using Resource = std::variant<
std::unique_ptr<OpatIO>,
std::unique_ptr<MeshIO>,
std::unique_ptr<EosIO>>;
std::string getFirstSegment(const std::string& input);
Resource createResource(const std::string& type, const std::string& path);
#endif // RESOURCE_MANAGER_TYPES_H

View File

@@ -0,0 +1,24 @@
# Test files for const
test_sources = [
'resourceManagerTest.cpp',
]
foreach test_file : test_sources
exe_name = test_file.split('.')[0]
message('Building test: ' + exe_name)
# Create an executable target for each test
test_exe = executable(
exe_name,
test_file,
dependencies: [gtest_dep, resourceManager_dep, gtest_main, macros_dep],
include_directories: include_directories('../../src/resource/public'),
install_rpath: '@loader_path/../../src' # Ensure runtime library path resolves correctly
)
# Add the executable as a test
test(
exe_name,
test_exe,
env: ['MESON_SOURCE_ROOT=' + meson.project_source_root(), 'MESON_BUILD_ROOT=' + meson.project_build_root()])
endforeach

View File

@@ -0,0 +1,61 @@
#include <gtest/gtest.h>
#include "resourceManager.h"
#include "config.h"
#include "eosIO.h"
#include "helm.h"
#include "resourceManagerTypes.h"
#include <string>
#include <stdexcept>
#include <vector>
#include <set>
#include "debug.h"
/**
* @file configTest.cpp
* @brief Unit tests for the resourceManager class.
*/
std::string TEST_CONFIG = std::string(getenv("MESON_SOURCE_ROOT")) + "/tests/testsConfig.yaml";
/**
* @brief Test suite for the resourceManager class.
*/
class resourceManagerTest : public ::testing::Test {};
/**
* @brief Test the constructor of the resourceManager class.
*/
TEST_F(resourceManagerTest, constructor) {
Config::getInstance().loadConfig(TEST_CONFIG);
EXPECT_NO_THROW(ResourceManager::getInstance());
}
TEST_F(resourceManagerTest, getAvaliableResources) {
Config::getInstance().loadConfig(TEST_CONFIG);
ResourceManager& rm = ResourceManager::getInstance();
std::vector<std::string> resources = rm.getAvaliableResources();
std::set<std::string> expected = {"eos:helm", "mesh:sphere"};
std::set<std::string> actual(resources.begin(), resources.end());
EXPECT_EQ(expected, actual);
}
TEST_F(resourceManagerTest, getResource) {
Config::getInstance().loadConfig(TEST_CONFIG);
ResourceManager& rm = ResourceManager::getInstance();
std::string name = "eos:helm";
const Resource &r = rm.getResource(name);
// BREAKPOINT();
const auto &eos = std::get<std::unique_ptr<EosIO>>(r);
EXPECT_EQ("helm", eos->getFormat());
EOSTable &table = eos->getTable();
// -- Extract the Helm table from the EOSTable
helmholtz::HELMTable &helmTable = *std::get<std::unique_ptr<helmholtz::HELMTable>>(table);
EXPECT_DOUBLE_EQ(helmTable.f[0][0], -1692098915534.8142);
EXPECT_THROW(rm.getResource("opac:GS98:high:doesNotExist"), std::runtime_error);
}