diff options
Diffstat (limited to 'platforms/tortuise-script-compiled/src')
| -rw-r--r-- | platforms/tortuise-script-compiled/src/buffer.rs | 296 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/entity.rs | 73 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/input.rs | 260 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/lib.rs | 119 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/process.rs | 10 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/random.rs | 70 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/scene.rs | 17 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/script.rs | 29 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/state.rs | 65 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/storage.rs | 181 | ||||
| -rw-r--r-- | platforms/tortuise-script-compiled/src/time.rs | 132 |
11 files changed, 1252 insertions, 0 deletions
diff --git a/platforms/tortuise-script-compiled/src/buffer.rs b/platforms/tortuise-script-compiled/src/buffer.rs new file mode 100644 index 0000000..c60ee40 --- /dev/null +++ b/platforms/tortuise-script-compiled/src/buffer.rs @@ -0,0 +1,296 @@ +use std::time::Duration; + +use crossterm::style::ContentStyle; +use serde::{Deserialize, Serialize}; +use tortuise::script::Operation; + +use crate::ScriptContext; + +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct Cell { + pub character: char, + pub foreground_color: Color, + pub background_color: Color, + pub underline_color: Color, + pub attributes: Attributes, + pub x: u16, + pub y: u16, + pub z: u8, +} + +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct WindowSize { + pub rows: u16, + pub columns: u16, +} + +#[derive( + Copy, Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, +)] +pub enum Color { + /// Resets the terminal color. + #[default] + Reset, + + /// Black color. + Black, + + /// Dark grey color. + DarkGrey, + + /// Light red color. + Red, + + /// Dark red color. + DarkRed, + + /// Light green color. + Green, + + /// Dark green color. + DarkGreen, + + /// Light yellow color. + Yellow, + + /// Dark yellow color. + DarkYellow, + + /// Light blue color. + Blue, + + /// Dark blue color. + DarkBlue, + + /// Light magenta color. + Magenta, + + /// Dark magenta color. + DarkMagenta, + + /// Light cyan color. + Cyan, + + /// Dark cyan color. + DarkCyan, + + /// White color. + White, + + /// Grey color. + Grey, + + /// An RGB color. See [RGB color model](https://en.wikipedia.org/wiki/RGB_color_model) for more info. + /// + /// Most UNIX terminals and Windows 10 supported only. + /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info. + Rgb { r: u8, g: u8, b: u8 }, + + /// An ANSI color. See [256 colors - cheat sheet](https://jonasjacek.github.io/colors/) for more info. + /// + /// Most UNIX terminals and Windows 10 supported only. + /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info. + AnsiValue(u8), +} + +#[derive( + Copy, Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, +)] +pub struct Attributes(u32); + +pub enum Attribute { + /// Increases the text intensity. + Bold = 1, + /// Decreases the text intensity. + Dim = 2, + /// Emphasises the text. + Italic = 4, + /// Underlines the text. + Underlined = 8, + + // Other types of underlining + /// Double underlines the text. + DoubleUnderlined = 16, + /// Undercurls the text. + Undercurled = 32, + /// Underdots the text. + Underdotted = 64, + /// Underdashes the text. + Underdashed = 128, + + /// Makes the text blinking (< 150 per minute). + SlowBlink = 256, + /// Makes the text blinking (>= 150 per minute). + RapidBlink = 512, + /// Swaps foreground and background colors. + Reverse = 1024, + /// Hides the text (also known as Conceal). + Hidden = 2048, + /// Crosses the text. + CrossedOut = 4096, + /// Sets the [Fraktur](https://en.wikipedia.org/wiki/Fraktur) typeface. + /// + /// Mostly used for [mathematical alphanumeric symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols). + Fraktur = 8192, + /// Makes the text framed. + Framed = 16384, + /// Makes the text encircled. + Encircled = 32768, + /// Draws a line at the top of the text. + OverLined = 65536, +} + +impl Attributes { + pub fn none() -> Self { + Self(0) + } + + pub fn with(self, attribute: Attribute) -> Self { + Self(self.0 | attribute as u32) + } + + pub fn without(self, attribute: Attribute) -> Self { + Self(self.0 & !(attribute as u32)) + } + + pub fn set(&mut self, attribute: Attribute) { + self.0 |= attribute as u32; + } + + pub fn unset(&mut self, attribute: Attribute) { + self.0 &= !(attribute as u32) + } + + pub fn has(self, attribute: Attribute) -> bool { + self.0 & (attribute as u32) != 0 + } + + pub fn extend(&mut self, attributes: Attributes) { + self.0 |= attributes.0 + } + + pub fn is_empty(self) -> bool { + self.0 == 0 + } +} + +impl From<Color> for crossterm::style::Color { + fn from(value: Color) -> Self { + match value { + Color::Reset => crossterm::style::Color::Reset, + Color::Black => crossterm::style::Color::Black, + Color::DarkGrey => crossterm::style::Color::DarkGrey, + Color::Red => crossterm::style::Color::Red, + Color::DarkRed => crossterm::style::Color::DarkRed, + Color::Green => crossterm::style::Color::Green, + Color::DarkGreen => crossterm::style::Color::DarkGreen, + Color::Yellow => crossterm::style::Color::Yellow, + Color::DarkYellow => crossterm::style::Color::DarkYellow, + Color::Blue => crossterm::style::Color::Blue, + Color::DarkBlue => crossterm::style::Color::DarkBlue, + Color::Magenta => crossterm::style::Color::Magenta, + Color::DarkMagenta => crossterm::style::Color::DarkMagenta, + Color::Cyan => crossterm::style::Color::Cyan, + Color::DarkCyan => crossterm::style::Color::DarkCyan, + Color::White => crossterm::style::Color::White, + Color::Grey => crossterm::style::Color::Grey, + Color::Rgb { r, g, b } => crossterm::style::Color::Rgb { r, g, b }, + Color::AnsiValue(v) => crossterm::style::Color::AnsiValue(v), + } + } +} + +impl From<Attributes> for crossterm::style::Attributes { + fn from(value: Attributes) -> Self { + let mut new = crossterm::style::Attributes::none(); + + if value.has(Attribute::Bold) { + new.set(crossterm::style::Attribute::Bold); + } + if value.has(Attribute::Dim) { + new.set(crossterm::style::Attribute::Dim); + } + if value.has(Attribute::Italic) { + new.set(crossterm::style::Attribute::Italic); + } + if value.has(Attribute::Underlined) { + new.set(crossterm::style::Attribute::Underlined); + } + if value.has(Attribute::DoubleUnderlined) { + new.set(crossterm::style::Attribute::DoubleUnderlined); + } + if value.has(Attribute::Undercurled) { + new.set(crossterm::style::Attribute::Undercurled); + } + if value.has(Attribute::Underdotted) { + new.set(crossterm::style::Attribute::Underdotted); + } + if value.has(Attribute::Underdashed) { + new.set(crossterm::style::Attribute::Underdashed); + } + if value.has(Attribute::SlowBlink) { + new.set(crossterm::style::Attribute::SlowBlink); + } + if value.has(Attribute::RapidBlink) { + new.set(crossterm::style::Attribute::RapidBlink); + } + if value.has(Attribute::Reverse) { + new.set(crossterm::style::Attribute::Reverse); + } + if value.has(Attribute::Hidden) { + new.set(crossterm::style::Attribute::Hidden); + } + if value.has(Attribute::CrossedOut) { + new.set(crossterm::style::Attribute::CrossedOut); + } + if value.has(Attribute::Fraktur) { + new.set(crossterm::style::Attribute::Fraktur); + } + if value.has(Attribute::Framed) { + new.set(crossterm::style::Attribute::Framed); + } + if value.has(Attribute::Encircled) { + new.set(crossterm::style::Attribute::Encircled); + } + if value.has(Attribute::OverLined) { + new.set(crossterm::style::Attribute::OverLined); + } + + new + } +} + +impl ScriptContext<'_> { + pub fn add_cell(&mut self, cell: Cell) { + puffin::profile_function!(); + self.0 + .operation_queue + .push(Operation::AddCell(tortuise::buffer::Cell { + character: cell.character, + style: ContentStyle { + foreground_color: Some(cell.foreground_color.into()), + background_color: Some(cell.background_color.into()), + underline_color: Some(cell.underline_color.into()), + attributes: cell.attributes.into(), + }, + x: cell.x, + y: cell.y, + z: cell.z, + })); + } + + pub fn window_size(&self) -> WindowSize { + puffin::profile_function!(); + let size = &self.0.buffer.window_size; + WindowSize { + rows: size.rows, + columns: size.columns, + } + } + + pub fn set_framerate_limit(&mut self, limit: Option<u32>) { + puffin::profile_function!(); + let limit = limit.map(|limit| Duration::from_nanos(1_000_000_000 / limit as u64)); + self.0.operation_queue.push(Operation::SetFrameLimit(limit)); + } +} diff --git a/platforms/tortuise-script-compiled/src/entity.rs b/platforms/tortuise-script-compiled/src/entity.rs new file mode 100644 index 0000000..507690d --- /dev/null +++ b/platforms/tortuise-script-compiled/src/entity.rs @@ -0,0 +1,73 @@ +use core::ops::{Deref, DerefMut}; + +use spin::lock_api::{MappedRwLockWriteGuard, RwLock, RwLockWriteGuard}; + +use crate::ScriptContext; + +pub struct Entity<T> { + data: RwLock<Option<T>>, + default: fn(&ScriptContext) -> T, +} + +pub struct EntityRef<'entity, T> { + data: MappedRwLockWriteGuard<'entity, T>, +} + +pub struct EntityMut<'entity, T> { + data: MappedRwLockWriteGuard<'entity, T>, +} + +impl<T> Entity<T> { + pub const fn new(_name: &'static str, default: fn(&ScriptContext) -> T) -> Self { + Self { + data: RwLock::new(None), + default, + } + } + + pub const fn from_string(name: String, default: fn(&ScriptContext) -> T) -> Self { + core::mem::forget(name); + Self { + data: RwLock::new(None), + default, + } + } + + pub fn get(&self, context: &ScriptContext) -> EntityRef<'_, T> { + puffin::profile_function!(); + let data = RwLockWriteGuard::map(self.data.write(), |data| { + data.get_or_insert_with(|| (self.default)(context)) + }); + EntityRef { data } + } + + pub fn get_mut(&self, context: &ScriptContext) -> EntityMut<'_, T> { + puffin::profile_function!(); + let data = RwLockWriteGuard::map(self.data.write(), |data| { + data.get_or_insert_with(|| (self.default)(context)) + }); + EntityMut { data } + } +} + +impl<T> Deref for EntityRef<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<T> Deref for EntityMut<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<T> DerefMut for EntityMut<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} diff --git a/platforms/tortuise-script-compiled/src/input.rs b/platforms/tortuise-script-compiled/src/input.rs new file mode 100644 index 0000000..a7d7340 --- /dev/null +++ b/platforms/tortuise-script-compiled/src/input.rs @@ -0,0 +1,260 @@ +use serde::{Deserialize, Serialize}; + +use crate::ScriptContext; + +/// Represents a key. +#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)] +pub enum KeyCode { + /// Backspace key (Delete on macOS, Backspace on other platforms). + Backspace, + /// Enter key. + Enter, + /// Left arrow key. + Left, + /// Right arrow key. + Right, + /// Up arrow key. + Up, + /// Down arrow key. + Down, + /// Home key. + Home, + /// End key. + End, + /// Page up key. + PageUp, + /// Page down key. + PageDown, + /// Tab key. + Tab, + /// Shift + Tab key. + BackTab, + /// Delete key. (Fn+Delete on macOS, Delete on other platforms) + Delete, + /// Insert key. + Insert, + /// F key. + /// + /// `KeyCode::F(1)` represents F1 key, etc. + F(u8), + /// A character. + /// + /// `KeyCode::Char('c')` represents `c` character, etc. + Char(char), + /// Null. + Null, + /// Escape key. + Esc, +} + +pub type Position = tortuise::input::Position; +pub type Direction = tortuise::input::Direction; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Button { + // Action Pad + South = 1, + East = 2, + North = 4, + West = 5, + C = 3, + Z = 6, + // Triggers + LeftTrigger = 7, + LeftTrigger2 = 9, + RightTrigger = 8, + RightTrigger2 = 10, + // Menu Pad + Select = 11, + Start = 12, + Mode = 13, + // Sticks + LeftThumb = 14, + RightThumb = 15, + // D-Pad + DPadUp = 16, + DPadDown = 17, + DPadLeft = 18, + DPadRight = 19, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Axis { + LeftStickX = 1, + LeftStickY = 2, + LeftZ = 3, + RightStickX = 4, + RightStickY = 5, + RightZ = 6, + DPadX = 7, + DPadY = 8, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum PowerInfo { + Wired, + Discharging(u8), + Charging(u8), + Charged, + Unknown, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Gamepad(usize); + +impl From<Button> for gilrs::Button { + fn from(value: Button) -> Self { + match value { + Button::South => gilrs::Button::South, + Button::East => gilrs::Button::East, + Button::North => gilrs::Button::North, + Button::West => gilrs::Button::West, + Button::C => gilrs::Button::C, + Button::Z => gilrs::Button::Z, + Button::LeftTrigger => gilrs::Button::LeftTrigger, + Button::LeftTrigger2 => gilrs::Button::LeftTrigger2, + Button::RightTrigger => gilrs::Button::RightTrigger, + Button::RightTrigger2 => gilrs::Button::RightTrigger2, + Button::Select => gilrs::Button::Select, + Button::Start => gilrs::Button::Start, + Button::Mode => gilrs::Button::Mode, + Button::LeftThumb => gilrs::Button::LeftThumb, + Button::RightThumb => gilrs::Button::RightThumb, + Button::DPadUp => gilrs::Button::DPadUp, + Button::DPadDown => gilrs::Button::DPadDown, + Button::DPadLeft => gilrs::Button::DPadLeft, + Button::DPadRight => gilrs::Button::DPadRight, + } + } +} + +impl From<Axis> for gilrs::Axis { + fn from(value: Axis) -> Self { + match value { + Axis::LeftStickX => gilrs::Axis::LeftStickX, + Axis::LeftStickY => gilrs::Axis::LeftStickY, + Axis::LeftZ => gilrs::Axis::LeftZ, + Axis::RightStickX => gilrs::Axis::RightStickX, + Axis::RightStickY => gilrs::Axis::RightStickY, + Axis::RightZ => gilrs::Axis::RightZ, + Axis::DPadX => gilrs::Axis::DPadX, + Axis::DPadY => gilrs::Axis::DPadY, + } + } +} + +impl From<Option<gilrs::PowerInfo>> for PowerInfo { + fn from(value: Option<gilrs::PowerInfo>) -> Self { + match value { + Some(gilrs::PowerInfo::Wired) => PowerInfo::Wired, + Some(gilrs::PowerInfo::Discharging(p)) => PowerInfo::Discharging(p), + Some(gilrs::PowerInfo::Charging(p)) => PowerInfo::Charging(p), + Some(gilrs::PowerInfo::Charged) => PowerInfo::Charged, + Some(gilrs::PowerInfo::Unknown) => PowerInfo::Unknown, + None => PowerInfo::Unknown, + } + } +} + +impl From<KeyCode> for crossterm::event::KeyCode { + fn from(value: KeyCode) -> Self { + match value { + KeyCode::Backspace => crossterm::event::KeyCode::Backspace, + KeyCode::Enter => crossterm::event::KeyCode::Enter, + KeyCode::Left => crossterm::event::KeyCode::Left, + KeyCode::Right => crossterm::event::KeyCode::Right, + KeyCode::Up => crossterm::event::KeyCode::Up, + KeyCode::Down => crossterm::event::KeyCode::Down, + KeyCode::Home => crossterm::event::KeyCode::Home, + KeyCode::End => crossterm::event::KeyCode::End, + KeyCode::PageUp => crossterm::event::KeyCode::PageUp, + KeyCode::PageDown => crossterm::event::KeyCode::PageDown, + KeyCode::Tab => crossterm::event::KeyCode::Tab, + KeyCode::BackTab => crossterm::event::KeyCode::BackTab, + KeyCode::Delete => crossterm::event::KeyCode::Delete, + KeyCode::Insert => crossterm::event::KeyCode::Insert, + KeyCode::F(f) => crossterm::event::KeyCode::F(f), + KeyCode::Char(c) => crossterm::event::KeyCode::Char(c), + KeyCode::Null => crossterm::event::KeyCode::Null, + KeyCode::Esc => crossterm::event::KeyCode::Esc, + } + } +} + +impl Gamepad { + pub fn is_button_pressed_this_frame(&self, context: &ScriptContext, button: Button) -> bool { + context + .0 + .input + .is_button_pressed_this_frame(self.0, button.into()) + } + + pub fn is_button_released_this_frame(&self, context: &ScriptContext, button: Button) -> bool { + context + .0 + .input + .is_button_released_this_frame(self.0, button.into()) + } + + pub fn is_button_down(&self, context: &ScriptContext, button: Button) -> bool { + context.0.input.is_button_down(self.0, button.into()) + } + + pub fn axis_value(&self, context: &ScriptContext, axis: Axis) -> f32 { + context + .0 + .input + .axis_value(self.0, axis.into()) + .unwrap_or_default() + } + + pub fn is_connected(&self, context: &ScriptContext) -> bool { + context.0.input.is_gamepad_connected(self.0) + } + + pub fn power_info(&self, context: &ScriptContext) -> PowerInfo { + context.0.input.gamepad_power_info(self.0).into() + } +} + +impl ScriptContext<'_> { + pub fn is_key_pressed_this_frame(&self, key_code: KeyCode) -> bool { + puffin::profile_function!(); + self.0.input.is_key_pressed_this_frame(key_code.into()) + } + + pub fn is_mouse_clicked_this_frame(&self) -> bool { + puffin::profile_function!(); + self.0.input.is_mouse_clicked_this_frame() + } + + pub fn mouse_location(&self) -> Option<Position> { + puffin::profile_function!(); + self.0.input.mouse_location() + } + + pub fn scroll_direction_this_frame(&self) -> Option<Direction> { + puffin::profile_function!(); + self.0.input.scroll_direction_this_frame() + } + + pub fn is_gamepad_connected_this_frame(&self) -> Option<Gamepad> { + puffin::profile_function!(); + self.0.input.is_gamepad_connected_this_frame().map(Gamepad) + } + + pub fn is_focused(&self) -> bool { + puffin::profile_function!(); + self.0.input.is_focused() + } + + pub fn was_focused_this_frame(&self) -> bool { + puffin::profile_function!(); + self.0.input.was_focused_this_frame() + } + + pub fn was_unfocused_this_frame(&self) -> bool { + puffin::profile_function!(); + self.0.input.was_unfocused_this_frame() + } +} diff --git a/platforms/tortuise-script-compiled/src/lib.rs b/platforms/tortuise-script-compiled/src/lib.rs new file mode 100644 index 0000000..47bc926 --- /dev/null +++ b/platforms/tortuise-script-compiled/src/lib.rs @@ -0,0 +1,119 @@ +use std::sync::Arc; + +use tortuise::{file::FileManager, script::WasmRunner}; + +pub mod buffer; +pub mod entity; +pub mod input; +pub mod process; +pub mod random; +pub mod scene; +pub mod script; +pub mod state; +pub mod storage; +pub mod time; + +pub struct ScriptContext<'a>(tortuise::script::FullScriptContext<'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 run_game( + name: &str, + author: &str, + tld: &str, + first_scene: &str, + scenes: &[(&str, TortuiseScene)], + scripts: &[(&str, TortuiseScript)], +) { + use tortuise::manifest::HumaneGameManifest; + + fn wrap_script_fn(f: fn(ScriptContext)) -> tortuise::script::StaticScriptFunction { + Some(Arc::new(move |c| f(ScriptContext(c)))) + } + + fn slice_to_boxed_slice(names: &[&str]) -> Box<[Box<str>]> { + names.iter().cloned().map(Into::into).collect() + } + + let manifest = HumaneGameManifest { + name: Box::from(name), + author: Box::from(author), + tld: Box::from(tld), + first_scene: Box::from(first_scene), + scene: scenes + .iter() + .map(|(name, scene)| tortuise::manifest::PartialScene { + name: (*name).into(), + scripts: slice_to_boxed_slice(scene.scripts), + }) + .collect(), + script: scripts + .iter() + .map( + |(name, script)| tortuise::script::ScriptDeclaration::Static { + name: (*name).into(), + start: wrap_script_fn(script.start), + update: wrap_script_fn(script.update), + end: wrap_script_fn(script.end), + }, + ) + .collect(), + }; + + let mut files = FileManager::new(); + let wasm_runner = WasmRunner::new(); + let mut manifest = manifest.into(); + tortuise::init_logger(&manifest); + if let Err(e) = tortuise::apply_patches(&mut manifest, &mut files, &wasm_runner) { + log::error!("{e}"); + } + tortuise::run_game(files, wasm_runner, manifest) +} + +#[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),* } + } => { + fn main() { + $crate::run_game($name, $author, $tld, $first_scene, &[$(($scene_name, $scene),)*], &[$(($script_name, $script),)*]); + } + }; +} diff --git a/platforms/tortuise-script-compiled/src/process.rs b/platforms/tortuise-script-compiled/src/process.rs new file mode 100644 index 0000000..1ee39f2 --- /dev/null +++ b/platforms/tortuise-script-compiled/src/process.rs @@ -0,0 +1,10 @@ +use tortuise::script::Operation; + +use crate::ScriptContext; + +impl ScriptContext<'_> { + pub fn exit(&mut self) { + puffin::profile_function!(); + self.0.operation_queue.push(Operation::Exit) + } +} diff --git a/platforms/tortuise-script-compiled/src/random.rs b/platforms/tortuise-script-compiled/src/random.rs new file mode 100644 index 0000000..148f152 --- /dev/null +++ b/platforms/tortuise-script-compiled/src/random.rs @@ -0,0 +1,70 @@ +use rand::{ + Rng, SeedableRng, TryRng, + rngs::{SmallRng, StdRng}, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TortuiseRng(SmallRng); + +impl TortuiseRng { + pub fn new() -> Self { + puffin::profile_function!(); + Self::seed_from_u64(rand::make_rng::<StdRng>().next_u64()) + } +} + +impl Default for TortuiseRng { + fn default() -> Self { + Self::new() + } +} + +impl SeedableRng for TortuiseRng { + type Seed = <SmallRng as SeedableRng>::Seed; + + fn from_seed(seed: Self::Seed) -> Self { + Self(SmallRng::from_seed(seed)) + } + + fn seed_from_u64(state: u64) -> Self { + Self(SmallRng::seed_from_u64(state)) + } + + fn from_rng<R: Rng + ?Sized>(rng: &mut R) -> Self { + Self(SmallRng::from_rng(rng)) + } + + fn try_from_rng<R: TryRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> { + Ok(Self(SmallRng::try_from_rng(rng)?)) + } + + fn fork(&mut self) -> Self + where + Self: Rng, + { + Self(self.0.fork()) + } + + fn try_fork(&mut self) -> Result<Self, <Self as TryRng>::Error> + where + Self: TryRng, + { + Ok(Self(self.0.try_fork()?)) + } +} + +impl TryRng for TortuiseRng { + type Error = <SmallRng as TryRng>::Error; + + fn try_next_u32(&mut self) -> Result<u32, Self::Error> { + self.0.try_next_u32() + } + + fn try_next_u64(&mut self) -> Result<u64, Self::Error> { + self.0.try_next_u64() + } + + fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { + self.0.try_fill_bytes(dst) + } +} diff --git a/platforms/tortuise-script-compiled/src/scene.rs b/platforms/tortuise-script-compiled/src/scene.rs new file mode 100644 index 0000000..fc83d6b --- /dev/null +++ b/platforms/tortuise-script-compiled/src/scene.rs @@ -0,0 +1,17 @@ +use tortuise::script::Operation; + +use crate::ScriptContext; + +impl ScriptContext<'_> { + pub fn scene_id(&self, name: &str) -> Option<usize> { + puffin::profile_function!(); + tortuise::scene::scene_id_by_name(&self.0.manifest.scenes, name) + } + + pub fn switch_scene(&mut self, name: &str) -> Option<()> { + puffin::profile_function!(); + let id = self.scene_id(name)?; + self.0.operation_queue.push(Operation::SwitchScene(id)); + Some(()) + } +} diff --git a/platforms/tortuise-script-compiled/src/script.rs b/platforms/tortuise-script-compiled/src/script.rs new file mode 100644 index 0000000..d316fe5 --- /dev/null +++ b/platforms/tortuise-script-compiled/src/script.rs @@ -0,0 +1,29 @@ +use tortuise::script::Operation; + +use crate::ScriptContext; + +impl ScriptContext<'_> { + pub fn current_script_name(&self) -> String { + puffin::profile_function!(); + self.0.manifest.scripts[self.0.script_id].name().to_owned() + } + + pub fn script_id(&self, name: &str) -> Option<usize> { + puffin::profile_function!(); + self.0.scripts.script_id_by_name(name) + } + + pub fn activate_script(&mut self, name: &str) -> Option<()> { + puffin::profile_function!(); + let id = self.script_id(name)?; + self.0.operation_queue.push(Operation::ActivateScript(id)); + Some(()) + } + + pub fn deactivate_script(&mut self, name: &str) -> Option<()> { + puffin::profile_function!(); + let id = self.script_id(name)?; + self.0.operation_queue.push(Operation::DeactivateScript(id)); + Some(()) + } +} diff --git a/platforms/tortuise-script-compiled/src/state.rs b/platforms/tortuise-script-compiled/src/state.rs new file mode 100644 index 0000000..896b836 --- /dev/null +++ b/platforms/tortuise-script-compiled/src/state.rs @@ -0,0 +1,65 @@ +use core::ops::{Deref, DerefMut}; + +use spin::lock_api::{MappedMutexGuard, Mutex, MutexGuard}; + +use crate::ScriptContext; + +pub struct ScriptState<T> { + data: Mutex<Option<T>>, + default: fn(&ScriptContext) -> T, +} + +pub struct StateRef<'state, T> { + data: MappedMutexGuard<'state, T>, +} + +pub struct StateMut<'state, T> { + data: MappedMutexGuard<'state, T>, +} + +impl<T> ScriptState<T> { + pub const fn new(default: fn(&ScriptContext) -> T) -> Self { + Self { + data: Mutex::new(None), + default, + } + } + + pub fn get(&self, context: &ScriptContext) -> StateRef<'_, T> { + puffin::profile_function!(); + let data = MutexGuard::map(self.data.lock(), |data| { + data.get_or_insert_with(|| (self.default)(context)) + }); + StateRef { data } + } + + pub fn get_mut(&self, context: &ScriptContext) -> StateMut<'_, T> { + puffin::profile_function!(); + let data = MutexGuard::map(self.data.lock(), |data| { + data.get_or_insert_with(|| (self.default)(context)) + }); + StateMut { data } + } +} + +impl<T> Deref for StateRef<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<T> Deref for StateMut<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<T> DerefMut for StateMut<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} diff --git a/platforms/tortuise-script-compiled/src/storage.rs b/platforms/tortuise-script-compiled/src/storage.rs new file mode 100644 index 0000000..d028a91 --- /dev/null +++ b/platforms/tortuise-script-compiled/src/storage.rs @@ -0,0 +1,181 @@ +use core::ops::{Deref, DerefMut}; + +use serde::{Serialize, de::DeserializeOwned}; +use spin::Mutex; +use tortuise::script::Operation; + +use crate::ScriptContext; + +pub struct Storage<T> { + initial_state: fn() -> T, + data: Mutex<Option<T>>, +} + +pub struct Config<T> { + initial_state: fn() -> T, + data: Mutex<Option<T>>, +} + +pub struct StorageMut<'a, 'cx, T: Serialize + Eq> { + context: &'cx mut ScriptContext<'a>, + data: T, + original: T, +} + +pub struct ConfigMut<'a, 'cx, T: Serialize + Eq> { + context: &'cx mut ScriptContext<'a>, + data: T, + original: T, +} + +impl<T: Serialize + DeserializeOwned + Clone + Eq> Storage<T> { + pub const fn new(initial_state: fn() -> T) -> Self { + Self { + initial_state, + data: Mutex::new(None), + } + } + + pub fn load(&self, context: &ScriptContext) -> T { + puffin::profile_function!(); + let mut data = self.data.lock(); + data.get_or_insert_with(|| { + context + .0 + .storage + .data() + .map(messagepack_serde::from_slice) + .and_then(|r| r.ok()) + .unwrap_or_else(self.initial_state) + }) + .clone() + } + + pub fn load_mut<'a, 'cx>(&self, context: &'cx mut ScriptContext<'a>) -> StorageMut<'a, 'cx, T> { + let data = self.load(context); + StorageMut { + original: data.clone(), + data, + context, + } + } + + pub fn is_saving(&self, context: &ScriptContext) -> bool { + context.0.storage.is_saving_data() + } + + pub fn save_failed(&self, context: &ScriptContext) -> bool { + context.0.storage.saving_data_failed() + } +} + +impl<T: Serialize + DeserializeOwned + Clone + Eq> Config<T> { + pub const fn new(initial_state: fn() -> T) -> Self { + Self { + initial_state, + data: Mutex::new(None), + } + } + + pub fn load(&self, context: &ScriptContext) -> T { + puffin::profile_function!(); + let mut data = self.data.lock(); + data.get_or_insert_with(|| { + context + .0 + .storage + .config() + .map(toml::from_slice) + .and_then(|r| r.ok()) + .unwrap_or_else(self.initial_state) + }) + .clone() + } + + pub fn load_mut<'a, 'cx>(&self, context: &'cx mut ScriptContext<'a>) -> StorageMut<'a, 'cx, T> { + let data = self.load(context); + StorageMut { + original: data.clone(), + data, + context, + } + } + + pub fn is_saving(&self, context: &ScriptContext) -> bool { + context.0.storage.is_saving_config() + } + + pub fn save_failed(&self, context: &ScriptContext) -> bool { + context.0.storage.saving_config_failed() + } +} + +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) { + puffin::profile_function!(); + if self.data == self.original { + return; + } + + let new_save = match messagepack_serde::to_vec(&self.data) { + Ok(new_save) => new_save.into_boxed_slice(), + Err(e) => { + log::error!("{e}"); + return; + } + }; + self.context + .0 + .operation_queue + .push(Operation::UpdateStorage { new_save }); + } +} + +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) { + puffin::profile_function!(); + if self.data == self.original { + return; + } + + let new_save = match toml::to_string(&self.data) { + Ok(new_save) => new_save.as_bytes().into(), + Err(e) => { + log::error!("{e}"); + return; + } + }; + self.context + .0 + .operation_queue + .push(Operation::UpdateConfig { new_save }); + } +} diff --git a/platforms/tortuise-script-compiled/src/time.rs b/platforms/tortuise-script-compiled/src/time.rs new file mode 100644 index 0000000..72a2f68 --- /dev/null +++ b/platforms/tortuise-script-compiled/src/time.rs @@ -0,0 +1,132 @@ +use core::{ + ops::{Add, AddAssign, Sub, SubAssign}, + time::Duration, +}; + +use serde::{Deserialize, Serialize}; + +use crate::ScriptContext; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct SystemTime(std::time::SystemTime); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct Instant(u128); + +impl ScriptContext<'_> { + pub fn delta_time(&self) -> Duration { + puffin::profile_function!(); + Duration::from_nanos_u128(self.0.time.delta_time().as_nanos()) + } +} + +impl SystemTime { + pub const UNIX_EPOCH: Self = Self(std::time::SystemTime::UNIX_EPOCH); + + pub fn now(context: &ScriptContext) -> Self { + puffin::profile_function!(); + Self(context.0.time.system_now()) + } + + pub fn duration_since(self, earlier: SystemTime) -> Option<Duration> { + puffin::profile_function!(); + self.0.duration_since(earlier.0).ok() + } + + pub fn checked_add(self, duration: Duration) -> Option<Self> { + Some(Self(self.0.checked_add(duration)?)) + } + + pub fn checked_sub(self, duration: Duration) -> Option<Self> { + Some(Self(self.0.checked_sub(duration)?)) + } +} + +impl Add<Duration> for SystemTime { + type Output = Self; + + fn add(self, rhs: Duration) -> Self::Output { + self.checked_add(rhs).unwrap() + } +} + +impl AddAssign<Duration> for SystemTime { + fn add_assign(&mut self, rhs: Duration) { + *self = self.add(rhs); + } +} + +impl Sub<Duration> for SystemTime { + type Output = Self; + + fn sub(self, rhs: Duration) -> Self::Output { + self.checked_sub(rhs).unwrap() + } +} + +impl SubAssign<Duration> for SystemTime { + fn sub_assign(&mut self, rhs: Duration) { + *self = self.sub(rhs) + } +} + +impl Instant { + pub fn now(context: &ScriptContext) -> Self { + puffin::profile_function!(); + Self(context.0.time.monotonic_now().as_nanos()) + } + + pub fn duration_since(self, earlier: Instant) -> Option<Duration> { + if earlier > self { + return None; + } + + Some(Duration::from_nanos_u128(self.0 - earlier.0)) + } + + pub fn elapsed(self, context: &ScriptContext) -> Duration { + Self::now(context) + .duration_since(self) + .unwrap_or(Duration::ZERO) + } + + pub fn checked_add(self, duration: Duration) -> Option<Self> { + Some(Self(self.0.checked_add(duration.as_nanos())?)) + } + + pub fn checked_sub(self, duration: Duration) -> Option<Self> { + Some(Self(self.0.checked_sub(duration.as_nanos())?)) + } +} + +impl Add<Duration> for Instant { + type Output = Self; + + fn add(self, rhs: Duration) -> Self::Output { + self.checked_add(rhs).unwrap() + } +} + +impl AddAssign<Duration> for Instant { + fn add_assign(&mut self, rhs: Duration) { + *self = self.add(rhs); + } +} + +impl Sub<Duration> for Instant { + type Output = Self; + + fn sub(self, rhs: Duration) -> Self::Output { + self.checked_sub(rhs).unwrap() + } +} + +impl SubAssign<Duration> for Instant { + fn sub_assign(&mut self, rhs: Duration) { + *self = self.sub(rhs) + } +} |
