summaryrefslogtreecommitdiff
path: root/platforms/tortuise-script-wasm/src/storage.rs
blob: 2606e2b349fef8a854431710ca70ee170d9d4cb1 (plain)
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<T: DeserializeOwned>(context: u64) -> Option<T> {
	unsafe {
		let size = tortuise::storage_size(context);
		let layout = Layout::array::<u8>(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<T: Serialize>(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<T: DeserializeOwned>(context: u64) -> Option<T> {
	unsafe {
		let size = tortuise::config_size(context);
		let layout = Layout::array::<u8>(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<T: Serialize>(context: u64, state: &T) {
	unsafe {
		let data = toml::to_string(state).unwrap();
		tortuise::save_config(context, data.as_ptr(), data.len());
	}
}

pub struct Storage<T> {
	initial_state: fn() -> T,
}

pub struct Config<T> {
	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<T: Serialize + DeserializeOwned + Clone + Eq> Storage<T> {
	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<T: Serialize + DeserializeOwned + Clone + Eq> Config<T> {
	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<T: Serialize + Eq> Deref for StorageMut<'_, '_, T> {
	type Target = T;

	fn deref(&self) -> &Self::Target {
		&self.data
	}
}

impl<T: Serialize + Eq> DerefMut for StorageMut<'_, '_, T> {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.data
	}
}

impl<T: Serialize + Eq> Drop for StorageMut<'_, '_, T> {
	fn drop(&mut self) {
		if self.data != self.original {
			save_storage(self.context, &self.data);
		}
	}
}

impl<T: Serialize + Eq> Deref for ConfigMut<'_, '_, T> {
	type Target = T;

	fn deref(&self) -> &Self::Target {
		&self.data
	}
}

impl<T: Serialize + Eq> DerefMut for ConfigMut<'_, '_, T> {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.data
	}
}

impl<T: Serialize + Eq> Drop for ConfigMut<'_, '_, T> {
	fn drop(&mut self) {
		if self.data != self.original {
			save_config(self.context, &self.data);
		}
	}
}