Asynchronous saving
This commit is contained in:
parent
b9c9e65672
commit
1ef8656a93
|
@ -56,30 +56,3 @@ void Sram_UpdateWriteToFlashOwlSave(SramContext* sramCtx) {
|
||||||
Lib_MemCpy(&gSaveContext, sramCtx->saveBuf, offsetof(SaveContext, fileNum));
|
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();
|
update_poll_time();
|
||||||
|
|
||||||
ultramodern::send_si_message();
|
ultramodern::send_si_message(rdram);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
|
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) {
|
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) {
|
extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
#include "../ultramodern/ultra64.h"
|
#include "../ultramodern/ultra64.h"
|
||||||
|
|
||||||
void save_write(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(uint8_t* rdram, gpr 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 eeprom_block_size = 8;
|
||||||
constexpr int eep4_size = 4096;
|
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;
|
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_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_write(RDRAM_ARG PTR(void) 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_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||||
void save_clear(uint32_t start, uint32_t size, char value);
|
void save_clear(uint32_t start, uint32_t size, char value);
|
||||||
|
|
||||||
std::array<char, page_size> write_buffer;
|
std::array<char, page_size> write_buffer;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <mutex>
|
||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
#include "recomp_game.h"
|
#include "recomp_game.h"
|
||||||
#include "recomp_config.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_folder = u8"saves";
|
||||||
const std::u8string save_filename = std::u8string{recomp::mm_game_id} + u8".bin";
|
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 };
|
std::ofstream save_file{ get_save_file_path(), std::ios_base::binary };
|
||||||
|
|
||||||
if (save_file.good()) {
|
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 {
|
} else {
|
||||||
fprintf(stderr, "Failed to save!\n");
|
fprintf(stderr, "Failed to save!\n");
|
||||||
std::exit(EXIT_FAILURE);
|
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) {
|
void save_write_ptr(const void* in, uint32_t offset, uint32_t count) {
|
||||||
memcpy(&save_buffer[offset], in, count);
|
{
|
||||||
update_save_file();
|
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||||
}
|
memcpy(&save_context.save_buffer[offset], in, count);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
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++) {
|
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) {
|
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 };
|
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||||
|
std::fill_n(save_context.save_buffer.begin() + start, size, value);
|
||||||
if (save_file.good()) {
|
|
||||||
save_file.write(save_buffer.data(), save_buffer.size());
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "Failed to save!\n");
|
|
||||||
std::exit(EXIT_FAILURE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
std::filesystem::path save_file_path = get_save_file_path();
|
||||||
|
|
||||||
// Ensure the save file directory exists.
|
// Ensure the save file directory exists.
|
||||||
|
@ -118,14 +158,20 @@ void ultramodern::save_init() {
|
||||||
// Read the save file if it exists.
|
// Read the save file if it exists.
|
||||||
std::ifstream save_file{ save_file_path, std::ios_base::binary };
|
std::ifstream save_file{ save_file_path, std::ios_base::binary };
|
||||||
if (save_file.good()) {
|
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 {
|
} else {
|
||||||
// Otherwise clear the save file to all zeroes.
|
// 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 asynchronous transfer
|
||||||
// TODO implement unaligned DMA correctly
|
// TODO implement unaligned DMA correctly
|
||||||
if (direction == 0) {
|
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 mb = ctx->r4;
|
||||||
uint32_t pri = ctx->r5;
|
uint32_t pri = ctx->r5;
|
||||||
uint32_t direction = ctx->r6;
|
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);
|
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;
|
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);
|
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
|
||||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5);
|
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5);
|
||||||
uint32_t direction = ctx->r6;
|
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);
|
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;
|
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);
|
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
|
||||||
uint32_t devAddr = handle->baseAddress | ctx->r5;
|
uint32_t devAddr = handle->baseAddress | ctx->r5;
|
||||||
gpr dramAddr = ctx->r6;
|
gpr dramAddr = ctx->r6;
|
||||||
|
@ -202,7 +248,7 @@ extern "C" void osEPiReadIo_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||||
|
|
||||||
if (physical_addr > rom_base) {
|
if (physical_addr > rom_base) {
|
||||||
// cart rom
|
// 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 {
|
} else {
|
||||||
// sram
|
// sram
|
||||||
assert(false && "SRAM ReadIo unimplemented");
|
assert(false && "SRAM ReadIo unimplemented");
|
||||||
|
@ -211,10 +257,10 @@ extern "C" void osEPiReadIo_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||||
ctx->r2 = 0;
|
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;
|
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;
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -437,4 +437,5 @@ void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::a
|
||||||
game_thread.join();
|
game_thread.join();
|
||||||
ultramodern::join_event_threads();
|
ultramodern::join_event_threads();
|
||||||
ultramodern::join_thread_cleaner_thread();
|
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() {
|
void ultramodern::send_si_message(RDRAM_ARG1) {
|
||||||
uint8_t* rdram = events_context.rdram;
|
|
||||||
osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK);
|
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 gfx_thread_ready;
|
||||||
moodycamel::LightweightSemaphore task_thread_ready;
|
moodycamel::LightweightSemaphore task_thread_ready;
|
||||||
events_context.rdram = rdram;
|
events_context.rdram = rdram;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
#include "ultra64.h"
|
#include "ultra64.h"
|
||||||
#include "ultramodern.hpp"
|
#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::set_main_thread();
|
||||||
ultramodern::init_events(rdram, window_handle);
|
ultramodern::init_events(PASS_RDRAM window_handle);
|
||||||
ultramodern::init_timers(rdram);
|
ultramodern::init_timers(PASS_RDRAM1);
|
||||||
ultramodern::init_audio();
|
ultramodern::init_audio();
|
||||||
ultramodern::save_init();
|
ultramodern::init_saving(PASS_RDRAM1);
|
||||||
ultramodern::init_thread_cleanup();
|
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
|
constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash
|
||||||
|
|
||||||
// Initialization.
|
// Initialization.
|
||||||
void preinit(uint8_t* rdram, WindowHandle window_handle);
|
void preinit(RDRAM_ARG WindowHandle window_handle);
|
||||||
void save_init();
|
void init_saving(RDRAM_ARG1);
|
||||||
void init_events(uint8_t* rdram, WindowHandle window_handle);
|
void init_events(RDRAM_ARG WindowHandle window_handle);
|
||||||
void init_timers(RDRAM_ARG1);
|
void init_timers(RDRAM_ARG1);
|
||||||
void init_thread_cleanup();
|
void init_thread_cleanup();
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ PTR(OSThread) this_thread();
|
||||||
void set_main_thread();
|
void set_main_thread();
|
||||||
bool is_game_thread();
|
bool is_game_thread();
|
||||||
void submit_rsp_task(RDRAM_ARG PTR(OSTask) task);
|
void submit_rsp_task(RDRAM_ARG PTR(OSTask) task);
|
||||||
void send_si_message();
|
void send_si_message(RDRAM_ARG1);
|
||||||
uint32_t get_speed_multiplier();
|
uint32_t get_speed_multiplier();
|
||||||
|
|
||||||
// Time
|
// Time
|
||||||
|
@ -153,6 +153,7 @@ bool is_game_started();
|
||||||
void quit();
|
void quit();
|
||||||
void join_event_threads();
|
void join_event_threads();
|
||||||
void join_thread_cleaner_thread();
|
void join_thread_cleaner_thread();
|
||||||
|
void join_saving_thread();
|
||||||
|
|
||||||
} // namespace ultramodern
|
} // namespace ultramodern
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue