mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-11 07:36:57 +00:00
Merge branch 'arm64'
This is a major architectural overhaul of the KVM core.
The monolithic core directory has been restructured into a more logical
component based structure under src/:
* src/common: Truly genercic platform agnostic utilities.
* src/host: The host abstraction layer for the OS soecific code.
* src/frontend: User-interface and session management.
* src/kvm: The core CPU and virtual machine emulation logic.
* src/targets: Machine specific hardware definitions.
The core of the logical changes is a new framework for initializing and
running virtual machines.
* Machine Probing: A new machine factory (kvm_probe) and operations
table (kvm_ops_t) has been introduced. The core now interacts with
the emulated machine through this abstraction interface.
* Data Oriented MMIO disaptcher: This uses a data oriented structure
of arrays design and a binary search lookup to provide efficient
(O(log N)) dispatch for guest physical addresses.
Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
commit
768355712d
69 changed files with 3214 additions and 2505 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,9 +1,6 @@
|
|||
[submodule "3rd_Party/fmt"]
|
||||
path = 3rd_Party/fmt
|
||||
url = https://github.com/fmtlib/fmt.git
|
||||
[submodule "3rd_Party/toml11"]
|
||||
path = 3rd_Party/toml11
|
||||
url = https://github.com/ToruNiina/toml11.git
|
||||
[submodule "3rd_Party/SDL3"]
|
||||
path = 3rd_Party/SDL3
|
||||
url = https://github.com/libsdl-org/SDL.git
|
||||
|
|
|
|||
13
3rd_Party/CMakeLists.txt
vendored
13
3rd_Party/CMakeLists.txt
vendored
|
|
@ -19,16 +19,3 @@ if (NOT TARGET SDL3::SDL3)
|
|||
set(SDL_PIPEWIRE OFF)
|
||||
add_subdirectory(SDL3)
|
||||
endif()
|
||||
|
||||
# Toml11
|
||||
if (NOT TARGET toml11::toml11)
|
||||
add_subdirectory(toml11)
|
||||
set(TOML11_INSTALL OFF)
|
||||
|
||||
# This removes the warnings generated by toml11
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
get_target_property(_toml11_compile_options toml11 INTERFACE_COMPILE_OPTIONS)
|
||||
list(REMOVE_ITEM _toml11_compile_options "/Zc:preprocessor")
|
||||
set_target_properties(toml11 PROPERTIES INTERFACE_COMPILE_OPTIONS ${_toml11_compile_options})
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
1
3rd_Party/toml11
vendored
1
3rd_Party/toml11
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit be08ba2be2a964edcdb3d3e3ea8d100abc26f286
|
||||
|
|
@ -24,18 +24,20 @@ 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}
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
|
||||
add_subdirectory(src/kvm)
|
||||
add_subdirectory(src/common)
|
||||
add_subdirectory(src/frontend)
|
||||
add_subdirectory(src/host)
|
||||
add_subdirectory(src/targets/switch1/hardware)
|
||||
|
||||
target_compile_options(Pound PRIVATE -Wall -Wpedantic
|
||||
-Wshadow
|
||||
-Wpointer-arith
|
||||
|
|
@ -44,10 +46,8 @@ 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)
|
||||
target_link_libraries(Pound PRIVATE fmt::fmt SDL3::SDL3)
|
||||
|
||||
if (WIN32)
|
||||
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
|
||||
|
|
@ -79,4 +79,3 @@ find_package(OpenGL REQUIRED)
|
|||
target_link_libraries(Pound PRIVATE OpenGL::GL)
|
||||
|
||||
# add ./gui directory
|
||||
add_subdirectory(gui)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
157
core/Base/Enum.h
157
core/Base/Enum.h
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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, ¶ms);
|
||||
}
|
||||
|
||||
static void AccurateSleep(std::chrono::nanoseconds duration) {
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
void SetCurrentThreadName(const std::string_view &name) {
|
||||
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
|
||||
}
|
||||
|
||||
void SetThreadName(void *thread, const std::string_view &name) {
|
||||
const char* nchar = name.data();
|
||||
std::string truncated(nchar, std::min(name.size(), static_cast<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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#include "memory.h"
|
||||
#include "Base/Assert.h"
|
||||
|
||||
namespace pound::aarch64::memory
|
||||
{
|
||||
|
||||
} // namespace pound::aarch64::memory
|
||||
|
|
@ -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
|
||||
|
|
@ -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}/..
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
src/common/Assert.h
Normal file
121
src/common/Assert.h
Normal 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
src/common/BoundedQueue.h
Normal file
260
src/common/BoundedQueue.h
Normal 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
|
||||
16
src/common/CMakeLists.txt
Normal file
16
src/common/CMakeLists.txt
Normal 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}/..)
|
||||
111
src/common/Config.cpp
Normal file
111
src/common/Config.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
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
|
||||
|
|
@ -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
src/common/Enum.h
Normal file
153
src/common/Enum.h
Normal 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
|
||||
|
|
@ -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
src/common/IoFile.cpp
Normal file
492
src/common/IoFile.cpp
Normal 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
src/common/IoFile.h
Normal file
231
src/common/IoFile.h
Normal 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
|
||||
|
|
@ -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"
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
#include <string_view>
|
||||
#include <filesystem>
|
||||
|
||||
#include "Base/PathUtil.h"
|
||||
#include "common/PathUtil.h"
|
||||
|
||||
#include "Filter.h"
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
||||
|
||||
#include "Base/Assert.h"
|
||||
#include "common/Assert.h"
|
||||
|
||||
#include "Filter.h"
|
||||
|
||||
|
|
@ -69,6 +69,8 @@ bool ParseFilterRule(Filter &instance, Iterator begin, Iterator end) {
|
|||
CLS(Render) \
|
||||
CLS(ARM) \
|
||||
CLS(Memory) \
|
||||
CLS(PROBE) \
|
||||
CLS(MMIO_S1)
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
const char* GetLogClassName(Class logClass) {
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Base/Config.h"
|
||||
#include "common/Config.h"
|
||||
|
||||
#include "LogTypes.h"
|
||||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Base/Types.h"
|
||||
#include "common/Types.h"
|
||||
|
||||
namespace Base {
|
||||
namespace Log {
|
||||
|
|
@ -34,6 +34,8 @@ enum class Class : const u8 {
|
|||
System, // Base System messages
|
||||
Render, // OpenGL and Window messages
|
||||
ARM,
|
||||
PROBE,
|
||||
MMIO_S1,
|
||||
Memory,
|
||||
Count // Total number of logging classes
|
||||
};
|
||||
|
|
@ -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
src/common/PathUtil.cpp
Normal file
134
src/common/PathUtil.cpp
Normal 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
|
||||
|
|
@ -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
|
||||
385
src/common/PolyfillThread.h
Normal file
385
src/common/PolyfillThread.h
Normal 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
|
||||
72
src/common/StringUtil.cpp
Normal file
72
src/common/StringUtil.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
||||
|
||||
#include "StringUtil.h"
|
||||
#include "Types.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
|
||||
|
|
@ -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
src/common/Thread.cpp
Normal file
228
src/common/Thread.cpp
Normal 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, ¶ms);
|
||||
}
|
||||
|
||||
static void AccurateSleep(std::chrono::nanoseconds duration)
|
||||
{
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
void SetCurrentThreadName(const std::string_view& name)
|
||||
{
|
||||
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
|
||||
}
|
||||
|
||||
void SetThreadName(void* thread, const std::string_view& name)
|
||||
{
|
||||
const char* nchar = name.data();
|
||||
std::string truncated(nchar, std::min(name.size(), static_cast<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
src/common/Thread.h
Normal file
45
src/common/Thread.h
Normal 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>;
|
||||
};
|
||||
11
src/frontend/CMakeLists.txt
Normal file
11
src/frontend/CMakeLists.txt
Normal 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} /..)
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
#define POUND_COLORS_H
|
||||
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace gui::color
|
||||
{
|
||||
4
gui/gui.cpp → src/frontend/gui.cpp
Executable file → Normal file
4
gui/gui.cpp → src/frontend/gui.cpp
Executable file → Normal 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>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <vector>
|
||||
#include "memory/arena.h"
|
||||
#include "host/memory/arena.h"
|
||||
|
||||
namespace gui
|
||||
{
|
||||
8
gui/panels.cpp → src/frontend/panels.cpp
Executable file → Normal file
8
gui/panels.cpp → src/frontend/panels.cpp
Executable file → Normal 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 "kvm/kvm.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)
|
||||
|
|
@ -94,7 +94,7 @@ int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup)
|
|||
|
||||
if (::ImGui::Button("Run CPU Test", ImVec2(120, 0)))
|
||||
{
|
||||
pound::arm64::cpuTest();
|
||||
pound::kvm::cpuTest();
|
||||
*show_cpu_result_popup = true;
|
||||
}
|
||||
if (true == *show_cpu_result_popup)
|
||||
2
gui/panels.h → src/frontend/panels.h
Executable file → Normal file
2
gui/panels.h → src/frontend/panels.h
Executable file → Normal file
|
|
@ -2,6 +2,8 @@
|
|||
#define POUND_PANELS_H
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
|
||||
namespace gui::panel
|
||||
13
src/host/CMakeLists.txt
Normal file
13
src/host/CMakeLists.txt
Normal 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}/..
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
75
src/host/memory/arena_stl.h
Normal file
75
src/host/memory/arena_stl.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#ifndef POUND_ARENA_ALLOCATOR_H
|
||||
#define POUND_ARENA_ALLOCATOR_H
|
||||
|
||||
#include "arena.h"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
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, 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
|
||||
10
src/kvm/CMakeLists.txt
Normal file
10
src/kvm/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#Copyright 2025 Pound Emulator Project.All rights reserved.
|
||||
|
||||
set(KVM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/mmu.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mmio.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/kvm.cpp
|
||||
)
|
||||
|
||||
target_sources(Pound PRIVATE ${KVM_SOURCES})
|
||||
|
||||
target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /..)
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
namespace pound::arm64::memory
|
||||
namespace pound::kvm::memory
|
||||
{
|
||||
|
||||
/*
|
||||
|
|
@ -192,4 +193,4 @@ static inline void guest_mem_writeq(guest_memory_t* memory, uint64_t gpa, uint64
|
|||
uint64_t* hva = (uint64_t*)gpa_to_hva(memory, gpa);
|
||||
*hva = val;
|
||||
}
|
||||
} // namespace pound::arm64::memory
|
||||
} // namespace pound::kvm::memory
|
||||
|
|
@ -1,11 +1,25 @@
|
|||
#include "isa.h"
|
||||
#include "guest.h"
|
||||
#include "memory/arena.h"
|
||||
#include "kvm.h"
|
||||
#include <cassert>
|
||||
#include "common/Logging/Log.h"
|
||||
#include "guest.h"
|
||||
#include "host/memory/arena.h"
|
||||
|
||||
namespace pound::arm64
|
||||
namespace pound::kvm
|
||||
{
|
||||
void take_synchronous_exception(vcpu_state_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address)
|
||||
|
||||
uint8_t kvm_probe(kvm_t* kvm, enum target_type type)
|
||||
{
|
||||
if (type != KVM_TARGET_SWITCH1)
|
||||
{
|
||||
assert(!"Only Switch 1 is supported");
|
||||
}
|
||||
kvm->ops = s1_ops;
|
||||
/* Go to targets/switch1/hardware/probe.cpp */
|
||||
(void)kvm->ops.init(kvm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void take_synchronous_exception(kvm_vcpu_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address)
|
||||
{
|
||||
assert(nullptr != vcpu);
|
||||
/* An EC holds 6 bits.*/
|
||||
|
|
@ -67,7 +81,7 @@ void take_synchronous_exception(vcpu_state_t* vcpu, uint8_t exception_class, uin
|
|||
* @param memory A pointer to an initialized guest_memory_t struct.
|
||||
* @return true if all tests pass, false otherwise.
|
||||
*/
|
||||
bool test_guest_ram_access(pound::arm64::memory::guest_memory_t* memory)
|
||||
bool test_guest_ram_access(pound::kvm::memory::guest_memory_t* memory)
|
||||
{
|
||||
LOG_INFO(Memory, "--- [ Starting Guest RAM Access Test ] ---");
|
||||
if (memory == nullptr || memory->base == nullptr || memory->size < 4096)
|
||||
|
|
@ -153,14 +167,13 @@ 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 = {};
|
||||
pound::kvm::memory::guest_memory_t guest_ram = {};
|
||||
guest_ram.base = static_cast<uint8_t*>(guest_memory_arena.data);
|
||||
guest_ram.size = guest_memory_arena.capacity;
|
||||
|
||||
(void)test_guest_ram_access(&guest_ram);
|
||||
}
|
||||
} // namespace pound::armv64
|
||||
} // namespace pound::kvm
|
||||
|
|
@ -1,20 +1,17 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
#include "guest.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "Base/Logging/Log.h"
|
||||
|
||||
namespace pound::arm64
|
||||
namespace pound::kvm
|
||||
{
|
||||
/* AArch64 R0-R31 */
|
||||
#define GP_REGISTERS 32
|
||||
|
||||
/* AArch64 V0-V31 */
|
||||
#define FP_REGISTERS 32
|
||||
|
||||
#define CACHE_LINE_SIZE 64
|
||||
#define GUEST_RAM_SIZE 10240 // 10KiB
|
||||
#define CPU_CORES 8
|
||||
|
|
@ -30,10 +27,8 @@ namespace pound::arm64
|
|||
#define PSTATE_EL1T 0b0100
|
||||
#define PSTATE_EL1H 0b0101
|
||||
|
||||
|
||||
/*
|
||||
* vcpu_state_t - Holds the architectural and selected system-register state for an emulated vCPU.
|
||||
* @v: 128-bit SIMD/FP vector registers V0–V31.
|
||||
* kvm_vcpu_t - Holds the architectural and selected system-register state for an emulated vCPU.
|
||||
* @r: General-purpose registers X0–X31 (X31 as SP/ZR as appropriate).
|
||||
* @pc: Program Counter.
|
||||
* @cntfreq_el0: Counter Frequency.
|
||||
|
|
@ -63,7 +58,6 @@ namespace pound::arm64
|
|||
*/
|
||||
typedef struct alignas(CACHE_LINE_SIZE)
|
||||
{
|
||||
unsigned __int128 v[FP_REGISTERS];
|
||||
uint64_t r[GP_REGISTERS];
|
||||
uint64_t pc;
|
||||
uint64_t cntfreq_el0;
|
||||
|
|
@ -108,7 +102,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
|
||||
|
|
@ -136,7 +130,92 @@ typedef struct alignas(CACHE_LINE_SIZE)
|
|||
uint32_t dczid_el0;
|
||||
uint32_t pmcr_el0;
|
||||
uint32_t pstate;
|
||||
} vcpu_state_t;
|
||||
} kvm_vcpu_t;
|
||||
|
||||
/* This is here to break the circular dependency between kvm_t and kvm_ops_t. */
|
||||
typedef struct kvm_s kvm_t;
|
||||
|
||||
/*
|
||||
* struct kvm_ops_t - A table of machine-specific operations.
|
||||
* @init: Function pointer to initialize the target machine's state.
|
||||
* Called once by kvm_probe(). It is responsible for setting up
|
||||
* the guest memory map, loading firmware, and registering all
|
||||
* MMIO device handlers.
|
||||
* @mmio_read: Function pointer to handle a guest read from an MMIO region.
|
||||
* @mmio_write: Function pointer to handle a guest write to an MMIO region.
|
||||
* @destroy: Function pointer to clean up and deallocate all resources
|
||||
* associated with the machine instance. Called on VM shutdown.
|
||||
*
|
||||
* This structure acts as a "virtual table" in C, allowing the generic KVM
|
||||
* core to call target-specific code (e.g., for a Switch 1 or Switch 2)
|
||||
* without needing to know the implementation details. Each supported target
|
||||
* machine must provide a globally visible instance of this struct.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/* Initialize the machine state */
|
||||
int8_t (*init)(kvm_t* kvm);
|
||||
|
||||
/* Handles an MMIO read from the guest. */
|
||||
int8_t (*mmio_read)(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len);
|
||||
|
||||
/* Handles an MMIO write from the guest. */
|
||||
int8_t (*mmio_write)(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len);
|
||||
|
||||
/* Clean up on shutdown */
|
||||
void (*destroy)(kvm_t* kvm);
|
||||
} kvm_ops_t;
|
||||
|
||||
/*
|
||||
* kvm_s - The main KVM instance structure.
|
||||
* @vcpu: The state of the emulated virtual CPU core. Contains all guest-
|
||||
* visible registers.
|
||||
* @memory: A structure representing the guest's physical RAM.
|
||||
* @ops: A table of function pointers to the machine-specific implementation
|
||||
* for this KVM instance. This should only be set by kvm_probe().
|
||||
*
|
||||
* This structure represents a single virtual machine instance.
|
||||
*/
|
||||
struct kvm_s
|
||||
{
|
||||
pound::kvm::kvm_vcpu_t vcpu;
|
||||
pound::kvm::memory::guest_memory_t memory;
|
||||
kvm_ops_t ops;
|
||||
};
|
||||
|
||||
/**
|
||||
* s1_ops - The machine-specific operations for the "Switch 1" target.
|
||||
*
|
||||
* This is the global instance of the operations table for the Switch 1.
|
||||
* It is defined in the target-specific source file
|
||||
* (targets/switch1/hardware/probe.cpp) and provides the iplementations
|
||||
* for initializing and running the emulated Switch 1 hardware.
|
||||
*/
|
||||
extern const kvm_ops_t s1_ops;
|
||||
|
||||
enum target_type
|
||||
{
|
||||
KVM_TARGET_SWITCH1 = 0,
|
||||
KVM_TARGET_SWITCH2 = 1,
|
||||
};
|
||||
|
||||
/*
|
||||
* kvm_probe - Probes for and initializes a target machine configuration.
|
||||
* @kvm: A pointer to the KVM instance to be initialized. This function will
|
||||
* populate the fields of this struct.
|
||||
* @type: The type of target machine to initialize.
|
||||
*
|
||||
* This function is the primary factory for creating a virtual machine. It
|
||||
* looks up the requested machine @type, attaches the corresponding operations
|
||||
* table (e.g., s1_ops) to the @kvm instance, and calls the machine-specific
|
||||
* init() function.
|
||||
*
|
||||
* On successful return, the @kvm struct will be fully configured and ready
|
||||
* for execution.
|
||||
*
|
||||
* Return: 0 on success, or a negative errno code on failure.
|
||||
*/
|
||||
uint8_t kvm_probe(kvm_t* kvm, enum target_type type);
|
||||
|
||||
/*
|
||||
* take_synchronous_exception() - Emulates the hardware process of taking a synchronous exception to EL1.
|
||||
|
|
@ -153,7 +232,7 @@ typedef struct alignas(CACHE_LINE_SIZE)
|
|||
* program counter by branching to the appropriate offset in the EL1 vector table.
|
||||
*
|
||||
*/
|
||||
void take_synchronous_exception(vcpu_state_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address);
|
||||
void take_synchronous_exception(kvm_vcpu_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address);
|
||||
|
||||
void cpuTest();
|
||||
} // namespace pound::arm64
|
||||
} // namespace pound::kvm
|
||||
141
src/kvm/mmio.cpp
Normal file
141
src/kvm/mmio.cpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#include "mmio.h"
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
namespace pound::kvm::memory
|
||||
{
|
||||
/*
|
||||
* This function implements a strict weak ordering comparison on two
|
||||
* MMIO ranges, based solely on their starting guest physical address.
|
||||
*
|
||||
* It is designed to be used with std::lower_bound.
|
||||
*/
|
||||
bool mmio_compare_ranges(const mmio_range_t& a, const mmio_range_t& b)
|
||||
{
|
||||
return a.gpa_base < b.gpa_base;
|
||||
}
|
||||
|
||||
int8_t mmio_db_register(mmio_db_t* db, const mmio_range_t range, const mmio_handler_t handler)
|
||||
{
|
||||
assert(nullptr != db);
|
||||
assert((db->address_ranges.size() + 1) <= MMIO_REGIONS);
|
||||
|
||||
auto it = std::lower_bound(db->address_ranges.begin(), db->address_ranges.end(), range, mmio_compare_ranges);
|
||||
size_t i = it - db->address_ranges.begin();
|
||||
|
||||
/*
|
||||
* Scenario: UART is a current region, TIMER is a new region being
|
||||
* registered.
|
||||
*
|
||||
* [-- UART --]
|
||||
* 0x9000 0x9004
|
||||
* [---- TIMER ----] <-- CONFLICT!
|
||||
* 0x9002 0x900A
|
||||
*/
|
||||
if (i > 0)
|
||||
{
|
||||
if (range.gpa_base < db->address_ranges[i - 1].gpa_end)
|
||||
{
|
||||
return EADDRESS_OVERLAP;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Scenario: UART is a current region, TIMER is a new region being
|
||||
* registered.
|
||||
*
|
||||
* [---- TIMER ----] <-- CONFLICT!
|
||||
* 0x9000 0x9004
|
||||
* [-- UART --]
|
||||
* 0x9002 0x900A
|
||||
*/
|
||||
if (i < db->address_ranges.size())
|
||||
{
|
||||
if (db->address_ranges[i].gpa_base < range.gpa_end)
|
||||
{
|
||||
return EADDRESS_OVERLAP;
|
||||
}
|
||||
}
|
||||
|
||||
db->address_ranges.insert(it, range);
|
||||
db->handlers.insert(db->handlers.begin() + i, handler);
|
||||
return MMIO_SUCCESS;
|
||||
}
|
||||
|
||||
bool mmio_compare_addresses(const mmio_range_t& a, const mmio_range_t& b)
|
||||
{
|
||||
return a.gpa_base < b.gpa_base;
|
||||
}
|
||||
|
||||
int8_t mmio_db_dispatch_write(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len)
|
||||
{
|
||||
assert(nullptr != db);
|
||||
assert(nullptr != kvm);
|
||||
assert(nullptr != data);
|
||||
assert(len > 0);
|
||||
|
||||
mmio_range_t search_key = {.gpa_base = gpa, .gpa_end = 0};
|
||||
/* Find the first region that starts after the target gpa */
|
||||
auto it =
|
||||
std::upper_bound(db->address_ranges.begin(), db->address_ranges.end(), search_key, mmio_compare_addresses);
|
||||
|
||||
/* If `it` is the beginning, then the gpa is smaller than all known regions. */
|
||||
if (db->address_ranges.begin() == it)
|
||||
{
|
||||
return ENOT_HANDLED;
|
||||
}
|
||||
|
||||
mmio_range_t candidate = *(it - 1);
|
||||
/* base <= gpa < end */
|
||||
if ((candidate.gpa_base <= gpa) && (gpa < candidate.gpa_end))
|
||||
{
|
||||
size_t i = (it - 1) - db->address_ranges.begin();
|
||||
if (nullptr == db->handlers[i].write)
|
||||
{
|
||||
return EACCESS_DENIED;
|
||||
}
|
||||
|
||||
db->handlers[i].write(kvm, gpa, data, len);
|
||||
return MMIO_SUCCESS;
|
||||
}
|
||||
|
||||
/* The gpa is not in any mmio region. */
|
||||
return ENOT_HANDLED;
|
||||
}
|
||||
|
||||
int8_t mmio_db_dispatch_read(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len)
|
||||
{
|
||||
assert(nullptr != db);
|
||||
assert(nullptr != kvm);
|
||||
assert(nullptr != data);
|
||||
assert(len > 0);
|
||||
|
||||
mmio_range_t search_key = {.gpa_base = gpa, .gpa_end = 0};
|
||||
/* Find the first region that starts after the target gpa */
|
||||
auto it =
|
||||
std::upper_bound(db->address_ranges.begin(), db->address_ranges.end(), search_key, mmio_compare_addresses);
|
||||
|
||||
/* If `it` is the beginning, then the gpa is smaller than all known regions. */
|
||||
if (db->address_ranges.begin() == it)
|
||||
{
|
||||
return ENOT_HANDLED;
|
||||
}
|
||||
|
||||
mmio_range_t candidate = *(it - 1);
|
||||
/* base <= gpa < end */
|
||||
if ((candidate.gpa_base <= gpa) && (gpa < candidate.gpa_end))
|
||||
{
|
||||
size_t i = (it - 1) - db->address_ranges.begin();
|
||||
if (nullptr == db->handlers[i].read)
|
||||
{
|
||||
return EACCESS_DENIED;
|
||||
}
|
||||
|
||||
db->handlers[i].read(kvm, gpa, data, len);
|
||||
return MMIO_SUCCESS;
|
||||
}
|
||||
|
||||
/* The gpa is not in any mmio region. */
|
||||
return ENOT_HANDLED;
|
||||
}
|
||||
} // namespace pound::kvm::memory
|
||||
191
src/kvm/mmio.h
Normal file
191
src/kvm/mmio.h
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "host/memory/arena_stl.h"
|
||||
#include "kvm.h"
|
||||
|
||||
namespace pound::kvm::memory
|
||||
{
|
||||
/*
|
||||
* MMIO_REGIONS - The maximum number of distinct MMIO regions supported.
|
||||
*
|
||||
* It sets a hard limit on how many separate hardware device regions
|
||||
* can be registered at boot time.
|
||||
*/
|
||||
#define MMIO_REGIONS 20
|
||||
|
||||
/* MMIO_SUCCESS - Return code for a successfull MMIO operation. */
|
||||
#define MMIO_SUCCESS 0
|
||||
|
||||
/* EADDRESS_OVERLAP - Error code for an MMIO address space conflict. */
|
||||
#define EADDRESS_OVERLAP (-1)
|
||||
|
||||
#define ENOT_HANDLED (-2)
|
||||
|
||||
#define EACCESS_DENIED (-3)
|
||||
|
||||
/*
|
||||
* typedef mmio - Function pointer type for an MMIO access handler.
|
||||
* @kvm: A pointer to the KVM instance.
|
||||
* @gpa: The guest physical address of the access.
|
||||
* @data: A pointer to the data buffer. For reads, this buffer
|
||||
* should be filled by the handler. For writes, this buffer
|
||||
* contains the data written by the guest.
|
||||
* @len: The size of the access in bytes.
|
||||
*
|
||||
* This function pointer defines the contract for all MMIO read
|
||||
* and write handler functions. Handlers are responsible for emulating
|
||||
* the hardware's response to a memory access at a specific register
|
||||
* address.
|
||||
*
|
||||
* Returns: MMIO_SUCCESS on success, negative errno code on failure.
|
||||
*/
|
||||
typedef int8_t (*mmio)(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len);
|
||||
|
||||
/*
|
||||
* mmio_handler_t - A pair of handlers for an MMIO region.
|
||||
* @read: A function pointer to be called for read access within the
|
||||
* region. Can be NULL if the region is write-only.
|
||||
* @write: A function pointer to be called for write access within the
|
||||
* region. Can NULL if the region is read-only.
|
||||
*
|
||||
* This structure stores the read and write operations for a single
|
||||
* hardware device or memory region.
|
||||
*
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
mmio read;
|
||||
mmio write;
|
||||
} mmio_handler_t;
|
||||
|
||||
/*
|
||||
* mmio_range_t - Defines a half-open guest physical address range.
|
||||
* @gpa_base: The starting (inclusive) guest physical address of
|
||||
* the region.
|
||||
* @gpa_end: The ending (exclusive) guest physical address of the region.
|
||||
*
|
||||
* This structure defines a contiguous block of guest physical address
|
||||
* space, [gpa_base, gpa_end). The use of an exclusive end address
|
||||
* simplifies range and adjacency calculations.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
uint64_t gpa_base;
|
||||
uint64_t gpa_end;
|
||||
} mmio_range_t;
|
||||
|
||||
/*
|
||||
* mmio_db_t - A data-oriented database for MMIO dispatch.
|
||||
* @handlers: A vector of MMIO handler pairs.
|
||||
* @address_ranges: A vector of physical address ranges, sorted by GPA base.
|
||||
*
|
||||
* This structure manages all registered Memory-Mapped I/O regions for a
|
||||
* virtual machine. It is designed with a "Structure of Arrays" layout
|
||||
* to maximize host CPU cache efficiency during lookups.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/*
|
||||
* This uses a custom arena allocator to ensure that all handler nodes
|
||||
* are allocated fron a single, pre-allocated memory block.
|
||||
*
|
||||
* This is a parallel array to @address_ranges.
|
||||
*/
|
||||
std::vector<mmio_handler_t, pound::host::memory::arena_allocator<mmio_handler_t>> handlers;
|
||||
|
||||
/*
|
||||
* This vector is the primary target for the binary search lookup
|
||||
* in the MMIO dispatcher. Maintaining its sort order is critical
|
||||
* for the performance of the system.
|
||||
*/
|
||||
std::vector<mmio_range_t, pound::host::memory::arena_allocator<mmio_range_t>> address_ranges;
|
||||
} mmio_db_t;
|
||||
|
||||
/*
|
||||
* Registers a new MMIO region into the database.
|
||||
* @db: A pointer to the MMIO database to be modified.
|
||||
* @range: The new region's address space.
|
||||
* @handler: The read and write callbacks.
|
||||
*
|
||||
* This function safely inserts a new MMIO region into the database.
|
||||
*
|
||||
* Returns:
|
||||
* MMIO_SUCCESS on successfull registration.
|
||||
* EADDRESS_OVERLAP if the new @range conflicts with any existing region.
|
||||
*/
|
||||
int8_t mmio_db_register(mmio_db_t* db, const mmio_range_t range, const mmio_handler_t handler);
|
||||
|
||||
/*
|
||||
* mmio_db_dispatch_write - Dispatches a guest physical write to a registered MMIO handler.
|
||||
* @db: A pointer to the MMIO database to be queried.
|
||||
* @kvm: A pointer to the KVM instance.
|
||||
* @gpa: The guest physical address of the memory write.
|
||||
* @data: A pointer to the buffer containing the data written by the guest.
|
||||
* @len: The size of the write access in bytes.
|
||||
*
|
||||
* This function is on the critical path ("hot path") of the emulator. It
|
||||
* performs a high-performance binary search to determine if the target @gpa
|
||||
* falls within any of the registered MMIO regions.
|
||||
*
|
||||
* The logic is a two-stage process:
|
||||
* 1. An approximate search using std::upper_bound finds the first region that
|
||||
* starts *after* the target @gpa. The actual candidate region must be the
|
||||
* one immediately preceding this result.
|
||||
* 2. A precise check verifies if the @gpa is contained within the candidate
|
||||
* region's half-open interval [base, end).
|
||||
*
|
||||
* If a match is found, the corresponding write handler is invoked. If not, the
|
||||
* function signals that the access is not handled by the MMIO system and
|
||||
* should be treated as a normal RAM access.
|
||||
*
|
||||
* --- Visual Scenario ---
|
||||
*
|
||||
* Database Ranges: [-- R1 --) [---- R2 ----) [--- R3 ---)
|
||||
* Address Space: 0x1000 0x1010 0x4000 0x4080 0x9000 0x9010
|
||||
*
|
||||
* Search for GPA = 0x4020:
|
||||
*
|
||||
* 1. upper_bound() finds the first region starting > 0x4020, which is R3.
|
||||
* The iterator 'it' points to R3 at index 2.
|
||||
*
|
||||
* [-- R1 --) [---- R2 ----) [--- R3 ---)
|
||||
* ^
|
||||
* |
|
||||
* 'it'
|
||||
*
|
||||
* 2. The candidate for the search is the region before 'it', which is R2.
|
||||
*
|
||||
* [-- R1 --) [---- R2 ----) [--- R3 ---)
|
||||
* ^
|
||||
* |
|
||||
* candidate
|
||||
*
|
||||
* 3. Final check: Is 0x4020 >= R2.base (0x4000) AND < R2.end (0x4080)? Yes.
|
||||
* Result: Match found. Dispatch to handler for R2.
|
||||
*
|
||||
* Return:
|
||||
* MMIO_SUCCESS if the write was handled by a registered device. Returns
|
||||
* ENOT_HANDLED if the @gpa does not map to any MMIO region.
|
||||
* EACCESS_DENIED if the MMIO region has no write function pointer.
|
||||
*/
|
||||
int8_t mmio_db_dispatch_write(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len);
|
||||
|
||||
/*
|
||||
* mmio_db_dispatch_read - Dispatches a guest physical read to a registered MMIO handler.
|
||||
* @db: A pointer to the MMIO database to be queried.
|
||||
* @kvm: A pointer to the KVM instance.
|
||||
* @gpa: The guest physical address of the memory write.
|
||||
* @data: A pointer to the buffer containing the data written by the guest.
|
||||
* @len: The size of the write access in bytes.
|
||||
*
|
||||
* See @mmio_db_dispatch_write() for proper explanation.
|
||||
*
|
||||
* Return:
|
||||
* MMIO_SUCCESS if the write was handled by a registered device. Returns
|
||||
* ENOT_HANDLED if the @gpa does not map to any MMIO region.
|
||||
* EACCESS_DENIED if the MMIO region has no write function pointer.
|
||||
*/
|
||||
int8_t mmio_db_dispatch_read(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len);
|
||||
} // namespace pound::kvm::memory
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#include "mmu.h"
|
||||
#include <limits.h>
|
||||
#include "isa.h"
|
||||
#include "kvm.h"
|
||||
|
||||
namespace pound::arm64::memory
|
||||
namespace pound::kvm::memory
|
||||
{
|
||||
#define GRANULE_4KB (1ULL << 12)
|
||||
#define GRANULE_16KB (1ULL << 14)
|
||||
|
|
@ -42,7 +42,7 @@ static inline uint8_t msvc_ctzll(unsigned long long val)
|
|||
/* Define the size of a page table entry (descriptor) */
|
||||
#define PAGE_TABLE_ENTRY_SHIFT 3 /* log2(8 bytes) */
|
||||
|
||||
int mmu_gva_to_gpa(pound::arm64::vcpu_state_t* vcpu, guest_memory_t* memory, uint64_t gva, uint64_t* out_gpa)
|
||||
int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_t gva, uint64_t* out_gpa)
|
||||
{
|
||||
const uint8_t SCTLR_EL1_M_BIT = (1 << 0);
|
||||
if (0 == (vcpu->sctlr_el1 & SCTLR_EL1_M_BIT))
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -392,4 +392,4 @@ int mmu_gva_to_gpa(pound::arm64::vcpu_state_t* vcpu, guest_memory_t* memory, uin
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
} // namespace pound::arm64::memory
|
||||
} // namespace pound::kvm::memory
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "isa.h"
|
||||
#include "guest.h"
|
||||
#include "kvm.h"
|
||||
|
||||
namespace pound::arm64::memory
|
||||
namespace pound::kvm::memory
|
||||
{
|
||||
/*
|
||||
* mmu_gva_to_gpa() - Translate a Guest Virtual Address to a Guest Physical Address.
|
||||
|
|
@ -36,5 +36,5 @@ namespace pound::arm64::memory
|
|||
* Return: 0 on successful translation. A negative error code on a translation
|
||||
* fault (e.g., for a page fault, permission error, or alignment fault).
|
||||
*/
|
||||
int mmu_gva_to_gpa(pound::arm64::vcpu_state_t* vcpu, guest_memory_t* memory, uint64_t gva, uint64_t* out_gpa);
|
||||
} // namespace pound::arm64::memory
|
||||
int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_t gva, uint64_t* out_gpa);
|
||||
} // namespace pound::kvm::memory
|
||||
|
|
@ -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"
|
||||
|
||||
13
src/targets/switch1/hardware/CMakeLists.txt
Normal file
13
src/targets/switch1/hardware/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
set(HARDWARE_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/probe.cpp
|
||||
)
|
||||
|
||||
target_sources(Pound PRIVATE
|
||||
${HARDWARE_SOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(Pound PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
)
|
||||
39
src/targets/switch1/hardware/probe.cpp
Normal file
39
src/targets/switch1/hardware/probe.cpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#include "kvm/kvm.h"
|
||||
#include "common/Logging/Log.h"
|
||||
|
||||
namespace pound::kvm
|
||||
{
|
||||
static int8_t s1_init(kvm_t* kvm);
|
||||
static int8_t s1_mmio_read(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len);
|
||||
static int8_t s1_mmio_write(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len);
|
||||
static void s1_destroy(kvm_t* kvm);
|
||||
|
||||
const kvm_ops_t s1_ops = {
|
||||
.init = s1_init,
|
||||
.mmio_read = s1_mmio_read,
|
||||
.mmio_write = s1_mmio_write,
|
||||
.destroy = s1_destroy,
|
||||
};
|
||||
|
||||
|
||||
static int8_t s1_init(kvm_t* kvm)
|
||||
{
|
||||
LOG_INFO(PROBE, "Initializing Switch 1 virtual machine");
|
||||
/* BOOTSTRAPPING CODE GOES HERE */
|
||||
return 0;
|
||||
}
|
||||
static int8_t s1_mmio_read(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len)
|
||||
{
|
||||
/* TODO(GloriousTacoo:kvm) */
|
||||
return 0;
|
||||
}
|
||||
static int8_t s1_mmio_write(kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len)
|
||||
{
|
||||
/* TODO(GloriousTacoo:kvm) */
|
||||
return 0;
|
||||
}
|
||||
static void s1_destroy(kvm_t* kvm)
|
||||
{
|
||||
/* TODO(GloriousTacoo:kvm) */
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue