diff --git a/patches/save_patches.c b/patches/save_patches.c index 5575bf3..229fed2 100644 --- a/patches/save_patches.c +++ b/patches/save_patches.c @@ -56,30 +56,3 @@ void Sram_UpdateWriteToFlashOwlSave(SramContext* sramCtx) { Lib_MemCpy(&gSaveContext, sramCtx->saveBuf, offsetof(SaveContext, fileNum)); } } - -// @recomp Patched to add a pause so that other threads can execute in the meantime. -s32 SysFlashrom_ExecWrite(void* addr, u32 pageNum, u32 pageCount) { - OSIoMesg msg; - s32 result; - u32 i; - - if (!SysFlashrom_IsInit()) { - return -1; - } - // Ensure the page is always aligned to a sector boundary. - if ((pageNum % FLASH_BLOCK_SIZE) != 0) { - Fault_AddHungupAndCrash("../sys_flashrom.c", 275); - } - osWritebackDCache(addr, pageCount * FLASH_BLOCK_SIZE); - for (i = 0; i < pageCount; i++) { - // @recomp Pause shortly to allow other threads to work. - Sleep_Msec(5); - osFlashWriteBuffer(&msg, OS_MESG_PRI_NORMAL, (u8*)addr + i * FLASH_BLOCK_SIZE, &sFlashromMesgQueue); - osRecvMesg(&sFlashromMesgQueue, NULL, OS_MESG_BLOCK); - result = osFlashWriteArray(i + pageNum); - if (result != 0) { - return result; - } - } - return 0; -} diff --git a/src/recomp/cont.cpp b/src/recomp/cont.cpp index c188fa0..42492c3 100644 --- a/src/recomp/cont.cpp +++ b/src/recomp/cont.cpp @@ -56,7 +56,7 @@ extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) } update_poll_time(); - ultramodern::send_si_message(); + ultramodern::send_si_message(rdram); } extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) { @@ -86,7 +86,7 @@ extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) { } extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) { - ultramodern::send_si_message(); + ultramodern::send_si_message(rdram); } extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) { diff --git a/src/recomp/eep.cpp b/src/recomp/eep.cpp index 16ad42e..ddc612d 100644 --- a/src/recomp/eep.cpp +++ b/src/recomp/eep.cpp @@ -1,8 +1,8 @@ #include "recomp.h" #include "../ultramodern/ultra64.h" -void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count); -void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count); +void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); +void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); constexpr int eeprom_block_size = 8; constexpr int eep4_size = 4096; diff --git a/src/recomp/flash.cpp b/src/recomp/flash.cpp index ae41111..519eaa3 100644 --- a/src/recomp/flash.cpp +++ b/src/recomp/flash.cpp @@ -14,8 +14,8 @@ constexpr uint32_t sector_size = page_size * pages_per_sector; constexpr uint32_t sector_count = flash_size / sector_size; void save_write_ptr(const void* in, uint32_t offset, uint32_t count); -void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count); -void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count); +void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); +void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); void save_clear(uint32_t start, uint32_t size, char value); std::array write_buffer; diff --git a/src/recomp/pi.cpp b/src/recomp/pi.cpp index ab0e4a0..809937a 100644 --- a/src/recomp/pi.cpp +++ b/src/recomp/pi.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "recomp.h" #include "recomp_game.h" #include "recomp_config.h" @@ -60,7 +61,13 @@ void recomp::do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr } } -std::array save_buffer; +struct { + std::array save_buffer; + std::thread saving_thread; + moodycamel::LightweightSemaphore write_sempahore; + std::mutex save_buffer_mutex; +} save_context; + const std::u8string save_folder = u8"saves"; const std::u8string save_filename = std::u8string{recomp::mm_game_id} + u8".bin"; @@ -72,44 +79,77 @@ void update_save_file() { std::ofstream save_file{ get_save_file_path(), std::ios_base::binary }; if (save_file.good()) { - save_file.write(save_buffer.data(), save_buffer.size()); + std::lock_guard lock{ save_context.save_buffer_mutex }; + save_file.write(save_context.save_buffer.data(), save_context.save_buffer.size()); } else { fprintf(stderr, "Failed to save!\n"); std::exit(EXIT_FAILURE); } } +extern std::atomic_bool exited; + +void saving_thread_func(RDRAM_ARG1) { + while (!exited) { + bool save_buffer_updated = false; + // Repeatedly wait for a new action to be sent. + constexpr int64_t wait_time_microseconds = 10000; + constexpr int max_actions = 128; + int num_actions = 0; + + // Wait up to the given timeout for a write to come in. Allow multiple writes to coalesce together into a single save. + // Cap the number of coalesced writes to guarantee that the save buffer eventually gets written out to the file even if the game + // is constantly sending writes. + while (save_context.write_sempahore.wait(wait_time_microseconds) && num_actions < max_actions) { + save_buffer_updated = true; + num_actions++; + } + + // If an action came through that affected the save file, save the updated contents. + if (save_buffer_updated) { + printf("Writing updated save buffer to disk\n"); + update_save_file(); + } + } +} + void save_write_ptr(const void* in, uint32_t offset, uint32_t count) { - memcpy(&save_buffer[offset], in, count); - update_save_file(); -} - -void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count) { - for (uint32_t i = 0; i < count; i++) { - save_buffer[offset + i] = MEM_B(i, rdram_address); + { + std::lock_guard lock { save_context.save_buffer_mutex }; + memcpy(&save_context.save_buffer[offset], in, count); } - update_save_file(); + + save_context.write_sempahore.signal(); } -void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count) { +void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { + { + std::lock_guard lock { save_context.save_buffer_mutex }; + for (uint32_t i = 0; i < count; i++) { + save_context.save_buffer[offset + i] = MEM_B(i, rdram_address); + } + } + + save_context.write_sempahore.signal(); +} + +void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { + std::lock_guard lock { save_context.save_buffer_mutex }; for (size_t i = 0; i < count; i++) { - MEM_B(i, rdram_address) = save_buffer[offset + i]; + MEM_B(i, rdram_address) = save_context.save_buffer[offset + i]; } } void save_clear(uint32_t start, uint32_t size, char value) { - std::fill_n(save_buffer.begin() + start, size, value); - std::ofstream save_file{ get_save_file_path(), std::ios_base::binary }; - - if (save_file.good()) { - save_file.write(save_buffer.data(), save_buffer.size()); - } else { - fprintf(stderr, "Failed to save!\n"); - std::exit(EXIT_FAILURE); + { + std::lock_guard lock { save_context.save_buffer_mutex }; + std::fill_n(save_context.save_buffer.begin() + start, size, value); } + + save_context.write_sempahore.signal(); } -void ultramodern::save_init() { +void ultramodern::init_saving(RDRAM_ARG1) { std::filesystem::path save_file_path = get_save_file_path(); // Ensure the save file directory exists. @@ -118,14 +158,20 @@ void ultramodern::save_init() { // Read the save file if it exists. std::ifstream save_file{ save_file_path, std::ios_base::binary }; if (save_file.good()) { - save_file.read(save_buffer.data(), save_buffer.size()); + save_file.read(save_context.save_buffer.data(), save_context.save_buffer.size()); } else { // Otherwise clear the save file to all zeroes. - save_buffer.fill(0); + save_context.save_buffer.fill(0); } + + save_context.saving_thread = std::thread{saving_thread_func, PASS_RDRAM}; } -void do_dma(uint8_t* rdram, PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) { +void ultramodern::join_saving_thread() { + save_context.saving_thread.join(); +} + +void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) { // TODO asynchronous transfer // TODO implement unaligned DMA correctly if (direction == 0) { @@ -160,7 +206,7 @@ void do_dma(uint8_t* rdram, PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t phy } } -extern "C" void osPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) { uint32_t mb = ctx->r4; uint32_t pri = ctx->r5; uint32_t direction = ctx->r6; @@ -172,12 +218,12 @@ extern "C" void osPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) { debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size); - do_dma(rdram, mq, dramAddr, physical_addr, size, direction); + do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction); ctx->r2 = 0; } -extern "C" void osEPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) { +extern "C" void osEPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) { OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4); OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5); uint32_t direction = ctx->r6; @@ -189,12 +235,12 @@ extern "C" void osEPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) { debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size); - do_dma(rdram, mq, dramAddr, physical_addr, size, direction); + do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction); ctx->r2 = 0; } -extern "C" void osEPiReadIo_recomp(uint8_t * rdram, recomp_context * ctx) { +extern "C" void osEPiReadIo_recomp(RDRAM_ARG recomp_context * ctx) { OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4); uint32_t devAddr = handle->baseAddress | ctx->r5; gpr dramAddr = ctx->r6; @@ -202,7 +248,7 @@ extern "C" void osEPiReadIo_recomp(uint8_t * rdram, recomp_context * ctx) { if (physical_addr > rom_base) { // cart rom - recomp::do_rom_read(rdram, dramAddr, physical_addr, sizeof(uint32_t)); + recomp::do_rom_read(PASS_RDRAM dramAddr, physical_addr, sizeof(uint32_t)); } else { // sram assert(false && "SRAM ReadIo unimplemented"); @@ -211,10 +257,10 @@ extern "C" void osEPiReadIo_recomp(uint8_t * rdram, recomp_context * ctx) { ctx->r2 = 0; } -extern "C" void osPiGetStatus_recomp(uint8_t * rdram, recomp_context * ctx) { +extern "C" void osPiGetStatus_recomp(RDRAM_ARG recomp_context * ctx) { ctx->r2 = 0; } -extern "C" void osPiRawStartDma_recomp(uint8_t * rdram, recomp_context * ctx) { +extern "C" void osPiRawStartDma_recomp(RDRAM_ARG recomp_context * ctx) { ctx->r2 = 0; } diff --git a/src/recomp/recomp.cpp b/src/recomp/recomp.cpp index 73230b3..712ff5c 100644 --- a/src/recomp/recomp.cpp +++ b/src/recomp/recomp.cpp @@ -437,4 +437,5 @@ void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::a game_thread.join(); ultramodern::join_event_threads(); ultramodern::join_thread_cleaner_thread(); + ultramodern::join_saving_thread(); } diff --git a/ultramodern/events.cpp b/ultramodern/events.cpp index 7627462..1597f76 100644 --- a/ultramodern/events.cpp +++ b/ultramodern/events.cpp @@ -533,12 +533,11 @@ void ultramodern::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) { } } -void ultramodern::send_si_message() { - uint8_t* rdram = events_context.rdram; +void ultramodern::send_si_message(RDRAM_ARG1) { osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK); } -void ultramodern::init_events(uint8_t* rdram, ultramodern::WindowHandle window_handle) { +void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle) { moodycamel::LightweightSemaphore gfx_thread_ready; moodycamel::LightweightSemaphore task_thread_ready; events_context.rdram = rdram; diff --git a/ultramodern/ultrainit.cpp b/ultramodern/ultrainit.cpp index 3e2cfff..9517113 100644 --- a/ultramodern/ultrainit.cpp +++ b/ultramodern/ultrainit.cpp @@ -1,12 +1,12 @@ #include "ultra64.h" #include "ultramodern.hpp" -void ultramodern::preinit(uint8_t* rdram, ultramodern::WindowHandle window_handle) { +void ultramodern::preinit(RDRAM_ARG ultramodern::WindowHandle window_handle) { ultramodern::set_main_thread(); - ultramodern::init_events(rdram, window_handle); - ultramodern::init_timers(rdram); + ultramodern::init_events(PASS_RDRAM window_handle); + ultramodern::init_timers(PASS_RDRAM1); ultramodern::init_audio(); - ultramodern::save_init(); + ultramodern::init_saving(PASS_RDRAM1); ultramodern::init_thread_cleanup(); } diff --git a/ultramodern/ultramodern.hpp b/ultramodern/ultramodern.hpp index 5cbaa5e..5e6045f 100644 --- a/ultramodern/ultramodern.hpp +++ b/ultramodern/ultramodern.hpp @@ -57,9 +57,9 @@ constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash // Initialization. -void preinit(uint8_t* rdram, WindowHandle window_handle); -void save_init(); -void init_events(uint8_t* rdram, WindowHandle window_handle); +void preinit(RDRAM_ARG WindowHandle window_handle); +void init_saving(RDRAM_ARG1); +void init_events(RDRAM_ARG WindowHandle window_handle); void init_timers(RDRAM_ARG1); void init_thread_cleanup(); @@ -99,7 +99,7 @@ PTR(OSThread) this_thread(); void set_main_thread(); bool is_game_thread(); void submit_rsp_task(RDRAM_ARG PTR(OSTask) task); -void send_si_message(); +void send_si_message(RDRAM_ARG1); uint32_t get_speed_multiplier(); // Time @@ -153,6 +153,7 @@ bool is_game_started(); void quit(); void join_event_threads(); void join_thread_cleaner_thread(); +void join_saving_thread(); } // namespace ultramodern