diff --git a/Cargo.toml b/Cargo.toml index fd1660edea..23c4ee9824 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ path = "src/lib.rs" features = ["lua54"] [features] -default = ["core_functions", "bevy_bindings", "unsafe_lua_modules"] +default = ["core_functions", "bevy_bindings"] ## lua lua = ["bevy_mod_scripting_lua"] @@ -39,12 +39,12 @@ bevy_bindings = ["bevy_mod_scripting_functions/bevy_bindings"] # optional unsafe_lua_modules = ["bevy_mod_scripting_lua?/unsafe_lua_modules"] -mlua_serialize = ["bevy_mod_scripting_lua/mlua_serialize"] -mlua_macros = ["bevy_mod_scripting_lua/mlua_macros"] -mlua_async = ["bevy_mod_scripting_lua/mlua_async"] +mlua_serialize = ["bevy_mod_scripting_lua?/mlua_serialize"] +mlua_macros = ["bevy_mod_scripting_lua?/mlua_macros"] +mlua_async = ["bevy_mod_scripting_lua?/mlua_async"] ## rhai -# rhai = ["bevy_mod_scripting_rhai"] +rhai = ["bevy_mod_scripting_rhai"] ## rune # rune = ["bevy_mod_scripting_rune"] @@ -53,7 +53,7 @@ mlua_async = ["bevy_mod_scripting_lua/mlua_async"] bevy = { workspace = true } bevy_mod_scripting_core = { workspace = true } bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", version = "0.9.0-alpha.7", optional = true } -# bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.9.0-alpha.2", optional = true } +bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.9.0-alpha.7", optional = true } # bevy_mod_scripting_rune = { path = "crates/languages/bevy_mod_scripting_rune", version = "0.9.0-alpha.2", optional = true } bevy_mod_scripting_functions = { workspace = true } @@ -61,8 +61,6 @@ bevy_mod_scripting_functions = { workspace = true } bevy = { version = "0.15.0", default-features = false } bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.9.0-alpha.7" } bevy_mod_scripting_functions = { path = "crates/bevy_mod_scripting_functions", version = "0.9.0-alpha.7", default-features = false } -mlua = { version = "0.10" } -# rhai = { version = "1.20.1" } # test utilities script_integration_test_harness = { path = "crates/script_integration_test_harness" } @@ -80,7 +78,7 @@ ansi-parser = "0.9" members = [ "crates/bevy_mod_scripting_core", "crates/languages/bevy_mod_scripting_lua", - # "crates/languages/bevy_mod_scripting_rhai", + "crates/languages/bevy_mod_scripting_rhai", # "crates/languages/bevy_mod_scripting_rune", "crates/test_utils", "crates/bevy_mod_scripting_functions", @@ -116,7 +114,12 @@ debug = true [[example]] name = "game_of_life" path = "examples/game_of_life.rs" -required-features = ["lua54", "bevy/file_watcher", "bevy/multi_threaded"] +required-features = [ + "lua54", + "rhai", + "bevy/file_watcher", + "bevy/multi_threaded", +] [workspace.lints.clippy] panic = "deny" diff --git a/assets/scripts/game_of_life.lua b/assets/scripts/game_of_life.lua index 8b4e59a826..85d6aa254e 100644 --- a/assets/scripts/game_of_life.lua +++ b/assets/scripts/game_of_life.lua @@ -1,7 +1,7 @@ LifeState = world.get_type_by_name("LifeState") Settings = world.get_type_by_name("Settings") -world.info("Lua: The game_of_life.lua script just got loaded") +info("Lua: The game_of_life.lua script just got loaded") math.randomseed(os.time()) @@ -12,8 +12,8 @@ function fetch_life_state() end function on_script_loaded() - world.info("Lua: Hello! I am initiating the game of life simulation state with randomness!") - world.info("Lua: Click on the screen to set cells alive after running the `gol start` command") + info("Lua: Hello! I am initiating the game of life simulation state with randomness!") + info("Lua: Click on the screen to set cells alive after running the `gol start` command") local life_state = fetch_life_state() local cells = life_state.cells @@ -27,8 +27,7 @@ end function on_click(x,y) -- get the settings - world.info("Lua: Clicked at x: " .. x .. " y: " .. y) - print(entity) + info("Lua: Clicked at x: " .. x .. " y: " .. y) local life_state = fetch_life_state() local cells = life_state.cells @@ -110,7 +109,7 @@ function on_update() end function on_script_unloaded() - world.info("Lua: I am being unloaded, goodbye!") + info("Lua: I am being unloaded, goodbye!") -- set state to 0's local life_state = fetch_life_state() diff --git a/assets/scripts/game_of_life.rhai b/assets/scripts/game_of_life.rhai index b388f01fa6..0b99e385c7 100644 --- a/assets/scripts/game_of_life.rhai +++ b/assets/scripts/game_of_life.rhai @@ -1,29 +1,84 @@ +info.call("Rhai: the game_of_life.rhai script just got loaded"); + + +fn fetch_life_state() { + let LifeState = world.get_type_by_name.call("LifeState"); + let Settings = world.get_type_by_name.call("Settings"); + for (v,i) in world.query.call().component.call(LifeState).build.call(){ + return v.components.call()[0] + } +} + + fn on_script_loaded() { - world.info("Game of Life script loaded"); - // let LifeState = world.get_type_by_name("LifeState"); - // let life_state = world.get_component(entity,LifeState); - // let cells = life_state.cells; - - // // set some cells alive - // for x in 1..10000 { - // let index = rand(0..cells.len()); - // cells[index] = 255; - // } + let LifeState = world.get_type_by_name.call("LifeState"); + let Settings = world.get_type_by_name.call("Settings"); + + info.call("Rhai: Hello! I am initiating the game of life simulation state with randomness!"); + info.call("Rhai: Click on the screen to set cells alive after running the `gol start` command"); + + let life_state = fetch_life_state!(); + let cells = life_state.cells; + let cells_len = cells.len.call(); + let x = 0; + while x < 1000 { + let index = to_int(floor(rand.call()*cells_len)) ; + cells[index] = 255; + x += 1; + } } -fn on_update() { +fn on_click(x,y) { + let Settings = world.get_type_by_name.call("Settings"); + let LifeState = world.get_type_by_name.call("LifeState"); + + info.call("Rhai: Clicked at x: "+ x + ", y: " + y ); + let life_state = fetch_life_state!(); + let cells = life_state.cells; + + let settings = world.get_resource.call(Settings); + let dimensions = settings.physical_grid_dimensions; + let screen = settings.display_grid_dimensions; + + let dimension_x = dimensions["_0"]; + let dimension_y = dimensions["_1"]; - let LifeState = world.get_type_by_name("LifeState"); - let Settings = world.get_type_by_name("Settings"); + let screen_x = screen["_0"]; + let screen_y = screen["_1"]; - let life_state = world.get_component(entity,LifeState); + let cell_width = screen_x / dimension_x; + let cell_height = screen_y / dimension_y; + + let cell_x = to_int(x / cell_width); + let cell_y = to_int(y / cell_height); + + let index = cell_y * dimension_x + cell_x; + let cell_offsets_x = [0, 1, 0, 1, -1, 0, -1, 1, -1]; + let cell_offsets_y = [0, 0, 1, 1, 0, -1, -1, -1, 1]; + for (v,i) in cell_offsets_x { + let offset_x = cell_offsets_x[i]; + let offset_y = cell_offsets_y[i]; + let new_index = index + offset_x + offset_y * dimension_x; + if new_index >= 0 && new_index < (dimension_x * dimension_y) { + cells[new_index] = 255; + } + } + +} + +fn on_update() { + let LifeState = world.get_type_by_name.call("LifeState"); + let Settings = world.get_type_by_name.call("Settings"); + + let life_state = fetch_life_state!(); let cells = life_state.cells; // note that here we do not make use of RhaiProxyable and just go off pure reflection - let settings = world.get_resource(Settings); + let settings = world.get_resource.call(Settings); let dimensions = settings.physical_grid_dimensions; - + let dimension_x = dimensions["_0"]; + let dimension_y = dimensions["_1"]; // primitives are passed by value to rhai, keep a hold of old state but turn 255's into 1's let prev_state = []; @@ -31,15 +86,15 @@ fn on_update() { prev_state.push(life_state.cells[k] != 0); } - for i in 0..(dimensions[0] * dimensions[1]) { - let north = prev_state.get(i - dimensions[0]); - let south = prev_state.get(i + dimensions[0]); + for i in 0..(dimension_x * dimension_y) { + let north = prev_state.get(i - dimension_x); + let south = prev_state.get(i + dimension_x); let east = prev_state.get(i + 1); let west = prev_state.get(i - 1); - let northeast = prev_state.get(i - dimensions[0] + 1); - let southeast = prev_state.get(i + dimensions[0] + 1); - let northwest = prev_state.get(i - dimensions[0] - 1); - let southwest = prev_state.get(i + dimensions[0] - 1); + let northeast = prev_state.get(i - dimension_x + 1); + let southeast = prev_state.get(i + dimension_x + 1); + let northwest = prev_state.get(i - dimension_x - 1); + let southwest = prev_state.get(i + dimension_x - 1); let neighbours = 0; if north == () || north {neighbours+=1} @@ -60,5 +115,18 @@ fn on_update() { cells[i] = 0; } } +} + +fn on_script_unloaded() { + let LifeState = world.get_type_by_name.call("LifeState"); + let Settings = world.get_type_by_name.call("Settings"); + info.call("Rhai: I am being unloaded, goodbye!"); + + // set state to 0's + let life_state = fetch_life_state!(); + let cells = life_state.cells; + for i in 0..cells.len.call() { + cells[i] = 0; + } } \ No newline at end of file diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index 5f92113c1e..64c699caf4 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -16,16 +16,19 @@ name = "bevy_mod_scripting_core" path = "src/lib.rs" [features] +default = [] # if enabled enables documentation updating in optimized builds doc_always = [] # if enabled enables some common mlua trait implementations mlua_impls = ["mlua"] -# rhai_impls = ["rhai"] +rhai_impls = ["rhai"] [dependencies] -mlua = { optional = true, workspace = true } -# rhai = { optional = true, workspace = true } +mlua = { version = "0.10", default-features = false, optional = true } +rhai = { git = "https://github.com/rhaiscript/rhai", rev = "4ead53eb40f4a18d6f827609041ef1c742f04799", default-features = false, features = [ + "sync", +], optional = true } bevy = { workspace = true, default-features = false, features = [ "bevy_asset", diff --git a/crates/bevy_mod_scripting_core/src/asset.rs b/crates/bevy_mod_scripting_core/src/asset.rs index 196e3b795b..19ad3c8822 100644 --- a/crates/bevy_mod_scripting_core/src/asset.rs +++ b/crates/bevy_mod_scripting_core/src/asset.rs @@ -252,10 +252,20 @@ pub(crate) fn sync_script_data( mut commands: Commands, ) { for event in events.read() { + let metadata = match event { + ScriptAssetEvent::Added(script_metadata) + | ScriptAssetEvent::Removed(script_metadata) + | ScriptAssetEvent::Modified(script_metadata) => script_metadata, + }; + + if metadata.language != P::LANGUAGE { + continue; + } + trace!("{}: Received script asset event: {:?}", P::LANGUAGE, event); match event { // emitted when a new script asset is loaded for the first time - ScriptAssetEvent::Added(metadata) | ScriptAssetEvent::Modified(metadata) => { + ScriptAssetEvent::Added(_) | ScriptAssetEvent::Modified(_) => { if metadata.language != P::LANGUAGE { trace!( "{}: Script asset with id: {} is for a different langauge than this sync system. Skipping.", @@ -275,7 +285,7 @@ pub(crate) fn sync_script_data( )); } } - ScriptAssetEvent::Removed(metadata) => { + ScriptAssetEvent::Removed(_) => { info!("{}: Deleting Script: {:?}", P::LANGUAGE, metadata.script_id,); commands.queue(DeleteScript::

::new(metadata.script_id.clone())); } diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs index 8ef4056489..1a48857581 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs @@ -353,7 +353,7 @@ impl ScriptFunctionRegistryArc { } } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct FunctionKey { pub name: Cow<'static, str>, pub namespace: Namespace, @@ -504,6 +504,23 @@ impl ScriptFunctionRegistry { pub fn iter_all(&self) -> impl Iterator { self.functions.iter() } + + /// Insert a function into the registry with the given key, this will not perform any overloading logic. + /// Do not use unless you really need to. + pub fn raw_insert( + &mut self, + namespace: Namespace, + name: impl Into>, + func: DynamicScriptFunction, + ) { + self.functions.insert( + FunctionKey { + name: name.into(), + namespace, + }, + func, + ); + } } macro_rules! count { diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index de12a29474..485cc7cfa2 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -85,7 +85,6 @@ impl Command for DeleteScript

{ script.context_id, self.id ); - return; } }; } else { diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs index ac5ec7bd93..b18496055d 100644 --- a/crates/bevy_mod_scripting_core/src/error.rs +++ b/crates/bevy_mod_scripting_core/src/error.rs @@ -101,21 +101,21 @@ impl ScriptError { } } - // #[cfg(feature = "rhai_impls")] - // pub fn from_rhai_error(error: rhai::EvalAltResult) -> Self { - // match error { - // rhai::EvalAltResult::ErrorSystem(message, error) => { - // if let Some(inner) = error.downcast_ref::() { - // Self::new(inner.clone()) - // } else if let Some(inner) = error.downcast_ref::() { - // inner.clone() - // } else { - // Self::new_external_boxed(error).with_context(message) - // } - // } - // _ => Self::new_external(error), - // } - // } + #[cfg(feature = "rhai_impls")] + pub fn from_rhai_error(error: rhai::EvalAltResult) -> Self { + match error { + rhai::EvalAltResult::ErrorSystem(message, error) => { + if let Some(inner) = error.downcast_ref::() { + Self::new(inner.clone()) + } else if let Some(inner) = error.downcast_ref::() { + inner.clone() + } else { + Self::new_external_boxed(error).with_context(message) + } + } + _ => Self::new_external(error), + } + } pub fn new_external(reason: impl std::error::Error + Send + Sync + 'static) -> Self { Self::new_external_boxed(Box::new(reason)) @@ -211,39 +211,39 @@ impl From for ScriptError { } } -// #[cfg(feature = "rhai_impls")] -// impl From for ScriptError { -// fn from(value: rhai::ParseError) -> Self { -// ScriptError::new_external(value) -// } -// } - -// #[cfg(feature = "rhai_impls")] -// impl From> for ScriptError { -// fn from(value: Box) -> Self { -// ScriptError::from_rhai_error(*value) -// } -// } - -// #[cfg(feature = "rhai_impls")] -// impl From for Box { -// fn from(value: ScriptError) -> Self { -// Box::new(rhai::EvalAltResult::ErrorSystem( -// "ScriptError".to_owned(), -// Box::new(value), -// )) -// } -// } - -// #[cfg(feature = "rhai_impls")] -// impl From for Box { -// fn from(value: InteropError) -> Self { -// Box::new(rhai::EvalAltResult::ErrorSystem( -// "InteropError".to_owned(), -// Box::new(value), -// )) -// } -// } +#[cfg(feature = "rhai_impls")] +impl From for ScriptError { + fn from(value: rhai::ParseError) -> Self { + ScriptError::new_external(value) + } +} + +#[cfg(feature = "rhai_impls")] +impl From> for ScriptError { + fn from(value: Box) -> Self { + ScriptError::from_rhai_error(*value) + } +} + +#[cfg(feature = "rhai_impls")] +impl From for Box { + fn from(value: ScriptError) -> Self { + Box::new(rhai::EvalAltResult::ErrorSystem( + "ScriptError".to_owned(), + Box::new(value), + )) + } +} + +#[cfg(feature = "rhai_impls")] +impl From for Box { + fn from(value: InteropError) -> Self { + Box::new(rhai::EvalAltResult::ErrorSystem( + "InteropError".to_owned(), + Box::new(value), + )) + } +} #[derive(Clone, Debug, PartialEq)] pub struct MissingResourceError(&'static str); diff --git a/crates/bevy_mod_scripting_core/src/handler.rs b/crates/bevy_mod_scripting_core/src/handler.rs index f540cc23e7..650aaed64d 100644 --- a/crates/bevy_mod_scripting_core/src/handler.rs +++ b/crates/bevy_mod_scripting_core/src/handler.rs @@ -16,7 +16,7 @@ use bevy::{ system::{Resource, SystemState}, world::World, }, - log::{debug, trace}, + log::{trace, trace_once}, prelude::{EventReader, Events, Query, Ref}, }; @@ -127,15 +127,12 @@ pub(crate) fn event_handler_internal (), } - debug!( - "Handling event for script {} on entity {:?}", - script_id, entity - ); let script = match res_ctxt.scripts.scripts.get(script_id) { Some(s) => s, None => { - trace!( - "Script `{}` on entity `{:?}` is either still loading or doesn't exist, ignoring.", + trace_once!( + "{}: Script `{}` on entity `{:?}` is either still loading or doesn't exist, ignoring until the corresponding script is loaded.", + P::LANGUAGE, script_id, entity ); continue; diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 78fdd6e73c..26b9ff3c46 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -13,6 +13,7 @@ use context::{ Context, ContextAssigner, ContextBuilder, ContextInitializer, ContextLoadingSettings, ContextPreHandlingInitializer, ScriptContexts, }; +use error::ScriptError; use event::ScriptCallbackEvent; use handler::{CallbackSettings, HandlerFn}; use runtime::{initialize_runtime, Runtime, RuntimeContainer, RuntimeInitializer, RuntimeSettings}; @@ -213,7 +214,12 @@ fn once_per_app_init(app: &mut App) { fn register_script_plugin_systems(app: &mut App) { app.add_systems( PostStartup, - (initialize_runtime::

).in_set(ScriptingSystemSet::RuntimeInitialization), + (initialize_runtime::

.pipe(|e: In>| { + if let Err(e) = e.0 { + error!("Error initializing runtime: {:?}", e); + } + })) + .in_set(ScriptingSystemSet::RuntimeInitialization), ); configure_asset_systems_for_plugin::

(app); diff --git a/crates/bevy_mod_scripting_core/src/runtime.rs b/crates/bevy_mod_scripting_core/src/runtime.rs index 01eb83fc50..01ac1cdf30 100644 --- a/crates/bevy_mod_scripting_core/src/runtime.rs +++ b/crates/bevy_mod_scripting_core/src/runtime.rs @@ -1,7 +1,7 @@ //! "Runtime" here refers to the execution evironment of scripts. This might be the VM executing bytecode or the interpreter executing source code. //! The important thing is that there is only one runtime which is used to execute all scripts of a particular type or `context`. -use crate::IntoScriptPluginParams; +use crate::{error::ScriptError, IntoScriptPluginParams}; use bevy::{ ecs::system::Resource, prelude::{NonSendMut, Res}, @@ -10,7 +10,8 @@ use bevy::{ pub trait Runtime: 'static {} impl Runtime for T {} -pub type RuntimeInitializer

= fn(&mut

::R); +pub type RuntimeInitializer

= + fn(&mut

::R) -> Result<(), ScriptError>; #[derive(Resource)] pub struct RuntimeSettings { @@ -42,8 +43,9 @@ pub struct RuntimeContainer { pub fn initialize_runtime( mut runtime: NonSendMut>, settings: Res>, -) { +) -> Result<(), ScriptError> { for initializer in settings.initializers.iter() { - (initializer)(&mut runtime.runtime); + (initializer)(&mut runtime.runtime)?; } + Ok(()) } diff --git a/crates/languages/bevy_mod_scripting_lua/Cargo.toml b/crates/languages/bevy_mod_scripting_lua/Cargo.toml index c045cff2f9..52410ecb6b 100644 --- a/crates/languages/bevy_mod_scripting_lua/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_lua/Cargo.toml @@ -40,7 +40,7 @@ path = "src/lib.rs" bevy = { workspace = true, default-features = false } bevy_mod_scripting_core = { workspace = true, features = ["mlua_impls"] } bevy_mod_scripting_functions = { workspace = true, features = [] } -mlua = { workspace = true, features = ["vendored", "send", "macros"] } +mlua = { version = "0.10", features = ["vendored", "send", "macros"] } parking_lot = "0.12.1" uuid = "1.1" smol_str = "0.2.2" diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs index 9b267249bd..d2e6c17b22 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs @@ -277,11 +277,6 @@ impl UserData for LuaStaticReflectReference { let key: ScriptValue = key.into(); - // if let ScriptValue::String(ref key) = key { - // if let Some(func) = lookup_function(lua, key, type_id) { - // return func?.into_lua(lua); - // } - // }; let key = match key.as_string() { Ok(name) => match world.lookup_function([type_id], name) { Ok(func) => return Ok(LuaScriptValue(ScriptValue::Function(func))), diff --git a/crates/languages/bevy_mod_scripting_lua/src/lib.rs b/crates/languages/bevy_mod_scripting_lua/src/lib.rs index 3490d78177..144274e58e 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/lib.rs @@ -209,7 +209,14 @@ pub fn lua_handler( let handler: Function = match context.globals().raw_get(callback_label.as_ref()) { Ok(handler) => handler, // not subscribed to this event type - Err(_) => return Ok(ScriptValue::Unit), + Err(_) => { + bevy::log::trace!( + "Script {} is not subscribed to callback {}", + script_id, + callback_label.as_ref() + ); + return Ok(ScriptValue::Unit); + } }; let input = MultiValue::from_vec( diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/unm/vec3.lua b/crates/languages/bevy_mod_scripting_lua/tests/data/unm/vec3.lua index 668d8fa602..79a0bf34be 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/data/unm/vec3.lua +++ b/crates/languages/bevy_mod_scripting_lua/tests/data/unm/vec3.lua @@ -1,5 +1,5 @@ local a = Vec3.new(2.0, -4.0, 6.0) -assert(-a.x == -2.0, "Negation did not work") -assert(-a.y == 4.0, "Negation did not work") -assert(-a.z == -6.0, "Negation did not work") \ No newline at end of file +assert((-a).x == -2.0, "Negation did not work") +assert((-a).y == 4.0, "Negation did not work") +assert((-a).z == -6.0, "Negation did not work") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml index 62e677fc0b..273bfcd387 100644 --- a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_rhai" -version = "0.9.0-alpha.2" +version = "0.9.0-alpha.7" authors = ["Maksymilian Mozolewski "] edition = "2021" license = "MIT OR Apache-2.0" @@ -17,10 +17,11 @@ path = "src/lib.rs" [dependencies] bevy = { workspace = true, default-features = false } -rhai = { workspace = true, features = ["sync"] } +rhai = { git = "https://github.com/rhaiscript/rhai", rev = "4ead53eb40f4a18d6f827609041ef1c742f04799" } bevy_mod_scripting_core = { workspace = true, features = ["rhai_impls"] } bevy_mod_scripting_functions = { workspace = true, features = [ ], default-features = false } +strum = { version = "0.26", features = ["derive"] } [dev-dependencies] script_integration_test_harness = { workspace = true } diff --git a/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs b/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs index 8677a6f4f1..1b942e7e89 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs @@ -1,8 +1,8 @@ -use super::script_value::{FromDynamic, RHAI_CALLER_CONTEXT}; +use super::script_value::{FromDynamic, FunctionWithReceiver, IntoDynamic, RHAI_CALLER_CONTEXT}; use bevy_mod_scripting_core::{ bindings::{ - pretty_print::DisplayWithWorld, script_value::ScriptValue, ReflectReference, - ThreadWorldContainer, WorldContainer, + function::script_function::DynamicScriptFunctionMut, pretty_print::DisplayWithWorld, + script_value::ScriptValue, ReflectReference, ThreadWorldContainer, WorldContainer, }, error::InteropError, reflection_extensions::TypeIdExtensions, @@ -12,6 +12,153 @@ use std::{ any::TypeId, ops::{Deref, DerefMut}, }; +use strum::VariantNames; + +#[derive(Debug, strum::EnumString, strum::VariantNames, Clone)] +pub enum ReservedKeyword { + // Reserved under certain flags + #[strum(serialize = "?.")] + QuestionDot, + #[strum(serialize = "?[")] + QuestionBracket, + #[strum(serialize = "fn")] + Fn, + #[strum(serialize = "private")] + Private, + #[strum(serialize = "import")] + Import, + #[strum(serialize = "export")] + Export, + #[strum(serialize = "as")] + As, + // Reserved symbols + #[strum(serialize = "===")] + TripleEquals, + #[strum(serialize = "!==")] + NotEquals, + #[strum(serialize = "->")] + ArrowRight, + #[strum(serialize = "<-")] + ArrowLeft, + #[strum(serialize = "?")] + Question, + #[strum(serialize = ":=")] + ColonEquals, + #[strum(serialize = ":;")] + ColonSemicolon, + #[strum(serialize = "~")] + Tilde, + #[strum(serialize = "!.")] + ExclamationDot, + #[strum(serialize = "::<")] + DoubleColonLess, + #[strum(serialize = "(*")] + ParenStar, + #[strum(serialize = "*)")] + StarParen, + #[strum(serialize = "#")] + Hash, + #[strum(serialize = "#!")] + HashBang, + #[strum(serialize = "@")] + At, + #[strum(serialize = "$")] + Dollar, + #[strum(serialize = "++")] + PlusPlus, + #[strum(serialize = "--")] + MinusMinus, + #[strum(serialize = "...")] + Ellipsis, + #[strum(serialize = "<|")] + LessPipe, + #[strum(serialize = "|>")] + PipeGreater, + // Reserved keywords + #[strum(serialize = "public")] + Public, + #[strum(serialize = "protected")] + Protected, + #[strum(serialize = "super")] + Super, + #[strum(serialize = "new")] + New, + #[strum(serialize = "use")] + Use, + #[strum(serialize = "module")] + Module, + #[strum(serialize = "package")] + Package, + #[strum(serialize = "var")] + Var, + #[strum(serialize = "static")] + Static, + #[strum(serialize = "shared")] + Shared, + #[strum(serialize = "with")] + With, + #[strum(serialize = "is")] + Is, + #[strum(serialize = "goto")] + Goto, + #[strum(serialize = "exit")] + Exit, + #[strum(serialize = "match")] + Match, + #[strum(serialize = "case")] + Case, + #[strum(serialize = "default")] + Default, + #[strum(serialize = "void")] + Void, + #[strum(serialize = "null")] + Null, + #[strum(serialize = "nil")] + Nil, + #[strum(serialize = "spawn")] + Spawn, + #[strum(serialize = "thread")] + Thread, + #[strum(serialize = "go")] + Go, + #[strum(serialize = "sync")] + Sync, + #[strum(serialize = "async")] + Async, + #[strum(serialize = "await")] + Await, + #[strum(serialize = "yield")] + Yield, + // Keyword functions + #[strum(serialize = "print")] + Print, + #[strum(serialize = "debug")] + Debug, + #[strum(serialize = "type_of")] + TypeOf, + #[strum(serialize = "eval")] + Eval, + #[strum(serialize = "Fn")] + FnKeyword, + #[strum(serialize = "call")] + Call, + #[strum(serialize = "curry")] + Curry, + #[strum(serialize = "this")] + This, + #[strum(serialize = "is_def_var")] + IsDefVar, + #[strum(serialize = "is_def_fn")] + IsDefFn, + #[strum(serialize = "is_shared")] + IsShared, +} + +impl ReservedKeyword { + pub fn is_reserved_keyword(s: impl AsRef) -> bool { + ReservedKeyword::VARIANTS.iter().any(|v| v == &s.as_ref()) + } +} #[derive(Clone, Debug, PartialEq)] pub struct RhaiReflectReference(pub ReflectReference); @@ -48,12 +195,96 @@ impl DerefMut for RhaiReflectReference { } } +pub enum RhaiOperator { + Sub, + Add, + Mul, + Div, + Mod, + Unm, + Pow, + Eq, + Ne, + Lt, +} + +impl RhaiOperator { + pub fn function_name(self) -> &'static str { + match self { + RhaiOperator::Sub => "-", + RhaiOperator::Add => "+", + RhaiOperator::Mul => "*", + RhaiOperator::Div => "/", + RhaiOperator::Mod => "%", + RhaiOperator::Unm => "-", + RhaiOperator::Pow => "**", + RhaiOperator::Eq => "==", + RhaiOperator::Lt => "<", + RhaiOperator::Ne => "!=", + } + } +} + +pub struct RhaiReflectRefIter { + next_func: DynamicScriptFunctionMut, +} + +impl Iterator for RhaiReflectRefIter { + type Item = Result>; + + fn next(&mut self) -> Option { + match self.next_func.call(vec![], RHAI_CALLER_CONTEXT) { + Ok(ScriptValue::Unit) => None, + Ok(v) => Some(v.into_dynamic()), + Err(error) => Some(Err(error.into())), + } + } +} + +impl IntoIterator for RhaiReflectReference { + type Item = Result>; + + type IntoIter = RhaiReflectRefIter; + + fn into_iter(self) -> Self::IntoIter { + let result = (|| { + let world = ThreadWorldContainer.try_get_world()?; + + let iter_func = world + .lookup_function([TypeId::of::()], "iter") + .map_err(|f| InteropError::missing_function(TypeId::of::(), f))?; + + iter_func.call( + vec![ScriptValue::Reference(self.0.clone())], + RHAI_CALLER_CONTEXT, + ) + })(); + + match result { + Ok(ScriptValue::FunctionMut(f)) => RhaiReflectRefIter { next_func: f }, + Ok(_) => RhaiReflectRefIter { + next_func: (|_, _| { + ScriptValue::Error(InteropError::invariant( + "iter function did not return a function", + )) + }) + .into(), + }, + Err(error) => RhaiReflectRefIter { + next_func: (move |_, _| ScriptValue::Error(error.clone())).into(), + }, + } + + // manually call + } +} + impl CustomType for RhaiReflectReference { fn build(mut builder: rhai::TypeBuilder) { builder - .with_name(std::any::type_name::()) + .with_name(std::any::type_name::()) .with_indexer_get(|self_: &mut Self, _index: Dynamic| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let self_ = &self_.0; let type_id = self_.tail_type_id(world.clone())?.or_fake_id(); @@ -63,7 +294,10 @@ impl CustomType for RhaiReflectReference { match world .lookup_function([type_id, TypeId::of::()], string) { - Ok(func) => return Ok(Dynamic::from(func)), + Ok(func) => { + return FunctionWithReceiver::curry(func, self_.clone().into()) + .into_dynamic() + } Err(string) => ScriptValue::String(string), } } @@ -72,15 +306,230 @@ impl CustomType for RhaiReflectReference { let func = world .lookup_function([type_id, TypeId::of::()], "get") - .expect("No 'get' function registered for ReflectReference"); + .map_err(|_| InteropError::missing_function(type_id, "get".to_owned()))?; let out = func.call( vec![ScriptValue::Reference(self_.clone()), key], - world, RHAI_CALLER_CONTEXT, )?; - Ok::<_, Box>(Dynamic::from(out)) + out.into_dynamic() + }) + .with_indexer_set(|self_: &mut Self, _index: Dynamic, _value: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_ = self_.0.clone(); + let key = ScriptValue::from_dynamic(_index)?; + let value = ScriptValue::from_dynamic(_value)?; + + let func = world + .lookup_function([TypeId::of::()], "set") + .map_err(|f| { + InteropError::missing_function(TypeId::of::(), f) + })?; + + let out = func.call( + vec![ScriptValue::Reference(self_), key, value], + RHAI_CALLER_CONTEXT, + )?; + + match out { + ScriptValue::Error(interop_error) => Err(interop_error)?, + _ => Ok(()), + } + }) + .with_fn( + RhaiOperator::Sub.function_name(), + |self_: Self, other: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let other: ScriptValue = ScriptValue::from_dynamic(other)?; + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_), other]; + let out = world.try_call_overloads( + target_type_id, + "sub", + args, + RHAI_CALLER_CONTEXT, + )?; + out.into_dynamic() + }, + ) + .with_fn( + RhaiOperator::Add.function_name(), + |self_: Self, other: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let other: ScriptValue = ScriptValue::from_dynamic(other)?; + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_), other]; + let out = world.try_call_overloads( + target_type_id, + "add", + args, + RHAI_CALLER_CONTEXT, + )?; + out.into_dynamic() + }, + ) + .with_fn( + RhaiOperator::Mul.function_name(), + |self_: Self, other: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let other: ScriptValue = ScriptValue::from_dynamic(other)?; + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_), other]; + let out = world.try_call_overloads( + target_type_id, + "mul", + args, + RHAI_CALLER_CONTEXT, + )?; + out.into_dynamic() + }, + ) + .with_fn( + RhaiOperator::Div.function_name(), + |self_: Self, other: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let other: ScriptValue = ScriptValue::from_dynamic(other)?; + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_), other]; + let out = world.try_call_overloads( + target_type_id, + "div", + args, + RHAI_CALLER_CONTEXT, + )?; + out.into_dynamic() + }, + ) + .with_fn( + RhaiOperator::Mod.function_name(), + |self_: Self, other: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let other: ScriptValue = ScriptValue::from_dynamic(other)?; + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_), other]; + let out = world.try_call_overloads( + target_type_id, + "rem", + args, + RHAI_CALLER_CONTEXT, + )?; + out.into_dynamic() + }, + ) + .with_fn(RhaiOperator::Unm.function_name(), |self_: Self| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_)]; + let out = + world.try_call_overloads(target_type_id, "neg", args, RHAI_CALLER_CONTEXT)?; + out.into_dynamic() + }) + .with_fn( + RhaiOperator::Pow.function_name(), + |self_: Self, other: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let other: ScriptValue = ScriptValue::from_dynamic(other)?; + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_), other]; + let out = world.try_call_overloads( + target_type_id, + "pow", + args, + RHAI_CALLER_CONTEXT, + )?; + out.into_dynamic() + }, + ) + .with_fn( + RhaiOperator::Eq.function_name(), + |self_: Self, other: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let other: ScriptValue = ScriptValue::from_dynamic(other)?; + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_), other]; + let out = world.try_call_overloads( + target_type_id, + "eq", + args, + RHAI_CALLER_CONTEXT, + )?; + out.into_dynamic() + }, + ) + .with_fn( + RhaiOperator::Ne.function_name(), + |self_: Self, other: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let other: ScriptValue = ScriptValue::from_dynamic(other)?; + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_), other]; + let out = world.try_call_overloads( + target_type_id, + "eq", + args, + RHAI_CALLER_CONTEXT, + )?; + match out { + ScriptValue::Bool(b) => ScriptValue::Bool(!b).into_dynamic(), + _ => Err(InteropError::invariant("eq did not return a bool").into()), + } + }, + ) + .with_fn( + RhaiOperator::Lt.function_name(), + |self_: Self, other: Dynamic| { + let world = ThreadWorldContainer.try_get_world()?; + let self_: ReflectReference = self_.0.clone(); + let other: ScriptValue = ScriptValue::from_dynamic(other)?; + let target_type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let args = vec![ScriptValue::Reference(self_), other]; + let out = world.try_call_overloads( + target_type_id, + "lt", + args, + RHAI_CALLER_CONTEXT, + )?; + out.into_dynamic() + }, + ) + .on_print(|self_| { + let result: Result<_, InteropError> = (|| { + let world = ThreadWorldContainer.try_get_world()?; + let reflect_reference = self_.0.clone(); + + let func = world + .lookup_function([TypeId::of::()], "display_ref") + .map_err(|f| { + InteropError::missing_function(TypeId::of::(), f) + })?; + + let out = func.call( + vec![ScriptValue::Reference(reflect_reference)], + RHAI_CALLER_CONTEXT, + )?; + + match out { + ScriptValue::String(s) => Ok(s), + _ => Err(InteropError::invariant( + "display_ref failed to return a string", + )), + } + })(); + + match result { + Ok(str_) => str_.into(), + Err(error) => error.to_string(), + } }); } } @@ -93,13 +542,13 @@ impl CustomType for RhaiStaticReflectReference { builder .with_name(std::any::type_name::()) .with_indexer_get(|self_: &mut Self, index: Dynamic| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; let type_id = self_.0; let key: ScriptValue = ScriptValue::from_dynamic(index)?; let key = match key.as_string() { Ok(name) => match world.lookup_function([type_id], name) { - Ok(func) => return Ok(Dynamic::from(func)), + Ok(func) => return ScriptValue::Function(func).into_dynamic(), Err(key) => ScriptValue::String(key), }, Err(key) => key, diff --git a/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs b/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs index d33e4b320d..5ea9038351 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs @@ -1,39 +1,19 @@ -use std::{str::FromStr, sync::Arc}; - use bevy_mod_scripting_core::{ bindings::{ - function::script_function::{ - CallerContext, DynamicScriptFunction, DynamicScriptFunctionMut, - }, + function::script_function::{DynamicScriptFunction, FunctionCallContext}, script_value::ScriptValue, }, error::InteropError, }; -use rhai::{ - plugin::{PluginFunc, RhaiFunc}, - Dynamic, EvalAltResult, FnPtr, RhaiNativeFunc, -}; +use rhai::{Dynamic, EvalAltResult, FnPtr, NativeCallContext}; +use std::str::FromStr; -pub const RHAI_CALLER_CONTEXT: CallerContext = CallerContext { +use super::reference::RhaiReflectReference; + +pub const RHAI_CALLER_CONTEXT: FunctionCallContext = FunctionCallContext { convert_to_0_indexed: false, }; -#[allow(dead_code)] -struct FuncWrapper(DynamicScriptFunction); - -#[allow(dead_code)] -struct FuncMutWrapper(DynamicScriptFunctionMut); - -impl RhaiNativeFunc for FuncWrapper { - fn into_rhai_function(self, is_pure: bool, is_volatile: bool) -> RhaiFunc { - todo!() - } - - fn param_types() -> [std::any::TypeId; N] { - todo!() - } -} - // impl PluginFunc for FuncWrapper { // fn call( // &self, @@ -95,23 +75,36 @@ impl RhaiNativeFunc for FuncWrapper { // } // } -#[allow(dead_code)] -pub(crate) fn to_rhai_fn(func: DynamicScriptFunction) -> RhaiFunc { - RhaiFunc::Plugin { - func: Arc::new(FuncWrapper(func)), +/// A function curried with one argument, i.e. the receiver +pub struct FunctionWithReceiver { + pub function: DynamicScriptFunction, + pub receiver: ScriptValue, +} + +impl FunctionWithReceiver { + pub fn curry(function: DynamicScriptFunction, receiver: ScriptValue) -> Self { + Self { function, receiver } } - .into() - // FnPtr { - // name: todo!(), - // curry: todo!(), - // environ: todo!(), - // fn_def: todo!(), - // } } -pub(crate) fn to_rhai_fn_mut(func: DynamicScriptFunctionMut) -> RhaiFunc { - RhaiFunc::Plugin { - func: Arc::new(FuncMutWrapper(func)), +impl IntoDynamic for FunctionWithReceiver { + fn into_dynamic(self) -> Result> { + Ok(Dynamic::from(FnPtr::from_fn( + self.function.name().to_string(), + move |_ctxt: NativeCallContext, args: &mut [&mut Dynamic]| { + let convert_args = args + .iter_mut() + .map(|arg| ScriptValue::from_dynamic(arg.clone())) + .collect::, _>>()?; + + let out = self.function.call( + std::iter::once(self.receiver.clone()).chain(convert_args), + RHAI_CALLER_CONTEXT, + )?; + + out.into_dynamic() + }, + )?)) } } @@ -119,6 +112,7 @@ pub trait IntoDynamic { fn into_dynamic(self) -> Result>; } +#[allow(clippy::todo)] impl IntoDynamic for ScriptValue { fn into_dynamic(self) -> Result> { Ok(match self { @@ -137,11 +131,47 @@ impl IntoDynamic for ScriptValue { .into(), ) })?, - ScriptValue::List(_vec) => todo!(), - ScriptValue::Reference(_reflect_reference) => todo!(), - ScriptValue::FunctionMut(func) => Dynamic::from(to_rhai_fn_mut(func)), - ScriptValue::Function(func) => Dynamic::from(to_rhai_fn(func)), - ScriptValue::Error(_interop_error) => todo!(), + ScriptValue::List(_vec) => Dynamic::from_array( + _vec.into_iter() + .map(|v| v.into_dynamic()) + .collect::, _>>()?, + ), + ScriptValue::Reference(reflect_reference) => { + Dynamic::from(RhaiReflectReference(reflect_reference)) + } + ScriptValue::FunctionMut(func) => Dynamic::from(FnPtr::from_fn( + func.name().to_string(), + move |_ctxt: NativeCallContext, args: &mut [&mut Dynamic]| { + let convert_args = args + .iter_mut() + .map(|arg| ScriptValue::from_dynamic(arg.clone())) + .collect::, _>>()?; + + let out = func.call(convert_args, RHAI_CALLER_CONTEXT)?; + + out.into_dynamic() + }, + )?), + ScriptValue::Function(func) => Dynamic::from(FnPtr::from_fn( + func.name().to_string(), + move |_ctxt: NativeCallContext, args: &mut [&mut Dynamic]| { + let convert_args = args + .iter_mut() + .map(|arg| ScriptValue::from_dynamic(arg.clone())) + .collect::, _>>()?; + + let out = func.call(convert_args, RHAI_CALLER_CONTEXT)?; + + out.into_dynamic() + }, + )?), + ScriptValue::Error(interop_error) => { + return Err(EvalAltResult::ErrorSystem( + "Interop error in rhai script".to_string(), + interop_error.into(), + ) + .into()) + } }) } } @@ -150,6 +180,7 @@ pub trait FromDynamic: Sized { fn from_dynamic(dynamic: Dynamic) -> Result>; } +#[allow(clippy::unwrap_used, clippy::todo)] impl FromDynamic for ScriptValue { fn from_dynamic(dynamic: Dynamic) -> Result> { match dynamic { @@ -160,7 +191,20 @@ impl FromDynamic for ScriptValue { d if d.is_string() => Ok(ScriptValue::String( d.into_immutable_string().unwrap().to_string().into(), )), - _ => todo!(), + d if d.is_array() => Ok(ScriptValue::List( + d.into_array() + .map_err(|_| InteropError::invariant("d is proved to be an array"))? + .into_iter() + .map(ScriptValue::from_dynamic) + .collect::, _>>()?, + )), + d => { + if let Some(v) = d.try_cast::() { + Ok(ScriptValue::Reference(v.0)) + } else { + todo!("from conversion not implemented yet") + } + } } } } diff --git a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs index dfc5aa77ab..17d84e358e 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs @@ -5,7 +5,8 @@ use bevy::{ use bevy_mod_scripting_core::{ asset::{AssetPathToLanguageMapper, Language}, bindings::{ - script_value::ScriptValue, ThreadWorldContainer, WorldCallbackAccess, WorldContainer, + function::namespace::Namespace, script_value::ScriptValue, ThreadWorldContainer, + WorldContainer, }, context::{ContextBuilder, ContextInitializer, ContextPreHandlingInitializer}, error::ScriptError, @@ -15,8 +16,11 @@ use bevy_mod_scripting_core::{ script::ScriptId, IntoScriptPluginParams, ScriptingPlugin, }; -use bindings::reference::{RhaiReflectReference, RhaiStaticReflectReference}; -use rhai::{CallFnOptions, Engine, FnPtr, Scope, AST}; +use bindings::{ + reference::{ReservedKeyword, RhaiReflectReference, RhaiStaticReflectReference}, + script_value::{FromDynamic, IntoDynamic}, +}; +use rhai::{CallFnOptions, Dynamic, Engine, EvalAltResult, Scope, AST}; pub use rhai; pub mod bindings; @@ -43,34 +47,97 @@ pub struct RhaiScriptingPlugin { pub scripting_plugin: ScriptingPlugin, } +impl AsMut> for RhaiScriptingPlugin { + fn as_mut(&mut self) -> &mut ScriptingPlugin { + &mut self.scripting_plugin + } +} + impl Default for RhaiScriptingPlugin { fn default() -> Self { RhaiScriptingPlugin { scripting_plugin: ScriptingPlugin { - runtime_settings: Some(RuntimeSettings { + context_assigner: Default::default(), + runtime_settings: RuntimeSettings { initializers: vec![|runtime: &mut Engine| { runtime.build_type::(); runtime.build_type::(); + runtime.register_iterator_result::(); + Ok(()) }], - }), - callback_handler: Some(rhai_callback_handler), - context_assigner: None, - context_builder: Some(ContextBuilder { + }, + callback_handler: rhai_callback_handler, + context_builder: ContextBuilder { load: rhai_context_load, reload: rhai_context_reload, - }), - language_mapper: Some(AssetPathToLanguageMapper { + }, + language_mapper: AssetPathToLanguageMapper { map: rhai_language_mapper, - }), - context_initializers: vec![|_script_id: _, context: &mut RhaiScriptContext| { - context.scope.set_or_push( - "world", - RhaiStaticReflectReference(std::any::TypeId::of::()), - ); - Ok(()) - }], + }, + context_initializers: vec![ + |_, context: &mut RhaiScriptContext| { + context.scope.set_or_push( + "world", + RhaiStaticReflectReference(std::any::TypeId::of::()), + ); + Ok(()) + }, + |_, context: &mut RhaiScriptContext| { + // initialize global functions + let world = ThreadWorldContainer.try_get_world()?; + let type_registry = world.type_registry(); + let type_registry = type_registry.read(); + + for registration in type_registry.iter() { + // only do this for non generic types + // we don't want to see `Vec:function()` in lua + if !registration.type_info().generics().is_empty() { + continue; + } + + if let Some(global_name) = + registration.type_info().type_path_table().ident() + { + let ref_ = RhaiStaticReflectReference(registration.type_id()); + context.scope.set_or_push(global_name, ref_); + } + } + + let mut script_function_registry = world.script_function_registry(); + let mut script_function_registry = script_function_registry.write(); + + // iterate all functions, and remap names with reserved keywords + let mut re_insertions = Vec::new(); + for (key, function) in script_function_registry.iter_all() { + let name = key.name.clone(); + if ReservedKeyword::is_reserved_keyword(&name) { + let new_name = format!("{}_", name); + let mut function = function.clone(); + function.info.name = new_name.clone().into(); + re_insertions.push((key.namespace, new_name, function.clone())); + } + } + for (namespace, name, func) in re_insertions { + script_function_registry.raw_insert(namespace, name, func); + } + + // then go through functions in the global namespace and add them to the lua context + + for (key, function) in script_function_registry + .iter_all() + .filter(|(k, _)| k.namespace == Namespace::Global) + { + context.scope.set_or_push( + key.name.clone(), + ScriptValue::Function(function.clone()).into_dynamic()?, + ); + } + + Ok(()) + }, + ], context_pre_handling_initializers: vec![|script, entity, context| { - let world = ThreadWorldContainer.get_world(); + let world = ThreadWorldContainer.try_get_world()?; context.scope.set_or_push( "entity", RhaiReflectReference(::allocate(Box::new(entity), world)), @@ -94,24 +161,6 @@ impl Plugin for RhaiScriptingPlugin { fn build(&self, app: &mut bevy::prelude::App) { self.scripting_plugin.build(app); } - - fn cleanup(&self, _app: &mut bevy::prelude::App) { - // let mut runtime = app - // .world_mut() - // .get_non_send_resource_mut::>() - // .expect("Rhai runtime not found"); - // let engine = &mut runtime.runtime; - // let function_registry = app - // .world_mut() - // .get_resource_or_init::(); - - // let function_registry = function_registry.read(); - - // for (k, func) in function_registry.iter_all() { - // let rhai_func = to_rhai_fn(func.clone()); - // // engine.register_fn("func", rhai_func); - // } - } } pub fn rhai_context_load( @@ -119,7 +168,6 @@ pub fn rhai_context_load( content: &[u8], initializers: &[ContextInitializer], pre_handling_initializers: &[ContextPreHandlingInitializer], - world: &mut World, runtime: &mut RhaiRuntime, ) -> Result { let mut ast = runtime.compile(std::str::from_utf8(content)?)?; @@ -129,21 +177,18 @@ pub fn rhai_context_load( ast, scope: Scope::new(), }; - with_world(world, &mut context, |context| { - initializers - .iter() - .try_for_each(|init| init(script, context))?; + initializers + .iter() + .try_for_each(|init| init(script, &mut context))?; - pre_handling_initializers - .iter() - .try_for_each(|init| init(script, Entity::from_raw(0), context))?; + pre_handling_initializers + .iter() + .try_for_each(|init| init(script, Entity::from_raw(0), &mut context))?; - runtime.eval_ast_with_scope(&mut context.scope, &context.ast)?; - // do not invoke top level statements after the first time we run the script - context.ast.clear_statements(); + runtime.eval_ast_with_scope(&mut context.scope, &context.ast)?; + // do not invoke top level statements after the first time we run the script + context.ast.clear_statements(); - Ok(()) - })?; Ok(context) } @@ -153,7 +198,6 @@ pub fn rhai_context_reload( context: &mut RhaiScriptContext, initializers: &[ContextInitializer], pre_handling_initializers: &[ContextPreHandlingInitializer], - world: &mut World, runtime: &mut RhaiRuntime, ) -> Result<(), ScriptError> { *context = rhai_context_load( @@ -161,7 +205,6 @@ pub fn rhai_context_reload( content, initializers, pre_handling_initializers, - world, runtime, )?; Ok(()) @@ -176,42 +219,43 @@ pub fn rhai_callback_handler( context: &mut RhaiScriptContext, pre_handling_initializers: &[ContextPreHandlingInitializer], runtime: &mut RhaiRuntime, - world: &mut World, ) -> Result { - with_world(world, context, |context| { - pre_handling_initializers - .iter() - .try_for_each(|init| init(script_id, entity, context))?; - - if context - .scope - .get_value::(callback.as_ref()) - .is_none() - { - // not subscribed to this handler - return Ok(ScriptValue::Unit); - }; - - // we want the call to be able to impact the scope - let options = CallFnOptions::new().rewind_scope(false); - let out = runtime.call_fn_with_options::( - options, - &mut context.scope, - &context.ast, - callback.as_ref(), - args, - )?; - Ok(out) - }) -} + pre_handling_initializers + .iter() + .try_for_each(|init| init(script_id, entity, context))?; -pub fn with_world Result>( - world: &mut World, - context: &mut RhaiScriptContext, - f: F, -) -> Result { - WorldCallbackAccess::with_callback_access(world, |guard| { - ThreadWorldContainer.set_world(guard.clone())?; - f(context) - }) + // we want the call to be able to impact the scope + let options = CallFnOptions::new().rewind_scope(false); + let args = args + .into_iter() + .map(|v| v.into_dynamic()) + .collect::, _>>()?; + + bevy::log::trace!( + "Calling callback {} in script {} with args: {:?}", + callback, + script_id, + args + ); + match runtime.call_fn_with_options::( + options, + &mut context.scope, + &context.ast, + callback.as_ref(), + args, + ) { + Ok(v) => Ok(ScriptValue::from_dynamic(v)?), + Err(e) => { + if let EvalAltResult::ErrorFunctionNotFound(_, _) = e.unwrap_inner() { + bevy::log::trace!( + "Script {} is not subscribed to callback {} with the provided arguments.", + script_id, + callback + ); + Ok(ScriptValue::Unit) + } else { + Err(ScriptError::from(e)) + } + } + } } diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/access/multiple_read_refs.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/access/multiple_read_refs.rhai new file mode 100644 index 0000000000..875e8eff06 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/access/multiple_read_refs.rhai @@ -0,0 +1,3 @@ +let entity = Entity.from_raw.call(9999); +// does not throw +let out = entity.eq.call(entity); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add/vec3.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/add/vec3.rhai new file mode 100644 index 0000000000..c538e36e29 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/add/vec3.rhai @@ -0,0 +1,11 @@ +let a = Vec3.new_.call(1.0, 2.0, 3.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + +assert((a + 1).x == 2.0, "Addition did not work"); +assert((a + 1).y == 3.0, "Addition did not work"); +assert((a + 1).z == 4.0, "Addition did not work"); + +assert((a + b).x == 5.0, "Addition did not work"); +assert((a + b).y == 7.0, "Addition did not work"); +assert((a + b).z == 9.0, "Addition did not work"); + diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_no_default_or_from_world_data_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_no_default_or_from_world_data_errors.rhai new file mode 100644 index 0000000000..463d3100c0 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_no_default_or_from_world_data_errors.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let type = world.get_type_by_name.call("TestComponent"); + +assert_throws(||{ + world.add_default_component.call(entity, type); +},"Missing type data ReflectDefault or ReflectFromWorld for type: .*TestComponent.*"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_and_component_data_adds_default.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_and_component_data_adds_default.rhai new file mode 100644 index 0000000000..e2ce895868 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_and_component_data_adds_default.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithDefaultAndComponentData"); +world.add_default_component.call(entity, _type); + +let added = world.has_component.call(entity, _type); +assert(type_of(added) != "()", "Component not added"); + +let component = world.get_component.call(entity, _type); +assert(component["_0"] == "Default", "Component did not have default value, got: " + component["_0"]); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_no_component_data_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_no_component_data_errors.rhai new file mode 100644 index 0000000000..988642e16c --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_no_component_data_errors.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithDefault"); + +assert_throws(||{ + world.add_default_component.call(entity, _type); +}, "Missing type data ReflectComponent for type: .*CompWithDefault.*") diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.rhai new file mode 100644 index 0000000000..63c81f3dec --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithFromWorldAndComponentData"); +world.add_default_component.call(entity, _type); + +let added = world.has_component.call(entity, _type); +assert(type_of(added) != "()", "Component not added"); + +let component = world.get_component.call(entity, _type); +assert(component["_0"] == "Default", "Component did not have default value, got: " + component["_0"]) \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_no_component_data_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_no_component_data_errors.rhai new file mode 100644 index 0000000000..323dfb84d1 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_no_component_data_errors.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithFromWorld"); + +assert_throws(||{ + world.add_default_component.call(entity, _type); +}, "Missing type data ReflectComponent for type: .*CompWithFromWorld.*") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_callback.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_callback.rhai new file mode 100644 index 0000000000..d37af11858 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_callback.rhai @@ -0,0 +1,5 @@ +fn on_test() { + assert!(type_of(world) != "()", "World was not found"); + assert!(type_of(world.get_type_by_name.call("TestComponent")) != "()", "Could not find TestComponent type"); + Entity.from_raw.call(1); +} \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_script_load.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_script_load.rhai new file mode 100644 index 0000000000..5661f55644 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_script_load.rhai @@ -0,0 +1,3 @@ +assert!(type_of(world) != "()", "World was not found"); +assert!(type_of(world.get_type_by_name.call("TestComponent")) != "()", "Could not find TestComponent type"); +let out = Entity.from_raw.call(1); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/clear/vec.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/clear/vec.rhai new file mode 100644 index 0000000000..4f1d5e7d10 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/clear/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +res.vec_usize.clear.call(); + +assert(res.vec_usize.len.call() == 0, "Clear did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/despawns_only_root.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/despawns_only_root.rhai new file mode 100644 index 0000000000..5c3a8fc466 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/despawns_only_root.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +world.push_children.call(entity, [child]); +world.despawn.call(entity); + +assert(world.has_entity.call(entity) == false, "Parent should be despawned"); +assert(world.has_entity.call(child) == true, "Child should not be despawned"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/invalid_entity_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/invalid_entity_errors.rhai new file mode 100644 index 0000000000..730fca0782 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.despawn_recursive.call(Entity.from_raw.call(9999)) +}, "Missing or invalid entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/despawns_only_child.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/despawns_only_child.rhai new file mode 100644 index 0000000000..fb6996cee4 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/despawns_only_child.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +world.push_children.call(entity, [child]); +world.despawn_descendants.call(entity); + +assert(world.has_entity.call(entity) == true, "Parent should not be despawned"); +assert(world.has_entity.call(child) == false, "Child should be despawned"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/invalid_entity_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/invalid_entity_errors.rhai new file mode 100644 index 0000000000..bdaa98ff33 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.despawn_recursive.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/despawns_recursively.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/despawns_recursively.rhai new file mode 100644 index 0000000000..8d4e2ab757 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/despawns_recursively.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +world.push_children.call(entity, [child]); +world.despawn_recursive.call(entity); + +assert(world.has_entity.call(entity) == false, "Parent should be despawned"); +assert(world.has_entity.call(child) == false, "Child should be despawned"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/invalid_entity_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/invalid_entity_errors.rhai new file mode 100644 index 0000000000..bdaa98ff33 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.despawn_recursive.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/div/vec3.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/div/vec3.rhai new file mode 100644 index 0000000000..af3a96d5f0 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/div/vec3.rhai @@ -0,0 +1,10 @@ +let a = Vec3.new_.call(2.0, 4.0, 6.0); +let b = Vec3.new_.call(1.0, 2.0, 3.0); + +assert((a / 2).x == 1.0, "Division did not work"); +assert((a / 2).y == 2.0, "Division did not work"); +assert((a / 2).z == 3.0, "Division did not work"); + +assert((a / b).x == 2.0, "Division did not work"); +assert((a / b).y == 2.0, "Division did not work"); +assert((a / b).z == 2.0, "Division did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/eq/vec3.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/eq/vec3.rhai new file mode 100644 index 0000000000..f4652fed8c --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/eq/vec3.rhai @@ -0,0 +1,7 @@ +let a = Vec3.new_.call(2.0, -4.0, 6.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + + +assert((a == b) == false, "Equality did not work"); +assert((a != b) == true, "Inequality did not work"); +assert((a == a) == true, "Equality did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/has_children_returns_them.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/has_children_returns_them.rhai new file mode 100644 index 0000000000..1f8bece3a4 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/has_children_returns_them.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); + +world.push_children.call(entity, [child]); + +let children = world.get_children.call(entity); + +assert(children.len == 1, "Expected 1 child"); +assert(children[0].index.call() == child.index.call(), "Child is the wrong entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/invalid_entity_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/invalid_entity_errors.rhai new file mode 100644 index 0000000000..90bf506ca9 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.get_children.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/no_children_returns_empty_table.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/no_children_returns_empty_table.rhai new file mode 100644 index 0000000000..1ecc14c5a0 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/no_children_returns_empty_table.rhai @@ -0,0 +1,4 @@ +let entity = world.spawn_.call(); +let children = world.get_children.call(entity); + +assert(children.len == 0); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_no_component_data.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_no_component_data.rhai new file mode 100644 index 0000000000..1000055921 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_no_component_data.rhai @@ -0,0 +1,6 @@ +let component = world.get_type_by_name.call("CompWithDefault"); +let entity = world._get_entity_with_test_component.call("CompWithDefault"); +let retrieved = world.get_component.call(entity, component); + +assert(type_of(retrieved) != "()", "Component was not found"); +assert(retrieved["_0"] == "Initial Value", "Component data was not retrieved correctly, retrieved._0 was: " + retrieved["_0"]); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_with_component_data.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_with_component_data.rhai new file mode 100644 index 0000000000..7a638c20ea --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_with_component_data.rhai @@ -0,0 +1,6 @@ +let component = world.get_type_by_name.call("TestComponent"); +let entity = world._get_entity_with_test_component.call("TestComponent"); +let retrieved = world.get_component.call(entity, component); + +assert(type_of(retrieved) != "()", "Component was not found"); +assert(retrieved.strings[0] == "Initial", "Component data was not retrieved correctly, retrieved.strings[0] was: " + retrieved.strings[0]); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/empty_entity_component_with_component_data.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/empty_entity_component_with_component_data.rhai new file mode 100644 index 0000000000..42c4abc0f1 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/empty_entity_component_with_component_data.rhai @@ -0,0 +1,5 @@ +let component = world.get_type_by_name.call("TestComponent"); +let entity = world.spawn_.call(); +let retrieved = world.get_component.call(entity, component); + +assert(type_of(retrieved) == "()", "Component found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/has_parent_returns_it.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/has_parent_returns_it.rhai new file mode 100644 index 0000000000..a992e6068c --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/has_parent_returns_it.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); + +world.push_children.call(entity, [child]); + +let parent = world.get_parent.call(child); + +assert(type_of(parent) != "()", "Expected a parent"); +assert(parent.index.call() == entity.index.call(), "Parent is the wrong entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/invalid_entity_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/invalid_entity_errors.rhai new file mode 100644 index 0000000000..b25d0981df --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/invalid_entity_errors.rhai @@ -0,0 +1,4 @@ + +assert_throws(||{ + world.get_parent.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/no_parent_returns_nil.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/no_parent_returns_nil.rhai new file mode 100644 index 0000000000..d4abc7d2d5 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/no_parent_returns_nil.rhai @@ -0,0 +1,4 @@ +let entity = world.spawn_.call(); +let parent = world.get_parent.call(entity); + +assert(type_of(parent) == "()", "Expected no parents"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/missing_resource_returns_nil.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/missing_resource_returns_nil.rhai new file mode 100644 index 0000000000..e127d1a958 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/missing_resource_returns_nil.rhai @@ -0,0 +1,2 @@ +let type = world._get_mock_resource_type.call(); +assert(type_of(world.get_resource.call(type)) == "()", "Resource should not exist"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/no_resource_data_returns_resource.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/no_resource_data_returns_resource.rhai new file mode 100644 index 0000000000..3c172e5ac3 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/no_resource_data_returns_resource.rhai @@ -0,0 +1,5 @@ +let resource = world.get_type_by_name.call("ResourceWithDefault"); + +let retrieved = world.get_resource.call(resource); +assert(type_of(retrieved) != "()", "Resource should exist"); +assert(retrieved["_0"] == "Initial Value", "Resource should have default value but got: " + retrieved["_0"]); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/with_resource_data_returns_resource.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/with_resource_data_returns_resource.rhai new file mode 100644 index 0000000000..c0f657d6b8 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/with_resource_data_returns_resource.rhai @@ -0,0 +1,5 @@ +let resource = world.get_type_by_name.call("TestResource"); + +let retrieved = world.get_resource.call(resource); +assert(type_of(retrieved) != "()", "Resource should exist"); +assert(retrieved.bytes[1] == 1, "Resource should have default value but got resource with #retrieved.bytes[1]: " + retrieved.bytes[1]); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/missing_type_returns_nothing.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/missing_type_returns_nothing.rhai index 22003426e8..2ac4944019 100644 --- a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/missing_type_returns_nothing.rhai +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/missing_type_returns_nothing.rhai @@ -1,4 +1,3 @@ -let my_fn = world["get_type_by_name"]; -print(my_fn); -let type = my_fn("UnregisteredType"); +let type = world.get_type_by_name.call("MissingType"); + assert(type == (), "Unregistered type was found"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/registered_type_returns_correct_type.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/registered_type_returns_correct_type.rhai index 4634858d09..00f5379b8a 100644 --- a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/registered_type_returns_correct_type.rhai +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/registered_type_returns_correct_type.rhai @@ -1,15 +1,17 @@ -let type = world.get_type_by_name("TestComponent"); +let type = world.get_type_by_name.call("TestComponent"); -local expected = { - type_name = 'test_utils::test_data::TestComponent', - short_name = 'TestComponent', -} +assert(type_of(type) != "()", "Registered type was not found"); -local received = { - type_name = type:type_name(), - short_name = type:short_name(), -} +let expected_type_name = "test_utils::test_data::TestComponent"; +let expected_short_name = "TestComponent"; -assert(type != (), 'Type not found') -assert(received.type_name == expected.type_name, 'type_name mismatch, expected: ' .. expected.type_name .. ', got: ' .. received.type_name) -assert(received.short_name == expected.short_name, 'short_name mismatch, expected: ' .. expected.short_name .. ', got: ' .. received.short_name) +let received_type_name = type.type_name.call(type); +let received_short_name = type.short_name.call(type); + + +// assert(type != (), 'Type not found') +// assert(received.type_name == expected.type_name, 'type_name mismatch, expected: ' .. expected.type_name .. ', got: ' .. received.type_name) +// assert(received.short_name == expected.short_name, 'short_name mismatch, expected: ' .. expected.short_name .. ', got: ' .. received.short_name) + +assert(received_type_name == expected_type_name, "type_name mismatch, expected: " + expected_type_name + ", got: " + received_type_name); +assert(received_short_name == expected_short_name, "short_name mismatch, expected: " + expected_short_name + ", got: " + received_short_name); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/globals/dynamic_globals_are_in_scope.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/globals/dynamic_globals_are_in_scope.rhai new file mode 100644 index 0000000000..32455f2363 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/globals/dynamic_globals_are_in_scope.rhai @@ -0,0 +1 @@ +assert(global_hello_world.call() == "hi!", "global_hello_world() == 'hi!'") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/empty_entity_mock_component_is_false.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/empty_entity_mock_component_is_false.rhai new file mode 100644 index 0000000000..65aaec5348 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/empty_entity_mock_component_is_false.rhai @@ -0,0 +1,4 @@ +let entity = world.spawn_.call(); +let type = world._get_mock_component_type.call(); + +assert(world.has_component.call(entity, type) == false, "Entity should not have component"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/no_component_data.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/no_component_data.rhai new file mode 100644 index 0000000000..e5b998e6b6 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/no_component_data.rhai @@ -0,0 +1,3 @@ +let entity = world._get_entity_with_test_component.call("CompWithDefault"); +let component = world.get_type_by_name.call("CompWithDefault"); +assert(world.has_component.call(entity, component) == true, "Component was not found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/with_component_data.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/with_component_data.rhai new file mode 100644 index 0000000000..78240ee203 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/with_component_data.rhai @@ -0,0 +1,3 @@ +let entity = world._get_entity_with_test_component.call("TestComponent"); +let component = world.get_type_by_name.call("TestComponent"); +assert(world.has_component.call(entity, component) == true, "Component was not found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_no_resource_data.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_no_resource_data.rhai new file mode 100644 index 0000000000..20c34cb167 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_no_resource_data.rhai @@ -0,0 +1,2 @@ +let component = world.get_type_by_name.call("ResourceWithDefault"); +assert(world.has_resource.call(component) == true, "Resource was not found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_with_resource_data.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_with_resource_data.rhai new file mode 100644 index 0000000000..28a9bf59b1 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_with_resource_data.rhai @@ -0,0 +1,2 @@ +let component = world.get_type_by_name.call("TestResource"); +assert(world.has_resource.call(component) == true, "Resource was not found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/missing_resource_mock_resource_is_false.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/missing_resource_mock_resource_is_false.rhai new file mode 100644 index 0000000000..191aca8f15 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/missing_resource_mock_resource_is_false.rhai @@ -0,0 +1,2 @@ +let type = world._get_mock_resource_type.call(); +assert(world.has_resource.call(type) == false, "Resource should not exist"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert/vec.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert/vec.rhai new file mode 100644 index 0000000000..d5f250418a --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +res.vec_usize.insert.call(2, 42); + +assert(res.vec_usize[2] == 42, "insert did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adding_empty_list_does_nothing.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adding_empty_list_does_nothing.rhai new file mode 100644 index 0000000000..85825b47ac --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adding_empty_list_does_nothing.rhai @@ -0,0 +1,5 @@ +let entity = world.spawn_.call(); + +world.insert_children.call(entity,0 ,[]); + +assert(world.get_children.call(entity).len == 0); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_at_correct_index.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_at_correct_index.rhai new file mode 100644 index 0000000000..6c922beb71 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_at_correct_index.rhai @@ -0,0 +1,8 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +let child2 = world.spawn_.call(); + +world.insert_children.call(entity, 0, [child]); +world.insert_children.call(entity, 0, [child2]); + +assert(world.get_children.call(entity)[0].index.call() == child2.index.call()); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_to_existing_enttity.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_to_existing_enttity.rhai new file mode 100644 index 0000000000..5c5a542ad9 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_to_existing_enttity.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +let child2 = world.spawn_.call(); +world.insert_children.call(entity, 0, [child, child2]); + +assert(world.get_children.call(entity).len == 2); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/invalid_entity_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/invalid_entity_errors.rhai new file mode 100644 index 0000000000..2b960661ec --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/invalid_entity_errors.rhai @@ -0,0 +1,10 @@ +let fake_entity = Entity.from_raw.call(9999); + +assert_throws(||{ + world.insert_children.call(fake_entity, 0, [fake_entity]); +}, "Missing or invalid entity"); + +let entity = world.spawn_.call(); +assert_throws(||{ + world.insert_children.call(entity, 0, [fake_entity]); +}, "Missing or invalid entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_no_default_or_from_world_data_inserts.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_no_default_or_from_world_data_inserts.rhai new file mode 100644 index 0000000000..fcf3aa2747 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_no_default_or_from_world_data_inserts.rhai @@ -0,0 +1,8 @@ +let entity = world.spawn_.call(); +let type = world.get_type_by_name.call("TestComponent"); +let entity_with_component = world._get_entity_with_test_component.call("TestComponent"); +let existing_component = world.get_component.call(entity_with_component, type); + +assert(world.has_component.call(entity, type) == false, "Expected entity to not have component before adding, test invalid"); +world.insert_component.call(entity, type, existing_component); +assert(world.has_component.call(entity, type) == true, "Expected entity to have component after adding"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_with_default_no_component_data_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_with_default_no_component_data_errors.rhai new file mode 100644 index 0000000000..bf2997a115 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_with_default_no_component_data_errors.rhai @@ -0,0 +1,8 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithDefault"); +let entity_with_component = world._get_entity_with_test_component.call("CompWithDefault"); +let existing_component = world.get_component.call(entity_with_component, _type); + +assert_throws(||{ + world.insert_component.call(entity, _type, existing_component); +}, "Missing type data ReflectComponent for type: .*CompWithDefault.*"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/iter/vec.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/iter/vec.rhai new file mode 100644 index 0000000000..accbcd587a --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/iter/vec.rhai @@ -0,0 +1,15 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +let iterated_vals = []; + +for v in res.vec_usize { + iterated_vals.push(v); +} + +assert(iterated_vals.len == 5, "Length is not 5"); +assert(iterated_vals[0] == 1, "First value is not 1"); +assert(iterated_vals[1] == 2, "Second value is not 2"); +assert(iterated_vals[2] == 3, "Third value is not 3"); +assert(iterated_vals[3] == 4, "Fourth value is not 4"); +assert(iterated_vals[4] == 5, "Fifth value is not 5"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/len/vec.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/len/vec.rhai new file mode 100644 index 0000000000..b77fecc68c --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/len/vec.rhai @@ -0,0 +1,4 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +assert(res.vec_usize.len.call() == 5, "Length is not 5"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/mod/vec3.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/mod/vec3.rhai new file mode 100644 index 0000000000..098425dcfe --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/mod/vec3.rhai @@ -0,0 +1,10 @@ +let a = Vec3.new_.call(2.0, 5.0, 6.0); +let b = Vec3.new_.call(1.0, 2.0, 3.0); + +assert((a % 2).x == 0.0, "Modulus did not work"); +assert((a % 2).y == 1.0, "Modulus did not work"); +assert((a % 2).z == 0.0, "Modulus did not work"); + +assert((a % b).x == 0.0, "Modulus did not work"); +assert((a % b).y == 1.0, "Modulus did not work"); +assert((a % b).z == 0.0, "Modulus did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/mul/vec3.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/mul/vec3.rhai new file mode 100644 index 0000000000..5b62477cd7 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/mul/vec3.rhai @@ -0,0 +1,10 @@ +let a = Vec3.new_.call(1.0, 2.0, 3.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + +assert((a * 2).x == 2.0, "Multiplication did not work"); +assert((a * 2).y == 4.0, "Multiplication did not work"); +assert((a * 2).z == 6.0, "Multiplication did not work"); + +assert((a * b).x == 4.0, "Multiplication did not work"); +assert((a * b).y == 10.0, "Multiplication did not work"); +assert((a * b).z == 18.0, "Multiplication did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/pop/vec.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/pop/vec.rhai new file mode 100644 index 0000000000..bf94edb9d2 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/pop/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +let popped = res.vec_usize.pop.call(); + +assert(popped == 5, "Pop did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/push/vec.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/push/vec.rhai new file mode 100644 index 0000000000..f6c6c37610 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/push/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +res.vec_usize.push.call(42); + +assert(res.vec_usize[5] == 42, "Push did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adding_empty_list_does_nothing.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adding_empty_list_does_nothing.rhai new file mode 100644 index 0000000000..e4d2905284 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adding_empty_list_does_nothing.rhai @@ -0,0 +1,5 @@ +let entity = world.spawn_.call(); + +world.push_children.call(entity, []); + +assert(world.get_children.call(entity).len == 0); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adds_children_to_existing_enttity.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adds_children_to_existing_enttity.rhai new file mode 100644 index 0000000000..7c77549edd --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adds_children_to_existing_enttity.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +let child2 = world.spawn_.call(); + +world.push_children.call(entity, [child, child2]); + +assert(world.get_children.call(entity).len == 2); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/invalid_entity_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/invalid_entity_errors.rhai new file mode 100644 index 0000000000..15d6ca9501 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/invalid_entity_errors.rhai @@ -0,0 +1,10 @@ +let fake_entity = Entity.from_raw.call(9999); + +assert_throws(||{ + world.push_children.call(fake_entity, [fake_entity]); +}, "Missing or invalid entity"); + +let entity = world.spawn_.call(); +assert_throws(||{ + world.push_children.call(entity, [fake_entity]); +}, "Missing or invalid entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/query/empty_query_returns_nothing.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/query/empty_query_returns_nothing.rhai new file mode 100644 index 0000000000..ba9293d2d0 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/query/empty_query_returns_nothing.rhai @@ -0,0 +1,5 @@ +let component_a = world.get_type_by_name.call("TestComponent"); + +for (result, i) in world.query.call().component.call(component_a).without.call(component_a).build.call() { + assert(false, "This should not be reached"); +} \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/query/query_returns_all_entities_matching.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/query/query_returns_all_entities_matching.rhai new file mode 100644 index 0000000000..232f5e71d2 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/query/query_returns_all_entities_matching.rhai @@ -0,0 +1,31 @@ +let entity_a = world.spawn_.call(); +let entity_b = world.spawn_.call(); +let entity_c = world.spawn_.call(); +let entity_d = world._get_entity_with_test_component.call("CompWithFromWorldAndComponentData"); + +let component_with = world.get_type_by_name.call("CompWithFromWorldAndComponentData"); +let component_without = world.get_type_by_name.call("CompWithDefaultAndComponentData"); + +world.add_default_component.call(entity_a, component_with); +world.add_default_component.call(entity_b, component_with); +world.add_default_component.call(entity_c, component_with); + +world.add_default_component.call(entity_b, component_without); + +let found_entities = []; +for (result, i) in world.query.call().component.call(component_with).without.call(component_without).build.call() { + found_entities.push(result.entity.call()); +} + +assert(found_entities.len == 3, "Expected 3 entities, got " + found_entities.len); + +let expected_entities = [ + entity_d, + entity_a, + entity_c, +]; + +for (entity, i) in found_entities { + assert(entity.index.call() == expected_entities[i].index.call(), "Expected entity " + expected_entities[i].index.call() + " but got " + entity.index.call()); +} + diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove/vec.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove/vec.rhai new file mode 100644 index 0000000000..617cc459a3 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +let removed = res.vec_usize.remove.call(4); + +assert(removed == 5, "Remove did not work"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/empty_entity_does_nothing.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/empty_entity_does_nothing.rhai new file mode 100644 index 0000000000..c93a8bde4f --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/empty_entity_does_nothing.rhai @@ -0,0 +1,5 @@ +let entity = world.spawn_.call(); +let type = world.get_type_by_name.call("TestComponent"); + +world.remove_component.call(entity, type); +world.remove_component.call(entity, type); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/no_component_data_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/no_component_data_errors.rhai new file mode 100644 index 0000000000..07f8f714a8 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/no_component_data_errors.rhai @@ -0,0 +1,7 @@ + +let entity = world._get_entity_with_test_component.call("CompWithDefault"); +let component = world.get_type_by_name.call("CompWithDefault"); + +assert_throws(||{ + world.remove_component.call(entity, component); +}, "Missing type data ReflectComponent for type: .*CompWithDefault.*") diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/with_component_data_removes_component.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/with_component_data_removes_component.rhai new file mode 100644 index 0000000000..ba8e3967fa --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/with_component_data_removes_component.rhai @@ -0,0 +1,5 @@ + +let entity = world._get_entity_with_test_component.call("TestComponent"); +let component = world.get_type_by_name.call("TestComponent"); +world.remove_component.call(entity, component); +assert(world.has_component.call(entity, component) == false, "Component was not removed"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/missing_resource_with_resource_data_does_nothing.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/missing_resource_with_resource_data_does_nothing.rhai new file mode 100644 index 0000000000..1ddf844425 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/missing_resource_with_resource_data_does_nothing.rhai @@ -0,0 +1,4 @@ +let type = world.get_type_by_name.call("TestResource"); + +world.remove_resource.call(type); +world.remove_resource.call(type); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/no_resource_data_errors.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/no_resource_data_errors.rhai new file mode 100644 index 0000000000..c69d9dc6d6 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/no_resource_data_errors.rhai @@ -0,0 +1,6 @@ + +let type = world._get_mock_resource_type.call(); + +assert_throws(||{ + world.remove_resource.call(type) +}, "Missing type data ReflectResource for type: Unregistered.TypeId.*"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/with_resource_data_removes_resource.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/with_resource_data_removes_resource.rhai new file mode 100644 index 0000000000..b8bb4b17a4 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/with_resource_data_removes_resource.rhai @@ -0,0 +1,4 @@ + +let type = world.get_type_by_name.call("TestResource"); +world.remove_resource.call(type); +assert(world.has_resource.call(type) == false, "Resource was not removed"); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/sub/vec3.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/sub/vec3.rhai new file mode 100644 index 0000000000..e65d428212 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/sub/vec3.rhai @@ -0,0 +1,11 @@ +let a = Vec3.new_.call(1.0, 2.0, 3.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + + +assert((a - 1).x == 0.0, "Subtraction did not work"); +assert((a - 1).y == 1.0, "Subtraction did not work"); +assert((a - 1).z == 2.0, "Subtraction did not work"); + +assert((a - b).x == -3.0, "Subtraction did not work"); +assert((a - b).y == -3.0, "Subtraction did not work"); +assert((a - b).z == -3.0, "Subtraction did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/unm/vec3.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/unm/vec3.rhai new file mode 100644 index 0000000000..1104596bf1 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/unm/vec3.rhai @@ -0,0 +1,5 @@ +let a = Vec3.new_.call(2.0, -4.0, 6.0); + +assert((-a).x == -2.0, "Negation did not work"); +assert((-a).y == 4.0, "Negation did not work"); +assert((-a).z == -6.0, "Negation did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs b/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs index 48f81176fe..afe157b0de 100644 --- a/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs +++ b/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs @@ -1,7 +1,12 @@ -use bevy_mod_scripting_core::AddRuntimeInitializer; +#![allow(clippy::unwrap_used, clippy::todo, clippy::expect_used, clippy::panic)] +use bevy_mod_scripting_core::{ + bindings::{pretty_print::DisplayWithWorld, ThreadWorldContainer, WorldContainer}, + error::ScriptError, + AddRuntimeInitializer, +}; use bevy_mod_scripting_rhai::RhaiScriptingPlugin; use libtest_mimic::{Arguments, Failed, Trial}; -use rhai::Dynamic; +use rhai::{Dynamic, EvalAltResult, FnPtr, NativeCallContext}; use script_integration_test_harness::execute_integration_test; use std::{ fs::{self, DirEntry}, @@ -32,9 +37,7 @@ impl Test { panic!("Assertion failed. {}", b); } }); - }); - app.add_runtime_initializer::(|runtime| { runtime.register_fn("assert", |a: Dynamic| { if !a.is::() { panic!("Expected a boolean value, but got {:?}", a); @@ -43,42 +46,30 @@ impl Test { panic!("Assertion failed"); } }); + runtime.register_fn("assert_throws", |ctxt: NativeCallContext, fn_: FnPtr, regex: String| { + let world = ThreadWorldContainer.try_get_world()?; + let args: [Dynamic;0] = []; + let result = fn_.call_within_context::<()>(&ctxt, args); + match result { + Ok(_) => panic!("Expected function to throw error, but it did not."), + Err(e) => { + let e = ScriptError::from_rhai_error(*e); + let err = e.display_with_world(world); + let regex = regex::Regex::new(®ex).unwrap(); + if regex.is_match(&err) { + Ok::<(), Box>(()) + } else { + panic!( + "Expected error message to match the regex: \n{}\n\nBut got:\n{}", + regex.as_str(), + err + ) + } + }, + } + }); + Ok(()) }); - - // app.add_context_initializer::(|_,ctxt: &mut Lua| { - // let globals = ctxt.globals(); - // globals.set( - // "assert_throws", - // ctxt.create_function(|lua, (f, reg): (Function, String)| { - // let world = lua.get_world(); - // let result = f.call::<()>(MultiValue::new()); - // let err = match result { - // Ok(_) => { - // return Err(mlua::Error::external( - // "Expected function to throw error, but it did not.", - // )) - // } - // Err(e) => - // ScriptError::from_mlua_error(e).display_with_world(world) - // , - // }; - - // let regex = regex::Regex::new(®).unwrap(); - // if regex.is_match(&err) { - // Ok(()) - // } else { - // Err(mlua::Error::external( - // format!( - // "Expected error message to match the regex: \n{}\n\nBut got:\n{}", - // regex.as_str(), - // err - // ), - // )) - // } - // })?, - // )?; - // Ok(()) - // }); }, self.path.as_os_str().to_str().unwrap(), self.code.as_bytes(), @@ -122,7 +113,9 @@ fn discover_all_tests() -> Vec { visit_dirs(&test_root, &mut |entry| { let path = entry.path(); let code = fs::read_to_string(&path).unwrap(); - test_files.push(Test { code, path }); + if path.extension().unwrap() == "rhai" { + test_files.push(Test { code, path }); + } }) .unwrap(); diff --git a/crates/script_integration_test_harness/src/lib.rs b/crates/script_integration_test_harness/src/lib.rs index b9dc3310d4..c25a5820a5 100644 --- a/crates/script_integration_test_harness/src/lib.rs +++ b/crates/script_integration_test_harness/src/lib.rs @@ -60,7 +60,7 @@ pub fn execute_integration_test< .initializers .iter() .for_each(|initializer| { - (initializer)(&mut runtime); + (initializer)(&mut runtime).unwrap(); }); // load the context as normal diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 3980e77589..d6477d06d0 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -45,9 +45,8 @@ enum Feature { MluaSerialize, MluaMacros, MluaAsync, - // Rhai // Rhai, - + Rhai, // Rune // Rune, } @@ -55,7 +54,7 @@ enum Feature { #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, strum::EnumIter)] enum FeatureGroup { LuaExclusive, - // RhaiExclusive, + RhaiExclusive, // RuneExclusive, ForExternalCrate, BMSFeature, @@ -65,7 +64,7 @@ impl FeatureGroup { fn default_feature(self) -> Feature { match self { FeatureGroup::LuaExclusive => Feature::Lua54, - // FeatureGroup::RhaiExclusive => Feature::Rhai, + FeatureGroup::RhaiExclusive => Feature::Rhai, // FeatureGroup::RuneExclusive => Feature::Rune, _ => panic!("No default feature for non-exclusive group"), } @@ -74,7 +73,7 @@ impl FeatureGroup { fn is_exclusive(self) -> bool { matches!( self, - FeatureGroup::LuaExclusive // | FeatureGroup::RhaiExclusive | FeatureGroup::RuneExclusive + FeatureGroup::LuaExclusive | FeatureGroup::RhaiExclusive // | FeatureGroup::RuneExclusive ) } } @@ -93,7 +92,7 @@ impl IntoFeatureGroup for Feature { | Feature::Luajit | Feature::Luajit52 | Feature::Luau => FeatureGroup::LuaExclusive, - // Feature::Rhai => FeatureGroup::RhaiExclusive, + Feature::Rhai => FeatureGroup::RhaiExclusive, // Feature::Rune => FeatureGroup::RuneExclusive, Feature::MluaAsync | Feature::MluaMacros @@ -139,16 +138,6 @@ impl Features { ) } - fn non_exclusive_features() -> Self { - Self( - ::VARIANTS - .iter() - .filter(|f| !f.to_feature_group().is_exclusive()) - .cloned() - .collect(), - ) - } - fn to_cargo_args(&self) -> Vec { if self.0.is_empty() { vec![] @@ -1251,26 +1240,7 @@ impl Xtasks { }) } - // also run a all features + each exclusive feature by itself - for feature in available_features - .0 - .iter() - .filter(|f| f.to_feature_group().is_exclusive()) - { - // run with all features - let mut features = Features::non_exclusive_features(); - features.0.insert(*feature); - - // don't include if we already ran this combination - if powersets.iter().any(|f| f == &features) { - continue; - } - - output.push(App { - global_args: default_args.clone().with_features(features), - subcmd: Xtasks::Build, - }); - } + log::info!("Powerset command combinations: {:?}", output); // next run a full lint check with all features output.push(App { diff --git a/examples/game_of_life.rs b/examples/game_of_life.rs index 2827958196..c8dde69bc3 100644 --- a/examples/game_of_life.rs +++ b/examples/game_of_life.rs @@ -15,14 +15,17 @@ use bevy_console::{make_layer, AddConsoleCommand, ConsoleCommand, ConsoleOpen, C use bevy_mod_scripting::ScriptFunctionsPlugin; use bevy_mod_scripting_core::{ asset::ScriptAsset, - bindings::{function::namespace::NamespaceBuilder, script_value::ScriptValue}, + bindings::{ + function::namespace::{GlobalNamespace, NamespaceBuilder}, + script_value::ScriptValue, + }, callback_labels, event::ScriptCallbackEvent, handler::event_handler, script::ScriptComponent, }; use bevy_mod_scripting_lua::LuaScriptingPlugin; -// use bevy_mod_scripting_rhai::RhaiScriptingPlugin; +use bevy_mod_scripting_rhai::RhaiScriptingPlugin; use clap::Parser; // CONSOLE SETUP @@ -98,7 +101,7 @@ fn game_of_life_app(app: &mut App) -> &mut App { .add_plugins(( // for scripting LuaScriptingPlugin::default(), - // RhaiScriptingPlugin::default(), + RhaiScriptingPlugin::default(), ScriptFunctionsPlugin, )) .register_type::() @@ -114,9 +117,9 @@ fn game_of_life_app(app: &mut App) -> &mut App { send_on_update.after(update_rendered_state), ( event_handler::, - // event_handler::, + event_handler::, event_handler::, - // event_handler::, + event_handler::, ) .after(send_on_update), ), @@ -168,9 +171,11 @@ pub fn load_script_assets( pub fn register_script_functions(app: &mut App) -> &mut App { let world = app.world_mut(); - NamespaceBuilder::::new_unregistered(world).register("info", |s: String| { - bevy::log::info!(s); - }); + NamespaceBuilder::::new_unregistered(world) + .register("info", |s: String| { + bevy::log::info!(s); + }) + .register("rand", rand::random::); app } @@ -274,10 +279,7 @@ callback_labels!( /// Sends events allowing scripts to drive update logic pub fn send_on_update(mut events: EventWriter) { - events.send(ScriptCallbackEvent::new_for_all( - OnUpdate, - vec![ScriptValue::Unit], - )); + events.send(ScriptCallbackEvent::new_for_all(OnUpdate, vec![])); } pub fn send_on_click( diff --git a/src/lib.rs b/src/lib.rs index 2d764464de..be3001e6e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,10 +9,10 @@ pub mod lua { pub use bevy_mod_scripting_lua::*; } -// #[cfg(feature = "rhai")] -// pub mod rhai { -// pub use bevy_mod_scripting_rhai::*; -// } +#[cfg(feature = "rhai")] +pub mod rhai { + pub use bevy_mod_scripting_rhai::*; +} // #[cfg(feature = "rune")] // pub mod rune { diff --git a/wsl.nu b/wsl.nu new file mode 100644 index 0000000000..4c7e5e2f13 --- /dev/null +++ b/wsl.nu @@ -0,0 +1,3 @@ +# updating to bevy 0.14 caused issues with WSL for me, these vars help, run source wsl.sh before other cargo commands and it might work, you might need to install mesa/vulkan drivers +$env.WGPU_BACKEND = "vulkan" +$env.WINIT_UNIX_BACKEND = "x11" \ No newline at end of file