From 93584d64cfb5e7dcc643c4f7742e97ac28e124de Mon Sep 17 00:00:00 2001 From: K1ngst0m Date: Sat, 28 Oct 2023 23:37:46 +0800 Subject: [PATCH] filesystem setup --- cmake/AphDefinitions.cmake | 1 + engine/CMakeLists.txt | 13 ++- engine/filesystem/CMakeLists.txt | 5 + engine/filesystem/filesystem.cpp | 191 +++++++++++++++++++++++++++++++ engine/filesystem/filesystem.h | 54 +++++++++ tests/filesystem.cpp | 147 ++++++++++++++++++++++++ 6 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 engine/filesystem/CMakeLists.txt create mode 100644 engine/filesystem/filesystem.cpp create mode 100644 engine/filesystem/filesystem.h create mode 100644 tests/filesystem.cpp diff --git a/cmake/AphDefinitions.cmake b/cmake/AphDefinitions.cmake index f62ab4ed..ef93099c 100644 --- a/cmake/AphDefinitions.cmake +++ b/cmake/AphDefinitions.cmake @@ -14,5 +14,6 @@ set(APH_ENGINE_APP_DIR ${APH_ENGINE_DIR}/app) set(APH_ENGINE_RENDERER_DIR ${APH_ENGINE_DIR}/renderer) set(APH_ENGINE_WSI_DIR ${APH_ENGINE_DIR}/wsi) set(APH_ENGINE_THREADS_DIR ${APH_ENGINE_DIR}/threads) +set(APH_ENGINE_FILESYSTEM_DIR ${APH_ENGINE_DIR}/filesystem) # set(APH_ENGINE_SCENE_DIR ${APH_ENGINE_DIR}/scene) # set(APH_ENGINE_UI_DIR ${APH_ENGINE_DIR}/ui) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 1e465c9c..5e417587 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(wsi) add_subdirectory(resource) add_subdirectory(api) add_subdirectory(renderer) +add_subdirectory(filesystem) add_library(aph_engine INTERFACE) @@ -14,7 +15,17 @@ target_include_directories(aph_engine INTERFACE ${APH_EXTERNAL_DIR}/glm ${APH_ENGINE_DIR} ) -target_link_libraries(aph_engine INTERFACE common app cli threads wsi resource api renderer) +target_link_libraries(aph_engine INTERFACE + common + app + cli + threads + wsi + resource + api + renderer + aph_filesystem +) target_link_libraries(aph_engine INTERFACE mimalloc-static diff --git a/engine/filesystem/CMakeLists.txt b/engine/filesystem/CMakeLists.txt new file mode 100644 index 00000000..de238ef0 --- /dev/null +++ b/engine/filesystem/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB APH_FILESYSTEM_SRC ${APH_ENGINE_FILESYSTEM_DIR}/*.cpp) +add_library(aph_filesystem STATIC ${APH_FILESYSTEM_SRC}) +aph_compiler_options(aph_filesystem) +target_include_directories(aph_filesystem PRIVATE ${APH_ENGINE_DIR}) +target_link_libraries(aph_filesystem PRIVATE common) diff --git a/engine/filesystem/filesystem.cpp b/engine/filesystem/filesystem.cpp new file mode 100644 index 00000000..158779a7 --- /dev/null +++ b/engine/filesystem/filesystem.cpp @@ -0,0 +1,191 @@ +#include "filesystem.h" +#include "common/logger.h" + +namespace aph +{ + +Filesystem::Filesystem() +{ + m_protocols["assets"] = "assets/"; + m_protocols["models"] = "assets/models"; + m_protocols["fonts"] = "assets/fonts"; + m_protocols["shader_glsl"] = "assets/shaders/glsl"; + m_protocols["shader_slang"] = "assets/shaders/slang"; + m_protocols["textures"] = "assets/textures"; + m_protocols["file"] = ""; +} + +Filesystem::~Filesystem() +{ + clearMappedFiles(); +} + +void Filesystem::registerProtocol(std::string_view protocol, const std::string& path) +{ + if(protocolExists(protocol)) + { + CM_LOG_WARN("overrided the existing protocol %s. path: %s -> %s", protocol, m_protocols[protocol.data()], path); + } + m_protocols[protocol.data()] = path; +} + +bool Filesystem::protocolExists(std::string_view protocol) +{ + return m_protocols.contains(protocol.data()); +} + +void Filesystem::removeProtocol(std::string_view protocol) +{ + m_protocols.erase(protocol.data()); +} + +void Filesystem::clearMappedFiles() +{ + for(auto& [data, size] : m_mappedFiles) + { + munmap(data, size); + } + m_mappedFiles.clear(); +} + +std::filesystem::path Filesystem::resolvePath(std::string_view inputPath) +{ + auto protocolEnd = inputPath.find("://"); + std::string protocol; + std::string relativePath; + + if(protocolEnd != std::string::npos) + { + protocol = inputPath.substr(0, protocolEnd); + relativePath = inputPath.substr(protocolEnd + 3); + } + else + { + protocol = "file"; + relativePath = inputPath; + } + + if(!m_protocols.contains(protocol)) + { + CM_LOG_ERR("Unknown protocol: %s", protocol); + return {}; + } + + return std::filesystem::path(m_protocols[protocol]) / relativePath; +} + +void* Filesystem::map(std::string_view path) +{ + std::lock_guard lock(m_mapLock); + auto resolvedPath = resolvePath(path); + + int fd = open(resolvedPath.string().c_str(), O_RDONLY); + if(fd == -1) + return nullptr; + + struct stat fileStat; + if(fstat(fd, &fileStat) == -1) + { + close(fd); + return nullptr; + } + + void* mappedData = mmap(0, fileStat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + + if(mappedData == MAP_FAILED) + { + return nullptr; + } + + m_mappedFiles[mappedData] = fileStat.st_size; + return mappedData; +} + +void Filesystem::unmap(void* data) +{ + if(m_mappedFiles.find(data) != m_mappedFiles.end()) + { + munmap(data, m_mappedFiles[data]); + m_mappedFiles.erase(data); + } +} + +std::string Filesystem::readFileToString(std::string_view path) +{ + std::ifstream file(resolvePath(path), std::ios::in); + if(!file) + { + CM_LOG_ERR("Unable to open file: %s", path); + return {}; + } + return std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator()); +} +std::vector Filesystem::readFileToBytes(std::string_view path) +{ + std::ifstream file(resolvePath(path), std::ios::binary | std::ios::ate); + if(!file) + { + CM_LOG_ERR("Unable to open file: %s", path); + return {}; + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector buffer(size); + if(!file.read((char*)buffer.data(), size)) + { + CM_LOG_ERR("Error reading file: %s", path); + return {}; + } + return buffer; +} +std::vector Filesystem::readFileLines(std::string_view path) +{ + std::ifstream file(resolvePath(path)); + if(!file) + { + CM_LOG_ERR("Unable to open file: %s", path); + return {}; + } + + std::vector lines; + std::string line; + while(std::getline(file, line)) + { + lines.push_back(line); + } + return lines; +} +void Filesystem::writeStringToFile(std::string_view path, const std::string& content) +{ + std::ofstream file(resolvePath(path).string(), std::ios::binary); + if(!file) + { + throw std::runtime_error("Failed to open file for writing: " + std::string(path)); + } + file << content; +} +void Filesystem::writeBytesToFile(std::string_view path, const std::vector& bytes) +{ + std::ofstream file(resolvePath(path).string(), std::ios::binary); + if(!file) + { + throw std::runtime_error("Failed to open file for writing: " + std::string(path)); + } + file.write(reinterpret_cast(bytes.data()), bytes.size()); +} +void Filesystem::writeLinesToFile(std::string_view path, const std::vector& lines) +{ + std::ofstream file(resolvePath(path).string()); + if(!file) + { + throw std::runtime_error("Failed to open file for writing: " + std::string(path)); + } + for(const auto& line : lines) + { + file << line << '\n'; + } +} +} // namespace aph diff --git a/engine/filesystem/filesystem.h b/engine/filesystem/filesystem.h new file mode 100644 index 00000000..79f6ea32 --- /dev/null +++ b/engine/filesystem/filesystem.h @@ -0,0 +1,54 @@ +#ifndef FILESYSTEM_H_ +#define FILESYSTEM_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace aph +{ +class Filesystem +{ +public: + Filesystem(); + + ~Filesystem(); + + void* map(std::string_view path); + void unmap(void* data); + void clearMappedFiles(); + + std::string readFileToString(std::string_view path); + std::vector readFileToBytes(std::string_view path); + std::vector readFileLines(std::string_view path); + + void writeStringToFile(std::string_view path, const std::string& content); + void writeBytesToFile(std::string_view path, const std::vector& bytes); + void writeLinesToFile(std::string_view path, const std::vector& lines); + + void registerProtocol(std::string_view protocol, const std::string& path); + bool protocolExists(std::string_view protocol); + void removeProtocol(std::string_view protocol); + +private: + std::filesystem::path resolvePath(std::string_view inputPath); + +private: + std::map> m_callbacks; + std::map m_protocols; + std::map m_mappedFiles; + std::mutex m_mapLock; +}; + +} // namespace aph + +#endif // FILESYSTEM_H_ diff --git a/tests/filesystem.cpp b/tests/filesystem.cpp new file mode 100644 index 00000000..79fa203c --- /dev/null +++ b/tests/filesystem.cpp @@ -0,0 +1,147 @@ +#include +#include "filesystem/filesystem.h" +#include +#include + +class FileGuard +{ +public: + explicit FileGuard(std::string filename) : m_filename(std::move(filename)) {} + + ~FileGuard() { std::remove(m_filename.c_str()); } + +private: + std::string m_filename; +}; + +using namespace aph; + +TEST_CASE("Test Filesystem Protocol Setup", "[Filesystem]") +{ + Filesystem fs; + + SECTION("Default protocols are set up") + { + REQUIRE(fs.protocolExists("assets")); + REQUIRE(fs.protocolExists("models")); + REQUIRE(fs.protocolExists("fonts")); + REQUIRE(fs.protocolExists("shader_glsl")); + REQUIRE(fs.protocolExists("shader_slang")); + REQUIRE(fs.protocolExists("textures")); + } + + SECTION("Add new protocol") + { + fs.registerProtocol("newprotocol", "/some/path"); + REQUIRE(fs.protocolExists("newprotocol")); + } + + SECTION("Remove existing protocol") + { + fs.removeProtocol("assets"); + REQUIRE_FALSE(fs.protocolExists("assets")); + } +} + +// Utility to create a temporary test file. +std::string createTempFile(const std::string& content) +{ + static int tempFileCount = 0; + std::string tempFileName = "tempFile_" + std::to_string(tempFileCount++) + ".txt"; + + std::ofstream out(tempFileName); + out << content; + out.close(); + + return tempFileName; +} + +TEST_CASE("Filesystem Basic Operations", "[Filesystem]") +{ + Filesystem fs; + + std::string testContent = "Hello, World!"; + std::string tempFilePath = createTempFile(testContent); + + SECTION("Mapping and Unmapping Files") + { + auto* mapped = fs.map(tempFilePath); + REQUIRE(mapped != nullptr); // Ensure mapping returns a non-null pointer. + fs.unmap(mapped); + } + + SECTION("Reading File to String") + { + std::string content = fs.readFileToString(tempFilePath); + REQUIRE(content == testContent); // Check if the content read is correct. + } + + SECTION("Reading File to Bytes") + { + std::vector bytes = fs.readFileToBytes(tempFilePath); + REQUIRE(bytes.size() == + testContent.size()); // Simple size check. You can add more specific byte-by-byte checks. + } + + SECTION("Reading File Lines") + { + std::string multiLineContent = "Line1\nLine2\nLine3"; + std::string multiLineFilePath = createTempFile(multiLineContent); + + std::vector lines = fs.readFileLines(multiLineFilePath); + REQUIRE(lines.size() == 3); // We added 3 lines. + REQUIRE(lines[0] == "Line1"); + REQUIRE(lines[1] == "Line2"); + REQUIRE(lines[2] == "Line3"); + } + + // Clean up the temporary files after tests. + std::remove(tempFilePath.c_str()); +} + +// Utility to read a file into a string for verification purposes. +std::string readFileToString(const std::string& path) +{ + std::ifstream in(path, std::ios::binary); + std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + return content; +} +TEST_CASE("Filesystem Write Operations", "[Filesystem]") +{ + Filesystem fs; + + std::string testFile = "writeTestFile.txt"; + + SECTION("Writing String to File") + { + std::string content = "Hello, Write!"; + fs.writeStringToFile(testFile, content); + + std::string readBack = readFileToString(testFile); + REQUIRE(readBack == content); + + std::remove(testFile.c_str()); // Clean up after the test. + } + + SECTION("Writing Bytes to File") + { + std::vector bytes = {72, 101, 108, 108, 111}; // ASCII for "Hello" + fs.writeBytesToFile(testFile, bytes); + + std::string readBack = readFileToString(testFile); + REQUIRE(readBack == "Hello"); + + std::remove(testFile.c_str()); // Clean up after the test. + } + + SECTION("Writing Lines to File") + { + std::vector lines = {"Line1", "Line2", "Line3"}; + fs.writeLinesToFile(testFile, lines); + + std::string readBack = readFileToString(testFile); + REQUIRE(readBack == "Line1\nLine2\nLine3\n"); // Note: The method adds '\n' after each line. + + std::remove(testFile.c_str()); // Clean up after the test. + } +}