summaryrefslogtreecommitdiff
path: root/platforms/tortuise-script-wasm/src/lib.rs
blob: 6394b8f72e2bc8c99db3bf1db1f38cf257d6564d (plain)
#![no_std]

use core::marker::PhantomData;

use alloc::vec::Vec;
use serde::Serialize;

extern crate alloc;

pub mod allocator;
pub mod buffer;
pub mod entity;
pub mod input;
pub mod log;
pub mod process;
pub mod random;
pub mod scene;
pub mod script;
pub mod state;
pub mod storage;
pub mod time;

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct ScriptContext<'a>(u64, PhantomData<&'a ()>);

pub struct TortuiseScript {
	pub start: fn(cx: ScriptContext),
	pub update: fn(cx: ScriptContext),
	pub end: fn(cx: ScriptContext),
}

#[derive(Debug, Default, Clone)]
pub struct TortuiseScene {
	pub scripts: &'static [&'static str],
}

fn default_script(_cx: ScriptContext) {}

impl Default for TortuiseScript {
	fn default() -> Self {
		Self {
			start: default_script,
			update: default_script,
			end: default_script,
		}
	}
}

impl TortuiseScript {
	pub const fn empty() -> Self {
		Self {
			start: default_script,
			update: default_script,
			end: default_script,
		}
	}
}

#[doc(hidden)]
pub fn to_vec<T: Serialize>(val: &T) -> &'static [u8] {
	Vec::leak(messagepack_serde::to_vec(val).unwrap())
}

#[doc(hidden)]
pub type Lazy<T> = spin::LazyLock<T>;

#[macro_export]
macro_rules! game {
	{
		name: $name: literal,
		author: $author: literal,
		tld: $tld: literal,
		first_scene: $first_scene: literal,
		scenes: { $($scene_name: literal => $scene: expr),* },
		scripts: { $($script_name: literal => $script: expr),* }
	} => {
		use alloc::boxed::Box;

		#[derive(Debug, Clone, serde::Serialize)]
		struct GameManifest {
			name: Box<str>,
			author: Box<str>,
			tld: Box<str>,
			first_scene: Box<str>,
			scene: Box<[ManifestScene]>,
			script: Box<[Box<str>]>,
		}

		#[derive(Debug, Clone, serde::Serialize)]
		struct ManifestScene {
			name: Box<str>,
			scripts: Box<[Box<str>]>,
		}

		#[global_allocator]
		static ALLOCATOR: $crate::allocator::TalcAllocator =
			$crate::allocator::TalcAllocator::new();

		static SCRIPTS: phf::Map<&'static str, $crate::TortuiseScript> = phf::phf_map! {
			$($script_name => $script,)*
		};

		static MANIFEST: $crate::Lazy<&'static [u8]> = $crate::Lazy::new(|| {
			fn slice_to_boxed_slice(names: &[&str]) -> Box<[Box<str>]> {
				names.iter().cloned().map(Into::into).collect()
			}

			let manifest = GameManifest {
				name: $name.into(),
				author: $author.into(),
				tld: $tld.into(),
				first_scene: $first_scene.into(),
				script: slice_to_boxed_slice([$($script_name,)*].as_slice()),
				scene: Box::from([$(ManifestScene {
					name: $scene_name.into(),
					scripts: slice_to_boxed_slice($scene.scripts),
				},)*].as_slice())
			};

			$crate::to_vec(&manifest)
		});

		#[unsafe(no_mangle)]
		extern "C" fn manifest_addr() -> *const u8 {
			MANIFEST.as_ptr()
		}

		#[unsafe(no_mangle)]
		extern "C" fn manifest_len() -> usize {
			MANIFEST.len()
		}

		#[unsafe(no_mangle)]
		extern "C" fn start_script(cx: $crate::ScriptContext) {
			let _ = $crate::log::TortuiseLogger::init();
			let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
			(script.start)(cx);
		}

		#[unsafe(no_mangle)]
		extern "C" fn update_script(cx: $crate::ScriptContext) {
			let _ = $crate::log::TortuiseLogger::init();
			let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
			(script.update)(cx);
		}

		#[unsafe(no_mangle)]
		extern "C" fn end_script(cx: $crate::ScriptContext) {
			let _ = $crate::log::TortuiseLogger::init();
			let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
			(script.end)(cx);
		}
	};
}

#[macro_export]
macro_rules! patch {
	{
		scenes: { $($scene_name: literal => $scene: expr),* },
		scripts: { $($script_name: literal => $script: expr),* }
	} => {
		use alloc::boxed::Box;

		#[derive(Debug, Clone, serde::Serialize)]
		struct GameManifestPatch {
			new_scenes: Box<[ManifestScene]>,
			new_scripts: Box<[Box<str>]>,
			patched_scenes: Box<[ScenePatch]>,
		}

		#[derive(Debug, Clone, serde::Serialize)]
		struct ManifestScene {
			name: Box<str>,
			scripts: Box<[Box<str>]>,
		}

		#[derive(Debug, Clone, serde::Serialize)]
		struct ScenePatch {
			add_scripts: Box<[Box<str>]>,
			remove_scripts: Box<[Box<str>]>,
		}

		#[global_allocator]
		static ALLOCATOR: $crate::allocator::TalcAllocator =
			$crate::allocator::TalcAllocator::new();

		static SCRIPTS: phf::Map<&'static str, $crate::TortuiseScript> = phf::phf_map! {
			$($script_name => $script,)*
		};

		static MANIFEST: $crate::Lazy<&'static [u8]> = $crate::Lazy::new(|| {
			fn slice_to_boxed_slice(names: &[&str]) -> Box<[Box<str>]> {
				names.iter().cloned().map(Into::into).collect()
			}

			let manifest = GameManifestPatch {
				added_scripts: slice_to_boxed_slice([$($script_name,)*].as_slice()),
				added_scenes: Box::from([$(ManifestScene {
					name: $scene_name.into(),
					scripts: slice_to_boxed_slice($scene.scripts),
				},)*].as_slice())
			};

			$crate::to_vec(&manifest)
		});

		#[unsafe(no_mangle)]
		extern "C" fn manifest_addr() -> *const u8 {
			MANIFEST.as_ptrhttps://botahamec.dev/cgit/shake/plain/shake()
		}

		#[unsafe(no_mangle)]
		extern "C" fn manifest_len() -> usize {
			MANIFEST.len()
		}

		#[unsafe(no_mangle)]
		extern "C" fn start_script(cx: $crate::ScriptContext) {
			let _ = $crate::log::TortuiseLogger::init();
			let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
			(script.start)(cx);
		}

		#[unsafe(no_mangle)]
		extern "C" fn update_script(cx: $crate::ScriptContext) {
			let _ = $crate::log::TortuiseLogger::init();
			let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
			(script.update)(cx);
		}

		#[unsafe(no_mangle)]
		extern "C" fn end_script(cx: $crate::ScriptContext) {
			let _ = $crate::log::TortuiseLogger::init();
			let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
			(script.end)(cx);
		}
	};
}