#![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(val: &T) -> &'static [u8] { Vec::leak(messagepack_serde::to_vec(val).unwrap()) } #[doc(hidden)] pub type Lazy = spin::LazyLock; #[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, author: Box, tld: Box, first_scene: Box, scene: Box<[ManifestScene]>, script: Box<[Box]>, } #[derive(Debug, Clone, serde::Serialize)] struct ManifestScene { name: Box, scripts: Box<[Box]>, } #[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]> { 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]>, patched_scenes: Box<[ScenePatch]>, } #[derive(Debug, Clone, serde::Serialize)] struct ManifestScene { name: Box, scripts: Box<[Box]>, } #[derive(Debug, Clone, serde::Serialize)] struct ScenePatch { add_scripts: Box<[Box]>, remove_scripts: Box<[Box]>, } #[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]> { 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); } }; }