mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-25 04:02:42 +01:00
Threads improved, ID manager improved
This commit is contained in:
parent
78bfd54ad4
commit
ca6783ba9a
@ -199,7 +199,7 @@ void LogManager::log(LogMessage msg)
|
|||||||
prefix = "E ";
|
prefix = "E ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (auto thr = get_current_thread_ctrl())
|
if (auto thr = thread_ctrl::get_current())
|
||||||
{
|
{
|
||||||
prefix += "{" + thr->get_name() + "} ";
|
prefix += "{" + thr->get_name() + "} ";
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
void sleep_queue_entry_t::add_entry()
|
void sleep_queue_entry_t::add_entry()
|
||||||
{
|
{
|
||||||
m_queue.emplace_back(m_thread.shared_from_this());
|
m_queue.emplace_back(std::static_pointer_cast<CPUThread>(m_thread.shared_from_this()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void sleep_queue_entry_t::remove_entry()
|
void sleep_queue_entry_t::remove_entry()
|
||||||
@ -33,7 +33,7 @@ bool sleep_queue_entry_t::find() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep_queue_entry_t::sleep_queue_entry_t(CPUThread& cpu, sleep_queue_t& queue)
|
sleep_queue_entry_t::sleep_queue_entry_t(sleep_entry_t& cpu, sleep_queue_t& queue)
|
||||||
: m_thread(cpu)
|
: m_thread(cpu)
|
||||||
, m_queue(queue)
|
, m_queue(queue)
|
||||||
{
|
{
|
||||||
@ -41,7 +41,7 @@ sleep_queue_entry_t::sleep_queue_entry_t(CPUThread& cpu, sleep_queue_t& queue)
|
|||||||
cpu.sleep();
|
cpu.sleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep_queue_entry_t::sleep_queue_entry_t(CPUThread& cpu, sleep_queue_t& queue, const defer_sleep_t&)
|
sleep_queue_entry_t::sleep_queue_entry_t(sleep_entry_t& cpu, sleep_queue_t& queue, const defer_sleep_t&)
|
||||||
: m_thread(cpu)
|
: m_thread(cpu)
|
||||||
, m_queue(queue)
|
, m_queue(queue)
|
||||||
{
|
{
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
class CPUThread;
|
using sleep_entry_t = class CPUThread;
|
||||||
|
using sleep_queue_t = std::deque<std::shared_ptr<sleep_entry_t>>;
|
||||||
using sleep_queue_t = std::deque<std::shared_ptr<CPUThread>>;
|
|
||||||
|
|
||||||
static struct defer_sleep_t {} const defer_sleep{};
|
static struct defer_sleep_t {} const defer_sleep{};
|
||||||
|
|
||||||
// automatic object handling a thread entry in the sleep queue
|
// automatic object handling a thread entry in the sleep queue
|
||||||
class sleep_queue_entry_t final
|
class sleep_queue_entry_t final
|
||||||
{
|
{
|
||||||
CPUThread& m_thread;
|
sleep_entry_t& m_thread;
|
||||||
sleep_queue_t& m_queue;
|
sleep_queue_t& m_queue;
|
||||||
|
|
||||||
void add_entry();
|
void add_entry();
|
||||||
@ -18,10 +17,10 @@ class sleep_queue_entry_t final
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// add specified thread to the sleep queue
|
// add specified thread to the sleep queue
|
||||||
sleep_queue_entry_t(CPUThread& cpu, sleep_queue_t& queue);
|
sleep_queue_entry_t(sleep_entry_t& entry, sleep_queue_t& queue);
|
||||||
|
|
||||||
// don't add specified thread to the sleep queue
|
// don't add specified thread to the sleep queue
|
||||||
sleep_queue_entry_t(CPUThread& cpu, sleep_queue_t& queue, const defer_sleep_t&);
|
sleep_queue_entry_t(sleep_entry_t& entry, sleep_queue_t& queue, const defer_sleep_t&);
|
||||||
|
|
||||||
// removes specified thread from the sleep queue if added
|
// removes specified thread from the sleep queue if added
|
||||||
~sleep_queue_entry_t();
|
~sleep_queue_entry_t();
|
||||||
|
@ -19,6 +19,27 @@
|
|||||||
#include <ucontext.h>
|
#include <ucontext.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static const auto s_terminate_handler_set = std::set_terminate([]()
|
||||||
|
{
|
||||||
|
if (std::uncaught_exception())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (const std::exception& ex)
|
||||||
|
{
|
||||||
|
std::printf("Unhandled exception: %s\n", ex.what());
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
std::printf("Unhandled exception of unknown type.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::abort();
|
||||||
|
});
|
||||||
|
|
||||||
void SetCurrentThreadDebugName(const char* threadName)
|
void SetCurrentThreadDebugName(const char* threadName)
|
||||||
{
|
{
|
||||||
#if defined(_MSC_VER) // this is VS-specific way to set thread names for the debugger
|
#if defined(_MSC_VER) // this is VS-specific way to set thread names for the debugger
|
||||||
@ -113,10 +134,11 @@ enum x64_op_t : u32
|
|||||||
X64OP_STOS,
|
X64OP_STOS,
|
||||||
X64OP_XCHG,
|
X64OP_XCHG,
|
||||||
X64OP_CMPXCHG,
|
X64OP_CMPXCHG,
|
||||||
X64OP_LOAD_AND_STORE, // lock and [mem],reg
|
X64OP_LOAD_AND_STORE, // lock and [mem], reg
|
||||||
X64OP_LOAD_OR_STORE, // TODO: lock or [mem], reg
|
X64OP_LOAD_OR_STORE, // lock or [mem], reg (TODO)
|
||||||
X64OP_INC, // TODO: lock inc [mem]
|
X64OP_LOAD_XOR_STORE, // lock xor [mem], reg (TODO)
|
||||||
X64OP_DEC, // TODO: lock dec [mem]
|
X64OP_INC, // lock inc [mem] (TODO)
|
||||||
|
X64OP_DEC, // lock dec [mem] (TODO)
|
||||||
};
|
};
|
||||||
|
|
||||||
void decode_x64_reg_op(const u8* code, x64_op_t& out_op, x64_reg_t& out_reg, size_t& out_size, size_t& out_length)
|
void decode_x64_reg_op(const u8* code, x64_op_t& out_op, x64_reg_t& out_reg, size_t& out_size, size_t& out_length)
|
||||||
@ -1132,10 +1154,7 @@ const PVOID exception_handler = (atexit([]{ RemoveVectoredExceptionHandler(excep
|
|||||||
const u64 addr64 = (u64)pExp->ExceptionRecord->ExceptionInformation[1] - (u64)vm::base(0);
|
const u64 addr64 = (u64)pExp->ExceptionRecord->ExceptionInformation[1] - (u64)vm::base(0);
|
||||||
const bool is_writing = pExp->ExceptionRecord->ExceptionInformation[0] != 0;
|
const bool is_writing = pExp->ExceptionRecord->ExceptionInformation[0] != 0;
|
||||||
|
|
||||||
if (pExp->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
|
if (pExp->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && (u32)addr64 == addr64 && thread_ctrl::get_current() && handle_access_violation((u32)addr64, is_writing, pExp->ContextRecord))
|
||||||
(u32)addr64 == addr64 &&
|
|
||||||
get_current_thread_ctrl() &&
|
|
||||||
handle_access_violation((u32)addr64, is_writing, pExp->ContextRecord))
|
|
||||||
{
|
{
|
||||||
return EXCEPTION_CONTINUE_EXECUTION;
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
}
|
}
|
||||||
@ -1164,7 +1183,7 @@ void signal_handler(int sig, siginfo_t* info, void* uct)
|
|||||||
const bool is_writing = ((ucontext_t*)uct)->uc_mcontext.gregs[REG_ERR] & 0x2;
|
const bool is_writing = ((ucontext_t*)uct)->uc_mcontext.gregs[REG_ERR] & 0x2;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ((u32)addr64 == addr64 && get_current_thread_ctrl())
|
if ((u32)addr64 == addr64 && thread_ctrl::get_current())
|
||||||
{
|
{
|
||||||
if (handle_access_violation((u32)addr64, is_writing, (ucontext_t*)uct))
|
if (handle_access_violation((u32)addr64, is_writing, (ucontext_t*)uct))
|
||||||
{
|
{
|
||||||
@ -1191,94 +1210,112 @@ const int sigaction_result = []() -> int
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
thread_local thread_ctrl_t* g_tls_this_thread = nullptr;
|
thread_local thread_ctrl* thread_ctrl::g_tls_this_thread = nullptr;
|
||||||
|
|
||||||
const thread_ctrl_t* get_current_thread_ctrl()
|
// TODO
|
||||||
{
|
std::atomic<u32> g_thread_count{ 0 };
|
||||||
return g_tls_this_thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string thread_ctrl_t::get_name() const
|
void thread_ctrl::initialize()
|
||||||
{
|
{
|
||||||
return m_name();
|
SetCurrentThreadDebugName(g_tls_this_thread->m_name().c_str());
|
||||||
}
|
|
||||||
|
|
||||||
named_thread_t::named_thread_t(std::function<std::string()> name, std::function<void()> func)
|
#if defined(_MSC_VER)
|
||||||
{
|
_set_se_translator(_se_translator); // not essential, disable if necessary
|
||||||
start(std::move(name), std::move(func));
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
named_thread_t::~named_thread_t()
|
#ifdef _WIN32
|
||||||
{
|
if (!exception_handler || !exception_filter)
|
||||||
if (m_thread)
|
#else
|
||||||
|
if (sigaction_result == -1)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
std::printf("Fatal: thread neither joined nor detached\n");
|
std::printf("Exceptions handlers are not set correctly.\n");
|
||||||
std::terminate();
|
std::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
g_thread_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void thread_ctrl::finalize() noexcept
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
vm::reservation_free();
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
g_thread_count--;
|
||||||
|
|
||||||
|
// Call atexit functions
|
||||||
|
for (const auto& func : decltype(m_atexit)(std::move(g_tls_this_thread->m_atexit)))
|
||||||
|
{
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_ctrl::~thread_ctrl()
|
||||||
|
{
|
||||||
|
m_thread.detach();
|
||||||
|
|
||||||
|
if (m_future.valid())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_future.get();
|
||||||
|
}
|
||||||
|
catch (const std::exception& ex)
|
||||||
|
{
|
||||||
|
LOG_ERROR(GENERAL, "Abandoned exception: %s", ex.what());
|
||||||
|
}
|
||||||
|
catch (EmulationStopped)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string thread_ctrl::get_name() const
|
||||||
|
{
|
||||||
|
CHECK_ASSERTION(m_name);
|
||||||
|
|
||||||
|
return m_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string named_thread_t::get_name() const
|
std::string named_thread_t::get_name() const
|
||||||
{
|
{
|
||||||
if (!m_thread)
|
return fmt::format("('%s') Unnamed Thread", typeid(*this).name());
|
||||||
{
|
|
||||||
throw EXCEPTION("Invalid thread");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_thread->m_name)
|
|
||||||
{
|
|
||||||
throw EXCEPTION("Invalid name getter");
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_thread->m_name();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::atomic<u32> g_thread_count{ 0 };
|
void named_thread_t::start()
|
||||||
|
|
||||||
void named_thread_t::start(std::function<std::string()> name, std::function<void()> func)
|
|
||||||
{
|
{
|
||||||
if (m_thread)
|
CHECK_ASSERTION(m_thread == nullptr);
|
||||||
|
|
||||||
|
// Get shared_ptr instance (will throw if called from the constructor or the object has been created incorrectly)
|
||||||
|
auto ptr = shared_from_this();
|
||||||
|
|
||||||
|
// Make name getter
|
||||||
|
auto name = [wptr = std::weak_ptr<named_thread_t>(ptr), type = &typeid(*this)]()
|
||||||
{
|
{
|
||||||
throw EXCEPTION("Thread already exists");
|
// Return actual name if available
|
||||||
}
|
if (const auto ptr = wptr.lock())
|
||||||
|
{
|
||||||
|
return ptr->get_name();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return fmt::format("('%s') Deleted Thread", type->name());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// create new thread control variable
|
// Run thread
|
||||||
m_thread = std::make_shared<thread_ctrl_t>(std::move(name));
|
m_thread = thread_ctrl::spawn(std::move(name), [thread = std::move(ptr)]()
|
||||||
|
|
||||||
// start thread
|
|
||||||
m_thread->m_thread = std::thread([](std::shared_ptr<thread_ctrl_t> ctrl, std::function<void()> func)
|
|
||||||
{
|
{
|
||||||
g_tls_this_thread = ctrl.get();
|
|
||||||
|
|
||||||
SetCurrentThreadDebugName(ctrl->get_name().c_str());
|
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
_set_se_translator(_se_translator);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (!exception_handler || !exception_filter)
|
|
||||||
{
|
|
||||||
LOG_ERROR(GENERAL, "exception_handler not set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (sigaction_result == -1)
|
|
||||||
{
|
|
||||||
printf("sigaction() failed");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
g_thread_count++;
|
|
||||||
|
|
||||||
if (rpcs3::config.misc.log.hle_logging.value())
|
if (rpcs3::config.misc.log.hle_logging.value())
|
||||||
{
|
{
|
||||||
LOG_NOTICE(GENERAL, "Thread started");
|
LOG_NOTICE(GENERAL, "Thread started");
|
||||||
}
|
}
|
||||||
|
|
||||||
func();
|
thread->on_task();
|
||||||
|
|
||||||
if (rpcs3::config.misc.log.hle_logging.value())
|
if (rpcs3::config.misc.log.hle_logging.value())
|
||||||
{
|
{
|
||||||
@ -1295,75 +1332,24 @@ void named_thread_t::start(std::function<std::string()> name, std::function<void
|
|||||||
LOG_NOTICE(GENERAL, "Thread aborted");
|
LOG_NOTICE(GENERAL, "Thread aborted");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& func : ctrl->m_atexit)
|
thread->on_exit();
|
||||||
{
|
});
|
||||||
func();
|
|
||||||
|
|
||||||
func = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
vm::reservation_free();
|
|
||||||
|
|
||||||
g_thread_count--;
|
|
||||||
|
|
||||||
}, m_thread, std::move(func));
|
|
||||||
}
|
|
||||||
|
|
||||||
void named_thread_t::detach()
|
|
||||||
{
|
|
||||||
if (!m_thread)
|
|
||||||
{
|
|
||||||
throw EXCEPTION("Invalid thread");
|
|
||||||
}
|
|
||||||
|
|
||||||
// +clear m_thread
|
|
||||||
const auto ctrl = std::move(m_thread);
|
|
||||||
|
|
||||||
// notify if detached by another thread
|
|
||||||
if (g_tls_this_thread != m_thread.get())
|
|
||||||
{
|
|
||||||
// lock for reliable notification
|
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
|
||||||
|
|
||||||
cv.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
ctrl->m_thread.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void named_thread_t::join()
|
void named_thread_t::join()
|
||||||
{
|
{
|
||||||
if (!m_thread)
|
CHECK_ASSERTION(m_thread != nullptr);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
throw EXCEPTION("Invalid thread");
|
m_thread->join();
|
||||||
|
m_thread.reset();
|
||||||
}
|
}
|
||||||
|
catch (...)
|
||||||
if (g_tls_this_thread == m_thread.get())
|
|
||||||
{
|
{
|
||||||
throw EXCEPTION("Deadlock");
|
m_thread.reset();
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
// +clear m_thread
|
|
||||||
const auto ctrl = std::move(m_thread);
|
|
||||||
|
|
||||||
{
|
|
||||||
// lock for reliable notification
|
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
|
||||||
|
|
||||||
cv.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
ctrl->m_thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool named_thread_t::is_current() const
|
|
||||||
{
|
|
||||||
if (!m_thread)
|
|
||||||
{
|
|
||||||
throw EXCEPTION("Invalid thread");
|
|
||||||
}
|
|
||||||
|
|
||||||
return g_tls_this_thread == m_thread.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::function<bool()> SQUEUE_ALWAYS_EXIT = [](){ return true; };
|
const std::function<bool()> SQUEUE_ALWAYS_EXIT = [](){ return true; };
|
||||||
|
@ -1,107 +1,171 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
const class thread_ctrl_t* get_current_thread_ctrl();
|
// Thread control class
|
||||||
|
class thread_ctrl final
|
||||||
// Named thread control class
|
|
||||||
class thread_ctrl_t final
|
|
||||||
{
|
{
|
||||||
friend class named_thread_t;
|
static thread_local thread_ctrl* g_tls_this_thread;
|
||||||
|
|
||||||
template<typename T> friend void current_thread_register_atexit(T);
|
|
||||||
|
|
||||||
// Thread handler
|
|
||||||
std::thread m_thread;
|
|
||||||
|
|
||||||
// Name getter
|
// Name getter
|
||||||
const std::function<std::string()> m_name;
|
std::function<std::string()> m_name;
|
||||||
|
|
||||||
// Functions executed at thread exit (temporarily)
|
// Thread handle (be careful)
|
||||||
std::vector<std::function<void()>> m_atexit;
|
std::thread m_thread;
|
||||||
|
|
||||||
|
// Thread result
|
||||||
|
std::future<void> m_future;
|
||||||
|
|
||||||
|
// Functions scheduled at thread exit
|
||||||
|
std::deque<std::function<void()>> m_atexit;
|
||||||
|
|
||||||
|
// Called at the thread start
|
||||||
|
static void initialize();
|
||||||
|
|
||||||
|
// Called at the thread end
|
||||||
|
static void finalize() noexcept;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
thread_ctrl_t(std::function<std::string()> name)
|
template<typename T>
|
||||||
: m_name(std::move(name))
|
thread_ctrl(T&& name)
|
||||||
|
: m_name(std::forward<T>(name))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_ctrl_t(const thread_ctrl_t&) = delete;
|
// Disable copy/move constructors and operators
|
||||||
|
thread_ctrl(const thread_ctrl&) = delete;
|
||||||
|
|
||||||
|
~thread_ctrl();
|
||||||
|
|
||||||
// Get thread name
|
// Get thread name
|
||||||
std::string get_name() const;
|
std::string get_name() const;
|
||||||
|
|
||||||
|
// Get future result (may throw)
|
||||||
|
void join()
|
||||||
|
{
|
||||||
|
return m_future.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current thread (may be nullptr)
|
||||||
|
static const thread_ctrl* get_current()
|
||||||
|
{
|
||||||
|
return g_tls_this_thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register function at thread exit (for the current thread)
|
||||||
|
template<typename T>
|
||||||
|
static inline void at_exit(T&& func)
|
||||||
|
{
|
||||||
|
CHECK_ASSERTION(g_tls_this_thread);
|
||||||
|
|
||||||
|
g_tls_this_thread->m_atexit.emplace_front(std::forward<T>(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named thread factory
|
||||||
|
template<typename N, typename F>
|
||||||
|
static inline std::shared_ptr<thread_ctrl> spawn(N&& name, F&& func)
|
||||||
|
{
|
||||||
|
auto ctrl = std::make_shared<thread_ctrl>(std::forward<N>(name));
|
||||||
|
|
||||||
|
std::promise<void> promise;
|
||||||
|
|
||||||
|
ctrl->m_future = promise.get_future();
|
||||||
|
|
||||||
|
ctrl->m_thread = std::thread([ctrl, task = std::forward<F>(func)](std::promise<void> promise)
|
||||||
|
{
|
||||||
|
g_tls_this_thread = ctrl.get();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
initialize();
|
||||||
|
task();
|
||||||
|
finalize();
|
||||||
|
promise.set_value();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
finalize();
|
||||||
|
promise.set_exception(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
}, std::move(promise));
|
||||||
|
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register function at thread exit (temporarily)
|
class named_thread_t : public std::enable_shared_from_this<named_thread_t>
|
||||||
template<typename T> void current_thread_register_atexit(T func)
|
|
||||||
{
|
|
||||||
extern thread_local thread_ctrl_t* g_tls_this_thread;
|
|
||||||
|
|
||||||
g_tls_this_thread->m_atexit.emplace_back(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
class named_thread_t
|
|
||||||
{
|
{
|
||||||
// Pointer to managed resource (shared with actual thread)
|
// Pointer to managed resource (shared with actual thread)
|
||||||
std::shared_ptr<thread_ctrl_t> m_thread;
|
std::shared_ptr<thread_ctrl> m_thread;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Thread mutex for external use
|
// Thread condition variable for external use (this thread waits on it, other threads may notify)
|
||||||
std::mutex mutex;
|
|
||||||
|
|
||||||
// Thread condition variable for external use
|
|
||||||
std::condition_variable cv;
|
std::condition_variable cv;
|
||||||
|
|
||||||
|
// Thread mutex for external use (can be used with `cv`)
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Thread task (called in the thread)
|
||||||
|
virtual void on_task() = 0;
|
||||||
|
|
||||||
|
// Thread finalization (called after on_task)
|
||||||
|
virtual void on_exit() {}
|
||||||
|
|
||||||
|
// ID initialization (called through id_aux_initialize)
|
||||||
|
virtual void on_id_aux_initialize() { start(); }
|
||||||
|
|
||||||
|
// ID finalization (called through id_aux_finalize)
|
||||||
|
virtual void on_id_aux_finalize() { join(); }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Initialize in empty state
|
|
||||||
named_thread_t() = default;
|
named_thread_t() = default;
|
||||||
|
|
||||||
// Create named thread
|
virtual ~named_thread_t() = default;
|
||||||
named_thread_t(std::function<std::string()> name, std::function<void()> func);
|
|
||||||
|
|
||||||
// Deleted copy/move constructors + copy/move operators
|
// Deleted copy/move constructors + copy/move operators
|
||||||
named_thread_t(const named_thread_t&) = delete;
|
named_thread_t(const named_thread_t&) = delete;
|
||||||
|
|
||||||
// Destructor, calls std::terminate if the thread is neither joined nor detached
|
|
||||||
virtual ~named_thread_t();
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Get thread name
|
// Get thread name
|
||||||
std::string get_name() const;
|
virtual std::string get_name() const;
|
||||||
|
|
||||||
// Create named thread (current state must be empty)
|
// Start thread (cannot be called from the constructor: should throw bad_weak_ptr in such case)
|
||||||
void start(std::function<std::string()> name, std::function<void()> func);
|
void start();
|
||||||
|
|
||||||
// Detach thread -> empty state
|
// Join thread (get future result)
|
||||||
void detach();
|
|
||||||
|
|
||||||
// Join thread -> empty state
|
|
||||||
void join();
|
void join();
|
||||||
|
|
||||||
// Check whether the thread is not in "empty state"
|
// Check whether the thread is not in "empty state"
|
||||||
bool joinable() const { return m_thread.operator bool(); }
|
bool is_started() const { return m_thread.operator bool(); }
|
||||||
|
|
||||||
// Check whether it is the currently running thread
|
// Compare with the current thread
|
||||||
bool is_current() const;
|
bool is_current() const { CHECK_ASSERTION(m_thread); return thread_ctrl::get_current() == m_thread.get(); }
|
||||||
|
|
||||||
// Get internal thread pointer
|
// Get thread_ctrl
|
||||||
const thread_ctrl_t* get_thread_ctrl() const { return m_thread.get(); }
|
const thread_ctrl* get_thread_ctrl() const { return m_thread.get(); }
|
||||||
|
|
||||||
|
friend void id_aux_initialize(named_thread_t* ptr) { ptr->on_id_aux_initialize(); }
|
||||||
|
friend void id_aux_finalize(named_thread_t* ptr) { ptr->on_id_aux_finalize(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wrapper for named_thread_t, joins automatically in the destructor
|
// Wrapper for named thread, joins automatically in the destructor, can only be used in function scope
|
||||||
class autojoin_thread_t final
|
class scope_thread_t final
|
||||||
{
|
{
|
||||||
named_thread_t m_thread;
|
std::shared_ptr<thread_ctrl> m_thread;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
autojoin_thread_t(std::function<std::string()> name, std::function<void()> func)
|
template<typename N, typename F>
|
||||||
: m_thread(std::move(name), std::move(func))
|
scope_thread_t(N&& name, F&& func)
|
||||||
|
: m_thread(thread_ctrl::spawn(std::forward<N>(name), std::forward<F>(func)))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
autojoin_thread_t(const autojoin_thread_t&) = delete;
|
// Deleted copy/move constructors + copy/move operators
|
||||||
|
scope_thread_t(const scope_thread_t&) = delete;
|
||||||
|
|
||||||
~autojoin_thread_t() noexcept(false) // Allow exceptions
|
// Destructor with exceptions allowed
|
||||||
|
~scope_thread_t() noexcept(false)
|
||||||
{
|
{
|
||||||
m_thread.join();
|
m_thread->join();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,20 +80,22 @@ void armv7_free_tls(u32 thread)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ARMv7Thread::ARMv7Thread(const std::string& name)
|
ARMv7Thread::ARMv7Thread(const std::string& name)
|
||||||
: CPUThread(CPU_THREAD_ARMv7, name, WRAP_EXPR(fmt::format("ARMv7[0x%x] Thread (%s)[0x%08x]", m_id, m_name.c_str(), PC)))
|
: CPUThread(CPU_THREAD_ARMv7, name)
|
||||||
, ARMv7Context({})
|
, ARMv7Context({})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ARMv7Thread::~ARMv7Thread()
|
ARMv7Thread::~ARMv7Thread()
|
||||||
{
|
{
|
||||||
cv.notify_one();
|
|
||||||
join();
|
|
||||||
|
|
||||||
close_stack();
|
close_stack();
|
||||||
armv7_free_tls(m_id);
|
armv7_free_tls(m_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string ARMv7Thread::get_name() const
|
||||||
|
{
|
||||||
|
return fmt::format("ARMv7 Thread[0x%x] (%s)[0x%08x]", m_id, CPUThread::get_name(), PC);
|
||||||
|
}
|
||||||
|
|
||||||
void ARMv7Thread::dump_info() const
|
void ARMv7Thread::dump_info() const
|
||||||
{
|
{
|
||||||
if (hle_func)
|
if (hle_func)
|
||||||
@ -191,7 +193,7 @@ void ARMv7Thread::do_run()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARMv7Thread::task()
|
void ARMv7Thread::cpu_task()
|
||||||
{
|
{
|
||||||
if (custom_task)
|
if (custom_task)
|
||||||
{
|
{
|
||||||
@ -225,7 +227,7 @@ void ARMv7Thread::fast_call(u32 addr)
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
task();
|
cpu_task();
|
||||||
}
|
}
|
||||||
catch (CPUThreadReturn)
|
catch (CPUThreadReturn)
|
||||||
{
|
{
|
||||||
|
@ -11,11 +11,12 @@ public:
|
|||||||
ARMv7Thread(const std::string& name);
|
ARMv7Thread(const std::string& name);
|
||||||
virtual ~ARMv7Thread() override;
|
virtual ~ARMv7Thread() override;
|
||||||
|
|
||||||
|
virtual std::string get_name() const override;
|
||||||
virtual void dump_info() const override;
|
virtual void dump_info() const override;
|
||||||
virtual u32 get_pc() const override { return PC; }
|
virtual u32 get_pc() const override { return PC; }
|
||||||
virtual u32 get_offset() const override { return 0; }
|
virtual u32 get_offset() const override { return 0; }
|
||||||
virtual void do_run() override;
|
virtual void do_run() override;
|
||||||
virtual void task() override;
|
virtual void cpu_task() override;
|
||||||
|
|
||||||
virtual void init_regs() override;
|
virtual void init_regs() override;
|
||||||
virtual void init_stack() override;
|
virtual void init_stack() override;
|
||||||
|
@ -9,65 +9,66 @@
|
|||||||
|
|
||||||
thread_local CPUThread* g_tls_current_cpu_thread = nullptr;
|
thread_local CPUThread* g_tls_current_cpu_thread = nullptr;
|
||||||
|
|
||||||
CPUThread::CPUThread(CPUThreadType type, const std::string& name, std::function<std::string()> thread_name)
|
void CPUThread::on_task()
|
||||||
|
{
|
||||||
|
g_tls_current_cpu_thread = this;
|
||||||
|
|
||||||
|
Emu.SendDbgCommand(DID_CREATE_THREAD, this);
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
|
||||||
|
// check thread status
|
||||||
|
while (is_alive())
|
||||||
|
{
|
||||||
|
CHECK_EMU_STATUS;
|
||||||
|
|
||||||
|
// check stop status
|
||||||
|
if (!is_stopped())
|
||||||
|
{
|
||||||
|
if (lock) lock.unlock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cpu_task();
|
||||||
|
}
|
||||||
|
catch (CPUThreadReturn)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
catch (CPUThreadStop)
|
||||||
|
{
|
||||||
|
m_state |= CPU_STATE_STOPPED;
|
||||||
|
}
|
||||||
|
catch (CPUThreadExit)
|
||||||
|
{
|
||||||
|
m_state |= CPU_STATE_DEAD;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
dump_info();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_state &= ~CPU_STATE_RETURN;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lock)
|
||||||
|
{
|
||||||
|
lock.lock();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv.wait(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CPUThread::CPUThread(CPUThreadType type, const std::string& name)
|
||||||
: m_id(idm::get_last_id())
|
: m_id(idm::get_last_id())
|
||||||
, m_type(type)
|
, m_type(type)
|
||||||
, m_name(name)
|
, m_name(name)
|
||||||
{
|
{
|
||||||
start(std::move(thread_name), [this]
|
|
||||||
{
|
|
||||||
g_tls_current_cpu_thread = this;
|
|
||||||
|
|
||||||
Emu.SendDbgCommand(DID_CREATE_THREAD, this);
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(mutex);
|
|
||||||
|
|
||||||
// check thread status
|
|
||||||
while (joinable() && is_alive())
|
|
||||||
{
|
|
||||||
CHECK_EMU_STATUS;
|
|
||||||
|
|
||||||
// check stop status
|
|
||||||
if (!is_stopped())
|
|
||||||
{
|
|
||||||
if (lock) lock.unlock();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
task();
|
|
||||||
}
|
|
||||||
catch (CPUThreadReturn)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
catch (CPUThreadStop)
|
|
||||||
{
|
|
||||||
m_state |= CPU_STATE_STOPPED;
|
|
||||||
}
|
|
||||||
catch (CPUThreadExit)
|
|
||||||
{
|
|
||||||
m_state |= CPU_STATE_DEAD;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
dump_info();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_state &= ~CPU_STATE_RETURN;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lock)
|
|
||||||
{
|
|
||||||
lock.lock();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
cv.wait(lock);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CPUThread::~CPUThread()
|
CPUThread::~CPUThread()
|
||||||
@ -75,6 +76,11 @@ CPUThread::~CPUThread()
|
|||||||
Emu.SendDbgCommand(DID_REMOVE_THREAD, this);
|
Emu.SendDbgCommand(DID_REMOVE_THREAD, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string CPUThread::get_name() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
bool CPUThread::is_paused() const
|
bool CPUThread::is_paused() const
|
||||||
{
|
{
|
||||||
return (m_state & CPU_STATE_PAUSED) != 0 || Emu.IsPaused();
|
return (m_state & CPU_STATE_PAUSED) != 0 || Emu.IsPaused();
|
||||||
@ -149,25 +155,26 @@ void CPUThread::exec()
|
|||||||
{
|
{
|
||||||
Emu.SendDbgCommand(DID_EXEC_THREAD, this);
|
Emu.SendDbgCommand(DID_EXEC_THREAD, this);
|
||||||
|
|
||||||
|
m_state &= ~CPU_STATE_STOPPED;
|
||||||
|
|
||||||
{
|
{
|
||||||
// lock for reliable notification
|
// lock for reliable notification
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
|
||||||
m_state &= ~CPU_STATE_STOPPED;
|
|
||||||
|
|
||||||
cv.notify_one();
|
cv.notify_one();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPUThread::exit()
|
void CPUThread::exit()
|
||||||
{
|
{
|
||||||
if (is_current())
|
m_state |= CPU_STATE_DEAD;
|
||||||
|
|
||||||
|
if (!is_current())
|
||||||
{
|
{
|
||||||
throw CPUThreadExit{};
|
// lock for reliable notification
|
||||||
}
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
else
|
|
||||||
{
|
cv.notify_one();
|
||||||
throw EXCEPTION("Unable to exit another thread");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,6 +266,11 @@ bool CPUThread::check_status()
|
|||||||
{
|
{
|
||||||
CHECK_EMU_STATUS; // check at least once
|
CHECK_EMU_STATUS; // check at least once
|
||||||
|
|
||||||
|
if (!is_alive())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!is_paused() && (m_state & CPU_STATE_INTR) == 0)
|
if (!is_paused() && (m_state & CPU_STATE_INTR) == 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
|
@ -15,52 +15,44 @@ enum : u64
|
|||||||
{
|
{
|
||||||
CPU_STATE_STOPPED = (1ull << 0), // basic execution state (stopped by default), removed by Exec()
|
CPU_STATE_STOPPED = (1ull << 0), // basic execution state (stopped by default), removed by Exec()
|
||||||
CPU_STATE_PAUSED = (1ull << 1), // pauses thread execution, set by the debugger (manually or after step execution)
|
CPU_STATE_PAUSED = (1ull << 1), // pauses thread execution, set by the debugger (manually or after step execution)
|
||||||
CPU_STATE_SLEEP = (1ull << 2), // shouldn't affect thread execution, set by Sleep() call, removed by the latest Awake() call, may possibly indicate waiting state of the thread
|
CPU_STATE_SLEEP = (1ull << 2), // shouldn't affect thread execution, set by sleep(), removed by the latest awake(), may possibly indicate waiting state of the thread
|
||||||
CPU_STATE_STEP = (1ull << 3), // forces the thread to pause after executing just one instruction or something appropriate, set by the debugger
|
CPU_STATE_STEP = (1ull << 3), // forces the thread to pause after executing just one instruction or something appropriate, set by the debugger
|
||||||
CPU_STATE_DEAD = (1ull << 4), // indicates irreversible exit of the thread
|
CPU_STATE_DEAD = (1ull << 4), // indicates irreversible exit of the thread
|
||||||
CPU_STATE_RETURN = (1ull << 5), // used for callback return
|
CPU_STATE_RETURN = (1ull << 5), // used for callback return
|
||||||
CPU_STATE_SIGNAL = (1ull << 6), // used for HLE signaling
|
CPU_STATE_SIGNAL = (1ull << 6), // used for HLE signaling
|
||||||
CPU_STATE_INTR = (1ull << 7), // thread interrupted
|
CPU_STATE_INTR = (1ull << 7), // thread interrupted
|
||||||
|
|
||||||
CPU_STATE_MAX = (1ull << 8), // added to (subtracted from) m_state by Sleep()/Awake() calls to trigger status check
|
CPU_STATE_MAX = (1ull << 8), // added to (subtracted from) m_state by sleep()/awake() calls to trigger status check
|
||||||
};
|
};
|
||||||
|
|
||||||
// "HLE return" exception event
|
class CPUThreadReturn {}; // "HLE return" exception event
|
||||||
class CPUThreadReturn {};
|
class CPUThreadStop {}; // CPUThread::Stop exception event
|
||||||
|
class CPUThreadExit {}; // CPUThread::Exit exception event
|
||||||
// CPUThread::Stop exception event
|
|
||||||
class CPUThreadStop {};
|
|
||||||
|
|
||||||
// CPUThread::Exit exception event
|
|
||||||
class CPUThreadExit {};
|
|
||||||
|
|
||||||
class CPUDecoder;
|
class CPUDecoder;
|
||||||
|
|
||||||
class CPUThread : public named_thread_t, public std::enable_shared_from_this<CPUThread>
|
class CPUThread : public named_thread_t
|
||||||
{
|
{
|
||||||
using named_thread_t::start;
|
void on_task() override;
|
||||||
|
void on_id_aux_finalize() override { exit(); } // call exit() instead of join()
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
using named_thread_t::detach;
|
|
||||||
using named_thread_t::join;
|
|
||||||
using named_thread_t::joinable;
|
|
||||||
|
|
||||||
atomic_t<u64> m_state{ CPU_STATE_STOPPED }; // thread state flags
|
atomic_t<u64> m_state{ CPU_STATE_STOPPED }; // thread state flags
|
||||||
|
|
||||||
std::unique_ptr<CPUDecoder> m_dec;
|
std::unique_ptr<CPUDecoder> m_dec;
|
||||||
|
|
||||||
const u32 m_id;
|
const u32 m_id;
|
||||||
const CPUThreadType m_type;
|
const CPUThreadType m_type;
|
||||||
const std::string m_name; // changing m_name would be terribly thread-unsafe in current implementation
|
const std::string m_name; // changing m_name is unsafe because it can be read at any moment
|
||||||
|
|
||||||
CPUThread(CPUThreadType type, const std::string& name, std::function<std::string()> thread_name);
|
CPUThread(CPUThreadType type, const std::string& name);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~CPUThread() override;
|
virtual ~CPUThread() override;
|
||||||
|
|
||||||
|
virtual std::string get_name() const override;
|
||||||
u32 get_id() const { return m_id; }
|
u32 get_id() const { return m_id; }
|
||||||
CPUThreadType get_type() const { return m_type; }
|
CPUThreadType get_type() const { return m_type; }
|
||||||
std::string get_name() const { return m_name; }
|
|
||||||
|
|
||||||
bool is_alive() const { return (m_state & CPU_STATE_DEAD) == 0; }
|
bool is_alive() const { return (m_state & CPU_STATE_DEAD) == 0; }
|
||||||
bool is_stopped() const { return (m_state & CPU_STATE_STOPPED) != 0; }
|
bool is_stopped() const { return (m_state & CPU_STATE_STOPPED) != 0; }
|
||||||
@ -70,7 +62,7 @@ public:
|
|||||||
virtual u32 get_pc() const = 0;
|
virtual u32 get_pc() const = 0;
|
||||||
virtual u32 get_offset() const = 0;
|
virtual u32 get_offset() const = 0;
|
||||||
virtual void do_run() = 0;
|
virtual void do_run() = 0;
|
||||||
virtual void task() = 0;
|
virtual void cpu_task() = 0;
|
||||||
|
|
||||||
virtual void init_regs() = 0;
|
virtual void init_regs() = 0;
|
||||||
virtual void init_stack() = 0;
|
virtual void init_stack() = 0;
|
||||||
@ -178,35 +170,6 @@ protected:
|
|||||||
std::shared_ptr<CPUThread> thread;
|
std::shared_ptr<CPUThread> thread;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
//u32 get_entry() const
|
|
||||||
//{
|
|
||||||
// return thread->entry;
|
|
||||||
//}
|
|
||||||
|
|
||||||
virtual cpu_thread& args(std::initializer_list<std::string> values) = 0;
|
virtual cpu_thread& args(std::initializer_list<std::string> values) = 0;
|
||||||
|
|
||||||
virtual cpu_thread& run() = 0;
|
virtual cpu_thread& run() = 0;
|
||||||
|
|
||||||
//u64 join()
|
|
||||||
//{
|
|
||||||
// if (!joinable())
|
|
||||||
// throw EXCEPTION("thread must be joinable for join");
|
|
||||||
|
|
||||||
// thread->SetJoinable(false);
|
|
||||||
|
|
||||||
// while (thread->IsRunning())
|
|
||||||
// std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack
|
|
||||||
|
|
||||||
// return thread->GetExitStatus();
|
|
||||||
//}
|
|
||||||
|
|
||||||
//bool joinable() const
|
|
||||||
//{
|
|
||||||
// return thread->IsJoinable();
|
|
||||||
//}
|
|
||||||
|
|
||||||
//u32 get_id() const
|
|
||||||
//{
|
|
||||||
// return thread->GetId();
|
|
||||||
//}
|
|
||||||
};
|
};
|
||||||
|
@ -263,7 +263,6 @@ RecompilationEngine::RecompilationEngine()
|
|||||||
|
|
||||||
RecompilationEngine::~RecompilationEngine() {
|
RecompilationEngine::~RecompilationEngine() {
|
||||||
m_executable_storage.clear();
|
m_executable_storage.clear();
|
||||||
join();
|
|
||||||
memory_helper::free_reserved_memory(FunctionCache, VIRTUAL_INSTRUCTION_COUNT * sizeof(ExecutableStorageType));
|
memory_helper::free_reserved_memory(FunctionCache, VIRTUAL_INSTRUCTION_COUNT * sizeof(ExecutableStorageType));
|
||||||
free(FunctionCachePagesCommited);
|
free(FunctionCachePagesCommited);
|
||||||
}
|
}
|
||||||
@ -306,8 +305,8 @@ void RecompilationEngine::NotifyBlockStart(u32 address) {
|
|||||||
m_pending_address_start.push_back(address);
|
m_pending_address_start.push_back(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!joinable()) {
|
if (!is_started()) {
|
||||||
start(WRAP_EXPR("PPU Recompilation Engine"), WRAP_EXPR(Task()));
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
cv.notify_one();
|
cv.notify_one();
|
||||||
@ -324,12 +323,12 @@ raw_fd_ostream & RecompilationEngine::Log() {
|
|||||||
return *m_log;
|
return *m_log;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecompilationEngine::Task() {
|
void RecompilationEngine::on_task() {
|
||||||
std::chrono::nanoseconds idling_time(0);
|
std::chrono::nanoseconds idling_time(0);
|
||||||
std::chrono::nanoseconds recompiling_time(0);
|
std::chrono::nanoseconds recompiling_time(0);
|
||||||
|
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
while (joinable() && !Emu.IsStopped()) {
|
while (!Emu.IsStopped()) {
|
||||||
bool work_done_this_iteration = false;
|
bool work_done_this_iteration = false;
|
||||||
std::list <u32> m_current_execution_traces;
|
std::list <u32> m_current_execution_traces;
|
||||||
|
|
||||||
|
@ -773,7 +773,7 @@ namespace ppu_recompiler_llvm {
|
|||||||
* It then builds them asynchroneously and update the executable mapping
|
* It then builds them asynchroneously and update the executable mapping
|
||||||
* using atomic based locks to avoid undefined behavior.
|
* using atomic based locks to avoid undefined behavior.
|
||||||
**/
|
**/
|
||||||
class RecompilationEngine final : protected named_thread_t {
|
class RecompilationEngine final : public named_thread_t {
|
||||||
friend class CPUHybridDecoderRecompiler;
|
friend class CPUHybridDecoderRecompiler;
|
||||||
public:
|
public:
|
||||||
virtual ~RecompilationEngine() override;
|
virtual ~RecompilationEngine() override;
|
||||||
@ -790,7 +790,9 @@ namespace ppu_recompiler_llvm {
|
|||||||
/// Log
|
/// Log
|
||||||
llvm::raw_fd_ostream & Log();
|
llvm::raw_fd_ostream & Log();
|
||||||
|
|
||||||
void Task();
|
std::string get_name() const override { return "PPU Recompilation Engine"; }
|
||||||
|
|
||||||
|
void on_task() override;
|
||||||
|
|
||||||
/// Get a pointer to the instance of this class
|
/// Get a pointer to the instance of this class
|
||||||
static std::shared_ptr<RecompilationEngine> GetInstance();
|
static std::shared_ptr<RecompilationEngine> GetInstance();
|
||||||
|
@ -57,26 +57,22 @@ void ppu_decoder_cache_t::initialize(u32 addr, u32 size)
|
|||||||
}
|
}
|
||||||
|
|
||||||
PPUThread::PPUThread(const std::string& name)
|
PPUThread::PPUThread(const std::string& name)
|
||||||
: CPUThread(CPU_THREAD_PPU, name, WRAP_EXPR(fmt::format("PPU[0x%x] Thread (%s)[0x%08x]", m_id, m_name.c_str(), PC)))
|
: CPUThread(CPU_THREAD_PPU, name)
|
||||||
{
|
{
|
||||||
InitRotateMask();
|
InitRotateMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
PPUThread::~PPUThread()
|
PPUThread::~PPUThread()
|
||||||
{
|
{
|
||||||
if (is_current())
|
|
||||||
{
|
|
||||||
detach();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
join();
|
|
||||||
}
|
|
||||||
|
|
||||||
close_stack();
|
close_stack();
|
||||||
ppu_free_tls(m_id);
|
ppu_free_tls(m_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string PPUThread::get_name() const
|
||||||
|
{
|
||||||
|
return fmt::format("PPU Thread[0x%x] (%s)[0x%08x]", m_id, CPUThread::get_name(), PC);
|
||||||
|
}
|
||||||
|
|
||||||
void PPUThread::dump_info() const
|
void PPUThread::dump_info() const
|
||||||
{
|
{
|
||||||
extern std::string get_ps3_function_name(u64 fid);
|
extern std::string get_ps3_function_name(u64 fid);
|
||||||
@ -239,7 +235,7 @@ void PPUThread::fast_call(u32 addr, u32 rtoc)
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
task();
|
cpu_task();
|
||||||
}
|
}
|
||||||
catch (CPUThreadReturn)
|
catch (CPUThreadReturn)
|
||||||
{
|
{
|
||||||
@ -264,7 +260,7 @@ void PPUThread::fast_stop()
|
|||||||
m_state |= CPU_STATE_RETURN;
|
m_state |= CPU_STATE_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PPUThread::task()
|
void PPUThread::cpu_task()
|
||||||
{
|
{
|
||||||
SetHostRoundingMode(FPSCR_RN_NEAR);
|
SetHostRoundingMode(FPSCR_RN_NEAR);
|
||||||
|
|
||||||
|
@ -543,11 +543,12 @@ public:
|
|||||||
PPUThread(const std::string& name);
|
PPUThread(const std::string& name);
|
||||||
virtual ~PPUThread() override;
|
virtual ~PPUThread() override;
|
||||||
|
|
||||||
|
virtual std::string get_name() const override;
|
||||||
virtual void dump_info() const override;
|
virtual void dump_info() const override;
|
||||||
virtual u32 get_pc() const override { return PC; }
|
virtual u32 get_pc() const override { return PC; }
|
||||||
virtual u32 get_offset() const override { return 0; }
|
virtual u32 get_offset() const override { return 0; }
|
||||||
virtual void do_run() override;
|
virtual void do_run() override;
|
||||||
virtual void task() override;
|
virtual void cpu_task() override;
|
||||||
|
|
||||||
virtual void init_regs() override;
|
virtual void init_regs() override;
|
||||||
virtual void init_stack() override;
|
virtual void init_stack() override;
|
||||||
|
@ -10,20 +10,9 @@
|
|||||||
thread_local spu_mfc_arg_t raw_spu_mfc[8] = {};
|
thread_local spu_mfc_arg_t raw_spu_mfc[8] = {};
|
||||||
|
|
||||||
RawSPUThread::RawSPUThread(const std::string& name, u32 index)
|
RawSPUThread::RawSPUThread(const std::string& name, u32 index)
|
||||||
: SPUThread(CPU_THREAD_RAW_SPU, name, COPY_EXPR(fmt::format("RawSPU[%d] Thread (0x%x)[0x%05x]", index, m_id, pc)), index, RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index)
|
: SPUThread(CPU_THREAD_RAW_SPU, name, index, RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index)
|
||||||
{
|
{
|
||||||
if (!vm::falloc(offset, 0x40000))
|
CHECK_ASSERTION(vm::falloc(offset, 0x40000) == offset);
|
||||||
{
|
|
||||||
throw EXCEPTION("Failed to allocate RawSPU local storage");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RawSPUThread::~RawSPUThread()
|
|
||||||
{
|
|
||||||
join();
|
|
||||||
|
|
||||||
// Deallocate Local Storage
|
|
||||||
vm::dealloc_verbose_nothrow(offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RawSPUThread::read_reg(const u32 addr, u32& value)
|
bool RawSPUThread::read_reg(const u32 addr, u32& value)
|
||||||
@ -233,7 +222,7 @@ bool RawSPUThread::write_reg(const u32 addr, const u32 value)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RawSPUThread::task()
|
void RawSPUThread::cpu_task()
|
||||||
{
|
{
|
||||||
// get next PC and SPU Interrupt status
|
// get next PC and SPU Interrupt status
|
||||||
pc = npc.exchange(0);
|
pc = npc.exchange(0);
|
||||||
@ -242,7 +231,7 @@ void RawSPUThread::task()
|
|||||||
|
|
||||||
pc &= 0x3fffc;
|
pc &= 0x3fffc;
|
||||||
|
|
||||||
SPUThread::task();
|
SPUThread::cpu_task();
|
||||||
|
|
||||||
// save next PC and current SPU Interrupt status
|
// save next PC and current SPU Interrupt status
|
||||||
npc = pc | ((ch_event_stat & SPU_EVENT_INTR_ENABLED) != 0);
|
npc = pc | ((ch_event_stat & SPU_EVENT_INTR_ENABLED) != 0);
|
||||||
|
@ -19,11 +19,10 @@ class RawSPUThread final : public SPUThread
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RawSPUThread(const std::string& name, u32 index);
|
RawSPUThread(const std::string& name, u32 index);
|
||||||
virtual ~RawSPUThread();
|
|
||||||
|
|
||||||
bool read_reg(const u32 addr, u32& value);
|
bool read_reg(const u32 addr, u32& value);
|
||||||
bool write_reg(const u32 addr, const u32 value);
|
bool write_reg(const u32 addr, const u32 value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual void task() override;
|
virtual void cpu_task() override;
|
||||||
};
|
};
|
||||||
|
@ -52,33 +52,25 @@ void spu_int_ctrl_t::clear(u64 ints)
|
|||||||
|
|
||||||
const spu_imm_table_t g_spu_imm;
|
const spu_imm_table_t g_spu_imm;
|
||||||
|
|
||||||
SPUThread::SPUThread(CPUThreadType type, const std::string& name, std::function<std::string()> thread_name, u32 index, u32 offset)
|
SPUThread::SPUThread(CPUThreadType type, const std::string& name, u32 index, u32 offset)
|
||||||
: CPUThread(type, name, std::move(thread_name))
|
: CPUThread(type, name)
|
||||||
, index(index)
|
, index(index)
|
||||||
, offset(offset)
|
, offset(offset)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
SPUThread::SPUThread(const std::string& name, u32 index)
|
SPUThread::SPUThread(const std::string& name, u32 index)
|
||||||
: CPUThread(CPU_THREAD_SPU, name, WRAP_EXPR(fmt::format("SPU[0x%x] Thread (%s)[0x%05x]", m_id, m_name.c_str(), pc)))
|
: CPUThread(CPU_THREAD_SPU, name)
|
||||||
, index(index)
|
, index(index)
|
||||||
, offset(vm::alloc(0x40000, vm::main))
|
, offset(vm::alloc(0x40000, vm::main))
|
||||||
{
|
{
|
||||||
if (!offset)
|
CHECK_ASSERTION(offset);
|
||||||
{
|
|
||||||
throw EXCEPTION("Failed to allocate SPU local storage");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SPUThread::~SPUThread()
|
SPUThread::~SPUThread()
|
||||||
{
|
{
|
||||||
if (m_type == CPU_THREAD_SPU)
|
// Deallocate Local Storage
|
||||||
{
|
vm::dealloc_verbose_nothrow(offset);
|
||||||
join();
|
|
||||||
|
|
||||||
// Deallocate Local Storage
|
|
||||||
vm::dealloc_verbose_nothrow(offset, vm::main);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SPUThread::is_paused() const
|
bool SPUThread::is_paused() const
|
||||||
@ -99,12 +91,17 @@ bool SPUThread::is_paused() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string SPUThread::get_name() const
|
||||||
|
{
|
||||||
|
return fmt::format("%s[0x%x] Thread (%s)[0x%05x]", CPUThread::GetTypeString(), m_id, CPUThread::get_name(), pc);
|
||||||
|
}
|
||||||
|
|
||||||
void SPUThread::dump_info() const
|
void SPUThread::dump_info() const
|
||||||
{
|
{
|
||||||
CPUThread::dump_info();
|
CPUThread::dump_info();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SPUThread::task()
|
void SPUThread::cpu_task()
|
||||||
{
|
{
|
||||||
std::fesetround(FE_TOWARDZERO);
|
std::fesetround(FE_TOWARDZERO);
|
||||||
|
|
||||||
@ -251,7 +248,7 @@ void SPUThread::fast_call(u32 ls_addr)
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
task();
|
cpu_task();
|
||||||
}
|
}
|
||||||
catch (CPUThreadReturn)
|
catch (CPUThreadReturn)
|
||||||
{
|
{
|
||||||
|
@ -661,7 +661,7 @@ public:
|
|||||||
u32 recursion_level = 0;
|
u32 recursion_level = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SPUThread(CPUThreadType type, const std::string& name, std::function<std::string()> thread_name, u32 index, u32 offset);
|
SPUThread(CPUThreadType type, const std::string& name, u32 index, u32 offset);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SPUThread(const std::string& name, u32 index);
|
SPUThread(const std::string& name, u32 index);
|
||||||
@ -669,11 +669,12 @@ public:
|
|||||||
|
|
||||||
virtual bool is_paused() const override;
|
virtual bool is_paused() const override;
|
||||||
|
|
||||||
|
virtual std::string get_name() const override;
|
||||||
virtual void dump_info() const override;
|
virtual void dump_info() const override;
|
||||||
virtual u32 get_pc() const override { return pc; }
|
virtual u32 get_pc() const override { return pc; }
|
||||||
virtual u32 get_offset() const override { return offset; }
|
virtual u32 get_offset() const override { return offset; }
|
||||||
virtual void do_run() override;
|
virtual void do_run() override;
|
||||||
virtual void task() override;
|
virtual void cpu_task() override;
|
||||||
|
|
||||||
virtual void init_regs() override;
|
virtual void init_regs() override;
|
||||||
virtual void init_stack() override;
|
virtual void init_stack() override;
|
||||||
|
@ -1,14 +1,157 @@
|
|||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "IdManager.h"
|
#include "IdManager.h"
|
||||||
|
|
||||||
std::mutex idm::g_mutex;
|
namespace idm
|
||||||
|
{
|
||||||
|
std::mutex g_mutex;
|
||||||
|
|
||||||
std::unordered_map<u32, id_data_t> idm::g_map;
|
idm::map_t g_map;
|
||||||
|
|
||||||
u32 idm::g_last_raw_id = 0;
|
u32 g_last_raw_id = 0;
|
||||||
|
|
||||||
thread_local u32 idm::g_tls_last_id = 0xdeadbeef;
|
thread_local u32 g_tls_last_id = 0xdeadbeef;
|
||||||
|
}
|
||||||
|
|
||||||
std::mutex fxm::g_mutex;
|
namespace fxm
|
||||||
|
{
|
||||||
|
std::mutex g_mutex;
|
||||||
|
|
||||||
std::unordered_map<const void*, std::shared_ptr<void>> fxm::g_map;
|
fxm::map_t g_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
void idm::clear()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock{g_mutex};
|
||||||
|
|
||||||
|
// Call recorded id_finalize functions for all IDs
|
||||||
|
for (auto& id : idm::map_t(std::move(g_map)))
|
||||||
|
{
|
||||||
|
(*id.second.type_index)(id.second.data.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
g_last_raw_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool idm::check(u32 in_id, id_type_index_t type)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
const auto found = g_map.find(in_id);
|
||||||
|
|
||||||
|
return found != g_map.end() && found->second.type_index == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if ID exists and return its type or nullptr
|
||||||
|
const std::type_info* idm::get_type(u32 raw_id)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
const auto found = g_map.find(raw_id);
|
||||||
|
|
||||||
|
return found == g_map.end() ? nullptr : found->second.info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<void> idm::get(u32 in_id, id_type_index_t type)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
const auto found = g_map.find(in_id);
|
||||||
|
|
||||||
|
if (found == g_map.end() || found->second.type_index != type)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return found->second.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
idm::map_t idm::get_all(id_type_index_t type)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
idm::map_t result;
|
||||||
|
|
||||||
|
for (auto& id : g_map)
|
||||||
|
{
|
||||||
|
if (id.second.type_index == type)
|
||||||
|
{
|
||||||
|
result.insert(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<void> idm::withdraw(u32 in_id, id_type_index_t type)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
const auto found = g_map.find(in_id);
|
||||||
|
|
||||||
|
if (found == g_map.end() || found->second.type_index != type)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ptr = std::move(found->second.data);
|
||||||
|
|
||||||
|
g_map.erase(found);
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 idm::get_count(id_type_index_t type)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
u32 result = 0;
|
||||||
|
|
||||||
|
for (auto& id : g_map)
|
||||||
|
{
|
||||||
|
if (id.second.type_index == type)
|
||||||
|
{
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fxm::clear()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock{g_mutex};
|
||||||
|
|
||||||
|
// Call recorded id_finalize functions for all IDs
|
||||||
|
for (auto& id : fxm::map_t(std::move(g_map)))
|
||||||
|
{
|
||||||
|
if (id.second) (*id.first)(id.second.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fxm::check(id_type_index_t type)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
const auto found = g_map.find(type);
|
||||||
|
|
||||||
|
return found != g_map.end() && found->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<void> fxm::get(id_type_index_t type)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
const auto found = g_map.find(type);
|
||||||
|
|
||||||
|
return found != g_map.end() ? found->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<void> fxm::withdraw(id_type_index_t type)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
const auto found = g_map.find(type);
|
||||||
|
|
||||||
|
return found != g_map.end() ? std::move(found->second) : nullptr;
|
||||||
|
}
|
||||||
|
@ -2,40 +2,61 @@
|
|||||||
|
|
||||||
#define ID_MANAGER_INCLUDED
|
#define ID_MANAGER_INCLUDED
|
||||||
|
|
||||||
template<typename T> struct type_info_t { static char value; };
|
// TODO: make id_aux_initialize and id_aux_finalize safer against a possible ODR violation
|
||||||
|
|
||||||
template<typename T> char type_info_t<T>::value = 42;
|
// Function called after the successfull creation of an ID (does nothing by default, provide an overload)
|
||||||
|
inline void id_aux_initialize(void*)
|
||||||
template<typename T> constexpr inline const void* get_type_index()
|
|
||||||
{
|
{
|
||||||
return &type_info_t<T>::value;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
// default traits for any arbitrary type
|
// Function called after the ID removal (does nothing by default, provide an overload)
|
||||||
template<typename T> struct id_traits
|
inline void id_aux_finalize(void*)
|
||||||
{
|
{
|
||||||
// get next mapped id (may return 0 if out of IDs)
|
;
|
||||||
static u32 next_id(u32 raw_id) { return raw_id < 0x80000000 ? (raw_id + 1) & 0x7fffffff : 0; }
|
}
|
||||||
|
|
||||||
// convert "public" id to mapped id (may return 0 if invalid)
|
// Type-erased id_aux_* function type
|
||||||
static u32 in_id(u32 id) { return id; }
|
using id_aux_func_t = void(*)(void*);
|
||||||
|
|
||||||
// convert mapped id to "public" id
|
template<typename T>
|
||||||
static u32 out_id(u32 raw_id) { return raw_id; }
|
struct id_type_info_t
|
||||||
|
{
|
||||||
|
static const auto size = sizeof(T); // forbid forward declarations
|
||||||
|
|
||||||
|
static const id_aux_func_t on_remove;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct id_data_t final
|
// Type-erased finalization function
|
||||||
|
template<typename T>
|
||||||
|
const id_aux_func_t id_type_info_t<T>::on_remove = [](void* ptr)
|
||||||
{
|
{
|
||||||
std::shared_ptr<void> data;
|
return id_aux_finalize(static_cast<T*>(ptr));
|
||||||
const std::type_info* info;
|
};
|
||||||
const void* type_index;
|
|
||||||
|
|
||||||
template<typename T> inline id_data_t(std::shared_ptr<T> data)
|
using id_type_index_t = const id_aux_func_t*;
|
||||||
: data(std::move(data))
|
|
||||||
, info(&typeid(T))
|
// Get a unique pointer to the on_remove value (will be unique for each type)
|
||||||
, type_index(get_type_index<T>())
|
template<typename T>
|
||||||
{
|
inline constexpr id_type_index_t get_id_type_index()
|
||||||
}
|
{
|
||||||
|
return &id_type_info_t<T>::on_remove;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default ID traits for any arbitrary type
|
||||||
|
template<typename T>
|
||||||
|
struct id_traits
|
||||||
|
{
|
||||||
|
static const auto size = sizeof(T); // forbid forward declarations
|
||||||
|
|
||||||
|
// Get next mapped id (may return 0 if out of IDs)
|
||||||
|
static u32 next_id(u32 raw_id) { return raw_id < 0x80000000 ? (raw_id + 1) & 0x7fffffff : 0; }
|
||||||
|
|
||||||
|
// Convert "public" id to mapped id (may return 0 if invalid)
|
||||||
|
static u32 in_id(u32 id) { return id; }
|
||||||
|
|
||||||
|
// Convert mapped id to "public" id
|
||||||
|
static u32 out_id(u32 raw_id) { return raw_id; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// ID Manager
|
// ID Manager
|
||||||
@ -44,52 +65,55 @@ struct id_data_t final
|
|||||||
// 0x80000000+ : reserved (may be used through id_traits specializations)
|
// 0x80000000+ : reserved (may be used through id_traits specializations)
|
||||||
namespace idm
|
namespace idm
|
||||||
{
|
{
|
||||||
extern std::mutex g_mutex;
|
struct id_data_t final
|
||||||
|
|
||||||
extern std::unordered_map<u32, id_data_t> g_map;
|
|
||||||
|
|
||||||
extern u32 g_last_raw_id;
|
|
||||||
|
|
||||||
thread_local extern u32 g_tls_last_id;
|
|
||||||
|
|
||||||
// can be called from the constructor called through make() or make_ptr() to get the ID of the object being created
|
|
||||||
static inline u32 get_last_id()
|
|
||||||
{
|
{
|
||||||
|
std::shared_ptr<void> data;
|
||||||
|
const std::type_info* info;
|
||||||
|
id_type_index_t type_index;
|
||||||
|
|
||||||
|
template<typename T> id_data_t(const std::shared_ptr<T>& data)
|
||||||
|
: data(data)
|
||||||
|
, info(&typeid(T))
|
||||||
|
, type_index(get_id_type_index<T>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using map_t = std::unordered_map<u32, id_data_t>;
|
||||||
|
|
||||||
|
// Can be called from the constructor called through make() or make_ptr() to get the ID of the object being created
|
||||||
|
inline u32 get_last_id()
|
||||||
|
{
|
||||||
|
extern thread_local u32 g_tls_last_id;
|
||||||
|
|
||||||
return g_tls_last_id;
|
return g_tls_last_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// reinitialize ID manager
|
// Remove all objects
|
||||||
static void clear()
|
void clear();
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
g_map.clear();
|
// Internal
|
||||||
g_last_raw_id = 0;
|
bool check(u32 in_id, id_type_index_t type);
|
||||||
|
|
||||||
|
// Check if an ID of specified type exists
|
||||||
|
template<typename T>
|
||||||
|
bool check(u32 id)
|
||||||
|
{
|
||||||
|
return check(id_traits<T>::in_id(id), get_id_type_index<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if ID of specified type exists
|
// Check if an ID exists and return its type or nullptr
|
||||||
template<typename T> static bool check(u32 id)
|
const std::type_info* get_type(u32 raw_id);
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
template<typename T, typename Ptr>
|
||||||
|
std::shared_ptr<T> add(Ptr&& get_ptr)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
extern std::mutex g_mutex;
|
||||||
|
extern idm::map_t g_map;
|
||||||
const auto found = g_map.find(id_traits<T>::in_id(id));
|
extern u32 g_last_raw_id;
|
||||||
|
extern thread_local u32 g_tls_last_id;
|
||||||
|
|
||||||
return found != g_map.end() && found->second.type_index == get_type_index<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if ID exists and return its type or nullptr
|
|
||||||
inline static const std::type_info* get_type(u32 raw_id)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
const auto found = g_map.find(raw_id);
|
|
||||||
|
|
||||||
return found == g_map.end() ? nullptr : found->second.info;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new ID of specified type with specified constructor arguments (returns object or nullptr)
|
|
||||||
template<typename T, typename... Args> static std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<T>> make_ptr(Args&&... args)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
for (u32 raw_id = g_last_raw_id; (raw_id = id_traits<T>::next_id(raw_id)); /**/)
|
for (u32 raw_id = g_last_raw_id; (raw_id = id_traits<T>::next_id(raw_id)); /**/)
|
||||||
@ -98,7 +122,7 @@ namespace idm
|
|||||||
|
|
||||||
g_tls_last_id = id_traits<T>::out_id(raw_id);
|
g_tls_last_id = id_traits<T>::out_id(raw_id);
|
||||||
|
|
||||||
auto ptr = std::make_shared<T>(std::forward<Args>(args)...);
|
std::shared_ptr<T> ptr = get_ptr();
|
||||||
|
|
||||||
g_map.emplace(raw_id, id_data_t(ptr));
|
g_map.emplace(raw_id, id_data_t(ptr));
|
||||||
|
|
||||||
@ -110,332 +134,302 @@ namespace idm
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add new ID of specified type with specified constructor arguments (returns id)
|
// Add a new ID of specified type with specified constructor arguments (returns object or nullptr)
|
||||||
template<typename T, typename... Args> static std::enable_if_t<std::is_constructible<T, Args...>::value, u32> make(Args&&... args)
|
template<typename T, typename... Args>
|
||||||
|
std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<T>> make_ptr(Args&&... args)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
if (auto ptr = add<T>(WRAP_EXPR(std::make_shared<T>(std::forward<Args>(args)...))))
|
||||||
|
|
||||||
for (u32 raw_id = g_last_raw_id; (raw_id = id_traits<T>::next_id(raw_id)); /**/)
|
|
||||||
{
|
{
|
||||||
if (g_map.find(raw_id) != g_map.end()) continue;
|
id_aux_initialize(ptr.get());
|
||||||
|
return ptr;
|
||||||
g_tls_last_id = id_traits<T>::out_id(raw_id);
|
|
||||||
|
|
||||||
g_map.emplace(raw_id, id_data_t(std::make_shared<T>(std::forward<Args>(args)...)));
|
|
||||||
|
|
||||||
if (raw_id < 0x80000000) g_last_raw_id = raw_id;
|
|
||||||
|
|
||||||
return id_traits<T>::out_id(raw_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw EXCEPTION("Out of IDs");
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add new ID for an existing object provided (don't use for initial object creation)
|
// Add a new ID of specified type with specified constructor arguments (returns id)
|
||||||
template<typename T> static u32 import(const std::shared_ptr<T>& ptr)
|
template<typename T, typename... Args>
|
||||||
|
std::enable_if_t<std::is_constructible<T, Args...>::value, u32> make(Args&&... args)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
if (auto ptr = add<T>(WRAP_EXPR(std::make_shared<T>(std::forward<Args>(args)...))))
|
||||||
|
|
||||||
for (u32 raw_id = g_last_raw_id; (raw_id = id_traits<T>::next_id(raw_id)); /**/)
|
|
||||||
{
|
{
|
||||||
if (g_map.find(raw_id) != g_map.end()) continue;
|
id_aux_initialize(ptr.get());
|
||||||
|
return get_last_id();
|
||||||
g_tls_last_id = id_traits<T>::out_id(raw_id);
|
|
||||||
|
|
||||||
g_map.emplace(raw_id, id_data_t(ptr));
|
|
||||||
|
|
||||||
if (raw_id < 0x80000000) g_last_raw_id = raw_id;
|
|
||||||
|
|
||||||
return id_traits<T>::out_id(raw_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw EXCEPTION("Out of IDs");
|
throw EXCEPTION("Out of IDs ('%s')", typeid(T).name());
|
||||||
}
|
}
|
||||||
|
|
||||||
// get ID of specified type
|
// Add a new ID for an existing object provided (returns new id)
|
||||||
template<typename T> static std::shared_ptr<T> get(u32 id)
|
template<typename T>
|
||||||
|
u32 import(const std::shared_ptr<T>& ptr)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
static const auto size = sizeof(T); // forbid forward declarations
|
||||||
|
|
||||||
const auto found = g_map.find(id_traits<T>::in_id(id));
|
if (add<T>(WRAP_EXPR(ptr)))
|
||||||
|
|
||||||
if (found == g_map.end() || found->second.type_index != get_type_index<T>())
|
|
||||||
{
|
{
|
||||||
return nullptr;
|
id_aux_initialize(ptr.get());
|
||||||
|
return get_last_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::static_pointer_cast<T>(found->second.data);
|
throw EXCEPTION("Out of IDs ('%s')", typeid(T).name());
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all IDs of specified type T (unsorted)
|
// Internal
|
||||||
template<typename T> static std::vector<std::shared_ptr<T>> get_all()
|
std::shared_ptr<void> get(u32 in_id, id_type_index_t type);
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
|
// Get ID of specified type
|
||||||
|
template<typename T>
|
||||||
|
std::shared_ptr<T> get(u32 id)
|
||||||
|
{
|
||||||
|
return std::static_pointer_cast<T>(get(id_traits<T>::in_id(id), get_id_type_index<T>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
idm::map_t get_all(id_type_index_t type);
|
||||||
|
|
||||||
|
// Get all IDs of specified type T (unsorted)
|
||||||
|
template<typename T>
|
||||||
|
std::vector<std::shared_ptr<T>> get_all()
|
||||||
|
{
|
||||||
std::vector<std::shared_ptr<T>> result;
|
std::vector<std::shared_ptr<T>> result;
|
||||||
|
|
||||||
const auto type = get_type_index<T>();
|
for (auto& id : get_all(get_id_type_index<T>()))
|
||||||
|
|
||||||
for (auto& v : g_map)
|
|
||||||
{
|
{
|
||||||
if (v.second.type_index == type)
|
result.emplace_back(std::static_pointer_cast<T>(id.second.data));
|
||||||
{
|
|
||||||
result.emplace_back(std::static_pointer_cast<T>(v.second.data));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove ID created with type T
|
std::shared_ptr<void> withdraw(u32 in_id, id_type_index_t type);
|
||||||
template<typename T> static bool remove(u32 id)
|
|
||||||
|
// Remove the ID created with type T
|
||||||
|
template<typename T>
|
||||||
|
bool remove(u32 id)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
if (auto ptr = withdraw(id_traits<T>::in_id(id), get_id_type_index<T>()))
|
||||||
|
|
||||||
const auto found = g_map.find(id_traits<T>::in_id(id));
|
|
||||||
|
|
||||||
if (found == g_map.end() || found->second.type_index != get_type_index<T>())
|
|
||||||
{
|
{
|
||||||
return false;
|
id_aux_finalize(static_cast<T*>(ptr.get()));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_map.erase(found);
|
return false;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove ID created with type T and return the object
|
// Remove the ID created with type T and return it
|
||||||
template<typename T> static std::shared_ptr<T> withdraw(u32 id)
|
template<typename T>
|
||||||
|
std::shared_ptr<T> withdraw(u32 id)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
if (auto ptr = std::static_pointer_cast<T>(withdraw(id_traits<T>::in_id(id), get_id_type_index<T>())))
|
||||||
|
|
||||||
const auto found = g_map.find(id_traits<T>::in_id(id));
|
|
||||||
|
|
||||||
if (found == g_map.end() || found->second.type_index != get_type_index<T>())
|
|
||||||
{
|
{
|
||||||
return nullptr;
|
id_aux_finalize(ptr.get());
|
||||||
|
|
||||||
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ptr = std::static_pointer_cast<T>(found->second.data);
|
return nullptr;
|
||||||
|
|
||||||
g_map.erase(found);
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> static u32 get_count()
|
u32 get_count(id_type_index_t type);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
u32 get_count()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
return get_count(get_id_type_index<T>());
|
||||||
|
|
||||||
u32 result = 0;
|
|
||||||
|
|
||||||
const auto type = get_type_index<T>();
|
|
||||||
|
|
||||||
for (auto& v : g_map)
|
|
||||||
{
|
|
||||||
if (v.second.type_index == type)
|
|
||||||
{
|
|
||||||
result++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get sorted ID list of specified type
|
// Get sorted list of all IDs of specified type
|
||||||
template<typename T> static std::set<u32> get_set()
|
template<typename T>
|
||||||
|
std::set<u32> get_set()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
std::set<u32> result;
|
std::set<u32> result;
|
||||||
|
|
||||||
const auto type = get_type_index<T>();
|
for (auto& id : get_all(get_id_type_index<T>()))
|
||||||
|
|
||||||
for (auto& v : g_map)
|
|
||||||
{
|
{
|
||||||
if (v.second.type_index == type)
|
result.emplace(id_traits<T>::out_id(id.first));
|
||||||
{
|
|
||||||
result.insert(id_traits<T>::out_id(v.first));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get sorted ID map (ID value -> ID data) of specified type
|
// Get sorted map (ID value -> ID data) of all IDs of specified type
|
||||||
template<typename T> static std::map<u32, std::shared_ptr<T>> get_map()
|
template<typename T>
|
||||||
|
std::map<u32, std::shared_ptr<T>> get_map()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
std::map<u32, std::shared_ptr<T>> result;
|
std::map<u32, std::shared_ptr<T>> result;
|
||||||
|
|
||||||
const auto type = get_type_index<T>();
|
for (auto& id : get_all(get_id_type_index<T>()))
|
||||||
|
|
||||||
for (auto& v : g_map)
|
|
||||||
{
|
{
|
||||||
if (v.second.type_index == type)
|
result[id_traits<T>::out_id(id.first)] = std::static_pointer_cast<T>(id.second.data);
|
||||||
{
|
|
||||||
result[id_traits<T>::out_id(v.first)] = std::static_pointer_cast<T>(v.second.data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Fixed Object Manager
|
// Fixed Object Manager
|
||||||
// allows to manage shared objects of any specified type, but only one object per type;
|
// allows to manage shared objects of any specified type, but only one object per type;
|
||||||
// object are deleted when the emulation is stopped
|
// object are deleted when the emulation is stopped
|
||||||
namespace fxm
|
namespace fxm
|
||||||
{
|
{
|
||||||
extern std::mutex g_mutex;
|
using map_t = std::unordered_map<id_type_index_t, std::shared_ptr<void>>;
|
||||||
|
|
||||||
extern std::unordered_map<const void*, std::shared_ptr<void>> g_map;
|
// Remove all objects
|
||||||
|
void clear();
|
||||||
|
|
||||||
// reinitialize
|
// Internal (returns old and new pointers)
|
||||||
static void clear()
|
template<typename T, bool Always, typename Ptr>
|
||||||
|
std::pair<std::shared_ptr<T>, std::shared_ptr<T>> add(Ptr&& get_ptr)
|
||||||
{
|
{
|
||||||
|
extern std::mutex g_mutex;
|
||||||
|
extern fxm::map_t g_map;
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
g_map.clear();
|
auto& item = g_map[get_id_type_index<T>()];
|
||||||
|
|
||||||
|
if (Always || !item)
|
||||||
|
{
|
||||||
|
std::shared_ptr<T> old = std::static_pointer_cast<T>(std::move(item));
|
||||||
|
std::shared_ptr<T> ptr = get_ptr();
|
||||||
|
|
||||||
|
// Set new object
|
||||||
|
item = ptr;
|
||||||
|
|
||||||
|
return{ std::move(old), std::move(ptr) };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return{ std::static_pointer_cast<T>(item), nullptr };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add fixed object of specified type only if it doesn't exist (one unique object per type may exist)
|
// Create the object (returns nullptr if it already exists)
|
||||||
template<typename T, typename... Args> static std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<T>> make(Args&&... args)
|
template<typename T, typename... Args>
|
||||||
|
std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<T>> make(Args&&... args)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
auto pair = add<T, false>(WRAP_EXPR(std::make_shared<T>(std::forward<Args>(args)...)));
|
||||||
|
|
||||||
const auto index = get_type_index<T>();
|
if (pair.second)
|
||||||
|
|
||||||
const auto found = g_map.find(index);
|
|
||||||
|
|
||||||
// only if object of this type doesn't exist
|
|
||||||
if (found == g_map.end())
|
|
||||||
{
|
{
|
||||||
auto ptr = std::make_shared<T>(std::forward<Args>(args)...);
|
id_aux_initialize(pair.second.get());
|
||||||
|
return std::move(pair.second);
|
||||||
g_map.emplace(index, ptr);
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add fixed object of specified type, replacing previous one if it exists
|
// Create the object unconditionally (old object will be removed if it exists)
|
||||||
template<typename T, typename... Args> static std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<T>> make_always(Args&&... args)
|
template<typename T, typename... Args>
|
||||||
|
std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<T>> make_always(Args&&... args)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
auto pair = add<T, true>(WRAP_EXPR(std::make_shared<T>(std::forward<Args>(args)...)));
|
||||||
|
|
||||||
auto ptr = std::make_shared<T>(std::forward<Args>(args)...);
|
if (pair.first)
|
||||||
|
|
||||||
g_map[get_type_index<T>()] = ptr;
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// import existing fixed object of specified type only if it doesn't exist (don't use)
|
|
||||||
template<typename T> static std::shared_ptr<T> import(std::shared_ptr<T>&& ptr)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
const auto index = get_type_index<T>();
|
|
||||||
|
|
||||||
const auto found = g_map.find(index);
|
|
||||||
|
|
||||||
if (found == g_map.end())
|
|
||||||
{
|
{
|
||||||
g_map.emplace(index, ptr);
|
id_aux_finalize(pair.first.get());
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id_aux_initialize(pair.second.get());
|
||||||
|
return std::move(pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emplace the object
|
||||||
|
template<typename T>
|
||||||
|
bool import(const std::shared_ptr<T>& ptr)
|
||||||
|
{
|
||||||
|
static const auto size = sizeof(T); // forbid forward declarations
|
||||||
|
|
||||||
|
auto pair = add<T, false>(WRAP_EXPR(ptr));
|
||||||
|
|
||||||
|
if (pair.second)
|
||||||
|
{
|
||||||
|
id_aux_initialize(pair.second.get());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emplace the object unconditionally (old object will be removed if it exists)
|
||||||
|
template<typename T>
|
||||||
|
void import_always(const std::shared_ptr<T>& ptr)
|
||||||
|
{
|
||||||
|
static const auto size = sizeof(T); // forbid forward declarations
|
||||||
|
|
||||||
|
auto pair = add<T, true>(WRAP_EXPR(ptr));
|
||||||
|
|
||||||
|
if (pair.first)
|
||||||
|
{
|
||||||
|
id_aux_finalize(pair.first.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
id_aux_initialize(pair.second.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the object unconditionally (create an object if it doesn't exist)
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<T>> get_always(Args&&... args)
|
||||||
|
{
|
||||||
|
auto pair = add<T, false>(WRAP_EXPR(std::make_shared<T>(std::forward<Args>(args)...)));
|
||||||
|
|
||||||
|
if (pair.second)
|
||||||
|
{
|
||||||
|
id_aux_initialize(pair.second.get());
|
||||||
|
return std::move(pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::move(pair.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
bool check(id_type_index_t type);
|
||||||
|
|
||||||
|
// Check whether the object exists
|
||||||
|
template<typename T>
|
||||||
|
bool check()
|
||||||
|
{
|
||||||
|
return check(get_id_type_index<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
std::shared_ptr<void> get(id_type_index_t type);
|
||||||
|
|
||||||
|
// Get the object (returns nullptr if it doesn't exist)
|
||||||
|
template<typename T>
|
||||||
|
std::shared_ptr<T> get()
|
||||||
|
{
|
||||||
|
return std::static_pointer_cast<T>(get(get_id_type_index<T>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
std::shared_ptr<void> withdraw(id_type_index_t type);
|
||||||
|
|
||||||
|
// Delete the object
|
||||||
|
template<typename T>
|
||||||
|
bool remove()
|
||||||
|
{
|
||||||
|
if (auto ptr = withdraw(get_id_type_index<T>()))
|
||||||
|
{
|
||||||
|
id_aux_finalize(static_cast<T*>(ptr.get()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the object and return it
|
||||||
|
template<typename T>
|
||||||
|
std::shared_ptr<T> withdraw()
|
||||||
|
{
|
||||||
|
if (auto ptr = std::static_pointer_cast<T>(withdraw(get_id_type_index<T>())))
|
||||||
|
{
|
||||||
|
id_aux_finalize(ptr.get());
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// import existing fixed object of specified type, replacing previous one if it exists (don't use)
|
|
||||||
template<typename T> static std::shared_ptr<T> import_always(std::shared_ptr<T>&& ptr)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
g_map[get_type_index<T>()] = ptr;
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get fixed object of specified type (always returns an object, it's created if it doesn't exist)
|
|
||||||
template<typename T, typename... Args> static std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<T>> get_always(Args&&... args)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
const auto index = get_type_index<T>();
|
|
||||||
|
|
||||||
const auto found = g_map.find(index);
|
|
||||||
|
|
||||||
if (found == g_map.end())
|
|
||||||
{
|
|
||||||
auto ptr = std::make_shared<T>(std::forward<Args>(args)...);
|
|
||||||
|
|
||||||
g_map[index] = ptr;
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::static_pointer_cast<T>(found->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check whether the object exists
|
|
||||||
template<typename T> static bool check()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
return g_map.find(get_type_index<T>()) != g_map.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
// get fixed object of specified type (returns nullptr if it doesn't exist)
|
|
||||||
template<typename T> static std::shared_ptr<T> get()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
const auto found = g_map.find(get_type_index<T>());
|
|
||||||
|
|
||||||
if (found == g_map.end())
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::static_pointer_cast<T>(found->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove fixed object created with type T
|
|
||||||
template<typename T> static bool remove()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
const auto found = g_map.find(get_type_index<T>());
|
|
||||||
|
|
||||||
if (found == g_map.end())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return g_map.erase(found), true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove fixed object created with type T and return it
|
|
||||||
template<typename T> static std::shared_ptr<T> withdraw()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
|
||||||
|
|
||||||
const auto found = g_map.find(get_type_index<T>());
|
|
||||||
|
|
||||||
if (found == g_map.end())
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ptr = std::static_pointer_cast<T>(std::move(found->second));
|
|
||||||
|
|
||||||
return g_map.erase(found), ptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -90,13 +90,13 @@ namespace vm
|
|||||||
|
|
||||||
std::vector<std::shared_ptr<block_t>> g_locations; // memory locations
|
std::vector<std::shared_ptr<block_t>> g_locations; // memory locations
|
||||||
|
|
||||||
const thread_ctrl_t* const INVALID_THREAD = reinterpret_cast<const thread_ctrl_t*>(~0ull);
|
|
||||||
|
|
||||||
//using reservation_mutex_t = std::mutex;
|
//using reservation_mutex_t = std::mutex;
|
||||||
|
|
||||||
class reservation_mutex_t
|
class reservation_mutex_t
|
||||||
{
|
{
|
||||||
atomic_t<const thread_ctrl_t*> m_owner{ INVALID_THREAD };
|
std::atomic<bool> m_lock{ false };
|
||||||
|
std::thread::id m_owner{};
|
||||||
|
|
||||||
std::condition_variable m_cv;
|
std::condition_variable m_cv;
|
||||||
std::mutex m_mutex;
|
std::mutex m_mutex;
|
||||||
|
|
||||||
@ -105,13 +105,11 @@ namespace vm
|
|||||||
|
|
||||||
never_inline void lock()
|
never_inline void lock()
|
||||||
{
|
{
|
||||||
auto owner = get_current_thread_ctrl();
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(m_mutex, std::defer_lock);
|
std::unique_lock<std::mutex> lock(m_mutex, std::defer_lock);
|
||||||
|
|
||||||
while (!m_owner.compare_and_swap_test(INVALID_THREAD, owner))
|
while (m_lock.exchange(true) == true)
|
||||||
{
|
{
|
||||||
if (m_owner == owner)
|
if (m_owner == std::this_thread::get_id())
|
||||||
{
|
{
|
||||||
throw EXCEPTION("Deadlock");
|
throw EXCEPTION("Deadlock");
|
||||||
}
|
}
|
||||||
@ -125,26 +123,33 @@ namespace vm
|
|||||||
m_cv.wait_for(lock, std::chrono::milliseconds(1));
|
m_cv.wait_for(lock, std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_owner = std::this_thread::get_id();
|
||||||
do_notify = true;
|
do_notify = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
never_inline void unlock()
|
never_inline void unlock()
|
||||||
{
|
{
|
||||||
auto owner = get_current_thread_ctrl();
|
if (m_owner != std::this_thread::get_id())
|
||||||
|
{
|
||||||
|
throw EXCEPTION("Mutex not owned");
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_owner.compare_and_swap_test(owner, INVALID_THREAD))
|
m_owner = {};
|
||||||
|
|
||||||
|
if (m_lock.exchange(false) == false)
|
||||||
{
|
{
|
||||||
throw EXCEPTION("Lost lock");
|
throw EXCEPTION("Lost lock");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (do_notify)
|
if (do_notify)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
m_cv.notify_one();
|
m_cv.notify_one();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const thread_ctrl_t* volatile g_reservation_owner = nullptr;
|
const thread_ctrl* volatile g_reservation_owner = nullptr;
|
||||||
|
|
||||||
u32 g_reservation_addr = 0;
|
u32 g_reservation_addr = 0;
|
||||||
u32 g_reservation_size = 0;
|
u32 g_reservation_size = 0;
|
||||||
@ -352,7 +357,7 @@ namespace vm
|
|||||||
void start()
|
void start()
|
||||||
{
|
{
|
||||||
// start notification thread
|
// start notification thread
|
||||||
named_thread_t(COPY_EXPR("vm::start thread"), []()
|
thread_ctrl::spawn(PURE_EXPR("vm::start thread"s), []()
|
||||||
{
|
{
|
||||||
while (!Emu.IsStopped())
|
while (!Emu.IsStopped())
|
||||||
{
|
{
|
||||||
@ -364,8 +369,7 @@ namespace vm
|
|||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}).detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _reservation_set(u32 addr, bool no_access = false)
|
void _reservation_set(u32 addr, bool no_access = false)
|
||||||
@ -436,9 +440,6 @@ namespace vm
|
|||||||
throw EXCEPTION("Invalid page flags (addr=0x%x, size=0x%x, flags=0x%x)", addr, size, flags);
|
throw EXCEPTION("Invalid page flags (addr=0x%x, size=0x%x, flags=0x%x)", addr, size, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// silent unlocking to prevent priority boost for threads going to break reservation
|
|
||||||
//g_reservation_mutex.do_notify = false;
|
|
||||||
|
|
||||||
// break the reservation
|
// break the reservation
|
||||||
g_tls_did_break_reservation = g_reservation_owner && _reservation_break(g_reservation_addr);
|
g_tls_did_break_reservation = g_reservation_owner && _reservation_break(g_reservation_addr);
|
||||||
|
|
||||||
@ -451,7 +452,7 @@ namespace vm
|
|||||||
// set additional information
|
// set additional information
|
||||||
g_reservation_addr = addr;
|
g_reservation_addr = addr;
|
||||||
g_reservation_size = size;
|
g_reservation_size = size;
|
||||||
g_reservation_owner = get_current_thread_ctrl();
|
g_reservation_owner = thread_ctrl::get_current();
|
||||||
|
|
||||||
// copy data
|
// copy data
|
||||||
std::memcpy(data, vm::base(addr), size);
|
std::memcpy(data, vm::base(addr), size);
|
||||||
@ -468,7 +469,7 @@ namespace vm
|
|||||||
throw EXCEPTION("Invalid arguments (addr=0x%x, size=0x%x)", addr, size);
|
throw EXCEPTION("Invalid arguments (addr=0x%x, size=0x%x)", addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_reservation_owner != get_current_thread_ctrl() || g_reservation_addr != addr || g_reservation_size != size)
|
if (g_reservation_owner != thread_ctrl::get_current() || g_reservation_addr != addr || g_reservation_size != size)
|
||||||
{
|
{
|
||||||
// atomic update failed
|
// atomic update failed
|
||||||
return false;
|
return false;
|
||||||
@ -522,7 +523,7 @@ namespace vm
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool reservation_test(const thread_ctrl_t* current)
|
bool reservation_test(const thread_ctrl* current)
|
||||||
{
|
{
|
||||||
const auto owner = g_reservation_owner;
|
const auto owner = g_reservation_owner;
|
||||||
|
|
||||||
@ -535,7 +536,7 @@ namespace vm
|
|||||||
{
|
{
|
||||||
std::lock_guard<reservation_mutex_t> lock(g_reservation_mutex);
|
std::lock_guard<reservation_mutex_t> lock(g_reservation_mutex);
|
||||||
|
|
||||||
if (g_reservation_owner && g_reservation_owner == get_current_thread_ctrl())
|
if (g_reservation_owner && g_reservation_owner == thread_ctrl::get_current())
|
||||||
{
|
{
|
||||||
g_tls_did_break_reservation = _reservation_break(g_reservation_addr);
|
g_tls_did_break_reservation = _reservation_break(g_reservation_addr);
|
||||||
}
|
}
|
||||||
@ -556,7 +557,7 @@ namespace vm
|
|||||||
g_tls_did_break_reservation = false;
|
g_tls_did_break_reservation = false;
|
||||||
|
|
||||||
// check and possibly break previous reservation
|
// check and possibly break previous reservation
|
||||||
if (g_reservation_owner != get_current_thread_ctrl() || g_reservation_addr != addr || g_reservation_size != size)
|
if (g_reservation_owner != thread_ctrl::get_current() || g_reservation_addr != addr || g_reservation_size != size)
|
||||||
{
|
{
|
||||||
if (g_reservation_owner)
|
if (g_reservation_owner)
|
||||||
{
|
{
|
||||||
@ -572,7 +573,7 @@ namespace vm
|
|||||||
// set additional information
|
// set additional information
|
||||||
g_reservation_addr = addr;
|
g_reservation_addr = addr;
|
||||||
g_reservation_size = size;
|
g_reservation_size = size;
|
||||||
g_reservation_owner = get_current_thread_ctrl();
|
g_reservation_owner = thread_ctrl::get_current();
|
||||||
|
|
||||||
// may not be necessary
|
// may not be necessary
|
||||||
_mm_mfence();
|
_mm_mfence();
|
||||||
@ -783,13 +784,13 @@ namespace vm
|
|||||||
|
|
||||||
if (!block)
|
if (!block)
|
||||||
{
|
{
|
||||||
LOG_ERROR(MEMORY, "%s(): invalid memory location (%d, addr=0x%x)\n", __func__, location, addr);
|
LOG_ERROR(MEMORY, "vm::dealloc(): invalid memory location (%d, addr=0x%x)\n", location, addr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!block->dealloc(addr))
|
if (!block->dealloc(addr))
|
||||||
{
|
{
|
||||||
LOG_ERROR(MEMORY, "%s(): deallocation failed (addr=0x%x)\n", __func__, addr);
|
LOG_ERROR(MEMORY, "vm::dealloc(): deallocation failed (addr=0x%x)\n", addr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Utilities/Thread.h"
|
||||||
const class thread_ctrl_t* get_current_thread_ctrl();
|
|
||||||
|
|
||||||
class named_thread_t;
|
|
||||||
|
|
||||||
namespace vm
|
namespace vm
|
||||||
{
|
{
|
||||||
@ -122,7 +119,7 @@ namespace vm
|
|||||||
bool reservation_query(u32 addr, u32 size, bool is_writing, std::function<bool()> callback);
|
bool reservation_query(u32 addr, u32 size, bool is_writing, std::function<bool()> callback);
|
||||||
|
|
||||||
// Returns true if the current thread owns reservation
|
// Returns true if the current thread owns reservation
|
||||||
bool reservation_test(const thread_ctrl_t* current = get_current_thread_ctrl());
|
bool reservation_test(const thread_ctrl* current = thread_ctrl::get_current());
|
||||||
|
|
||||||
// Break all reservations created by the current thread
|
// Break all reservations created by the current thread
|
||||||
void reservation_free();
|
void reservation_free();
|
||||||
|
@ -232,11 +232,11 @@ D3D12GSRender::~D3D12GSRender()
|
|||||||
release_d2d_structures();
|
release_d2d_structures();
|
||||||
}
|
}
|
||||||
|
|
||||||
void D3D12GSRender::onexit_thread()
|
void D3D12GSRender::on_exit()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool D3D12GSRender::domethod(u32 cmd, u32 arg)
|
bool D3D12GSRender::do_method(u32 cmd, u32 arg)
|
||||||
{
|
{
|
||||||
switch (cmd)
|
switch (cmd)
|
||||||
{
|
{
|
||||||
|
@ -205,8 +205,8 @@ private:
|
|||||||
void copy_render_target_to_dma_location();
|
void copy_render_target_to_dma_location();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void onexit_thread() override;
|
virtual void on_exit() override;
|
||||||
virtual bool domethod(u32 cmd, u32 arg) override;
|
virtual bool do_method(u32 cmd, u32 arg) override;
|
||||||
virtual void end() override;
|
virtual void end() override;
|
||||||
virtual void flip(int buffer) override;
|
virtual void flip(int buffer) override;
|
||||||
|
|
||||||
|
@ -1046,9 +1046,9 @@ void GLGSRender::end()
|
|||||||
rsx::thread::end();
|
rsx::thread::end();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGSRender::oninit_thread()
|
void GLGSRender::on_init_thread()
|
||||||
{
|
{
|
||||||
GSRender::oninit_thread();
|
GSRender::on_init_thread();
|
||||||
|
|
||||||
gl::init();
|
gl::init();
|
||||||
LOG_NOTICE(Log::RSX, (const char*)glGetString(GL_VERSION));
|
LOG_NOTICE(Log::RSX, (const char*)glGetString(GL_VERSION));
|
||||||
@ -1071,7 +1071,7 @@ void GLGSRender::oninit_thread()
|
|||||||
m_vao.element_array_buffer = m_ebo;
|
m_vao.element_array_buffer = m_ebo;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGSRender::onexit_thread()
|
void GLGSRender::on_exit()
|
||||||
{
|
{
|
||||||
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
|
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
|
||||||
|
|
||||||
@ -1184,7 +1184,7 @@ static const std::unordered_map<u32, rsx_method_impl_t> g_gl_method_tbl =
|
|||||||
{ NV4097_CLEAR_SURFACE, nv4097_clear_surface }
|
{ NV4097_CLEAR_SURFACE, nv4097_clear_surface }
|
||||||
};
|
};
|
||||||
|
|
||||||
bool GLGSRender::domethod(u32 cmd, u32 arg)
|
bool GLGSRender::do_method(u32 cmd, u32 arg)
|
||||||
{
|
{
|
||||||
auto found = g_gl_method_tbl.find(cmd);
|
auto found = g_gl_method_tbl.find(cmd);
|
||||||
|
|
||||||
|
@ -100,9 +100,9 @@ protected:
|
|||||||
void begin() override;
|
void begin() override;
|
||||||
void end() override;
|
void end() override;
|
||||||
|
|
||||||
void oninit_thread() override;
|
void on_init_thread() override;
|
||||||
void onexit_thread() override;
|
void on_exit() override;
|
||||||
bool domethod(u32 id, u32 arg) override;
|
bool do_method(u32 id, u32 arg) override;
|
||||||
void flip(int buffer) override;
|
void flip(int buffer) override;
|
||||||
u64 timestamp() const override;
|
u64 timestamp() const override;
|
||||||
};
|
};
|
||||||
|
@ -22,23 +22,19 @@ void GSInfo::Init()
|
|||||||
mode.pitch = 4;
|
mode.pitch = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
GSManager::GSManager() : m_render(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void GSManager::Init()
|
void GSManager::Init()
|
||||||
{
|
{
|
||||||
if(m_render) return;
|
if (m_render) return;
|
||||||
|
|
||||||
m_info.Init();
|
m_info.Init();
|
||||||
|
|
||||||
switch (rpcs3::state.config.rsx.renderer.value())
|
switch (rpcs3::state.config.rsx.renderer.value())
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case rsx_renderer_type::Null : m_render = new NullGSRender(); break;
|
case rsx_renderer_type::Null : m_render = std::make_shared<NullGSRender>(); break;
|
||||||
case rsx_renderer_type::OpenGL: m_render = new GLGSRender(); break;
|
case rsx_renderer_type::OpenGL: m_render = std::make_shared<GLGSRender>(); break;
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
case rsx_renderer_type::DX12: m_render = new D3D12GSRender(); break;
|
case rsx_renderer_type::DX12: m_render = std::make_shared<D3D12GSRender>(); break;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,12 +43,7 @@ void GSManager::Init()
|
|||||||
|
|
||||||
void GSManager::Close()
|
void GSManager::Close()
|
||||||
{
|
{
|
||||||
if(m_render)
|
m_render.reset();
|
||||||
{
|
|
||||||
m_render->close();
|
|
||||||
delete m_render;
|
|
||||||
m_render = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u8 GSManager::GetState()
|
u8 GSManager::GetState()
|
||||||
|
@ -25,18 +25,16 @@ struct GSInfo
|
|||||||
class GSManager
|
class GSManager
|
||||||
{
|
{
|
||||||
GSInfo m_info;
|
GSInfo m_info;
|
||||||
GSRender* m_render;
|
std::shared_ptr<GSRender> m_render;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GSManager();
|
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
bool IsInited() const { return m_render != nullptr; }
|
bool IsInited() const { return m_render != nullptr; }
|
||||||
|
|
||||||
GSInfo& GetInfo() { return m_info; }
|
GSInfo& GetInfo() { return m_info; }
|
||||||
GSRender& GetRender() { assert(m_render); return *m_render; }
|
GSRender& GetRender() { return *m_render; }
|
||||||
|
|
||||||
u8 GetState();
|
u8 GetState();
|
||||||
u8 GetColorSpace();
|
u8 GetColorSpace();
|
||||||
|
@ -31,11 +31,12 @@ GSRender::~GSRender()
|
|||||||
|
|
||||||
if (m_frame)
|
if (m_frame)
|
||||||
{
|
{
|
||||||
|
m_frame->hide();
|
||||||
m_frame->close();
|
m_frame->close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GSRender::oninit()
|
void GSRender::on_init()
|
||||||
{
|
{
|
||||||
if (m_frame)
|
if (m_frame)
|
||||||
{
|
{
|
||||||
@ -43,7 +44,7 @@ void GSRender::oninit()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GSRender::oninit_thread()
|
void GSRender::on_init_thread()
|
||||||
{
|
{
|
||||||
if (m_frame)
|
if (m_frame)
|
||||||
{
|
{
|
||||||
@ -52,21 +53,10 @@ void GSRender::oninit_thread()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GSRender::close()
|
|
||||||
{
|
|
||||||
if (m_frame && m_frame->shown())
|
|
||||||
{
|
|
||||||
m_frame->hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (joinable())
|
|
||||||
{
|
|
||||||
join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GSRender::flip(int buffer)
|
void GSRender::flip(int buffer)
|
||||||
{
|
{
|
||||||
if (m_frame)
|
if (m_frame)
|
||||||
|
{
|
||||||
m_frame->flip(m_context);
|
m_frame->flip(m_context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,9 +49,8 @@ public:
|
|||||||
GSRender(frame_type type);
|
GSRender(frame_type type);
|
||||||
virtual ~GSRender();
|
virtual ~GSRender();
|
||||||
|
|
||||||
void oninit() override;
|
void on_init() override;
|
||||||
void oninit_thread() override;
|
void on_init_thread() override;
|
||||||
|
|
||||||
void close();
|
|
||||||
void flip(int buffer) override;
|
void flip(int buffer) override;
|
||||||
};
|
};
|
||||||
|
@ -6,11 +6,7 @@ NullGSRender::NullGSRender() : GSRender(frame_type::Null)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void NullGSRender::onexit_thread()
|
bool NullGSRender::do_method(u32 cmd, u32 value)
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NullGSRender::domethod(u32 cmd, u32 value)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ public:
|
|||||||
NullGSRender();
|
NullGSRender();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onexit_thread() override;
|
bool do_method(u32 cmd, u32 value) override;
|
||||||
bool domethod(u32 cmd, u32 value) override;
|
|
||||||
};
|
};
|
||||||
|
@ -689,7 +689,7 @@ namespace rsx
|
|||||||
static void wrapper(thread *rsx, u32 arg)
|
static void wrapper(thread *rsx, u32 arg)
|
||||||
{
|
{
|
||||||
// try process using gpu
|
// try process using gpu
|
||||||
if (rsx->domethod(id, arg))
|
if (rsx->do_method(id, arg))
|
||||||
{
|
{
|
||||||
if (rsx->capture_current_frame && id == NV4097_CLEAR_SURFACE)
|
if (rsx->capture_current_frame && id == NV4097_CLEAR_SURFACE)
|
||||||
rsx->capture_frame("clear");
|
rsx->capture_frame("clear");
|
||||||
@ -974,25 +974,23 @@ namespace rsx
|
|||||||
capture_frame("Draw " + std::to_string(vertex_draw_count));
|
capture_frame("Draw " + std::to_string(vertex_draw_count));
|
||||||
}
|
}
|
||||||
|
|
||||||
void thread::task()
|
void thread::on_task()
|
||||||
{
|
{
|
||||||
u8 inc;
|
on_init_thread();
|
||||||
LOG_NOTICE(RSX, "RSX thread started");
|
|
||||||
|
|
||||||
oninit_thread();
|
reset();
|
||||||
|
|
||||||
last_flip_time = get_system_time() - 1000000;
|
last_flip_time = get_system_time() - 1000000;
|
||||||
|
|
||||||
autojoin_thread_t vblank(WRAP_EXPR("VBlank Thread"), [this]()
|
scope_thread_t vblank(PURE_EXPR("VBlank Thread"s), [this]()
|
||||||
{
|
{
|
||||||
const u64 start_time = get_system_time();
|
const u64 start_time = get_system_time();
|
||||||
|
|
||||||
vblank_count = 0;
|
vblank_count = 0;
|
||||||
|
|
||||||
while (joinable())
|
// TODO: exit condition
|
||||||
|
while (!Emu.IsStopped())
|
||||||
{
|
{
|
||||||
CHECK_EMU_STATUS;
|
|
||||||
|
|
||||||
if (get_system_time() - start_time > vblank_count * 1000000 / 60)
|
if (get_system_time() - start_time > vblank_count * 1000000 / 60)
|
||||||
{
|
{
|
||||||
vblank_count++;
|
vblank_count++;
|
||||||
@ -1012,107 +1010,87 @@ namespace rsx
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reset();
|
// TODO: exit condition
|
||||||
|
while (true)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
while (joinable())
|
CHECK_EMU_STATUS;
|
||||||
|
|
||||||
|
be_t<u32> get = ctrl->get;
|
||||||
|
be_t<u32> put = ctrl->put;
|
||||||
|
|
||||||
|
if (put == get || !Emu.IsRunning())
|
||||||
{
|
{
|
||||||
//TODO: async mode
|
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack
|
||||||
if (Emu.IsStopped())
|
continue;
|
||||||
{
|
|
||||||
LOG_WARNING(RSX, "RSX thread aborted");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
inc = 1;
|
|
||||||
|
|
||||||
be_t<u32> get = ctrl->get;
|
|
||||||
be_t<u32> put = ctrl->put;
|
|
||||||
|
|
||||||
if (put == get || !Emu.IsRunning())
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // hack
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const u32 cmd = ReadIO32(get);
|
|
||||||
const u32 count = (cmd >> 18) & 0x7ff;
|
|
||||||
|
|
||||||
if (cmd & CELL_GCM_METHOD_FLAG_JUMP)
|
|
||||||
{
|
|
||||||
u32 offs = cmd & 0x1fffffff;
|
|
||||||
//LOG_WARNING(RSX, "rsx jump(0x%x) #addr=0x%x, cmd=0x%x, get=0x%x, put=0x%x", offs, m_ioAddress + get, cmd, get, put);
|
|
||||||
ctrl->get = offs;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cmd & CELL_GCM_METHOD_FLAG_CALL)
|
|
||||||
{
|
|
||||||
m_call_stack.push(get + 4);
|
|
||||||
u32 offs = cmd & ~3;
|
|
||||||
//LOG_WARNING(RSX, "rsx call(0x%x) #0x%x - 0x%x", offs, cmd, get);
|
|
||||||
ctrl->get = offs;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cmd == CELL_GCM_METHOD_FLAG_RETURN)
|
|
||||||
{
|
|
||||||
u32 get = m_call_stack.top();
|
|
||||||
m_call_stack.pop();
|
|
||||||
//LOG_WARNING(RSX, "rsx return(0x%x)", get);
|
|
||||||
ctrl->get = get;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cmd & CELL_GCM_METHOD_FLAG_NON_INCREMENT)
|
|
||||||
{
|
|
||||||
//LOG_WARNING(RSX, "rsx non increment cmd! 0x%x", cmd);
|
|
||||||
inc = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmd == 0) //nop
|
|
||||||
{
|
|
||||||
ctrl->get = get + 4;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto args = vm::ptr<u32>::make((u32)RSXIOMem.RealAddr(get + 4));
|
|
||||||
|
|
||||||
u32 first_cmd = (cmd & 0xffff) >> 2;
|
|
||||||
|
|
||||||
if (cmd & 0x3)
|
|
||||||
{
|
|
||||||
LOG_WARNING(Log::RSX, "unaligned command: %s (0x%x from 0x%x)", get_method_name(first_cmd).c_str(), first_cmd, cmd & 0xffff);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (u32 i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
u32 reg = first_cmd + (i * inc);
|
|
||||||
u32 value = args[i];
|
|
||||||
|
|
||||||
if (rpcs3::config.misc.log.rsx_logging.value())
|
|
||||||
{
|
|
||||||
LOG_NOTICE(Log::RSX, "%s(0x%x) = 0x%x", get_method_name(reg).c_str(), reg, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
method_registers[reg] = value;
|
|
||||||
if (capture_current_frame)
|
|
||||||
frame_debug.command_queue.push_back(std::make_pair(reg, value));
|
|
||||||
|
|
||||||
if (auto method = methods[reg])
|
|
||||||
method(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctrl->get = get + (count + 1) * 4;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (const std::exception& ex)
|
|
||||||
{
|
|
||||||
LOG_ERROR(Log::RSX, ex.what());
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_NOTICE(RSX, "RSX thread ended");
|
const u32 cmd = ReadIO32(get);
|
||||||
|
const u32 count = (cmd >> 18) & 0x7ff;
|
||||||
|
|
||||||
onexit_thread();
|
if (cmd & CELL_GCM_METHOD_FLAG_JUMP)
|
||||||
|
{
|
||||||
|
u32 offs = cmd & 0x1fffffff;
|
||||||
|
//LOG_WARNING(RSX, "rsx jump(0x%x) #addr=0x%x, cmd=0x%x, get=0x%x, put=0x%x", offs, m_ioAddress + get, cmd, get, put);
|
||||||
|
ctrl->get = offs;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cmd & CELL_GCM_METHOD_FLAG_CALL)
|
||||||
|
{
|
||||||
|
m_call_stack.push(get + 4);
|
||||||
|
u32 offs = cmd & ~3;
|
||||||
|
//LOG_WARNING(RSX, "rsx call(0x%x) #0x%x - 0x%x", offs, cmd, get);
|
||||||
|
ctrl->get = offs;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cmd == CELL_GCM_METHOD_FLAG_RETURN)
|
||||||
|
{
|
||||||
|
u32 get = m_call_stack.top();
|
||||||
|
m_call_stack.pop();
|
||||||
|
//LOG_WARNING(RSX, "rsx return(0x%x)", get);
|
||||||
|
ctrl->get = get;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == 0) //nop
|
||||||
|
{
|
||||||
|
ctrl->get = get + 4;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto args = vm::ptr<u32>::make((u32)RSXIOMem.RealAddr(get + 4));
|
||||||
|
|
||||||
|
u32 first_cmd = (cmd & 0xffff) >> 2;
|
||||||
|
|
||||||
|
if (cmd & 0x3)
|
||||||
|
{
|
||||||
|
LOG_WARNING(Log::RSX, "unaligned command: %s (0x%x from 0x%x)", get_method_name(first_cmd).c_str(), first_cmd, cmd & 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
u32 reg = cmd & CELL_GCM_METHOD_FLAG_NON_INCREMENT ? first_cmd : first_cmd + i;
|
||||||
|
u32 value = args[i];
|
||||||
|
|
||||||
|
if (rpcs3::config.misc.log.rsx_logging.value())
|
||||||
|
{
|
||||||
|
LOG_NOTICE(Log::RSX, "%s(0x%x) = 0x%x", get_method_name(reg).c_str(), reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
method_registers[reg] = value;
|
||||||
|
if (capture_current_frame)
|
||||||
|
frame_debug.command_queue.push_back(std::make_pair(reg, value));
|
||||||
|
|
||||||
|
if (auto method = methods[reg])
|
||||||
|
method(this, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl->get = get + (count + 1) * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string thread::get_name() const
|
||||||
|
{
|
||||||
|
return "rsx::thread"s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void thread::fill_scale_offset_data(void *buffer, bool is_d3d) const noexcept
|
void thread::fill_scale_offset_data(void *buffer, bool is_d3d) const noexcept
|
||||||
@ -1240,8 +1218,8 @@ namespace rsx
|
|||||||
|
|
||||||
m_used_gcm_commands.clear();
|
m_used_gcm_commands.clear();
|
||||||
|
|
||||||
oninit();
|
on_init();
|
||||||
named_thread_t::start(WRAP_EXPR("rsx::thread"), WRAP_EXPR(task()));
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 thread::ReadIO32(u32 addr)
|
u32 thread::ReadIO32(u32 addr)
|
||||||
|
@ -255,7 +255,7 @@ namespace rsx
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class thread : protected named_thread_t
|
class thread : public named_thread_t
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
std::stack<u32> m_call_stack;
|
std::stack<u32> m_call_stack;
|
||||||
@ -327,19 +327,20 @@ namespace rsx
|
|||||||
protected:
|
protected:
|
||||||
virtual ~thread() {}
|
virtual ~thread() {}
|
||||||
|
|
||||||
|
virtual void on_task() override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
virtual std::string get_name() const override;
|
||||||
|
|
||||||
virtual void begin();
|
virtual void begin();
|
||||||
virtual void end();
|
virtual void end();
|
||||||
|
|
||||||
virtual void oninit() = 0;
|
virtual void on_init() = 0;
|
||||||
virtual void oninit_thread() = 0;
|
virtual void on_init_thread() = 0;
|
||||||
virtual void onexit_thread() = 0;
|
virtual bool do_method(u32 cmd, u32 value) { return false; }
|
||||||
virtual bool domethod(u32 cmd, u32 value) { return false; }
|
|
||||||
virtual void flip(int buffer) = 0;
|
virtual void flip(int buffer) = 0;
|
||||||
virtual u64 timestamp() const;
|
virtual u64 timestamp() const;
|
||||||
|
|
||||||
void task();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill buffer with 4x4 scale offset matrix.
|
* Fill buffer with 4x4 scale offset matrix.
|
||||||
* Vertex shader's position is to be multiplied by this matrix.
|
* Vertex shader's position is to be multiplied by this matrix.
|
||||||
|
@ -20,6 +20,8 @@ extern u64 get_system_time();
|
|||||||
|
|
||||||
AudioConfig g_audio;
|
AudioConfig g_audio;
|
||||||
|
|
||||||
|
std::shared_ptr<thread_ctrl> g_audio_thread;
|
||||||
|
|
||||||
s32 cellAudioInit()
|
s32 cellAudioInit()
|
||||||
{
|
{
|
||||||
cellAudio.Warning("cellAudioInit()");
|
cellAudio.Warning("cellAudioInit()");
|
||||||
@ -48,17 +50,9 @@ s32 cellAudioInit()
|
|||||||
std::memset(vm::base(g_audio.buffer), 0, AUDIO_PORT_OFFSET * AUDIO_PORT_COUNT);
|
std::memset(vm::base(g_audio.buffer), 0, AUDIO_PORT_OFFSET * AUDIO_PORT_COUNT);
|
||||||
std::memset(vm::base(g_audio.indexes), 0, sizeof32(u64) * AUDIO_PORT_COUNT);
|
std::memset(vm::base(g_audio.indexes), 0, sizeof32(u64) * AUDIO_PORT_COUNT);
|
||||||
|
|
||||||
// check thread status
|
|
||||||
if (g_audio.thread.joinable())
|
|
||||||
{
|
|
||||||
g_audio.thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
// start audio thread
|
// start audio thread
|
||||||
g_audio.thread.start(WRAP_EXPR("Audio Thread"), []()
|
g_audio_thread = thread_ctrl::spawn(PURE_EXPR("Audio Thread"s), []()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(g_audio.thread.mutex);
|
|
||||||
|
|
||||||
const bool do_dump = rpcs3::config.audio.dump_to_file.value();
|
const bool do_dump = rpcs3::config.audio.dump_to_file.value();
|
||||||
|
|
||||||
AudioDumper m_dump;
|
AudioDumper m_dump;
|
||||||
@ -81,9 +75,9 @@ s32 cellAudioInit()
|
|||||||
|
|
||||||
squeue_t<float*, BUFFER_NUM - 1> out_queue;
|
squeue_t<float*, BUFFER_NUM - 1> out_queue;
|
||||||
|
|
||||||
autojoin_thread_t iat(WRAP_EXPR("Internal Audio Thread"), [&out_queue]()
|
scope_thread_t iat(PURE_EXPR("Internal Audio Thread"s), [&out_queue]()
|
||||||
{
|
{
|
||||||
const bool use_u16 = rpcs3::config.audio.convert_to_u16.value();;
|
const bool use_u16 = rpcs3::config.audio.convert_to_u16.value();
|
||||||
|
|
||||||
Emu.GetAudioManager().GetAudioOut().Init();
|
Emu.GetAudioManager().GetAudioOut().Init();
|
||||||
|
|
||||||
@ -141,7 +135,7 @@ s32 cellAudioInit()
|
|||||||
{
|
{
|
||||||
if (Emu.IsPaused())
|
if (Emu.IsPaused())
|
||||||
{
|
{
|
||||||
g_audio.thread.cv.wait_for(lock, std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(1ms); // hack
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +149,7 @@ s32 cellAudioInit()
|
|||||||
const u64 expected_time = g_audio.counter * AUDIO_SAMPLES * 1000000 / 48000;
|
const u64 expected_time = g_audio.counter * AUDIO_SAMPLES * 1000000 / 48000;
|
||||||
if (expected_time >= time_pos)
|
if (expected_time >= time_pos)
|
||||||
{
|
{
|
||||||
g_audio.thread.cv.wait_for(lock, std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(1ms); // hack
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,6 +362,8 @@ s32 cellAudioInit()
|
|||||||
|
|
||||||
LV2_LOCK;
|
LV2_LOCK;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(g_audio.mutex);
|
||||||
|
|
||||||
for (auto key : g_audio.keys)
|
for (auto key : g_audio.keys)
|
||||||
{
|
{
|
||||||
if (const auto queue = Emu.GetEventManager().GetEventQueue(key))
|
if (const auto queue = Emu.GetEventManager().GetEventQueue(key))
|
||||||
@ -419,8 +415,10 @@ s32 cellAudioQuit()
|
|||||||
return CELL_AUDIO_ERROR_NOT_INIT;
|
return CELL_AUDIO_ERROR_NOT_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_audio.thread.join();
|
g_audio_thread->join();
|
||||||
|
g_audio_thread.reset();
|
||||||
g_audio.state.exchange(AUDIO_STATE_NOT_INITIALIZED);
|
g_audio.state.exchange(AUDIO_STATE_NOT_INITIALIZED);
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -653,8 +651,6 @@ s32 cellAudioGetPortTimestamp(u32 portNum, u64 tag, vm::ptr<u64> stamp)
|
|||||||
|
|
||||||
// TODO: check tag (CELL_AUDIO_ERROR_TAG_NOT_FOUND error)
|
// TODO: check tag (CELL_AUDIO_ERROR_TAG_NOT_FOUND error)
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(g_audio.thread.mutex);
|
|
||||||
|
|
||||||
*stamp = g_audio.start_time + Emu.GetPauseTime() + (port.counter + (tag - port.tag)) * 256000000 / 48000;
|
*stamp = g_audio.start_time + Emu.GetPauseTime() + (port.counter + (tag - port.tag)) * 256000000 / 48000;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
@ -686,8 +682,6 @@ s32 cellAudioGetPortBlockTag(u32 portNum, u64 blockNo, vm::ptr<u64> tag)
|
|||||||
return CELL_AUDIO_ERROR_PARAM;
|
return CELL_AUDIO_ERROR_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(g_audio.thread.mutex);
|
|
||||||
|
|
||||||
u64 tag_base = port.tag;
|
u64 tag_base = port.tag;
|
||||||
if (tag_base % port.block > blockNo)
|
if (tag_base % port.block > blockNo)
|
||||||
{
|
{
|
||||||
@ -780,7 +774,7 @@ s32 cellAudioSetNotifyEventQueue(u64 key)
|
|||||||
return CELL_AUDIO_ERROR_NOT_INIT;
|
return CELL_AUDIO_ERROR_NOT_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(g_audio.thread.mutex);
|
std::lock_guard<std::mutex> lock(g_audio.mutex);
|
||||||
|
|
||||||
for (auto k : g_audio.keys) // check for duplicates
|
for (auto k : g_audio.keys) // check for duplicates
|
||||||
{
|
{
|
||||||
@ -813,7 +807,7 @@ s32 cellAudioRemoveNotifyEventQueue(u64 key)
|
|||||||
return CELL_AUDIO_ERROR_NOT_INIT;
|
return CELL_AUDIO_ERROR_NOT_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(g_audio.thread.mutex);
|
std::lock_guard<std::mutex> lock(g_audio.mutex);
|
||||||
|
|
||||||
for (auto i = g_audio.keys.begin(); i != g_audio.keys.end(); i++)
|
for (auto i = g_audio.keys.begin(); i != g_audio.keys.end(); i++)
|
||||||
{
|
{
|
||||||
|
@ -99,9 +99,10 @@ enum AudioPortState : u32
|
|||||||
|
|
||||||
struct AudioPortConfig
|
struct AudioPortConfig
|
||||||
{
|
{
|
||||||
std::mutex mutex;
|
|
||||||
atomic_t<AudioPortState> state;
|
atomic_t<AudioPortState> state;
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
u32 channel;
|
u32 channel;
|
||||||
u32 block;
|
u32 block;
|
||||||
u64 attr;
|
u64 attr;
|
||||||
@ -124,7 +125,8 @@ struct AudioPortConfig
|
|||||||
struct AudioConfig final // custom structure
|
struct AudioConfig final // custom structure
|
||||||
{
|
{
|
||||||
atomic_t<AudioState> state;
|
atomic_t<AudioState> state;
|
||||||
named_thread_t thread;
|
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
AudioPortConfig ports[AUDIO_PORT_COUNT];
|
AudioPortConfig ports[AUDIO_PORT_COUNT];
|
||||||
u32 buffer; // 1 MB memory for audio ports
|
u32 buffer; // 1 MB memory for audio ports
|
||||||
@ -133,16 +135,6 @@ struct AudioConfig final // custom structure
|
|||||||
u64 start_time;
|
u64 start_time;
|
||||||
std::vector<u64> keys;
|
std::vector<u64> keys;
|
||||||
|
|
||||||
AudioConfig() = default;
|
|
||||||
|
|
||||||
~AudioConfig()
|
|
||||||
{
|
|
||||||
if (thread.joinable())
|
|
||||||
{
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 open_port()
|
u32 open_port()
|
||||||
{
|
{
|
||||||
for (u32 i = 0; i < AUDIO_PORT_COUNT; i++)
|
for (u32 i = 0; i < AUDIO_PORT_COUNT; i++)
|
||||||
|
@ -494,7 +494,7 @@ s32 cellFsStReadStart(u32 fd, u64 offset, u64 size)
|
|||||||
|
|
||||||
file->st_read_size = size;
|
file->st_read_size = size;
|
||||||
|
|
||||||
file->st_thread.start(COPY_EXPR(fmt::format("FS ST Thread[0x%x]", fd)), [=]()
|
file->st_thread = thread_ctrl::spawn(PURE_EXPR("FS ST Thread"s), [=]()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(file->mutex);
|
std::unique_lock<std::mutex> lock(file->mutex);
|
||||||
|
|
||||||
@ -572,7 +572,7 @@ s32 cellFsStReadStop(u32 fd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
file->cv.notify_all();
|
file->cv.notify_all();
|
||||||
file->st_thread.join();
|
file->st_thread->join();
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
@ -937,7 +937,7 @@ s32 cellFsAioRead(vm::ptr<CellFsAio> aio, vm::ptr<s32> id, fs_aio_cb_t func)
|
|||||||
|
|
||||||
const s32 xid = (*id = ++g_fs_aio_id);
|
const s32 xid = (*id = ++g_fs_aio_id);
|
||||||
|
|
||||||
named_thread_t(WRAP_EXPR("FS AIO Read Thread"), [=]{ fsAio(aio, false, xid, func); }).detach();
|
thread_ctrl::spawn(PURE_EXPR("FS AIO Read Thread"s), COPY_EXPR(fsAio(aio, false, xid, func)));
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
@ -950,7 +950,7 @@ s32 cellFsAioWrite(vm::ptr<CellFsAio> aio, vm::ptr<s32> id, fs_aio_cb_t func)
|
|||||||
|
|
||||||
const s32 xid = (*id = ++g_fs_aio_id);
|
const s32 xid = (*id = ++g_fs_aio_id);
|
||||||
|
|
||||||
named_thread_t(WRAP_EXPR("FS AIO Write Thread"), [=]{ fsAio(aio, true, xid, func); }).detach();
|
thread_ctrl::spawn(PURE_EXPR("FS AIO Write Thread"s), COPY_EXPR(fsAio(aio, true, xid, func)));
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
@ -61,9 +61,9 @@ s32 cellMsgDialogOpen2(u32 type, vm::cptr<char> msgString, vm::ptr<CellMsgDialog
|
|||||||
default: return CELL_MSGDIALOG_ERROR_PARAM;
|
default: return CELL_MSGDIALOG_ERROR_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto dlg = fxm::import<MsgDialogBase>(Emu.GetCallbacks().get_msg_dialog());
|
const std::shared_ptr<MsgDialogBase> dlg(Emu.GetCallbacks().get_msg_dialog());
|
||||||
|
|
||||||
if (!dlg)
|
if (!fxm::import(dlg))
|
||||||
{
|
{
|
||||||
return CELL_SYSUTIL_ERROR_BUSY;
|
return CELL_SYSUTIL_ERROR_BUSY;
|
||||||
}
|
}
|
||||||
@ -206,18 +206,17 @@ s32 cellMsgDialogClose(f32 delay)
|
|||||||
|
|
||||||
const u64 wait_until = get_system_time() + static_cast<s64>(std::max<float>(delay, 0.0f) * 1000);
|
const u64 wait_until = get_system_time() + static_cast<s64>(std::max<float>(delay, 0.0f) * 1000);
|
||||||
|
|
||||||
named_thread_t(WRAP_EXPR("MsgDialog Thread"), [=]()
|
thread_ctrl::spawn(PURE_EXPR("MsgDialog Thread"s), [=]()
|
||||||
{
|
{
|
||||||
while (dlg->state == MsgDialogState::Open && get_system_time() < wait_until)
|
while (dlg->state == MsgDialogState::Open && get_system_time() < wait_until)
|
||||||
{
|
{
|
||||||
CHECK_EMU_STATUS;
|
if (Emu.IsStopped()) return;
|
||||||
|
|
||||||
std::this_thread::sleep_for(1ms);
|
std::this_thread::sleep_for(1ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
dlg->on_close(CELL_MSGDIALOG_BUTTON_NONE);
|
dlg->on_close(CELL_MSGDIALOG_BUTTON_NONE);
|
||||||
|
});
|
||||||
}).detach();
|
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ namespace sys_net
|
|||||||
{
|
{
|
||||||
g_tls_net_data.set(vm::alloc(sizeof(decltype(g_tls_net_data)::type), vm::main));
|
g_tls_net_data.set(vm::alloc(sizeof(decltype(g_tls_net_data)::type), vm::main));
|
||||||
|
|
||||||
current_thread_register_atexit([addr = g_tls_net_data.addr()]
|
thread_ctrl::at_exit([addr = g_tls_net_data.addr()]
|
||||||
{
|
{
|
||||||
vm::dealloc_verbose_nothrow(addr, vm::main);
|
vm::dealloc_verbose_nothrow(addr, vm::main);
|
||||||
});
|
});
|
||||||
|
@ -43,8 +43,14 @@ void sys_ppu_thread_exit(PPUThread& ppu, u64 val)
|
|||||||
// (deallocate TLS)
|
// (deallocate TLS)
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
// call the syscall
|
if (ppu.hle_code == 0xaff080a4)
|
||||||
_sys_ppu_thread_exit(ppu, val);
|
{
|
||||||
|
// Change sys_ppu_thread_exit code to the syscall code
|
||||||
|
ppu.hle_code = ~41;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the syscall
|
||||||
|
return _sys_ppu_thread_exit(ppu, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::mutex g_once_mutex;
|
std::mutex g_once_mutex;
|
||||||
|
@ -215,7 +215,7 @@ s32 sys_cond_wait(PPUThread& ppu, u32 cond_id, u64 timeout)
|
|||||||
// try to reown mutex and exit if timed out
|
// try to reown mutex and exit if timed out
|
||||||
if (!cond->mutex->owner)
|
if (!cond->mutex->owner)
|
||||||
{
|
{
|
||||||
cond->mutex->owner = ppu.shared_from_this();
|
cond->mutex->owner = std::static_pointer_cast<CPUThread>(ppu.shared_from_this());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ struct lv2_file_t
|
|||||||
u64 st_trans_rate;
|
u64 st_trans_rate;
|
||||||
bool st_copyless;
|
bool st_copyless;
|
||||||
|
|
||||||
named_thread_t st_thread;
|
std::shared_ptr<thread_ctrl> st_thread;
|
||||||
|
|
||||||
u32 st_buffer;
|
u32 st_buffer;
|
||||||
u64 st_read_size;
|
u64 st_read_size;
|
||||||
|
@ -132,7 +132,7 @@ s32 sys_mutex_lock(PPUThread& ppu, u32 mutex_id, u64 timeout)
|
|||||||
// lock immediately if not locked
|
// lock immediately if not locked
|
||||||
if (!mutex->owner)
|
if (!mutex->owner)
|
||||||
{
|
{
|
||||||
mutex->owner = ppu.shared_from_this();
|
mutex->owner = std::static_pointer_cast<CPUThread>(ppu.shared_from_this());
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
@ -207,7 +207,7 @@ s32 sys_mutex_trylock(PPUThread& ppu, u32 mutex_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// own the mutex if free
|
// own the mutex if free
|
||||||
mutex->owner = ppu.shared_from_this();
|
mutex->owner = std::static_pointer_cast<CPUThread>(ppu.shared_from_this());
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,20 @@ void _sys_ppu_thread_exit(PPUThread& ppu, u64 errorcode)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto thread = ppu.shared_from_this();
|
|
||||||
|
|
||||||
if (!ppu.is_joinable)
|
if (!ppu.is_joinable)
|
||||||
{
|
{
|
||||||
idm::remove<PPUThread>(ppu.get_id());
|
idm::remove<PPUThread>(ppu.get_id());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ppu.exit();
|
||||||
|
}
|
||||||
|
|
||||||
ppu.exit();
|
// Throw if this syscall was not called directly by the SC instruction
|
||||||
|
if (~ppu.hle_code != 41)
|
||||||
|
{
|
||||||
|
throw CPUThreadExit{};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sys_ppu_thread_yield()
|
void sys_ppu_thread_yield()
|
||||||
|
@ -228,7 +228,7 @@ s32 sys_rwlock_wlock(PPUThread& ppu, u32 rw_lock_id, u64 timeout)
|
|||||||
|
|
||||||
if (!rwlock->readers && !rwlock->writer)
|
if (!rwlock->readers && !rwlock->writer)
|
||||||
{
|
{
|
||||||
rwlock->writer = ppu.shared_from_this();
|
rwlock->writer = std::static_pointer_cast<CPUThread>(ppu.shared_from_this());
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
@ -300,7 +300,7 @@ s32 sys_rwlock_trywlock(PPUThread& ppu, u32 rw_lock_id)
|
|||||||
return CELL_EBUSY;
|
return CELL_EBUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
rwlock->writer = ppu.shared_from_this();
|
rwlock->writer = std::static_pointer_cast<CPUThread>(ppu.shared_from_this());
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
@ -13,55 +13,55 @@ SysCallBase sys_timer("sys_timer");
|
|||||||
|
|
||||||
extern u64 get_system_time();
|
extern u64 get_system_time();
|
||||||
|
|
||||||
lv2_timer_t::lv2_timer_t()
|
void lv2_timer_t::on_task()
|
||||||
: start(0)
|
|
||||||
, period(0)
|
|
||||||
, state(SYS_TIMER_STATE_STOP)
|
|
||||||
{
|
{
|
||||||
auto name = fmt::format("Timer[0x%x] Thread", idm::get_last_id());
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
|
||||||
thread.start([name]{ return name; }, [this]()
|
while (state <= SYS_TIMER_STATE_RUN)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(thread.mutex);
|
CHECK_EMU_STATUS;
|
||||||
|
|
||||||
while (thread.joinable())
|
if (state == SYS_TIMER_STATE_RUN)
|
||||||
{
|
{
|
||||||
CHECK_EMU_STATUS;
|
if (lock) lock.unlock();
|
||||||
|
|
||||||
if (state == SYS_TIMER_STATE_RUN)
|
LV2_LOCK;
|
||||||
|
|
||||||
|
if (get_system_time() >= expire)
|
||||||
{
|
{
|
||||||
LV2_LOCK;
|
const auto queue = port.lock();
|
||||||
|
|
||||||
if (get_system_time() >= start)
|
if (queue)
|
||||||
{
|
{
|
||||||
const auto queue = port.lock();
|
queue->push(lv2_lock, source, data1, data2, expire);
|
||||||
|
}
|
||||||
|
|
||||||
if (queue)
|
if (period && queue)
|
||||||
{
|
{
|
||||||
queue->push(lv2_lock, source, data1, data2, start);
|
expire += period; // set next expiration time
|
||||||
}
|
|
||||||
|
|
||||||
if (period && queue)
|
continue; // hack: check again
|
||||||
{
|
}
|
||||||
start += period; // set next expiration time
|
else
|
||||||
|
{
|
||||||
continue; // hack: check again
|
state = SYS_TIMER_STATE_STOP; // stop if oneshot or the event port was disconnected (TODO: is it correct?)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = SYS_TIMER_STATE_STOP; // stop if oneshot or the event port was disconnected (TODO: is it correct?)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread.cv.wait_for(lock, std::chrono::milliseconds(1));
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
if (!lock)
|
||||||
|
{
|
||||||
|
lock.lock();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv.wait_for(lock, std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lv2_timer_t::~lv2_timer_t()
|
lv2_timer_t::lv2_timer_t()
|
||||||
|
: id(idm::get_last_id())
|
||||||
{
|
{
|
||||||
thread.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 sys_timer_create(vm::ptr<u32> timer_id)
|
s32 sys_timer_create(vm::ptr<u32> timer_id)
|
||||||
@ -109,7 +109,7 @@ s32 sys_timer_get_information(u32 timer_id, vm::ptr<sys_timer_information_t> inf
|
|||||||
return CELL_ESRCH;
|
return CELL_ESRCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
info->next_expiration_time = timer->start;
|
info->next_expiration_time = timer->expire;
|
||||||
|
|
||||||
info->period = timer->period;
|
info->period = timer->period;
|
||||||
info->timer_state = timer->state;
|
info->timer_state = timer->state;
|
||||||
@ -163,11 +163,14 @@ s32 _sys_timer_start(u32 timer_id, u64 base_time, u64 period)
|
|||||||
|
|
||||||
// sys_timer_start_periodic() will use current time (TODO: is it correct?)
|
// sys_timer_start_periodic() will use current time (TODO: is it correct?)
|
||||||
|
|
||||||
timer->start = base_time ? base_time : start_time + period;
|
// lock for reliable notification
|
||||||
|
std::lock_guard<std::mutex> lock(timer->mutex);
|
||||||
|
|
||||||
|
timer->expire = base_time ? base_time : start_time + period;
|
||||||
timer->period = period;
|
timer->period = period;
|
||||||
timer->state = SYS_TIMER_STATE_RUN;
|
timer->state = SYS_TIMER_STATE_RUN;
|
||||||
|
|
||||||
timer->thread.cv.notify_one();
|
timer->cv.notify_one();
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
@ -196,8 +199,8 @@ s32 sys_timer_connect_event_queue(u32 timer_id, u32 queue_id, u64 name, u64 data
|
|||||||
|
|
||||||
LV2_LOCK;
|
LV2_LOCK;
|
||||||
|
|
||||||
const auto timer = idm::get<lv2_timer_t>(timer_id);
|
const auto timer(idm::get<lv2_timer_t>(timer_id));
|
||||||
const auto queue = idm::get<lv2_event_queue_t>(queue_id);
|
const auto queue(idm::get<lv2_event_queue_t>(queue_id));
|
||||||
|
|
||||||
if (!timer || !queue)
|
if (!timer || !queue)
|
||||||
{
|
{
|
||||||
|
@ -19,22 +19,35 @@ struct sys_timer_information_t
|
|||||||
be_t<u32> pad;
|
be_t<u32> pad;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct lv2_timer_t final
|
class lv2_timer_t final : public named_thread_t
|
||||||
{
|
{
|
||||||
|
void on_task() override;
|
||||||
|
|
||||||
|
void on_id_aux_finalize() override
|
||||||
|
{
|
||||||
|
// Signal thread using invalid state and join
|
||||||
|
{ std::lock_guard<std::mutex>{mutex}, state = -1; }
|
||||||
|
|
||||||
|
cv.notify_one();
|
||||||
|
join();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
lv2_timer_t();
|
||||||
|
|
||||||
|
std::string get_name() const override { return fmt::format("Timer Thread[0x%x]", id); }
|
||||||
|
|
||||||
|
const u32 id;
|
||||||
|
|
||||||
std::weak_ptr<lv2_event_queue_t> port; // event queue
|
std::weak_ptr<lv2_event_queue_t> port; // event queue
|
||||||
u64 source; // event source
|
u64 source; // event source
|
||||||
u64 data1; // event arg 1
|
u64 data1; // event arg 1
|
||||||
u64 data2; // event arg 2
|
u64 data2; // event arg 2
|
||||||
|
|
||||||
u64 start; // next expiration time
|
u64 expire = 0; // next expiration time
|
||||||
u64 period; // period (oneshot if 0)
|
u64 period = 0; // period (oneshot if 0)
|
||||||
|
|
||||||
std::atomic<u32> state; // timer state
|
std::atomic<u32> state{ SYS_TIMER_STATE_RUN }; // timer state
|
||||||
|
|
||||||
named_thread_t thread; // timer thread
|
|
||||||
|
|
||||||
lv2_timer_t();
|
|
||||||
~lv2_timer_t();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
s32 sys_timer_create(vm::ptr<u32> timer_id);
|
s32 sys_timer_create(vm::ptr<u32> timer_id);
|
||||||
|
@ -125,19 +125,28 @@ public:
|
|||||||
m_cb.send_dbg_command(cmd, thread);
|
m_cb.send_dbg_command(cmd, thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a future object associated with the result of the function called from the GUI thread
|
// Returns a future object associated with the result of the function called from the GUI thread
|
||||||
template<typename F, typename RT = std::result_of_t<F()>> inline std::future<RT> CallAfter(F&& func) const
|
template<typename F>
|
||||||
|
std::future<void> CallAfter(F&& func) const
|
||||||
{
|
{
|
||||||
// create task
|
// Make "shared" promise to workaround std::function limitation
|
||||||
auto task = std::make_shared<std::packaged_task<RT()>>(std::forward<F>(func));
|
auto spr = std::make_shared<std::promise<void>>();
|
||||||
|
|
||||||
// get future
|
// Get future
|
||||||
std::future<RT> future = task->get_future();
|
std::future<void> future = spr->get_future();
|
||||||
|
|
||||||
// run asynchronously in GUI thread
|
// Run asynchronously in GUI thread
|
||||||
m_cb.call_after([=]
|
m_cb.call_after([spr = std::move(spr), task = std::forward<F>(func)]()
|
||||||
{
|
{
|
||||||
(*task)();
|
try
|
||||||
|
{
|
||||||
|
task();
|
||||||
|
spr->set_value();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
spr->set_exception(std::current_exception());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
|
@ -164,17 +164,19 @@ template<typename T1, typename T2, typename T3 = const char*> struct triplet_t
|
|||||||
// return 32 bit .size() for container
|
// return 32 bit .size() for container
|
||||||
template<typename T> inline auto size32(const T& container) -> decltype(static_cast<u32>(container.size()))
|
template<typename T> inline auto size32(const T& container) -> decltype(static_cast<u32>(container.size()))
|
||||||
{
|
{
|
||||||
return container.size() <= UINT32_MAX ? static_cast<u32>(container.size()) : throw std::length_error(__FUNCTION__);
|
const auto size = container.size();
|
||||||
|
return size >= 0 && size <= UINT32_MAX ? static_cast<u32>(size) : throw std::length_error(__FUNCTION__);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 32 bit size for an array
|
// return 32 bit size for an array
|
||||||
template<typename T, std::size_t Size> constexpr u32 size32(const T(&)[Size])
|
template<typename T, std::size_t Size> constexpr u32 size32(const T(&)[Size])
|
||||||
{
|
{
|
||||||
return Size <= UINT32_MAX ? static_cast<u32>(Size) : throw std::length_error(__FUNCTION__);
|
return Size >= 0 && Size <= UINT32_MAX ? static_cast<u32>(Size) : throw std::length_error(__FUNCTION__);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define WRAP_EXPR(expr) [&]{ return expr; }
|
#define WRAP_EXPR(expr) [&]{ return expr; }
|
||||||
#define COPY_EXPR(expr) [=]{ return expr; }
|
#define COPY_EXPR(expr) [=]{ return expr; }
|
||||||
|
#define PURE_EXPR(expr) [] { return expr; }
|
||||||
#define EXCEPTION(text, ...) fmt::exception(__FILE__, __LINE__, __FUNCTION__, text, ##__VA_ARGS__)
|
#define EXCEPTION(text, ...) fmt::exception(__FILE__, __LINE__, __FUNCTION__, text, ##__VA_ARGS__)
|
||||||
#define VM_CAST(value) vm::impl_cast(value, __FILE__, __LINE__, __FUNCTION__)
|
#define VM_CAST(value) vm::impl_cast(value, __FILE__, __LINE__, __FUNCTION__)
|
||||||
#define IS_INTEGRAL(t) (std::is_integral<t>::value || std::is_same<std::decay_t<t>, u128>::value)
|
#define IS_INTEGRAL(t) (std::is_integral<t>::value || std::is_same<std::decay_t<t>, u128>::value)
|
||||||
@ -183,6 +185,7 @@ template<typename T, std::size_t Size> constexpr u32 size32(const T(&)[Size])
|
|||||||
#define CHECK_ASSERTION(expr) if (expr) {} else throw EXCEPTION("Assertion failed: " #expr)
|
#define CHECK_ASSERTION(expr) if (expr) {} else throw EXCEPTION("Assertion failed: " #expr)
|
||||||
#define CHECK_SUCCESS(expr) if (s32 _r = (expr)) throw EXCEPTION(#expr " failed (0x%x)", _r)
|
#define CHECK_SUCCESS(expr) if (s32 _r = (expr)) throw EXCEPTION(#expr " failed (0x%x)", _r)
|
||||||
|
|
||||||
|
// Some forward declarations for the ID manager
|
||||||
template<typename T> struct id_traits;
|
template<typename T> struct id_traits;
|
||||||
|
|
||||||
#define _PRGNAME_ "RPCS3"
|
#define _PRGNAME_ "RPCS3"
|
||||||
|
Loading…
Reference in New Issue
Block a user