#include #include #include #include #include "ultra64.h" #include "multilibultra.hpp" // Native APIs only used to set thread names for easier debugging #ifdef _WIN32 #include #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 Multilibultra::set_main_thread() { ::is_game_thread = true; is_main_thread = true; } bool Multilibultra::is_game_thread() { return ::is_game_thread; } #if 0 int main(int argc, char** argv) { Multilibultra::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 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 #ifdef _WIN32 std::wstring thread_name = L"Game Thread " + std::to_wstring(self->id); HRESULT r; r = SetThreadDescription( GetCurrentThread(), thread_name.c_str() ); #endif // Perform any necessary native thread initialization. Multilibultra::native_thread_init(self); // 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. Multilibultra::set_self_paused(PASS_RDRAM1); Multilibultra::wait_for_resumed(PASS_RDRAM1); debug_printf("[Thread] Thread started: %d\n", self->id); // Run the thread's function with the provided argument. run_thread_function(PASS_RDRAM entrypoint, self->sp, arg); // Dispose of this thread after it completes. Multilibultra::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)) { Multilibultra::swap_to_thread(PASS_RDRAM t); } else { Multilibultra::schedule_running_thread(t); } // The main thread "becomes" the first thread started, so join on it and exit after it completes. if (is_main_thread) { t->context->host_thread.join(); std::exit(EXIT_SUCCESS); } } 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 // 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_) { assert(false); } 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; Multilibultra::set_self_paused(PASS_RDRAM1); } else if (t == thread_self && pri < TO_PTR(OSThread, thread_self)->priority) { pause_self = true; Multilibultra::set_self_paused(PASS_RDRAM1); } Multilibultra::reprioritize_thread(TO_PTR(OSThread, t), pri); if (pause_self) { Multilibultra::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 Multilibultra::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 Multilibultra::wait_for_resumed(RDRAM_ARG1) { TO_PTR(OSThread, thread_self)->context->running.wait(false); } void Multilibultra::pause_thread_impl(OSThread* t) { t->state = OSThreadState::PREEMPTED; t->context->running.store(false); t->context->running.notify_all(); Multilibultra::pause_thread_native_impl(t); } void Multilibultra::resume_thread_impl(OSThread *t) { if (t->state == OSThreadState::PREEMPTED) { Multilibultra::resume_thread_native_impl(t); } t->state = OSThreadState::RUNNING; t->context->running.store(true); t->context->running.notify_all(); } PTR(OSThread) Multilibultra::this_thread() { return thread_self; }