From 95ddd4b23f2f3161dec5d85f466efc8b676b8231 Mon Sep 17 00:00:00 2001 From: mtuncbilek Date: Sat, 21 Sep 2024 14:46:39 +0200 Subject: [PATCH] memmappedfile-fix (#115) Co-authored-by: Metehan Tuncbilek Reviewed-by: Georg Hagen Co-authored-by: mtuncbilek Co-committed-by: mtuncbilek --- 3rdParty/tinyusdz/CMakeLists.txt | 7 + openVulkanoCpp/Host/Windows/ErrorUtils.cpp | 45 +++ openVulkanoCpp/Host/Windows/ErrorUtils.hpp | 18 + openVulkanoCpp/IO/MemMappedFile.cpp | 114 +----- openVulkanoCpp/IO/MemMappedFile.hpp | 12 +- openVulkanoCpp/IO/MemMappedFileInternal.cpp | 125 ++++++ openVulkanoCpp/IO/MemMappedFileInternal.hpp | 50 +++ .../IO/MemMappedFileWriteHelper.cpp | 367 ++++++++++++++++++ .../IO/MemMappedFileWriteHelper.hpp | 48 +++ openVulkanoCpp/Scene/AtlasData.hpp | 3 +- tests/MemFileTests.cpp | 126 ++++++ 11 files changed, 801 insertions(+), 114 deletions(-) create mode 100644 openVulkanoCpp/Host/Windows/ErrorUtils.cpp create mode 100644 openVulkanoCpp/Host/Windows/ErrorUtils.hpp create mode 100644 openVulkanoCpp/IO/MemMappedFileInternal.cpp create mode 100644 openVulkanoCpp/IO/MemMappedFileInternal.hpp create mode 100644 openVulkanoCpp/IO/MemMappedFileWriteHelper.cpp create mode 100644 openVulkanoCpp/IO/MemMappedFileWriteHelper.hpp create mode 100644 tests/MemFileTests.cpp diff --git a/3rdParty/tinyusdz/CMakeLists.txt b/3rdParty/tinyusdz/CMakeLists.txt index ac968c7..29799ce 100644 --- a/3rdParty/tinyusdz/CMakeLists.txt +++ b/3rdParty/tinyusdz/CMakeLists.txt @@ -18,6 +18,13 @@ FetchContent_Declare( -DTINYUSDZ_BUILD_TESTS:BOOL=OFF -DTINYUSDZ_BUILD_EXAMPLES:BOOL=OFF ) + +set(TINYUSDZ_NO_WERROR ON) +set(TINYUSDZ_BUILD_TESTS OFF) +set(TINYUSDZ_BUILD_BENCHMARKS OFF) +set(TINYUSDZ_BUILD_EXAMPLES OFF) +set(TINYUSDZ_WITH_BUILTIN_IMAGE_LOADER OFF) + FetchContent_MakeAvailable(tinyusdz) function (LinkTinyUSDZ TARGET) diff --git a/openVulkanoCpp/Host/Windows/ErrorUtils.cpp b/openVulkanoCpp/Host/Windows/ErrorUtils.cpp new file mode 100644 index 0000000..04f8245 --- /dev/null +++ b/openVulkanoCpp/Host/Windows/ErrorUtils.cpp @@ -0,0 +1,45 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "ErrorUtils.hpp" + +#if defined(_MSC_VER) +#include "Windows.h" +#endif + +namespace OpenVulkano +{ +#if defined(_MSC_VER) + std::string ErrorUtils::GetLastErrorMessage() + { + // Get the error message ID, if any. + DWORD errorID = GetLastError(); + if (errorID == 0) + { + return std::string(); + } + + LPSTR messageBuffer = nullptr; + // Format the error message + uint64_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &messageBuffer, 0, NULL); + + // Buffer it up + std::string message(messageBuffer, size); + + // Free the buffer and return the message + LocalFree(messageBuffer); + return message; + } +#else + std::string ErrorUtils::GetLastErrorMessage() + { + return std::string(); + } +#endif + +} \ No newline at end of file diff --git a/openVulkanoCpp/Host/Windows/ErrorUtils.hpp b/openVulkanoCpp/Host/Windows/ErrorUtils.hpp new file mode 100644 index 0000000..7ee864d --- /dev/null +++ b/openVulkanoCpp/Host/Windows/ErrorUtils.hpp @@ -0,0 +1,18 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include + +namespace OpenVulkano +{ + class ErrorUtils + { + public: + static std::string GetLastErrorMessage(); + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/IO/MemMappedFile.cpp b/openVulkanoCpp/IO/MemMappedFile.cpp index f298e7c..dbb6963 100644 --- a/openVulkanoCpp/IO/MemMappedFile.cpp +++ b/openVulkanoCpp/IO/MemMappedFile.cpp @@ -6,121 +6,15 @@ #include "MemMappedFile.hpp" #include "Base/Logger.hpp" -#ifdef _MSC_VER -#include -#else -#include -#include -#include -#include -#include -#endif +#include "MemMappedFileInternal.hpp" namespace OpenVulkano { - class MemMappedFile::Internal final - { - public: - void* address = nullptr; - size_t size = 0; -#ifdef _MSC_VER - HANDLE fileHandle; - HANDLE fileMappingHandle; - - Internal(const char* file, FileMode fileMode) - { //TODO handle other file modes than read - fileHandle = CreateFile( - TEXT(file), // file to open - GENERIC_READ, // open for reading - 0, // do not share - NULL, // default security - OPEN_EXISTING, // existing file only - FILE_ATTRIBUTE_NORMAL, // normal file - NULL); // no attribute template - if (fileHandle == INVALID_HANDLE_VALUE) - { - Logger::FILESYS->critical("Failed to open file: {}", file); - return; - } - - LARGE_INTEGER fileSize; - if (!GetFileSizeEx(fileHandle, &fileSize)) - { - Logger::FILESYS->critical("Failed to get size of file: {}", file); - CloseHandle(fileHandle); - return; - } - size = fileSize.QuadPart; - - fileMappingHandle = CreateFileMapping( - fileHandle, - NULL, // default security - PAGE_READONLY, // read-only access - 0, // maximum object size (high-order) - 0, // maximum object size (low-order) - NULL); // name of the mapping object - if (fileMappingHandle == 0) - { - Logger::FILESYS->critical("Failed to create file mapping handle: {}", file); - CloseHandle(fileHandle); - size = 0; - return; - } - - address = MapViewOfFile( - fileMappingHandle, // handle to the map object - FILE_MAP_READ, // read access - 0, // max. object size - 0, // size of the hFile - 0); // map the entire file - - if (address == nullptr) - { - Logger::FILESYS->critical("Failed to create file mapping: {}", file); - CloseHandle(fileMappingHandle); - CloseHandle(fileHandle); - size = 0; - } - } - - ~Internal() - { - UnmapViewOfFile(address); - CloseHandle(fileMappingHandle); - CloseHandle(fileHandle); - } -#else - int fileHandle; - struct stat fileStat; - - Internal(const char* file, FileMode fileMode) - { - fileHandle = open(file, fileMode); - if (fstat(fileHandle, &fileStat) == -1) - { - Logger::FILESYS->critical("Failed to query stats for file: {}", file); - return; - } - address = mmap(nullptr, fileStat.st_size, fileMode + 1, MAP_PRIVATE, fileHandle, 0); - if (address == MAP_FAILED) - { - Logger::FILESYS->critical("Failed to memory map file: {}", file); - address = nullptr; - } - size = fileStat.st_size; - } - - ~Internal() - { - munmap(address, fileStat.st_size); - close(fileHandle); - } -#endif - }; - MemMappedFile::MemMappedFile(const std::filesystem::path& path, FileMode fileMode) + : m_data(nullptr), m_size(0) { - m_internal = std::make_shared(path.string().c_str(), fileMode); + std::string file = path.string(); + m_internal = std::make_shared(file.c_str(), fileMode); m_data = m_internal->address; m_size = (m_data) ? m_internal->size : 0; } diff --git a/openVulkanoCpp/IO/MemMappedFile.hpp b/openVulkanoCpp/IO/MemMappedFile.hpp index 42ae856..72ef781 100644 --- a/openVulkanoCpp/IO/MemMappedFile.hpp +++ b/openVulkanoCpp/IO/MemMappedFile.hpp @@ -10,16 +10,22 @@ namespace OpenVulkano { + class MemMappedFileInternal; class MemMappedFile { - class Internal; + friend class MemMappedFileWriteHelper; - std::shared_ptr m_internal; + std::shared_ptr m_internal; void* m_data; size_t m_size; public: - enum FileMode : int { READ_ONLY = 0, WRITE_ONLY, READ_WRITE }; + enum FileMode : int + { + READ_ONLY = 0, + WRITE_ONLY, + READ_WRITE + }; MemMappedFile(const std::filesystem::path& path, FileMode fileMode = READ_ONLY); diff --git a/openVulkanoCpp/IO/MemMappedFileInternal.cpp b/openVulkanoCpp/IO/MemMappedFileInternal.cpp new file mode 100644 index 0000000..460552e --- /dev/null +++ b/openVulkanoCpp/IO/MemMappedFileInternal.cpp @@ -0,0 +1,125 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "MemMappedFileInternal.hpp" + +#include "Base/Logger.hpp" +#include "Host/Windows/ErrorUtils.hpp" + +namespace OpenVulkano +{ +#ifdef _MSC_VER + MemMappedFileInternal::MemMappedFileInternal(const char* file, MemMappedFile::FileMode fileMode) + : size(0), fileHandle(nullptr), fileMappingHandle(nullptr) + { + DWORD openMode { 0 }, mapMode { 0 }, viewMode { 0 }; + + switch (fileMode) + { + case MemMappedFile::FileMode::READ_ONLY: + openMode = GENERIC_READ; + mapMode = PAGE_READONLY; + viewMode = FILE_MAP_READ; + break; + case MemMappedFile::FileMode::WRITE_ONLY: + openMode = GENERIC_WRITE; + mapMode = PAGE_READWRITE; + viewMode = FILE_MAP_WRITE; + break; + case MemMappedFile::FileMode::READ_WRITE: + openMode = GENERIC_ALL; + mapMode = PAGE_READWRITE; + viewMode = FILE_MAP_ALL_ACCESS; + break; + default: + break; + } + + fileHandle = CreateFileA(file, // file to open + openMode, // open for switch case + NULL, // share mode + NULL, // default security + OPEN_ALWAYS, // always + FILE_ATTRIBUTE_NORMAL, // normal file + NULL); // no attribute template + + if (fileHandle == INVALID_HANDLE_VALUE || fileHandle == NULL) + { + Logger::FILESYS->critical("Failed to open file: {}.", ErrorUtils::GetLastErrorMessage()); + return; + } + + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(fileHandle, &fileSize)) + { + Logger::FILESYS->critical("Failed to get size of file: {}", ErrorUtils::GetLastErrorMessage()); + CloseHandle(fileHandle); + return; + } + size = fileSize.QuadPart; + + fileMappingHandle = CreateFileMappingA(fileHandle, + NULL, // default security + mapMode, // mod for switch case + 0, // maximum object size (high-order) + 0, // maximum object size (low-order) + NULL); // name of the mapping object + if (fileMappingHandle == INVALID_HANDLE_VALUE || fileMappingHandle == NULL) + { + Logger::FILESYS->critical("Failed to create file mapping handle: {}", ErrorUtils::GetLastErrorMessage()); + CloseHandle(fileHandle); + size = 0; + return; + } + + address = MapViewOfFile(fileMappingHandle, // handle to the map object + viewMode, // access for switch case + 0, // max. object size + 0, // size of the hFile + 0); // map the entire file + + if (address == nullptr) + { + Logger::FILESYS->critical("Failed to create file mapping: {}", ErrorUtils::GetLastErrorMessage()); + CloseHandle(fileMappingHandle); + CloseHandle(fileHandle); + size = 0; + } + } + + MemMappedFileInternal::~MemMappedFileInternal() + { + UnmapViewOfFile(address); + CloseHandle(fileMappingHandle); + CloseHandle(fileHandle); + } +#else + MemMappedFileInternal::MemMappedFileInternal(const char* file, MemMappedFile::FileMode fileMode) + { + fileHandle = open(file, fileMode); + if (fstat(fileHandle, &fileStat) == -1) + { + Logger::FILESYS->critical("Failed to query stats for file: {}", file); + return; + } + address = mmap(nullptr, fileStat.st_size, fileMode + 1, MAP_SHARED, fileHandle, 0); + if (address == MAP_FAILED) + { + Logger::FILESYS->critical("Failed to memory map file: {}", file); + address = nullptr; + } + size = fileStat.st_size; + } + + MemMappedFileInternal::~MemMappedFileInternal() + { + munmap(address, fileStat.st_size); + close(fileHandle); + } +#endif + + +} \ No newline at end of file diff --git a/openVulkanoCpp/IO/MemMappedFileInternal.hpp b/openVulkanoCpp/IO/MemMappedFileInternal.hpp new file mode 100644 index 0000000..2d7852b --- /dev/null +++ b/openVulkanoCpp/IO/MemMappedFileInternal.hpp @@ -0,0 +1,50 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include "MemMappedFile.hpp" + +#ifdef _MSC_VER + #include +#else + #include + #include + #include + #include + #include +#endif + +namespace OpenVulkano +{ + class MemMappedFileInternal + { + friend class MemMappedFileWriteHelper; + friend class MemMappedFile; + + public: + MemMappedFileInternal(const char* file, MemMappedFile::FileMode fileMode); + ~MemMappedFileInternal(); + + private: +#if defined(_MSC_VER) + bool InvalidHandle() const { return fileHandle == INVALID_HANDLE_VALUE || fileHandle == NULL; } +#else + [[nodiscard]] bool InvalidHandle() const { return fileHandle == -1; } +#endif + + private: + void* address = nullptr; + size_t size = 0; + +#ifdef _MSC_VER + HANDLE fileHandle; + HANDLE fileMappingHandle; +#else + int fileHandle; + struct stat fileStat; +#endif + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/IO/MemMappedFileWriteHelper.cpp b/openVulkanoCpp/IO/MemMappedFileWriteHelper.cpp new file mode 100644 index 0000000..4c51dc7 --- /dev/null +++ b/openVulkanoCpp/IO/MemMappedFileWriteHelper.cpp @@ -0,0 +1,367 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "MemMappedFileWriteHelper.hpp" + +#include "MemMappedFile.hpp" +#include "MemMappedFileInternal.hpp" + +#if defined(_WIN32) + #include + #include +#else + #include + #include + #include + #include + #include +#endif + +#include "Base/Logger.hpp" +#include + +namespace OpenVulkano +{ +#if defined(_WIN32) + + MemMappedFileWriteHelper::MemMappedFileWriteHelper(const std::filesystem::path& path, size_t maxFileSize) + : m_path(path), m_file(nullptr) + { + // Check if file exists, so we can open it in the correct mode + DWORD openMode = CREATE_ALWAYS; + if (std::filesystem::exists(path)) + { + openMode = OPEN_ALWAYS; + } + + // Open the file + HANDLE fileHandle = + CreateFileA(path.string().c_str(), GENERIC_ALL, FILE_SHARE_WRITE, NULL, openMode, FILE_ATTRIBUTE_NORMAL, NULL); + + if (fileHandle == INVALID_HANDLE_VALUE || fileHandle == NULL) + { + throw std::runtime_error(ErrorUtils::GetLastErrorMessage().c_str()); + } + + // Control the file size + LARGE_INTEGER currentSize {}; + if (fileHandle) + { + if (!GetFileSizeEx(fileHandle, ¤tSize)) + { + throw std::runtime_error(ErrorUtils::GetLastErrorMessage().c_str()); + } + } + + if (currentSize.QuadPart <= 0 && maxFileSize == 0) + { + throw std::runtime_error("Either the file size has to be greater than 0 or Initial Size!"); + } + + // Set the file size + if (maxFileSize > 0) + { + if (fileHandle) + { + // Set the file size + if (!SetFilePointer(fileHandle, DWORD(maxFileSize), NULL, FILE_BEGIN)) + { + throw std::runtime_error(ErrorUtils::GetLastErrorMessage().c_str()); + } + + if (!SetEndOfFile(fileHandle)) + { + throw std::runtime_error(ErrorUtils::GetLastErrorMessage().c_str()); + } + } + } + + if (fileHandle) + { + CloseHandle(fileHandle); + } + + m_file = new MemMappedFile(path.string().c_str(), MemMappedFile::FileMode::READ_WRITE); + } + + MemMappedFileWriteHelper::~MemMappedFileWriteHelper() = default; + + bool MemMappedFileWriteHelper::Resize(const size_t newSize) + { + if (newSize == 0) + { + // Handle the case where the new size is zero + Logger::FILESYS->critical("New size is zero."); + return false; + } + + // Get the current file handle + if (m_file->m_internal->fileHandle == INVALID_HANDLE_VALUE) + { + Logger::FILESYS->critical("Invalid file handle."); + return false; + } + + // Unmap the old view of the file and close the file mapping handle + if (!(m_file->m_internal->fileMappingHandle == NULL) + && !(m_file->m_internal->fileMappingHandle == INVALID_HANDLE_VALUE)) + { + UnmapViewOfFile(m_file->m_internal->address); + CloseHandle(m_file->m_internal->fileMappingHandle); + } + + // Resize the file + if (!SetFilePointer(m_file->m_internal->fileHandle, static_cast(newSize), NULL, FILE_BEGIN)) + { + Logger::FILESYS->critical("Failed to set file pointer. Reason: {}", GetLastError()); + return false; + } + + if (!SetEndOfFile(m_file->m_internal->fileHandle)) + { + Logger::FILESYS->critical("Failed to set end of file."); + return false; + } + + // Create a new file mapping handle + m_file->m_internal->fileMappingHandle = + CreateFileMappingW(m_file->m_internal->fileHandle, NULL, PAGE_READWRITE, 0, 0, NULL); + if (m_file->m_internal->fileMappingHandle == NULL) + { + Logger::FILESYS->critical("Failed to create file mapping."); + return false; + } + + // Map the file to a new view + m_file->m_internal->address = + MapViewOfFile(m_file->m_internal->fileMappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0); + if (m_file->m_internal->address == NULL || m_file->m_internal->address == INVALID_HANDLE_VALUE) + { + Logger::FILESYS->critical("Failed to map view of file."); + return false; + } + + return true; + } + + + bool MemMappedFileWriteHelper::Trim(const size_t finalFileSize) + { + HANDLE fileHandle {}; + if (m_file->m_internal->InvalidHandle()) + { + auto file = m_path.string(); + fileHandle = CreateFileA(file.c_str(), // file to open + GENERIC_WRITE, // open for reading + 0, // do not share + NULL, // default security + CREATE_ALWAYS, // existing file only + FILE_ATTRIBUTE_NORMAL, // normal file + NULL); // no attribute template + } + else + { + fileHandle = m_file->m_internal->fileHandle; + } + + if (m_file) + { + if (!UnmapViewOfFile(m_file->m_internal->address)) + { + Logger::FILESYS->critical("Failed to unmap view of file on trim."); + return false; + } + + if (!CloseHandle(m_file->m_internal->fileMappingHandle)) + { + Logger::FILESYS->critical("Failed to close file mapping handle."); + return false; + } + } + + LARGE_INTEGER liSize; + liSize.QuadPart = finalFileSize; + if (!SetFilePointerEx(fileHandle, liSize, NULL, FILE_BEGIN) || !SetEndOfFile(fileHandle)) + { + Logger::FILESYS->critical("Failed to trim file: {}", ErrorUtils::GetLastErrorMessage()); + return false; + } + + // Create a new file mapping handle + if (m_file) + { + m_file->m_internal->fileMappingHandle = CreateFileMappingW(fileHandle, NULL, PAGE_READWRITE, 0, 0, NULL); + if (m_file->m_internal->fileMappingHandle == NULL) + { + Logger::FILESYS->critical("Failed to create file mapping."); + return false; + } + + // Map the file to a new view + m_file->m_internal->address = + MapViewOfFile(m_file->m_internal->fileMappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0); + if (m_file->m_internal->address == NULL || m_file->m_internal->address == INVALID_HANDLE_VALUE) + { + Logger::FILESYS->critical("Failed to map view of file."); + return false; + } + } + + if (m_file) + { + m_file->m_size = finalFileSize; + m_file->m_internal->size = finalFileSize; + } + + return true; + } +#else + MemMappedFileWriteHelper::MemMappedFileWriteHelper(const std::filesystem::path& path, size_t maxFileSize) + { + const auto file = path.string(); + const int fileHandle = open(file.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (fileHandle == -1) + { + throw std::runtime_error("Failed to open file: " + file); + } + + struct stat fileStat + { + }; + if (fstat(fileHandle, &fileStat) == -1) + { + close(fileHandle); + throw std::runtime_error("Failed to get file size: " + file); + } + + if (const size_t fileSize = fileStat.st_size; fileSize < maxFileSize) + { + if (ftruncate(fileHandle, maxFileSize) == -1) + { + close(fileHandle); + throw std::runtime_error("Failed to resize file: " + file); + } + } + + close(fileHandle); + m_file = new MemMappedFile(file.c_str(), MemMappedFile::FileMode::READ_WRITE); + } + + MemMappedFileWriteHelper::~MemMappedFileWriteHelper() = default; + + bool MemMappedFileWriteHelper::Resize(const size_t newSize) + { + if (newSize == 0) + { + // Handle the case where the new size is zero + Logger::FILESYS->error("New size is zero. New size must be greater than zero."); + return false; + } + + // Get the current file handle + if (m_file->m_internal->fileHandle == -1) + { + Logger::FILESYS->critical("Invalid file handle."); + return false; + } + + // Resize the file + if (ftruncate(m_file->m_internal->fileHandle, newSize) == -1) + { + Logger::FILESYS->critical("Failed to resize file."); + return false; + } + + // Unmap the old view and map a new view of the resized file + if (m_file->m_internal->address != nullptr) + { + if (munmap(m_file->m_internal->address, m_file->m_internal->size) == -1) + { + Logger::FILESYS->critical("Failed to unmap view of file."); + return false; + } + } + + // Map the file to a new view + m_file->m_internal->address = + mmap(nullptr, newSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_file->m_internal->fileHandle, 0); + if (m_file->m_internal->address == MAP_FAILED) + { + Logger::FILESYS->critical("Failed to map file."); + return false; + } + + // Update the size of the file + m_file->m_internal->size = newSize; + return true; + } + + bool MemMappedFileWriteHelper::Trim(const size_t finalFileSize) + { + // If getting handle returns invalid file recreate the file + if (m_file->m_internal->InvalidHandle()) + { + const auto file = m_path.string(); + m_file->m_internal->fileHandle = open(file.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + } + + // Unmap the old view of the file + if(munmap(m_file->m_internal->address, m_file->m_internal->size) == -1) + { + Logger::FILESYS->critical("Failed to unmap address."); + } + + // Trim the file + if (ftruncate(m_file->m_internal->fileHandle, finalFileSize) == -1) + { + Logger::FILESYS->critical("Failed to trim file."); + return false; + } + + // Remap the file with the new size + m_file->m_internal->address = mmap(nullptr, finalFileSize, PROT_READ | PROT_WRITE, MAP_SHARED, + m_file->m_internal->fileHandle, 0); + if (m_file->m_internal->address == MAP_FAILED) + { + Logger::FILESYS->critical("Failed to map file."); + return false; + } + + // Update the size in MemMappedFile structure + m_file->m_size = finalFileSize; + return true; + } + +#endif + + void MemMappedFileWriteHelper::Close() + { + if (m_file->IsOpen()) + { + delete m_file; + } + } + + MemMappedFileWriter::MemMappedFileWriter(const std::filesystem::path& path, size_t maxFileSize) + : m_helper(MemMappedFileWriteHelper(path, maxFileSize)) + { + } + + MemMappedFileWriter::~MemMappedFileWriter() { m_helper.Close(); } + + void MemMappedFileWriter::Write(const char* data, const size_t size) + { + // If the new size is greater than the current size, resize the file + if (m_writtenSize + size > m_helper.Size()) + { + m_helper.Resize(m_writtenSize + size); + } + + // Write to the end of the file + memcpy(static_cast(m_helper.Data()) + m_writtenSize, data, size); + m_writtenSize += size; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/IO/MemMappedFileWriteHelper.hpp b/openVulkanoCpp/IO/MemMappedFileWriteHelper.hpp new file mode 100644 index 0000000..7b3ac03 --- /dev/null +++ b/openVulkanoCpp/IO/MemMappedFileWriteHelper.hpp @@ -0,0 +1,48 @@ +/* +* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "IO/MemMappedFile.hpp" +#include + +namespace OpenVulkano +{ + class MemMappedFileWriteHelper + { + public: + MemMappedFileWriteHelper(const std::filesystem::path& path, size_t maxFileSize); + ~MemMappedFileWriteHelper(); + + [[nodiscard]] void* Data() const { return m_file->Data(); } + [[nodiscard]] size_t Size() const { return m_file->Size(); } + + bool Resize(size_t newSize); + void Close(); + bool Trim(size_t finalFileSize); + + static constexpr size_t USE_CURRENT_FILE_SIZE = 0; + + private: + MemMappedFile* m_file; + std::filesystem::path m_path; + }; + + class MemMappedFileWriter final + { + public: + MemMappedFileWriter(const std::filesystem::path& path, size_t maxFileSize); + ~MemMappedFileWriter(); + + void Write(const char* data, size_t size); + void SetOffset(size_t offset) { m_writtenSize = offset; } + [[nodiscard]] size_t GetOffset() const { return m_writtenSize; } + + private: + MemMappedFileWriteHelper m_helper; + size_t m_writtenSize = 0; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/AtlasData.hpp b/openVulkanoCpp/Scene/AtlasData.hpp index 7bfd8f7..08dc6a6 100644 --- a/openVulkanoCpp/Scene/AtlasData.hpp +++ b/openVulkanoCpp/Scene/AtlasData.hpp @@ -6,6 +6,7 @@ #pragma once +#include "Base/Wrapper.hpp" #include "Math/Math.hpp" #include "Image/Image.hpp" #include "Scene/Texture.hpp" @@ -56,7 +57,7 @@ namespace OpenVulkano::Scene { std::map glyphs; AtlasMetadata meta; - std::unique_ptr img; + Unique img; Texture texture; }; diff --git a/tests/MemFileTests.cpp b/tests/MemFileTests.cpp new file mode 100644 index 0000000..117ffa5 --- /dev/null +++ b/tests/MemFileTests.cpp @@ -0,0 +1,126 @@ +#include + +#include "IO/MemMappedFile.hpp" +#include "IO/MemMappedFileWriteHelper.hpp" +#include "IO/AppFolders.hpp" +#include "Base/Logger.hpp" + +#include +#include + +namespace fs = std::filesystem; + +TEST_CASE("MemMappedFileWrite") +{ + OpenVulkano::Logger::SetupLogger("", "tests.log"); + + auto path = OpenVulkano::AppFolders::GetAppTempDir(); + path += "/MemFileTest.txt"; + + std::string data = "Hello World X\n" + "Hello World Y\n" + "Hello World Z\n" + "Hello World W\n" + "Hello World A\n" + "Hello World B\n" + "Hello World C\n" + "Hello World D\n" + "Hello World E\n" + "Hello World F\n" + "Hello World G\n" + "Hello World H\n" + "Hello World I\n" + "Hello World J\n" + "Hello World K\n" + "Hello World L\n" + "Hello World M\n" + "Hello World N\n"; + + + SECTION("Write to MemMappedFile") + { + OpenVulkano::MemMappedFileWriter writer(path, 1024); + writer.Write(data.data(), data.size()); + + REQUIRE(writer.GetOffset() == data.size()); + } + + SECTION("Control Size") + { + OpenVulkano::MemMappedFile memFile(path, OpenVulkano::MemMappedFile::READ_ONLY); + REQUIRE(memFile.Size() == 1024); // The size that comes from the first Test. + } + + SECTION("Compare String Data for per char") + { + OpenVulkano::MemMappedFileWriteHelper memFileHelper(path, OpenVulkano::MemMappedFile::READ_ONLY); + REQUIRE(memFileHelper.Trim(data.size())); + + std::string testData((char*) memFileHelper.Data()); + + for (size_t i = 0; i < data.size(); i++) + { + if (data[i] != testData[i]) + { + REQUIRE(false); + } + } + + memFileHelper.Close(); + } + + SECTION("Trim File") + { + OpenVulkano::MemMappedFileWriteHelper helper(path, + OpenVulkano::MemMappedFileWriteHelper::USE_CURRENT_FILE_SIZE); + REQUIRE(helper.Trim(100)); + REQUIRE(helper.Size() == 100); + + helper.Close(); + } + + SECTION("Write Data 2") + { + OpenVulkano::MemMappedFileWriter writer(path, OpenVulkano::MemMappedFileWriteHelper::USE_CURRENT_FILE_SIZE); + writer.Write(data.data(), data.size()); + writer.Write(data.data(), data.size()); + writer.Write(data.data(), data.size()); + + REQUIRE(writer.GetOffset() == data.size() * 3); + } + + SECTION("Compare Data") + { + OpenVulkano::MemMappedFileWriteHelper helper(path, + OpenVulkano::MemMappedFileWriteHelper::USE_CURRENT_FILE_SIZE); + REQUIRE(helper.Data() != nullptr); + std::string testData((char*) helper.Data()); + printf("size: %llu", helper.Size()); + helper.Close(); + + std::ifstream file(path, std::ios::binary); + std::string streamData; + std::getline(file, streamData, '\0'); + file.close(); + + for (size_t i = 0; i < testData.size(); i++) + { + if (streamData[i] != testData[i]) + { + REQUIRE(false); + } + } + + REQUIRE(streamData.size() == testData.size()); + + printf("helper size: %llu\n", helper.Size()); + } + + SECTION("Actual Size") + { + std::error_code ec; + std::uintmax_t size = fs::file_size(path, ec); + + REQUIRE(size == 756); + } +}