Zelda64Recomp/ultramodern/threads.cpp

272 lines
8.2 KiB
C++

#include <cstdio>
#include <thread>
#include <cassert>
#include <string>
#include "ultra64.h"
#include "ultramodern.hpp"
// Native APIs only used to set thread names for easier debugging
#ifdef _WIN32
#include <Windows.h>
#endif
extern "C" void bootproc();
thread_local bool is_main_thread = false;
// Whether this thread is part of the game (i.e. the start thread or one spawned by osCreateThread)
thread_local bool is_game_thread = false;
thread_local PTR(OSThread) thread_self = NULLPTR;
void ultramodern::set_main_thread() {
::is_game_thread = true;
is_main_thread = true;
}
bool ultramodern::is_game_thread() {
return ::is_game_thread;
}
#if 0
int main(int argc, char** argv) {
ultramodern::set_main_thread();
bootproc();
}
#endif
#if 1
void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg);
#else
#define run_thread_function(func, sp, arg) func(arg)
#endif
struct thread_terminated : std::exception {};
#if defined(_WIN32)
void ultramodern::set_native_thread_name(const std::string& name) {
std::wstring wname{name.begin(), name.end()};
HRESULT r;
r = SetThreadDescription(
GetCurrentThread(),
wname.c_str()
);
}
void ultramodern::set_native_thread_priority(ThreadPriority pri) {
int nPriority = THREAD_PRIORITY_NORMAL;
// Convert ThreadPriority to Win32 priority
switch (pri) {
case ThreadPriority::Low:
nPriority = THREAD_PRIORITY_BELOW_NORMAL;
break;
case ThreadPriority::Normal:
nPriority = THREAD_PRIORITY_NORMAL;
break;
case ThreadPriority::High:
nPriority = THREAD_PRIORITY_ABOVE_NORMAL;
break;
case ThreadPriority::VeryHigh:
nPriority = THREAD_PRIORITY_HIGHEST;
break;
case ThreadPriority::Critical:
nPriority = THREAD_PRIORITY_TIME_CRITICAL;
break;
default:
throw std::runtime_error("Invalid thread priority!");
break;
}
// SetThreadPriority(GetCurrentThread(), nPriority);
}
#elif defined(__linux__)
void ultramodern::set_native_thread_name(const std::string& name) {
pthread_setname_np(pthread_self(), name.c_str());
}
void ultramodern::set_native_thread_priority(ThreadPriority pri) {
// TODO linux thread priority
printf("set_native_thread_priority unimplemented\n");
// int nPriority = THREAD_PRIORITY_NORMAL;
// // Convert ThreadPriority to Win32 priority
// switch (pri) {
// case ThreadPriority::Low:
// nPriority = THREAD_PRIORITY_BELOW_NORMAL;
// break;
// case ThreadPriority::Normal:
// nPriority = THREAD_PRIORITY_NORMAL;
// break;
// case ThreadPriority::High:
// nPriority = THREAD_PRIORITY_ABOVE_NORMAL;
// break;
// case ThreadPriority::VeryHigh:
// nPriority = THREAD_PRIORITY_HIGHEST;
// break;
// case ThreadPriority::Critical:
// nPriority = THREAD_PRIORITY_TIME_CRITICAL;
// break;
// default:
// throw std::runtime_error("Invalid thread priority!");
// break;
// }
}
#endif
static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg) {
OSThread *self = TO_PTR(OSThread, self_);
debug_printf("[Thread] Thread created: %d\n", self->id);
thread_self = self_;
is_game_thread = true;
// Set the thread name
ultramodern::set_native_thread_name("Game Thread " + std::to_string(self->id));
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::High);
// Set initialized to false to indicate that this thread can be started.
self->context->initialized.store(true);
self->context->initialized.notify_all();
debug_printf("[Thread] Thread waiting to be started: %d\n", self->id);
// Wait until the thread is marked as running.
ultramodern::set_self_paused(PASS_RDRAM1);
ultramodern::wait_for_resumed(PASS_RDRAM1);
debug_printf("[Thread] Thread started: %d\n", self->id);
try {
// Run the thread's function with the provided argument.
run_thread_function(PASS_RDRAM entrypoint, self->sp, arg);
} catch (thread_terminated& terminated) {
}
// Dispose of this thread after it completes.
ultramodern::cleanup_thread(self);
}
extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) {
OSThread* t = TO_PTR(OSThread, t_);
debug_printf("[os] Start Thread %d\n", t->id);
// Wait until the thread is initialized to indicate that it's action_queued to be started.
t->context->initialized.wait(false);
debug_printf("[os] Thread %d is ready to be started\n", t->id);
if (thread_self && (t->priority > TO_PTR(OSThread, thread_self)->priority)) {
ultramodern::swap_to_thread(PASS_RDRAM t);
} else {
ultramodern::schedule_running_thread(t);
}
}
extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) {
debug_printf("[os] Create Thread %d\n", id);
OSThread *t = TO_PTR(OSThread, t_);
t->next = NULLPTR;
t->priority = pri;
t->id = id;
t->state = OSThreadState::PAUSED;
t->sp = sp - 0x10; // Set up the first stack frame
t->destroyed = false;
// Spawn a new thread, which will immediately pause itself and wait until it's been started.
t->context = new UltraThreadContext{};
t->context->initialized.store(false);
t->context->running.store(false);
t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg};
}
extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) {
assert(false);
}
extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) {
// Check if the thread is destroying itself (arg is null or thread_self)
if (t_ == NULLPTR || t_ == thread_self) {
throw thread_terminated{};
}
// Otherwise, mark the target thread as destroyed. Next time it reaches a stopping point,
// it'll check this and terminate itself instead of pausing.
else {
OSThread* t = TO_PTR(OSThread, t_);
t->destroyed = true;
}
}
extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri) {
if (t == NULLPTR) {
t = thread_self;
}
bool pause_self = false;
if (pri > TO_PTR(OSThread, thread_self)->priority) {
pause_self = true;
ultramodern::set_self_paused(PASS_RDRAM1);
} else if (t == thread_self && pri < TO_PTR(OSThread, thread_self)->priority) {
pause_self = true;
ultramodern::set_self_paused(PASS_RDRAM1);
}
ultramodern::reprioritize_thread(TO_PTR(OSThread, t), pri);
if (pause_self) {
ultramodern::wait_for_resumed(PASS_RDRAM1);
}
}
extern "C" OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) t) {
if (t == NULLPTR) {
t = thread_self;
}
return TO_PTR(OSThread, t)->priority;
}
extern "C" OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t) {
if (t == NULLPTR) {
t = thread_self;
}
return TO_PTR(OSThread, t)->id;
}
// TODO yield thread, need a stable priority queue in the scheduler
void ultramodern::set_self_paused(RDRAM_ARG1) {
debug_printf("[Thread] Thread pausing itself: %d\n", TO_PTR(OSThread, thread_self)->id);
TO_PTR(OSThread, thread_self)->state = OSThreadState::PAUSED;
TO_PTR(OSThread, thread_self)->context->running.store(false);
TO_PTR(OSThread, thread_self)->context->running.notify_all();
}
void check_destroyed(OSThread* t) {
if (t->destroyed) {
throw thread_terminated{};
}
}
void ultramodern::wait_for_resumed(RDRAM_ARG1) {
check_destroyed(TO_PTR(OSThread, thread_self));
TO_PTR(OSThread, thread_self)->context->running.wait(false);
check_destroyed(TO_PTR(OSThread, thread_self));
}
void ultramodern::pause_thread_impl(OSThread* t) {
t->state = OSThreadState::PREEMPTED;
t->context->running.store(false);
t->context->running.notify_all();
}
void ultramodern::resume_thread_impl(OSThread *t) {
if (t->state == OSThreadState::PREEMPTED) {
// Nothing to do here
}
t->state = OSThreadState::RUNNING;
t->context->running.store(true);
t->context->running.notify_all();
}
PTR(OSThread) ultramodern::this_thread() {
return thread_self;
}