mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-11 07:36:57 +00:00
Merge branch 'arm64'
Ronald Caesar (7):
common: Implement logging framework
common: add assert framework dedign doc
common: Implement assertion framework
Add Pound log and assert macros in every system, and fixed all linux compiler warnings
common: Remove unused files
build: remove commented code
Remove outdated contributing suggestions
This commit is contained in:
commit
5b0aaf5fc4
41 changed files with 302 additions and 4017 deletions
|
|
@ -10,12 +10,6 @@ if (NOT CMAKE_BUILD_TYPE)
|
|||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
|
||||
# Enable asserts in release mode. Windows throws a metric fuck ton of compiler
|
||||
# errors when asserts are disabled.
|
||||
#if (UNIX)
|
||||
# string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
#endif()
|
||||
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_C_STANDARD_REQUIRED TRUE)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ by current and future programmers working on this project.
|
|||
|
||||
## Style Conventions
|
||||
|
||||
Refer to `main.cpp` and the GUI folder for examples of proper code styling. Here are some specific rules:
|
||||
Refer to `main.cpp` and the kvm folder for examples of proper code styling. Here are some specific rules:
|
||||
|
||||
1. **Constant First in Equality Tests**:
|
||||
```c
|
||||
|
|
@ -77,15 +77,3 @@ Refer to `main.cpp` and the GUI folder for examples of proper code styling. Here
|
|||
// Todo(GloriousTaco:memory): Create a custom allocator.
|
||||
```
|
||||
|
||||
## Contributing Suggestions
|
||||
|
||||
For those who want to contribute now, we suggest rewriting:
|
||||
|
||||
- Base::Log::Initialize()
|
||||
- Base::Log::Start()
|
||||
- Config::load()
|
||||
|
||||
in main() using the principles outlined in this document.
|
||||
|
||||
If you're familiar with memory allocators, please consider implementing std::allocator for our custom memory allocator
|
||||
in core/memory/arena.h. This would allow us to manage the memory of C++ standard types like std::vector.
|
||||
|
|
|
|||
|
|
@ -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,15 +1,8 @@
|
|||
add_library(common STATIC)
|
||||
|
||||
target_sources(common PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Config.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/IoFile.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PathUtil.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/StringUtil.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Thread.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/logging.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Logging/Backend.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Logging/Filter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Logging/TextFormatter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/passert.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(common PUBLIC fmt::fmt)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
153
src/common/DESIGN_DOC_ASSERT_FRAMEWORK.md
Normal file
153
src/common/DESIGN_DOC_ASSERT_FRAMEWORK.md
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
## **Design Document: Core Assertion Subsystem**
|
||||
|
||||
**Author:** GloriousTacoo, Lead Developer
|
||||
**Status:** FINAL
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-09-20
|
||||
|
||||
*Disclaimer: This document was written by AI. I'm not a good technical writer.riter**
|
||||
|
||||
### **1. Problem Statement**
|
||||
|
||||
We require a rigorous, inescapable, and informative assertion framework that codifies our fail-fast philosophy. An invalid program state must lead to immediate, loud, and unrecoverable termination. There is no other acceptable outcome. This system will serve as the bedrock of the PVM's stability, ensuring that programmer errors are caught and exposed, not hidden.
|
||||
|
||||
### **2. Glossary**
|
||||
|
||||
* **Assertion:** A predicate within the code that declares an invariant—a condition that must be true for the program to be considered correct. Its failure indicates a bug.
|
||||
* **Predicate:** The boolean expression evaluated by an assertion.
|
||||
* **Assertion Failure:** The event triggered when an assertion's predicate evaluates to false. This signifies a critical, non-recoverable programmer error.
|
||||
* **Fail-Fast:** The core design principle of this entire project. The system will not attempt to limp along in a corrupted state. At the first sign of an invalid condition, it will terminate.
|
||||
* **Unrecoverable Error:** Any state from which continued correct operation cannot be guaranteed. By definition, any assertion failure signals such a state.
|
||||
|
||||
### **3. Breaking Changes**
|
||||
|
||||
* All usage of the standard library `<assert.h>` is hereby deprecated and forbidden.
|
||||
* A pre-commit hook will be integrated into the repository. It will scan for the token `assert(` and will reject any commit that contains it. This is not a suggestion. Your code will not be accepted if it uses the standard macro.
|
||||
* All existing modules must be migrated to the new `PVM_ASSERT` API. This is a one-time, mandatory refactoring effort. All pull requests will be blocked until this migration is complete.
|
||||
|
||||
### **4. Success Criteria**
|
||||
|
||||
* **Adoption:** 100% of all precondition, postcondition, and invariant checks within the PVM codebase will utilize the new assertion framework. Zero exceptions.
|
||||
* **Information Richness:** A failed assertion must produce a diagnostic message on `stderr` containing, at minimum: the full text of the failed expression, the source file name, the line number, the enclosing function name, and an optional, developer-supplied formatted message.
|
||||
* **Inescapability:** The assertion framework must be impossible to disable. The state of the `NDEBUG` macro will have no effect. Assertions are a permanent, non-optional feature of the executable in all build configurations.
|
||||
* **Termination Guarantee:** A failed assertion must, without exception, result in immediate program termination via a call to `abort()`. No cleanup, no unwinding, no second chances. The process will stop *now*.
|
||||
|
||||
### **5. Proposed Design**
|
||||
|
||||
This framework is built upon an unyielding philosophy. You will internalize it.
|
||||
|
||||
* **Tenet 1: Bugs Are Defects.** An assertion failure is not a runtime error. It is proof of a flaw in the logic of the code. It is a bug that you, the developer, introduced. The purpose of this system is to expose these flaws.
|
||||
* **Tenet 2: Failure is Absolute.** There is no "graceful" way to handle a broken invariant. The only correct action is immediate termination to prevent the propagation of a corrupt state. The output must be loud, clear, and provide maximum context to diagnose the defect.
|
||||
* **Tenet 3: Correctness is Not a "Debug" Feature.** Assertions are always on. They are an integral part of the executable's logic and its contract for safe operation. Any performance argument against this is invalid and will be dismissed. The cost of a passing check is infinitesimal; the cost of a latent bug is mission failure.
|
||||
* **Tenet 4: Clarity is Mandatory.** The API will be simple, but its use requires discipline. You will provide context in your assertions. A naked expression is often not enough to explain *why* a condition must be true.
|
||||
|
||||
The API will consist of three macros. Use them correctly.
|
||||
|
||||
`PVM_ASSERT(expression)`
|
||||
`PVM_ASSERT_MSG(expression, fmt, ...)`
|
||||
`PVM_UNREACHABLE()`
|
||||
|
||||
### **6. Technical Design**
|
||||
|
||||
The implementation will be brutally simple and effective.
|
||||
|
||||
1. **Frontend Macros (`common/assert.h`):**
|
||||
* These macros are the complete public API. There are no other entry points.
|
||||
* `PVM_ASSERT(expression)`: Expands to an `if` statement. If `(expression)` is false, it calls the internal failure handler, passing the stringified expression (`#expression`), file, line, and function name.
|
||||
```c
|
||||
do {
|
||||
if (!(expression)) {
|
||||
pound_internal_assert_fail(__FILE__, __LINE__, __func__, #expression, NULL);
|
||||
}
|
||||
} while(0)
|
||||
```
|
||||
* `PVM_ASSERT_MSG(expression, fmt, ...)`: Similar to the above, but if the check fails, it first formats the developer-supplied message into a temporary buffer and passes that buffer to the failure handler. The formatting cost is **only** paid on failure. There is no excuse for not using this macro to provide context for complex invariants.
|
||||
* `PVM_UNREACHABLE()`: This is not a suggestion. It is a declaration that a code path is logically impossible. It expands to a direct call to the failure handler with a static message like "Unreachable code executed". If this assertion ever fires, the logical model of the function is wrong.
|
||||
|
||||
2. **Failure Handler (`common/assert.c`):**
|
||||
* A single, internal C function: `void pound_internal_assert_fail(const char* file, int line, const char* func, const char* expr_str, const char* user_msg)`.
|
||||
* This function is marked with `_Noreturn` (or `__attribute__((noreturn))`). It will never return to the caller. The compiler will know this.
|
||||
* It will lock a mutex to prevent garbled output if two threads fail simultaneously (a near-impossibility, but we engineer for correctness).
|
||||
* It will format a single, comprehensive, multi-line error message to `stderr`. The format is fixed and not subject to debate:
|
||||
```
|
||||
================================================================================
|
||||
PVM ASSERTION FAILURE
|
||||
================================================================================
|
||||
File: src/kvm/mmu.cpp
|
||||
Line: 521
|
||||
Function: mmu_translate_va
|
||||
Expression: page_table->entry[idx].is_valid()
|
||||
Message: Attempted to translate VA 0xDEADBEEF but page table entry was invalid.
|
||||
================================================================================
|
||||
Terminating program via abort(). Core dump expected.
|
||||
```
|
||||
* After printing, it will immediately call `abort()`.
|
||||
|
||||
### **7. Components**
|
||||
|
||||
* **Application Modules (kvm, frontend, etc.):** These are the components whose logic is being enforced. They will include `common/assert.h` and use the macros to state invariants.
|
||||
* **Assertion Core (`common/assert`):** This small, self-contained module provides the macros and the single failure handler function. It has no purpose other than to enforce correctness and terminate the program.
|
||||
* **Build System:** Will be configured to enforce the ban on `<assert.h>`.
|
||||
|
||||
### **8. Dependencies**
|
||||
|
||||
* **C Standard Library:** For `fprintf`, `stderr`, and `abort`.
|
||||
|
||||
### **9. Major Risks & Mitigations**
|
||||
|
||||
* **Risk 1: Performance Complacency.** A developer may write a computationally expensive operation inside an assertion's predicate.
|
||||
* **Mitigation:** This is a failure of code review, not a failure of the framework. The performance of a *passing* assertion (a boolean check) is negligible and is the accepted cost of safety. The performance of a *failing* assertion is irrelevant, as the program ceases to exist. Code reviewers are directed to reject any assertion that performs non-trivial work. Assertions check state; they do not compute it.
|
||||
* **Risk 2: Confusion with Error Handling.** A developer might use `PVM_ASSERT` to handle recoverable, runtime errors (e.g., failed I/O, invalid user input).
|
||||
* **Mitigation:** Documentation and merciless code review. This will be made explicitly clear: **Assertions are for bugs.** They are for conditions that, if false, prove the program's logic is flawed. Runtime errors must be handled through status codes or other proper error-handling mechanisms. Any pull request that misuses assertions for error handling will be rejected immediately.
|
||||
|
||||
### **10. Out of Scope**
|
||||
|
||||
* **Error Recovery:** The word "recovery" does not apply to an assertion failure. It is, by definition, an unrecoverable state.
|
||||
* **Crash Reporting Infrastructure:** This framework's responsibility ends at calling `abort()`. The generation of a core dump and any subsequent analysis is the responsibility of the execution environment, not this library. We provide the trigger; other systems can handle the post-mortem.
|
||||
* **Any Form of "Gentle" Shutdown:** Resources will not be freed. Files will not be flushed. Sockets will not be closed. Such actions would be performed by a program in a corrupt state and cannot be trusted. `abort()` is the only clean exit.
|
||||
|
||||
### **12. Alternatives Considered**
|
||||
|
||||
* **Alternative #1: Use standard `assert.h` and globally `#undef NDEBUG`.**
|
||||
* **Pros:** Requires no new code.
|
||||
* **Cons:** The output is spartan and implementation-defined. It provides no mechanism for custom, formatted messages. It creates a dependency on a build flag that could be accidentally overridden by a sub-project or complex build configuration. It is fragile.
|
||||
* **Reasons Discarded:** Insufficiently informative and not robust enough for Pound. We will control our own destiny, not hope a build flag is set correctly.
|
||||
|
||||
* **Alternative #2: Ad-hoc `if (condition) { fprintf(...); abort(); }`.**
|
||||
* **Pros:** None. This is engineering malpractice.
|
||||
* **Cons:** Verbose, error-prone, guarantees inconsistent output formats, and omits critical context like the stringified expression and function name unless manually added each time. It is a recipe for unmaintainable chaos.
|
||||
* **Reasons Discarded:** This is not a real alternative. It is an anti-pattern that this framework is designed to eradicate.
|
||||
|
||||
### **13. Appendix**
|
||||
#### **Example Usage and Output**
|
||||
|
||||
**Code:**
|
||||
```c
|
||||
// in some function in memory_manager.c
|
||||
void* memory_manager_alloc(struct mmu* mmu, size_t bytes) {
|
||||
PVM_ASSERT_MSG(mmu != NULL, "MMU context must not be null.");
|
||||
PVM_ASSERT_MSG(bytes > 0 && bytes < MAX_ALLOC_SIZE, "Invalid allocation size requested: %zu bytes", bytes);
|
||||
|
||||
// ... logic ...
|
||||
|
||||
// This case should be handled by prior logic
|
||||
if (page_is_full) {
|
||||
PVM_UNREACHABLE();
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
```
|
||||
|
||||
**Sample output from a failed assertion:**
|
||||
```
|
||||
================================================================================
|
||||
PVM ASSERTION FAILURE
|
||||
================================================================================
|
||||
File: src/core/memory_manager.c
|
||||
Line: 84
|
||||
Function: memory_manager_alloc
|
||||
Expression: bytes > 0 && bytes < MAX_ALLOC_SIZE
|
||||
Message: Invalid allocation size requested: 0 bytes
|
||||
================================================================================
|
||||
Terminating program via abort().
|
||||
|
|
@ -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;
|
||||
}
|
||||
44
src/common/passert.cpp
Normal file
44
src/common/passert.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#include "passert.h"
|
||||
#include "stdarg.h"
|
||||
#include "stdio.h"
|
||||
#include "stdlib.h"
|
||||
#include "string.h"
|
||||
|
||||
#define ASSERT_MESSAGE_BUFFER_SIZE 1024
|
||||
|
||||
void pound_internal_assert_fail(const char* file, int line, const char* func, const char* expr_str,
|
||||
const char* user_msg, ...)
|
||||
{
|
||||
char assert_format[] =
|
||||
" \
|
||||
================================================================================ \n \
|
||||
PVM ASSERTION FAILURE \n \
|
||||
================================================================================ \n \
|
||||
File: %s \n \
|
||||
Line: %d \n \
|
||||
Function: %s \n \
|
||||
Expression: %s \n \
|
||||
Message: %s \n \
|
||||
================================================================================ \n \
|
||||
Terminating program via abort(). Core dump expected. \n \
|
||||
";
|
||||
|
||||
char message_str[ASSERT_MESSAGE_BUFFER_SIZE] = {};
|
||||
if (nullptr == user_msg)
|
||||
{
|
||||
(void)strcpy(message_str, "n/a");
|
||||
}
|
||||
else
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, user_msg);
|
||||
(void)vsnprintf(message_str, ASSERT_MESSAGE_BUFFER_SIZE, user_msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
char buffer[ASSERT_MESSAGE_BUFFER_SIZE] = {};
|
||||
(void)snprintf(buffer, ASSERT_MESSAGE_BUFFER_SIZE, assert_format, file, line, func, expr_str, message_str);
|
||||
|
||||
(void)fprintf(stderr, "%s", buffer);
|
||||
abort();
|
||||
}
|
||||
27
src/common/passert.h
Normal file
27
src/common/passert.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef POUND_COMMON_ASSERT_H
|
||||
#define POUND_COMMON_ASSERT_H
|
||||
|
||||
__attribute__((noreturn)) void pound_internal_assert_fail(const char* file, int line, const char* func,
|
||||
const char* expr_str, const char* user_msg, ...);
|
||||
|
||||
#define PVM_ASSERT(expression) \
|
||||
do \
|
||||
{ \
|
||||
if (!(expression)) \
|
||||
{ \
|
||||
pound_internal_assert_fail(__FILE__, __LINE__, __func__, #expression, nullptr, nullptr); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define PVM_ASSERT_MSG(expression, format, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (!(expression)) \
|
||||
{ \
|
||||
pound_internal_assert_fail(__FILE__, __LINE__, __func__, #expression, format __VA_OPT__(,) __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define PVM_UNREACHABLE(...) pound_internal_assert_fail(__FILE__, __LINE__, __func__, "PVM_UNREACHABLE()", "Unreachable code executed", ##__VA_ARGS__);
|
||||
|
||||
#endif // POUND_COMMON_ASSERT_H
|
||||
|
|
@ -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;
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
#include "gui.h"
|
||||
#include "color.h"
|
||||
#include <assert.h>
|
||||
#include "common/Logging/Log.h"
|
||||
#include "common/passert.h"
|
||||
#include "imgui_impl_opengl3_loader.h"
|
||||
|
||||
#define LOG_MODULE "FRONTEND"
|
||||
#include "common/logging.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <imgui_impl_sdl3.h>
|
||||
|
|
@ -16,8 +18,8 @@ static void apply_theme();
|
|||
|
||||
bool gui::window_init(window_t* window, const char* title, int64_t width, int64_t height)
|
||||
{
|
||||
assert(nullptr != window);
|
||||
assert(nullptr != title);
|
||||
PVM_ASSERT(nullptr != window);
|
||||
PVM_ASSERT(nullptr != title);
|
||||
|
||||
bool ret = ::SDL_Init(SDL_INIT_VIDEO);
|
||||
if (false == ret)
|
||||
|
|
@ -119,8 +121,8 @@ void gui::window_destroy(gui::window_t* window)
|
|||
|
||||
bool gui::init_imgui(gui::window_t* main_window)
|
||||
{
|
||||
assert(nullptr != main_window->data);
|
||||
assert(nullptr != main_window->gl_context);
|
||||
PVM_ASSERT(nullptr != main_window->data);
|
||||
PVM_ASSERT(nullptr != main_window->gl_context);
|
||||
|
||||
// Initialize ImGui
|
||||
IMGUI_CHECKVERSION();
|
||||
|
|
@ -137,9 +139,9 @@ bool gui::init_imgui(gui::window_t* main_window)
|
|||
return false;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__ && (__aarch64__)
|
||||
#if defined(__APPLE__) && defined(__aarch64__)
|
||||
ret = ::ImGui_ImplOpenGL3_Init("#version 120");
|
||||
#elif __APPLE__ && (__x86_64__)
|
||||
#elif defined(__APPLE__) && defined(__x86_64__)
|
||||
ret = ::ImGui_ImplOpenGL3_Init("#version 150");
|
||||
#else
|
||||
ret = ::ImGui_ImplOpenGL3_Init("#version 330");
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
#include <imgui.h>
|
||||
#include <math.h>
|
||||
#include "kvm/kvm.h"
|
||||
#include <assert.h>"
|
||||
#include "common/passert.h"
|
||||
|
||||
int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* panel, performance_data_t* data,
|
||||
std::chrono::steady_clock::time_point* last_render)
|
||||
{
|
||||
assert(nullptr != panel);
|
||||
assert(nullptr != data);
|
||||
assert(nullptr != last_render);
|
||||
PVM_ASSERT(nullptr != panel);
|
||||
PVM_ASSERT(nullptr != data);
|
||||
PVM_ASSERT(nullptr != last_render);
|
||||
|
||||
bool is_visible = true;
|
||||
(void)::ImGui::Begin(PANEL_NAME_PERFORMANCE, &is_visible);
|
||||
|
|
@ -25,8 +25,8 @@ int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* pan
|
|||
if (duration.count() >= 100)
|
||||
{
|
||||
// Every 100ms
|
||||
data->fps = data->frame_count * 1000.0f / duration.count();
|
||||
data->frame_time = duration.count() / (float)data->frame_count;
|
||||
data->fps = (float)data->frame_count * 1000.0f / (float)duration.count();
|
||||
data->frame_time = (float)duration.count() / (float)data->frame_count;
|
||||
|
||||
panel->fps_history.push_back(data->fps);
|
||||
panel->frame_time_history.push_back(data->frame_time);
|
||||
|
|
@ -82,7 +82,7 @@ int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* pan
|
|||
|
||||
int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup)
|
||||
{
|
||||
assert(nullptr != show_cpu_result_popup);
|
||||
PVM_ASSERT(nullptr != show_cpu_result_popup);
|
||||
|
||||
bool is_visible = true;
|
||||
(void)::ImGui::Begin(PANEL_NAME_CPU, &is_visible, ImGuiWindowFlags_NoCollapse);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include "arena.h"
|
||||
#include <cassert>
|
||||
#include "common/passert.h"
|
||||
#include <cstring>
|
||||
#ifndef WIN32
|
||||
#include <sys/mman.h>
|
||||
|
|
@ -33,22 +33,22 @@ arena_t arena_init(size_t capacity)
|
|||
|
||||
void* arena_allocate(memory::arena_t* arena, const std::size_t size)
|
||||
{
|
||||
assert(arena != nullptr);
|
||||
assert(arena->size + size <= arena->capacity);
|
||||
PVM_ASSERT(arena != nullptr);
|
||||
PVM_ASSERT(arena->size + size <= arena->capacity);
|
||||
void* const data = static_cast<uint8_t*>(arena->data) + arena->size;
|
||||
arena->size += size;
|
||||
return data;
|
||||
}
|
||||
void arena_reset(memory::arena_t* arena)
|
||||
{
|
||||
assert(nullptr != arena);
|
||||
assert(nullptr != arena->data);
|
||||
PVM_ASSERT(nullptr != arena);
|
||||
PVM_ASSERT(nullptr != arena->data);
|
||||
arena->size = 0;
|
||||
(void)std::memset(arena->data, POISON_PATTERN, arena->capacity);
|
||||
}
|
||||
void arena_free(memory::arena_t* arena)
|
||||
{
|
||||
assert(arena != nullptr);
|
||||
PVM_ASSERT(arena != nullptr);
|
||||
arena->capacity = 0;
|
||||
arena->size = 0;
|
||||
// TODO(GloriousTaco:memory): Replace free with a memory safe alternative.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
#include "guest.h"
|
||||
#include <cassert>
|
||||
#include "common/passert.h"
|
||||
|
||||
namespace pound::kvm::memory
|
||||
{
|
||||
guest_memory_t* guest_memory_create(pound::host::memory::arena_t* arena)
|
||||
{
|
||||
assert(nullptr != arena);
|
||||
assert(nullptr != arena->data);
|
||||
PVM_ASSERT(nullptr != arena);
|
||||
PVM_ASSERT(nullptr != arena->data);
|
||||
|
||||
guest_memory_t* memory = (guest_memory_t*)pound::host::memory::arena_allocate(arena, sizeof(guest_memory_t));
|
||||
size_t ram_size = arena->capacity - arena->size;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#include "kvm.h"
|
||||
#include <cassert>
|
||||
#include "common/Logging/Log.h"
|
||||
#include "guest.h"
|
||||
#include "common/passert.h"
|
||||
#include "host/memory/arena.h"
|
||||
|
||||
#define LOG_MODULE "kvm"
|
||||
#include "common/logging.h"
|
||||
|
||||
namespace pound::kvm
|
||||
{
|
||||
|
||||
|
|
@ -11,7 +13,7 @@ uint8_t kvm_probe(kvm_t* kvm, enum target_type type)
|
|||
{
|
||||
if (type != KVM_TARGET_SWITCH1)
|
||||
{
|
||||
assert(!"Only Switch 1 is supported");
|
||||
PVM_ASSERT_MSG(false, "Only Switch 1 is supported");
|
||||
}
|
||||
kvm->ops = s1_ops;
|
||||
/* Go to targets/switch1/hardware/probe.cpp */
|
||||
|
|
@ -21,11 +23,11 @@ uint8_t kvm_probe(kvm_t* kvm, enum target_type type)
|
|||
|
||||
void take_synchronous_exception(kvm_vcpu_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address)
|
||||
{
|
||||
assert(nullptr != vcpu);
|
||||
PVM_ASSERT(nullptr != vcpu);
|
||||
/* An EC holds 6 bits.*/
|
||||
assert(0 == (exception_class & 11000000));
|
||||
PVM_ASSERT(0 == (exception_class & 11000000));
|
||||
/* An ISS holds 25 bits */
|
||||
assert(0 == (iss & 0xFE000000));
|
||||
PVM_ASSERT(0 == (iss & 0xFE000000));
|
||||
|
||||
vcpu->elr_el1 = vcpu->pc;
|
||||
vcpu->spsr_el1 = vcpu->pstate;
|
||||
|
|
@ -69,11 +71,13 @@ void take_synchronous_exception(kvm_vcpu_t* vcpu, uint8_t exception_class, uint3
|
|||
|
||||
void cpuTest()
|
||||
{
|
||||
#if 0
|
||||
pound::host::memory::arena_t guest_memory_arena = pound::host::memory::arena_init(GUEST_RAM_SIZE);
|
||||
assert(nullptr != guest_memory_arena.data);
|
||||
PVM_ASSERT(nullptr != guest_memory_arena.data);
|
||||
|
||||
memory::guest_memory_t* guest_ram = memory::guest_memory_create(&guest_memory_arena);
|
||||
|
||||
//(void)test_guest_ram_access(guest_ram);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} // namespace pound::kvm
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include "mmio.h"
|
||||
#include <cassert>
|
||||
#include "common/passert.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace pound::kvm::memory
|
||||
|
|
@ -17,11 +17,11 @@ bool mmio_compare_ranges(const mmio_range_t& a, const mmio_range_t& b)
|
|||
|
||||
int8_t mmio_db_register(mmio_db_t* db, const mmio_range_t range, const mmio_handler_t handler)
|
||||
{
|
||||
assert(nullptr != db);
|
||||
assert((db->address_ranges.size() + 1) <= MMIO_REGIONS);
|
||||
PVM_ASSERT(nullptr != db);
|
||||
PVM_ASSERT((db->address_ranges.size() + 1) <= MMIO_REGIONS);
|
||||
|
||||
auto it = std::lower_bound(db->address_ranges.begin(), db->address_ranges.end(), range, mmio_compare_ranges);
|
||||
size_t i = it - db->address_ranges.begin();
|
||||
auto i = it - db->address_ranges.begin();
|
||||
|
||||
/*
|
||||
* Scenario: UART is a current region, TIMER is a new region being
|
||||
|
|
@ -34,7 +34,7 @@ int8_t mmio_db_register(mmio_db_t* db, const mmio_range_t range, const mmio_hand
|
|||
*/
|
||||
if (i > 0)
|
||||
{
|
||||
if (range.gpa_base < db->address_ranges[i - 1].gpa_end)
|
||||
if (range.gpa_base < db->address_ranges[(size_t)i - 1].gpa_end)
|
||||
{
|
||||
return EADDRESS_OVERLAP;
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ int8_t mmio_db_register(mmio_db_t* db, const mmio_range_t range, const mmio_hand
|
|||
*/
|
||||
if (i < db->address_ranges.size())
|
||||
{
|
||||
if (db->address_ranges[i].gpa_base < range.gpa_end)
|
||||
if (db->address_ranges[(size_t)i].gpa_base < range.gpa_end)
|
||||
{
|
||||
return EADDRESS_OVERLAP;
|
||||
}
|
||||
|
|
@ -69,10 +69,10 @@ bool mmio_compare_addresses(const mmio_range_t& a, const mmio_range_t& b)
|
|||
|
||||
int8_t mmio_db_dispatch_write(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len)
|
||||
{
|
||||
assert(nullptr != db);
|
||||
assert(nullptr != kvm);
|
||||
assert(nullptr != data);
|
||||
assert(len > 0);
|
||||
PVM_ASSERT(nullptr != db);
|
||||
PVM_ASSERT(nullptr != kvm);
|
||||
PVM_ASSERT(nullptr != data);
|
||||
PVM_ASSERT(len > 0);
|
||||
|
||||
mmio_range_t search_key = {.gpa_base = gpa, .gpa_end = 0};
|
||||
/* Find the first region that starts after the target gpa */
|
||||
|
|
@ -89,13 +89,13 @@ int8_t mmio_db_dispatch_write(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t*
|
|||
/* base <= gpa < end */
|
||||
if ((candidate.gpa_base <= gpa) && (gpa < candidate.gpa_end))
|
||||
{
|
||||
size_t i = (it - 1) - db->address_ranges.begin();
|
||||
if (nullptr == db->handlers[i].write)
|
||||
auto i = (it - 1) - db->address_ranges.begin();
|
||||
if (nullptr == db->handlers[(size_t)i].write)
|
||||
{
|
||||
return EACCESS_DENIED;
|
||||
}
|
||||
|
||||
db->handlers[i].write(kvm, gpa, data, len);
|
||||
db->handlers[(size_t)i].write(kvm, gpa, data, len);
|
||||
return MMIO_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
@ -104,11 +104,11 @@ int8_t mmio_db_dispatch_write(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t*
|
|||
}
|
||||
|
||||
int8_t mmio_db_dispatch_read(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* data, size_t len)
|
||||
{
|
||||
assert(nullptr != db);
|
||||
assert(nullptr != kvm);
|
||||
assert(nullptr != data);
|
||||
assert(len > 0);
|
||||
{
|
||||
PVM_ASSERT(nullptr != db);
|
||||
PVM_ASSERT(nullptr != kvm);
|
||||
PVM_ASSERT(nullptr != data);
|
||||
PVM_ASSERT(len > 0);
|
||||
|
||||
mmio_range_t search_key = {.gpa_base = gpa, .gpa_end = 0};
|
||||
/* Find the first region that starts after the target gpa */
|
||||
|
|
@ -125,13 +125,13 @@ int8_t mmio_db_dispatch_read(mmio_db_t* db, kvm_t* kvm, uint64_t gpa, uint8_t* d
|
|||
/* base <= gpa < end */
|
||||
if ((candidate.gpa_base <= gpa) && (gpa < candidate.gpa_end))
|
||||
{
|
||||
size_t i = (it - 1) - db->address_ranges.begin();
|
||||
if (nullptr == db->handlers[i].read)
|
||||
auto i = (it - 1) - db->address_ranges.begin();
|
||||
if (nullptr == db->handlers[(size_t)i].read)
|
||||
{
|
||||
return EACCESS_DENIED;
|
||||
}
|
||||
|
||||
db->handlers[i].read(kvm, gpa, data, len);
|
||||
db->handlers[(size_t)i].read(kvm, gpa, data, len);
|
||||
return MMIO_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "mmu.h"
|
||||
#include <limits.h>
|
||||
#include "kvm.h"
|
||||
#include "common/passert.h"
|
||||
#include <limits.h>
|
||||
|
||||
namespace pound::kvm::memory
|
||||
{
|
||||
|
|
@ -133,7 +134,7 @@ int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_
|
|||
* in bytes from the correct TGx field.
|
||||
*/
|
||||
uint64_t granule_size = 0;
|
||||
assert((true == is_ttbr0) || (true == is_ttbr1));
|
||||
PVM_ASSERT((true == is_ttbr0) || (true == is_ttbr1));
|
||||
if (true == is_ttbr0)
|
||||
{
|
||||
/*
|
||||
|
|
@ -165,7 +166,7 @@ int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_
|
|||
* This is an illegal configuration. The hardware will fault.
|
||||
* For now, an assert will catch bad guest OS behaviour.
|
||||
*/
|
||||
assert(!"Invalid TG0 value in TCR_EL1");
|
||||
PVM_ASSERT_MSG(false, "Invalid TG0 value in TCR_EL1");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -199,7 +200,7 @@ int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_
|
|||
granule_size = GRANULE_64KB;
|
||||
break;
|
||||
default:
|
||||
assert(!"Invalid TG1 value in TCR_EL1");
|
||||
PVM_ASSERT_MSG(false, "Invalid TG1 value in TCR_EL1");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -336,7 +337,7 @@ int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_
|
|||
level_index = (gva >> l3_shift) & page_table_index_mask;
|
||||
break;
|
||||
default:
|
||||
assert(!"Invalid page table configuration!");
|
||||
PVM_ASSERT_MSG(false, "Invalid page table configuration!");
|
||||
}
|
||||
|
||||
const uint64_t level_entry_address = table_address + (level_index * page_table_entry_size);
|
||||
|
|
@ -388,7 +389,7 @@ int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_
|
|||
*/
|
||||
else if (0b01 == (descriptor & 0b11))
|
||||
{
|
||||
assert(!"Block descriptors are not supported");
|
||||
PVM_ASSERT_MSG(false, "Block descriptors are not supported");
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
|
|
|||
19
src/main.cpp
19
src/main.cpp
|
|
@ -4,9 +4,9 @@
|
|||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "common/Config.h"
|
||||
#include "common/Logging/Backend.h"
|
||||
#include "common/Logging/Log.h"
|
||||
#define LOG_MODULE "main"
|
||||
#include "common/logging.h"
|
||||
#include "common/passert.h"
|
||||
#include "frontend/gui.h"
|
||||
#include "host/memory/arena.h"
|
||||
|
||||
|
|
@ -18,19 +18,12 @@
|
|||
|
||||
int main()
|
||||
{
|
||||
Base::Log::Initialize();
|
||||
Base::Log::Start();
|
||||
|
||||
const auto& config_dir = Base::FS::GetUserPath(Base::FS::PathType::BinaryDir);
|
||||
Config::Load(config_dir / "config.toml");
|
||||
|
||||
// Create GUI manager
|
||||
gui::window_t window = {.data = nullptr, .gl_context = nullptr};
|
||||
(void)gui::window_init(&window, "Pound Emulator", Config::windowWidth(), Config::windowHeight());
|
||||
(void)gui::window_init(&window, "Pound Emulator", 640, 480);
|
||||
|
||||
if (bool return_code = gui::init_imgui(&window); false == return_code)
|
||||
{
|
||||
LOG_ERROR(Render, "Failed to initialize GUI");
|
||||
LOG_ERROR( "Failed to initialize GUI");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +104,7 @@ int main()
|
|||
::ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
if (bool sdl_ret_code = ::SDL_GL_SwapWindow(gui.window.data); false == sdl_ret_code)
|
||||
{
|
||||
LOG_ERROR(Render, "Failed to update window with OpenGL rendering: {}", SDL_GetError());
|
||||
LOG_ERROR("Failed to update window with OpenGL rendering: {}", SDL_GetError());
|
||||
is_running = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "kvm/kvm.h"
|
||||
#include "common/Logging/Log.h"
|
||||
|
||||
#define LOG_MODULE "switch1"
|
||||
#include "common/logging.h"
|
||||
|
||||
namespace pound::kvm
|
||||
{
|
||||
|
|
@ -18,7 +20,7 @@ const kvm_ops_t s1_ops = {
|
|||
|
||||
static int8_t s1_init(kvm_t* kvm)
|
||||
{
|
||||
LOG_INFO(PROBE, "Initializing Switch 1 virtual machine");
|
||||
LOG_INFO("Initializing Switch 1 virtual machine");
|
||||
/* BOOTSTRAPPING CODE GOES HERE */
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue