Savestates: HLE state saving POC in sys_lwmutex

This commit is contained in:
Eladash 2022-07-05 14:12:21 +03:00 committed by Ivan
parent 2815aecd0c
commit b692108f1e
12 changed files with 159 additions and 63 deletions

View file

@ -39,6 +39,8 @@ public:
{ {
return value; return value;
} }
ENABLE_BITWISE_SERIALIZATION;
}; };
enum CellNotAnError : s32 enum CellNotAnError : s32

View file

@ -69,10 +69,7 @@ error_code sys_lwcond_signal(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond)
// call the syscall // call the syscall
if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, u32{umax}, 1)) if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, u32{umax}, 1))
{ {
if (ppu.test_stopped()) static_cast<void>(ppu.test_stopped());
{
return 0;
}
lwmutex->all_info--; lwmutex->all_info--;
@ -108,10 +105,7 @@ error_code sys_lwcond_signal(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond)
// call the syscall // call the syscall
if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, u32{umax}, 3)) if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, u32{umax}, 3))
{ {
if (ppu.test_stopped()) static_cast<void>(ppu.test_stopped());
{
return 0;
}
lwmutex->lock_var.atomic_op([&](sys_lwmutex_t::sync_var_t& var) lwmutex->lock_var.atomic_op([&](sys_lwmutex_t::sync_var_t& var)
{ {
@ -158,10 +152,7 @@ error_code sys_lwcond_signal_all(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond)
return res; return res;
} }
if (ppu.test_stopped()) static_cast<void>(ppu.test_stopped());
{
return 0;
}
lwmutex->all_info += +res; lwmutex->all_info += +res;
return CELL_OK; return CELL_OK;
@ -183,10 +174,7 @@ error_code sys_lwcond_signal_all(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond)
// if locking succeeded, call the syscall // if locking succeeded, call the syscall
error_code res = _sys_lwcond_signal_all(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, 1); error_code res = _sys_lwcond_signal_all(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, 1);
if (ppu.test_stopped()) static_cast<void>(ppu.test_stopped());
{
return 0;
}
if (res > 0) if (res > 0)
{ {
@ -225,10 +213,7 @@ error_code sys_lwcond_signal_to(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond, u
// call the syscall // call the syscall
if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, ppu_thread_id, 1)) if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, ppu_thread_id, 1))
{ {
if (ppu.test_stopped()) static_cast<void>(ppu.test_stopped());
{
return 0;
}
lwmutex->all_info--; lwmutex->all_info--;
@ -261,10 +246,7 @@ error_code sys_lwcond_signal_to(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond, u
// call the syscall // call the syscall
if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, ppu_thread_id, 3)) if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, ppu_thread_id, 3))
{ {
if (ppu.test_stopped()) static_cast<void>(ppu.test_stopped());
{
return 0;
}
lwmutex->lock_var.atomic_op([&](sys_lwmutex_t::sync_var_t& var) lwmutex->lock_var.atomic_op([&](sys_lwmutex_t::sync_var_t& var)
{ {
@ -290,6 +272,9 @@ error_code sys_lwcond_wait(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond, u64 ti
return sys_cond_wait(ppu, lwcond->lwcond_queue, timeout); return sys_cond_wait(ppu, lwcond->lwcond_queue, timeout);
} }
auto& sstate = *ppu.optional_savestate_state;
const auto lwcond_ec = sstate.try_read<error_code>().second;
const be_t<u32> tid(ppu.id); const be_t<u32> tid(ppu.id);
const vm::ptr<sys_lwmutex_t> lwmutex = lwcond->lwmutex; const vm::ptr<sys_lwmutex_t> lwmutex = lwcond->lwmutex;
@ -301,18 +286,22 @@ error_code sys_lwcond_wait(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond, u64 ti
} }
// save old recursive value // save old recursive value
const be_t<u32> recursive_value = lwmutex->recursive_count; const be_t<u32> recursive_value = !lwcond_ec ? lwmutex->recursive_count : sstate.operator be_t<u32>();
// set special value // set special value
lwmutex->vars.owner = lwmutex_reserved; lwmutex->vars.owner = lwmutex_reserved;
lwmutex->recursive_count = 0; lwmutex->recursive_count = 0;
// call the syscall // call the syscall
const error_code res = _sys_lwcond_queue_wait(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, timeout); const error_code res = !lwcond_ec ? _sys_lwcond_queue_wait(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, timeout) : lwcond_ec;
if (ppu.test_stopped()) static_cast<void>(ppu.test_stopped());
if (ppu.state & cpu_flag::again)
{ {
return 0; sstate.pos = 0;
sstate(error_code{}, recursive_value); // Not aborted on mutex sleep
return {};
} }
if (res == CELL_OK || res + 0u == CELL_ESRCH) if (res == CELL_OK || res + 0u == CELL_ESRCH)
@ -341,6 +330,13 @@ error_code sys_lwcond_wait(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond, u64 ti
return res2; return res2;
} }
if (ppu.state & cpu_flag::again)
{
sstate.pos = 0;
sstate(res, recursive_value);
return {};
}
// if successfully locked, restore recursive value // if successfully locked, restore recursive value
lwmutex->recursive_count = recursive_value; lwmutex->recursive_count = recursive_value;

View file

@ -99,6 +99,15 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr<sys_lwmutex_t> lwmutex, u64
return sys_mutex_lock(ppu, lwmutex->sleep_queue, timeout); return sys_mutex_lock(ppu, lwmutex->sleep_queue, timeout);
} }
auto& sstate = *ppu.optional_savestate_state;
const bool aborted = sstate.try_read<bool>().second;
if (aborted)
{
// Restore timeout (SYS_SYNC_RETRY mode)
sstate(timeout);
}
const be_t<u32> tid(ppu.id); const be_t<u32> tid(ppu.id);
// try to lock lightweight mutex // try to lock lightweight mutex
@ -154,7 +163,10 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr<sys_lwmutex_t> lwmutex, u64
} }
// atomically increment waiter value using 64 bit op // atomically increment waiter value using 64 bit op
lwmutex->all_info++; if (!aborted)
{
lwmutex->all_info++;
}
if (lwmutex->vars.owner.compare_and_swap_test(lwmutex_free, tid)) if (lwmutex->vars.owner.compare_and_swap_test(lwmutex_free, tid))
{ {
@ -167,9 +179,13 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr<sys_lwmutex_t> lwmutex, u64
// lock using the syscall // lock using the syscall
const error_code res = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout); const error_code res = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout);
if (ppu.test_stopped()) static_cast<void>(ppu.test_stopped());
if (ppu.state & cpu_flag::again)
{ {
return 0; sstate.pos = 0;
sstate(true, timeout); // Aborted
return {};
} }
lwmutex->all_info--; lwmutex->all_info--;
@ -216,9 +232,13 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr<sys_lwmutex_t> lwmutex, u64
const error_code res_ = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout); const error_code res_ = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout);
if (ppu.test_stopped()) static_cast<void>(ppu.test_stopped());
if (ppu.state & cpu_flag::again)
{ {
return 0; sstate.pos = 0;
sstate(true, timeout); // Aborted
return {};
} }
if (res_ == CELL_OK) if (res_ == CELL_OK)

View file

@ -24,7 +24,8 @@ error_code sys_spinlock_lock(ppu_thread& ppu, vm::ptr<atomic_be_t<u32>> lock)
{ {
if (ppu.test_stopped()) if (ppu.test_stopped())
{ {
return 0; ppu.state += cpu_flag::again;
return {};
} }
} }

View file

@ -1396,7 +1396,7 @@ void ppu_thread::cpu_task()
thread_ctrl::wait_on<atomic_wait::op_ne>(g_progr_ptotal, 0); thread_ctrl::wait_on<atomic_wait::op_ne>(g_progr_ptotal, 0);
g_fxo->get<progress_dialog_workaround>().skip_the_progress_dialog = true; g_fxo->get<progress_dialog_workaround>().skip_the_progress_dialog = true;
// Sadly we can't postpone initializing guest time because we need ti run PPU threads // Sadly we can't postpone initializing guest time because we need to run PPU threads
// (the farther it's postponed, the less accuracy of guest time has been lost) // (the farther it's postponed, the less accuracy of guest time has been lost)
Emu.FixGuestTime(); Emu.FixGuestTime();
@ -1527,6 +1527,8 @@ ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u3
gpr[4] = param.arg1; gpr[4] = param.arg1;
} }
optional_savestate_state = std::make_shared<utils::serial>();
// Trigger the scheduler // Trigger the scheduler
state += cpu_flag::suspend; state += cpu_flag::suspend;
@ -1575,10 +1577,15 @@ bool ppu_thread::savable() const
void ppu_thread::serialize_common(utils::serial& ar) void ppu_thread::serialize_common(utils::serial& ar)
{ {
ar(gpr, fpr, cr, fpscr.bits, lr, ctr, vrsave, cia, xer, sat, nj, prio, optional_syscall_state); ar(gpr, fpr, cr, fpscr.bits, lr, ctr, vrsave, cia, xer, sat, nj, prio, optional_savestate_state);
for (v128& reg : vr) for (v128& reg : vr)
ar(reg._bytes); ar(reg._bytes);
if (optional_savestate_state->data.empty())
{
optional_savestate_state->clear();
}
} }
ppu_thread::ppu_thread(utils::serial& ar) ppu_thread::ppu_thread(utils::serial& ar)
@ -1669,8 +1676,12 @@ ppu_thread::ppu_thread(utils::serial& ar)
+[](ppu_thread& ppu) -> bool +[](ppu_thread& ppu) -> bool
{ {
const u32 op = vm::read32(ppu.cia);
const auto& table = g_fxo->get<ppu_interpreter_rt>();
ppu.loaded_from_savestate = true; ppu.loaded_from_savestate = true;
ppu_execute_syscall(ppu, ppu.gpr[11]); table.decode(op)(ppu, {op}, vm::_ptr<u32>(ppu.cia), &ppu_ret);
ppu.optional_savestate_state->clear(); // Reset to writing state
ppu.loaded_from_savestate = false; ppu.loaded_from_savestate = false;
return true; return true;
} }
@ -1708,6 +1719,8 @@ ppu_thread::ppu_thread(utils::serial& ar)
void ppu_thread::save(utils::serial& ar) void ppu_thread::save(utils::serial& ar)
{ {
USING_SERIALIZATION_VERSION(ppu);
const u64 entry = std::bit_cast<u64>(entry_func); const u64 entry = std::bit_cast<u64>(entry_func);
ppu_join_status _joiner = joiner; ppu_join_status _joiner = joiner;
@ -1870,7 +1883,7 @@ void ppu_thread::fast_call(u32 addr, u64 rtoc)
} }
else if (old_cia) else if (old_cia)
{ {
if (state & cpu_flag::exit) if (state & cpu_flag::again)
{ {
ppu_log.error("HLE callstack savestate is not implemented!"); ppu_log.error("HLE callstack savestate is not implemented!");
} }

View file

@ -318,9 +318,9 @@ public:
} thread_name{ this }; } thread_name{ this };
// For savestates // For savestates
bool stop_flag_removal_protection = false; // If set, Emulator::Run won't remove stop flag bool stop_flag_removal_protection = false; // If set, Emulator::Run won't remove stop flag
bool loaded_from_savestate = false; // Indicates the thread had just started straight from savestate load bool loaded_from_savestate = false; // Indicates the thread had just started straight from savestate load
u64 optional_syscall_state{}; std::shared_ptr<utils::serial> optional_savestate_state;
bool interrupt_thread_executing = false; bool interrupt_thread_executing = false;
be_t<u64>* get_stack_arg(s32 i, u64 align = alignof(u64)); be_t<u64>* get_stack_arg(s32 i, u64 align = alignof(u64));

View file

@ -285,6 +285,8 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout)
// Further function result // Further function result
ppu.gpr[3] = CELL_OK; ppu.gpr[3] = CELL_OK;
auto& sstate = *ppu.optional_savestate_state;
const auto cond = idm::get<lv2_obj, lv2_cond>(cond_id, [&](lv2_cond& cond) -> s64 const auto cond = idm::get<lv2_obj, lv2_cond>(cond_id, [&](lv2_cond& cond) -> s64
{ {
if (!ppu.loaded_from_savestate && cond.mutex->owner >> 1 != ppu.id) if (!ppu.loaded_from_savestate && cond.mutex->owner >> 1 != ppu.id)
@ -294,7 +296,10 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout)
std::lock_guard lock(cond.mutex->mutex); std::lock_guard lock(cond.mutex->mutex);
if (ppu.loaded_from_savestate && ppu.optional_syscall_state & 1) const u64 syscall_state = sstate.try_read<u64>().second;
sstate.clear();
if (syscall_state & 1)
{ {
// Mutex sleep // Mutex sleep
ensure(!cond.mutex->try_own(ppu, ppu.id)); ensure(!cond.mutex->try_own(ppu, ppu.id));
@ -309,7 +314,7 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout)
if (ppu.loaded_from_savestate) if (ppu.loaded_from_savestate)
{ {
cond.sleep(ppu, timeout); cond.sleep(ppu, timeout);
return static_cast<u32>(ppu.optional_syscall_state >> 32); return static_cast<u32>(syscall_state >> 32);
} }
// Unlock the mutex // Unlock the mutex
@ -356,7 +361,9 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout)
break; break;
} }
ppu.optional_syscall_state = u32{mutex_sleep} | (u64{static_cast<u32>(cond.ret)} << 32); const u64 optional_syscall_state = u32{mutex_sleep} | (u64{static_cast<u32>(cond.ret)} << 32);
sstate(optional_syscall_state);
ppu.state += cpu_flag::again; ppu.state += cpu_flag::again;
return {}; return {};
} }

View file

@ -321,6 +321,8 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id
std::shared_ptr<lv2_lwmutex> mutex; std::shared_ptr<lv2_lwmutex> mutex;
auto& sstate = *ppu.optional_savestate_state;
const auto cond = idm::get<lv2_obj, lv2_lwcond>(lwcond_id, [&](lv2_lwcond& cond) const auto cond = idm::get<lv2_obj, lv2_lwcond>(lwcond_id, [&](lv2_lwcond& cond)
{ {
mutex = idm::get_unlocked<lv2_obj, lv2_lwmutex>(lwmutex_id); mutex = idm::get_unlocked<lv2_obj, lv2_lwmutex>(lwmutex_id);
@ -335,7 +337,10 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id
std::lock_guard lock(cond.mutex); std::lock_guard lock(cond.mutex);
if (ppu.loaded_from_savestate && ppu.optional_syscall_state) const bool mutex_sleep = sstate.try_read<bool>().second;
sstate.clear();
if (mutex_sleep)
{ {
// Special: loading state from the point of waiting on lwmutex sleep queue // Special: loading state from the point of waiting on lwmutex sleep queue
std::lock_guard lock2(mutex->mutex); std::lock_guard lock2(mutex->mutex);
@ -403,7 +408,7 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id
break; break;
} }
ppu.optional_syscall_state = +mutex_sleep; sstate(mutex_sleep);
ppu.state += cpu_flag::again; ppu.state += cpu_flag::again;
break; break;
} }

View file

@ -9,6 +9,19 @@ namespace id_manager
thread_local u32 g_id = 0; thread_local u32 g_id = 0;
} }
template <>
bool serialize<std::shared_ptr<utils::serial>>(utils::serial& ar, std::shared_ptr<utils::serial>& o)
{
if (!o || !ar.is_writing())
{
o.reset();
o = std::make_shared<utils::serial>();
}
ar(o->data);
return true;
}
std::vector<std::pair<u128, id_manager::typeinfo>>& id_manager::get_typeinfo_map() std::vector<std::pair<u128, id_manager::typeinfo>>& id_manager::get_typeinfo_map()
{ {
// Magic static // Magic static

View file

@ -1569,7 +1569,7 @@ namespace vm
if (!(ar.data.back() & page_writable) && is_memory_read_only_of_executable(addr)) if (!(ar.data.back() & page_writable) && is_memory_read_only_of_executable(addr))
{ {
// Revert changes // Revert changes
ar.data.resize(ar.data.size() - (sizeof(u32) * 2 + sizeof(memory_page))); ar.data.resize(ar.seek_end(sizeof(u32) * 2 + sizeof(memory_page)));
vm_log.success("Removed read-only memory block of the executable from savestate. (addr=0x%x, size=0x%x)", addr, shm.first); vm_log.success("Removed read-only memory block of the executable from savestate. (addr=0x%x, size=0x%x)", addr, shm.first);
continue; continue;
} }

View file

@ -71,7 +71,7 @@ struct serial_ver_t
std::set<u32> compatible_versions; std::set<u32> compatible_versions;
}; };
static std::array<serial_ver_t, 18> s_serial_versions; static std::array<serial_ver_t, 22> s_serial_versions;
#define SERIALIZATION_VER(name, identifier, ...) \ #define SERIALIZATION_VER(name, identifier, ...) \
\ \
@ -88,10 +88,10 @@ static std::array<serial_ver_t, 18> s_serial_versions;
return ::s_serial_versions[identifier].current_version;\ return ::s_serial_versions[identifier].current_version;\
} }
SERIALIZATION_VER(global_version, 0, 10) // For stuff not listed here SERIALIZATION_VER(global_version, 0, 11) // For stuff not listed here
SERIALIZATION_VER(ppu, 1, 1) SERIALIZATION_VER(ppu, 1, 1)
SERIALIZATION_VER(spu, 2, 1, 2) SERIALIZATION_VER(spu, 2, 1, 2)
SERIALIZATION_VER(lv2_sync, 3, 1) SERIALIZATION_VER(lv2_sync, 3, 1)
SERIALIZATION_VER(lv2_vm, 4, 1) SERIALIZATION_VER(lv2_vm, 4, 1)
SERIALIZATION_VER(lv2_net, 5, 1) SERIALIZATION_VER(lv2_net, 5, 1)
SERIALIZATION_VER(lv2_fs, 6, 1) SERIALIZATION_VER(lv2_fs, 6, 1)
@ -121,7 +121,10 @@ SERIALIZATION_VER(cellCamera, 14, 1)
SERIALIZATION_VER(cellGem, 15, 1) SERIALIZATION_VER(cellGem, 15, 1)
SERIALIZATION_VER(sceNpTrophy, 16, 1) SERIALIZATION_VER(sceNpTrophy, 16, 1)
SERIALIZATION_VER(cellMusic, 17, 1) SERIALIZATION_VER(cellMusic, 17, 1)
SERIALIZATION_VER(cellVoice, 15, 1) SERIALIZATION_VER(cellVoice, 18, 1)
SERIALIZATION_VER(cellGcm, 19, 1)
SERIALIZATION_VER(sysPrxForUser, 20, 1)
SERIALIZATION_VER(cellSaveData, 21, 1)
#undef SERIALIZATION_VER #undef SERIALIZATION_VER
@ -857,12 +860,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
nse_t<u64, 1> offset; nse_t<u64, 1> offset;
}; };
if (m_ar->data.size() <= sizeof(file_header)) const auto header = m_ar->try_read<file_header>().second;
{
return game_boot_result::savestate_corrupted;
}
const file_header header = m_ar->operator file_header();
if (header.magic != "RPCS3SAV"_u64) if (header.magic != "RPCS3SAV"_u64)
{ {
@ -2376,6 +2374,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate)
ar(usz{}); // Reserve memory to be patched later with correct size ar(usz{}); // Reserve memory to be patched later with correct size
const usz old_size = ar.data.size(); const usz old_size = ar.data.size();
ar.data = tar_object::save_directory(path, std::move(ar.data)); ar.data = tar_object::save_directory(path, std::move(ar.data));
ar.seek_end();
const usz tar_size = ar.data.size() - old_size; const usz tar_size = ar.data.size() - old_size;
std::memcpy(ar.data.data() + old_size - sizeof(usz), &tar_size, sizeof(usz)); std::memcpy(ar.data.data() + old_size - sizeof(usz), &tar_size, sizeof(usz));
sys_log.success("Saved the contents of directory '%s' (size=0x%x)", path, tar_size); sys_log.success("Saved the contents of directory '%s' (size=0x%x)", path, tar_size);
@ -2511,7 +2510,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate)
} }
auto& ar = *m_ar; auto& ar = *m_ar;
const usz pos = ar.data.size(); const usz pos = ar.seek_end();
std::memcpy(&ar.data[10], &pos, 8);// Set offset std::memcpy(&ar.data[10], &pos, 8);// Set offset
ar(used_serial); ar(used_serial);

View file

@ -35,7 +35,8 @@ namespace utils
struct serial struct serial
{ {
std::vector<u8> data; std::vector<u8> data;
usz pos = umax; usz pos = 0;
bool m_is_writing = true;
serial() = default; serial() = default;
serial(const serial&) = delete; serial(const serial&) = delete;
@ -44,7 +45,7 @@ namespace utils
// Checks if this instance is currently used for serialization // Checks if this instance is currently used for serialization
bool is_writing() const bool is_writing() const
{ {
return pos == umax; return m_is_writing;
} }
// Reserve memory for serialization // Reserve memory for serialization
@ -61,7 +62,8 @@ namespace utils
{ {
if (is_writing()) if (is_writing())
{ {
data.insert(data.end(), static_cast<const u8*>(ptr), static_cast<const u8*>(ptr) + size); data.insert(data.begin() + pos, static_cast<const u8*>(ptr), static_cast<const u8*>(ptr) + size);
pos += size;
return true; return true;
} }
@ -287,7 +289,7 @@ namespace utils
, serialize(const_cast<std::remove_cvref_t<Args>&>(static_cast<const Args&>(args)))), ...); , serialize(const_cast<std::remove_cvref_t<Args>&>(static_cast<const Args&>(args)))), ...);
} }
// Convert serialization manager to deserializion manager (can't go the other way) // Convert serialization manager to deserializion manager
// If no arg is provided reuse saved buffer // If no arg is provided reuse saved buffer
void set_reading_state(std::vector<u8>&& _data = std::vector<u8>{}) void set_reading_state(std::vector<u8>&& _data = std::vector<u8>{})
{ {
@ -296,9 +298,25 @@ namespace utils
data = std::move(_data); data = std::move(_data);
} }
m_is_writing = false;
pos = 0; pos = 0;
} }
// Reset to empty serialization manager
void clear()
{
data.clear();
m_is_writing = true;
pos = 0;
}
usz seek_end(usz backwards = 0)
{
ensure(pos >= backwards);
pos = data.size() - backwards;
return pos;
}
template <typename T> requires (std::is_copy_constructible_v<std::remove_const_t<T>>) && (std::is_constructible_v<std::remove_const_t<T>> || Bitcopy<std::remove_const_t<T>> || template <typename T> requires (std::is_copy_constructible_v<std::remove_const_t<T>>) && (std::is_constructible_v<std::remove_const_t<T>> || Bitcopy<std::remove_const_t<T>> ||
std::is_constructible_v<std::remove_const_t<T>, stx::exact_t<serial&>> || TupleAlike<std::remove_const_t<T>>) std::is_constructible_v<std::remove_const_t<T>, stx::exact_t<serial&>> || TupleAlike<std::remove_const_t<T>>)
operator T() operator T()
@ -331,10 +349,32 @@ namespace utils
} }
} }
// Returns true if writable or readable and valid template <typename T> requires (std::is_copy_constructible_v<T> && std::is_constructible_v<T> && Bitcopy<T>)
std::pair<bool, T> try_read()
{
if (is_writing())
{
return {};
}
const usz left = data.size() - pos;
using type = std::remove_const_t<T>;
if (left >= sizeof(type))
{
u8 buf[sizeof(type)]{};
ensure(raw_serialize(buf, sizeof(buf)));
return {true, std::bit_cast<type>(buf)};
}
return {};
}
// Returns true if valid, can be invalidated by setting pos to umax
// Used when an invalid state is encountered somewhere in a place we can't check success code such as constructor)
bool is_valid() const bool is_valid() const
{ {
return is_writing() || pos < data.size(); return pos <= data.size();
} }
}; };
} }