diff --git a/Src/IACore/CMakeLists.txt b/Src/IACore/CMakeLists.txt index a00cbe6..8d8ccb2 100644 --- a/Src/IACore/CMakeLists.txt +++ b/Src/IACore/CMakeLists.txt @@ -5,6 +5,8 @@ set(SRC_FILES "imp/cpp/AsyncOps.cpp" "imp/cpp/SocketOps.cpp" "imp/cpp/ProcessOps.cpp" + "imp/cpp/BinaryStreamReader.cpp" + "imp/cpp/BinaryStreamWriter.cpp" ) add_library(IACore STATIC ${SRC_FILES}) diff --git a/Src/IACore/imp/cpp/FileOps.cpp b/Src/IACore/imp/cpp/FileOps.cpp index e69de29..bb6cce3 100644 --- a/Src/IACore/imp/cpp/FileOps.cpp +++ b/Src/IACore/imp/cpp/FileOps.cpp @@ -0,0 +1,186 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 IAS (ias@iasoft.dev) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +namespace IACore +{ + UnorderedMap FileOps::s_mappedFiles; + + VOID FileOps::UnmapFile(IN PCUINT8 mappedPtr) + { + if (!s_mappedFiles.contains(mappedPtr)) + return; + const auto handle = s_mappedFiles.extract(mappedPtr)->second; +#if IA_PLATFORM_WINDOWS + +#elif IA_PLATFORM_UNIX +#else +# error "IACore FileOps does not support this platform" +#endif + } + + Expected FileOps::MapFile(IN CONST FilePath &path) + { +#if IA_PLATFORM_WINDOWS + + const auto handle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + + if (handle == INVALID_HANDLE_VALUE) + return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str())); + + auto hmap = CreateFileMappingW(handle, NULL, PAGE_READWRITE, 0, 0, NULL); + if (hmap == NULL) + { + CloseHandle(handle); + return MakeUnexpected(std::format("Failed to memory map {}", path.c_str())); + } + + const auto result = static_cast(MapViewOfFile(hmap, FILE_MAP_ALL_ACCESS, 0, 0, 0)); + if (result == NULL) + { + CloseHandle(handle); + CloseHandle(hmap); + return MakeUnexpected(std::format("Failed to memory map {}", path.c_str())); + } + s_mappedFiles[result] = (PVOID) handle; + return result; + +#elif IA_PLATFORM_UNIX + + const auto handle = open(path.c_str(), O_RDONLY); + if (handle == -1) + return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str())); + + void *addr = mmap(nullptr, 0, PROT_WRITE | PROT_READ, MAP_PRIVATE, handle, 0); + if (addr == MAP_FAILED) + { + close(handle); + return MakeUnexpected(std::format("Failed to memory map {}", path.c_str())); + } + const auto result = static_cast(addr); + s_mappedFiles[result] = (PVOID) ((UINT64) handle); + return result; + +#else +# error "IACore FileOps does not support this platform" +#endif + } + + Expected FileOps::MapReadonlyFile(IN CONST FilePath &path) + { + SIZE_T size; + return MapReadonlyFile(path, size); + } + + Expected FileOps::MapReadonlyFile(IN CONST FilePath &path, OUT SIZE_T &size) + { +#if IA_PLATFORM_WINDOWS + + const auto handle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + + if (handle == INVALID_HANDLE_VALUE) + return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str())); + + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(handle, &fileSize)) + { + CloseHandle(handle); + return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.c_str())); + } + size = static_cast(fileSize.QuadPart); + if (size == 0) + { + CloseHandle(handle); + return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.c_str())); + } + + auto hmap = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL); + if (hmap == NULL) + { + CloseHandle(handle); + return MakeUnexpected(std::format("Failed to memory map {}", path.c_str())); + } + + const auto result = static_cast(MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, 0)); + if (result == NULL) + { + CloseHandle(handle); + CloseHandle(hmap); + return MakeUnexpected(std::format("Failed to memory map {}", path.c_str())); + } + s_mappedFiles[result] = (PVOID) handle; + return result; + +#elif IA_PLATFORM_UNIX + + const auto handle = open(path.c_str(), O_RDONLY); + if (handle == -1) + return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str())); + struct stat sb; + if (fstat(handle, &sb) == -1) + { + close(handle); + return MakeUnexpected(std::format("Failed to get stats of {} for memory mapping", path.c_str())); + } + size = static_cast(sb.st_size); + if (size == 0) + { + close(handle); + return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.c_str())); + } + void *addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0); + if (addr == MAP_FAILED) + { + close(handle); + return MakeUnexpected(std::format("Failed to memory map {}", path.c_str())); + } + const auto result = static_cast(addr); + madvise(addr, size, MADV_SEQUENTIAL); + s_mappedFiles[result] = (PVOID) ((UINT64) handle); + return result; + +#else +# error "IACore FileOps does not support this platform" +#endif + } + + Expected FileOps::StreamToFile(IN CONST FilePath &path) + { + } + + Expected FileOps::StreamFromFile(IN CONST FilePath &path) + { + } + + Expected FileOps::ReadTextFile(IN CONST FilePath &path) + { + } + + Expected, String> FileOps::ReadBinaryFile(IN CONST FilePath &path) + { + } + + Expected FileOps::WriteTextFile(IN CONST FilePath &path, IN CONST String &contents) + { + } + + Expected FileOps::WriteBinaryFile(IN CONST FilePath &path, IN Span contents) + { + } +} // namespace IACore \ No newline at end of file diff --git a/Src/IACore/imp/cpp/SocketOps.cpp b/Src/IACore/imp/cpp/SocketOps.cpp index 7d1cf70..7ef4943 100644 --- a/Src/IACore/imp/cpp/SocketOps.cpp +++ b/Src/IACore/imp/cpp/SocketOps.cpp @@ -18,5 +18,23 @@ namespace IACore { - -} \ No newline at end of file + BOOL SocketOps::IsPortAvailable(IN UINT16 port, IN INT32 type) + { + SocketHandle sock = socket(AF_INET, type, IPPROTO_UDP); + if (!IS_VALID_SOCKET(sock)) + return false; + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + bool isFree = false; + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == 0) + isFree = true; + + CLOSE_SOCKET(sock); + + return isFree; + } +} // namespace IACore \ No newline at end of file diff --git a/Src/IACore/imp/cpp/StreamReader.cpp b/Src/IACore/imp/cpp/StreamReader.cpp new file mode 100644 index 0000000..9473eda --- /dev/null +++ b/Src/IACore/imp/cpp/StreamReader.cpp @@ -0,0 +1,59 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 IAS (ias@iasoft.dev) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include + +namespace IACore +{ + StreamReader::StreamReader(IN CONST FilePath &path) : m_storageType(EStorageType::OWNING_MMAP) + { + const auto t = FileOps::MapReadonlyFile(path, m_dataSize); + if (!t) + { + Logger::Error("Failed to memory map file {}", path.string()); + return; + } + m_data = *t; + } + + StreamReader::StreamReader(IN Vector &&data) + : m_owningVector(IA_MOVE(data)), m_storageType(EStorageType::OWNING_VECTOR) + { + m_data = m_owningVector.data(); + m_dataSize = m_owningVector.size(); + } + + StreamReader::StreamReader(IN Span data) + : m_data(data.data()), m_dataSize(data.size()), m_storageType(EStorageType::NON_OWNING) + { + } + + StreamReader::~StreamReader() + { + switch (m_storageType) + { + case EStorageType::OWNING_MMAP: + FileOps::UnmapFile(m_data); + break; + + case EStorageType::NON_OWNING: + case EStorageType::OWNING_VECTOR: + break; + } + } +} // namespace IACore \ No newline at end of file diff --git a/Src/IACore/imp/cpp/StreamWriter.cpp b/Src/IACore/imp/cpp/StreamWriter.cpp new file mode 100644 index 0000000..452d411 --- /dev/null +++ b/Src/IACore/imp/cpp/StreamWriter.cpp @@ -0,0 +1,88 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 IAS (ias@iasoft.dev) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include + +namespace IACore +{ + StreamWriter::StreamWriter() : m_storageType(EStorageType::OWNING_VECTOR) + { + m_owningVector.resize(m_capacity = 256); + m_buffer = m_owningVector.data(); + } + + StreamWriter::StreamWriter(IN Span data) + : m_buffer(data.data()), m_capacity(data.size()), m_storageType(EStorageType::NON_OWNING) + { + } + + StreamWriter::StreamWriter(IN CONST FilePath &path) : m_storageType(EStorageType::OWNING_MMAP) + { + const auto t = FileOps::MapFile(path); + if (!t) + { + Logger::Error("Failed to memory map file {}", path.string()); + return; + } + m_buffer = *t; + m_capacity = SIZE_MAX; + } + + StreamWriter::~StreamWriter() + { + switch (m_storageType) + { + case EStorageType::OWNING_MMAP: + FileOps::UnmapFile(m_buffer); + break; + + case EStorageType::OWNING_VECTOR: + case EStorageType::NON_OWNING: + break; + } + } + +#define HANDLE_OUT_OF_CAPACITY(_size) \ + if B_UNLIKELY ((m_cursor + _size) > m_capacity) \ + { \ + if (m_storageType == EStorageType::OWNING_VECTOR) \ + { \ + m_owningVector.resize(m_capacity + (_size << 1)); \ + m_capacity = m_owningVector.size(); \ + m_buffer = m_owningVector.data(); \ + } \ + else \ + return false; \ + } + + BOOL StreamWriter::Write(IN UINT8 byte, IN SIZE_T count) + { + HANDLE_OUT_OF_CAPACITY(count); + memset(&m_buffer[m_cursor], byte, count); + m_cursor += count; + return true; + } + + BOOL StreamWriter::Write(IN PCVOID buffer, IN SIZE_T size) + { + HANDLE_OUT_OF_CAPACITY(size); + memcpy(&m_buffer[m_cursor], buffer, size); + m_cursor += size; + return true; + } +} // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/BinaryReader.hpp b/Src/IACore/inc/IACore/BinaryReader.hpp deleted file mode 100644 index e7c4c3b..0000000 --- a/Src/IACore/inc/IACore/BinaryReader.hpp +++ /dev/null @@ -1,101 +0,0 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects -// Copyright (C) 2025 IAS (ias@iasoft.dev) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#pragma once - -#include - -namespace IACore { - - class BinaryReader { - public: - // --------------------------------------------------------------------- - // Construction (Zero Copy) - // --------------------------------------------------------------------- - - // Accepts Vector, std::array, or C-arrays automatically - BinaryReader(std::span data) - : m_span(data), m_cursor(0) {} - - // --------------------------------------------------------------------- - // Core API - // --------------------------------------------------------------------- - - // Generic Primitive Reader (Read(), Read(), etc.) - template - NO_DISCARD("Check for EOF") - Expected Read() { - constexpr SIZE_T size = sizeof(T); - - if B_UNLIKELY((m_cursor + size > m_span.size())) { - return MakeUnexpected(String("Unexpected EOF reading type")); - } - - T value; - // SAFE: memcpy handles alignment issues on ARM/Android automatically. - // Modern compilers optimize this into a single register load instruction. - std::memcpy(&value, &m_span[m_cursor], size); - - m_cursor += size; - return value; - } - - // Batch Read (Copy to external buffer) - tl::expected Read(PVOID buffer, SIZE_T size) { - if B_UNLIKELY((m_cursor + size > m_span.size())) { - return tl::make_unexpected(String("Unexpected EOF reading buffer")); - } - - std::memcpy(buffer, &m_span[m_cursor], size); - m_cursor += size; - return {}; - } - - // String Reader (Null Terminated or Length Prefixed) - tl::expected ReadString(SIZE_T length) { - if B_UNLIKELY((m_cursor + length > m_span.size())) { - return tl::make_unexpected(String("Unexpected EOF reading string")); - } - - // Create string from current pointer - String str(REINTERPRET(&m_span[m_cursor], const char*), length); - m_cursor += length; - return str; - } - - // --------------------------------------------------------------------- - // Navigation - // --------------------------------------------------------------------- - - VOID Skip(SIZE_T amount) { - // Clamp to end - m_cursor = std::min(m_cursor + amount, m_span.size()); - } - - VOID Seek(SIZE_T pos) { - if (pos > m_span.size()) m_cursor = m_span.size(); - else m_cursor = pos; - } - - SIZE_T Cursor() CONST { return m_cursor; } - SIZE_T Remaining() CONST { return m_span.size() - m_cursor; } - BOOL IsEOF() CONST { return m_cursor >= m_span.size(); } - - private: - std::span m_span; - SIZE_T m_cursor; - }; -} \ No newline at end of file diff --git a/Src/IACore/inc/IACore/BinaryWriter.hpp b/Src/IACore/inc/IACore/BinaryWriter.hpp deleted file mode 100644 index 24c60ec..0000000 --- a/Src/IACore/inc/IACore/BinaryWriter.hpp +++ /dev/null @@ -1,100 +0,0 @@ -// IACore-OSS; The Core Library for All IA Open Source Projects -// Copyright (C) 2025 IAS (ias@iasoft.dev) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#pragma once - -#include - -namespace IACore { - - class BinaryWriter { - public: - // Mode 1: Append to a Vector (Growing) - BinaryWriter(Vector& target) : m_buffer(&target), m_span({}), m_cursor(target.size()), m_isVector(true) {} - - // Mode 2: Write into existing fixed memory (No allocs) - BinaryWriter(std::span target) : m_buffer(nullptr), m_span(target), m_cursor(0), m_isVector(false) {} - - // --------------------------------------------------------------------- - // Core API - // --------------------------------------------------------------------- - - // Append T (Handling Endianness automatically if needed) - template - VOID Write(T value) { - // If we are Little Endian (x86/ARM), this compiles to a raw MOV/memcpy. - // If we need specific Endianness (Network Byte Order), use std::byteswap (C++23) - // or a simple swap helper. - // For a game engine, we usually stick to Native Endian (LE) for speed. - - CONST SIZE_T size = sizeof(T); - - if (m_isVector) { - // Vector guarantees contiguous memory, but push_back is slow for primitives. - // We resize and memcpy. - SIZE_T currentPos = m_buffer->size(); - m_buffer->resize(currentPos + size); - std::memcpy(m_buffer->data() + currentPos, &value, size); - } else { - // Fixed Buffer Safety Check - if B_UNLIKELY((m_cursor + size > m_span.size())) { - IA_PANIC("BinaryWriter Overflow"); - } - std::memcpy(m_span.data() + m_cursor, &value, size); - m_cursor += size; - } - } - - // Random Access Write (Replaces put32, put16, etc.) - template - VOID WriteAt(SIZE_T position, T value) { - PUINT8 ptr = GetPtrAt(position); - if (ptr) { - std::memcpy(ptr, &value, sizeof(T)); - } else { - IA_PANIC("BinaryWriter Out of Bounds Write"); - } - } - - VOID WriteBytes(CONST PVOID data, SIZE_T size) { - if (m_isVector) { - SIZE_T currentPos = m_buffer->size(); - m_buffer->resize(currentPos + size); - std::memcpy(m_buffer->data() + currentPos, data, size); - } else { - if B_UNLIKELY((m_cursor + size > m_span.size())) IA_PANIC("Overflow"); - std::memcpy(m_span.data() + m_cursor, data, size); - m_cursor += size; - } - } - - PUINT8 GetPtrAt(SIZE_T pos) { - if (m_isVector) { - if (pos >= m_buffer->size()) return nullptr; - return m_buffer->data() + pos; - } else { - if (pos >= m_span.size()) return nullptr; - return m_span.data() + pos; - } - } - - private: - Vector* m_buffer; - std::span m_span; - SIZE_T m_cursor; - BOOL m_isVector; - }; -} \ No newline at end of file diff --git a/Src/IACore/inc/IACore/FileOps.hpp b/Src/IACore/inc/IACore/FileOps.hpp index 4625bf6..b121700 100644 --- a/Src/IACore/inc/IACore/FileOps.hpp +++ b/Src/IACore/inc/IACore/FileOps.hpp @@ -16,342 +16,28 @@ #pragma once -#include - -#include -#include +#include +#include namespace IACore { - - namespace fs = std::filesystem; - - class File + class FileOps { public: - // Modern mapping of flags to standard IO streams - enum class EOpenFlags : UINT32 - { - Read = 1 << 0, // std::ios::in - Write = 1 << 1, // std::ios::out - Binary = 1 << 2, // std::ios::binary - Append = 1 << 3, // std::ios::app - Trunc = 1 << 4 // std::ios::trunc - }; + STATIC VOID UnmapFile(IN PCUINT8 mappedPtr); + STATIC Expected MapFile(IN CONST FilePath &path); + STATIC Expected MapReadonlyFile(IN CONST FilePath &path); + STATIC Expected MapReadonlyFile(IN CONST FilePath &path, OUT SIZE_T &size); - // --------------------------------------------------------------------- - // Static Helper API (The "One-Liners") - // --------------------------------------------------------------------- + STATIC Expected StreamToFile(IN CONST FilePath &path); + STATIC Expected StreamFromFile(IN CONST FilePath &path); - // Reads entire file into a binary vector - NO_DISCARD("Handle the error") - - STATIC tl::expected, String> ReadToVector(CONST String &path) - { - // 1. Check File Existence & Size - std::error_code ec; - auto fileSize = fs::file_size(path, ec); - - if (ec) - { - return tl::make_unexpected(String("File not found or inaccessible: ") + path); - } - - // 2. Open Stream - std::ifstream file(path, std::ios::binary); - if (!file.is_open()) - { - return tl::make_unexpected(String("Failed to open file handle: ") + path); - } - - // 3. Read - Vector buffer; - buffer.resize(CAST(fileSize, SIZE_T)); - file.read(REINTERPRET(buffer.data(), char *), fileSize); - if (file.fail()) - return tl::make_unexpected(String("Read error: ") + path); - - return buffer; - } - - // Reads entire file into a String - NO_DISCARD("Handle the error") - - STATIC tl::expected ReadToString(CONST String &path) - { - // Reuse the binary logic to avoid code duplication, then cast - auto result = ReadToVector(path); - if (!result) - { - return tl::make_unexpected(result.error()); - } - - // Efficient move into string (reinterpret_cast approach or move) - // Since Vector and String memory layout isn't guaranteed identical, we copy. - // (Though in many STL implementations you could theoretically steal the buffer, copying is safer) - String str(result->begin(), result->end()); - return str; - } - - // --------------------------------------------------------------------- - // Path Utilities (Replaces manual parsing) - // --------------------------------------------------------------------- - - // Old: ExtractFilenameFromPath - // New: true -> filename(), false -> stem() - template STATIC String ExtractFilename(CONST String &pathStr) - { - fs::path p(pathStr); - if CONSTEXPR (includeExtension) - { - return p.filename().string(); // "sprite.png" - } - else - { - return p.stem().string(); // "sprite" - } - } - - // Old: ExtractDirectoryFromPath - STATIC String ExtractDirectory(CONST String &pathStr) - { - fs::path p(pathStr); - return p.parent_path().string(); // "assets/textures" - } - - STATIC BOOL Exists(CONST String &pathStr) - { - std::error_code ec; - return fs::exists(pathStr, ec); - } - - public: - // --------------------------------------------------------------------- - // Instance API (For streaming/partial reads) - // --------------------------------------------------------------------- - - File() = default; - -#ifdef __cplusplus - File(CONST String &path, EOpenFlags flags) -#else - File(CONST String &path, UINT32 flags) -#endif - { - UNUSED(Open(path, flags)); - } - - ~File() - { - Close(); - } - -#ifdef __cplusplus - tl::expected Open(CONST String &path, EOpenFlags flags) -#else - tl::expected Open(CONST String &path, UINT32 flags) -#endif - { - Close(); // Ensure previous handle is closed - - std::ios_base::openmode mode = static_cast(0); - if ((UINT32) flags & (UINT32) EOpenFlags::Read) - mode |= std::ios::in; - if ((UINT32) flags & (UINT32) EOpenFlags::Write) - mode |= std::ios::out; - if ((UINT32) flags & (UINT32) EOpenFlags::Binary) - mode |= std::ios::binary; - if ((UINT32) flags & (UINT32) EOpenFlags::Append) - mode |= std::ios::app; - if ((UINT32) flags & (UINT32) EOpenFlags::Trunc) - mode |= std::ios::trunc; - - m_fs.open(path, mode); - - if (!m_fs.is_open()) - { - return tl::make_unexpected(String("Failed to open file: ") + path); - } - return {}; - } - - VOID Close() - { - if (m_fs.is_open()) - { - m_fs.close(); - } - } - - BOOL IsOpen() CONST - { - return m_fs.is_open(); - } - - // Returns number of bytes read - SIZE_T Read(PVOID buffer, SIZE_T size) - { - if B_UNLIKELY (!m_fs.is_open()) - return 0; - m_fs.read(REINTERPRET(buffer, char *), size); - return static_cast(m_fs.gcount()); - } - - VOID Write(CONST PVOID buffer, SIZE_T size) - { - if B_UNLIKELY (!m_fs.is_open()) - return; - m_fs.write(REINTERPRET(buffer, const char *), size); - } - - std::fstream *GetStreamHandle() - { - return &m_fs; - } + STATIC Expected ReadTextFile(IN CONST FilePath &path); + STATIC Expected, String> ReadBinaryFile(IN CONST FilePath &path); + STATIC Expected WriteTextFile(IN CONST FilePath &path, IN CONST String &contents); + STATIC Expected WriteBinaryFile(IN CONST FilePath &path, IN Span contents); private: - std::fstream m_fs; - }; - - class MMFile - { - public: - MMFile() = default; - - // RAII - Automatically unmap on destruction - ~MMFile() - { - Close(); - } - - // Disable copy (managing ownership of raw handles is messy) - MMFile(const MMFile &) = delete; - MMFile &operator=(const MMFile &) = delete; - - // Open and Map - bool Map(const std::string &filepath) - { - Close(); // Cleanup any existing map - -#if IA_PLATFORM_WINDOWS > 0 - // 1. Open File - hFile_ = ::CreateFileA(filepath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile_ == INVALID_HANDLE_VALUE) - return false; - - // 2. Get File Size - LARGE_INTEGER size; - if (!::GetFileSizeEx(hFile_, &size)) - { - Close(); - return false; - } - size_ = static_cast(size.QuadPart); - - // 3. Create Mapping Object - hMapping_ = ::CreateFileMappingA(hFile_, nullptr, PAGE_READWRITE, 0, 0, nullptr); - if (!hMapping_) - { - Close(); - return false; - } - - // 4. Map View - data_ = ::MapViewOfFile(hMapping_, FILE_MAP_ALL_ACCESS, 0, 0, 0); - if (!data_) - { - Close(); - return false; - } - -#else // LINUX / POSIX - // 1. Open File - fd_ = ::open(filepath.c_str(), O_RDWR); - if (fd_ == -1) - return false; - - // 2. Get File Size - struct stat sb; - if (fstat(fd_, &sb) == -1) - { - Close(); - return false; - } - size_ = static_cast(sb.st_size); - - // 3. mmap - // PROT_READ: Read only - // MAP_PRIVATE: Copy-on-write (safe if you accidentally modify, though we return const) - data_ = ::mmap(nullptr, size_, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd_, 0); - if (data_ == MAP_FAILED) - { - data_ = nullptr; - Close(); - return false; - } -#endif - return true; - } - - void Close() - { -#ifdef _WIN32 - if (data_) - { - ::UnmapViewOfFile(data_); - data_ = nullptr; - } - if (hMapping_) - { - ::CloseHandle(hMapping_); - hMapping_ = nullptr; - } - if (hFile_ != INVALID_HANDLE_VALUE) - { - ::CloseHandle(hFile_); - hFile_ = INVALID_HANDLE_VALUE; - } -#else - if (data_) - { - ::munmap(data_, size_); - data_ = nullptr; - } - if (fd_ != -1) - { - ::close(fd_); - fd_ = -1; - } -#endif - size_ = 0; - } - - // Accessors - const void *GetData() const - { - return data_; - } - - size_t GetSize() const - { - return size_; - } - - bool IsValid() const - { - return data_ != nullptr; - } - - private: - void *data_ = nullptr; - size_t size_ = 0; - -#ifdef _WIN32 - HANDLE hFile_ = INVALID_HANDLE_VALUE; - HANDLE hMapping_ = nullptr; -#else - int fd_ = -1; -#endif + STATIC UnorderedMap s_mappedFiles; }; } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/PCH.hpp b/Src/IACore/inc/IACore/PCH.hpp index 9a73c4c..95c19de 100644 --- a/Src/IACore/inc/IACore/PCH.hpp +++ b/Src/IACore/inc/IACore/PCH.hpp @@ -33,8 +33,10 @@ # include # include # include +# include # include # include +# include # include # include # include @@ -546,6 +548,9 @@ using ScopedLock = std::scoped_lock; using UniqueLock = std::unique_lock; using JoiningThread = std::jthread; +namespace FileSystem = std::filesystem; +using FilePath = FileSystem::path; + template using FormatterString = std::format_string; #endif diff --git a/Src/IACore/inc/IACore/SocketOps.hpp b/Src/IACore/inc/IACore/SocketOps.hpp index 0db9df3..879d3c6 100644 --- a/Src/IACore/inc/IACore/SocketOps.hpp +++ b/Src/IACore/inc/IACore/SocketOps.hpp @@ -73,24 +73,6 @@ namespace IACore } private: - STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type) - { - SocketHandle sock = socket(AF_INET, type, IPPROTO_UDP); - if (!IS_VALID_SOCKET(sock)) - return false; - - sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = htonl(INADDR_ANY); - - bool isFree = false; - if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == 0) - isFree = true; - - CLOSE_SOCKET(sock); - - return isFree; - } + STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type); }; } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/StreamReader.hpp b/Src/IACore/inc/IACore/StreamReader.hpp new file mode 100644 index 0000000..3b917c4 --- /dev/null +++ b/Src/IACore/inc/IACore/StreamReader.hpp @@ -0,0 +1,104 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 IAS (ias@iasoft.dev) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +namespace IACore +{ + class StreamReader + { + enum class EStorageType + { + NON_OWNING, + OWNING_MMAP, + OWNING_VECTOR, + }; + + public: + INLINE Expected Read(IN PVOID buffer, IN SIZE_T size); + template NO_DISCARD("Check for EOF") Expected Read(); + + VOID Skip(SIZE_T amount) + { + m_cursor = std::min(m_cursor + amount, m_dataSize); + } + + VOID Seek(SIZE_T pos) + { + m_cursor = (pos > m_dataSize) ? m_dataSize : pos; + } + + SIZE_T Cursor() CONST + { + return m_cursor; + } + + SIZE_T Size() CONST + { + return m_dataSize; + } + + SIZE_T Remaining() CONST + { + return m_dataSize - m_cursor; + } + + BOOL IsEOF() CONST + { + return m_cursor >= m_dataSize; + } + + public: + StreamReader(IN CONST FilePath &path); + explicit StreamReader(IN Vector &&data); + explicit StreamReader(IN Span data); + ~StreamReader(); + + private: + PCUINT8 m_data{}; + SIZE_T m_cursor{}; + SIZE_T m_dataSize{}; + Vector m_owningVector; + CONST EStorageType m_storageType; + }; + + Expected StreamReader::Read(IN PVOID buffer, IN SIZE_T size) + { + if B_UNLIKELY ((m_cursor + size > m_dataSize)) + return MakeUnexpected(String("Unexpected EOF while reading")); + + std::memcpy(buffer, &m_data[m_cursor], size); + m_cursor += size; + + return {}; + } + + template NO_DISCARD("Check for EOF") Expected StreamReader::Read() + { + constexpr SIZE_T size = sizeof(T); + + if B_UNLIKELY ((m_cursor + size > m_dataSize)) + return MakeUnexpected(String("Unexpected EOF while reading")); + + T value; + std::memcpy(&value, &m_data[m_cursor], size); + m_cursor += size; + + return value; + } +} // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/StreamWriter.hpp b/Src/IACore/inc/IACore/StreamWriter.hpp new file mode 100644 index 0000000..a4d8a31 --- /dev/null +++ b/Src/IACore/inc/IACore/StreamWriter.hpp @@ -0,0 +1,65 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 IAS (ias@iasoft.dev) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +namespace IACore +{ + class StreamWriter + { + enum class EStorageType + { + NON_OWNING, + OWNING_MMAP, + OWNING_VECTOR, + }; + + public: + BOOL Write(IN UINT8 byte, IN SIZE_T count); + BOOL Write(IN PCVOID buffer, IN SIZE_T size); + template BOOL Write(IN CONST T &value); + + PCUINT8 Data() CONST + { + return m_buffer; + } + + SIZE_T Cursor() CONST + { + return m_cursor; + } + + public: + StreamWriter(); + explicit StreamWriter(IN Span data); + explicit StreamWriter(IN CONST FilePath &path); + ~StreamWriter(); + + private: + PUINT8 m_buffer{}; + SIZE_T m_cursor{}; + SIZE_T m_capacity{}; + Vector m_owningVector; + CONST EStorageType m_storageType; + }; + + template BOOL StreamWriter::Write(IN CONST T &value) + { + return Write(&value, sizeof(T)); + } +} // namespace IACore \ No newline at end of file diff --git a/Tests/Unit/BinaryReader.cpp b/Tests/Unit/BinaryStreamReader.cpp similarity index 93% rename from Tests/Unit/BinaryReader.cpp rename to Tests/Unit/BinaryStreamReader.cpp index 6e14057..5840a79 100644 --- a/Tests/Unit/BinaryReader.cpp +++ b/Tests/Unit/BinaryStreamReader.cpp @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include +#include #include @@ -24,7 +24,7 @@ using namespace IACore; // Test Block Definition // ----------------------------------------------------------------------------- -IAT_BEGIN_BLOCK(Core, BinaryReader) +IAT_BEGIN_BLOCK(Core, BinaryStreamReader) // ------------------------------------------------------------------------- // 1. Basic Primitive Reading (UINT8) @@ -32,7 +32,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader) BOOL TestReadUint8() { UINT8 data[] = { 0xAA, 0xBB, 0xCC }; - BinaryReader reader(data); + BinaryStreamReader reader(data); // Read First Byte auto val1 = reader.Read(); @@ -54,7 +54,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader) { // 0x04030201 in Little Endian memory layout UINT8 data[] = { 0x01, 0x02, 0x03, 0x04 }; - BinaryReader reader(data); + BinaryStreamReader reader(data); auto val = reader.Read(); IAT_CHECK(val.has_value()); @@ -79,7 +79,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader) UINT8 data[4]; std::memcpy(data, &pi, 4); - BinaryReader reader(data); + BinaryStreamReader reader(data); auto val = reader.Read(); IAT_CHECK(val.has_value()); @@ -94,7 +94,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader) BOOL TestReadString() { const char* raw = "HelloIA"; - BinaryReader reader(std::span((const UINT8*)raw, 7)); + BinaryStreamReader reader(std::span((const UINT8*)raw, 7)); // Read "Hello" (5 bytes) auto str = reader.ReadString(5); @@ -116,7 +116,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader) { UINT8 src[] = { 1, 2, 3, 4, 5 }; UINT8 dst[3] = { 0 }; - BinaryReader reader(src); + BinaryStreamReader reader(src); // Read 3 bytes into dst auto res = reader.Read(dst, 3); @@ -139,7 +139,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader) BOOL TestNavigation() { UINT8 data[10] = { 0 }; // Zero init - BinaryReader reader(data); + BinaryStreamReader reader(data); IAT_CHECK_EQ(reader.Remaining(), (SIZE_T)10); @@ -168,7 +168,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader) BOOL TestBoundaryChecks() { UINT8 data[] = { 0x00, 0x00 }; - BinaryReader reader(data); + BinaryStreamReader reader(data); // Valid read UNUSED(reader.Read()); @@ -213,7 +213,7 @@ int main(int argc, char* argv[]) // Define runner (StopOnFail=false, Verbose=true) ia::iatest::runner testRunner; - // Run the BinaryReader block + // Run the BinaryStreamReader block testRunner.testBlock(); return 0; diff --git a/Tests/Unit/BinaryWriter.cpp b/Tests/Unit/BinaryStreamWriter.cpp similarity index 93% rename from Tests/Unit/BinaryWriter.cpp rename to Tests/Unit/BinaryStreamWriter.cpp index 9378aa6..11dd941 100644 --- a/Tests/Unit/BinaryWriter.cpp +++ b/Tests/Unit/BinaryStreamWriter.cpp @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include +#include #include @@ -24,7 +24,7 @@ using namespace IACore; // Test Block Definition // ----------------------------------------------------------------------------- -IAT_BEGIN_BLOCK(Core, BinaryWriter) +IAT_BEGIN_BLOCK(Core, BinaryStreamWriter) // ------------------------------------------------------------------------- // 1. Vector Mode (Dynamic Growth) @@ -33,7 +33,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter) { std::vector buffer; // Start empty - BinaryWriter writer(buffer); + BinaryStreamWriter writer(buffer); // Write 1 Byte writer.Write(0xAA); @@ -62,7 +62,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter) { // Vector starts with existing data std::vector buffer = { 0x01, 0x02 }; - BinaryWriter writer(buffer); + BinaryStreamWriter writer(buffer); // Should append to end, not overwrite 0x01 writer.Write(0x03); @@ -83,7 +83,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter) // Initialize with zeros std::memset(rawData, 0, 10); - BinaryWriter writer(rawData); // Implicit conversion to span + BinaryStreamWriter writer(rawData); // Implicit conversion to span // Write UINT8 writer.Write(0xFF); @@ -106,7 +106,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter) BOOL TestWriteBytes() { std::vector buffer; - BinaryWriter writer(buffer); + BinaryStreamWriter writer(buffer); const char* msg = "IA"; writer.WriteBytes((PVOID)msg, 2); @@ -126,7 +126,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter) BOOL TestRandomAccess() { std::vector buffer; - BinaryWriter writer(buffer); + BinaryStreamWriter writer(buffer); // Fill with placeholders writer.Write(0xFFFFFFFF); @@ -152,7 +152,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter) BOOL TestWriteFloat() { std::vector buffer; - BinaryWriter writer(buffer); + BinaryStreamWriter writer(buffer); FLOAT32 val = 1.234f; writer.Write(val); @@ -174,7 +174,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter) BOOL TestGetPtr() { std::vector buffer = { 0x10, 0x20, 0x30 }; - BinaryWriter writer(buffer); + BinaryStreamWriter writer(buffer); PUINT8 p1 = writer.GetPtrAt(1); IAT_CHECK(p1 != nullptr); @@ -216,7 +216,7 @@ int main(int argc, char* argv[]) ia::iatest::runner testRunner; // Run the BinaryReader block - testRunner.testBlock(); + testRunner.testBlock(); return 0; } \ No newline at end of file diff --git a/Tests/Unit/CMakeLists.txt b/Tests/Unit/CMakeLists.txt index 5801492..0a9b626 100644 --- a/Tests/Unit/CMakeLists.txt +++ b/Tests/Unit/CMakeLists.txt @@ -13,21 +13,21 @@ set_target_properties(${TEST_NAME_PREFIX}CCompile PROPERTIES target_link_libraries(${TEST_NAME_PREFIX}CCompile PRIVATE IACore) -# ------------------------------------------------ -# Unit: BinaryReader -# ------------------------------------------------ -add_executable(${TEST_NAME_PREFIX}BinaryReader "BinaryReader.cpp") -target_link_libraries(${TEST_NAME_PREFIX}BinaryReader PRIVATE IACore) -target_compile_options(${TEST_NAME_PREFIX}BinaryReader PRIVATE -fexceptions) -set_target_properties(${TEST_NAME_PREFIX}BinaryReader PROPERTIES USE_EXCEPTIONS ON) - -# ------------------------------------------------ -# Unit: BinaryWriter -# ------------------------------------------------ -add_executable(${TEST_NAME_PREFIX}BinaryWriter "BinaryWriter.cpp") -target_link_libraries(${TEST_NAME_PREFIX}BinaryWriter PRIVATE IACore) -target_compile_options(${TEST_NAME_PREFIX}BinaryWriter PRIVATE -fexceptions) -set_target_properties(${TEST_NAME_PREFIX}BinaryWriter PROPERTIES USE_EXCEPTIONS ON) +## ------------------------------------------------ +## Unit: BinaryReader +## ------------------------------------------------ +#add_executable(${TEST_NAME_PREFIX}BinaryReader "BinaryReader.cpp") +#target_link_libraries(${TEST_NAME_PREFIX}BinaryReader PRIVATE IACore) +#target_compile_options(${TEST_NAME_PREFIX}BinaryReader PRIVATE -fexceptions) +#set_target_properties(${TEST_NAME_PREFIX}BinaryReader PROPERTIES USE_EXCEPTIONS ON) +# +## ------------------------------------------------ +## Unit: BinaryWriter +## ------------------------------------------------ +#add_executable(${TEST_NAME_PREFIX}BinaryWriter "BinaryWriter.cpp") +#target_link_libraries(${TEST_NAME_PREFIX}BinaryWriter PRIVATE IACore) +#target_compile_options(${TEST_NAME_PREFIX}BinaryWriter PRIVATE -fexceptions) +#set_target_properties(${TEST_NAME_PREFIX}BinaryWriter PROPERTIES USE_EXCEPTIONS ON) # ------------------------------------------------ # Unit: Environment