use core::{ alloc::Layout, marker::PhantomData, ops::{Deref, DerefMut}, }; use alloc::alloc::{alloc, dealloc}; use serde::{Serialize, de::DeserializeOwned}; use crate::ScriptContext; mod tortuise { #[link(wasm_import_module = "tortuise")] unsafe extern "C" { pub safe fn storage_size(context: u64) -> usize; pub unsafe fn load_storage(context: u64, ptr: *mut u8); pub unsafe fn save_storage(context: u64, ptr: *const u8, size: usize); pub safe fn is_saving_storage(context: u64) -> usize; pub safe fn saving_storage_failed(context: u64) -> usize; pub safe fn config_size(context: u64) -> usize; pub unsafe fn load_config(context: u64, ptr: *mut u8); pub unsafe fn save_config(context: u64, ptr: *const u8, size: usize); pub safe fn is_saving_config(context: u64) -> usize; pub safe fn saving_config_failed(context: u64) -> usize; } } fn load_storage(context: u64) -> Option { unsafe { let size = tortuise::storage_size(context); let layout = Layout::array::(size).unwrap(); if layout.size() == 0 { return None; } let ptr = alloc(layout); tortuise::load_storage(context, ptr); let data = core::slice::from_raw_parts(ptr, size); let data = messagepack_serde::from_slice(data).ok()?; dealloc(ptr, layout); Some(data) } } fn save_storage(context: u64, state: &T) { unsafe { let data = messagepack_serde::to_vec(state).unwrap(); tortuise::save_storage(context, data.as_ptr(), data.len()); } } fn load_config(context: u64) -> Option { unsafe { let size = tortuise::config_size(context); let layout = Layout::array::(size).unwrap(); if layout.size() == 0 { return None; } let ptr = alloc(layout); tortuise::load_config(context, ptr); let data = core::slice::from_raw_parts(ptr, size); let data = toml::from_slice(data).ok()?; dealloc(ptr, layout); Some(data) } } fn save_config(context: u64, state: &T) { unsafe { let data = toml::to_string(state).unwrap(); tortuise::save_config(context, data.as_ptr(), data.len()); } } pub struct Storage { initial_state: fn() -> T, } pub struct Config { initial_state: fn() -> T, } pub struct StorageMut<'a, 'cx, T: Serialize + Eq> { context: u64, original: T, data: T, _phantom: PhantomData<(&'a (), &'cx ())>, } pub struct ConfigMut<'a, 'cx, T: Serialize + Eq> { context: u64, original: T, data: T, _phantom: PhantomData<(&'a (), &'cx ())>, } impl Storage { pub const fn new(initial_state: fn() -> T) -> Self { Self { initial_state } } pub fn load(&self, context: &ScriptContext) -> T { load_storage(context.0).unwrap_or_else(self.initial_state) } pub fn load_mut<'a, 'cx>(&self, context: &'cx mut ScriptContext<'a>) -> StorageMut<'a, 'cx, T> { let data = load_storage(context.0).unwrap_or_else(self.initial_state); StorageMut { context: context.0, original: data.clone(), data, _phantom: PhantomData, } } pub fn is_saving(&self, context: &ScriptContext) -> bool { tortuise::is_saving_storage(context.0) > 0 } pub fn save_failed(&self, context: &ScriptContext) -> bool { tortuise::saving_storage_failed(context.0) > 0 } } impl Config { pub const fn new(initial_state: fn() -> T) -> Self { Self { initial_state } } pub fn load(&self, context: &ScriptContext) -> T { load_config(context.0).unwrap_or_else(self.initial_state) } pub fn load_mut<'a, 'cx>(&self, context: &'cx mut ScriptContext<'a>) -> StorageMut<'a, 'cx, T> { let data = load_config(context.0).unwrap_or_else(self.initial_state); StorageMut { context: context.0, original: data.clone(), data, _phantom: PhantomData, } } pub fn is_saving(&self, context: &ScriptContext) -> bool { tortuise::is_saving_config(context.0) > 0 } pub fn save_failed(&self, context: &ScriptContext) -> bool { tortuise::saving_config_failed(context.0) > 0 } } impl Deref for StorageMut<'_, '_, T> { type Target = T; fn deref(&self) -> &Self::Target { &self.data } } impl DerefMut for StorageMut<'_, '_, T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } } impl Drop for StorageMut<'_, '_, T> { fn drop(&mut self) { if self.data != self.original { save_storage(self.context, &self.data); } } } impl Deref for ConfigMut<'_, '_, T> { type Target = T; fn deref(&self) -> &Self::Target { &self.data } } impl DerefMut for ConfigMut<'_, '_, T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } } impl Drop for ConfigMut<'_, '_, T> { fn drop(&mut self) { if self.data != self.original { save_config(self.context, &self.data); } } }