From 05c4f7025f0098e0511502ba8fe5b9f035672238 Mon Sep 17 00:00:00 2001 From: Ronald Caesar Date: Sat, 23 Aug 2025 17:23:33 -0400 Subject: [PATCH] Major project restructuring Remove unecessary files and made the tree much more cleaner. Signed-off-by: Ronald Caesar --- CMakeLists.txt | 15 +- core/Base/Assert.h | 101 ---- core/Base/BoundedQueue.h | 242 --------- core/Base/Config.cpp | 89 ---- core/Base/Enum.h | 157 ------ core/Base/IoFile.cpp | 428 --------------- core/Base/IoFile.h | 222 -------- core/Base/PathUtil.cpp | 117 ----- core/Base/PolyfillThread.h | 373 ------------- core/Base/StringUtil.cpp | 65 --- core/Base/Thread.cpp | 214 -------- core/Base/Thread.h | 43 -- core/arm64/CMakeLists.txt | 8 + core/arm64/guest.cpp | 1 - core/arm64/guest.h | 1 + core/arm64/isa.cpp | 9 +- core/arm64/isa.h | 6 +- core/arm64/mmu.cpp | 2 +- core/arm64/mmu.h | 2 +- core/{Base => common}/Arch.h | 3 +- core/{Base => common}/Assert.cpp | 45 +- core/common/Assert.h | 121 +++++ core/common/BoundedQueue.h | 260 +++++++++ core/common/CMakeLists.txt | 16 + core/common/Config.cpp | 108 ++++ core/{Base => common}/Config.h | 5 +- core/common/Enum.h | 153 ++++++ core/{Base => common}/Error.h | 5 +- core/common/IoFile.cpp | 492 ++++++++++++++++++ core/common/IoFile.h | 231 ++++++++ core/{Base => common}/Logging/Backend.cpp | 10 +- core/{Base => common}/Logging/Backend.h | 2 +- core/{Base => common}/Logging/Filter.cpp | 2 +- core/{Base => common}/Logging/Filter.h | 0 core/{Base => common}/Logging/Log.h | 2 +- core/{Base => common}/Logging/LogEntry.h | 0 core/{Base => common}/Logging/LogTypes.h | 2 +- .../Logging/TextFormatter.cpp | 4 +- core/{Base => common}/Logging/TextFormatter.h | 0 core/common/PathUtil.cpp | 134 +++++ core/{Base => common}/PathUtil.h | 47 +- core/common/PolyfillThread.h | 385 ++++++++++++++ core/common/StringUtil.cpp | 71 +++ core/{Base => common}/StringUtil.h | 15 +- core/common/Thread.cpp | 228 ++++++++ core/common/Thread.h | 45 ++ core/{Base => common}/Types.h | 33 +- core/{Base => common}/swap.h | 404 +++++++------- core/frontend/CMakeLists.txt | 11 + {gui => core/frontend}/color.cpp | 0 {gui => core/frontend}/color.h | 2 + {gui => core/frontend}/gui.cpp | 4 +- {gui => core/frontend}/gui.h | 2 +- {gui => core/frontend}/panels.cpp | 4 +- {gui => core/frontend}/panels.h | 2 + core/host/CMakeLists.txt | 13 + core/{ => host}/memory/arena.cpp | 6 +- core/{ => host}/memory/arena.h | 14 +- core/host/memory/arena_allocator.h | 75 +++ core/main.cpp | 14 +- core/memory/arena_allocator.h | 68 --- gui/CMakeLists.txt | 18 - 62 files changed, 2698 insertions(+), 2453 deletions(-) delete mode 100644 core/Base/Assert.h delete mode 100644 core/Base/BoundedQueue.h delete mode 100644 core/Base/Config.cpp delete mode 100644 core/Base/Enum.h delete mode 100644 core/Base/IoFile.cpp delete mode 100644 core/Base/IoFile.h delete mode 100644 core/Base/PathUtil.cpp delete mode 100644 core/Base/PolyfillThread.h delete mode 100644 core/Base/StringUtil.cpp delete mode 100644 core/Base/Thread.cpp delete mode 100644 core/Base/Thread.h create mode 100644 core/arm64/CMakeLists.txt rename core/{Base => common}/Arch.h (79%) rename core/{Base => common}/Assert.cpp (51%) create mode 100644 core/common/Assert.h create mode 100644 core/common/BoundedQueue.h create mode 100644 core/common/CMakeLists.txt create mode 100644 core/common/Config.cpp rename core/{Base => common}/Config.h (87%) create mode 100644 core/common/Enum.h rename core/{Base => common}/Error.h (92%) create mode 100644 core/common/IoFile.cpp create mode 100644 core/common/IoFile.h rename core/{Base => common}/Logging/Backend.cpp (98%) rename core/{Base => common}/Logging/Backend.h (96%) rename core/{Base => common}/Logging/Filter.cpp (99%) rename core/{Base => common}/Logging/Filter.h (100%) rename core/{Base => common}/Logging/Log.h (99%) rename core/{Base => common}/Logging/LogEntry.h (100%) rename core/{Base => common}/Logging/LogTypes.h (98%) rename core/{Base => common}/Logging/TextFormatter.cpp (97%) rename core/{Base => common}/Logging/TextFormatter.h (100%) create mode 100644 core/common/PathUtil.cpp rename core/{Base => common}/PathUtil.h (51%) create mode 100644 core/common/PolyfillThread.h create mode 100644 core/common/StringUtil.cpp rename core/{Base => common}/StringUtil.h (52%) create mode 100644 core/common/Thread.cpp create mode 100644 core/common/Thread.h rename core/{Base => common}/Types.h (51%) rename core/{Base => common}/swap.h (61%) create mode 100644 core/frontend/CMakeLists.txt rename {gui => core/frontend}/color.cpp (100%) rename {gui => core/frontend}/color.h (99%) rename {gui => core/frontend}/gui.cpp (99%) mode change 100755 => 100644 rename {gui => core/frontend}/gui.h (99%) rename {gui => core/frontend}/panels.cpp (98%) mode change 100755 => 100644 rename {gui => core/frontend}/panels.h (98%) mode change 100755 => 100644 create mode 100644 core/host/CMakeLists.txt rename core/{ => host}/memory/arena.cpp (94%) rename core/{ => host}/memory/arena.h (87%) create mode 100644 core/host/memory/arena_allocator.h delete mode 100644 core/memory/arena_allocator.h delete mode 100644 gui/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fc63a1..aafe2c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,17 +25,19 @@ project(Pound) find_package(fmt 10.2.1 CONFIG) find_package(SDL3 3.2.10 CONFIG) find_package(toml11 4.4.0 CONFIG) - -include_directories(core) add_subdirectory(3rd_Party) -file(GLOB_RECURSE Core core/*.cpp core/*.h) - add_executable(Pound - ${Core} + core/main.cpp ) + +add_subdirectory(core/arm64) +add_subdirectory(core/common) +add_subdirectory(core/frontend) +add_subdirectory(core/host) + target_compile_options(Pound PRIVATE -Wall -Wpedantic -Wshadow -Wpointer-arith @@ -44,8 +46,6 @@ target_compile_options(Pound PRIVATE -Wall -Wpedantic -Wconversion ) -target_precompile_headers(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/core/Base/Types.h) - # Link libraries target_link_libraries(Pound PRIVATE fmt::fmt SDL3::SDL3 toml11::toml11) @@ -79,4 +79,3 @@ find_package(OpenGL REQUIRED) target_link_libraries(Pound PRIVATE OpenGL::GL) # add ./gui directory -add_subdirectory(gui) diff --git a/core/Base/Assert.h b/core/Base/Assert.h deleted file mode 100644 index b8595de..0000000 --- a/core/Base/Assert.h +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -#pragma once - -#include "Logging/Log.h" - -// Sometimes we want to try to continue even after hitting an assert. -// However touching this file yields a global recompilation as this header is included almost -// everywhere. So let's just move the handling of the failed assert to a single cpp file. - -void assert_fail_impl(); -void throw_fail_impl(); -[[noreturn]] void unreachable_impl(); -void assert_fail_debug_msg(const std::string& msg); -void throw_fail_debug_msg(const std::string& msg); - -#ifdef _MSC_VER -#define POUND_NO_INLINE __declspec(noinline) -#else -#define POUND_NO_INLINE __attribute__((noinline)) -#endif - -#define THROW(_a_) \ - ([&]() POUND_NO_INLINE { \ - if (!(_a_)) [[unlikely]] { \ - LOG_CRITICAL(Debug, "Assertion Failed!"); \ - throw_fail_impl(); \ - } \ - }) - -#define ASSERT(_a_) \ - ([&]() POUND_NO_INLINE { \ - if (!(_a_)) [[unlikely]] { \ - LOG_CRITICAL(Debug, "Assertion Failed!"); \ - assert_fail_impl(); \ - } \ - }) - -#define THROW_MSG(_a_, ...) \ - ([&]() POUND_NO_INLINE { \ - if (!(_a_)) [[unlikely]] { \ - LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \ - throw_fail_impl(); \ - } \ - }) - -#define ASSERT_MSG(_a_, ...) \ - ([&]() POUND_NO_INLINE { \ - if (!(_a_)) [[unlikely]] { \ - LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \ - assert_fail_impl(); \ - } \ - }) - -#define UNREACHABLE() \ - do { \ - LOG_CRITICAL(Debug, "Unreachable code!"); \ - unreachable_impl(); \ - } while (0) - -#define UNREACHABLE_MSG(...) \ - do { \ - LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \ - unreachable_impl(); \ - } while (0) - -#ifdef _DEBUG -#define DEBUG_ASSERT(_a_) ASSERT(_a_) -#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__) -#else // not debug -#define DEBUG_ASSERT(_a_) \ - do { \ - } while (0) -#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \ - do { \ - } while (0) -#endif - -#define UNIMPLEMENTED() THROW_MSG(false, "Unimplemented code!") -#define UNIMPLEMENTED_MSG(...) THROW_MSG(false, __VA_ARGS__) - -#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!") -#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__) - -// If the assert is ignored, execute _b_ -#define ASSERT_OR_EXECUTE(_a_, _b_) \ - do { \ - ASSERT(_a_); \ - if (!(_a_)) [[unlikely]] { \ - _b_ \ - } \ - } while (0) - -// If the assert is ignored, execute _b_ -#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \ - do { \ - ASSERT_MSG(_a_, __VA_ARGS__); \ - if (!(_a_)) [[unlikely]] { \ - _b_ \ - } \ - } while (0) diff --git a/core/Base/BoundedQueue.h b/core/Base/BoundedQueue.h deleted file mode 100644 index 72c0952..0000000 --- a/core/Base/BoundedQueue.h +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -#pragma once - -#include "PolyfillThread.h" - -namespace Base { - -namespace detail { -constexpr size_t DefaultCapacity = 0x1000; -} // namespace detail - -template -class SPSCQueue { - static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two."); - -public: - template - bool TryEmplace(Args&&... args) { - return Emplace(std::forward(args)...); - } - - template - void EmplaceWait(Args&&... args) { - Emplace(std::forward(args)...); - } - - bool TryPop(T& t) { - return Pop(t); - } - - bool PopWait(T& t) { - return Pop(t); - } - - bool PopWait(T& t, std::stop_token stopToken) { - return Pop(t, stopToken); - } - - T PopWait() { - T t; - Pop(t); - return t; - } - - T PopWait(std::stop_token stopToken) { - T t; - Pop(t, stopToken); - return t; - } - -private: - enum class PushMode { - Try, - Wait, - Count - }; - - enum class PopMode { - Try, - Wait, - WaitWithStopToken, - Count - }; - - template - bool Emplace(Args&&... args) { - const size_t write_index = m_write_index.load(std::memory_order::relaxed); - - if constexpr (Mode == PushMode::Try) { - // Check if we have free slots to write to. - if ((write_index - m_read_index.load(std::memory_order::acquire)) == Capacity) { - return false; - } - } else if constexpr (Mode == PushMode::Wait) { - // Wait until we have free slots to write to. - std::unique_lock lock{producer_cv_mutex}; - producer_cv.wait(lock, [this, write_index] { - return (write_index - m_read_index.load(std::memory_order::acquire)) < Capacity; - }); - } else { - static_assert(Mode < PushMode::Count, "Invalid PushMode."); - } - - // Determine the position to write to. - const size_t pos = write_index % Capacity; - - // Emplace into the queue. - new (std::addressof(m_data[pos])) T(std::forward(args)...); - - // Increment the write index. - ++m_write_index; - - // Notify the consumer that we have pushed into the queue. - std::scoped_lock lock{consumer_cv_mutex}; - consumer_cv.notify_one(); - - return true; - } - - template - bool Pop(T& t, [[maybe_unused]] std::stop_token stop_token = {}) { - const size_t read_index = m_read_index.load(std::memory_order::relaxed); - - if constexpr (Mode == PopMode::Try) { - // Check if the queue is empty. - if (read_index == m_write_index.load(std::memory_order::acquire)) { - return false; - } - } else if constexpr (Mode == PopMode::Wait) { - // Wait until the queue is not empty. - std::unique_lock lock{consumer_cv_mutex}; - consumer_cv.wait(lock, [this, read_index] { - return read_index != m_write_index.load(std::memory_order::acquire); - }); - } else if constexpr (Mode == PopMode::WaitWithStopToken) { - // Wait until the queue is not empty. - std::unique_lock lock{consumer_cv_mutex}; - Base::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] { - return read_index != m_write_index.load(std::memory_order::acquire); - }); - if (stop_token.stop_requested()) { - return false; - } - } else { - static_assert(Mode < PopMode::Count, "Invalid PopMode."); - } - - // Determine the position to read from. - const size_t pos = read_index % Capacity; - - // Pop the data off the queue, moving it. - t = std::move(m_data[pos]); - - // Increment the read index. - ++m_read_index; - - // Notify the producer that we have popped off the queue. - std::scoped_lock lock{producer_cv_mutex}; - producer_cv.notify_one(); - - return true; - } - - alignas(128) std::atomic_size_t m_read_index{0}; - alignas(128) std::atomic_size_t m_write_index{0}; - - std::array m_data; - - std::condition_variable_any producer_cv; - std::mutex producer_cv_mutex; - std::condition_variable_any consumer_cv; - std::mutex consumer_cv_mutex; -}; - -template -class MPSCQueue { -public: - template - bool TryEmplace(Args&&... args) { - std::scoped_lock lock{writeMutex}; - return spscQueue.TryEmplace(std::forward(args)...); - } - - template - void EmplaceWait(Args&&... args) { - std::scoped_lock lock{writeMutex}; - spscQueue.EmplaceWait(std::forward(args)...); - } - - bool TryPop(T& t) { - return spscQueue.TryPop(t); - } - - bool PopWait(T& t) { - return spscQueue.PopWait(t); - } - - bool PopWait(T& t, std::stop_token stop_token) { - return spscQueue.PopWait(t, stop_token); - } - - T PopWait() { - return spscQueue.PopWait(); - } - - T PopWait(std::stop_token stop_token) { - return spscQueue.PopWait(stop_token); - } - -private: - SPSCQueue spscQueue; - std::mutex writeMutex; -}; - -template -class MPMCQueue { -public: - template - bool TryEmplace(Args&&... args) { - std::scoped_lock lock{ writeMutex }; - return spscQueue.TryEmplace(std::forward(args)...); - } - - template - void EmplaceWait(Args&&... args) { - std::scoped_lock lock{ writeMutex }; - spscQueue.EmplaceWait(std::forward(args)...); - } - - bool TryPop(T& t) { - std::scoped_lock lock{ readMutex }; - return spscQueue.TryPop(t); - } - - bool PopWait(T& t) { - std::scoped_lock lock{ readMutex }; - return spscQueue.PopWait(t); - } - - bool PopWait(T& t, std::stop_token stopToken) { - std::scoped_lock lock{ readMutex }; - return spscQueue.PopWait(t, stopToken); - } - - T PopWait() { - std::scoped_lock lock{ readMutex }; - return spscQueue.PopWait(); - } - - T PopWait(std::stop_token stop_token) { - std::scoped_lock lock{ readMutex }; - return spscQueue.PopWait(stop_token); - } - -private: - SPSCQueue spscQueue; - std::mutex writeMutex; - std::mutex readMutex; -}; - -} // namespace Base diff --git a/core/Base/Config.cpp b/core/Base/Config.cpp deleted file mode 100644 index b9b37ee..0000000 --- a/core/Base/Config.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2025 Pound Emulator Project. All rights reserved. - -#include "Config.h" - -#include -#include - -namespace Config { - -static int widthWindow = 640; - -static int heightWindow = 480; - -static bool logAdvanced = false; - -static std::string typeLog = "async"; - -int windowWidth() { - return widthWindow; -} - -int windowHeight() { - return heightWindow; -} - -bool isLogAdvanced() { - return logAdvanced; -} - -std::string logType() { - return typeLog; -} - -void Load(const std::filesystem::path& path) { - // If the configuration file does not exist, create it and return - std::error_code error; - if (!std::filesystem::exists(path, error)) { - Save(path); - return; - } - - toml::value data; - - try { - data = toml::parse(path); - } catch (std::exception& ex) { - fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); - return; - } - if (data.contains("General")) { - const toml::value& general = data.at("General"); - - widthWindow = toml::find_or(general, "Window Width", 640); - heightWindow = toml::find_or(general, "Window Height", 480); - - logAdvanced = toml::find_or(general, "Advanced Log", false); - typeLog = toml::find_or(general, "Log Type", "async"); - } -} - -void Save(const std::filesystem::path& path) { - toml::ordered_value data; - - std::error_code error; - if (std::filesystem::exists(path, error)) { - try { - data = toml::parse(path); - } catch (const std::exception& ex) { - fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what()); - return; - } - } else { - if (error) { - fmt::print("Filesystem error: {}\n", error.message()); - } - fmt::print("Saving new configuration file {}\n", path.string()); - } - - data["General"]["Window Width"] = widthWindow; - data["General"]["Window Height"] = heightWindow; - data["General"]["Advanced Log"] = logAdvanced; - data["General"]["Log Type"] = typeLog; - - std::ofstream file(path, std::ios::binary); - file << data; - file.close(); -} - -} // namespace Config diff --git a/core/Base/Enum.h b/core/Base/Enum.h deleted file mode 100644 index cbefb51..0000000 --- a/core/Base/Enum.h +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -#pragma once - -#define DECLARE_ENUM_FLAG_OPERATORS(type) \ - [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) | static_cast(b)); \ - } \ - [[nodiscard]] constexpr type operator&(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) & static_cast(b)); \ - } \ - [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) ^ static_cast(b)); \ - } \ - [[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) << static_cast(b)); \ - } \ - [[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) >> static_cast(b)); \ - } \ - constexpr type& operator|=(type& a, type b) noexcept { \ - a = a | b; \ - return a; \ - } \ - constexpr type& operator&=(type& a, type b) noexcept { \ - a = a & b; \ - return a; \ - } \ - constexpr type& operator^=(type& a, type b) noexcept { \ - a = a ^ b; \ - return a; \ - } \ - constexpr type& operator<<=(type& a, type b) noexcept { \ - a = a << b; \ - return a; \ - } \ - constexpr type& operator>>=(type& a, type b) noexcept { \ - a = a >> b; \ - return a; \ - } \ - [[nodiscard]] constexpr type operator~(type key) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(~static_cast(key)); \ - } \ - [[nodiscard]] constexpr bool True(type key) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(key) != 0; \ - } \ - [[nodiscard]] constexpr bool False(type key) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(key) == 0; \ - } - -namespace Base { - -template -class Flags { -public: - using IntType = std::underlying_type_t; - - Flags() {} - - Flags(IntType t) : m_bits(t) {} - - template - Flags(T f, Tx... fx) { - set(f, fx...); - } - - template - void set(Tx... fx) { - m_bits |= bits(fx...); - } - - void set(Flags flags) { - m_bits |= flags.m_bits; - } - - template - void clr(Tx... fx) { - m_bits &= ~bits(fx...); - } - - void clr(Flags flags) { - m_bits &= ~flags.m_bits; - } - - template - bool any(Tx... fx) const { - return (m_bits & bits(fx...)) != 0; - } - - template - bool all(Tx... fx) const { - const IntType mask = bits(fx...); - return (m_bits & mask) == mask; - } - - bool test(T f) const { - return any(f); - } - - bool isClear() const { - return m_bits == 0; - } - - void clrAll() { - m_bits = 0; - } - - u32 raw() const { - return m_bits; - } - - Flags operator&(const Flags& other) const { - return Flags(m_bits & other.m_bits); - } - - Flags operator|(const Flags& other) const { - return Flags(m_bits | other.m_bits); - } - - Flags operator^(const Flags& other) const { - return Flags(m_bits ^ other.m_bits); - } - - bool operator==(const Flags& other) const { - return m_bits == other.m_bits; - } - - bool operator!=(const Flags& other) const { - return m_bits != other.m_bits; - } - -private: - IntType m_bits = 0; - - static IntType bit(T f) { - return IntType(1) << static_cast(f); - } - - template - static IntType bits(T f, Tx... fx) { - return bit(f) | bits(fx...); - } - - static IntType bits() { - return 0; - } -}; - -} // namespace Base diff --git a/core/Base/IoFile.cpp b/core/Base/IoFile.cpp deleted file mode 100644 index 72b7d08..0000000 --- a/core/Base/IoFile.cpp +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -#include - -#include "IoFile.h" - -#include "Assert.h" -#include "Error.h" -#include "Logging/Log.h" -#include "PathUtil.h" - -#ifdef _WIN32 -#define ftruncate _chsize_s -#define fsync _commit - -#include -#include -#include -#else -#include -#endif - -#ifdef _MSC_VER -#define fileno _fileno -#define fseeko _fseeki64 -#define ftello _ftelli64 -#endif - -namespace fs = std::filesystem; - -namespace Base::FS { - -#ifdef _WIN32 - -[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileMode type) { - switch (type) { - case FileMode::BinaryMode: - switch (mode) { - case FileAccessMode::Read: - return L"rb"; - case FileAccessMode::Write: - return L"wb"; - case FileAccessMode::Append: - return L"ab"; - case FileAccessMode::ReadWrite: - return L"r+b"; - case FileAccessMode::ReadAppend: - return L"a+b"; - } - break; - case FileMode::TextMode: - switch (mode) { - case FileAccessMode::Read: - return L"r"; - case FileAccessMode::Write: - return L"w"; - case FileAccessMode::Append: - return L"a"; - case FileAccessMode::ReadWrite: - return L"r+"; - case FileAccessMode::ReadAppend: - return L"a+"; - } - break; - } - - return L""; -} - -[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) { - switch (flag) { - case FileShareFlag::ShareNone: - default: - return _SH_DENYRW; - case FileShareFlag::ShareReadOnly: - return _SH_DENYWR; - case FileShareFlag::ShareWriteOnly: - return _SH_DENYRD; - case FileShareFlag::ShareReadWrite: - return _SH_DENYNO; - } -} - -#else - -[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileMode type) { - switch (type) { - case FileMode::BinaryMode: - switch (mode) { - case FileAccessMode::Read: - return "rb"; - case FileAccessMode::Write: - return "wb"; - case FileAccessMode::Append: - return "ab"; - case FileAccessMode::ReadWrite: - return "r+b"; - case FileAccessMode::ReadAppend: - return "a+b"; - } - break; - case FileMode::TextMode: - switch (mode) { - case FileAccessMode::Read: - return "r"; - case FileAccessMode::Write: - return "w"; - case FileAccessMode::Append: - return "a"; - case FileAccessMode::ReadWrite: - return "r+"; - case FileAccessMode::ReadAppend: - return "a+"; - } - break; - } - - return ""; -} - -#endif - -[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) { - switch (origin) { - case SeekOrigin::SetOrigin: - default: - return SEEK_SET; - case SeekOrigin::CurrentPosition: - return SEEK_CUR; - case SeekOrigin::End: - return SEEK_END; - } -} - -IOFile::IOFile() = default; - -IOFile::IOFile(const std::string& path, FileAccessMode mode, FileMode type, FileShareFlag flag) { - Open(path, mode, type, flag); -} - -IOFile::IOFile(std::string_view path, FileAccessMode mode, FileMode type, FileShareFlag flag) { - Open(path, mode, type, flag); -} - -IOFile::IOFile(const fs::path &path, FileAccessMode mode, FileMode type, FileShareFlag flag) { - Open(path, mode, type, flag); -} - -IOFile::~IOFile() { - Close(); -} - -IOFile::IOFile(IOFile&& other) noexcept { - std::swap(filePath, other.filePath); - std::swap(fileAccessMode, other.fileAccessMode); - std::swap(fileType, other.fileType); - std::swap(file, other.file); -} - -IOFile& IOFile::operator=(IOFile&& other) noexcept { - std::swap(filePath, other.filePath); - std::swap(fileAccessMode, other.fileAccessMode); - std::swap(fileType, other.fileType); - std::swap(file, other.file); - return *this; -} - -int IOFile::Open(const fs::path& path, FileAccessMode mode, FileMode type, FileShareFlag flag) { - Close(); - - filePath = path; - fileAccessMode = mode; - fileType = type; - - errno = 0; - int result = 0; - -#ifdef _WIN32 - if (flag != FileShareFlag::ShareNone) { - file = ::_wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag)); - result = errno; - } else { - result = ::_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); - } -#else - file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); - result = errno; -#endif - - if (!IsOpen()) { - const auto ec = std::error_code{result, std::generic_category()}; - LOG_ERROR(Base_Filesystem, "Failed to open the file at path={}, error_message={}", - PathToUTF8String(filePath), ec.message()); - } - - return result; -} - -void IOFile::Close() { - if (!IsOpen()) { - return; - } - - errno = 0; - - const bool closeResult = ::fclose(file) == 0; - - if (!closeResult) { - const auto ec = std::error_code{errno, std::generic_category()}; - LOG_ERROR(Base_Filesystem, "Failed to close the file at path={}, ec_message={}", - PathToUTF8String(filePath), ec.message()); - } - - file = nullptr; - -#ifdef _WIN64 - if (fileMapping && fileAccessMode == FileAccessMode::ReadWrite) { - ::CloseHandle(std::bit_cast(fileMapping)); - } -#endif -} - -void IOFile::Unlink() { - if (!IsOpen()) { - return; - } - - // Mark the file for deletion - std::error_code fsError; - fs::remove_all(filePath, fsError); - if (fsError) { - LOG_ERROR(Base_Filesystem, "Failed to remove the file at '{}'. Reason: {}", - PathToUTF8String(filePath), fsError.message()); - } -} - -uptr IOFile::GetFileMapping() { - if (fileMapping) { - return fileMapping; - } -#ifdef _WIN64 - const s32 fd = fileno(file); - - HANDLE hfile = reinterpret_cast(::_get_osfhandle(fd)); - HANDLE mapping = nullptr; - -/* if (fileAccessMode == FileAccessMode::ReadWrite) { - mapping = CreateFileMapping2(hfile, NULL, FILE_MAP_WRITE, PAGE_READWRITE, SEC_COMMIT, 0, - NULL, NULL, 0); - } else { - mapping = hfile; - }*/ - - mapping = hfile; - - fileMapping = std::bit_cast(mapping); - ASSERT_MSG(fileMapping, "{}", Base::GetLastErrorMsg()); - return fileMapping; -#else - fileMapping = fileno(file); - return fileMapping; -#endif -} - -std::string IOFile::ReadString(size_t length) const { - std::vector string_buffer(length); - - const u64 charsRead = ReadSpan(string_buffer); - const auto stringSize = charsRead != length ? charsRead : length; - - return std::string{ string_buffer.data(), stringSize }; -} - -bool IOFile::Flush() const { - if (!IsOpen()) { - return false; - } - - errno = 0; - - const bool flushResult = ::fflush(file) == 0; - - if (!flushResult) { - const std::error_code ec = { errno, std::generic_category() }; - LOG_ERROR(Base_Filesystem, "Failed to flush the file at path={}, ec_message={}", - PathToUTF8String(filePath), ec.message()); - } - - return flushResult; -} - -bool IOFile::Commit() const { - if (!IsOpen()) { - return false; - } - - errno = 0; - - bool commitResult = ::fflush(file) == 0 && ::fsync(::fileno(file)) == 0; - - if (!commitResult) { - const std::error_code ec = { errno, std::generic_category() }; - LOG_ERROR(Base_Filesystem, "Failed to commit the file at path={}, ec_message={}", - PathToUTF8String(filePath), ec.message()); - } - - return commitResult; -} - -bool IOFile::SetSize(u64 size) const { - if (!IsOpen()) { - return false; - } - - errno = 0; - - const bool setSizeResult = ::ftruncate(::fileno(file), static_cast(size)) == 0; - - if (!setSizeResult) { - const std::error_code ec = { errno, std::generic_category() }; - LOG_ERROR(Base_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}", - PathToUTF8String(filePath), size, ec.message()); - } - - return setSizeResult; -} - -u64 IOFile::GetSize() const { - if (!IsOpen()) { - return 0; - } - - // Flush any unwritten buffered data into the file prior to retrieving the file size. - std::fflush(file); - - - u64 fSize = 0; - // fs::file_size can cause a exception if it is not a valid file - try { - std::error_code ec{}; - fSize = fs::file_size(filePath, ec); - if (fSize == -1 || !fSize) { - LOG_ERROR(Base_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", - PathToUTF8String(filePath), ec.message()); - return 0; - } - } catch (const std::exception &ex) { - LOG_ERROR(Base_Filesystem, "Exception trying to get file size. Exception: {}", - ex.what()); - return 0; - } - - return fSize; -} - -bool IOFile::Seek(s64 offset, SeekOrigin origin) const { - if (!IsOpen()) { - return false; - } - - if (False(fileAccessMode & (FileAccessMode::Write | FileAccessMode::Append))) { - u64 size = GetSize(); - if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) { - LOG_ERROR(Base_Filesystem, "Seeking past the end of the file"); - return false; - } else if (origin == SeekOrigin::SetOrigin && static_cast(offset) > size) { - LOG_ERROR(Base_Filesystem, "Seeking past the end of the file"); - return false; - } else if (origin == SeekOrigin::End && offset > 0) { - LOG_ERROR(Base_Filesystem, "Seeking past the end of the file"); - return false; - } - } - - errno = 0; - - const s32 seekResult = fseeko(file, offset, ToSeekOrigin(origin)) == 0; - - if (!seekResult) { - const std::error_code ec = { errno, std::generic_category() }; - LOG_ERROR(Base_Filesystem, - "Failed to seek the file at path={}, offset={}, origin={}, ec_message={}", - PathToUTF8String(filePath), offset, static_cast(origin), ec.message()); - } - - return seekResult; -} - -s64 IOFile::Tell() const { - if (!IsOpen()) { - return 0; - } - - errno = 0; - - return ftello(file); -} - -u64 GetDirectorySize(const std::filesystem::path &path) { - if (!fs::exists(path)) { - return 0; - } - - u64 total = 0; - for (const auto& entry : fs::recursive_directory_iterator(path)) { - if (fs::is_regular_file(entry.path())) { - // fs::file_size can cause a exception if it is not a valid file - try { - std::error_code ec{}; - u64 fileSize = fs::file_size(entry.path(), ec); - if (fileSize != -1 && fileSize) { - total += fileSize; - } - else { - LOG_ERROR(Base_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", - PathToUTF8String(entry.path()), ec.message()); - } - } - catch (const std::exception& ex) { - LOG_ERROR(Base_Filesystem, "Exception trying to get file size. Exception: {}", - ex.what()); - } - } - } - return total; -} - -} // namespace Base::FS diff --git a/core/Base/IoFile.h b/core/Base/IoFile.h deleted file mode 100644 index a37f9c2..0000000 --- a/core/Base/IoFile.h +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -#pragma once - -#include -#include - -#include "Enum.h" -#include "PathUtil.h" - -namespace Base::FS { - -enum class FileAccessMode { - /** - * If the file at path exists, it opens the file for reading. - * If the file at path does not exist, it fails to open the file. - */ - Read = 1 << 0, - /** - * If the file at path exists, the existing contents of the file are erased. - * The empty file is then opened for writing. - * If the file at path does not exist, it creates and opens a new empty file for writing. - */ - Write = 1 << 1, - /** - * If the file at path exists, it opens the file for reading and writing. - * If the file at path does not exist, it fails to open the file. - */ - ReadWrite = Read | Write, - /** - * If the file at path exists, it opens the file for appending. - * If the file at path does not exist, it creates and opens a new empty file for appending. - */ - Append = 1 << 2, - /** - * If the file at path exists, it opens the file for both reading and appending. - * If the file at path does not exist, it creates and opens a new empty file for both - * reading and appending. - */ - ReadAppend = Read | Append -}; -DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode) - -enum class FileMode { - BinaryMode, - TextMode -}; - -enum class FileShareFlag { - ShareNone, // Provides exclusive access to the file. - ShareReadOnly, // Provides read only shared access to the file. - ShareWriteOnly, // Provides write only shared access to the file. - ShareReadWrite // Provides read and write shared access to the file. -}; - -enum class SeekOrigin : u32 { - SetOrigin, // Seeks from the start of the file. - CurrentPosition, // Seeks from the current file pointer position. - End // Seeks from the end of the file. -}; - -class IOFile final { -public: - IOFile(); - - explicit IOFile(const std::string& path, FileAccessMode mode, - FileMode type = FileMode::BinaryMode, - FileShareFlag flag = FileShareFlag::ShareReadOnly); - - explicit IOFile(std::string_view path, FileAccessMode mode, - FileMode type = FileMode::BinaryMode, - FileShareFlag flag = FileShareFlag::ShareReadOnly); - explicit IOFile(const fs::path &path, FileAccessMode mode, - FileMode type = FileMode::BinaryMode, - FileShareFlag flag = FileShareFlag::ShareReadOnly); - - ~IOFile(); - - IOFile(const IOFile&) = delete; - IOFile& operator=(const IOFile&) = delete; - - IOFile(IOFile&& other) noexcept; - IOFile& operator=(IOFile&& other) noexcept; - - fs::path GetPath() const { - return filePath; - } - - FileAccessMode GetAccessMode() const { - return fileAccessMode; - } - - FileMode GetType() const { - return fileType; - } - - bool IsOpen() const { - return file != nullptr; - } - - uptr GetFileMapping(); - - int Open(const fs::path& path, FileAccessMode mode, - FileMode type = FileMode::BinaryMode, - FileShareFlag flag = FileShareFlag::ShareReadOnly); - void Close(); - - void Unlink(); - - bool Flush() const; - bool Commit() const; - - bool SetSize(u64 size) const; - u64 GetSize() const; - - bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const; - s64 Tell() const; - - template - size_t Read(T& data) const { - if constexpr (std::contiguous_iterator) { - using ContiguousType = typename T::value_type; - static_assert(std::is_trivially_copyable_v, - "Data type must be trivially copyable."); - return ReadSpan(data); - } else { - return ReadObject(data) ? 1 : 0; - } - } - - template - size_t Write(const T& data) const { - if constexpr (std::contiguous_iterator) { - using ContiguousType = typename T::value_type; - static_assert(std::is_trivially_copyable_v, - "Data type must be trivially copyable."); - return WriteSpan(data); - } else { - static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); - return WriteObject(data) ? 1 : 0; - } - } - - template - size_t ReadSpan(std::span data) const { - static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); - - if (!IsOpen()) { - return 0; - } - - return ReadRaw(data.data(), data.size()); - } - - template - size_t ReadRaw(void* data, size_t size) const { - return std::fread(data, sizeof(T), size, file); - } - - template - size_t WriteSpan(std::span data) const { - static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); - - if (!IsOpen()) { - return 0; - } - - return std::fwrite(data.data(), sizeof(T), data.size(), file); - } - - template - bool ReadObject(T& object) const { - static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); - static_assert(!std::is_pointer_v, "T must not be a pointer to an object."); - - if (!IsOpen()) { - return false; - } - - return std::fread(&object, sizeof(T), 1, file) == 1; - } - - template - size_t WriteRaw(const void* data, size_t size) const { - return std::fwrite(data, sizeof(T), size, file); - } - - template - bool WriteObject(const T& object) const { - static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); - static_assert(!std::is_pointer_v, "T must not be a pointer to an object."); - - if (!IsOpen()) { - return false; - } - - return std::fwrite(&object, sizeof(T), 1, file) == 1; - } - - std::string ReadString(size_t length) const; - - size_t WriteString(std::span string) const { - return WriteSpan(string); - } - - static size_t WriteBytes(const fs::path path, const auto& data) { - IOFile out(path, FileAccessMode::Write); - return out.Write(data); - } - -private: - fs::path filePath{}; - FileAccessMode fileAccessMode{}; - FileMode fileType{}; - - std::FILE *file = nullptr; - uptr fileMapping = 0; -}; - -u64 GetDirectorySize(const fs::path &path); - -} // namespace Base::FS diff --git a/core/Base/PathUtil.cpp b/core/Base/PathUtil.cpp deleted file mode 100644 index 373b8d8..0000000 --- a/core/Base/PathUtil.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -#include "PathUtil.h" -#include "Base/Logging/Log.h" - -#include - -#include -#include -#ifdef _WIN32 -#include -#endif // _WIN32 -#ifdef __APPLE__ -#include -#include -#endif // __APPLE__ - -namespace Base { -namespace FS { - -const fs::path GetBinaryDirectory() { - fs::path fspath = {}; -#ifdef _WIN32 - char path[256]; - GetModuleFileNameA(nullptr, path, sizeof(path)); - fspath = path; -#elif __linux__ - fspath = fs::canonical("/proc/self/exe"); -#elif __APPLE__ - pid_t pid = getpid(); - char path[PROC_PIDPATHINFO_MAXSIZE]; - // While this is fine for a raw executable, - // an application bundle is read-only and these files - // should instead be placed in Application Support. - proc_pidpath(pid, path, sizeof(path)); - fspath = path; -#else - // Unknown, just return rootdir - fspath = fs::current_path() / "Pound"; -#endif - return fs::weakly_canonical(fmt::format("{}/..", fspath.string())); -} - -static auto UserPaths = [] { - auto currentDir = fs::current_path(); - auto binaryDir = GetBinaryDirectory(); - bool nixos = false; - - std::unordered_map paths; - - const auto insert_path = [&](PathType pound_path, const fs::path &new_path, bool create = true) { - if (create && !fs::exists(new_path)) - fs::create_directory(new_path); - - paths.insert_or_assign(pound_path, new_path); - }; - - insert_path(PathType::BinaryDir, binaryDir, false); - // If we are in the nix store, it's read-only. Change to currentDir if needed - if (binaryDir.string().find("/nix/store/") != std::string::npos) { - nixos = true; - } - if (nixos) { - currentDir /= "files"; - insert_path(PathType::RootDir, currentDir); - insert_path(PathType::FirmwareDir, currentDir / FW_DIR); - insert_path(PathType::LogDir, currentDir / LOG_DIR); - } - else { - insert_path(PathType::RootDir, currentDir, false); - insert_path(PathType::FirmwareDir, binaryDir / FW_DIR); - insert_path(PathType::LogDir, binaryDir / LOG_DIR); - } - return paths; -}(); - -std::string PathToUTF8String(const fs::path &path) { - const auto u8_string = path.u8string(); - return std::string{u8_string.begin(), u8_string.end()}; -} - -const fs::path &GetUserPath(PathType pound_path) { - return UserPaths.at(pound_path); -} - -std::string GetUserPathString(PathType pound_path) { - return PathToUTF8String(GetUserPath(pound_path)); -} - -std::vector ListFilesFromPath(const fs::path &path) { - std::vector fileList; - - fs::path _path = fs::weakly_canonical(path); - - for (auto &entry : fs::directory_iterator{ _path }) { - FileInfo fileInfo; - if (entry.is_directory()) { - fileInfo.fileSize = 0; - fileInfo.fileType = FileType::Directory; - } else { - fileInfo.fileSize = fs::file_size(_path); - fileInfo.fileType = FileType::File; - } - - fileInfo.filePath = entry.path(); - fileInfo.fileName = entry.path().filename(); - fileList.push_back(fileInfo); - } - - return fileList; -} - -void SetUserPath(PathType pound_path, const fs::path &new_path) { - UserPaths.insert_or_assign(pound_path, new_path); -} -} // namespace FS -} // namespace Base diff --git a/core/Base/PolyfillThread.h b/core/Base/PolyfillThread.h deleted file mode 100644 index 9bd825e..0000000 --- a/core/Base/PolyfillThread.h +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -// -// TODO: remove this file when jthread is supported by all compilation targets -// - -#pragma once - -#if defined(__cpp_lib_jthread) || !defined(_MSVC_VER) - -#include -#include -#include -#include -#include -#include - -namespace Base { - -template -void CondvarWait(Condvar& cv, std::unique_lock& lk, std::stop_token token, Pred&& pred) { - cv.wait(lk, token, std::forward(pred)); -} - -template -bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& rel_time) { - std::condition_variable_any cv; - std::mutex m; - - // Perform the timed wait. - std::unique_lock lk{m}; - return !cv.wait_for(lk, token, rel_time, [&] { return token.stop_requested(); }); -} - -} // namespace Base - -#else - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace std { -namespace polyfill { - -using stop_state_callback = size_t; - -class stop_state { -public: - stop_state() = default; - ~stop_state() = default; - - bool request_stop() { - unique_lock lk{m_lock}; - - if (m_stop_requested) { - // Already set, nothing to do. - return false; - } - - // Mark stop requested. - m_stop_requested = true; - - while (!m_callbacks.empty()) { - // Get an iterator to the first element. - const auto it = m_callbacks.begin(); - - // Move the callback function out of the map. - function f; - swap(it->second, f); - - // Erase the now-empty map element. - m_callbacks.erase(it); - - // Run the callback. - if (f) { - f(); - } - } - - return true; - } - - bool stop_requested() const { - unique_lock lk{m_lock}; - return m_stop_requested; - } - - stop_state_callback insert_callback(function f) { - unique_lock lk{m_lock}; - - if (m_stop_requested) { - // Stop already requested. Don't insert anything, - // just run the callback synchronously. - if (f) { - f(); - } - return 0; - } - - // Insert the callback. - stop_state_callback ret = ++m_next_callback; - m_callbacks.emplace(ret, std::move(f)); - return ret; - } - - void remove_callback(stop_state_callback cb) { - unique_lock lk{m_lock}; - m_callbacks.erase(cb); - } - -private: - mutable recursive_mutex m_lock; - map> m_callbacks; - stop_state_callback m_next_callback{0}; - bool m_stop_requested{false}; -}; - -} // namespace polyfill - -class stop_token; -class stop_source; -struct nostopstate_t { - explicit nostopstate_t() = default; -}; -inline constexpr nostopstate_t nostopstate{}; - -template -class stop_callback; - -class stop_token { -public: - stop_token() noexcept = default; - - stop_token(const stop_token&) noexcept = default; - stop_token(stop_token&&) noexcept = default; - stop_token& operator=(const stop_token&) noexcept = default; - stop_token& operator=(stop_token&&) noexcept = default; - ~stop_token() = default; - - void swap(stop_token& other) noexcept { - m_stop_state.swap(other.m_stop_state); - } - - [[nodiscard]] bool stop_requested() const noexcept { - return m_stop_state && m_stop_state->stop_requested(); - } - [[nodiscard]] bool stop_possible() const noexcept { - return m_stop_state != nullptr; - } - -private: - friend class stop_source; - template - friend class stop_callback; - stop_token(shared_ptr stop_state) : m_stop_state(std::move(stop_state)) {} - -private: - shared_ptr m_stop_state; -}; - -class stop_source { -public: - stop_source() : m_stop_state(make_shared()) {} - explicit stop_source(nostopstate_t) noexcept {} - - stop_source(const stop_source&) noexcept = default; - stop_source(stop_source&&) noexcept = default; - stop_source& operator=(const stop_source&) noexcept = default; - stop_source& operator=(stop_source&&) noexcept = default; - ~stop_source() = default; - void swap(stop_source& other) noexcept { - m_stop_state.swap(other.m_stop_state); - } - - [[nodiscard]] stop_token get_token() const noexcept { - return stop_token(m_stop_state); - } - [[nodiscard]] bool stop_possible() const noexcept { - return m_stop_state != nullptr; - } - [[nodiscard]] bool stop_requested() const noexcept { - return m_stop_state && m_stop_state->stop_requested(); - } - bool request_stop() noexcept { - return m_stop_state && m_stop_state->request_stop(); - } - -private: - friend class jthread; - explicit stop_source(shared_ptr stop_state) - : m_stop_state(std::move(stop_state)) {} - -private: - shared_ptr m_stop_state; -}; - -template -class stop_callback { - static_assert(is_nothrow_destructible_v); - static_assert(is_invocable_v); - -public: - using callback_type = Callback; - - template - requires constructible_from - explicit stop_callback(const stop_token& st, - C&& cb) noexcept(is_nothrow_constructible_v) - : m_stop_state(st.m_stop_state) { - if (m_stop_state) { - m_callback = m_stop_state->insert_callback(std::move(cb)); - } - } - template - requires constructible_from - explicit stop_callback(stop_token&& st, - C&& cb) noexcept(is_nothrow_constructible_v) - : m_stop_state(std::move(st.m_stop_state)) { - if (m_stop_state) { - m_callback = m_stop_state->insert_callback(std::move(cb)); - } - } - ~stop_callback() { - if (m_stop_state && m_callback) { - m_stop_state->remove_callback(m_callback); - } - } - - stop_callback(const stop_callback&) = delete; - stop_callback(stop_callback&&) = delete; - stop_callback& operator=(const stop_callback&) = delete; - stop_callback& operator=(stop_callback&&) = delete; - -private: - shared_ptr m_stop_state; - polyfill::stop_state_callback m_callback; -}; - -template -stop_callback(stop_token, Callback) -> stop_callback; - -class jthread { -public: - using id = thread::id; - using native_handle_type = thread::native_handle_type; - - jthread() noexcept = default; - - template , jthread>>> - explicit jthread(F&& f, Args&&... args) - : m_stop_state(make_shared()), - m_thread(make_thread(std::forward(f), std::forward(args)...)) {} - - ~jthread() { - if (joinable()) { - request_stop(); - join(); - } - } - - jthread(const jthread&) = delete; - jthread(jthread&&) noexcept = default; - jthread& operator=(const jthread&) = delete; - - jthread& operator=(jthread&& other) noexcept { - m_thread.swap(other.m_thread); - m_stop_state.swap(other.m_stop_state); - return *this; - } - - void swap(jthread& other) noexcept { - m_thread.swap(other.m_thread); - m_stop_state.swap(other.m_stop_state); - } - [[nodiscard]] bool joinable() const noexcept { - return m_thread.joinable(); - } - void join() { - m_thread.join(); - } - void detach() { - m_thread.detach(); - m_stop_state.reset(); - } - - [[nodiscard]] id get_id() const noexcept { - return m_thread.get_id(); - } - [[nodiscard]] native_handle_type native_handle() { - return m_thread.native_handle(); - } - [[nodiscard]] stop_source get_stop_source() noexcept { - return stop_source(m_stop_state); - } - [[nodiscard]] stop_token get_stop_token() const noexcept { - return stop_source(m_stop_state).get_token(); - } - bool request_stop() noexcept { - return get_stop_source().request_stop(); - } - [[nodiscard]] static u32 hardware_concurrency() noexcept { - return thread::hardware_concurrency(); - } - -private: - template - thread make_thread(F&& f, Args&&... args) { - if constexpr (is_invocable_v, stop_token, decay_t...>) { - return thread(std::forward(f), get_stop_token(), std::forward(args)...); - } else { - return thread(std::forward(f), std::forward(args)...); - } - } - - shared_ptr m_stop_state; - thread m_thread; -}; - -} // namespace std - -namespace Base { - -template -void CondvarWait(Condvar& cv, std::unique_lock& lk, std::stop_token token, Pred pred) { - if (token.stop_requested()) { - return; - } - - std::stop_callback callback(token, [&] { - { std::scoped_lock lk2{*lk.mutex()}; } - cv.notify_all(); - }); - - cv.wait(lk, [&] { return pred() || token.stop_requested(); }); -} - -template -bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& rel_time) { - if (token.stop_requested()) { - return false; - } - - bool stop_requested = false; - std::condition_variable cv; - std::mutex m; - - std::stop_callback cb(token, [&] { - // Wake up the waiting thread. - { - std::scoped_lock lk{m}; - stop_requested = true; - } - cv.notify_one(); - }); - - // Perform the timed wait. - std::unique_lock lk{m}; - return !cv.wait_for(lk, rel_time, [&] { return stop_requested; }); -} - -} // namespace Base - -#endif diff --git a/core/Base/StringUtil.cpp b/core/Base/StringUtil.cpp deleted file mode 100644 index db115fc..0000000 --- a/core/Base/StringUtil.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -#include "StringUtil.h" - -namespace Base { - -#ifdef _WIN32 -static std::wstring CPToUTF16(u32 code_page, std::string_view input) { - const s32 size = - ::MultiByteToWideChar(code_page, 0, input.data(), static_cast(input.size()), nullptr, 0); - - if (size == 0) { - return {}; - } - - std::wstring output(size, L'\0'); - - if (size != ::MultiByteToWideChar(code_page, 0, input.data(), static_cast(input.size()), - &output[0], static_cast(output.size()))) { - output.clear(); - } - - return output; -} -#endif // ifdef _WIN32 - -std::string UTF16ToUTF8(const std::wstring_view &input) { -#ifdef _WIN32 - const s32 size = ::WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(input.size()), - nullptr, 0, nullptr, nullptr); - if (size == 0) { - return {}; - } - - std::string output(size, '\0'); - - if (size != ::WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(input.size()), - &output[0], static_cast(output.size()), nullptr, - nullptr)) { - output.clear(); - } - return output; -#else - // Very hacky way to get cross-platform wstring conversion - return std::filesystem::path(input).string(); -#endif // ifdef _WIN32 -} - -std::wstring UTF8ToUTF16W(const std::string_view &input) { -#ifdef _WIN32 - return CPToUTF16(CP_UTF8, input); -#else - // Very hacky way to get cross-platform wstring conversion - return std::filesystem::path(input).wstring(); -#endif // ifdef _WIN32 -} - -std::string ToLower(const std::string &str) { - std::string out = str; - std::transform(str.begin(), str.end(), out.data(), ::tolower); - return out; -} - - -} // namespace Base diff --git a/core/Base/Thread.cpp b/core/Base/Thread.cpp deleted file mode 100644 index a2425c4..0000000 --- a/core/Base/Thread.cpp +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -#include "Thread.h" -#include "Error.h" -#include "Logging/Log.h" - -#ifdef __APPLE__ -#include -#include -#include -#elif defined(_WIN32) -#include -#include "StringUtil.h" -#else - -#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) -#include -#else -#include -#endif -#include -#endif -#ifndef _WIN32 -#include -#endif -#include -#include - -#ifdef __FreeBSD__ -#define cpu_set_t cpuset_t -#endif - -namespace Base { - -#ifdef __APPLE__ - -void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) { - // CPU time to grant. - const std::chrono::nanoseconds computation_ns = period_ns / 2; - - // Determine the timebase for converting time to ticks. - struct mach_timebase_info timebase {}; - mach_timebase_info(&timebase); - const auto ticks_per_ns = - static_cast(timebase.denom) / static_cast(timebase.numer); - - const auto period_ticks = - static_cast(static_cast(period_ns.count()) * ticks_per_ns); - const auto computation_ticks = - static_cast(static_cast(computation_ns.count()) * ticks_per_ns); - - thread_time_constraint_policy policy = { - .period = period_ticks, - .computation = computation_ticks, - // Should not matter since preemptible is false, but needs to be >= computation regardless. - .constraint = computation_ticks, - .preemptible = false, - }; - - int ret = thread_policy_set( - pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY, - reinterpret_cast(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT); - if (ret != KERN_SUCCESS) { - LOG_ERROR(Base, "Could not set thread to real-time with period {} ns: {}", - period_ns.count(), ret); - } -} - -#else - -void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) { - // Not implemented -} - -#endif - -#ifdef _WIN32 - -void SetCurrentThreadPriority(ThreadPriority new_priority) { - const auto handle = GetCurrentThread(); - int windows_priority = 0; - switch (new_priority) { - case ThreadPriority::Low: - windows_priority = THREAD_PRIORITY_BELOW_NORMAL; - break; - case ThreadPriority::Normal: - windows_priority = THREAD_PRIORITY_NORMAL; - break; - case ThreadPriority::High: - windows_priority = THREAD_PRIORITY_ABOVE_NORMAL; - break; - case ThreadPriority::VeryHigh: - windows_priority = THREAD_PRIORITY_HIGHEST; - break; - case ThreadPriority::Critical: - windows_priority = THREAD_PRIORITY_TIME_CRITICAL; - break; - default: - windows_priority = THREAD_PRIORITY_NORMAL; - break; - } - SetThreadPriority(handle, windows_priority); -} - -static void AccurateSleep(std::chrono::nanoseconds duration) { - LARGE_INTEGER interval{ - .QuadPart = -1 * (duration.count() / 100u), - }; - const HANDLE timer = ::CreateWaitableTimer(nullptr, TRUE, nullptr); - SetWaitableTimer(timer, &interval, 0, nullptr, nullptr, 0); - WaitForSingleObject(timer, INFINITE); - ::CloseHandle(timer); -} - -#else - -void SetCurrentThreadPriority(ThreadPriority new_priority) { - pthread_t this_thread = pthread_self(); - - constexpr auto scheduling_type = SCHED_OTHER; - const s32 max_prio = sched_get_priority_max(scheduling_type); - const s32 min_prio = sched_get_priority_min(scheduling_type); - const u32 level = std::max(static_cast(new_priority) + 1, 4U); - - struct sched_param params; - if (max_prio > min_prio) { - params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4; - } else { - params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4; - } - - pthread_setschedparam(this_thread, scheduling_type, ¶ms); -} - -static void AccurateSleep(std::chrono::nanoseconds duration) { - std::this_thread::sleep_for(duration); -} - -#endif - -#if defined(_MSC_VER) || defined(__MINGW32__) - -// Sets the debugger-visible name of the current thread. -void SetCurrentThreadName(const std::string_view &name) { - SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data()); -} - -void SetThreadName(void *thread, const std::string_view &name) { - const char* nchar = name.data(); - std::string truncated(nchar, std::min(name.size(), static_cast(15))); - SetThreadDescription(thread, UTF8ToUTF16W(name).data()); -} - -#else // !MSVC_VER, so must be POSIX threads - -// MinGW with the POSIX threading model does not support pthread_setname_np -#if !defined(_WIN32) || defined(_MSC_VER) -void SetCurrentThreadName(const std::string_view &name) { - const char* nchar = name.data(); -#ifdef __APPLE__ - pthread_setname_np(nchar); -#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(pthread_self(), nchar); -#elif defined(__NetBSD__) - pthread_setname_np(pthread_self(), "%s", (void*)nchar); -#elif defined(__linux__) - // Linux limits thread names to 15 characters and will outright reject any - // attempt to set a longer name with ERANGE. - std::string truncated(nchar, std::min(name.size(), static_cast(15))); - if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) { - errno = e; - } -#else - pthread_setname_np(pthread_self(), nchar); -#endif -} - -void SetThreadName(void *thread, const std::string_view &name) { - // TODO -} -#endif - -#if defined(_WIN32) -void SetCurrentThreadName(const std::string_view &name) { - // Do Nothing on MinGW -} - -void SetThreadName(void *thread, const std::string_view &name) { - // Do Nothing on MinGW -} -#endif - -#endif - -AccurateTimer::AccurateTimer(std::chrono::nanoseconds target_interval) : - target_interval(target_interval) -{} - -void AccurateTimer::Start() { - const auto begin_sleep = std::chrono::high_resolution_clock::now(); - if (total_wait.count() > 0) { - AccurateSleep(total_wait); - } - start_time = std::chrono::high_resolution_clock::now(); - total_wait -= std::chrono::duration_cast(start_time - begin_sleep); -} - -void AccurateTimer::End() { - const auto now = std::chrono::high_resolution_clock::now(); - total_wait += - target_interval - std::chrono::duration_cast(now - start_time); -} - -} // namespace Base diff --git a/core/Base/Thread.h b/core/Base/Thread.h deleted file mode 100644 index b9855d6..0000000 --- a/core/Base/Thread.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2025 Xenon Emulator Project. All rights reserved. - -#pragma once - -#include - -namespace Base { - -enum class ThreadPriority : u32 { - Low = 0, - Normal = 1, - High = 2, - VeryHigh = 3, - Critical = 4 -}; - -void SetCurrentThreadRealtime(std::chrono::nanoseconds period_ns); - -void SetCurrentThreadPriority(ThreadPriority new_priority); - -void SetCurrentThreadName(const std::string_view &name); - -void SetThreadName(void *thread, const std::string_view &name); - -class AccurateTimer { - std::chrono::nanoseconds target_interval{}; - std::chrono::nanoseconds total_wait{}; - - std::chrono::high_resolution_clock::time_point start_time; - -public: - explicit AccurateTimer(std::chrono::nanoseconds target_interval); - - void Start(); - - void End(); - - std::chrono::nanoseconds GetTotalWait() const { - return total_wait; - } -}; - -} // namespace Base diff --git a/core/arm64/CMakeLists.txt b/core/arm64/CMakeLists.txt new file mode 100644 index 0000000..73d2b1e --- /dev/null +++ b/core/arm64/CMakeLists.txt @@ -0,0 +1,8 @@ +#Copyright 2025 Pound Emulator Project.All rights reserved. + +set(ARM64_SOURCES ${CMAKE_CURRENT_SOURCE_DIR} / isa.cpp ${CMAKE_CURRENT_SOURCE_DIR} / + guest.cpp ${CMAKE_CURRENT_SOURCE_DIR} / mmu.cpp) + + target_sources(Pound PRIVATE ${ARM64_SOURCES}) + + target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /..) diff --git a/core/arm64/guest.cpp b/core/arm64/guest.cpp index 77473d1..48bb9da 100644 --- a/core/arm64/guest.cpp +++ b/core/arm64/guest.cpp @@ -1,5 +1,4 @@ #include "memory.h" -#include "Base/Assert.h" namespace pound::aarch64::memory { diff --git a/core/arm64/guest.h b/core/arm64/guest.h index ee06b61..9d78990 100644 --- a/core/arm64/guest.h +++ b/core/arm64/guest.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace pound::arm64::memory { diff --git a/core/arm64/isa.cpp b/core/arm64/isa.cpp index e6c0c67..5dfe355 100644 --- a/core/arm64/isa.cpp +++ b/core/arm64/isa.cpp @@ -1,7 +1,8 @@ #include "isa.h" -#include "guest.h" -#include "memory/arena.h" #include +#include "common/Logging/Log.h" +#include "guest.h" +#include "host/memory/arena.h" namespace pound::arm64 { @@ -154,7 +155,7 @@ bool test_guest_ram_access(pound::arm64::memory::guest_memory_t* memory) void cpuTest() { vcpu_state_t vcpu_states[CPU_CORES] = {}; - pound::memory::arena_t guest_memory_arena = pound::memory::arena_init(GUEST_RAM_SIZE); + pound::host::memory::arena_t guest_memory_arena = pound::host::memory::arena_init(GUEST_RAM_SIZE); assert(nullptr != guest_memory_arena.data); pound::arm64::memory::guest_memory_t guest_ram = {}; @@ -163,4 +164,4 @@ void cpuTest() (void)test_guest_ram_access(&guest_ram); } -} // namespace pound::armv64 +} // namespace pound::arm64 diff --git a/core/arm64/isa.h b/core/arm64/isa.h index 41599c3..d3b990d 100644 --- a/core/arm64/isa.h +++ b/core/arm64/isa.h @@ -2,11 +2,10 @@ #pragma once +#include #include #include -#include "Base/Logging/Log.h" - namespace pound::arm64 { /* AArch64 R0-R31 */ @@ -30,7 +29,6 @@ namespace pound::arm64 #define PSTATE_EL1T 0b0100 #define PSTATE_EL1H 0b0101 - /* * vcpu_state_t - Holds the architectural and selected system-register state for an emulated vCPU. * @v: 128-bit SIMD/FP vector registers V0–V31. @@ -108,7 +106,7 @@ typedef struct alignas(CACHE_LINE_SIZE) * Bits [21:16], T1SZ, does the same for the top half of the virtual * address space (controlled by TTBR1). */ uint64_t tcr_el1; - + /* * Holds the 64-bit base physical address of the initial page table * used for translating virtual addresses in the lower half of the diff --git a/core/arm64/mmu.cpp b/core/arm64/mmu.cpp index 3737133..3ed509b 100644 --- a/core/arm64/mmu.cpp +++ b/core/arm64/mmu.cpp @@ -265,7 +265,7 @@ int mmu_gva_to_gpa(pound::arm64::vcpu_state_t* vcpu, guest_memory_t* memory, uin case GRANULE_4KB: /* A 4KB granule supports up to a 4-level walk starting at L0. */ page_table_levels = 3; /* 0..3 inclusive */ - if (virtual_address_size > l0_shift) + if (virtual_address_size > l0_shift) { starting_level = 0; } diff --git a/core/arm64/mmu.h b/core/arm64/mmu.h index 8ab5246..85ba026 100644 --- a/core/arm64/mmu.h +++ b/core/arm64/mmu.h @@ -1,7 +1,7 @@ #pragma once -#include "isa.h" #include "guest.h" +#include "isa.h" namespace pound::arm64::memory { diff --git a/core/Base/Arch.h b/core/common/Arch.h similarity index 79% rename from core/Base/Arch.h rename to core/common/Arch.h index 9a6a9ac..e2ba7da 100644 --- a/core/Base/Arch.h +++ b/core/common/Arch.h @@ -8,6 +8,7 @@ #define ARCH_X86 1 #elif defined(__aarch64__) || defined(_M_ARM64) #define ARCH_AARCH64 1 -#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__POWERPC__) || defined(__ppc__) || defined(__PPC__) || defined(_ARCH_PPC) +#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__POWERPC__) || \ + defined(__ppc__) || defined(__PPC__) || defined(_ARCH_PPC) #define ARCH_PPC 1 #endif diff --git a/core/Base/Assert.cpp b/core/common/Assert.cpp similarity index 51% rename from core/Base/Assert.cpp rename to core/common/Assert.cpp index 7735249..1d434a8 100644 --- a/core/Base/Assert.cpp +++ b/core/common/Assert.cpp @@ -2,11 +2,11 @@ #include -#include "Logging/Backend.h" -#include "Assert.h" #include "Arch.h" +#include "Assert.h" +#include "Logging/Backend.h" -#ifdef _MSC_VER +#ifdef _MSC_VER #define Crash() __debugbreak() #else #if defined(ARCH_X86_64) @@ -25,30 +25,35 @@ #else #define Crash() __builtin_trap() #endif -#endif // _MSVC_VER +#endif // _MSVC_VER -void throw_fail_impl() { - ::fflush(stdout); - Crash(); +void throw_fail_impl() +{ + ::fflush(stdout); + Crash(); } -void assert_fail_impl() { - printf("Assertion Failed!\n"); - throw_fail_impl(); +void assert_fail_impl() +{ + printf("Assertion Failed!\n"); + throw_fail_impl(); } -[[noreturn]] void unreachable_impl() { - ::fflush(stdout); - Crash(); - throw std::runtime_error("Unreachable code"); +[[noreturn]] void unreachable_impl() +{ + ::fflush(stdout); + Crash(); + throw std::runtime_error("Unreachable code"); } -void assert_fail_debug_msg(const std::string& msg) { - printf("Assertion Failed! %s\n", msg.c_str()); - assert_fail_impl(); +void assert_fail_debug_msg(const std::string& msg) +{ + printf("Assertion Failed! %s\n", msg.c_str()); + assert_fail_impl(); } -void throw_fail_debug_msg(const std::string& msg) { - printf("Assertion Failed! %s\n", msg.c_str()); - throw_fail_impl(); +void throw_fail_debug_msg(const std::string& msg) +{ + printf("Assertion Failed! %s\n", msg.c_str()); + throw_fail_impl(); } diff --git a/core/common/Assert.h b/core/common/Assert.h new file mode 100644 index 0000000..682cb04 --- /dev/null +++ b/core/common/Assert.h @@ -0,0 +1,121 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include "Logging/Log.h" + +// Sometimes we want to try to continue even after hitting an assert. +// However touching this file yields a global recompilation as this header is included almost +// everywhere. So let's just move the handling of the failed assert to a single cpp file. + +void assert_fail_impl(); +void throw_fail_impl(); +[[noreturn]] void unreachable_impl(); +void assert_fail_debug_msg(const std::string& msg); +void throw_fail_debug_msg(const std::string& msg); + +#ifdef _MSC_VER +#define POUND_NO_INLINE __declspec(noinline) +#else +#define POUND_NO_INLINE __attribute__((noinline)) +#endif + +#define THROW(_a_) \ + ( \ + [&]() POUND_NO_INLINE \ + { \ + if (!(_a_)) [[unlikely]] \ + { \ + LOG_CRITICAL(Debug, "Assertion Failed!"); \ + throw_fail_impl(); \ + } \ + }) + +#define ASSERT(_a_) \ + ( \ + [&]() POUND_NO_INLINE \ + { \ + if (!(_a_)) [[unlikely]] \ + { \ + LOG_CRITICAL(Debug, "Assertion Failed!"); \ + assert_fail_impl(); \ + } \ + }) + +#define THROW_MSG(_a_, ...) \ + ( \ + [&]() POUND_NO_INLINE \ + { \ + if (!(_a_)) [[unlikely]] \ + { \ + LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \ + throw_fail_impl(); \ + } \ + }) + +#define ASSERT_MSG(_a_, ...) \ + ( \ + [&]() POUND_NO_INLINE \ + { \ + if (!(_a_)) [[unlikely]] \ + { \ + LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \ + assert_fail_impl(); \ + } \ + }) + +#define UNREACHABLE() \ + do \ + { \ + LOG_CRITICAL(Debug, "Unreachable code!"); \ + unreachable_impl(); \ + } while (0) + +#define UNREACHABLE_MSG(...) \ + do \ + { \ + LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \ + unreachable_impl(); \ + } while (0) + +#ifdef _DEBUG +#define DEBUG_ASSERT(_a_) ASSERT(_a_) +#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__) +#else // not debug +#define DEBUG_ASSERT(_a_) \ + do \ + { \ + } while (0) +#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \ + do \ + { \ + } while (0) +#endif + +#define UNIMPLEMENTED() THROW_MSG(false, "Unimplemented code!") +#define UNIMPLEMENTED_MSG(...) THROW_MSG(false, __VA_ARGS__) + +#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!") +#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__) + +// If the assert is ignored, execute _b_ +#define ASSERT_OR_EXECUTE(_a_, _b_) \ + do \ + { \ + ASSERT(_a_); \ + if (!(_a_)) [[unlikely]] \ + { \ + _b_ \ + } \ + } while (0) + +// If the assert is ignored, execute _b_ +#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \ + do \ + { \ + ASSERT_MSG(_a_, __VA_ARGS__); \ + if (!(_a_)) [[unlikely]] \ + { \ + _b_ \ + } \ + } while (0) diff --git a/core/common/BoundedQueue.h b/core/common/BoundedQueue.h new file mode 100644 index 0000000..8510b95 --- /dev/null +++ b/core/common/BoundedQueue.h @@ -0,0 +1,260 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include "PolyfillThread.h" + +namespace Base +{ + +namespace detail +{ +constexpr size_t DefaultCapacity = 0x1000; +} // namespace detail + +template +class SPSCQueue +{ + static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two."); + + public: + template + bool TryEmplace(Args&&... args) + { + return Emplace(std::forward(args)...); + } + + template + void EmplaceWait(Args&&... args) + { + Emplace(std::forward(args)...); + } + + bool TryPop(T& t) { return Pop(t); } + + bool PopWait(T& t) { return Pop(t); } + + bool PopWait(T& t, std::stop_token stopToken) { return Pop(t, stopToken); } + + T PopWait() + { + T t; + Pop(t); + return t; + } + + T PopWait(std::stop_token stopToken) + { + T t; + Pop(t, stopToken); + return t; + } + + private: + enum class PushMode + { + Try, + Wait, + Count + }; + + enum class PopMode + { + Try, + Wait, + WaitWithStopToken, + Count + }; + + template + bool Emplace(Args&&... args) + { + const size_t write_index = m_write_index.load(std::memory_order::relaxed); + + if constexpr (Mode == PushMode::Try) + { + // Check if we have free slots to write to. + if ((write_index - m_read_index.load(std::memory_order::acquire)) == Capacity) + { + return false; + } + } + else if constexpr (Mode == PushMode::Wait) + { + // Wait until we have free slots to write to. + std::unique_lock lock{producer_cv_mutex}; + producer_cv.wait(lock, [this, write_index] + { return (write_index - m_read_index.load(std::memory_order::acquire)) < Capacity; }); + } + else + { + static_assert(Mode < PushMode::Count, "Invalid PushMode."); + } + + // Determine the position to write to. + const size_t pos = write_index % Capacity; + + // Emplace into the queue. + new (std::addressof(m_data[pos])) T(std::forward(args)...); + + // Increment the write index. + ++m_write_index; + + // Notify the consumer that we have pushed into the queue. + std::scoped_lock lock{consumer_cv_mutex}; + consumer_cv.notify_one(); + + return true; + } + + template + bool Pop(T& t, [[maybe_unused]] std::stop_token stop_token = {}) + { + const size_t read_index = m_read_index.load(std::memory_order::relaxed); + + if constexpr (Mode == PopMode::Try) + { + // Check if the queue is empty. + if (read_index == m_write_index.load(std::memory_order::acquire)) + { + return false; + } + } + else if constexpr (Mode == PopMode::Wait) + { + // Wait until the queue is not empty. + std::unique_lock lock{consumer_cv_mutex}; + consumer_cv.wait( + lock, [this, read_index] { return read_index != m_write_index.load(std::memory_order::acquire); }); + } + else if constexpr (Mode == PopMode::WaitWithStopToken) + { + // Wait until the queue is not empty. + std::unique_lock lock{consumer_cv_mutex}; + Base::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] + { return read_index != m_write_index.load(std::memory_order::acquire); }); + if (stop_token.stop_requested()) + { + return false; + } + } + else + { + static_assert(Mode < PopMode::Count, "Invalid PopMode."); + } + + // Determine the position to read from. + const size_t pos = read_index % Capacity; + + // Pop the data off the queue, moving it. + t = std::move(m_data[pos]); + + // Increment the read index. + ++m_read_index; + + // Notify the producer that we have popped off the queue. + std::scoped_lock lock{producer_cv_mutex}; + producer_cv.notify_one(); + + return true; + } + + alignas(128) std::atomic_size_t m_read_index{0}; + alignas(128) std::atomic_size_t m_write_index{0}; + + std::array m_data; + + std::condition_variable_any producer_cv; + std::mutex producer_cv_mutex; + std::condition_variable_any consumer_cv; + std::mutex consumer_cv_mutex; +}; + +template +class MPSCQueue +{ + public: + template + bool TryEmplace(Args&&... args) + { + std::scoped_lock lock{writeMutex}; + return spscQueue.TryEmplace(std::forward(args)...); + } + + template + void EmplaceWait(Args&&... args) + { + std::scoped_lock lock{writeMutex}; + spscQueue.EmplaceWait(std::forward(args)...); + } + + bool TryPop(T& t) { return spscQueue.TryPop(t); } + + bool PopWait(T& t) { return spscQueue.PopWait(t); } + + bool PopWait(T& t, std::stop_token stop_token) { return spscQueue.PopWait(t, stop_token); } + + T PopWait() { return spscQueue.PopWait(); } + + T PopWait(std::stop_token stop_token) { return spscQueue.PopWait(stop_token); } + + private: + SPSCQueue spscQueue; + std::mutex writeMutex; +}; + +template +class MPMCQueue +{ + public: + template + bool TryEmplace(Args&&... args) + { + std::scoped_lock lock{writeMutex}; + return spscQueue.TryEmplace(std::forward(args)...); + } + + template + void EmplaceWait(Args&&... args) + { + std::scoped_lock lock{writeMutex}; + spscQueue.EmplaceWait(std::forward(args)...); + } + + bool TryPop(T& t) + { + std::scoped_lock lock{readMutex}; + return spscQueue.TryPop(t); + } + + bool PopWait(T& t) + { + std::scoped_lock lock{readMutex}; + return spscQueue.PopWait(t); + } + + bool PopWait(T& t, std::stop_token stopToken) + { + std::scoped_lock lock{readMutex}; + return spscQueue.PopWait(t, stopToken); + } + + T PopWait() + { + std::scoped_lock lock{readMutex}; + return spscQueue.PopWait(); + } + + T PopWait(std::stop_token stop_token) + { + std::scoped_lock lock{readMutex}; + return spscQueue.PopWait(stop_token); + } + + private: + SPSCQueue spscQueue; + std::mutex writeMutex; + std::mutex readMutex; +}; + +} // namespace Base diff --git a/core/common/CMakeLists.txt b/core/common/CMakeLists.txt new file mode 100644 index 0000000..85bc7ec --- /dev/null +++ b/core/common/CMakeLists.txt @@ -0,0 +1,16 @@ +#Copyright 2025 Pound Emulator Project.All rights reserved. + +set(COMMON_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/Assert.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Config.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/IoFile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/PathUtil.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/StringUtil.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Thread.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Logging/Backend.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Logging/Filter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Logging/TextFormatter.cpp) + +target_sources(Pound PRIVATE ${COMMON_SOURCES}) + +target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/core/common/Config.cpp b/core/common/Config.cpp new file mode 100644 index 0000000..d3b2e96 --- /dev/null +++ b/core/common/Config.cpp @@ -0,0 +1,108 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. + +#include "Config.h" + +#include +#include + +namespace Config +{ + +static int widthWindow = 640; + +static int heightWindow = 480; + +static bool logAdvanced = false; + +static std::string typeLog = "async"; + +int windowWidth() +{ + return widthWindow; +} + +int windowHeight() +{ + return heightWindow; +} + +bool isLogAdvanced() +{ + return logAdvanced; +} + +std::string logType() +{ + return typeLog; +} + +void Load(const std::filesystem::path& path) +{ + // If the configuration file does not exist, create it and return + std::error_code error; + if (!std::filesystem::exists(path, error)) + { + Save(path); + return; + } + + toml::value data; + + try + { + data = toml::parse(path); + } + catch (std::exception& ex) + { + fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); + return; + } + if (data.contains("General")) + { + const toml::value& general = data.at("General"); + + widthWindow = toml::find_or(general, "Window Width", 640); + heightWindow = toml::find_or(general, "Window Height", 480); + + logAdvanced = toml::find_or(general, "Advanced Log", false); + typeLog = toml::find_or(general, "Log Type", "async"); + } +} + +void Save(const std::filesystem::path& path) +{ + toml::ordered_value data; + + std::error_code error; + if (std::filesystem::exists(path, error)) + { + try + { + data = toml::parse(path); + } + catch (const std::exception& ex) + { + fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what()); + return; + } + } + else + { + if (error) + { + fmt::print("Filesystem error: {}\n", error.message()); + } + fmt::print("Saving new configuration file {}\n", path.string()); + } + + data["General"]["Window Width"] = widthWindow; + data["General"]["Window Height"] = heightWindow; + data["General"]["Advanced Log"] = logAdvanced; + data["General"]["Log Type"] = typeLog; + + std::ofstream file(path, std::ios::binary); + file << data; + file.close(); +} + +} // namespace Config diff --git a/core/Base/Config.h b/core/common/Config.h similarity index 87% rename from core/Base/Config.h rename to core/common/Config.h index 624b4e1..5f7ed27 100644 --- a/core/Base/Config.h +++ b/core/common/Config.h @@ -4,7 +4,8 @@ #include -namespace Config { +namespace Config +{ void Load(const std::filesystem::path& path); void Save(const std::filesystem::path& path); @@ -17,4 +18,4 @@ bool isLogAdvanced(); std::string logType(); -} // namespace Config +} // namespace Config diff --git a/core/common/Enum.h b/core/common/Enum.h new file mode 100644 index 0000000..3d3a719 --- /dev/null +++ b/core/common/Enum.h @@ -0,0 +1,153 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once +#include "Types.h" + +#define DECLARE_ENUM_FLAG_OPERATORS(type) \ + [[nodiscard]] constexpr type operator|(type a, type b) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) | static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator&(type a, type b) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) & static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator^(type a, type b) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) ^ static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator<<(type a, type b) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) << static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator>>(type a, type b) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) >> static_cast(b)); \ + } \ + constexpr type& operator|=(type& a, type b) noexcept \ + { \ + a = a | b; \ + return a; \ + } \ + constexpr type& operator&=(type& a, type b) noexcept \ + { \ + a = a & b; \ + return a; \ + } \ + constexpr type& operator^=(type& a, type b) noexcept \ + { \ + a = a ^ b; \ + return a; \ + } \ + constexpr type& operator<<=(type& a, type b) noexcept \ + { \ + a = a << b; \ + return a; \ + } \ + constexpr type& operator>>=(type& a, type b) noexcept \ + { \ + a = a >> b; \ + return a; \ + } \ + [[nodiscard]] constexpr type operator~(type key) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(~static_cast(key)); \ + } \ + [[nodiscard]] constexpr bool True(type key) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(key) != 0; \ + } \ + [[nodiscard]] constexpr bool False(type key) noexcept \ + { \ + using T = std::underlying_type_t; \ + return static_cast(key) == 0; \ + } + +namespace Base +{ + +template +class Flags +{ + public: + using IntType = std::underlying_type_t; + + Flags() {} + + Flags(IntType t) : m_bits(t) {} + + template + Flags(T f, Tx... fx) + { + set(f, fx...); + } + + template + void set(Tx... fx) + { + m_bits |= bits(fx...); + } + + void set(Flags flags) { m_bits |= flags.m_bits; } + + template + void clr(Tx... fx) + { + m_bits &= ~bits(fx...); + } + + void clr(Flags flags) { m_bits &= ~flags.m_bits; } + + template + bool any(Tx... fx) const + { + return (m_bits & bits(fx...)) != 0; + } + + template + bool all(Tx... fx) const + { + const IntType mask = bits(fx...); + return (m_bits & mask) == mask; + } + + bool test(T f) const { return any(f); } + + bool isClear() const { return m_bits == 0; } + + void clrAll() { m_bits = 0; } + + u32 raw() const { return m_bits; } + + Flags operator&(const Flags& other) const { return Flags(m_bits & other.m_bits); } + + Flags operator|(const Flags& other) const { return Flags(m_bits | other.m_bits); } + + Flags operator^(const Flags& other) const { return Flags(m_bits ^ other.m_bits); } + + bool operator==(const Flags& other) const { return m_bits == other.m_bits; } + + bool operator!=(const Flags& other) const { return m_bits != other.m_bits; } + + private: + IntType m_bits = 0; + + static IntType bit(T f) { return IntType(1) << static_cast(f); } + + template + static IntType bits(T f, Tx... fx) + { + return bit(f) | bits(fx...); + } + + static IntType bits() { return 0; } +}; + +} // namespace Base diff --git a/core/Base/Error.h b/core/common/Error.h similarity index 92% rename from core/Base/Error.h rename to core/common/Error.h index 2fed300..77c335d 100644 --- a/core/Base/Error.h +++ b/core/common/Error.h @@ -4,7 +4,8 @@ #include -namespace Base { +namespace Base +{ // Like GetLastErrorMsg(), but passing an explicit error code. // Defined in error.cpp. @@ -16,4 +17,4 @@ namespace Base { // Defined in error.cpp. [[nodiscard]] std::string GetLastErrorMsg(); -} // namespace Base +} // namespace Base diff --git a/core/common/IoFile.cpp b/core/common/IoFile.cpp new file mode 100644 index 0000000..c12c805 --- /dev/null +++ b/core/common/IoFile.cpp @@ -0,0 +1,492 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#include + +#include "IoFile.h" + +#include "Assert.h" +#include "Error.h" +#include "Logging/Log.h" +#include "PathUtil.h" + +#ifdef _WIN32 +#define ftruncate _chsize_s +#define fsync _commit + +#include +#include +#include +#else +#include +#endif + +#ifdef _MSC_VER +#define fileno _fileno +#define fseeko _fseeki64 +#define ftello _ftelli64 +#endif + +namespace fs = std::filesystem; + +namespace Base::FS +{ + +#ifdef _WIN32 + +[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileMode type) +{ + switch (type) + { + case FileMode::BinaryMode: + switch (mode) + { + case FileAccessMode::Read: + return L"rb"; + case FileAccessMode::Write: + return L"wb"; + case FileAccessMode::Append: + return L"ab"; + case FileAccessMode::ReadWrite: + return L"r+b"; + case FileAccessMode::ReadAppend: + return L"a+b"; + } + break; + case FileMode::TextMode: + switch (mode) + { + case FileAccessMode::Read: + return L"r"; + case FileAccessMode::Write: + return L"w"; + case FileAccessMode::Append: + return L"a"; + case FileAccessMode::ReadWrite: + return L"r+"; + case FileAccessMode::ReadAppend: + return L"a+"; + } + break; + } + + return L""; +} + +[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) +{ + switch (flag) + { + case FileShareFlag::ShareNone: + default: + return _SH_DENYRW; + case FileShareFlag::ShareReadOnly: + return _SH_DENYWR; + case FileShareFlag::ShareWriteOnly: + return _SH_DENYRD; + case FileShareFlag::ShareReadWrite: + return _SH_DENYNO; + } +} + +#else + +[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileMode type) +{ + switch (type) + { + case FileMode::BinaryMode: + switch (mode) + { + case FileAccessMode::Read: + return "rb"; + case FileAccessMode::Write: + return "wb"; + case FileAccessMode::Append: + return "ab"; + case FileAccessMode::ReadWrite: + return "r+b"; + case FileAccessMode::ReadAppend: + return "a+b"; + } + break; + case FileMode::TextMode: + switch (mode) + { + case FileAccessMode::Read: + return "r"; + case FileAccessMode::Write: + return "w"; + case FileAccessMode::Append: + return "a"; + case FileAccessMode::ReadWrite: + return "r+"; + case FileAccessMode::ReadAppend: + return "a+"; + } + break; + } + + return ""; +} + +#endif + +[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) +{ + switch (origin) + { + case SeekOrigin::SetOrigin: + default: + return SEEK_SET; + case SeekOrigin::CurrentPosition: + return SEEK_CUR; + case SeekOrigin::End: + return SEEK_END; + } +} + +IOFile::IOFile() = default; + +IOFile::IOFile(const std::string& path, FileAccessMode mode, FileMode type, FileShareFlag flag) +{ + Open(path, mode, type, flag); +} + +IOFile::IOFile(std::string_view path, FileAccessMode mode, FileMode type, FileShareFlag flag) +{ + Open(path, mode, type, flag); +} + +IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileMode type, FileShareFlag flag) +{ + Open(path, mode, type, flag); +} + +IOFile::~IOFile() +{ + Close(); +} + +IOFile::IOFile(IOFile&& other) noexcept +{ + std::swap(filePath, other.filePath); + std::swap(fileAccessMode, other.fileAccessMode); + std::swap(fileType, other.fileType); + std::swap(file, other.file); +} + +IOFile& IOFile::operator=(IOFile&& other) noexcept +{ + std::swap(filePath, other.filePath); + std::swap(fileAccessMode, other.fileAccessMode); + std::swap(fileType, other.fileType); + std::swap(file, other.file); + return *this; +} + +int IOFile::Open(const fs::path& path, FileAccessMode mode, FileMode type, FileShareFlag flag) +{ + Close(); + + filePath = path; + fileAccessMode = mode; + fileType = type; + + errno = 0; + int result = 0; + +#ifdef _WIN32 + if (flag != FileShareFlag::ShareNone) + { + file = ::_wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag)); + result = errno; + } + else + { + result = ::_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); + } +#else + file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); + result = errno; +#endif + + if (!IsOpen()) + { + const auto ec = std::error_code{result, std::generic_category()}; + LOG_ERROR(Base_Filesystem, "Failed to open the file at path={}, error_message={}", PathToUTF8String(filePath), + ec.message()); + } + + return result; +} + +void IOFile::Close() +{ + if (!IsOpen()) + { + return; + } + + errno = 0; + + const bool closeResult = ::fclose(file) == 0; + + if (!closeResult) + { + const auto ec = std::error_code{errno, std::generic_category()}; + LOG_ERROR(Base_Filesystem, "Failed to close the file at path={}, ec_message={}", PathToUTF8String(filePath), + ec.message()); + } + + file = nullptr; + +#ifdef _WIN64 + if (fileMapping && fileAccessMode == FileAccessMode::ReadWrite) + { + ::CloseHandle(std::bit_cast(fileMapping)); + } +#endif +} + +void IOFile::Unlink() +{ + if (!IsOpen()) + { + return; + } + + // Mark the file for deletion + std::error_code fsError; + fs::remove_all(filePath, fsError); + if (fsError) + { + LOG_ERROR(Base_Filesystem, "Failed to remove the file at '{}'. Reason: {}", PathToUTF8String(filePath), + fsError.message()); + } +} + +uptr IOFile::GetFileMapping() +{ + if (fileMapping) + { + return fileMapping; + } +#ifdef _WIN64 + const s32 fd = fileno(file); + + HANDLE hfile = reinterpret_cast(::_get_osfhandle(fd)); + HANDLE mapping = nullptr; + + /* if (fileAccessMode == FileAccessMode::ReadWrite) { + mapping = CreateFileMapping2(hfile, NULL, FILE_MAP_WRITE, PAGE_READWRITE, SEC_COMMIT, 0, + NULL, NULL, 0); + } else { + mapping = hfile; + }*/ + + mapping = hfile; + + fileMapping = std::bit_cast(mapping); + ASSERT_MSG(fileMapping, "{}", Base::GetLastErrorMsg()); + return fileMapping; +#else + fileMapping = fileno(file); + return fileMapping; +#endif +} + +std::string IOFile::ReadString(size_t length) const +{ + std::vector string_buffer(length); + + const u64 charsRead = ReadSpan(string_buffer); + const auto stringSize = charsRead != length ? charsRead : length; + + return std::string{string_buffer.data(), stringSize}; +} + +bool IOFile::Flush() const +{ + if (!IsOpen()) + { + return false; + } + + errno = 0; + + const bool flushResult = ::fflush(file) == 0; + + if (!flushResult) + { + const std::error_code ec = {errno, std::generic_category()}; + LOG_ERROR(Base_Filesystem, "Failed to flush the file at path={}, ec_message={}", PathToUTF8String(filePath), + ec.message()); + } + + return flushResult; +} + +bool IOFile::Commit() const +{ + if (!IsOpen()) + { + return false; + } + + errno = 0; + + bool commitResult = ::fflush(file) == 0 && ::fsync(::fileno(file)) == 0; + + if (!commitResult) + { + const std::error_code ec = {errno, std::generic_category()}; + LOG_ERROR(Base_Filesystem, "Failed to commit the file at path={}, ec_message={}", PathToUTF8String(filePath), + ec.message()); + } + + return commitResult; +} + +bool IOFile::SetSize(u64 size) const +{ + if (!IsOpen()) + { + return false; + } + + errno = 0; + + const bool setSizeResult = ::ftruncate(::fileno(file), static_cast(size)) == 0; + + if (!setSizeResult) + { + const std::error_code ec = {errno, std::generic_category()}; + LOG_ERROR(Base_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}", + PathToUTF8String(filePath), size, ec.message()); + } + + return setSizeResult; +} + +u64 IOFile::GetSize() const +{ + if (!IsOpen()) + { + return 0; + } + + // Flush any unwritten buffered data into the file prior to retrieving the file size. + std::fflush(file); + + u64 fSize = 0; + // fs::file_size can cause a exception if it is not a valid file + try + { + std::error_code ec{}; + fSize = fs::file_size(filePath, ec); + if (fSize == -1 || !fSize) + { + LOG_ERROR(Base_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", + PathToUTF8String(filePath), ec.message()); + return 0; + } + } + catch (const std::exception& ex) + { + LOG_ERROR(Base_Filesystem, "Exception trying to get file size. Exception: {}", ex.what()); + return 0; + } + + return fSize; +} + +bool IOFile::Seek(s64 offset, SeekOrigin origin) const +{ + if (!IsOpen()) + { + return false; + } + + if (False(fileAccessMode & (FileAccessMode::Write | FileAccessMode::Append))) + { + u64 size = GetSize(); + if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) + { + LOG_ERROR(Base_Filesystem, "Seeking past the end of the file"); + return false; + } + else if (origin == SeekOrigin::SetOrigin && static_cast(offset) > size) + { + LOG_ERROR(Base_Filesystem, "Seeking past the end of the file"); + return false; + } + else if (origin == SeekOrigin::End && offset > 0) + { + LOG_ERROR(Base_Filesystem, "Seeking past the end of the file"); + return false; + } + } + + errno = 0; + + const s32 seekResult = fseeko(file, offset, ToSeekOrigin(origin)) == 0; + + if (!seekResult) + { + const std::error_code ec = {errno, std::generic_category()}; + LOG_ERROR(Base_Filesystem, "Failed to seek the file at path={}, offset={}, origin={}, ec_message={}", + PathToUTF8String(filePath), offset, static_cast(origin), ec.message()); + } + + return seekResult; +} + +s64 IOFile::Tell() const +{ + if (!IsOpen()) + { + return 0; + } + + errno = 0; + + return ftello(file); +} + +u64 GetDirectorySize(const std::filesystem::path& path) +{ + if (!fs::exists(path)) + { + return 0; + } + + u64 total = 0; + for (const auto& entry : fs::recursive_directory_iterator(path)) + { + if (fs::is_regular_file(entry.path())) + { + // fs::file_size can cause a exception if it is not a valid file + try + { + std::error_code ec{}; + u64 fileSize = fs::file_size(entry.path(), ec); + if (fileSize != -1 && fileSize) + { + total += fileSize; + } + else + { + LOG_ERROR(Base_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", + PathToUTF8String(entry.path()), ec.message()); + } + } + catch (const std::exception& ex) + { + LOG_ERROR(Base_Filesystem, "Exception trying to get file size. Exception: {}", ex.what()); + } + } + } + return total; +} + +} // namespace Base::FS diff --git a/core/common/IoFile.h b/core/common/IoFile.h new file mode 100644 index 0000000..5cca462 --- /dev/null +++ b/core/common/IoFile.h @@ -0,0 +1,231 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include +#include + +#include "Enum.h" +#include "PathUtil.h" + +namespace Base::FS +{ + +enum class FileAccessMode +{ + /** + * If the file at path exists, it opens the file for reading. + * If the file at path does not exist, it fails to open the file. + */ + Read = 1 << 0, + /** + * If the file at path exists, the existing contents of the file are erased. + * The empty file is then opened for writing. + * If the file at path does not exist, it creates and opens a new empty file for writing. + */ + Write = 1 << 1, + /** + * If the file at path exists, it opens the file for reading and writing. + * If the file at path does not exist, it fails to open the file. + */ + ReadWrite = Read | Write, + /** + * If the file at path exists, it opens the file for appending. + * If the file at path does not exist, it creates and opens a new empty file for appending. + */ + Append = 1 << 2, + /** + * If the file at path exists, it opens the file for both reading and appending. + * If the file at path does not exist, it creates and opens a new empty file for both + * reading and appending. + */ + ReadAppend = Read | Append +}; +DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode) + +enum class FileMode +{ + BinaryMode, + TextMode +}; + +enum class FileShareFlag +{ + ShareNone, // Provides exclusive access to the file. + ShareReadOnly, // Provides read only shared access to the file. + ShareWriteOnly, // Provides write only shared access to the file. + ShareReadWrite // Provides read and write shared access to the file. +}; + +enum class SeekOrigin : u32 +{ + SetOrigin, // Seeks from the start of the file. + CurrentPosition, // Seeks from the current file pointer position. + End // Seeks from the end of the file. +}; + +class IOFile final +{ + public: + IOFile(); + + explicit IOFile(const std::string& path, FileAccessMode mode, FileMode type = FileMode::BinaryMode, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + + explicit IOFile(std::string_view path, FileAccessMode mode, FileMode type = FileMode::BinaryMode, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + explicit IOFile(const fs::path& path, FileAccessMode mode, FileMode type = FileMode::BinaryMode, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + + ~IOFile(); + + IOFile(const IOFile&) = delete; + IOFile& operator=(const IOFile&) = delete; + + IOFile(IOFile&& other) noexcept; + IOFile& operator=(IOFile&& other) noexcept; + + fs::path GetPath() const { return filePath; } + + FileAccessMode GetAccessMode() const { return fileAccessMode; } + + FileMode GetType() const { return fileType; } + + bool IsOpen() const { return file != nullptr; } + + uptr GetFileMapping(); + + int Open(const fs::path& path, FileAccessMode mode, FileMode type = FileMode::BinaryMode, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + void Close(); + + void Unlink(); + + bool Flush() const; + bool Commit() const; + + bool SetSize(u64 size) const; + u64 GetSize() const; + + bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const; + s64 Tell() const; + + template + size_t Read(T& data) const + { + if constexpr (std::contiguous_iterator) + { + using ContiguousType = typename T::value_type; + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return ReadSpan(data); + } + else + { + return ReadObject(data) ? 1 : 0; + } + } + + template + size_t Write(const T& data) const + { + if constexpr (std::contiguous_iterator) + { + using ContiguousType = typename T::value_type; + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return WriteSpan(data); + } + else + { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return WriteObject(data) ? 1 : 0; + } + } + + template + size_t ReadSpan(std::span data) const + { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + + if (!IsOpen()) + { + return 0; + } + + return ReadRaw(data.data(), data.size()); + } + + template + size_t ReadRaw(void* data, size_t size) const + { + return std::fread(data, sizeof(T), size, file); + } + + template + size_t WriteSpan(std::span data) const + { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + + if (!IsOpen()) + { + return 0; + } + + return std::fwrite(data.data(), sizeof(T), data.size(), file); + } + + template + bool ReadObject(T& object) const + { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + static_assert(!std::is_pointer_v, "T must not be a pointer to an object."); + + if (!IsOpen()) + { + return false; + } + + return std::fread(&object, sizeof(T), 1, file) == 1; + } + + template + size_t WriteRaw(const void* data, size_t size) const + { + return std::fwrite(data, sizeof(T), size, file); + } + + template + bool WriteObject(const T& object) const + { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + static_assert(!std::is_pointer_v, "T must not be a pointer to an object."); + + if (!IsOpen()) + { + return false; + } + + return std::fwrite(&object, sizeof(T), 1, file) == 1; + } + + std::string ReadString(size_t length) const; + + size_t WriteString(std::span string) const { return WriteSpan(string); } + + static size_t WriteBytes(const fs::path path, const auto& data) + { + IOFile out(path, FileAccessMode::Write); + return out.Write(data); + } + + private: + fs::path filePath{}; + FileAccessMode fileAccessMode{}; + FileMode fileType{}; + + std::FILE* file = nullptr; + uptr fileMapping = 0; +}; + +u64 GetDirectorySize(const fs::path& path); + +} // namespace Base::FS diff --git a/core/Base/Logging/Backend.cpp b/core/common/Logging/Backend.cpp similarity index 98% rename from core/Base/Logging/Backend.cpp rename to core/common/Logging/Backend.cpp index 6590da1..13aa748 100644 --- a/core/Base/Logging/Backend.cpp +++ b/core/common/Logging/Backend.cpp @@ -4,11 +4,11 @@ #include #include -#include "Base/BoundedQueue.h" -#include "Base/IoFile.h" -#include "Base/PathUtil.h" -#include "Base/StringUtil.h" -#include "Base/Thread.h" +#include "common/BoundedQueue.h" +#include "common/IoFile.h" +#include "common/PathUtil.h" +#include "common/StringUtil.h" +#include "common/Thread.h" #include "Backend.h" #include "Log.h" diff --git a/core/Base/Logging/Backend.h b/core/common/Logging/Backend.h similarity index 96% rename from core/Base/Logging/Backend.h rename to core/common/Logging/Backend.h index c5b2d44..abf2964 100644 --- a/core/Base/Logging/Backend.h +++ b/core/common/Logging/Backend.h @@ -5,7 +5,7 @@ #include #include -#include "Base/PathUtil.h" +#include "common/PathUtil.h" #include "Filter.h" diff --git a/core/Base/Logging/Filter.cpp b/core/common/Logging/Filter.cpp similarity index 99% rename from core/Base/Logging/Filter.cpp rename to core/common/Logging/Filter.cpp index b0c2e0c..522114b 100644 --- a/core/Base/Logging/Filter.cpp +++ b/core/common/Logging/Filter.cpp @@ -1,6 +1,6 @@ // Copyright 2025 Xenon Emulator Project. All rights reserved. -#include "Base/Assert.h" +#include "common/Assert.h" #include "Filter.h" diff --git a/core/Base/Logging/Filter.h b/core/common/Logging/Filter.h similarity index 100% rename from core/Base/Logging/Filter.h rename to core/common/Logging/Filter.h diff --git a/core/Base/Logging/Log.h b/core/common/Logging/Log.h similarity index 99% rename from core/Base/Logging/Log.h rename to core/common/Logging/Log.h index ee45a02..1033b2c 100644 --- a/core/Base/Logging/Log.h +++ b/core/common/Logging/Log.h @@ -5,7 +5,7 @@ #include #include -#include "Base/Config.h" +#include "common/Config.h" #include "LogTypes.h" diff --git a/core/Base/Logging/LogEntry.h b/core/common/Logging/LogEntry.h similarity index 100% rename from core/Base/Logging/LogEntry.h rename to core/common/Logging/LogEntry.h diff --git a/core/Base/Logging/LogTypes.h b/core/common/Logging/LogTypes.h similarity index 98% rename from core/Base/Logging/LogTypes.h rename to core/common/Logging/LogTypes.h index fc99703..a580535 100644 --- a/core/Base/Logging/LogTypes.h +++ b/core/common/Logging/LogTypes.h @@ -2,7 +2,7 @@ #pragma once -#include "Base/Types.h" +#include "common/Types.h" namespace Base { namespace Log { diff --git a/core/Base/Logging/TextFormatter.cpp b/core/common/Logging/TextFormatter.cpp similarity index 97% rename from core/Base/Logging/TextFormatter.cpp rename to core/common/Logging/TextFormatter.cpp index 36f4f0d..240bbc7 100644 --- a/core/Base/Logging/TextFormatter.cpp +++ b/core/common/Logging/TextFormatter.cpp @@ -1,7 +1,7 @@ // Copyright 2025 Xenon Emulator Project. All rights reserved. -#include "Base/Assert.h" -#include "Base/Config.h" +#include "common/Assert.h" +#include "common/Config.h" #include "TextFormatter.h" diff --git a/core/Base/Logging/TextFormatter.h b/core/common/Logging/TextFormatter.h similarity index 100% rename from core/Base/Logging/TextFormatter.h rename to core/common/Logging/TextFormatter.h diff --git a/core/common/PathUtil.cpp b/core/common/PathUtil.cpp new file mode 100644 index 0000000..85e973a --- /dev/null +++ b/core/common/PathUtil.cpp @@ -0,0 +1,134 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#include "PathUtil.h" +#include "common/Logging/Log.h" + +#include + +#include +#include +#ifdef _WIN32 +#include +#endif // _WIN32 +#ifdef __APPLE__ +#include +#include +#endif // __APPLE__ + +namespace Base +{ +namespace FS +{ + +const fs::path GetBinaryDirectory() +{ + fs::path fspath = {}; +#ifdef _WIN32 + char path[256]; + GetModuleFileNameA(nullptr, path, sizeof(path)); + fspath = path; +#elif __linux__ + fspath = fs::canonical("/proc/self/exe"); +#elif __APPLE__ + pid_t pid = getpid(); + char path[PROC_PIDPATHINFO_MAXSIZE]; + // While this is fine for a raw executable, + // an application bundle is read-only and these files + // should instead be placed in Application Support. + proc_pidpath(pid, path, sizeof(path)); + fspath = path; +#else + // Unknown, just return rootdir + fspath = fs::current_path() / "Pound"; +#endif + return fs::weakly_canonical(fmt::format("{}/..", fspath.string())); +} + +static auto UserPaths = [] +{ + auto currentDir = fs::current_path(); + auto binaryDir = GetBinaryDirectory(); + bool nixos = false; + + std::unordered_map paths; + + const auto insert_path = [&](PathType pound_path, const fs::path& new_path, bool create = true) + { + if (create && !fs::exists(new_path)) + fs::create_directory(new_path); + + paths.insert_or_assign(pound_path, new_path); + }; + + insert_path(PathType::BinaryDir, binaryDir, false); + // If we are in the nix store, it's read-only. Change to currentDir if needed + if (binaryDir.string().find("/nix/store/") != std::string::npos) + { + nixos = true; + } + if (nixos) + { + currentDir /= "files"; + insert_path(PathType::RootDir, currentDir); + insert_path(PathType::FirmwareDir, currentDir / FW_DIR); + insert_path(PathType::LogDir, currentDir / LOG_DIR); + } + else + { + insert_path(PathType::RootDir, currentDir, false); + insert_path(PathType::FirmwareDir, binaryDir / FW_DIR); + insert_path(PathType::LogDir, binaryDir / LOG_DIR); + } + return paths; +}(); + +std::string PathToUTF8String(const fs::path& path) +{ + const auto u8_string = path.u8string(); + return std::string{u8_string.begin(), u8_string.end()}; +} + +const fs::path& GetUserPath(PathType pound_path) +{ + return UserPaths.at(pound_path); +} + +std::string GetUserPathString(PathType pound_path) +{ + return PathToUTF8String(GetUserPath(pound_path)); +} + +std::vector ListFilesFromPath(const fs::path& path) +{ + std::vector fileList; + + fs::path _path = fs::weakly_canonical(path); + + for (auto& entry : fs::directory_iterator{_path}) + { + FileInfo fileInfo; + if (entry.is_directory()) + { + fileInfo.fileSize = 0; + fileInfo.fileType = FileType::Directory; + } + else + { + fileInfo.fileSize = fs::file_size(_path); + fileInfo.fileType = FileType::File; + } + + fileInfo.filePath = entry.path(); + fileInfo.fileName = entry.path().filename(); + fileList.push_back(fileInfo); + } + + return fileList; +} + +void SetUserPath(PathType pound_path, const fs::path& new_path) +{ + UserPaths.insert_or_assign(pound_path, new_path); +} +} // namespace FS +} // namespace Base diff --git a/core/Base/PathUtil.h b/core/common/PathUtil.h similarity index 51% rename from core/Base/PathUtil.h rename to core/common/PathUtil.h index c02f10a..513f99d 100644 --- a/core/Base/PathUtil.h +++ b/core/common/PathUtil.h @@ -7,27 +7,32 @@ namespace fs = std::filesystem; -namespace Base { -namespace FS { +namespace Base +{ +namespace FS +{ -enum class PathType { - BinaryDir, // Binary Path - FirmwareDir, // Where log files are stored - RootDir, // Execution Path - LogDir, // Where log files are stored +enum class PathType +{ + BinaryDir, // Binary Path + FirmwareDir, // Where log files are stored + RootDir, // Execution Path + LogDir, // Where log files are stored }; -enum FileType { - Directory, - File +enum FileType +{ + Directory, + File }; // Represents a given file inside a directory. -typedef struct _FileInfo { - fs::path fileName; // The file name and extension - fs::path filePath; // The file path - size_t fileSize; // File size - FileType fileType; // File Type (directory/file) +typedef struct _FileInfo +{ + fs::path fileName; // The file name and extension + fs::path filePath; // The file path + size_t fileSize; // File size + FileType fileType; // File Type (directory/file) } FileInfo; constexpr auto FW_DIR = "firmware"; @@ -37,19 +42,19 @@ constexpr auto LOG_DIR = "log"; constexpr auto LOG_FILE = "pound_log.txt"; // Converts a given fs::path to a UTF8 string. -[[nodiscard]] std::string PathToUTF8String(const fs::path &path); +[[nodiscard]] std::string PathToUTF8String(const fs::path& path); // Returns a fs::path object containing the current 'User' path. -[[nodiscard]] const fs::path &GetUserPath(PathType user_path); +[[nodiscard]] const fs::path& GetUserPath(PathType user_path); // Returns a string containing the current 'User' path. [[nodiscard]] std::string GetUserPathString(PathType user_path); // Returns a container with a list of the files inside the specified path. -[[nodiscard]] std::vector ListFilesFromPath(const fs::path &path); +[[nodiscard]] std::vector ListFilesFromPath(const fs::path& path); // Sets the current Path for a given PathType. -void SetUserPath(PathType user_path, const fs::path &new_path); +void SetUserPath(PathType user_path, const fs::path& new_path); -} // namespace FS -} // namespace Base +} // namespace FS +} // namespace Base diff --git a/core/common/PolyfillThread.h b/core/common/PolyfillThread.h new file mode 100644 index 0000000..2719621 --- /dev/null +++ b/core/common/PolyfillThread.h @@ -0,0 +1,385 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +// +// TODO: remove this file when jthread is supported by all compilation targets +// + +#pragma once + +#if defined(__cpp_lib_jthread) || !defined(_MSVC_VER) + +#include +#include +#include +#include +#include +#include + +namespace Base +{ + +template +void CondvarWait(Condvar& cv, std::unique_lock& lk, std::stop_token token, Pred&& pred) +{ + cv.wait(lk, token, std::forward(pred)); +} + +template +bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& rel_time) +{ + std::condition_variable_any cv; + std::mutex m; + + // Perform the timed wait. + std::unique_lock lk{m}; + return !cv.wait_for(lk, token, rel_time, [&] { return token.stop_requested(); }); +} + +} // namespace Base + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace std +{ +namespace polyfill +{ + +using stop_state_callback = size_t; + +class stop_state +{ + public: + stop_state() = default; + ~stop_state() = default; + + bool request_stop() + { + unique_lock lk{m_lock}; + + if (m_stop_requested) + { + // Already set, nothing to do. + return false; + } + + // Mark stop requested. + m_stop_requested = true; + + while (!m_callbacks.empty()) + { + // Get an iterator to the first element. + const auto it = m_callbacks.begin(); + + // Move the callback function out of the map. + function f; + swap(it->second, f); + + // Erase the now-empty map element. + m_callbacks.erase(it); + + // Run the callback. + if (f) + { + f(); + } + } + + return true; + } + + bool stop_requested() const + { + unique_lock lk{m_lock}; + return m_stop_requested; + } + + stop_state_callback insert_callback(function f) + { + unique_lock lk{m_lock}; + + if (m_stop_requested) + { + // Stop already requested. Don't insert anything, + // just run the callback synchronously. + if (f) + { + f(); + } + return 0; + } + + // Insert the callback. + stop_state_callback ret = ++m_next_callback; + m_callbacks.emplace(ret, std::move(f)); + return ret; + } + + void remove_callback(stop_state_callback cb) + { + unique_lock lk{m_lock}; + m_callbacks.erase(cb); + } + + private: + mutable recursive_mutex m_lock; + map> m_callbacks; + stop_state_callback m_next_callback{0}; + bool m_stop_requested{false}; +}; + +} // namespace polyfill + +class stop_token; +class stop_source; +struct nostopstate_t +{ + explicit nostopstate_t() = default; +}; +inline constexpr nostopstate_t nostopstate{}; + +template +class stop_callback; + +class stop_token +{ + public: + stop_token() noexcept = default; + + stop_token(const stop_token&) noexcept = default; + stop_token(stop_token&&) noexcept = default; + stop_token& operator=(const stop_token&) noexcept = default; + stop_token& operator=(stop_token&&) noexcept = default; + ~stop_token() = default; + + void swap(stop_token& other) noexcept { m_stop_state.swap(other.m_stop_state); } + + [[nodiscard]] bool stop_requested() const noexcept { return m_stop_state && m_stop_state->stop_requested(); } + [[nodiscard]] bool stop_possible() const noexcept { return m_stop_state != nullptr; } + + private: + friend class stop_source; + template + friend class stop_callback; + stop_token(shared_ptr stop_state) : m_stop_state(std::move(stop_state)) {} + + private: + shared_ptr m_stop_state; +}; + +class stop_source +{ + public: + stop_source() : m_stop_state(make_shared()) {} + explicit stop_source(nostopstate_t) noexcept {} + + stop_source(const stop_source&) noexcept = default; + stop_source(stop_source&&) noexcept = default; + stop_source& operator=(const stop_source&) noexcept = default; + stop_source& operator=(stop_source&&) noexcept = default; + ~stop_source() = default; + void swap(stop_source& other) noexcept { m_stop_state.swap(other.m_stop_state); } + + [[nodiscard]] stop_token get_token() const noexcept { return stop_token(m_stop_state); } + [[nodiscard]] bool stop_possible() const noexcept { return m_stop_state != nullptr; } + [[nodiscard]] bool stop_requested() const noexcept { return m_stop_state && m_stop_state->stop_requested(); } + bool request_stop() noexcept { return m_stop_state && m_stop_state->request_stop(); } + + private: + friend class jthread; + explicit stop_source(shared_ptr stop_state) : m_stop_state(std::move(stop_state)) {} + + private: + shared_ptr m_stop_state; +}; + +template +class stop_callback +{ + static_assert(is_nothrow_destructible_v); + static_assert(is_invocable_v); + + public: + using callback_type = Callback; + + template + requires constructible_from explicit stop_callback(const stop_token& st, C&& cb) noexcept( + is_nothrow_constructible_v) + : m_stop_state(st.m_stop_state) + { + if (m_stop_state) + { + m_callback = m_stop_state->insert_callback(std::move(cb)); + } + } + template + requires constructible_from explicit stop_callback(stop_token&& st, C&& cb) noexcept( + is_nothrow_constructible_v) + : m_stop_state(std::move(st.m_stop_state)) + { + if (m_stop_state) + { + m_callback = m_stop_state->insert_callback(std::move(cb)); + } + } + ~stop_callback() + { + if (m_stop_state && m_callback) + { + m_stop_state->remove_callback(m_callback); + } + } + + stop_callback(const stop_callback&) = delete; + stop_callback(stop_callback&&) = delete; + stop_callback& operator=(const stop_callback&) = delete; + stop_callback& operator=(stop_callback&&) = delete; + + private: + shared_ptr m_stop_state; + polyfill::stop_state_callback m_callback; +}; + +template +stop_callback(stop_token, Callback) -> stop_callback; + +class jthread +{ + public: + using id = thread::id; + using native_handle_type = thread::native_handle_type; + + jthread() noexcept = default; + + template , jthread>>> + explicit jthread(F&& f, Args&&... args) + : m_stop_state(make_shared()), + m_thread(make_thread(std::forward(f), std::forward(args)...)) + { + } + + ~jthread() + { + if (joinable()) + { + request_stop(); + join(); + } + } + + jthread(const jthread&) = delete; + jthread(jthread&&) noexcept = default; + jthread& operator=(const jthread&) = delete; + + jthread& operator=(jthread&& other) noexcept + { + m_thread.swap(other.m_thread); + m_stop_state.swap(other.m_stop_state); + return *this; + } + + void swap(jthread& other) noexcept + { + m_thread.swap(other.m_thread); + m_stop_state.swap(other.m_stop_state); + } + [[nodiscard]] bool joinable() const noexcept { return m_thread.joinable(); } + void join() { m_thread.join(); } + void detach() + { + m_thread.detach(); + m_stop_state.reset(); + } + + [[nodiscard]] id get_id() const noexcept { return m_thread.get_id(); } + [[nodiscard]] native_handle_type native_handle() { return m_thread.native_handle(); } + [[nodiscard]] stop_source get_stop_source() noexcept { return stop_source(m_stop_state); } + [[nodiscard]] stop_token get_stop_token() const noexcept { return stop_source(m_stop_state).get_token(); } + bool request_stop() noexcept { return get_stop_source().request_stop(); } + [[nodiscard]] static u32 hardware_concurrency() noexcept { return thread::hardware_concurrency(); } + + private: + template + thread make_thread(F&& f, Args&&... args) + { + if constexpr (is_invocable_v, stop_token, decay_t...>) + { + return thread(std::forward(f), get_stop_token(), std::forward(args)...); + } + else + { + return thread(std::forward(f), std::forward(args)...); + } + } + + shared_ptr m_stop_state; + thread m_thread; +}; + +} // namespace std + +namespace Base +{ + +template +void CondvarWait(Condvar& cv, std::unique_lock& lk, std::stop_token token, Pred pred) +{ + if (token.stop_requested()) + { + return; + } + + std::stop_callback callback(token, + [&] + { + { + std::scoped_lock lk2{*lk.mutex()}; + } + cv.notify_all(); + }); + + cv.wait(lk, [&] { return pred() || token.stop_requested(); }); +} + +template +bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& rel_time) +{ + if (token.stop_requested()) + { + return false; + } + + bool stop_requested = false; + std::condition_variable cv; + std::mutex m; + + std::stop_callback cb(token, + [&] + { + // Wake up the waiting thread. + { + std::scoped_lock lk{m}; + stop_requested = true; + } + cv.notify_one(); + }); + + // Perform the timed wait. + std::unique_lock lk{m}; + return !cv.wait_for(lk, rel_time, [&] { return stop_requested; }); +} + +} // namespace Base + +#endif diff --git a/core/common/StringUtil.cpp b/core/common/StringUtil.cpp new file mode 100644 index 0000000..319a9ba --- /dev/null +++ b/core/common/StringUtil.cpp @@ -0,0 +1,71 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#include "StringUtil.h" + +namespace Base +{ + +#ifdef _WIN32 +static std::wstring CPToUTF16(u32 code_page, std::string_view input) +{ + const s32 size = ::MultiByteToWideChar(code_page, 0, input.data(), static_cast(input.size()), nullptr, 0); + + if (size == 0) + { + return {}; + } + + std::wstring output(size, L'\0'); + + if (size != ::MultiByteToWideChar(code_page, 0, input.data(), static_cast(input.size()), &output[0], + static_cast(output.size()))) + { + output.clear(); + } + + return output; +} +#endif // ifdef _WIN32 + +std::string UTF16ToUTF8(const std::wstring_view& input) +{ +#ifdef _WIN32 + const s32 size = + ::WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(input.size()), nullptr, 0, nullptr, nullptr); + if (size == 0) + { + return {}; + } + + std::string output(size, '\0'); + + if (size != ::WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(input.size()), &output[0], + static_cast(output.size()), nullptr, nullptr)) + { + output.clear(); + } + return output; +#else + // Very hacky way to get cross-platform wstring conversion + return std::filesystem::path(input).string(); +#endif // ifdef _WIN32 +} + +std::wstring UTF8ToUTF16W(const std::string_view& input) +{ +#ifdef _WIN32 + return CPToUTF16(CP_UTF8, input); +#else + // Very hacky way to get cross-platform wstring conversion + return std::filesystem::path(input).wstring(); +#endif // ifdef _WIN32 +} + +std::string ToLower(const std::string& str) +{ + std::string out = str; + std::transform(str.begin(), str.end(), out.data(), ::tolower); + return out; +} + +} // namespace Base diff --git a/core/Base/StringUtil.h b/core/common/StringUtil.h similarity index 52% rename from core/Base/StringUtil.h rename to core/common/StringUtil.h index 0c57e9e..5dc8731 100644 --- a/core/Base/StringUtil.h +++ b/core/common/StringUtil.h @@ -2,10 +2,10 @@ #pragma once -#include -#include #include #include +#include +#include #ifdef _WIN32 #include @@ -13,10 +13,11 @@ #include #endif -namespace Base { +namespace Base +{ -[[nodiscard]] std::string UTF16ToUTF8(const std::wstring_view &input); -[[nodiscard]] std::wstring UTF8ToUTF16W(const std::string_view &str); -[[nodiscard]] std::string ToLower(const std::string &str); +[[nodiscard]] std::string UTF16ToUTF8(const std::wstring_view& input); +[[nodiscard]] std::wstring UTF8ToUTF16W(const std::string_view& str); +[[nodiscard]] std::string ToLower(const std::string& str); -} // namespace Base +} // namespace Base diff --git a/core/common/Thread.cpp b/core/common/Thread.cpp new file mode 100644 index 0000000..36ca65f --- /dev/null +++ b/core/common/Thread.cpp @@ -0,0 +1,228 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#include "Thread.h" +#include "Error.h" +#include "Logging/Log.h" + +#ifdef __APPLE__ +#include +#include +#include +#elif defined(_WIN32) +#include +#include "StringUtil.h" +#else + +#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif +#include +#endif +#ifndef _WIN32 +#include +#endif +#include +#include + +#ifdef __FreeBSD__ +#define cpu_set_t cpuset_t +#endif + +namespace Base +{ + +#ifdef __APPLE__ + +void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) +{ + // CPU time to grant. + const std::chrono::nanoseconds computation_ns = period_ns / 2; + + // Determine the timebase for converting time to ticks. + struct mach_timebase_info timebase{}; + mach_timebase_info(&timebase); + const auto ticks_per_ns = static_cast(timebase.denom) / static_cast(timebase.numer); + + const auto period_ticks = static_cast(static_cast(period_ns.count()) * ticks_per_ns); + const auto computation_ticks = static_cast(static_cast(computation_ns.count()) * ticks_per_ns); + + thread_time_constraint_policy policy = { + .period = period_ticks, + .computation = computation_ticks, + // Should not matter since preemptible is false, but needs to be >= computation regardless. + .constraint = computation_ticks, + .preemptible = false, + }; + + int ret = thread_policy_set(pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY, + reinterpret_cast(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (ret != KERN_SUCCESS) + { + LOG_ERROR(Base, "Could not set thread to real-time with period {} ns: {}", period_ns.count(), ret); + } +} + +#else + +void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) +{ + // Not implemented +} + +#endif + +#ifdef _WIN32 + +void SetCurrentThreadPriority(ThreadPriority new_priority) +{ + const auto handle = GetCurrentThread(); + int windows_priority = 0; + switch (new_priority) + { + case ThreadPriority::Low: + windows_priority = THREAD_PRIORITY_BELOW_NORMAL; + break; + case ThreadPriority::Normal: + windows_priority = THREAD_PRIORITY_NORMAL; + break; + case ThreadPriority::High: + windows_priority = THREAD_PRIORITY_ABOVE_NORMAL; + break; + case ThreadPriority::VeryHigh: + windows_priority = THREAD_PRIORITY_HIGHEST; + break; + case ThreadPriority::Critical: + windows_priority = THREAD_PRIORITY_TIME_CRITICAL; + break; + default: + windows_priority = THREAD_PRIORITY_NORMAL; + break; + } + SetThreadPriority(handle, windows_priority); +} + +static void AccurateSleep(std::chrono::nanoseconds duration) +{ + LARGE_INTEGER interval{ + .QuadPart = -1 * (duration.count() / 100u), + }; + const HANDLE timer = ::CreateWaitableTimer(nullptr, TRUE, nullptr); + SetWaitableTimer(timer, &interval, 0, nullptr, nullptr, 0); + WaitForSingleObject(timer, INFINITE); + ::CloseHandle(timer); +} + +#else + +void SetCurrentThreadPriority(ThreadPriority new_priority) +{ + pthread_t this_thread = pthread_self(); + + constexpr auto scheduling_type = SCHED_OTHER; + const s32 max_prio = sched_get_priority_max(scheduling_type); + const s32 min_prio = sched_get_priority_min(scheduling_type); + const u32 level = std::max(static_cast(new_priority) + 1, 4U); + + struct sched_param params; + if (max_prio > min_prio) + { + params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4; + } + else + { + params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4; + } + + pthread_setschedparam(this_thread, scheduling_type, ¶ms); +} + +static void AccurateSleep(std::chrono::nanoseconds duration) +{ + std::this_thread::sleep_for(duration); +} + +#endif + +#if defined(_MSC_VER) || defined(__MINGW32__) + +// Sets the debugger-visible name of the current thread. +void SetCurrentThreadName(const std::string_view& name) +{ + SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data()); +} + +void SetThreadName(void* thread, const std::string_view& name) +{ + const char* nchar = name.data(); + std::string truncated(nchar, std::min(name.size(), static_cast(15))); + SetThreadDescription(thread, UTF8ToUTF16W(name).data()); +} + +#else // !MSVC_VER, so must be POSIX threads + +// MinGW with the POSIX threading model does not support pthread_setname_np +#if !defined(_WIN32) || defined(_MSC_VER) +void SetCurrentThreadName(const std::string_view& name) +{ + const char* nchar = name.data(); +#ifdef __APPLE__ + pthread_setname_np(nchar); +#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) + pthread_set_name_np(pthread_self(), nchar); +#elif defined(__NetBSD__) + pthread_setname_np(pthread_self(), "%s", (void*)nchar); +#elif defined(__linux__) + // Linux limits thread names to 15 characters and will outright reject any + // attempt to set a longer name with ERANGE. + std::string truncated(nchar, std::min(name.size(), static_cast(15))); + if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) + { + errno = e; + } +#else + pthread_setname_np(pthread_self(), nchar); +#endif +} + +void SetThreadName(void* thread, const std::string_view& name) +{ + // TODO +} +#endif + +#if defined(_WIN32) +void SetCurrentThreadName(const std::string_view& name) +{ + // Do Nothing on MinGW +} + +void SetThreadName(void* thread, const std::string_view& name) +{ + // Do Nothing on MinGW +} +#endif + +#endif + +AccurateTimer::AccurateTimer(std::chrono::nanoseconds target_interval) : target_interval(target_interval) {} + +void AccurateTimer::Start() +{ + const auto begin_sleep = std::chrono::high_resolution_clock::now(); + if (total_wait.count() > 0) + { + AccurateSleep(total_wait); + } + start_time = std::chrono::high_resolution_clock::now(); + total_wait -= std::chrono::duration_cast(start_time - begin_sleep); +} + +void AccurateTimer::End() +{ + const auto now = std::chrono::high_resolution_clock::now(); + total_wait += target_interval - std::chrono::duration_cast(now - start_time); +} + +} // namespace Base diff --git a/core/common/Thread.h b/core/common/Thread.h new file mode 100644 index 0000000..3c9a2e7 --- /dev/null +++ b/core/common/Thread.h @@ -0,0 +1,45 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include +#include "Types.h" + +namespace Base +{ + +enum class ThreadPriority : u32 +{ + Low = 0, + Normal = 1, + High = 2, + VeryHigh = 3, + Critical = 4 +}; + +void SetCurrentThreadRealtime(std::chrono::nanoseconds period_ns); + +void SetCurrentThreadPriority(ThreadPriority new_priority); + +void SetCurrentThreadName(const std::string_view& name); + +void SetThreadName(void* thread, const std::string_view& name); + +class AccurateTimer +{ + std::chrono::nanoseconds target_interval{}; + std::chrono::nanoseconds total_wait{}; + + std::chrono::high_resolution_clock::time_point start_time; + + public: + explicit AccurateTimer(std::chrono::nanoseconds target_interval); + + void Start(); + + void End(); + + std::chrono::nanoseconds GetTotalWait() const { return total_wait; } +}; + +} // namespace Base diff --git a/core/Base/Types.h b/core/common/Types.h similarity index 51% rename from core/Base/Types.h rename to core/common/Types.h index bd9a387..c72fa7e 100644 --- a/core/Base/Types.h +++ b/core/common/Types.h @@ -28,25 +28,30 @@ using f64 = double; // Function pointer template - requires std::is_function_v -using fptr = std::add_pointer_t; +requires std::is_function_v using fptr = std::add_pointer_t; // UDLs for memory size values -[[nodiscard]] constexpr u64 operator""_KB(const u64 x) { - return 1000ULL * x; +[[nodiscard]] constexpr u64 operator""_KB(const u64 x) +{ + return 1000ULL * x; } -[[nodiscard]] constexpr u64 operator""_KiB(const u64 x) { - return 1024ULL * x; +[[nodiscard]] constexpr u64 operator""_KiB(const u64 x) +{ + return 1024ULL * x; } -[[nodiscard]] constexpr u64 operator""_MB(const u64 x) { - return 1000_KB * x; +[[nodiscard]] constexpr u64 operator""_MB(const u64 x) +{ + return 1000_KB * x; } -[[nodiscard]] constexpr u64 operator""_MiB(const u64 x) { - return 1024_KiB * x; +[[nodiscard]] constexpr u64 operator""_MiB(const u64 x) +{ + return 1024_KiB * x; } -[[nodiscard]] constexpr u64 operator""_GB(const u64 x) { - return 1000_MB * x; +[[nodiscard]] constexpr u64 operator""_GB(const u64 x) +{ + return 1000_MB * x; } -[[nodiscard]] constexpr u64 operator""_GiB(const u64 x) { - return 1024_MiB * x; +[[nodiscard]] constexpr u64 operator""_GiB(const u64 x) +{ + return 1024_MiB * x; } diff --git a/core/Base/swap.h b/core/common/swap.h similarity index 61% rename from core/Base/swap.h rename to core/common/swap.h index fde343e..ed4f988 100644 --- a/core/Base/swap.h +++ b/core/common/swap.h @@ -15,16 +15,20 @@ #include #include "common/common_types.h" -namespace Common { +namespace Common +{ #ifdef _MSC_VER -[[nodiscard]] inline u16 swap16(u16 data) noexcept { +[[nodiscard]] inline u16 swap16(u16 data) noexcept +{ return _byteswap_ushort(data); } -[[nodiscard]] inline u32 swap32(u32 data) noexcept { +[[nodiscard]] inline u32 swap32(u32 data) noexcept +{ return _byteswap_ulong(data); } -[[nodiscard]] inline u64 swap64(u64 data) noexcept { +[[nodiscard]] inline u64 swap64(u64 data) noexcept +{ return _byteswap_uint64(data); } #elif defined(__clang__) || defined(__GNUC__) @@ -34,25 +38,31 @@ namespace Common { #undef swap32 #undef swap64 #endif -[[nodiscard]] inline u16 swap16(u16 data) noexcept { +[[nodiscard]] inline u16 swap16(u16 data) noexcept +{ return __builtin_bswap16(data); } -[[nodiscard]] inline u32 swap32(u32 data) noexcept { +[[nodiscard]] inline u32 swap32(u32 data) noexcept +{ return __builtin_bswap32(data); } -[[nodiscard]] inline u64 swap64(u64 data) noexcept { +[[nodiscard]] inline u64 swap64(u64 data) noexcept +{ return __builtin_bswap64(data); } #else // Generic implementation. -[[nodiscard]] inline u16 swap16(u16 data) noexcept { +[[nodiscard]] inline u16 swap16(u16 data) noexcept +{ return (data >> 8) | (data << 8); } -[[nodiscard]] inline u32 swap32(u32 data) noexcept { - return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) | - ((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24); +[[nodiscard]] inline u32 swap32(u32 data) noexcept +{ + return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) | ((data & 0x0000FF00U) << 8) | + ((data & 0x000000FFU) << 24); } -[[nodiscard]] inline u64 swap64(u64 data) noexcept { +[[nodiscard]] inline u64 swap64(u64 data) noexcept +{ return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) | ((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) | ((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) | @@ -60,7 +70,8 @@ namespace Common { } #endif -[[nodiscard]] inline float swapf(float f) noexcept { +[[nodiscard]] inline float swapf(float f) noexcept +{ static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t."); u32 value; @@ -72,7 +83,8 @@ namespace Common { return f; } -[[nodiscard]] inline double swapd(double f) noexcept { +[[nodiscard]] inline double swapd(double f) noexcept +{ static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t."); u64 value; @@ -84,283 +96,258 @@ namespace Common { return f; } -} // Namespace Common +} // Namespace Common template -struct swap_struct_t { +struct swap_struct_t +{ using swapped_t = swap_struct_t; -protected: + protected: T value; - static T swap(T v) { - return F::swap(v); - } + static T swap(T v) { return F::swap(v); } -public: - T swap() const { - return swap(value); - } + public: + T swap() const { return swap(value); } swap_struct_t() = default; swap_struct_t(const T& v) : value(swap(v)) {} template - swapped_t& operator=(const S& source) { + swapped_t& operator=(const S& source) + { value = swap(static_cast(source)); return *this; } - operator s8() const { - return static_cast(swap()); - } - operator u8() const { - return static_cast(swap()); - } - operator s16() const { - return static_cast(swap()); - } - operator u16() const { - return static_cast(swap()); - } - operator s32() const { - return static_cast(swap()); - } - operator u32() const { - return static_cast(swap()); - } - operator s64() const { - return static_cast(swap()); - } - operator u64() const { - return static_cast(swap()); - } - operator float() const { - return static_cast(swap()); - } - operator double() const { - return static_cast(swap()); - } + operator s8() const { return static_cast(swap()); } + operator u8() const { return static_cast(swap()); } + operator s16() const { return static_cast(swap()); } + operator u16() const { return static_cast(swap()); } + operator s32() const { return static_cast(swap()); } + operator u32() const { return static_cast(swap()); } + operator s64() const { return static_cast(swap()); } + operator u64() const { return static_cast(swap()); } + operator float() const { return static_cast(swap()); } + operator double() const { return static_cast(swap()); } // +v - swapped_t operator+() const { - return +swap(); - } + swapped_t operator+() const { return +swap(); } // -v - swapped_t operator-() const { - return -swap(); - } + swapped_t operator-() const { return -swap(); } // v / 5 - swapped_t operator/(const swapped_t& i) const { - return swap() / i.swap(); - } + swapped_t operator/(const swapped_t& i) const { return swap() / i.swap(); } template - swapped_t operator/(const S& i) const { + swapped_t operator/(const S& i) const + { return swap() / i; } // v * 5 - swapped_t operator*(const swapped_t& i) const { - return swap() * i.swap(); - } + swapped_t operator*(const swapped_t& i) const { return swap() * i.swap(); } template - swapped_t operator*(const S& i) const { + swapped_t operator*(const S& i) const + { return swap() * i; } // v + 5 - swapped_t operator+(const swapped_t& i) const { - return swap() + i.swap(); - } + swapped_t operator+(const swapped_t& i) const { return swap() + i.swap(); } template - swapped_t operator+(const S& i) const { + swapped_t operator+(const S& i) const + { return swap() + static_cast(i); } // v - 5 - swapped_t operator-(const swapped_t& i) const { - return swap() - i.swap(); - } + swapped_t operator-(const swapped_t& i) const { return swap() - i.swap(); } template - swapped_t operator-(const S& i) const { + swapped_t operator-(const S& i) const + { return swap() - static_cast(i); } // v += 5 - swapped_t& operator+=(const swapped_t& i) { + swapped_t& operator+=(const swapped_t& i) + { value = swap(swap() + i.swap()); return *this; } template - swapped_t& operator+=(const S& i) { + swapped_t& operator+=(const S& i) + { value = swap(swap() + static_cast(i)); return *this; } // v -= 5 - swapped_t& operator-=(const swapped_t& i) { + swapped_t& operator-=(const swapped_t& i) + { value = swap(swap() - i.swap()); return *this; } template - swapped_t& operator-=(const S& i) { + swapped_t& operator-=(const S& i) + { value = swap(swap() - static_cast(i)); return *this; } // ++v - swapped_t& operator++() { + swapped_t& operator++() + { value = swap(swap() + 1); return *this; } // --v - swapped_t& operator--() { + swapped_t& operator--() + { value = swap(swap() - 1); return *this; } // v++ - swapped_t operator++(int) { + swapped_t operator++(int) + { swapped_t old = *this; value = swap(swap() + 1); return old; } // v-- - swapped_t operator--(int) { + swapped_t operator--(int) + { swapped_t old = *this; value = swap(swap() - 1); return old; } // Comparison // v == i - bool operator==(const swapped_t& i) const { - return swap() == i.swap(); - } + bool operator==(const swapped_t& i) const { return swap() == i.swap(); } template - bool operator==(const S& i) const { + bool operator==(const S& i) const + { return swap() == i; } // v != i - bool operator!=(const swapped_t& i) const { - return swap() != i.swap(); - } + bool operator!=(const swapped_t& i) const { return swap() != i.swap(); } template - bool operator!=(const S& i) const { + bool operator!=(const S& i) const + { return swap() != i; } // v > i - bool operator>(const swapped_t& i) const { - return swap() > i.swap(); - } + bool operator>(const swapped_t& i) const { return swap() > i.swap(); } template - bool operator>(const S& i) const { + bool operator>(const S& i) const + { return swap() > i; } // v < i - bool operator<(const swapped_t& i) const { - return swap() < i.swap(); - } + bool operator<(const swapped_t& i) const { return swap() < i.swap(); } template - bool operator<(const S& i) const { + bool operator<(const S& i) const + { return swap() < i; } // v >= i - bool operator>=(const swapped_t& i) const { - return swap() >= i.swap(); - } + bool operator>=(const swapped_t& i) const { return swap() >= i.swap(); } template - bool operator>=(const S& i) const { + bool operator>=(const S& i) const + { return swap() >= i; } // v <= i - bool operator<=(const swapped_t& i) const { - return swap() <= i.swap(); - } + bool operator<=(const swapped_t& i) const { return swap() <= i.swap(); } template - bool operator<=(const S& i) const { + bool operator<=(const S& i) const + { return swap() <= i; } // logical - swapped_t operator!() const { - return !swap(); - } + swapped_t operator!() const { return !swap(); } // bitmath - swapped_t operator~() const { - return ~swap(); - } + swapped_t operator~() const { return ~swap(); } - swapped_t operator&(const swapped_t& b) const { - return swap() & b.swap(); - } + swapped_t operator&(const swapped_t& b) const { return swap() & b.swap(); } template - swapped_t operator&(const S& b) const { + swapped_t operator&(const S& b) const + { return swap() & b; } - swapped_t& operator&=(const swapped_t& b) { + swapped_t& operator&=(const swapped_t& b) + { value = swap(swap() & b.swap()); return *this; } template - swapped_t& operator&=(const S b) { + swapped_t& operator&=(const S b) + { value = swap(swap() & b); return *this; } - swapped_t operator|(const swapped_t& b) const { - return swap() | b.swap(); - } + swapped_t operator|(const swapped_t& b) const { return swap() | b.swap(); } template - swapped_t operator|(const S& b) const { + swapped_t operator|(const S& b) const + { return swap() | b; } - swapped_t& operator|=(const swapped_t& b) { + swapped_t& operator|=(const swapped_t& b) + { value = swap(swap() | b.swap()); return *this; } template - swapped_t& operator|=(const S& b) { + swapped_t& operator|=(const S& b) + { value = swap(swap() | b); return *this; } - swapped_t operator^(const swapped_t& b) const { - return swap() ^ b.swap(); - } + swapped_t operator^(const swapped_t& b) const { return swap() ^ b.swap(); } template - swapped_t operator^(const S& b) const { + swapped_t operator^(const S& b) const + { return swap() ^ b; } - swapped_t& operator^=(const swapped_t& b) { + swapped_t& operator^=(const swapped_t& b) + { value = swap(swap() ^ b.swap()); return *this; } template - swapped_t& operator^=(const S& b) { + swapped_t& operator^=(const S& b) + { value = swap(swap() ^ b); return *this; } template - swapped_t operator<<(const S& b) const { + swapped_t operator<<(const S& b) const + { return swap() << b; } template - swapped_t& operator<<=(const S& b) const { + swapped_t& operator<<=(const S& b) const + { value = swap(swap() << b); return *this; } template - swapped_t operator>>(const S& b) const { + swapped_t operator>>(const S& b) const + { return swap() >> b; } template - swapped_t& operator>>=(const S& b) const { + swapped_t& operator>>=(const S& b) const + { value = swap(swap() >> b); return *this; } @@ -417,133 +404,140 @@ public: // Arithmetic template -S operator+(const S& i, const swap_struct_t v) { +S operator+(const S& i, const swap_struct_t v) +{ return i + v.swap(); } template -S operator-(const S& i, const swap_struct_t v) { +S operator-(const S& i, const swap_struct_t v) +{ return i - v.swap(); } template -S operator/(const S& i, const swap_struct_t v) { +S operator/(const S& i, const swap_struct_t v) +{ return i / v.swap(); } template -S operator*(const S& i, const swap_struct_t v) { +S operator*(const S& i, const swap_struct_t v) +{ return i * v.swap(); } template -S operator%(const S& i, const swap_struct_t v) { +S operator%(const S& i, const swap_struct_t v) +{ return i % v.swap(); } // Arithmetic + assignments template -S& operator+=(S& i, const swap_struct_t v) { +S& operator+=(S& i, const swap_struct_t v) +{ i += v.swap(); return i; } template -S& operator-=(S& i, const swap_struct_t v) { +S& operator-=(S& i, const swap_struct_t v) +{ i -= v.swap(); return i; } // Logical template -S operator&(const S& i, const swap_struct_t v) { +S operator&(const S& i, const swap_struct_t v) +{ return i & v.swap(); } // Comparison template -bool operator<(const S& p, const swap_struct_t v) { +bool operator<(const S& p, const swap_struct_t v) +{ return p < v.swap(); } template -bool operator>(const S& p, const swap_struct_t v) { +bool operator>(const S& p, const swap_struct_t v) +{ return p > v.swap(); } template -bool operator<=(const S& p, const swap_struct_t v) { +bool operator<=(const S& p, const swap_struct_t v) +{ return p <= v.swap(); } template -bool operator>=(const S& p, const swap_struct_t v) { +bool operator>=(const S& p, const swap_struct_t v) +{ return p >= v.swap(); } template -bool operator!=(const S& p, const swap_struct_t v) { +bool operator!=(const S& p, const swap_struct_t v) +{ return p != v.swap(); } template -bool operator==(const S& p, const swap_struct_t v) { +bool operator==(const S& p, const swap_struct_t v) +{ return p == v.swap(); } template -struct swap_64_t { - static T swap(T x) { - return static_cast(Common::swap64(x)); - } +struct swap_64_t +{ + static T swap(T x) { return static_cast(Common::swap64(x)); } }; template -struct swap_32_t { - static T swap(T x) { - return static_cast(Common::swap32(x)); - } +struct swap_32_t +{ + static T swap(T x) { return static_cast(Common::swap32(x)); } }; template -struct swap_16_t { - static T swap(T x) { - return static_cast(Common::swap16(x)); - } +struct swap_16_t +{ + static T swap(T x) { return static_cast(Common::swap16(x)); } }; template -struct swap_float_t { - static T swap(T x) { - return static_cast(Common::swapf(x)); - } +struct swap_float_t +{ + static T swap(T x) { return static_cast(Common::swapf(x)); } }; template -struct swap_double_t { - static T swap(T x) { - return static_cast(Common::swapd(x)); - } +struct swap_double_t +{ + static T swap(T x) { return static_cast(Common::swapd(x)); } }; template -struct swap_enum_t { +struct swap_enum_t +{ static_assert(std::is_enum_v); using base = std::underlying_type_t; -public: + public: swap_enum_t() = default; swap_enum_t(const T& v) : value(swap(v)) {} - swap_enum_t& operator=(const T& v) { + swap_enum_t& operator=(const T& v) + { value = swap(v); return *this; } - operator T() const { - return swap(value); - } + operator T() const { return swap(value); } - explicit operator base() const { - return static_cast(swap(value)); - } + explicit operator base() const { return static_cast(swap(value)); } -protected: + protected: T value{}; // clang-format off using swap_t = std::conditional_t< @@ -554,13 +548,15 @@ protected: std::is_same_v, swap_64_t, std::conditional_t< std::is_same_v, swap_64_t, void>>>>>>; // clang-format on - static T swap(T x) { - return static_cast(swap_t::swap(static_cast(x))); - } + static T swap(T x) { return static_cast(swap_t::swap(static_cast(x))); } }; -struct SwapTag {}; // Use the different endianness from the system -struct KeepTag {}; // Use the same endianness as the system +struct SwapTag +{ +}; // Use the different endianness from the system +struct KeepTag +{ +}; // Use the same endianness as the system template struct AddEndian; @@ -568,64 +564,76 @@ struct AddEndian; // KeepTag specializations template -struct AddEndian { +struct AddEndian +{ using type = T; }; // SwapTag specializations template <> -struct AddEndian { +struct AddEndian +{ using type = u8; }; template <> -struct AddEndian { +struct AddEndian +{ using type = swap_struct_t>; }; template <> -struct AddEndian { +struct AddEndian +{ using type = swap_struct_t>; }; template <> -struct AddEndian { +struct AddEndian +{ using type = swap_struct_t>; }; template <> -struct AddEndian { +struct AddEndian +{ using type = s8; }; template <> -struct AddEndian { +struct AddEndian +{ using type = swap_struct_t>; }; template <> -struct AddEndian { +struct AddEndian +{ using type = swap_struct_t>; }; template <> -struct AddEndian { +struct AddEndian +{ using type = swap_struct_t>; }; template <> -struct AddEndian { +struct AddEndian +{ using type = swap_struct_t>; }; template <> -struct AddEndian { +struct AddEndian +{ using type = swap_struct_t>; }; template -struct AddEndian { +struct AddEndian +{ static_assert(std::is_enum_v); using type = swap_enum_t; }; diff --git a/core/frontend/CMakeLists.txt b/core/frontend/CMakeLists.txt new file mode 100644 index 0000000..33dc67a --- /dev/null +++ b/core/frontend/CMakeLists.txt @@ -0,0 +1,11 @@ +#Copyright 2025 Pound Emulator Project.All rights reserved. + +#GUI sources +set(GUI_SOURCES ${CMAKE_CURRENT_SOURCE_DIR} / gui.cpp ${CMAKE_CURRENT_SOURCE_DIR} / + color.cpp ${CMAKE_CURRENT_SOURCE_DIR} / panels.cpp) + +#Add all GUI sources to the main target + target_sources(Pound PRIVATE ${GUI_SOURCES}) + +#Include directories for GUI + target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /..) diff --git a/gui/color.cpp b/core/frontend/color.cpp similarity index 100% rename from gui/color.cpp rename to core/frontend/color.cpp diff --git a/gui/color.h b/core/frontend/color.h similarity index 99% rename from gui/color.h rename to core/frontend/color.h index 5a9d99e..253c92e 100755 --- a/gui/color.h +++ b/core/frontend/color.h @@ -2,6 +2,8 @@ #define POUND_COLORS_H #include +#include +#include namespace gui::color { diff --git a/gui/gui.cpp b/core/frontend/gui.cpp old mode 100755 new mode 100644 similarity index 99% rename from gui/gui.cpp rename to core/frontend/gui.cpp index 901b0bb..2240d7e --- a/gui/gui.cpp +++ b/core/frontend/gui.cpp @@ -1,7 +1,7 @@ #include "gui.h" -#include "Base/Assert.h" -#include "Base/Logging/Log.h" #include "color.h" +#include "common/Assert.h" +#include "common/Logging/Log.h" #include "imgui_impl_opengl3_loader.h" #include diff --git a/gui/gui.h b/core/frontend/gui.h similarity index 99% rename from gui/gui.h rename to core/frontend/gui.h index 1561fb2..33034ba 100755 --- a/gui/gui.h +++ b/core/frontend/gui.h @@ -3,7 +3,7 @@ #include #include -#include "memory/arena.h" +#include "host/memory/arena.h" namespace gui { diff --git a/gui/panels.cpp b/core/frontend/panels.cpp old mode 100755 new mode 100644 similarity index 98% rename from gui/panels.cpp rename to core/frontend/panels.cpp index 49f1ac1..d58abae --- a/gui/panels.cpp +++ b/core/frontend/panels.cpp @@ -1,8 +1,8 @@ #include "panels.h" +#include #include -#include "Base/Assert.h" #include "arm64/isa.h" -#include "imgui.h" +#include "common/Assert.h" int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* panel, performance_data_t* data, std::chrono::steady_clock::time_point* last_render) diff --git a/gui/panels.h b/core/frontend/panels.h old mode 100755 new mode 100644 similarity index 98% rename from gui/panels.h rename to core/frontend/panels.h index 4aa691e..49d1013 --- a/gui/panels.h +++ b/core/frontend/panels.h @@ -2,6 +2,8 @@ #define POUND_PANELS_H #include +#include +#include #include namespace gui::panel diff --git a/core/host/CMakeLists.txt b/core/host/CMakeLists.txt new file mode 100644 index 0000000..bbd08df --- /dev/null +++ b/core/host/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright 2025 Pound Emulator Project. All rights reserved. + +set(HOST_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/memory/arena.cpp +) + +target_sources(Pound PRIVATE + ${HOST_SOURCES} +) + +target_include_directories(Pound PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) diff --git a/core/memory/arena.cpp b/core/host/memory/arena.cpp similarity index 94% rename from core/memory/arena.cpp rename to core/host/memory/arena.cpp index 001918a..e2ded77 100644 --- a/core/memory/arena.cpp +++ b/core/host/memory/arena.cpp @@ -1,10 +1,12 @@ #include "arena.h" #include +#include #ifndef WIN32 #include #endif -namespace pound::memory { +namespace pound::host::memory +{ arena_t arena_init(size_t capacity) { @@ -52,4 +54,4 @@ void arena_free(memory::arena_t* arena) // TODO(GloriousTaco:memory): Replace free with a memory safe alternative. free(arena->data); } -} +} // namespace pound::host::memory diff --git a/core/memory/arena.h b/core/host/memory/arena.h similarity index 87% rename from core/memory/arena.h rename to core/host/memory/arena.h index 8ea3add..59722b6 100644 --- a/core/memory/arena.h +++ b/core/host/memory/arena.h @@ -5,7 +5,7 @@ #include #include -namespace pound::memory +namespace pound::host::memory { #define POISON_PATTERN 0xAA @@ -40,7 +40,7 @@ typedef struct * arena_init - Initialize a memory arena with specified capacity. * * SYNOPSIS - * memory::arena_t memory::arena_init(size_t capacity); + * arena_t arena_init(size_t capacity); * * DESCRIPTION * The function creates and returns a new memory arena instance with the @@ -50,7 +50,7 @@ typedef struct * capacity - Size of the memory arena to allocate in bytes * * RETURN VALUE - * Returns a valid memory::arena_t object on success. arena_t->data will be null on failure. + * Returns a valid arena_t object on success. arena_t->data will be null on failure. */ memory::arena_t arena_init(size_t capacity); /* @@ -58,7 +58,7 @@ memory::arena_t arena_init(size_t capacity); * arena_allocate - Allocate memory from a pre-initialized arena. * * SYNOPSIS - * const void* memory::arena_allocate(memory::arena_t* arena, std::size_t size); + * const void* arena_allocate(arena_t* arena, std::size_t size); * * DESCRIPTION * The function allocates size bytes from the specified arena. It assumes @@ -79,7 +79,7 @@ const void* arena_allocate(arena_t* arena, const std::size_t size); * arena_reset - Reset a memory arena's allocation size to zero. * * SYNOPSIS - * void memory::arena_reset(memory::arena_t* arena); + * void arena_reset(arena_t* arena); * * DESCRIPTION * The function resets the allocation size of a pre-initialized arena_t to zero. @@ -99,7 +99,7 @@ void arena_reset(arena_t* arena); * arena_free - Free the memory allocated by an arena * * SYNOPSIS - * void memory::arena_free(memory::arena_t* arena); + * void arena_free(arena_t* arena); * * DESCRIPTION * The function releases the memory buffer associated with a arena_t and @@ -108,5 +108,5 @@ void arena_reset(arena_t* arena); */ void arena_free(memory::arena_t* arena); -} // namespace pound::memory +} // namespace pound::host::memory #endif //POUND_ARENA_H diff --git a/core/host/memory/arena_allocator.h b/core/host/memory/arena_allocator.h new file mode 100644 index 0000000..4bd9c74 --- /dev/null +++ b/core/host/memory/arena_allocator.h @@ -0,0 +1,75 @@ +#ifndef POUND_ARENA_ALLOCATOR_H +#define POUND_ARENA_ALLOCATOR_H + +#include +#include +#include "arena.h" + +namespace pound::host::memory +{ +/** + @brief An STL-compatible allocator that uses a custom arena for memory management. + + This allocator allows STL containers (such as std::vector) to allocate memory from a user-provided arena, + enabling efficient bulk allocation and deallocation patterns. The arena must provide an `arena_allocate` function. + + @tparam T Type of object to allocate. + + @code + arena_t my_arena = memory::arena_init(4096); + arena_allocator alloc(&my_arena); + + std::vector> vec(alloc); + vec.push_back(42); + // ... + arena_reset(&my_arena); // Frees all allocations in the arena + @endcode + + @note The deallocate function is a no-op; memory is managed by the arena. + + @see arena_t + @see arena_allocate + */ +template +struct arena_allocator +{ + using value_type = T; + + arena_t* arena; + + arena_allocator(arena_t* a) noexcept : arena(a) {} + + template + arena_allocator(const arena_allocator& other) noexcept : arena(other.arena) + { + } + + T* allocate(std::size_t n) { return static_cast(const_cast(arena_allocate(arena, n * sizeof(T)))); } + + void deallocate(T*, std::size_t) noexcept + { + // noop since memory should be freed by arena + } + + template + struct rebind + { + using other = arena_allocator; + }; +}; + +template +inline bool operator==(const arena_allocator& a, const arena_allocator& b) +{ + return a.arena == b.arena; +} + +template +inline bool operator!=(const arena_allocator& a, const arena_allocator& b) +{ + return a.arena != b.arena; +} + +} // namespace pound::host::memory + +#endif // POUND_ARENA_ALLOCATOR_H diff --git a/core/main.cpp b/core/main.cpp index f4fb66d..6408e26 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -4,15 +4,15 @@ #include #include -#include "Base/Config.h" -#include "Base/Logging/Log.h" -#include "Base/Logging/Backend.h" -#include "gui/gui.h" -#include "memory/arena.h" +#include "common/Config.h" +#include "common/Logging/Backend.h" +#include "common/Logging/Log.h" +#include "frontend/gui.h" +#include "host/memory/arena.h" #include -#include "gui/color.h" -#include "gui/panels.h" +#include "frontend/color.h" +#include "frontend/panels.h" #include "imgui_impl_opengl3.h" #include "imgui_impl_sdl3.h" diff --git a/core/memory/arena_allocator.h b/core/memory/arena_allocator.h deleted file mode 100644 index 164ebef..0000000 --- a/core/memory/arena_allocator.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef POUND_ARENA_ALLOCATOR_H -#define POUND_ARENA_ALLOCATOR_H - -#include -#include -#include "arena.h" - -namespace pound::memory -{ - /** - @brief An STL-compatible allocator that uses a custom arena for memory management. - - This allocator allows STL containers (such as std::vector) to allocate memory from a user-provided arena, - enabling efficient bulk allocation and deallocation patterns. The arena must provide an `arena_allocate` function. - - @tparam T Type of object to allocate. - - @code - memory::arena_t my_arena = memory::arena_init(4096); - memory::arena_allocator alloc(&my_arena); - - std::vector> vec(alloc); - vec.push_back(42); - // ... - memory::arena_reset(&my_arena); // Frees all allocations in the arena - @endcode - - @note The deallocate function is a no-op; memory is managed by the arena. - - @see arena_t - @see arena_allocate - */ - template - struct arena_allocator { - using value_type = T; - - arena_t* arena; - - arena_allocator(arena_t* a) noexcept : arena(a) {} - - template - arena_allocator(const arena_allocator& other) noexcept : arena(other.arena) {} - - T* allocate(std::size_t n) { - return static_cast(const_cast(arena_allocate(arena, n * sizeof(T)))); - } - - void deallocate(T*, std::size_t) noexcept { - // noop since memory should be freed by arena - } - - template - struct rebind { using other = arena_allocator; }; - }; - - template - inline bool operator==(const arena_allocator& a, const arena_allocator& b) { - return a.arena == b.arena; - } - - template - inline bool operator!=(const arena_allocator& a, const arena_allocator& b) { - return a.arena != b.arena; - } - -} - -#endif // POUND_ARENA_ALLOCATOR_H diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt deleted file mode 100644 index c5b3a61..0000000 --- a/gui/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2025 Pound Emulator Project. All rights reserved. - -# GUI sources -set(GUI_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/gui.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/color.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/panels.cpp -) - -# Add all GUI sources to the main target -target_sources(Pound PRIVATE - ${GUI_SOURCES} -) - -# Include directories for GUI -target_include_directories(Pound PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/.. -) \ No newline at end of file