Major project restructuring

Remove unecessary files and made the tree much more cleaner.

Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
Ronald Caesar 2025-08-23 17:23:33 -04:00
parent 13b2e741b9
commit 05c4f7025f
62 changed files with 2698 additions and 2453 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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 <typename T, size_t Capacity = detail::DefaultCapacity>
class SPSCQueue {
static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two.");
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
return Emplace<PushMode::Try>(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
Emplace<PushMode::Wait>(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
return Pop<PopMode::Try>(t);
}
bool PopWait(T& t) {
return Pop<PopMode::Wait>(t);
}
bool PopWait(T& t, std::stop_token stopToken) {
return Pop<PopMode::WaitWithStopToken>(t, stopToken);
}
T PopWait() {
T t;
Pop<PopMode::Wait>(t);
return t;
}
T PopWait(std::stop_token stopToken) {
T t;
Pop<PopMode::WaitWithStopToken>(t, stopToken);
return t;
}
private:
enum class PushMode {
Try,
Wait,
Count
};
enum class PopMode {
Try,
Wait,
WaitWithStopToken,
Count
};
template <PushMode Mode, typename... Args>
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>(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 <PopMode Mode>
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<T, Capacity> 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 <typename T, size_t Capacity = detail::DefaultCapacity>
class MPSCQueue {
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
std::scoped_lock lock{writeMutex};
return spscQueue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
std::scoped_lock lock{writeMutex};
spscQueue.EmplaceWait(std::forward<Args>(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<T, Capacity> spscQueue;
std::mutex writeMutex;
};
template <typename T, size_t Capacity = detail::DefaultCapacity>
class MPMCQueue {
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
std::scoped_lock lock{ writeMutex };
return spscQueue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
std::scoped_lock lock{ writeMutex };
spscQueue.EmplaceWait(std::forward<Args>(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<T, Capacity> spscQueue;
std::mutex writeMutex;
std::mutex readMutex;
};
} // namespace Base

View file

@ -1,89 +0,0 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "Config.h"
#include <fmt/core.h>
#include <toml.hpp>
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<int>(general, "Window Width", 640);
heightWindow = toml::find_or<int>(general, "Window Height", 480);
logAdvanced = toml::find_or<bool>(general, "Advanced Log", false);
typeLog = toml::find_or<std::string>(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

View file

@ -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<type>; \
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(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<type>; \
return static_cast<type>(~static_cast<T>(key)); \
} \
[[nodiscard]] constexpr bool True(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) != 0; \
} \
[[nodiscard]] constexpr bool False(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) == 0; \
}
namespace Base {
template <typename T>
class Flags {
public:
using IntType = std::underlying_type_t<T>;
Flags() {}
Flags(IntType t) : m_bits(t) {}
template <typename... Tx>
Flags(T f, Tx... fx) {
set(f, fx...);
}
template <typename... Tx>
void set(Tx... fx) {
m_bits |= bits(fx...);
}
void set(Flags flags) {
m_bits |= flags.m_bits;
}
template <typename... Tx>
void clr(Tx... fx) {
m_bits &= ~bits(fx...);
}
void clr(Flags flags) {
m_bits &= ~flags.m_bits;
}
template <typename... Tx>
bool any(Tx... fx) const {
return (m_bits & bits(fx...)) != 0;
}
template <typename... Tx>
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<IntType>(f);
}
template <typename... Tx>
static IntType bits(T f, Tx... fx) {
return bit(f) | bits(fx...);
}
static IntType bits() {
return 0;
}
};
} // namespace Base

View file

@ -1,428 +0,0 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include <vector>
#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 <io.h>
#include <share.h>
#include <Windows.h>
#else
#include <unistd.h>
#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<HANDLE>(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<HANDLE>(::_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<uptr>(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<char> string_buffer(length);
const u64 charsRead = ReadSpan<char>(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<s64>(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<u64>(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<u32>(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

View file

@ -1,222 +0,0 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <filesystem>
#include <span>
#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 <typename T>
size_t Read(T& data) const {
if constexpr (std::contiguous_iterator<typename T::iterator>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return ReadSpan<ContiguousType>(data);
} else {
return ReadObject(data) ? 1 : 0;
}
}
template <typename T>
size_t Write(const T& data) const {
if constexpr (std::contiguous_iterator<typename T::iterator>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return WriteSpan<ContiguousType>(data);
} else {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
return WriteObject(data) ? 1 : 0;
}
}
template <typename T>
size_t ReadSpan(std::span<T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return ReadRaw<T>(data.data(), data.size());
}
template <typename T>
size_t ReadRaw(void* data, size_t size) const {
return std::fread(data, sizeof(T), size, file);
}
template <typename T>
size_t WriteSpan(std::span<const T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return std::fwrite(data.data(), sizeof(T), data.size(), file);
}
template <typename T>
bool ReadObject(T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen()) {
return false;
}
return std::fread(&object, sizeof(T), 1, file) == 1;
}
template <typename T>
size_t WriteRaw(const void* data, size_t size) const {
return std::fwrite(data, sizeof(T), size, file);
}
template <typename T>
bool WriteObject(const T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "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<const char> 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

View file

@ -1,117 +0,0 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "PathUtil.h"
#include "Base/Logging/Log.h"
#include <fmt/format.h>
#include <unordered_map>
#include <fstream>
#ifdef _WIN32
#include <Windows.h>
#endif // _WIN32
#ifdef __APPLE__
#include <unistd.h>
#include <libproc.h>
#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<PathType, fs::path> 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<FileInfo> ListFilesFromPath(const fs::path &path) {
std::vector<FileInfo> 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

View file

@ -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 <mutex>
#include <chrono>
#include <condition_variable>
#include <stop_token>
#include <thread>
#include <utility>
namespace Base {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
cv.wait(lk, token, std::forward<Pred>(pred));
}
template <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& 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 <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <type_traits>
#include <utility>
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<void()> 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<void()> 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<stop_state_callback, function<void()>> 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 Callback>
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 <typename Callback>
friend class stop_callback;
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
class stop_source {
public:
stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {}
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<polyfill::stop_state> stop_state)
: m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
template <typename Callback>
class stop_callback {
static_assert(is_nothrow_destructible_v<Callback>);
static_assert(is_invocable_v<Callback>);
public:
using callback_type = Callback;
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(const stop_token& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state) {
if (m_stop_state) {
m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(stop_token&& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: 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<polyfill::stop_state> m_stop_state;
polyfill::stop_state_callback m_callback;
};
template <typename Callback>
stop_callback(stop_token, Callback) -> stop_callback<Callback>;
class jthread {
public:
using id = thread::id;
using native_handle_type = thread::native_handle_type;
jthread() noexcept = default;
template <typename F, typename... Args,
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()),
m_thread(make_thread(std::forward<F>(f), std::forward<Args>(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 <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args) {
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
} else {
return thread(std::forward<F>(f), std::forward<Args>(args)...);
}
}
shared_ptr<polyfill::stop_state> m_stop_state;
thread m_thread;
};
} // namespace std
namespace Base {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<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 <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& 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

View file

@ -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<s32>(input.size()), nullptr, 0);
if (size == 0) {
return {};
}
std::wstring output(size, L'\0');
if (size != ::MultiByteToWideChar(code_page, 0, input.data(), static_cast<s32>(input.size()),
&output[0], static_cast<s32>(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<s32>(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<s32>(input.size()),
&output[0], static_cast<s32>(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

View file

@ -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 <mach/mach.h>
#include <mach/mach_time.h>
#include <pthread.h>
#elif defined(_WIN32)
#include <Windows.h>
#include "StringUtil.h"
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <pthread_np.h>
#else
#include <pthread.h>
#endif
#include <sched.h>
#endif
#ifndef _WIN32
#include <unistd.h>
#endif
#include <thread>
#include <algorithm>
#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<f64>(timebase.denom) / static_cast<f64>(timebase.numer);
const auto period_ticks =
static_cast<u32>(static_cast<f64>(period_ns.count()) * ticks_per_ns);
const auto computation_ticks =
static_cast<u32>(static_cast<f64>(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<thread_policy_t>(&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<u32>(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, &params);
}
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<size_t>(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<size_t>(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<std::chrono::nanoseconds>(start_time - begin_sleep);
}
void AccurateTimer::End() {
const auto now = std::chrono::high_resolution_clock::now();
total_wait +=
target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
}
} // namespace Base

View file

@ -1,43 +0,0 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <chrono>
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

View file

@ -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} /..)

View file

@ -1,5 +1,4 @@
#include "memory.h"
#include "Base/Assert.h"
namespace pound::aarch64::memory
{

View file

@ -1,6 +1,7 @@
#pragma once
#include <cassert>
#include <cstdint>
namespace pound::arm64::memory
{

View file

@ -1,7 +1,8 @@
#include "isa.h"
#include "guest.h"
#include "memory/arena.h"
#include <cassert>
#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

View file

@ -2,11 +2,10 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#include <cstring>
#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 V0V31.
@ -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

View file

@ -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;
}

View file

@ -1,7 +1,7 @@
#pragma once
#include "isa.h"
#include "guest.h"
#include "isa.h"
namespace pound::arm64::memory
{

View file

@ -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

View file

@ -2,11 +2,11 @@
#include <stdio.h>
#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();
}

121
core/common/Assert.h Normal file
View file

@ -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)

260
core/common/BoundedQueue.h Normal file
View file

@ -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 <typename T, size_t Capacity = detail::DefaultCapacity>
class SPSCQueue
{
static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two.");
public:
template <typename... Args>
bool TryEmplace(Args&&... args)
{
return Emplace<PushMode::Try>(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args)
{
Emplace<PushMode::Wait>(std::forward<Args>(args)...);
}
bool TryPop(T& t) { return Pop<PopMode::Try>(t); }
bool PopWait(T& t) { return Pop<PopMode::Wait>(t); }
bool PopWait(T& t, std::stop_token stopToken) { return Pop<PopMode::WaitWithStopToken>(t, stopToken); }
T PopWait()
{
T t;
Pop<PopMode::Wait>(t);
return t;
}
T PopWait(std::stop_token stopToken)
{
T t;
Pop<PopMode::WaitWithStopToken>(t, stopToken);
return t;
}
private:
enum class PushMode
{
Try,
Wait,
Count
};
enum class PopMode
{
Try,
Wait,
WaitWithStopToken,
Count
};
template <PushMode Mode, typename... Args>
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>(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 <PopMode Mode>
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<T, Capacity> 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 <typename T, size_t Capacity = detail::DefaultCapacity>
class MPSCQueue
{
public:
template <typename... Args>
bool TryEmplace(Args&&... args)
{
std::scoped_lock lock{writeMutex};
return spscQueue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args)
{
std::scoped_lock lock{writeMutex};
spscQueue.EmplaceWait(std::forward<Args>(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<T, Capacity> spscQueue;
std::mutex writeMutex;
};
template <typename T, size_t Capacity = detail::DefaultCapacity>
class MPMCQueue
{
public:
template <typename... Args>
bool TryEmplace(Args&&... args)
{
std::scoped_lock lock{writeMutex};
return spscQueue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args)
{
std::scoped_lock lock{writeMutex};
spscQueue.EmplaceWait(std::forward<Args>(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<T, Capacity> spscQueue;
std::mutex writeMutex;
std::mutex readMutex;
};
} // namespace Base

View file

@ -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}/..)

108
core/common/Config.cpp Normal file
View file

@ -0,0 +1,108 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "Config.h"
#include <fmt/core.h>
#include <toml.hpp>
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<int>(general, "Window Width", 640);
heightWindow = toml::find_or<int>(general, "Window Height", 480);
logAdvanced = toml::find_or<bool>(general, "Advanced Log", false);
typeLog = toml::find_or<std::string>(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

View file

@ -4,7 +4,8 @@
#include <filesystem>
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

153
core/common/Enum.h Normal file
View file

@ -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<type>; \
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator&(type a, type b) noexcept \
{ \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator^(type a, type b) noexcept \
{ \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept \
{ \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept \
{ \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(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<type>; \
return static_cast<type>(~static_cast<T>(key)); \
} \
[[nodiscard]] constexpr bool True(type key) noexcept \
{ \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) != 0; \
} \
[[nodiscard]] constexpr bool False(type key) noexcept \
{ \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) == 0; \
}
namespace Base
{
template <typename T>
class Flags
{
public:
using IntType = std::underlying_type_t<T>;
Flags() {}
Flags(IntType t) : m_bits(t) {}
template <typename... Tx>
Flags(T f, Tx... fx)
{
set(f, fx...);
}
template <typename... Tx>
void set(Tx... fx)
{
m_bits |= bits(fx...);
}
void set(Flags flags) { m_bits |= flags.m_bits; }
template <typename... Tx>
void clr(Tx... fx)
{
m_bits &= ~bits(fx...);
}
void clr(Flags flags) { m_bits &= ~flags.m_bits; }
template <typename... Tx>
bool any(Tx... fx) const
{
return (m_bits & bits(fx...)) != 0;
}
template <typename... Tx>
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<IntType>(f); }
template <typename... Tx>
static IntType bits(T f, Tx... fx)
{
return bit(f) | bits(fx...);
}
static IntType bits() { return 0; }
};
} // namespace Base

View file

@ -4,7 +4,8 @@
#include <string>
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

492
core/common/IoFile.cpp Normal file
View file

@ -0,0 +1,492 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include <vector>
#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 <Windows.h>
#include <io.h>
#include <share.h>
#else
#include <unistd.h>
#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<HANDLE>(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<HANDLE>(::_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<uptr>(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<char> string_buffer(length);
const u64 charsRead = ReadSpan<char>(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<s64>(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<u64>(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<u32>(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

231
core/common/IoFile.h Normal file
View file

@ -0,0 +1,231 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <filesystem>
#include <span>
#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 <typename T>
size_t Read(T& data) const
{
if constexpr (std::contiguous_iterator<typename T::iterator>)
{
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>, "Data type must be trivially copyable.");
return ReadSpan<ContiguousType>(data);
}
else
{
return ReadObject(data) ? 1 : 0;
}
}
template <typename T>
size_t Write(const T& data) const
{
if constexpr (std::contiguous_iterator<typename T::iterator>)
{
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>, "Data type must be trivially copyable.");
return WriteSpan<ContiguousType>(data);
}
else
{
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
return WriteObject(data) ? 1 : 0;
}
}
template <typename T>
size_t ReadSpan(std::span<T> data) const
{
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen())
{
return 0;
}
return ReadRaw<T>(data.data(), data.size());
}
template <typename T>
size_t ReadRaw(void* data, size_t size) const
{
return std::fread(data, sizeof(T), size, file);
}
template <typename T>
size_t WriteSpan(std::span<const T> data) const
{
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen())
{
return 0;
}
return std::fwrite(data.data(), sizeof(T), data.size(), file);
}
template <typename T>
bool ReadObject(T& object) const
{
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen())
{
return false;
}
return std::fread(&object, sizeof(T), 1, file) == 1;
}
template <typename T>
size_t WriteRaw(const void* data, size_t size) const
{
return std::fwrite(data, sizeof(T), size, file);
}
template <typename T>
bool WriteObject(const T& object) const
{
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "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<const char> 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

View file

@ -4,11 +4,11 @@
#include <functional>
#include <map>
#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"

View file

@ -5,7 +5,7 @@
#include <string_view>
#include <filesystem>
#include "Base/PathUtil.h"
#include "common/PathUtil.h"
#include "Filter.h"

View file

@ -1,6 +1,6 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "Base/Assert.h"
#include "common/Assert.h"
#include "Filter.h"

View file

@ -5,7 +5,7 @@
#include <algorithm>
#include <fmt/format.h>
#include "Base/Config.h"
#include "common/Config.h"
#include "LogTypes.h"

View file

@ -2,7 +2,7 @@
#pragma once
#include "Base/Types.h"
#include "common/Types.h"
namespace Base {
namespace Log {

View file

@ -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"

134
core/common/PathUtil.cpp Normal file
View file

@ -0,0 +1,134 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "PathUtil.h"
#include "common/Logging/Log.h"
#include <fmt/format.h>
#include <fstream>
#include <unordered_map>
#ifdef _WIN32
#include <Windows.h>
#endif // _WIN32
#ifdef __APPLE__
#include <libproc.h>
#include <unistd.h>
#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<PathType, fs::path> 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<FileInfo> ListFilesFromPath(const fs::path& path)
{
std::vector<FileInfo> 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

View file

@ -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<FileInfo> ListFilesFromPath(const fs::path &path);
[[nodiscard]] std::vector<FileInfo> 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

View file

@ -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 <chrono>
#include <condition_variable>
#include <mutex>
#include <stop_token>
#include <thread>
#include <utility>
namespace Base
{
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred)
{
cv.wait(lk, token, std::forward<Pred>(pred));
}
template <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& 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 <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <type_traits>
#include <utility>
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<void()> 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<void()> 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<stop_state_callback, function<void()>> 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 Callback>
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 <typename Callback>
friend class stop_callback;
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
class stop_source
{
public:
stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {}
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<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
template <typename Callback>
class stop_callback
{
static_assert(is_nothrow_destructible_v<Callback>);
static_assert(is_invocable_v<Callback>);
public:
using callback_type = Callback;
template <typename C>
requires constructible_from<Callback, C> explicit stop_callback(const stop_token& st, C&& cb) noexcept(
is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state)
{
if (m_stop_state)
{
m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
template <typename C>
requires constructible_from<Callback, C> explicit stop_callback(stop_token&& st, C&& cb) noexcept(
is_nothrow_constructible_v<Callback, C>)
: 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<polyfill::stop_state> m_stop_state;
polyfill::stop_state_callback m_callback;
};
template <typename Callback>
stop_callback(stop_token, Callback) -> stop_callback<Callback>;
class jthread
{
public:
using id = thread::id;
using native_handle_type = thread::native_handle_type;
jthread() noexcept = default;
template <typename F, typename... Args, typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()),
m_thread(make_thread(std::forward<F>(f), std::forward<Args>(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 <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args)
{
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>)
{
return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
}
else
{
return thread(std::forward<F>(f), std::forward<Args>(args)...);
}
}
shared_ptr<polyfill::stop_state> m_stop_state;
thread m_thread;
};
} // namespace std
namespace Base
{
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<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 <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& 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

View file

@ -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<s32>(input.size()), nullptr, 0);
if (size == 0)
{
return {};
}
std::wstring output(size, L'\0');
if (size != ::MultiByteToWideChar(code_page, 0, input.data(), static_cast<s32>(input.size()), &output[0],
static_cast<s32>(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<s32>(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<s32>(input.size()), &output[0],
static_cast<s32>(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

View file

@ -2,10 +2,10 @@
#pragma once
#include <string>
#include <string_view>
#include <algorithm>
#include <cctype>
#include <string>
#include <string_view>
#ifdef _WIN32
#include <Windows.h>
@ -13,10 +13,11 @@
#include <filesystem>
#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

228
core/common/Thread.cpp Normal file
View file

@ -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 <mach/mach.h>
#include <mach/mach_time.h>
#include <pthread.h>
#elif defined(_WIN32)
#include <Windows.h>
#include "StringUtil.h"
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <pthread_np.h>
#else
#include <pthread.h>
#endif
#include <sched.h>
#endif
#ifndef _WIN32
#include <unistd.h>
#endif
#include <algorithm>
#include <thread>
#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<f64>(timebase.denom) / static_cast<f64>(timebase.numer);
const auto period_ticks = static_cast<u32>(static_cast<f64>(period_ns.count()) * ticks_per_ns);
const auto computation_ticks = static_cast<u32>(static_cast<f64>(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<thread_policy_t>(&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<u32>(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, &params);
}
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<size_t>(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<size_t>(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<std::chrono::nanoseconds>(start_time - begin_sleep);
}
void AccurateTimer::End()
{
const auto now = std::chrono::high_resolution_clock::now();
total_wait += target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
}
} // namespace Base

45
core/common/Thread.h Normal file
View file

@ -0,0 +1,45 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <chrono>
#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

View file

@ -28,25 +28,30 @@ using f64 = double;
// Function pointer
template <typename T>
requires std::is_function_v<T>
using fptr = std::add_pointer_t<T>;
requires std::is_function_v<T> using fptr = std::add_pointer_t<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;
}

View file

@ -15,16 +15,20 @@
#include <type_traits>
#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 <typename T, typename F>
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 <typename S>
swapped_t& operator=(const S& source) {
swapped_t& operator=(const S& source)
{
value = swap(static_cast<T>(source));
return *this;
}
operator s8() const {
return static_cast<s8>(swap());
}
operator u8() const {
return static_cast<u8>(swap());
}
operator s16() const {
return static_cast<s16>(swap());
}
operator u16() const {
return static_cast<u16>(swap());
}
operator s32() const {
return static_cast<s32>(swap());
}
operator u32() const {
return static_cast<u32>(swap());
}
operator s64() const {
return static_cast<s64>(swap());
}
operator u64() const {
return static_cast<u64>(swap());
}
operator float() const {
return static_cast<float>(swap());
}
operator double() const {
return static_cast<double>(swap());
}
operator s8() const { return static_cast<s8>(swap()); }
operator u8() const { return static_cast<u8>(swap()); }
operator s16() const { return static_cast<s16>(swap()); }
operator u16() const { return static_cast<u16>(swap()); }
operator s32() const { return static_cast<s32>(swap()); }
operator u32() const { return static_cast<u32>(swap()); }
operator s64() const { return static_cast<s64>(swap()); }
operator u64() const { return static_cast<u64>(swap()); }
operator float() const { return static_cast<float>(swap()); }
operator double() const { return static_cast<double>(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 <typename S>
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 <typename S>
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 <typename S>
swapped_t operator+(const S& i) const {
swapped_t operator+(const S& i) const
{
return swap() + static_cast<T>(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 <typename S>
swapped_t operator-(const S& i) const {
swapped_t operator-(const S& i) const
{
return swap() - static_cast<T>(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 <typename S>
swapped_t& operator+=(const S& i) {
swapped_t& operator+=(const S& i)
{
value = swap(swap() + static_cast<T>(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 <typename S>
swapped_t& operator-=(const S& i) {
swapped_t& operator-=(const S& i)
{
value = swap(swap() - static_cast<T>(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 <typename S>
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 <typename S>
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 <typename S>
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 <typename S>
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 <typename S>
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 <typename S>
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 <typename S>
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 <typename S>
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 <typename S>
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 <typename S>
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 <typename S>
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 <typename S>
swapped_t& operator^=(const S& b) {
swapped_t& operator^=(const S& b)
{
value = swap(swap() ^ b);
return *this;
}
template <typename S>
swapped_t operator<<(const S& b) const {
swapped_t operator<<(const S& b) const
{
return swap() << b;
}
template <typename S>
swapped_t& operator<<=(const S& b) const {
swapped_t& operator<<=(const S& b) const
{
value = swap(swap() << b);
return *this;
}
template <typename S>
swapped_t operator>>(const S& b) const {
swapped_t operator>>(const S& b) const
{
return swap() >> b;
}
template <typename S>
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 <typename S, typename T, typename F>
S operator+(const S& i, const swap_struct_t<T, F> v) {
S operator+(const S& i, const swap_struct_t<T, F> v)
{
return i + v.swap();
}
template <typename S, typename T, typename F>
S operator-(const S& i, const swap_struct_t<T, F> v) {
S operator-(const S& i, const swap_struct_t<T, F> v)
{
return i - v.swap();
}
template <typename S, typename T, typename F>
S operator/(const S& i, const swap_struct_t<T, F> v) {
S operator/(const S& i, const swap_struct_t<T, F> v)
{
return i / v.swap();
}
template <typename S, typename T, typename F>
S operator*(const S& i, const swap_struct_t<T, F> v) {
S operator*(const S& i, const swap_struct_t<T, F> v)
{
return i * v.swap();
}
template <typename S, typename T, typename F>
S operator%(const S& i, const swap_struct_t<T, F> v) {
S operator%(const S& i, const swap_struct_t<T, F> v)
{
return i % v.swap();
}
// Arithmetic + assignments
template <typename S, typename T, typename F>
S& operator+=(S& i, const swap_struct_t<T, F> v) {
S& operator+=(S& i, const swap_struct_t<T, F> v)
{
i += v.swap();
return i;
}
template <typename S, typename T, typename F>
S& operator-=(S& i, const swap_struct_t<T, F> v) {
S& operator-=(S& i, const swap_struct_t<T, F> v)
{
i -= v.swap();
return i;
}
// Logical
template <typename S, typename T, typename F>
S operator&(const S& i, const swap_struct_t<T, F> v) {
S operator&(const S& i, const swap_struct_t<T, F> v)
{
return i & v.swap();
}
// Comparison
template <typename S, typename T, typename F>
bool operator<(const S& p, const swap_struct_t<T, F> v) {
bool operator<(const S& p, const swap_struct_t<T, F> v)
{
return p < v.swap();
}
template <typename S, typename T, typename F>
bool operator>(const S& p, const swap_struct_t<T, F> v) {
bool operator>(const S& p, const swap_struct_t<T, F> v)
{
return p > v.swap();
}
template <typename S, typename T, typename F>
bool operator<=(const S& p, const swap_struct_t<T, F> v) {
bool operator<=(const S& p, const swap_struct_t<T, F> v)
{
return p <= v.swap();
}
template <typename S, typename T, typename F>
bool operator>=(const S& p, const swap_struct_t<T, F> v) {
bool operator>=(const S& p, const swap_struct_t<T, F> v)
{
return p >= v.swap();
}
template <typename S, typename T, typename F>
bool operator!=(const S& p, const swap_struct_t<T, F> v) {
bool operator!=(const S& p, const swap_struct_t<T, F> v)
{
return p != v.swap();
}
template <typename S, typename T, typename F>
bool operator==(const S& p, const swap_struct_t<T, F> v) {
bool operator==(const S& p, const swap_struct_t<T, F> v)
{
return p == v.swap();
}
template <typename T>
struct swap_64_t {
static T swap(T x) {
return static_cast<T>(Common::swap64(x));
}
struct swap_64_t
{
static T swap(T x) { return static_cast<T>(Common::swap64(x)); }
};
template <typename T>
struct swap_32_t {
static T swap(T x) {
return static_cast<T>(Common::swap32(x));
}
struct swap_32_t
{
static T swap(T x) { return static_cast<T>(Common::swap32(x)); }
};
template <typename T>
struct swap_16_t {
static T swap(T x) {
return static_cast<T>(Common::swap16(x));
}
struct swap_16_t
{
static T swap(T x) { return static_cast<T>(Common::swap16(x)); }
};
template <typename T>
struct swap_float_t {
static T swap(T x) {
return static_cast<T>(Common::swapf(x));
}
struct swap_float_t
{
static T swap(T x) { return static_cast<T>(Common::swapf(x)); }
};
template <typename T>
struct swap_double_t {
static T swap(T x) {
return static_cast<T>(Common::swapd(x));
}
struct swap_double_t
{
static T swap(T x) { return static_cast<T>(Common::swapd(x)); }
};
template <typename T>
struct swap_enum_t {
struct swap_enum_t
{
static_assert(std::is_enum_v<T>);
using base = std::underlying_type_t<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<base>(swap(value));
}
explicit operator base() const { return static_cast<base>(swap(value)); }
protected:
protected:
T value{};
// clang-format off
using swap_t = std::conditional_t<
@ -554,13 +548,15 @@ protected:
std::is_same_v<base, u64>, swap_64_t<u64>, std::conditional_t<
std::is_same_v<base, s64>, swap_64_t<s64>, void>>>>>>;
// clang-format on
static T swap(T x) {
return static_cast<T>(swap_t::swap(static_cast<base>(x)));
}
static T swap(T x) { return static_cast<T>(swap_t::swap(static_cast<base>(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 <typename T, typename Tag>
struct AddEndian;
@ -568,64 +564,76 @@ struct AddEndian;
// KeepTag specializations
template <typename T>
struct AddEndian<T, KeepTag> {
struct AddEndian<T, KeepTag>
{
using type = T;
};
// SwapTag specializations
template <>
struct AddEndian<u8, SwapTag> {
struct AddEndian<u8, SwapTag>
{
using type = u8;
};
template <>
struct AddEndian<u16, SwapTag> {
struct AddEndian<u16, SwapTag>
{
using type = swap_struct_t<u16, swap_16_t<u16>>;
};
template <>
struct AddEndian<u32, SwapTag> {
struct AddEndian<u32, SwapTag>
{
using type = swap_struct_t<u32, swap_32_t<u32>>;
};
template <>
struct AddEndian<u64, SwapTag> {
struct AddEndian<u64, SwapTag>
{
using type = swap_struct_t<u64, swap_64_t<u64>>;
};
template <>
struct AddEndian<s8, SwapTag> {
struct AddEndian<s8, SwapTag>
{
using type = s8;
};
template <>
struct AddEndian<s16, SwapTag> {
struct AddEndian<s16, SwapTag>
{
using type = swap_struct_t<s16, swap_16_t<s16>>;
};
template <>
struct AddEndian<s32, SwapTag> {
struct AddEndian<s32, SwapTag>
{
using type = swap_struct_t<s32, swap_32_t<s32>>;
};
template <>
struct AddEndian<s64, SwapTag> {
struct AddEndian<s64, SwapTag>
{
using type = swap_struct_t<s64, swap_64_t<s64>>;
};
template <>
struct AddEndian<float, SwapTag> {
struct AddEndian<float, SwapTag>
{
using type = swap_struct_t<float, swap_float_t<float>>;
};
template <>
struct AddEndian<double, SwapTag> {
struct AddEndian<double, SwapTag>
{
using type = swap_struct_t<double, swap_double_t<double>>;
};
template <typename T>
struct AddEndian<T, SwapTag> {
struct AddEndian<T, SwapTag>
{
static_assert(std::is_enum_v<T>);
using type = swap_enum_t<T>;
};

View file

@ -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} /..)

View file

@ -2,6 +2,8 @@
#define POUND_COLORS_H
#include <imgui.h>
#include <algorithm>
#include <cstdint>
namespace gui::color
{

4
gui/gui.cpp → core/frontend/gui.cpp Executable file → Normal file
View file

@ -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 <imgui.h>

View file

@ -3,7 +3,7 @@
#include <SDL3/SDL.h>
#include <vector>
#include "memory/arena.h"
#include "host/memory/arena.h"
namespace gui
{

4
gui/panels.cpp → core/frontend/panels.cpp Executable file → Normal file
View file

@ -1,8 +1,8 @@
#include "panels.h"
#include <imgui.h>
#include <math.h>
#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)

2
gui/panels.h → core/frontend/panels.h Executable file → Normal file
View file

@ -2,6 +2,8 @@
#define POUND_PANELS_H
#include <chrono>
#include <cmath>
#include <cstdint>
#include <deque>
namespace gui::panel

13
core/host/CMakeLists.txt Normal file
View file

@ -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}/..
)

View file

@ -1,10 +1,12 @@
#include "arena.h"
#include <cassert>
#include <cstring>
#ifndef WIN32
#include <sys/mman.h>
#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

View file

@ -5,7 +5,7 @@
#include <cstdint>
#include <cstdlib>
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

View file

@ -0,0 +1,75 @@
#ifndef POUND_ARENA_ALLOCATOR_H
#define POUND_ARENA_ALLOCATOR_H
#include <cstddef>
#include <memory>
#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<int> alloc(&my_arena);
std::vector<int, memory::arena_allocator<int>> 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 <typename T>
struct arena_allocator
{
using value_type = T;
arena_t* arena;
arena_allocator(arena_t* a) noexcept : arena(a) {}
template <typename U>
arena_allocator(const arena_allocator<U>& other) noexcept : arena(other.arena)
{
}
T* allocate(std::size_t n) { return static_cast<T*>(const_cast<void*>(arena_allocate(arena, n * sizeof(T)))); }
void deallocate(T*, std::size_t) noexcept
{
// noop since memory should be freed by arena
}
template <typename U>
struct rebind
{
using other = arena_allocator<U>;
};
};
template <typename T, typename U>
inline bool operator==(const arena_allocator<T>& a, const arena_allocator<U>& b)
{
return a.arena == b.arena;
}
template <typename T, typename U>
inline bool operator!=(const arena_allocator<T>& a, const arena_allocator<U>& b)
{
return a.arena != b.arena;
}
} // namespace pound::host::memory
#endif // POUND_ARENA_ALLOCATOR_H

View file

@ -4,15 +4,15 @@
#include <memory>
#include <thread>
#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 <SDL3/SDL_opengl.h>
#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"

View file

@ -1,68 +0,0 @@
#ifndef POUND_ARENA_ALLOCATOR_H
#define POUND_ARENA_ALLOCATOR_H
#include <cstddef>
#include <memory>
#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<int> alloc(&my_arena);
std::vector<int, memory::arena_allocator<int>> 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 <typename T>
struct arena_allocator {
using value_type = T;
arena_t* arena;
arena_allocator(arena_t* a) noexcept : arena(a) {}
template <typename U>
arena_allocator(const arena_allocator<U>& other) noexcept : arena(other.arena) {}
T* allocate(std::size_t n) {
return static_cast<T*>(const_cast<void*>(arena_allocate(arena, n * sizeof(T))));
}
void deallocate(T*, std::size_t) noexcept {
// noop since memory should be freed by arena
}
template <typename U>
struct rebind { using other = arena_allocator<U>; };
};
template <typename T, typename U>
inline bool operator==(const arena_allocator<T>& a, const arena_allocator<U>& b) {
return a.arena == b.arena;
}
template <typename T, typename U>
inline bool operator!=(const arena_allocator<T>& a, const arena_allocator<U>& b) {
return a.arena != b.arena;
}
}
#endif // POUND_ARENA_ALLOCATOR_H

View file

@ -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}/..
)