diff --git a/.github/workflows/bevy_mod_scripting.yml b/.github/workflows/bevy_mod_scripting.yml index 4f036f5437..888a815352 100644 --- a/.github/workflows/bevy_mod_scripting.yml +++ b/.github/workflows/bevy_mod_scripting.yml @@ -43,6 +43,9 @@ jobs: filters: | src: - 'src/**' + - 'crates/**' + - 'examples/**' + - 'assets/**' - 'docs/**' - '.github/workflows/bevy_mod_scripting.yml' diff --git a/crates/bevy_mod_scripting_derive/src/lib.rs b/crates/bevy_mod_scripting_derive/src/lib.rs index d12271279e..24b4cddce3 100644 --- a/crates/bevy_mod_scripting_derive/src/lib.rs +++ b/crates/bevy_mod_scripting_derive/src/lib.rs @@ -12,6 +12,7 @@ use syn::{spanned::Spanned, ImplItemFn, ItemImpl}; /// - `name`: the name to use to suffix the generated function, i.e. `test_fn` will generate `register_test_fn. Defaults to `functions` /// - `remote`: If true the original impl block will be ignored, and only the function registrations will be generated /// - `bms_core_path`: If set the path to override bms imports, normally only used internally +/// - `unregistered`: If set, will use `new_unregistered` instead of `new` for the namespace builder #[proc_macro_attribute] pub fn script_bindings( args: proc_macro::TokenStream, @@ -45,10 +46,15 @@ pub fn script_bindings( let bms_core_path = &args.bms_core_path; let function_name = format_ident!("register_{}", args.name); + let builder_function_name = if args.unregistered { + format_ident!("new_unregistered") + } else { + format_ident!("new") + }; let out = quote_spanned! {impl_span=> fn #function_name(world: &mut bevy::ecs::world::World) { - #bms_core_path::bindings::function::namespace::NamespaceBuilder::<#type_ident_with_generics>::new(world) + #bms_core_path::bindings::function::namespace::NamespaceBuilder::<#type_ident_with_generics>::#builder_function_name(world) #(#function_registrations)*; } @@ -65,6 +71,8 @@ struct Args { pub remote: bool, /// If set the path to override bms imports pub bms_core_path: syn::Path, + /// If true will use `new_unregistered` instead of `new` for the namespace builder + pub unregistered: bool, } impl syn::parse::Parse for Args { @@ -75,6 +83,7 @@ impl syn::parse::Parse for Args { let mut name = syn::Ident::new("functions", Span::call_site()); let mut remote = false; + let mut unregistered = false; let mut bms_core_path = syn::Path::from(syn::Ident::new("bevy_mod_scripting", Span::call_site())); bms_core_path.segments.push(syn::PathSegment { @@ -88,6 +97,9 @@ impl syn::parse::Parse for Args { if path.is_ident("remote") { remote = true; continue; + } else if path.is_ident("unregistered") { + unregistered = true; + continue; } } syn::Meta::NameValue(name_value) => { @@ -107,23 +119,24 @@ impl syn::parse::Parse for Args { } } } - _ => {} + _ => { + unknown_spans.push((pair.span(), "Unsupported meta kind for script_bindings")); + continue; + } } - unknown_spans.push(pair.span()); + unknown_spans.push((pair.span(), "Unknown argument to script_bindings")); } if !unknown_spans.is_empty() { - return Err(syn::Error::new( - unknown_spans[0], - "Unknown argument to script_bindings", - )); + return Err(syn::Error::new(unknown_spans[0].0, unknown_spans[0].1)); } Ok(Self { remote, bms_core_path, name, + unregistered, }) } } @@ -152,7 +165,8 @@ fn process_impl_fn(fun: &ImplItemFn) -> TokenStream { .map(|s| syn::LitStr::new(&s, Span::call_site())) .unwrap_or(syn::LitStr::new("", Span::call_site())); let fun_name = syn::LitStr::new(&fun.sig.ident.to_string(), Span::call_site()); - quote_spanned! {Span::call_site()=> + let fun_span = fun.sig.ident.span(); + quote_spanned! {fun_span=> .register_documented( #fun_name, |#args| #body, diff --git a/crates/bevy_mod_scripting_functions/Cargo.toml b/crates/bevy_mod_scripting_functions/Cargo.toml index 49624cd9d3..5b50abcb0c 100644 --- a/crates/bevy_mod_scripting_functions/Cargo.toml +++ b/crates/bevy_mod_scripting_functions/Cargo.toml @@ -34,6 +34,7 @@ profiling = { workspace = true } uuid = "1.11" smol_str = "0.2.2" bevy_mod_scripting_core = { workspace = true } +bevy_mod_scripting_derive = { workspace = true } [lints] workspace = true diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 81d20abe91..953f279dbb 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -1,16 +1,13 @@ //! Contains functions defined by the [`bevy_mod_scripting_core`] crate -use bevy::{ - prelude::*, - reflect::{func::FunctionRegistrationError, ParsedPath}, -}; +use bevy::{prelude::*, reflect::ParsedPath}; use bevy_mod_scripting_core::*; +use bevy_mod_scripting_derive::script_bindings; use bindings::{ function::{ from::{Ref, Val}, from_ref::FromScriptRef, into_ref::IntoScriptRef, - namespace::NamespaceBuilder, script_function::{FunctionCallContext, ScriptFunctionMut}, }, pretty_print::DisplayWithWorld, @@ -27,488 +24,574 @@ pub fn register_bevy_bindings(app: &mut App) { app.add_plugins(crate::bevy_bindings::LuaBevyScriptingPlugin); } -pub fn register_world_functions(reg: &mut World) -> Result<(), FunctionRegistrationError> { - NamespaceBuilder::::new_unregistered(reg) - .register( - "get_type_by_name", - |ctxt: FunctionCallContext, type_name: String| { - profiling::function_scope!("get_type_by_name"); - profiling::function_scope!("get_type_by_name"); - let world = ctxt.world()?; - let val = world.get_type_by_name(type_name); - - Ok(match val { - Some(registration) => { - let allocator = world.allocator(); - - let registration = match world.get_resource_type(registration)? { - Ok(res) => { - let mut allocator = allocator.write(); - return Ok(Some(ReflectReference::new_allocated( - res, - &mut allocator, - ))); - } - Err(registration) => registration, - }; - - let registration = match world.get_component_type(registration)? { - Ok(comp) => { - let mut allocator = allocator.write(); - return Ok(Some(ReflectReference::new_allocated( - comp, - &mut allocator, - ))); - } - Err(registration) => registration, - }; +#[script_bindings( + remote, + bms_core_path = "bevy_mod_scripting_core", + name = "world_functions", + unregistered +)] +impl World { + fn get_type_by_name(ctxt: FunctionCallContext, type_name: String) -> Option { + profiling::function_scope!("get_type_by_name"); + let world = ctxt.world()?; + let val = world.get_type_by_name(type_name); + + Ok(match val { + Some(registration) => { + let allocator = world.allocator(); + let registration = match world.get_resource_type(registration)? { + Ok(res) => { let mut allocator = allocator.write(); - Some(ReflectReference::new_allocated( - registration, - &mut allocator, - )) + return Ok(Some(ReflectReference::new_allocated(res, &mut allocator))); } - None => None, - }) - }, - ) - .register( - "get_component", - |ctxt: FunctionCallContext, - entity: Val, - registration: Val| { - profiling::function_scope!("get_component"); - let world = ctxt.world()?; - world.get_component(*entity, registration.component_id()) - }, - ) - .register( - "has_component", - |ctxt: FunctionCallContext, - entity: Val, - registration: Val| { - profiling::function_scope!("has_component"); - let world = ctxt.world()?; - world.has_component(*entity, registration.component_id()) - }, - ) - .register( - "remove_component", - |ctxt: FunctionCallContext, e: Val, r: Val| { - profiling::function_scope!("remove_component"); - let world = ctxt.world()?; - world.remove_component(*e, r.clone()) - }, - ) - .register( - "get_resource", - |ctxt: FunctionCallContext, registration: Val| { - profiling::function_scope!("get_resource"); - let world = ctxt.world()?; - world.get_resource(registration.resource_id()) - }, - ) - .register( - "has_resource", - |ctxt: FunctionCallContext, registration: Val| { - profiling::function_scope!("has_resource"); - let world = ctxt.world()?; - world.has_resource(registration.resource_id()) - }, - ) - .register( - "remove_resource", - |ctxt: FunctionCallContext, r: Val| { - profiling::function_scope!("remove_resource"); - let world = ctxt.world()?; - world.remove_resource(r.into_inner()) - }, - ) - .register( - "add_default_component", - |ctxt: FunctionCallContext, e: Val, r: Val| { - profiling::function_scope!("add_default_component"); - let world = ctxt.world()?; - world.add_default_component(*e, r.clone()) - }, - ) - .register("spawn", |ctxt: FunctionCallContext| { - profiling::function_scope!("spawn"); - let world = ctxt.world()?; - Ok(Val(world.spawn()?)) - }) - .register( - "insert_component", - |ctxt: FunctionCallContext, - e: Val, - r: Val, - v: ReflectReference| { - profiling::function_scope!("insert_component"); - let world = ctxt.world()?; - world.insert_component(*e, r.into_inner(), v) - }, - ) - .register( - "insert_children", - |ctxt: FunctionCallContext, e: Val, index: usize, c: Vec>| { - profiling::function_scope!("insert_children"); - let world = ctxt.world()?; - let index = if ctxt.convert_to_0_indexed { - index - 1 - } else { - index + Err(registration) => registration, }; - world.insert_children(*e, index, &c.into_iter().map(|v| *v).collect::>()) - }, - ) - .register( - "push_children", - |ctxt: FunctionCallContext, e: Val, c: Vec>| { - profiling::function_scope!("push_children"); - let world = ctxt.world()?; - world.push_children(*e, &c.into_iter().map(|v| *v).collect::>()) - }, - ) - .register( - "get_children", - |ctxt: FunctionCallContext, e: Val| { - profiling::function_scope!("get_children"); - let world = ctxt.world()?; - let children = world.get_children(*e)?; - Ok(children.into_iter().map(Val).collect::>()) - }, - ) - .register("get_parent", |ctxt: FunctionCallContext, e: Val| { - profiling::function_scope!("get_parent"); - let world = ctxt.world()?; - let parent = world.get_parent(*e)?; - Ok(parent.map(Val)) - }) - .register("despawn", |ctxt: FunctionCallContext, e: Val| { - profiling::function_scope!("despawn"); - let world = ctxt.world()?; - world.despawn(*e) + + let registration = match world.get_component_type(registration)? { + Ok(comp) => { + let mut allocator = allocator.write(); + return Ok(Some(ReflectReference::new_allocated(comp, &mut allocator))); + } + Err(registration) => registration, + }; + + let mut allocator = allocator.write(); + Some(ReflectReference::new_allocated( + registration, + &mut allocator, + )) + } + None => None, }) - .register( - "despawn_descendants", - |ctxt: FunctionCallContext, e: Val| { - profiling::function_scope!("despawn_descendants"); - let world = ctxt.world()?; - world.despawn_descendants(*e) - }, + } + + fn get_component( + ctxt: FunctionCallContext, + entity: Val, + registration: Val, + ) -> Result, InteropError> { + profiling::function_scope!("get_component"); + let world = ctxt.world()?; + let val = world.get_component(*entity, registration.component_id())?; + Ok(val) + } + + fn has_component( + ctxt: FunctionCallContext, + entity: Val, + registration: Val, + ) -> Result { + profiling::function_scope!("has_component"); + let world = ctxt.world()?; + world.has_component(*entity, registration.component_id()) + } + + fn remove_component( + ctxt: FunctionCallContext, + entity: Val, + registration: Val, + ) -> Result<(), InteropError> { + profiling::function_scope!("remove_component"); + let world = ctxt.world()?; + world.remove_component(*entity, registration.clone()) + } + + fn get_resource( + ctxt: FunctionCallContext, + registration: Val, + ) -> Result, InteropError> { + profiling::function_scope!("get_resource"); + let world = ctxt.world()?; + let val = world.get_resource(registration.resource_id())?; + Ok(val) + } + + fn has_resource( + ctxt: FunctionCallContext, + registration: Val, + ) -> Result { + profiling::function_scope!("has_resource"); + let world = ctxt.world()?; + world.has_resource(registration.resource_id()) + } + + fn remove_resource( + ctxt: FunctionCallContext, + registration: Val, + ) -> Result<(), InteropError> { + profiling::function_scope!("remove_resource"); + let world = ctxt.world()?; + world.remove_resource(registration.into_inner()) + } + + fn add_default_component( + ctxt: FunctionCallContext, + entity: Val, + registration: Val, + ) -> Result<(), InteropError> { + profiling::function_scope!("add_default_component"); + let world = ctxt.world()?; + world.add_default_component(*entity, registration.clone()) + } + + fn spawn(ctxt: FunctionCallContext) -> Result, InteropError> { + profiling::function_scope!("spawn"); + let world = ctxt.world()?; + Ok(Val(world.spawn()?)) + } + + fn insert_component( + ctxt: FunctionCallContext, + entity: Val, + registration: Val, + value: ReflectReference, + ) -> Result<(), InteropError> { + profiling::function_scope!("insert_component"); + let world = ctxt.world()?; + world.insert_component(*entity, registration.into_inner(), value) + } + + fn insert_children( + ctxt: FunctionCallContext, + entity: Val, + index: usize, + children: Vec>, + ) -> Result<(), InteropError> { + profiling::function_scope!("insert_children"); + let world = ctxt.world()?; + let index = if ctxt.convert_to_0_indexed { + index - 1 + } else { + index + }; + world.insert_children( + *entity, + index, + &children.into_iter().map(|v| *v).collect::>(), ) - .register( - "despawn_recursive", - |ctxt: FunctionCallContext, e: Val| { - profiling::function_scope!("despawn_recursive"); - let world = ctxt.world()?; - world.despawn_recursive(*e) - }, + } + + fn push_children( + ctxt: FunctionCallContext, + entity: Val, + children: Vec>, + ) -> Result<(), InteropError> { + profiling::function_scope!("push_children"); + let world = ctxt.world()?; + world.push_children( + *entity, + &children.into_iter().map(|v| *v).collect::>(), ) - .register("has_entity", |ctxt: FunctionCallContext, e: Val| { - profiling::function_scope!("has_entity"); - let world = ctxt.world()?; - world.has_entity(*e) - }) - .register("query", || { - profiling::function_scope!("query"); - let query_builder = ScriptQueryBuilder::default(); - Ok(Val(query_builder)) - }) - .register("exit", |ctxt: FunctionCallContext| { - profiling::function_scope!("exit"); - let world = ctxt.world()?; - world.exit() - }); - Ok(()) + } + + fn get_children( + ctxt: FunctionCallContext, + entity: Val, + ) -> Result>, InteropError> { + profiling::function_scope!("get_children"); + let world = ctxt.world()?; + let children = world.get_children(*entity)?; + Ok(children.into_iter().map(Val).collect::>()) + } + + fn get_parent( + ctxt: FunctionCallContext, + e: Val, + ) -> Result>, InteropError> { + profiling::function_scope!("get_parent"); + let world = ctxt.world()?; + let parent = world.get_parent(*e)?; + Ok(parent.map(Val)) + } + + fn despawn(ctxt: FunctionCallContext, e: Val) -> Result<(), InteropError> { + profiling::function_scope!("despawn"); + let world = ctxt.world()?; + world.despawn(*e) + } + + fn despawn_descendants(ctxt: FunctionCallContext, e: Val) -> Result<(), InteropError> { + profiling::function_scope!("despawn_descendants"); + let world = ctxt.world()?; + world.despawn_descendants(*e) + } + + fn despawn_recursive(ctxt: FunctionCallContext, e: Val) -> Result<(), InteropError> { + profiling::function_scope!("despawn_recursive"); + let world = ctxt.world()?; + world.despawn_recursive(*e) + } + + fn has_entity(ctxt: FunctionCallContext, e: Val) -> Result { + profiling::function_scope!("has_entity"); + let world = ctxt.world()?; + world.has_entity(*e) + } + + fn query() -> Result, InteropError> { + profiling::function_scope!("query"); + let query_builder = ScriptQueryBuilder::default(); + Ok(Val(query_builder)) + } + + fn exit(ctxt: FunctionCallContext) -> Result<(), InteropError> { + profiling::function_scope!("exit"); + let world = ctxt.world()?; + world.exit() + } } -pub fn register_reflect_reference_functions( - reg: &mut World, -) -> Result<(), FunctionRegistrationError> { - NamespaceBuilder::::new(reg) - .register( - "display_ref", - |ctxt: FunctionCallContext, s: ReflectReference| { - profiling::function_scope!("display_ref"); - let world = ctxt.world()?; - Ok(s.display_with_world(world)) - }, - ) - .register("display_value", |ctxt: FunctionCallContext, s: ReflectReference| { - profiling::function_scope!("display_value"); - let world = ctxt.world()?; - Ok(s.display_value_with_world(world)) - }) - .register( - "get", - |ctxt: FunctionCallContext, - mut self_: ReflectReference, - key: ScriptValue| { - profiling::function_scope!("get"); - let mut path: ParsedPath = key.try_into()?; - if ctxt.convert_to_0_indexed { - path.convert_to_0_indexed(); - } - self_.index_path(path); - let world = ctxt.world()?; - ReflectReference::into_script_ref(self_, world) - }, - ) - .register( - "set", - |ctxt: FunctionCallContext, - self_: ScriptValue, - key: ScriptValue, - value: ScriptValue| { - profiling::function_scope!("set"); - if let ScriptValue::Reference(mut self_) = self_ { - let world = ctxt.world()?; - let mut path: ParsedPath = key.try_into()?; - if ctxt.convert_to_0_indexed { - path.convert_to_0_indexed(); - } - self_.index_path(path); - let r: ScriptValue = self_ - .with_reflect_mut(world.clone(), |r| { - let target_type_id = r - .get_represented_type_info() - .map(|i| i.type_id()) - .or_fake_id(); - let other = >::from_script_ref( - target_type_id, - value, - world.clone(), - )?; - r.try_apply(other.as_partial_reflect()).map_err(|e| InteropError::external_error(Box::new(e)))?; - Ok::<_, InteropError>(()) - }) - .into(); - return Ok(r); - } - Ok(ScriptValue::Unit) - }, - ) - .register( - "push", - |ctxt: FunctionCallContext, s: ReflectReference, v: ScriptValue| { - profiling::function_scope!("push"); - let world = ctxt.world()?; - let target_type_id = s.element_type_id(world.clone())?.ok_or_else(|| { - InteropError::unsupported_operation( - s.tail_type_id(world.clone()).unwrap_or_default(), - Some(Box::new(v.clone())), - "Could not get element type id. Are you trying to insert elements into a type that's not a list?".to_owned(), - ) - })?; - let other = >::from_script_ref(target_type_id, v, world.clone())?; - s.with_reflect_mut(world, |s| s.try_push_boxed(other))? - }, - ) - .register("pop", |ctxt: FunctionCallContext, s: ReflectReference| { - profiling::function_scope!("pop"); - let world = ctxt.world()?; - let o = s.with_reflect_mut(world.clone(), |s| s.try_pop_boxed())??; - let reference = { - let allocator = world.allocator(); - let mut allocator = allocator.write(); - ReflectReference::new_allocated_boxed_parial_reflect(o, &mut allocator)? - }; +#[script_bindings( + remote, + bms_core_path = "bevy_mod_scripting_core", + name = "reflect_reference_functions", + unregistered +)] +impl ReflectReference { + fn display_ref( + ctxt: FunctionCallContext, + s: ReflectReference, + ) -> Result { + profiling::function_scope!("display_ref"); + let world = ctxt.world()?; + Ok(s.display_with_world(world)) + } - ReflectReference::into_script_ref(reference, world) - }) - .register("insert", |ctxt: FunctionCallContext, s: ReflectReference, k: ScriptValue, v: ScriptValue| { - profiling::function_scope!("insert"); - let world = ctxt.world()?; - let key_type_id = s.key_type_id(world.clone())?.ok_or_else(|| { - InteropError::unsupported_operation( - s.tail_type_id(world.clone()).unwrap_or_default(), - Some(Box::new(k.clone())), - "Could not get key type id. Are you trying to insert elements into a type that's not a map?".to_owned(), - ) - })?; + fn display_value( + ctxt: FunctionCallContext, + s: ReflectReference, + ) -> Result { + profiling::function_scope!("display_value"); + let world = ctxt.world()?; + Ok(s.display_value_with_world(world)) + } - let mut key = >::from_script_ref(key_type_id, k, world.clone())?; + fn get( + ctxt: FunctionCallContext, + mut self_: ReflectReference, + key: ScriptValue, + ) -> Result { + profiling::function_scope!("get"); + let mut path: ParsedPath = key.try_into()?; + if ctxt.convert_to_0_indexed { + path.convert_to_0_indexed(); + } + self_.index_path(path); + let world = ctxt.world()?; + ReflectReference::into_script_ref(self_, world) + } + fn set( + ctxt: FunctionCallContext, + self_: ScriptValue, + key: ScriptValue, + value: ScriptValue, + ) -> Result { + profiling::function_scope!("set"); + if let ScriptValue::Reference(mut self_) = self_ { + let world = ctxt.world()?; + let mut path: ParsedPath = key.try_into()?; if ctxt.convert_to_0_indexed { - key.convert_to_0_indexed_key(); + path.convert_to_0_indexed(); } + self_.index_path(path); + let r: ScriptValue = self_ + .with_reflect_mut(world.clone(), |r| { + let target_type_id = r + .get_represented_type_info() + .map(|i| i.type_id()) + .or_fake_id(); + let other = >::from_script_ref( + target_type_id, + value, + world.clone(), + )?; + r.try_apply(other.as_partial_reflect()) + .map_err(|e| InteropError::external_error(Box::new(e)))?; + Ok::<_, InteropError>(()) + }) + .into(); + return Ok(r); + } + Ok(ScriptValue::Unit) + } - let value_type_id = s.element_type_id(world.clone())?.ok_or_else(|| { - InteropError::unsupported_operation( - s.tail_type_id(world.clone()).unwrap_or_default(), - Some(Box::new(v.clone())), - "Could not get element type id. Are you trying to insert elements into a type that's not a map?".to_owned(), - ) - })?; + fn push( + ctxt: FunctionCallContext, + s: ReflectReference, + v: ScriptValue, + ) -> Result { + profiling::function_scope!("push"); + let world = ctxt.world()?; + let target_type_id = s.element_type_id(world.clone())?.ok_or_else(|| { + InteropError::unsupported_operation( + s.tail_type_id(world.clone()).unwrap_or_default(), + Some(Box::new(v.clone())), + "Could not get element type id. Are you trying to insert elements into a type that's not a list?".to_owned(), + ) + })?; + let other = >::from_script_ref(target_type_id, v, world.clone())?; + s.with_reflect_mut(world, |s| s.try_push_boxed(other))? + } - let value = >::from_script_ref(value_type_id, v, world.clone())?; + fn pop(ctxt: FunctionCallContext, s: ReflectReference) -> Result { + profiling::function_scope!("pop"); + let world = ctxt.world()?; + let o = s.with_reflect_mut(world.clone(), |s| s.try_pop_boxed())??; + let reference = { + let allocator = world.allocator(); + let mut allocator = allocator.write(); + ReflectReference::new_allocated_boxed_parial_reflect(o, &mut allocator)? + }; + + ReflectReference::into_script_ref(reference, world) + } - s.with_reflect_mut(world, |s| s.try_insert_boxed(key, value))? - }) - .register("clear", |ctxt: FunctionCallContext, s: ReflectReference| { - profiling::function_scope!("clear"); - let world = ctxt.world()?; - s.with_reflect_mut(world, |s| s.try_clear())? - }) - .register("len", |ctxt: FunctionCallContext, s: ReflectReference| { - profiling::function_scope!("len"); - let world = ctxt.world()?; - s.len(world) - }) - .register("remove", |ctxt: FunctionCallContext, s: ReflectReference, k: ScriptValue| { - profiling::function_scope!("remove"); - let world = ctxt.world()?; - let key_type_id = s.key_type_id(world.clone())?.ok_or_else(|| { - InteropError::unsupported_operation( - s.tail_type_id(world.clone()).unwrap_or_default(), - Some(Box::new(k.clone())), - "Could not get key type id. Are you trying to remove elements from a type that's not a map?".to_owned(), - ) - })?; + fn insert( + ctxt: FunctionCallContext, + s: ReflectReference, + k: ScriptValue, + v: ScriptValue, + ) -> Result { + profiling::function_scope!("insert"); + let world = ctxt.world()?; + let key_type_id = s.key_type_id(world.clone())?.ok_or_else(|| { + InteropError::unsupported_operation( + s.tail_type_id(world.clone()).unwrap_or_default(), + Some(Box::new(k.clone())), + "Could not get key type id. Are you trying to insert elements into a type that's not a map?".to_owned(), + ) + })?; + + let mut key = >::from_script_ref(key_type_id, k, world.clone())?; + + if ctxt.convert_to_0_indexed { + key.convert_to_0_indexed_key(); + } + + let value_type_id = s.element_type_id(world.clone())?.ok_or_else(|| { + InteropError::unsupported_operation( + s.tail_type_id(world.clone()).unwrap_or_default(), + Some(Box::new(v.clone())), + "Could not get element type id. Are you trying to insert elements into a type that's not a map?".to_owned(), + ) + })?; + + let value = >::from_script_ref(value_type_id, v, world.clone())?; + + s.with_reflect_mut(world, |s| s.try_insert_boxed(key, value))? + } - let mut key = >::from_script_ref(key_type_id, k, world.clone())?; + fn clear(ctxt: FunctionCallContext, s: ReflectReference) -> Result { + profiling::function_scope!("clear"); + let world = ctxt.world()?; + s.with_reflect_mut(world, |s| s.try_clear())? + } - if ctxt.convert_to_0_indexed { - key.convert_to_0_indexed_key(); + fn len(ctxt: FunctionCallContext, s: ReflectReference) -> Result { + profiling::function_scope!("len"); + let world = ctxt.world()?; + s.len(world) + } + + fn remove( + ctxt: FunctionCallContext, + s: ReflectReference, + k: ScriptValue, + ) -> Result { + profiling::function_scope!("remove"); + let world = ctxt.world()?; + let key_type_id = s.key_type_id(world.clone())?.ok_or_else(|| { + InteropError::unsupported_operation( + s.tail_type_id(world.clone()).unwrap_or_default(), + Some(Box::new(k.clone())), + "Could not get key type id. Are you trying to remove elements from a type that's not a map?".to_owned(), + ) + })?; + + let mut key = >::from_script_ref(key_type_id, k, world.clone())?; + + if ctxt.convert_to_0_indexed { + key.convert_to_0_indexed_key(); + } + + let removed = s.with_reflect_mut(world.clone(), |s| s.try_remove_boxed(key))??; + match removed { + Some(removed) => { + let reference = { + let allocator = world.allocator(); + let mut allocator = allocator.write(); + ReflectReference::new_allocated_boxed_parial_reflect(removed, &mut allocator)? + }; + ReflectReference::into_script_ref(reference, world) } + None => Ok(ScriptValue::Unit), + } + } - let removed = s.with_reflect_mut(world.clone(), |s| s.try_remove_boxed(key))??; - match removed { - Some(removed) => { - let reference = { - let allocator = world.allocator(); - let mut allocator = allocator.write(); - ReflectReference::new_allocated_boxed_parial_reflect(removed, &mut allocator)? - }; - ReflectReference::into_script_ref(reference, world) - } - None => Ok(ScriptValue::Unit), + fn iter(ctxt: FunctionCallContext, s: ReflectReference) -> Result { + profiling::function_scope!("iter"); + let world = ctxt.world()?; + let mut len = s.len(world.clone())?.unwrap_or_default(); + let mut infinite_iter = s.into_iter_infinite(); + let iter_function = move || { + // world is not thread safe, we can't capture it in the closure + // or it will also be non-thread safe + let world = ThreadWorldContainer.try_get_world()?; + if len == 0 { + return Ok(ScriptValue::Unit); } - }) - .register("iter", |ctxt: FunctionCallContext, s: ReflectReference| { - profiling::function_scope!("iter"); - let world = ctxt.world()?; - let mut len = s.len(world.clone())?.unwrap_or_default(); - let mut infinite_iter = s.into_iter_infinite(); - let iter_function = move || { - // world is not thread safe, we can't capture it in the closure - // or it will also be non-thread safe - let world = ThreadWorldContainer.try_get_world()?; - if len == 0 { - return Ok(ScriptValue::Unit); - } - - let (next_ref, _) = infinite_iter.next_ref(); - - let converted = ReflectReference::into_script_ref(next_ref, world); - // println!("idx: {idx:?}, converted: {converted:?}"); - len -= 1; - // we stop once the reflection path is invalid - converted - }; - - Ok(iter_function.into_dynamic_script_function_mut()) - }) - .register("functions", |ctxt: FunctionCallContext, s: ReflectReference| { - profiling::function_scope!("functions"); - let world = ctxt.world()?; - let type_id = s.tail_type_id(world.clone())?.or_fake_id(); - let functions = world.get_functions_on_type(type_id) - .into_iter() - .map(|(_,v)| Val::new(v.info)) - .collect::>(); - // convert to info - Ok(functions) - }); - - Ok(()) + + let (next_ref, _) = infinite_iter.next_ref(); + + let converted = ReflectReference::into_script_ref(next_ref, world); + // println!("idx: {idx:?}, converted: {converted:?}"); + len -= 1; + // we stop once the reflection path is invalid + converted + }; + + Ok(iter_function.into_dynamic_script_function_mut()) + } + + fn functions( + ctxt: FunctionCallContext, + s: ReflectReference, + ) -> Result { + profiling::function_scope!("functions"); + let world = ctxt.world()?; + let type_id = s.tail_type_id(world.clone())?.or_fake_id(); + let functions = world + .get_functions_on_type(type_id) + .into_iter() + .map(|(_, v)| Val::new(v.info)) + .collect::>(); + // convert to info + Ok(functions) + } } -pub fn register_script_type_registration_functions( - registry: &mut World, -) -> Result<(), FunctionRegistrationError> { - NamespaceBuilder::::new(registry) - .register("type_name", |s: Ref| s.type_name()) - .register("short_name", |s: Ref| { - s.short_name() - }); - - NamespaceBuilder::::new(registry) - .register("type_name", |s: Ref| { - s.type_registration().type_name() - }) - .register("short_name", |s: Ref| { - s.type_registration().short_name() - }); +#[script_bindings( + remote, + bms_core_path = "bevy_mod_scripting_core", + name = "script_type_registration_functions", + unregistered +)] +impl ScriptTypeRegistration { + fn type_name(s: Ref) -> String { + profiling::function_scope!("type_name"); + s.type_name() + } - NamespaceBuilder::::new(registry) - .register("type_name", |s: Ref| { - s.type_registration().type_name() - }) - .register("short_name", |s: Ref| { - s.type_registration().short_name() - }); + fn short_name(s: Ref) -> String { + profiling::function_scope!("short_name"); + s.short_name() + } +} + +#[script_bindings( + remote, + bms_core_path = "bevy_mod_scripting_core", + name = "script_component_registration_functions", + unregistered +)] +impl ScriptComponentRegistration { + fn type_name(s: Ref) -> String { + profiling::function_scope!("type_name"); + s.type_registration().type_name() + } - Ok(()) + fn short_name(s: Ref) -> String { + profiling::function_scope!("short_name"); + s.type_registration().short_name() + } } -pub fn register_script_query_builder_functions( - registry: &mut World, -) -> Result<(), FunctionRegistrationError> { - NamespaceBuilder::::new(registry) - .register( - "component", - |s: Val, components: Val| { - profiling::function_scope!("component"); - let mut builder = s.into_inner(); - builder.component(components.into_inner()); - Val(builder) - }, - ) - .register( - "with", - |s: Val, with: Val| { - profiling::function_scope!("with"); - let mut builder = s.into_inner(); - builder.with_component(with.into_inner()); - Val(builder) - }, - ) - .register( - "without", - |s: Val, without: Val| { - profiling::function_scope!("without"); - let mut builder = s.into_inner(); - builder.without_component(without.into_inner()); - Val(builder) - }, - ) - .register( - "build", - |ctxt: FunctionCallContext, s: Val| { - profiling::function_scope!("build"); - let world = ctxt.world()?; - let builder = s.into_inner(); - let result = world.query(builder)?; - let result = result.into_iter().map(Val).collect::>(); - Ok(result) - }, - ); - Ok(()) +#[script_bindings( + remote, + bms_core_path = "bevy_mod_scripting_core", + name = "script_resource_registration_functions", + unregistered +)] +impl ScriptResourceRegistration { + fn type_name(s: Ref) -> String { + profiling::function_scope!("type_name"); + s.type_registration().type_name() + } + + fn short_name(s: Ref) -> String { + profiling::function_scope!("short_name"); + s.type_registration().short_name() + } } -pub fn register_script_query_result_functions( - world: &mut World, -) -> Result<(), FunctionRegistrationError> { - NamespaceBuilder::::new(world) - .register("entity", |s: Ref| Val::new(s.entity)) - .register("components", |s: Ref| { - s.components.to_vec() - }); +#[script_bindings( + remote, + bms_core_path = "bevy_mod_scripting_core", + name = "script_query_builder_functions", + unregistered +)] +impl ScriptQueryBuilder { + fn component( + s: Val, + components: Val, + ) -> Val { + profiling::function_scope!("component"); + let mut builder = s.into_inner(); + builder.component(components.into_inner()); + Val(builder) + } - Ok(()) + fn with( + s: Val, + with: Val, + ) -> Val { + profiling::function_scope!("with"); + let mut builder = s.into_inner(); + builder.with_component(with.into_inner()); + Val(builder) + } + + fn without( + s: Val, + without: Val, + ) -> Val { + profiling::function_scope!("without"); + let mut builder = s.into_inner(); + builder.without_component(without.into_inner()); + Val(builder) + } + + fn build( + ctxt: FunctionCallContext, + s: Val, + ) -> Result>, InteropError> { + profiling::function_scope!("build"); + let world = ctxt.world()?; + let builder = s.into_inner(); + let result = world.query(builder)?; + let result = result.into_iter().map(Val).collect::>(); + Ok(result) + } +} + +#[script_bindings( + remote, + bms_core_path = "bevy_mod_scripting_core", + name = "script_query_result_functions", + unregistered +)] +impl ScriptQueryResult { + fn entity(s: Ref) -> Val { + profiling::function_scope!("entity"); + Val::new(s.entity) + } + + fn components(s: Ref) -> Vec> { + profiling::function_scope!("components"); + s.components.to_vec() + } } pub fn register_core_functions(app: &mut App) { @@ -518,30 +601,16 @@ pub fn register_core_functions(app: &mut App) { // perhaps people might want to include some but not all of these #[cfg(feature = "core_functions")] - if let Err(e) = register_world_functions(world) { - bevy::log::error!("Failed to register script world functions: {:?}", e); - } + { + register_world_functions(world); - #[cfg(feature = "core_functions")] - if let Err(e) = register_reflect_reference_functions(world) { - bevy::log::error!("Failed to register reflect reference functions: {:?}", e); - } + register_reflect_reference_functions(world); - #[cfg(feature = "core_functions")] - if let Err(e) = register_script_type_registration_functions(world) { - bevy::log::error!( - "Failed to register script type registration functions: {:?}", - e - ); - } + register_script_type_registration_functions(world); + register_script_component_registration_functions(world); + register_script_resource_registration_functions(world); - #[cfg(feature = "core_functions")] - if let Err(e) = register_script_query_builder_functions(world) { - bevy::log::error!("Failed to register script query builder functions: {:?}", e); - } - - #[cfg(feature = "core_functions")] - if let Err(e) = register_script_query_result_functions(world) { - bevy::log::error!("Failed to register script query result functions: {:?}", e); + register_script_query_builder_functions(world); + register_script_query_result_functions(world); } }