Skip to main content

dotzuki_engine_script/
lib.rs

1//! dotzuki-engine-script — an async JavaScript scripting engine (Boa-based) for games.
2//!
3//! Provides an async/await-based scripting system using Boa (a pure-Rust JS engine)
4//! that replaces the hardcoded ScriptAction queue. Map scripts are written in
5//! JavaScript and can `await` game operations like showText(), moveNpc(), etc.
6//!
7//! # Architecture
8//!
9//! ```text
10//! JS Script (async fn)
11//!     │
12//!     ├─ await game.showText("...")  ──► ScriptCommand::ShowText
13//!     │       ↑ Rust resolves promise when text dismissed
14//!     │
15//!     ├─ game.getFlag("GOT_STARTER") ──► synchronous bool return
16//!     │
17//!     └─ await game.startBattle("RIVAL") ──► ScriptCommand::StartBattle
18//!             ↑ Rust resolves promise with battle result
19//! ```
20//!
21//! The game loop calls `ScriptEngine::tick()` each frame:
22//! 1. If a pending command was resolved by Rust, `run_jobs()` drains the JS
23//!    microtask queue so the async function continues to its next `await`.
24//! 2. If the script issues a new command, it's returned to the caller for dispatch.
25//! 3. If no script is active, returns `None`.
26
27// no_std port (GBA / thumbv4t): without the `script-boa` feature this crate
28// provides only the boa-free runtime protocol (`ScriptCommand`,
29// `CommandResult`, `MapScriptConfig`, `CutsceneManager`), which is what
30// bare-metal downstreams consume. See the cfg gates below for what drops out.
31#![cfg_attr(target_os = "none", no_std)]
32#![cfg_attr(target_os = "none", feature(prelude_import))]
33#![cfg_attr(target_os = "none", allow(internal_features))]
34
35extern crate alloc;
36
37#[allow(unused_imports)]
38mod alloc_prelude {
39    pub use core::prelude::v1::*;
40    pub use core::convert::{TryFrom, TryInto};
41    pub use alloc::borrow::ToOwned;
42    pub use core::iter::FromIterator;
43    pub use alloc::boxed::Box;
44    pub use alloc::format;
45    pub use alloc::string::{String, ToString};
46    pub use alloc::vec;
47    pub use alloc::vec::Vec;
48    pub use core::{assert_eq, assert_ne, matches, todo, unimplemented, write, writeln};
49    pub use core::debug_assert;
50}
51
52#[cfg_attr(target_os = "none", prelude_import)]
53#[allow(unused_imports)]
54use alloc_prelude::*;
55
56pub mod command;
57pub mod config;
58pub mod cutscene;
59
60// Boa-backed runtime (hosted JS engine) — excluded from bare-metal builds.
61#[cfg(feature = "script-boa")]
62pub mod api_registrar;
63#[cfg(feature = "script-boa")]
64pub mod engine;
65#[cfg(feature = "script-boa")]
66pub mod game_api;
67// Filesystem-backed script/config loading — hosted only.
68#[cfg(not(target_os = "none"))]
69pub mod loader;
70
71// Bare-metal stub with the same surface: no filesystem, no JS engine. The
72// overworld's native AST interpreter path (embedded scene tables) never
73// reads through this loader; the shim exists so downstream crates compile
74// unchanged on target_os = "none".
75#[cfg(target_os = "none")]
76pub mod loader {
77    use crate::MapScriptConfig;
78
79    #[derive(Debug, Clone, Default)]
80    pub struct ScriptLoader;
81
82    #[derive(Debug)]
83    pub struct ScriptLoaderError;
84
85    impl ScriptLoader {
86        pub fn new() -> Self {
87            Self
88        }
89        pub fn register_script(&mut self, _map_id: &str, _source: &str) {}
90        pub fn register_config(&mut self, _map_id: &str, _config: MapScriptConfig) {}
91        pub fn register_config_json(
92            &mut self,
93            _map_id: &str,
94            _json: &str,
95        ) -> Result<(), String> {
96            Ok(())
97        }
98        pub fn get_script(&self, _map_id: &str) -> Option<&str> {
99            None
100        }
101        pub fn get_config(&self, _map_id: &str) -> Option<&MapScriptConfig> {
102            None
103        }
104        pub fn has_script(&self, _map_id: &str) -> bool {
105            false
106        }
107        pub fn has_config(&self, _map_id: &str) -> bool {
108            false
109        }
110        pub fn loaded_maps(&self) -> Vec<&str> {
111            Vec::new()
112        }
113        pub fn load_auto<T>(&mut self, _dirs: Option<T>) -> Result<usize, ScriptLoaderError> {
114            Ok(0)
115        }
116    }
117}
118
119#[cfg(feature = "embedded-scripts")]
120mod embedded_scripts {
121    include!(concat!(env!("OUT_DIR"), "/embedded_scripts.rs"));
122}
123
124#[cfg(all(test, feature = "script-boa"))]
125mod tests;
126
127pub use command::{CommandResult, ScriptCommand};
128pub use config::MapScriptConfig;
129pub use cutscene::CutsceneManager;
130#[cfg(feature = "script-boa")]
131pub use api_registrar::ScriptApiRegistrar;
132#[cfg(feature = "script-boa")]
133pub use engine::{BridgeView, ScriptEngine, ScriptEngineError};
134#[cfg(not(target_os = "none"))]
135pub use loader::{ScriptLoader, ScriptLoaderError};
136#[cfg(target_os = "none")]
137pub use loader::{ScriptLoader, ScriptLoaderError};