From 575c8f22a06390deabae1d5b25a0ca9d93c127e1 Mon Sep 17 00:00:00 2001 From: Garrett Smith Date: Thu, 15 Jan 2026 17:23:39 -0800 Subject: [PATCH] Add recomp::Configuration and ultramodern::MessageQueueControl + plumbing (#131) --- librecomp/include/librecomp/game.hpp | 45 +++++++++++-------- librecomp/src/flash.cpp | 4 +- librecomp/src/pi.cpp | 6 +-- librecomp/src/recomp.cpp | 24 ++++------ .../include/ultramodern/ultramodern.hpp | 21 +++++++++ ultramodern/src/events.cpp | 10 ++--- ultramodern/src/extensions.cpp | 2 +- ultramodern/src/mesgqueue.cpp | 17 +++++++ ultramodern/src/timer.cpp | 2 +- 9 files changed, 84 insertions(+), 47 deletions(-) diff --git a/librecomp/include/librecomp/game.hpp b/librecomp/include/librecomp/game.hpp index 56480d6..6df55ee 100644 --- a/librecomp/include/librecomp/game.hpp +++ b/librecomp/include/librecomp/game.hpp @@ -82,25 +82,32 @@ namespace recomp { void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr); const Version& get_project_version(); - /** - * The following arguments contain mandatory callbacks that need to be registered (i.e., can't be `nullptr`): - * - `rsp_callbacks` - * - `renderer_callbacks` - * - * It must be called only once and it must be called before `ultramodern::preinit`. - */ - void start( - const Version& project_version, - ultramodern::renderer::WindowHandle window_handle, - const recomp::rsp::callbacks_t& rsp_callbacks, - const ultramodern::renderer::callbacks_t& renderer_callbacks, - const ultramodern::audio_callbacks_t& audio_callbacks, - const ultramodern::input::callbacks_t& input_callbacks, - const ultramodern::gfx_callbacks_t& gfx_callbacks, - const ultramodern::events::callbacks_t& events_callbacks, - const ultramodern::error_handling::callbacks_t& error_handling_callbacks, - const ultramodern::threads::callbacks_t& threads_callbacks - ); + /// Specify the input configuration to the recomp runtime. + /// + /// The following callback fields are mandatory (i.e., fail on empty()): + /// - `rsp_callbacks` + /// - `renderer_callbacks` + /// + struct Configuration { + Version project_version; + ultramodern::renderer::WindowHandle window_handle; + recomp::rsp::callbacks_t rsp_callbacks; + ultramodern::renderer::callbacks_t renderer_callbacks; + ultramodern::audio_callbacks_t audio_callbacks; + ultramodern::input::callbacks_t input_callbacks; + ultramodern::gfx_callbacks_t gfx_callbacks; + ultramodern::events::callbacks_t events_callbacks; + ultramodern::error_handling::callbacks_t error_handling_callbacks; + ultramodern::threads::callbacks_t threads_callbacks; + ultramodern::MessageQueueControl message_queue_control; + }; + + /// Start the recomp runtime. + /// + /// This routine must be called only once and it must be called before + /// `ultramodern::preinit`. + /// + void start(const Configuration& cfg); SaveType get_save_type(); bool eeprom_allowed(); diff --git a/librecomp/src/flash.cpp b/librecomp/src/flash.cpp index 47f860a..f46261c 100644 --- a/librecomp/src/flash.cpp +++ b/librecomp/src/flash.cpp @@ -154,7 +154,7 @@ extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) } // Send the message indicating write completion - ultramodern::enqueue_external_message(mq, 0, false, true); + ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); ctx->r2 = 0; } @@ -193,7 +193,7 @@ extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) { save_read(PASS_RDRAM dramAddr, offset, count); // Send the message indicating read completion - ultramodern::enqueue_external_message(mq, 0, false, true); + ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); ctx->r2 = 0; } diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp index 62656ad..43509ba 100644 --- a/librecomp/src/pi.cpp +++ b/librecomp/src/pi.cpp @@ -275,7 +275,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ recomp::do_rom_read(rdram, rdram_address, physical_addr, size); // Send a message to the mq to indicate that the transfer completed - ultramodern::enqueue_external_message(mq, 0, false, true); + ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); } else if (physical_addr >= recomp::sram_base) { if (!recomp::sram_allowed()) { ultramodern::error_handling::message_box("Attempted to use SRAM saving with other save type"); @@ -285,7 +285,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ save_read(rdram, rdram_address, physical_addr - recomp::sram_base, size); // Send a message to the mq to indicate that the transfer completed - ultramodern::enqueue_external_message(mq, 0, false, true); + ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); } else { fprintf(stderr, "[WARN] PI DMA read from unknown region, phys address 0x%08X\n", physical_addr); } @@ -302,7 +302,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_ save_write(rdram, rdram_address, physical_addr - recomp::sram_base, size); // Send a message to the mq to indicate that the transfer completed - ultramodern::enqueue_external_message(mq, 0, false, true); + ultramodern::enqueue_external_message_src(mq, 0, false, ultramodern::EventMessageSource::Pi); } else { fprintf(stderr, "[WARN] PI DMA write to unknown region, phys address 0x%08X\n", physical_addr); } diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index 78b33e1..ea17675 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -729,31 +729,20 @@ bool recomp::flashram_allowed() { save_type == SaveType::AllowAll; } -void recomp::start( - const recomp::Version& version, - ultramodern::renderer::WindowHandle window_handle, - const recomp::rsp::callbacks_t& rsp_callbacks, - const ultramodern::renderer::callbacks_t& renderer_callbacks, - const ultramodern::audio_callbacks_t& audio_callbacks, - const ultramodern::input::callbacks_t& input_callbacks, - const ultramodern::gfx_callbacks_t& gfx_callbacks_, - const ultramodern::events::callbacks_t& events_callbacks, - const ultramodern::error_handling::callbacks_t& error_handling_callbacks, - const ultramodern::threads::callbacks_t& threads_callbacks -) { - project_version = version; +void recomp::start(const recomp::Configuration& cfg) { + project_version = cfg.project_version; recomp::check_all_stored_roms(); - recomp::rsp::set_callbacks(rsp_callbacks); + recomp::rsp::set_callbacks(cfg.rsp_callbacks); static const ultramodern::rsp::callbacks_t ultramodern_rsp_callbacks { .init = recomp::rsp::constants_init, .run_task = recomp::rsp::run_task, }; - ultramodern::set_callbacks(ultramodern_rsp_callbacks, renderer_callbacks, audio_callbacks, input_callbacks, gfx_callbacks_, events_callbacks, error_handling_callbacks, threads_callbacks); + ultramodern::set_callbacks(ultramodern_rsp_callbacks, cfg.renderer_callbacks, cfg.audio_callbacks, cfg.input_callbacks, cfg.gfx_callbacks, cfg.events_callbacks, cfg.error_handling_callbacks, cfg.threads_callbacks); - ultramodern::gfx_callbacks_t gfx_callbacks = gfx_callbacks_; + ultramodern::gfx_callbacks_t gfx_callbacks = cfg.gfx_callbacks; ultramodern::gfx_callbacks_t::gfx_data_t gfx_data{}; @@ -761,6 +750,7 @@ void recomp::start( gfx_data = gfx_callbacks.create_gfx(); } + auto window_handle = cfg.window_handle; if (window_handle == ultramodern::renderer::WindowHandle{}) { if (gfx_callbacks.create_window) { window_handle = gfx_callbacks.create_window(gfx_data); @@ -770,6 +760,8 @@ void recomp::start( } } + ultramodern::set_message_queue_control(cfg.message_queue_control); + recomp::mods::initialize_mods(); recomp::mods::scan_mods(); diff --git a/ultramodern/include/ultramodern/ultramodern.hpp b/ultramodern/include/ultramodern/ultramodern.hpp index 3b28af3..57db6f6 100644 --- a/ultramodern/include/ultramodern/ultramodern.hpp +++ b/ultramodern/include/ultramodern/ultramodern.hpp @@ -52,6 +52,27 @@ bool thread_queue_empty(RDRAM_ARG PTR(PTR(OSThread)) queue); PTR(OSThread) thread_queue_peek(RDRAM_ARG PTR(PTR(OSThread)) queue); // Message queues. +enum class EventMessageSource : int { + Timer, + Sp, + Si, + Ai, + Vi, + Pi, + Dp, +}; + +struct MessageQueueControl { + bool requeue_timer = true; + bool requeue_sp = true; + bool requeue_si = true; + bool requeue_ai = false; + bool requeue_vi = false; + bool requeue_pi = false; + bool requeue_dp = true; +}; +void set_message_queue_control(const MessageQueueControl& mqc); +void enqueue_external_message_src(PTR(OSMesgQueue) mq, OSMesg msg, bool jam, EventMessageSource src); void enqueue_external_message(PTR(OSMesgQueue) mq, OSMesg msg, bool jam, bool requeue_if_blocked); void wait_for_external_message(RDRAM_ARG1); void wait_for_external_message_timed(RDRAM_ARG1, u32 millis); diff --git a/ultramodern/src/events.cpp b/ultramodern/src/events.cpp index 1cd7907..518529d 100644 --- a/ultramodern/src/events.cpp +++ b/ultramodern/src/events.cpp @@ -233,13 +233,13 @@ void vi_thread_func() { if (cur_state->mq != NULLPTR) { // Send a message to the VI queue, and do not set it to be requeued if the queue was full. // The worst case scenario is that the game misses a VI message and has to wait a little longer for the next. - ultramodern::enqueue_external_message(cur_state->mq, cur_state->msg, false, false); + ultramodern::enqueue_external_message_src(cur_state->mq, cur_state->msg, false, ultramodern::EventMessageSource::Vi); } remaining_retraces = cur_state->retrace_count; } if (events_context.ai.mq != NULLPTR) { // Send a message to the VI queue, and do not set it to be requeued if the queue was full for the same reason as the VI message above. - ultramodern::enqueue_external_message(events_context.ai.mq, events_context.ai.msg, false, false); + ultramodern::enqueue_external_message_src(events_context.ai.mq, events_context.ai.msg, false, ultramodern::EventMessageSource::Ai); } } @@ -252,13 +252,13 @@ void vi_thread_func() { void sp_complete() { uint8_t* rdram = events_context.rdram; std::lock_guard lock{ events_context.message_mutex }; - ultramodern::enqueue_external_message(events_context.sp.mq, events_context.sp.msg, false, true); + ultramodern::enqueue_external_message_src(events_context.sp.mq, events_context.sp.msg, false, ultramodern::EventMessageSource::Sp); } void dp_complete() { uint8_t* rdram = events_context.rdram; std::lock_guard lock{ events_context.message_mutex }; - ultramodern::enqueue_external_message(events_context.dp.mq, events_context.dp.msg, false, true); + ultramodern::enqueue_external_message_src(events_context.dp.mq, events_context.dp.msg, false, ultramodern::EventMessageSource::Dp); } void task_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready) { @@ -566,7 +566,7 @@ void ultramodern::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) { } void ultramodern::send_si_message() { - ultramodern::enqueue_external_message(events_context.si.mq, events_context.si.msg, false, true); + ultramodern::enqueue_external_message_src(events_context.si.mq, events_context.si.msg, false, ultramodern::EventMessageSource::Si); } void ultramodern::init_events(RDRAM_ARG ultramodern::renderer::WindowHandle window_handle) { diff --git a/ultramodern/src/extensions.cpp b/ultramodern/src/extensions.cpp index b0f4dff..e18b276 100644 --- a/ultramodern/src/extensions.cpp +++ b/ultramodern/src/extensions.cpp @@ -38,7 +38,7 @@ void dispatch_displaylist_events(PTR(void) displaylist, u32 event_type) { for (auto iter = extension_state.dl_events.pending_events.begin(); iter != extension_state.dl_events.pending_events.end(); ) { if (iter->displaylist == displaylist && iter->event_type == event_type) { // Send the provided message to the corresponding message queue for this event, then remove this event from the queue. - ultramodern::enqueue_external_message(iter->mq, iter->mesg, false, true); + ultramodern::enqueue_external_message_src(iter->mq, iter->mesg, false, ultramodern::EventMessageSource::Sp); iter = extension_state.dl_events.pending_events.erase(iter); } else { diff --git a/ultramodern/src/mesgqueue.cpp b/ultramodern/src/mesgqueue.cpp index 961de3d..9944a02 100644 --- a/ultramodern/src/mesgqueue.cpp +++ b/ultramodern/src/mesgqueue.cpp @@ -1,3 +1,4 @@ +#include #include #include "blockingconcurrentqueue.h" @@ -13,6 +14,22 @@ struct QueuedMessage { }; static moodycamel::BlockingConcurrentQueue external_messages {}; +std::bitset<32> requeue_enabled; + +void ultramodern::set_message_queue_control(const ultramodern::MessageQueueControl& mqc) { + requeue_enabled.reset(); + requeue_enabled.set(static_cast(EventMessageSource::Timer), mqc.requeue_timer); + requeue_enabled.set(static_cast(EventMessageSource::Sp), mqc.requeue_sp); + requeue_enabled.set(static_cast(EventMessageSource::Si), mqc.requeue_si); + requeue_enabled.set(static_cast(EventMessageSource::Ai), mqc.requeue_ai); + requeue_enabled.set(static_cast(EventMessageSource::Vi), mqc.requeue_vi); + requeue_enabled.set(static_cast(EventMessageSource::Pi), mqc.requeue_pi); + requeue_enabled.set(static_cast(EventMessageSource::Dp), mqc.requeue_dp); +} + +void ultramodern::enqueue_external_message_src(PTR(OSMesgQueue) mq, OSMesg msg, bool jam, EventMessageSource src) { + external_messages.enqueue({mq, msg, jam, requeue_enabled[static_cast(src)]}); +} void ultramodern::enqueue_external_message(PTR(OSMesgQueue) mq, OSMesg msg, bool jam, bool requeue_if_blocked) { external_messages.enqueue({mq, msg, jam, requeue_if_blocked}); diff --git a/ultramodern/src/timer.cpp b/ultramodern/src/timer.cpp index 276301b..ee26c45 100644 --- a/ultramodern/src/timer.cpp +++ b/ultramodern/src/timer.cpp @@ -130,7 +130,7 @@ void timer_thread(RDRAM_ARG1) { } else { // Waiting for the timer completed, so send the timer's message to its message queue - ultramodern::enqueue_external_message(cur_timer->mq, cur_timer->msg, false, true); + ultramodern::enqueue_external_message_src(cur_timer->mq, cur_timer->msg, false, ultramodern::EventMessageSource::Timer); // If the timer has a specified interval then reload it with that value if (cur_timer->interval != 0) { cur_timer->timestamp = cur_timer->interval + time_now();