mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-13 04:36:57 +00:00
common: Remove unused files
Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
parent
61f1340dee
commit
3f8ce127ea
26 changed files with 0 additions and 3916 deletions
|
|
@ -1,14 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#if defined(__x86_64__) || defined(_M_X64)
|
|
||||||
#define ARCH_X86_64 1
|
|
||||||
#elif defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86)
|
|
||||||
#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)
|
|
||||||
#define ARCH_PPC 1
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,260 +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,111 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
namespace Config
|
|
||||||
{
|
|
||||||
|
|
||||||
void Load(const std::filesystem::path& path);
|
|
||||||
void Save(const std::filesystem::path& path);
|
|
||||||
|
|
||||||
int windowWidth();
|
|
||||||
|
|
||||||
int windowHeight();
|
|
||||||
|
|
||||||
bool isLogAdvanced();
|
|
||||||
|
|
||||||
std::string logType();
|
|
||||||
|
|
||||||
} // namespace Config
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace Base
|
|
||||||
{
|
|
||||||
|
|
||||||
// Like GetLastErrorMsg(), but passing an explicit error code.
|
|
||||||
// Defined in error.cpp.
|
|
||||||
[[nodiscard]] std::string NativeErrorToString(const s32 e);
|
|
||||||
|
|
||||||
// Generic function to get last error message.
|
|
||||||
// Call directly after the command or use the error num.
|
|
||||||
// This function might change the error code.
|
|
||||||
// Defined in error.cpp.
|
|
||||||
[[nodiscard]] std::string GetLastErrorMsg();
|
|
||||||
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,490 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "IoFile.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);
|
|
||||||
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,231 +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,442 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#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"
|
|
||||||
#include "LogEntry.h"
|
|
||||||
#include "TextFormatter.h"
|
|
||||||
|
|
||||||
namespace Base {
|
|
||||||
namespace Log {
|
|
||||||
|
|
||||||
using namespace Base::FS;
|
|
||||||
|
|
||||||
// Base backend with shell functions
|
|
||||||
class BaseBackend {
|
|
||||||
public:
|
|
||||||
virtual ~BaseBackend() = default;
|
|
||||||
virtual void Write(const Entry &entry) = 0;
|
|
||||||
virtual void Flush() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Backend that writes to stdout and with color
|
|
||||||
class ColorConsoleBackend : public BaseBackend {
|
|
||||||
public:
|
|
||||||
explicit ColorConsoleBackend() = default;
|
|
||||||
|
|
||||||
~ColorConsoleBackend() = default;
|
|
||||||
|
|
||||||
void Write(const Entry &entry) override {
|
|
||||||
if (enabled.load(std::memory_order_relaxed)) {
|
|
||||||
PrintColoredMessage(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flush() override {
|
|
||||||
// stdout shouldn't be buffered
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetEnabled(bool enabled_) {
|
|
||||||
enabled = enabled_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::atomic<bool> enabled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Backend that writes to a file passed into the constructor
|
|
||||||
class FileBackend : public BaseBackend {
|
|
||||||
public:
|
|
||||||
explicit FileBackend(const fs::path &filename)
|
|
||||||
: file(filename, FS::FileAccessMode::Write, FS::FileMode::TextMode)
|
|
||||||
{}
|
|
||||||
|
|
||||||
~FileBackend() {
|
|
||||||
file.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Write(const Entry &entry) override {
|
|
||||||
if (!enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.formatted) {
|
|
||||||
bytesWritten += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
bytesWritten += file.WriteString(entry.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
|
|
||||||
constexpr u64 writeLimit = 100_MB;
|
|
||||||
const bool writeLimitExceeded = bytesWritten > writeLimit;
|
|
||||||
if (entry.logLevel >= Level::Error || writeLimitExceeded) {
|
|
||||||
if (writeLimitExceeded) {
|
|
||||||
// Stop writing after the write limit is exceeded.
|
|
||||||
// Don't close the file so we can print a stacktrace if necessary
|
|
||||||
enabled = false;
|
|
||||||
}
|
|
||||||
file.Flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flush() override {
|
|
||||||
file.Flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Base::FS::IOFile file;
|
|
||||||
std::atomic<bool> enabled = true;
|
|
||||||
size_t bytesWritten = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool currentlyInitialising = true;
|
|
||||||
|
|
||||||
// Static state as a singleton.
|
|
||||||
class Impl {
|
|
||||||
public:
|
|
||||||
static Impl& Instance() {
|
|
||||||
if (!instance) {
|
|
||||||
throw std::runtime_error("Using Logging instance before its initialization");
|
|
||||||
}
|
|
||||||
return *instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Initialize(const std::string_view logFile) {
|
|
||||||
if (instance) {
|
|
||||||
LOG_WARNING(Log, "Reinitializing logging backend");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto logDir = GetUserPath(PathType::LogDir);
|
|
||||||
Filter filter;
|
|
||||||
//filter.ParseFilterString(Config::getLogFilter());
|
|
||||||
instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(logDir / logFile, filter), Deleter);
|
|
||||||
currentlyInitialising = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsActive() {
|
|
||||||
return instance != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Start() {
|
|
||||||
instance->StartBackendThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Stop() {
|
|
||||||
instance->StopBackendThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
Impl(const Impl&) = delete;
|
|
||||||
Impl& operator=(const Impl&) = delete;
|
|
||||||
|
|
||||||
Impl(Impl&&) = delete;
|
|
||||||
Impl& operator=(Impl&&) = delete;
|
|
||||||
|
|
||||||
void SetGlobalFilter(const Filter& f) {
|
|
||||||
filter = f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetColorConsoleBackendEnabled(bool enabled) {
|
|
||||||
colorConsoleBackend->SetEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PushEntry(Class logClass, Level logLevel, const char *filename, u32 lineNum,
|
|
||||||
const char *function, const std::string &message) {
|
|
||||||
|
|
||||||
if (!filter.CheckMessage(logClass, logLevel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using std::chrono::duration_cast;
|
|
||||||
using std::chrono::microseconds;
|
|
||||||
using std::chrono::steady_clock;
|
|
||||||
|
|
||||||
const Entry entry = {
|
|
||||||
.timestamp = duration_cast<microseconds>(steady_clock::now() - timeOrigin),
|
|
||||||
.logClass = logClass,
|
|
||||||
.logLevel = logLevel,
|
|
||||||
.filename = filename,
|
|
||||||
.lineNum = lineNum,
|
|
||||||
.function = function,
|
|
||||||
.message = message,
|
|
||||||
};
|
|
||||||
if (Config::logType() == "async") {
|
|
||||||
messageQueue.EmplaceWait(entry);
|
|
||||||
} else {
|
|
||||||
ForEachBackend([&entry](BaseBackend* backend) { if (backend) { backend->Write(entry); } });
|
|
||||||
std::fflush(stdout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PushEntryNoFmt(Class logClass, Level logLevel, const std::string &message) {
|
|
||||||
if (!filter.CheckMessage(logClass, logLevel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using std::chrono::duration_cast;
|
|
||||||
using std::chrono::microseconds;
|
|
||||||
using std::chrono::steady_clock;
|
|
||||||
|
|
||||||
const Entry entry = {
|
|
||||||
.timestamp = duration_cast<microseconds>(steady_clock::now() - timeOrigin),
|
|
||||||
.logClass = logClass,
|
|
||||||
.logLevel = logLevel,
|
|
||||||
.message = message,
|
|
||||||
.formatted = false
|
|
||||||
};
|
|
||||||
if (Config::logType() == "async") {
|
|
||||||
messageQueue.EmplaceWait(entry);
|
|
||||||
} else {
|
|
||||||
ForEachBackend([&entry](BaseBackend* backend) { if (backend) { backend->Write(entry); } });
|
|
||||||
std::fflush(stdout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Impl(const fs::path &fileBackendFilename, const Filter &filter) :
|
|
||||||
filter(filter) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
HANDLE conOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
// Get current console mode
|
|
||||||
ul32 mode = 0;
|
|
||||||
GetConsoleMode(conOut, &mode);
|
|
||||||
// Set WinAPI to use a more 'modern' approach, by enabling VT
|
|
||||||
// Allows ASCII escape codes
|
|
||||||
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
||||||
// Write adjusted mode back
|
|
||||||
SetConsoleMode(conOut, mode);
|
|
||||||
#endif
|
|
||||||
colorConsoleBackend = std::make_unique<ColorConsoleBackend>();
|
|
||||||
fileBackend = std::make_unique<FileBackend>(fileBackendFilename);
|
|
||||||
}
|
|
||||||
|
|
||||||
~Impl() {
|
|
||||||
Stop();
|
|
||||||
fileBackend.reset();
|
|
||||||
colorConsoleBackend.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StartBackendThread() {
|
|
||||||
backendThread = std::jthread([this](std::stop_token stopToken) {
|
|
||||||
Base::SetCurrentThreadName("[Xe] Log");
|
|
||||||
Entry entry = {};
|
|
||||||
const auto writeLogs = [this, &entry]() {
|
|
||||||
ForEachBackend([&entry](BaseBackend *backend) { backend->Write(entry); });
|
|
||||||
};
|
|
||||||
while (!stopToken.stop_requested()) {
|
|
||||||
if (messageQueue.PopWait(entry, stopToken))
|
|
||||||
writeLogs();
|
|
||||||
}
|
|
||||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
|
|
||||||
// case where a system is repeatedly spamming logs even on close.
|
|
||||||
s32 maxLogsToWrite = filter.IsDebug() ? std::numeric_limits<s32>::max() : 100;
|
|
||||||
while (maxLogsToWrite-- > 0) {
|
|
||||||
if (messageQueue.TryPop(entry)) {
|
|
||||||
writeLogs();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void StopBackendThread() {
|
|
||||||
backendThread.request_stop();
|
|
||||||
if (backendThread.joinable()) {
|
|
||||||
backendThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
ForEachBackend([](BaseBackend *backend) { backend->Flush(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ForEachBackend(std::function<void(BaseBackend*)> lambda) {
|
|
||||||
lambda(colorConsoleBackend.get());
|
|
||||||
lambda(fileBackend.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Deleter(Impl* ptr) {
|
|
||||||
delete ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{ nullptr, Deleter };
|
|
||||||
|
|
||||||
Filter filter;
|
|
||||||
std::unique_ptr<ColorConsoleBackend> colorConsoleBackend = {};
|
|
||||||
std::unique_ptr<FileBackend> fileBackend = {};
|
|
||||||
|
|
||||||
MPSCQueue<Entry> messageQueue = {};
|
|
||||||
std::chrono::steady_clock::time_point timeOrigin = std::chrono::steady_clock::now();
|
|
||||||
std::jthread backendThread;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<fs::path> filepaths{};
|
|
||||||
|
|
||||||
void DeleteOldLogs(const fs::path& path, u64 num_logs, const u16 logLimit) {
|
|
||||||
const std::string filename = path.filename().string();
|
|
||||||
const std::chrono::time_point Now = std::chrono::system_clock::now();
|
|
||||||
const time_t timeNow = std::chrono::system_clock::to_time_t(Now);
|
|
||||||
const tm* time = std::localtime(&timeNow);
|
|
||||||
// We want to get rid of anything that isn't that current day's date
|
|
||||||
const std::string currentDate = fmt::format("{}-{}-{}", time->tm_mon + 1, time->tm_mday, 1900 + time->tm_year);
|
|
||||||
if (filename.find(currentDate) == std::string::npos) {
|
|
||||||
fs::remove_all(path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// We want to delete in date of creation, so just add it to a array
|
|
||||||
if (num_logs >= logLimit) {
|
|
||||||
filepaths.push_back(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 CreateIntegralTimestamp(const std::string &date) {
|
|
||||||
const u64 monthPos = date.find('-');
|
|
||||||
if (monthPos == std::string::npos) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const std::string month = date.substr(0, monthPos);
|
|
||||||
const u64 dayPos = date.find('-', monthPos);
|
|
||||||
if (dayPos == std::string::npos) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const u64 yearPos = date.find('-', dayPos);
|
|
||||||
const std::string day = date.substr(monthPos+1);
|
|
||||||
if (yearPos == std::string::npos) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const std::string year = date.substr(yearPos + 1);
|
|
||||||
const u64 yearInt = std::stoull(year);
|
|
||||||
const std::string timestamp = fmt::format("{}{}{}", month, day, yearInt - 1900);
|
|
||||||
return std::stoull(timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CleanupOldLogs(const std::string_view &logFileBase, const fs::path &logDir, const u16 logLimit) {
|
|
||||||
const fs::path LogFile = logFileBase;
|
|
||||||
// Track how many logs we have
|
|
||||||
size_t numLogs = 0;
|
|
||||||
for (auto &entry : fs::directory_iterator(logDir)) {
|
|
||||||
if (entry.is_regular_file()) {
|
|
||||||
const fs::path path = entry.path();
|
|
||||||
const std::string ext = path.extension().string();
|
|
||||||
if (!path.has_extension()) {
|
|
||||||
// Skip anything that isn't a log file
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ext != LogFile.extension()) {
|
|
||||||
// Skip anything that isn't a log file
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
numLogs++;
|
|
||||||
DeleteOldLogs(path, numLogs, logLimit);
|
|
||||||
} else {
|
|
||||||
// Skip anything that isn't a file
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (filepaths.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
u64 numToDelete{ logLimit };
|
|
||||||
std::map<u64, fs::path> date_sorted_paths{};
|
|
||||||
for (const auto &path : filepaths) {
|
|
||||||
const std::string stem = path.stem().string();
|
|
||||||
u64 basePos = stem.find('_');
|
|
||||||
// If we cannot get the base, just delete it
|
|
||||||
if (basePos == std::string::npos) {
|
|
||||||
numToDelete--;
|
|
||||||
fs::remove_all(path);
|
|
||||||
} else {
|
|
||||||
const std::string base = stem.substr(0, basePos);
|
|
||||||
const u64 datePos = base.find('_', basePos+1);
|
|
||||||
const std::string date = base.substr(datePos+1);
|
|
||||||
const u64 dateInt = CreateIntegralTimestamp(date);
|
|
||||||
if (datePos == std::string::npos) {
|
|
||||||
// If we cannot find the date, just delete it
|
|
||||||
numToDelete--;
|
|
||||||
fs::remove_all(path);
|
|
||||||
} else {
|
|
||||||
const u64 timePos = base.find('_', datePos+1);
|
|
||||||
const std::string time = base.substr(timePos+1);
|
|
||||||
const u64 timestamp = CreateIntegralTimestamp(time);
|
|
||||||
if (!timestamp) {
|
|
||||||
numToDelete--;
|
|
||||||
fs::remove_all(path);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
date_sorted_paths.insert({ dateInt + timestamp, path });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Start deleting based off timestamp
|
|
||||||
for (const auto &entry : date_sorted_paths) {
|
|
||||||
fs::remove_all(entry.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Initialize(const std::string_view &logFile) {
|
|
||||||
// Create directory vars to so we can use fs::path::stem
|
|
||||||
const fs::path LogDir = GetUserPath(PathType::LogDir);
|
|
||||||
const fs::path LogFile = LOG_FILE;
|
|
||||||
const fs::path LogFileStem = LogFile.stem();
|
|
||||||
const fs::path LogFileName = LogFile.filename();
|
|
||||||
// This is to make string_view happy
|
|
||||||
const std::string LogFileStemStr = LogFileStem.string();
|
|
||||||
const std::string LogFileNameStr = LogFileName.string();
|
|
||||||
// Setup filename
|
|
||||||
const std::string_view filestemBase = logFile.empty() ? LogFileStemStr : logFile;
|
|
||||||
const std::string_view filenameBase = logFile.empty() ? LogFileNameStr : logFile;
|
|
||||||
const std::chrono::time_point now = std::chrono::system_clock::now();
|
|
||||||
const time_t timeNow = std::chrono::system_clock::to_time_t(now);
|
|
||||||
const tm *time = std::localtime(&timeNow);
|
|
||||||
const std::string currentTime = fmt::format("{}-{}-{}", time->tm_hour, time->tm_min, time->tm_sec);
|
|
||||||
const std::string currentDate = fmt::format("{}-{}-{}", time->tm_mon + 1, time->tm_mday, 1900 + time->tm_year);
|
|
||||||
const std::string filename = fmt::format("{}_{}_{}.txt", filestemBase, currentDate, currentTime);
|
|
||||||
CleanupOldLogs(filenameBase, LogDir);
|
|
||||||
Impl::Initialize(logFile.empty() ? filename : logFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActive() {
|
|
||||||
return Impl::IsActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Start() {
|
|
||||||
Impl::Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Stop() {
|
|
||||||
Impl::Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetGlobalFilter(const Filter &filter) {
|
|
||||||
Impl::Instance().SetGlobalFilter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetColorConsoleBackendEnabled(bool enabled) {
|
|
||||||
Impl::Instance().SetColorConsoleBackendEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FmtLogMessageImpl(Class logClass, Level logLevel, const char *filename,
|
|
||||||
u32 lineNum, const char *function, const char *format,
|
|
||||||
const fmt::format_args &args) {
|
|
||||||
if (!currentlyInitialising) [[likely]] {
|
|
||||||
Impl::Instance().PushEntry(logClass, logLevel, filename, lineNum, function,
|
|
||||||
fmt::vformat(format, args));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoFmtMessage(Class logClass, Level logLevel, const std::string &message) {
|
|
||||||
if (!currentlyInitialising) [[likely]] {
|
|
||||||
Impl::Instance().PushEntryNoFmt(logClass, logLevel, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace Log
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include "common/PathUtil.h"
|
|
||||||
|
|
||||||
#include "Filter.h"
|
|
||||||
|
|
||||||
namespace Base {
|
|
||||||
namespace Log {
|
|
||||||
|
|
||||||
class Filter;
|
|
||||||
|
|
||||||
/// Cleans up logs from previous days, and any logs within the desired limit
|
|
||||||
void CleanupOldLogs(const std::string_view &logFileBase, const fs::path &logDir, const u16 logLimit = 50);
|
|
||||||
|
|
||||||
/// Initializes the logging system
|
|
||||||
void Initialize(const std::string_view &logFile = {});
|
|
||||||
|
|
||||||
bool IsActive();
|
|
||||||
|
|
||||||
/// Starts the logging threads
|
|
||||||
void Start();
|
|
||||||
|
|
||||||
/// Explictily stops the logger thread and flushes the buffers
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
/// The global filter will prevent any messages from even being processed if they are filtered
|
|
||||||
void SetGlobalFilter(const Filter &filter);
|
|
||||||
|
|
||||||
void SetColorConsoleBackendEnabled(bool enabled);
|
|
||||||
|
|
||||||
} // namespace Log
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#include "Filter.h"
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace Base {
|
|
||||||
namespace Log {
|
|
||||||
|
|
||||||
template <typename It>
|
|
||||||
Level GetLevelByName(const It begin, const It end) {
|
|
||||||
for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) {
|
|
||||||
const char* level_name = GetLevelName(static_cast<Level>(i));
|
|
||||||
if (std::string_view(begin, end).compare(level_name) == 0) {
|
|
||||||
return static_cast<Level>(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Level::Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename It>
|
|
||||||
Class GetClassByName(const It begin, const It end) {
|
|
||||||
for (u8 i = 0; i < static_cast<u8>(Class::Count); ++i) {
|
|
||||||
const char* level_name = GetLogClassName(static_cast<Class>(i));
|
|
||||||
if (std::string_view(begin, end).compare(level_name) == 0) {
|
|
||||||
return static_cast<Class>(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Class::Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Iterator>
|
|
||||||
bool ParseFilterRule(Filter &instance, Iterator begin, Iterator end) {
|
|
||||||
const auto levelSeparator = std::find(begin, end, ':');
|
|
||||||
if (levelSeparator == end) {
|
|
||||||
// LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", std::string_view(begin, end));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Level level = GetLevelByName(levelSeparator + 1, end);
|
|
||||||
if (level == Level::Count) {
|
|
||||||
//LOG_ERROR(Log, "Unknown log level in filter: {}", std::string_view(begin, end));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::string_view(begin, levelSeparator).compare("*") == 0) {
|
|
||||||
instance.ResetAll(level);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Class logClass = GetClassByName(begin, levelSeparator);
|
|
||||||
if (logClass == Class::Count) {
|
|
||||||
//LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.SetClassLevel(logClass, level);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
|
||||||
#define ALL_LOG_CLASSES() \
|
|
||||||
CLS(Log) \
|
|
||||||
CLS(Base) \
|
|
||||||
SUB(Base, Filesystem) \
|
|
||||||
CLS(Config) \
|
|
||||||
CLS(Debug) \
|
|
||||||
CLS(System) \
|
|
||||||
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) {
|
|
||||||
switch (logClass) {
|
|
||||||
#define CLS(x) \
|
|
||||||
case Class::x: \
|
|
||||||
return #x;
|
|
||||||
#define SUB(x, y) \
|
|
||||||
case Class::x##_##y: \
|
|
||||||
return #x "." #y;
|
|
||||||
ALL_LOG_CLASSES()
|
|
||||||
#undef CLS
|
|
||||||
#undef SUB
|
|
||||||
case Class::Count:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetLevelName(Level logLevel) {
|
|
||||||
#define LVL(x) \
|
|
||||||
case Level::x: \
|
|
||||||
return #x
|
|
||||||
switch (logLevel) {
|
|
||||||
LVL(Trace);
|
|
||||||
LVL(Debug);
|
|
||||||
LVL(Info);
|
|
||||||
LVL(Warning);
|
|
||||||
LVL(Error);
|
|
||||||
LVL(Critical);
|
|
||||||
LVL(Guest);
|
|
||||||
case Level::Count:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#undef LVL
|
|
||||||
}
|
|
||||||
|
|
||||||
Filter::Filter(Level defaultLevel) {
|
|
||||||
ResetAll(defaultLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Filter::ResetAll(Level level) {
|
|
||||||
classLevels.fill(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Filter::SetClassLevel(Class logClass, Level level) {
|
|
||||||
classLevels[static_cast<size_t>(logClass)] = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Filter::ParseFilterString(const std::string_view &filterView) {
|
|
||||||
auto clause_begin = filterView.cbegin();
|
|
||||||
while (clause_begin != filterView.cend()) {
|
|
||||||
auto clause_end = std::find(clause_begin, filterView.cend(), ' ');
|
|
||||||
|
|
||||||
// If clause isn't empty
|
|
||||||
if (clause_end != clause_begin) {
|
|
||||||
ParseFilterRule(*this, clause_begin, clause_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clause_end != filterView.cend()) {
|
|
||||||
// Skip over the whitespace
|
|
||||||
++clause_end;
|
|
||||||
}
|
|
||||||
clause_begin = clause_end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Filter::CheckMessage(Class logClass, Level level) const {
|
|
||||||
return static_cast<u8>(level) >=
|
|
||||||
static_cast<u8>(classLevels[static_cast<size_t>(logClass)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Filter::IsDebug() const {
|
|
||||||
return std::any_of(classLevels.begin(), classLevels.end(), [](const Level& l) {
|
|
||||||
return static_cast<u8>(l) <= static_cast<u8>(Level::Debug);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Log
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#include "LogTypes.h"
|
|
||||||
|
|
||||||
namespace Base {
|
|
||||||
namespace Log {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
|
||||||
* instead of underscores as in the enumeration.
|
|
||||||
*/
|
|
||||||
const char* GetLogClassName(Class log_class);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns the name of the passed log level as a C-string.
|
|
||||||
*/
|
|
||||||
const char* GetLevelName(Level log_level);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Implements a log message filter which allows different log classes to have different minimum
|
|
||||||
* severity levels. The filter can be changed at runtime and can be parsed from a string to allow
|
|
||||||
* editing via the interface or loading from a configuration file.
|
|
||||||
*/
|
|
||||||
class Filter {
|
|
||||||
public:
|
|
||||||
/// Initializes the filter with all classes having `default_level` as the minimum level.
|
|
||||||
explicit Filter(Level defaultLevel = Level::Info);
|
|
||||||
|
|
||||||
/// Resets the filter so that all classes have `level` as the minimum displayed level.
|
|
||||||
void ResetAll(Level level);
|
|
||||||
|
|
||||||
/// Sets the minimum level of `log_class` (and not of its subclasses) to `level`.
|
|
||||||
void SetClassLevel(Class logClass, Level level);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parses a filter string and applies it to this filter.
|
|
||||||
*
|
|
||||||
* A filter string consists of a space-separated list of filter rules, each of the format
|
|
||||||
* `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
|
|
||||||
* `*` is allowed as a class name and will reset all filters to the specified level. `<level>`
|
|
||||||
* a severity level name which will be set as the minimum logging level of the matched classes.
|
|
||||||
* Rules are applied left to right, with each rule overriding previous ones in the sequence.
|
|
||||||
*
|
|
||||||
* A few examples of filter rules:
|
|
||||||
* - `*:Info` -- Resets the level of all classes to Info.
|
|
||||||
* - `Service:Info` -- Sets the level of Service to Info.
|
|
||||||
* - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
|
|
||||||
*/
|
|
||||||
void ParseFilterString(const std::string_view &filterView);
|
|
||||||
|
|
||||||
/// Matches class/level combination against the filter, returning true if it passed.
|
|
||||||
bool CheckMessage(Class logClass, Level level) const;
|
|
||||||
|
|
||||||
/// Returns true if any logging classes are set to debug
|
|
||||||
bool IsDebug() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::array<Level, static_cast<const size_t>(Class::Count)> classLevels;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Log
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <fmt/format.h>
|
|
||||||
|
|
||||||
#include "common/Config.h"
|
|
||||||
|
|
||||||
#include "LogTypes.h"
|
|
||||||
|
|
||||||
namespace Base {
|
|
||||||
namespace Log {
|
|
||||||
|
|
||||||
constexpr const char* TrimSourcePath(const std::string_view &source) {
|
|
||||||
const auto rfind = [source](const std::string_view match) {
|
|
||||||
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
|
|
||||||
};
|
|
||||||
const auto idx = std::max({ rfind("/"), rfind("\\") });
|
|
||||||
return source.data() + idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Logs a message to the global logger, using fmt
|
|
||||||
void FmtLogMessageImpl(Class logClass, Level logLevel, const char *filename,
|
|
||||||
u32 lineNum, const char *function, const char *format,
|
|
||||||
const fmt::format_args& args);
|
|
||||||
|
|
||||||
/// Logs a message without any formatting
|
|
||||||
void NoFmtMessage(Class logClass, Level logLevel, const std::string &message);
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void FmtLogMessage(Class logClass, Level logLevel, const char *filename, u32 lineNum,
|
|
||||||
const char *function, const char *format, const Args&... args) {
|
|
||||||
FmtLogMessageImpl(logClass, logLevel, filename, lineNum, function, format,
|
|
||||||
fmt::make_format_args(args...));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Log
|
|
||||||
} // namespace Base
|
|
||||||
|
|
||||||
// Define the fmt lib macros
|
|
||||||
#define LOG_GENERIC(logClass, logLevel, ...) \
|
|
||||||
Base::Log::FmtLogMessage(logClass, logLevel, Base::Log::TrimSourcePath(__FILE__), \
|
|
||||||
__LINE__, __func__, __VA_ARGS__)
|
|
||||||
#ifdef DEBUG_BUILD
|
|
||||||
#define LOG_TRACE(logClass, ...) \
|
|
||||||
if (Config::log.debugOnly) \
|
|
||||||
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Trace, \
|
|
||||||
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
|
|
||||||
__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define LOG_TRACE(logClass, ...) ;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef DEBUG_BUILD
|
|
||||||
#define LOG_DEBUG(logClass, ...) \
|
|
||||||
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Debug, \
|
|
||||||
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
|
|
||||||
__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define LOG_DEBUG(logClass, ...) ;
|
|
||||||
#endif
|
|
||||||
#define LOG_INFO(logClass, ...) \
|
|
||||||
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Info, \
|
|
||||||
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
|
|
||||||
__VA_ARGS__)
|
|
||||||
#define LOG_WARNING(logClass, ...) \
|
|
||||||
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Warning, \
|
|
||||||
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
|
|
||||||
__VA_ARGS__)
|
|
||||||
#define LOG_ERROR(logClass, ...) \
|
|
||||||
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Error, \
|
|
||||||
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
|
|
||||||
__VA_ARGS__)
|
|
||||||
#define LOG_CRITICAL(logClass, ...) \
|
|
||||||
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Critical, \
|
|
||||||
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
|
|
||||||
__VA_ARGS__)
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include "LogTypes.h"
|
|
||||||
|
|
||||||
namespace Base {
|
|
||||||
namespace Log {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A log entry. Log entries are store in a structured format to permit more varied output
|
|
||||||
* formatting on different frontends, as well as facilitating filtering and aggregation.
|
|
||||||
*/
|
|
||||||
struct Entry {
|
|
||||||
std::chrono::microseconds timestamp = {};
|
|
||||||
Class logClass = {};
|
|
||||||
Level logLevel = {};
|
|
||||||
const char *filename = nullptr;
|
|
||||||
u32 lineNum = 0;
|
|
||||||
std::string function = {};
|
|
||||||
std::string message = {};
|
|
||||||
bool formatted = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Log
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/Types.h"
|
|
||||||
|
|
||||||
namespace Base {
|
|
||||||
namespace Log {
|
|
||||||
|
|
||||||
/// Specifies the severity or level of detail of the log message
|
|
||||||
enum class Level : const u8 {
|
|
||||||
Trace, ///< Extremely detailed and repetitive debugging information that is likely to pollute logs
|
|
||||||
Debug, ///< Less detailed debugging information
|
|
||||||
Info, ///< Status information from important points during execution
|
|
||||||
Warning, ///< Minor or potential problems found during execution of a task
|
|
||||||
Error, ///< Major problems found during execution of a task that prevent it from being completed
|
|
||||||
Critical, ///< Major problems during execution that threaten the stability of the entire application
|
|
||||||
Guest, ///< Output from the guest system
|
|
||||||
Count ///< Total number of logging levels
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Specifies the sub-system that generated the log message
|
|
||||||
*
|
|
||||||
* @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in
|
|
||||||
* filtercpp
|
|
||||||
*/
|
|
||||||
enum class Class : const u8 {
|
|
||||||
Log, // Messages about the log system itself
|
|
||||||
Base, // System base routines: FS, logging, etc
|
|
||||||
Base_Filesystem, // Filesystem messages
|
|
||||||
Config, // Emulator configuration (including commandline)
|
|
||||||
Debug, // Debugging tools
|
|
||||||
System, // Base System messages
|
|
||||||
Render, // OpenGL and Window messages
|
|
||||||
ARM,
|
|
||||||
PROBE,
|
|
||||||
MMIO_S1,
|
|
||||||
Memory,
|
|
||||||
Count // Total number of logging classes
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Log
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#include "common/Config.h"
|
|
||||||
|
|
||||||
#include "TextFormatter.h"
|
|
||||||
|
|
||||||
#include "Filter.h"
|
|
||||||
#include "LogEntry.h"
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace Base {
|
|
||||||
namespace Log {
|
|
||||||
|
|
||||||
std::string FormatLogMessage(const Entry &entry) {
|
|
||||||
const char *className = GetLogClassName(entry.logClass);
|
|
||||||
const char *levelName = GetLevelName(entry.logLevel);
|
|
||||||
|
|
||||||
if (Config::isLogAdvanced() && entry.filename) {
|
|
||||||
return fmt::format("[{}] <{}> {}:{}:{}: {}", className, levelName, entry.filename,
|
|
||||||
entry.function, entry.lineNum, entry.message);
|
|
||||||
} else {
|
|
||||||
return fmt::format("[{}] <{}> {}", className, levelName, entry.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ESC "\x1b"
|
|
||||||
void PrintMessage(const std::string &color, const Entry &entry) {
|
|
||||||
std::string msg = entry.formatted ? FormatLogMessage(entry) : entry.message;
|
|
||||||
const std::string str = color + msg.append(ESC "[0m") + (entry.formatted ? "\n" : "");
|
|
||||||
fputs(str.c_str(), stdout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintColoredMessage(const Entry &entry) {
|
|
||||||
// NOTE: Custom colors can be achieved
|
|
||||||
// std::format("\x1b[{};2;{};{};{}m", color.bg ? 48 : 38, color.r, color.g, color.b)
|
|
||||||
const char *color = "";
|
|
||||||
switch (entry.logLevel) {
|
|
||||||
case Level::Trace: // Grey
|
|
||||||
color = ESC "[1;30m";
|
|
||||||
break;
|
|
||||||
case Level::Debug: // Cyan
|
|
||||||
color = ESC "[0;36m";
|
|
||||||
break;
|
|
||||||
case Level::Info: // Bright gray
|
|
||||||
color = ESC "[0;37m";
|
|
||||||
break;
|
|
||||||
case Level::Warning: // Bright yellow
|
|
||||||
color = ESC "[1;33m";
|
|
||||||
break;
|
|
||||||
case Level::Error: // Bright red
|
|
||||||
color = ESC "[1;31m";
|
|
||||||
break;
|
|
||||||
case Level::Critical: // Bright magenta
|
|
||||||
color = ESC "[1;35m";
|
|
||||||
break;
|
|
||||||
case Level::Guest: // Green
|
|
||||||
color = ESC "[0;92m";
|
|
||||||
break;
|
|
||||||
case Level::Count:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintMessage(color, entry);
|
|
||||||
}
|
|
||||||
#undef ESC
|
|
||||||
|
|
||||||
} // namespace Log
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace Base {
|
|
||||||
namespace Log {
|
|
||||||
|
|
||||||
struct Entry;
|
|
||||||
|
|
||||||
/// Formats a log entry into the provided text buffer.
|
|
||||||
std::string FormatLogMessage(const Entry &entry);
|
|
||||||
|
|
||||||
/// Formats and prints a log entry to stderr.
|
|
||||||
void PrintMessage(const std::string &color, const Entry &entry);
|
|
||||||
|
|
||||||
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
|
|
||||||
void PrintColoredMessage(const Entry &entry);
|
|
||||||
|
|
||||||
} // namespace Log
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
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 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)
|
|
||||||
} FileInfo;
|
|
||||||
|
|
||||||
constexpr auto FW_DIR = "firmware";
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Returns a fs::path object containing the current '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);
|
|
||||||
|
|
||||||
// Sets the current Path for a given PathType.
|
|
||||||
void SetUserPath(PathType user_path, const fs::path& new_path);
|
|
||||||
|
|
||||||
} // namespace FS
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,385 +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 <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
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <Windows.h>
|
|
||||||
#else
|
|
||||||
#include <filesystem>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
} // namespace Base
|
|
||||||
|
|
@ -1,228 +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 <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
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
// Copyright 2025 Xenon Emulator Project. All rights reserved.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fmt/format.h>
|
|
||||||
|
|
||||||
#include "Arch.h"
|
|
||||||
|
|
||||||
// Signed
|
|
||||||
using s8 = signed char;
|
|
||||||
using s16 = signed short;
|
|
||||||
using s32 = signed int;
|
|
||||||
using sl32 = signed long;
|
|
||||||
using s64 = signed long long;
|
|
||||||
|
|
||||||
// Unsigned
|
|
||||||
using u8 = unsigned char;
|
|
||||||
using u16 = unsigned short;
|
|
||||||
using u32 = unsigned int;
|
|
||||||
using ul32 = unsigned long;
|
|
||||||
using u64 = unsigned long long;
|
|
||||||
|
|
||||||
using uptr = uintptr_t;
|
|
||||||
|
|
||||||
// Floating point
|
|
||||||
using f32 = float;
|
|
||||||
using f64 = double;
|
|
||||||
|
|
||||||
// Function pointer
|
|
||||||
template <typename 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""_KiB(const u64 x)
|
|
||||||
{
|
|
||||||
return 1024ULL * 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""_GB(const u64 x)
|
|
||||||
{
|
|
||||||
return 1000_MB * x;
|
|
||||||
}
|
|
||||||
[[nodiscard]] constexpr u64 operator""_GiB(const u64 x)
|
|
||||||
{
|
|
||||||
return 1024_MiB * x;
|
|
||||||
}
|
|
||||||
|
|
@ -1,673 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2012 PPSSPP Project
|
|
||||||
// SPDX-FileCopyrightText: 2012 Dolphin Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
// Official git repository and contact information can be found at
|
|
||||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#include <cstdlib>
|
|
||||||
#endif
|
|
||||||
#include <bit>
|
|
||||||
#include <cstring>
|
|
||||||
#include <type_traits>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace Common
|
|
||||||
{
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
[[nodiscard]] inline u16 swap16(u16 data) noexcept
|
|
||||||
{
|
|
||||||
return _byteswap_ushort(data);
|
|
||||||
}
|
|
||||||
[[nodiscard]] inline u32 swap32(u32 data) noexcept
|
|
||||||
{
|
|
||||||
return _byteswap_ulong(data);
|
|
||||||
}
|
|
||||||
[[nodiscard]] inline u64 swap64(u64 data) noexcept
|
|
||||||
{
|
|
||||||
return _byteswap_uint64(data);
|
|
||||||
}
|
|
||||||
#elif defined(__clang__) || defined(__GNUC__)
|
|
||||||
#if defined(__Bitrig__) || defined(__OpenBSD__)
|
|
||||||
// redefine swap16, swap32, swap64 as inline functions
|
|
||||||
#undef swap16
|
|
||||||
#undef swap32
|
|
||||||
#undef swap64
|
|
||||||
#endif
|
|
||||||
[[nodiscard]] inline u16 swap16(u16 data) noexcept
|
|
||||||
{
|
|
||||||
return __builtin_bswap16(data);
|
|
||||||
}
|
|
||||||
[[nodiscard]] inline u32 swap32(u32 data) noexcept
|
|
||||||
{
|
|
||||||
return __builtin_bswap32(data);
|
|
||||||
}
|
|
||||||
[[nodiscard]] inline u64 swap64(u64 data) noexcept
|
|
||||||
{
|
|
||||||
return __builtin_bswap64(data);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Generic implementation.
|
|
||||||
[[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 u64 swap64(u64 data) noexcept
|
|
||||||
{
|
|
||||||
return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) |
|
|
||||||
((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) |
|
|
||||||
((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) |
|
|
||||||
((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[[nodiscard]] inline float swapf(float f) noexcept
|
|
||||||
{
|
|
||||||
static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t.");
|
|
||||||
|
|
||||||
u32 value;
|
|
||||||
std::memcpy(&value, &f, sizeof(u32));
|
|
||||||
|
|
||||||
value = swap32(value);
|
|
||||||
std::memcpy(&f, &value, sizeof(u32));
|
|
||||||
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] inline double swapd(double f) noexcept
|
|
||||||
{
|
|
||||||
static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t.");
|
|
||||||
|
|
||||||
u64 value;
|
|
||||||
std::memcpy(&value, &f, sizeof(u64));
|
|
||||||
|
|
||||||
value = swap64(value);
|
|
||||||
std::memcpy(&f, &value, sizeof(u64));
|
|
||||||
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // Namespace Common
|
|
||||||
|
|
||||||
template <typename T, typename F>
|
|
||||||
struct swap_struct_t
|
|
||||||
{
|
|
||||||
using swapped_t = swap_struct_t;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
T value;
|
|
||||||
|
|
||||||
static T swap(T v) { return F::swap(v); }
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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()); }
|
|
||||||
|
|
||||||
// +v
|
|
||||||
swapped_t operator+() const { return +swap(); }
|
|
||||||
// -v
|
|
||||||
swapped_t operator-() const { return -swap(); }
|
|
||||||
|
|
||||||
// v / 5
|
|
||||||
swapped_t operator/(const swapped_t& i) const { return swap() / i.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
swapped_t operator/(const S& i) const
|
|
||||||
{
|
|
||||||
return swap() / i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// v * 5
|
|
||||||
swapped_t operator*(const swapped_t& i) const { return swap() * i.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
swapped_t operator*(const S& i) const
|
|
||||||
{
|
|
||||||
return swap() * i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// v + 5
|
|
||||||
swapped_t operator+(const swapped_t& i) const { return swap() + i.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
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(); }
|
|
||||||
template <typename S>
|
|
||||||
swapped_t operator-(const S& i) const
|
|
||||||
{
|
|
||||||
return swap() - static_cast<T>(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// v += 5
|
|
||||||
swapped_t& operator+=(const swapped_t& i)
|
|
||||||
{
|
|
||||||
value = swap(swap() + i.swap());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
template <typename S>
|
|
||||||
swapped_t& operator+=(const S& i)
|
|
||||||
{
|
|
||||||
value = swap(swap() + static_cast<T>(i));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
// v -= 5
|
|
||||||
swapped_t& operator-=(const swapped_t& i)
|
|
||||||
{
|
|
||||||
value = swap(swap() - i.swap());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
template <typename S>
|
|
||||||
swapped_t& operator-=(const S& i)
|
|
||||||
{
|
|
||||||
value = swap(swap() - static_cast<T>(i));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ++v
|
|
||||||
swapped_t& operator++()
|
|
||||||
{
|
|
||||||
value = swap(swap() + 1);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
// --v
|
|
||||||
swapped_t& operator--()
|
|
||||||
{
|
|
||||||
value = swap(swap() - 1);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// v++
|
|
||||||
swapped_t operator++(int)
|
|
||||||
{
|
|
||||||
swapped_t old = *this;
|
|
||||||
value = swap(swap() + 1);
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
// v--
|
|
||||||
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(); }
|
|
||||||
template <typename S>
|
|
||||||
bool operator==(const S& i) const
|
|
||||||
{
|
|
||||||
return swap() == i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// v != i
|
|
||||||
bool operator!=(const swapped_t& i) const { return swap() != i.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
bool operator!=(const S& i) const
|
|
||||||
{
|
|
||||||
return swap() != i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// v > i
|
|
||||||
bool operator>(const swapped_t& i) const { return swap() > i.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
bool operator>(const S& i) const
|
|
||||||
{
|
|
||||||
return swap() > i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// v < i
|
|
||||||
bool operator<(const swapped_t& i) const { return swap() < i.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
bool operator<(const S& i) const
|
|
||||||
{
|
|
||||||
return swap() < i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// v >= i
|
|
||||||
bool operator>=(const swapped_t& i) const { return swap() >= i.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
bool operator>=(const S& i) const
|
|
||||||
{
|
|
||||||
return swap() >= i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// v <= i
|
|
||||||
bool operator<=(const swapped_t& i) const { return swap() <= i.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
bool operator<=(const S& i) const
|
|
||||||
{
|
|
||||||
return swap() <= i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// logical
|
|
||||||
swapped_t operator!() const { return !swap(); }
|
|
||||||
|
|
||||||
// bitmath
|
|
||||||
swapped_t operator~() const { return ~swap(); }
|
|
||||||
|
|
||||||
swapped_t operator&(const swapped_t& b) const { return swap() & b.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
swapped_t operator&(const S& b) const
|
|
||||||
{
|
|
||||||
return swap() & b;
|
|
||||||
}
|
|
||||||
swapped_t& operator&=(const swapped_t& b)
|
|
||||||
{
|
|
||||||
value = swap(swap() & b.swap());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
template <typename S>
|
|
||||||
swapped_t& operator&=(const S b)
|
|
||||||
{
|
|
||||||
value = swap(swap() & b);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
swapped_t operator|(const swapped_t& b) const { return swap() | b.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
swapped_t operator|(const S& b) const
|
|
||||||
{
|
|
||||||
return swap() | b;
|
|
||||||
}
|
|
||||||
swapped_t& operator|=(const swapped_t& b)
|
|
||||||
{
|
|
||||||
value = swap(swap() | b.swap());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
template <typename S>
|
|
||||||
swapped_t& operator|=(const S& b)
|
|
||||||
{
|
|
||||||
value = swap(swap() | b);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
swapped_t operator^(const swapped_t& b) const { return swap() ^ b.swap(); }
|
|
||||||
template <typename S>
|
|
||||||
swapped_t operator^(const S& b) const
|
|
||||||
{
|
|
||||||
return swap() ^ b;
|
|
||||||
}
|
|
||||||
swapped_t& operator^=(const swapped_t& b)
|
|
||||||
{
|
|
||||||
value = swap(swap() ^ b.swap());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
template <typename S>
|
|
||||||
swapped_t& operator^=(const S& b)
|
|
||||||
{
|
|
||||||
value = swap(swap() ^ b);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S>
|
|
||||||
swapped_t operator<<(const S& b) const
|
|
||||||
{
|
|
||||||
return swap() << b;
|
|
||||||
}
|
|
||||||
template <typename S>
|
|
||||||
swapped_t& operator<<=(const S& b) const
|
|
||||||
{
|
|
||||||
value = swap(swap() << b);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S>
|
|
||||||
swapped_t operator>>(const S& b) const
|
|
||||||
{
|
|
||||||
return swap() >> b;
|
|
||||||
}
|
|
||||||
template <typename S>
|
|
||||||
swapped_t& operator>>=(const S& b) const
|
|
||||||
{
|
|
||||||
value = swap(swap() >> b);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Member
|
|
||||||
/** todo **/
|
|
||||||
|
|
||||||
// Arithmetic
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend S operator+(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend S operator-(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend S operator/(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend S operator*(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend S operator%(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
// Arithmetic + assignments
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend S operator+=(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend S operator-=(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
// Bitmath
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend S operator&(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
// Comparison
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend bool operator<(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend bool operator>(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend bool operator<=(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend bool operator>=(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend bool operator!=(const S& p, const swapped_t v);
|
|
||||||
|
|
||||||
template <typename S, typename T2, typename F2>
|
|
||||||
friend bool operator==(const S& p, const swapped_t v);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Arithmetic
|
|
||||||
template <typename S, typename T, typename F>
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return i - v.swap();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S, typename T, typename F>
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return i * v.swap();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S, typename T, typename F>
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
i += v.swap();
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S, typename T, typename F>
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return i & v.swap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comparison
|
|
||||||
template <typename S, typename T, typename F>
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return p > v.swap();
|
|
||||||
}
|
|
||||||
template <typename S, typename T, typename F>
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return p >= v.swap();
|
|
||||||
}
|
|
||||||
template <typename S, typename T, typename F>
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return p == v.swap();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
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)); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
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)); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct swap_double_t
|
|
||||||
{
|
|
||||||
static T swap(T x) { return static_cast<T>(Common::swapd(x)); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct swap_enum_t
|
|
||||||
{
|
|
||||||
static_assert(std::is_enum_v<T>);
|
|
||||||
using base = std::underlying_type_t<T>;
|
|
||||||
|
|
||||||
public:
|
|
||||||
swap_enum_t() = default;
|
|
||||||
swap_enum_t(const T& v) : value(swap(v)) {}
|
|
||||||
|
|
||||||
swap_enum_t& operator=(const T& v)
|
|
||||||
{
|
|
||||||
value = swap(v);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator T() const { return swap(value); }
|
|
||||||
|
|
||||||
explicit operator base() const { return static_cast<base>(swap(value)); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
T value{};
|
|
||||||
// clang-format off
|
|
||||||
using swap_t = std::conditional_t<
|
|
||||||
std::is_same_v<base, u16>, swap_16_t<u16>, std::conditional_t<
|
|
||||||
std::is_same_v<base, s16>, swap_16_t<s16>, std::conditional_t<
|
|
||||||
std::is_same_v<base, u32>, swap_32_t<u32>, std::conditional_t<
|
|
||||||
std::is_same_v<base, s32>, swap_32_t<s32>, std::conditional_t<
|
|
||||||
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))); }
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// KeepTag specializations
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct AddEndian<T, KeepTag>
|
|
||||||
{
|
|
||||||
using type = T;
|
|
||||||
};
|
|
||||||
|
|
||||||
// SwapTag specializations
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<u8, SwapTag>
|
|
||||||
{
|
|
||||||
using type = u8;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<u16, SwapTag>
|
|
||||||
{
|
|
||||||
using type = swap_struct_t<u16, swap_16_t<u16>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<u32, SwapTag>
|
|
||||||
{
|
|
||||||
using type = swap_struct_t<u32, swap_32_t<u32>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<u64, SwapTag>
|
|
||||||
{
|
|
||||||
using type = swap_struct_t<u64, swap_64_t<u64>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<s8, SwapTag>
|
|
||||||
{
|
|
||||||
using type = s8;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<s16, SwapTag>
|
|
||||||
{
|
|
||||||
using type = swap_struct_t<s16, swap_16_t<s16>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<s32, SwapTag>
|
|
||||||
{
|
|
||||||
using type = swap_struct_t<s32, swap_32_t<s32>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<s64, SwapTag>
|
|
||||||
{
|
|
||||||
using type = swap_struct_t<s64, swap_64_t<s64>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<float, SwapTag>
|
|
||||||
{
|
|
||||||
using type = swap_struct_t<float, swap_float_t<float>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AddEndian<double, SwapTag>
|
|
||||||
{
|
|
||||||
using type = swap_struct_t<double, swap_double_t<double>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct AddEndian<T, SwapTag>
|
|
||||||
{
|
|
||||||
static_assert(std::is_enum_v<T>);
|
|
||||||
using type = swap_enum_t<T>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Alias LETag/BETag as KeepTag/SwapTag depending on the system
|
|
||||||
using LETag = std::conditional_t<std::endian::native == std::endian::little, KeepTag, SwapTag>;
|
|
||||||
using BETag = std::conditional_t<std::endian::native == std::endian::big, KeepTag, SwapTag>;
|
|
||||||
|
|
||||||
// Aliases for LE types
|
|
||||||
using u16_le = AddEndian<u16, LETag>::type;
|
|
||||||
using u32_le = AddEndian<u32, LETag>::type;
|
|
||||||
using u64_le = AddEndian<u64, LETag>::type;
|
|
||||||
|
|
||||||
using s16_le = AddEndian<s16, LETag>::type;
|
|
||||||
using s32_le = AddEndian<s32, LETag>::type;
|
|
||||||
using s64_le = AddEndian<s64, LETag>::type;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using enum_le = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, LETag>::type>;
|
|
||||||
|
|
||||||
using float_le = AddEndian<float, LETag>::type;
|
|
||||||
using double_le = AddEndian<double, LETag>::type;
|
|
||||||
|
|
||||||
// Aliases for BE types
|
|
||||||
using u16_be = AddEndian<u16, BETag>::type;
|
|
||||||
using u32_be = AddEndian<u32, BETag>::type;
|
|
||||||
using u64_be = AddEndian<u64, BETag>::type;
|
|
||||||
|
|
||||||
using s16_be = AddEndian<s16, BETag>::type;
|
|
||||||
using s32_be = AddEndian<s32, BETag>::type;
|
|
||||||
using s64_be = AddEndian<s64, BETag>::type;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using enum_be = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, BETag>::type>;
|
|
||||||
|
|
||||||
using float_be = AddEndian<float, BETag>::type;
|
|
||||||
using double_be = AddEndian<double, BETag>::type;
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue