mirror of
				https://github.com/Zelda64Recomp/Zelda64Recomp.git
				synced 2025-10-30 08:03:03 +00:00 
			
		
		
		
	Asynchronous saving
This commit is contained in:
		
							parent
							
								
									b9c9e65672
								
							
						
					
					
						commit
						1ef8656a93
					
				
					 9 changed files with 96 additions and 76 deletions
				
			
		|  | @ -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; | ||||
| } | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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<char, page_size> write_buffer; | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| #include <array> | ||||
| #include <cstring> | ||||
| #include <string> | ||||
| #include <mutex> | ||||
| #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<char, 0x20000> save_buffer; | ||||
| struct { | ||||
|     std::array<char, 0x20000> 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; | ||||
| } | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Mr-Wiseguy
						Mr-Wiseguy