mirror of
				https://github.com/Zelda64Recomp/Zelda64Recomp.git
				synced 2025-10-30 08:03:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			296 lines
		
	
	
	
		
			9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
	
		
			9 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
 | 
						|
 | 
						|
std::atomic_int temporary_threads = 0;
 | 
						|
std::atomic_int permanent_threads = 0;
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
    // TODO fix these being hardcoded (this is only used for quicksaving)
 | 
						|
    if ((self->id == 2 && self->priority == 5) || self->id == 13) { // slowly, flashrom
 | 
						|
        temporary_threads.fetch_add(1);
 | 
						|
    }
 | 
						|
    else if (self->id != 1 && self->id != 2) { // ignore idle and fault
 | 
						|
        permanent_threads.fetch_add(1);
 | 
						|
    }
 | 
						|
 | 
						|
    // 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);
 | 
						|
    
 | 
						|
    // TODO fix these being hardcoded (this is only used for quicksaving)
 | 
						|
    if ((self->id == 2 && self->priority == 5) || self->id == 13) { // slowly, flashrom
 | 
						|
        temporary_threads.fetch_sub(1);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
uint32_t ultramodern::permanent_thread_count() {
 | 
						|
    return permanent_threads.load();
 | 
						|
}
 | 
						|
 | 
						|
uint32_t ultramodern::temporary_thread_count() {
 | 
						|
    return temporary_threads.load();
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
}
 |