diff --git a/assets/tests/push_children/adding_empty_list_does_nothing.lua b/assets/tests/push_children/adding_empty_list_does_nothing.lua index b6a58402ae..d94d42f575 100644 --- a/assets/tests/push_children/adding_empty_list_does_nothing.lua +++ b/assets/tests/push_children/adding_empty_list_does_nothing.lua @@ -2,4 +2,4 @@ local entity = world.spawn() world.push_children(entity, {}) -assert(#world.get_children(entity) == 0) \ No newline at end of file +assert(#world.get_children(entity) == 0) diff --git a/crates/bevy_mod_scripting_asset/Cargo.toml b/crates/bevy_mod_scripting_asset/Cargo.toml index b07e14b202..6c39b6753e 100644 --- a/crates/bevy_mod_scripting_asset/Cargo.toml +++ b/crates/bevy_mod_scripting_asset/Cargo.toml @@ -12,6 +12,8 @@ categories.workspace = true readme.workspace = true [dependencies] +bevy_mod_scripting_display = { workspace = true } +bevy_mod_scripting_derive = { workspace = true } bevy_reflect = { workspace = true } bevy_asset = { workspace = true } bevy_log = { workspace = true } diff --git a/crates/bevy_mod_scripting_asset/src/language.rs b/crates/bevy_mod_scripting_asset/src/language.rs index 1586f45bf1..863e6a5d24 100644 --- a/crates/bevy_mod_scripting_asset/src/language.rs +++ b/crates/bevy_mod_scripting_asset/src/language.rs @@ -1,10 +1,14 @@ //! Defines supported scripting languages and their file extensions. +use bevy_mod_scripting_derive::DebugWithTypeInfo; use serde::{Deserialize, Serialize}; use std::borrow::Cow; /// Represents a scripting language. Languages which compile into another language should use the target language as their language. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)] +#[derive( + Clone, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, DebugWithTypeInfo, +)] +#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] pub enum Language { /// The Rhai scripting language Rhai, diff --git a/crates/bevy_mod_scripting_bindings/src/error.rs b/crates/bevy_mod_scripting_bindings/src/error.rs index 6249b917b0..254288cafd 100644 --- a/crates/bevy_mod_scripting_bindings/src/error.rs +++ b/crates/bevy_mod_scripting_bindings/src/error.rs @@ -1,9 +1,10 @@ //! Error types for the bindings use crate::{ - Namespace, ReflectBaseType, ReflectReference, access_map::ReflectAccessId, + FunctionCallContext, Namespace, ReflectBaseType, ReflectReference, access_map::ReflectAccessId, script_value::ScriptValue, }; use bevy_ecs::entity::Entity; +use bevy_mod_scripting_asset::Language; use bevy_mod_scripting_derive::DebugWithTypeInfo; use bevy_mod_scripting_display::{ DebugWithTypeInfo, DisplayWithTypeInfo, GetTypeInfo, OrFakeId, PrintReflectAsDebug, @@ -148,6 +149,8 @@ pub enum InteropError { on: Box, /// The error that occurred error: Box, + /// The context at the time of the call + context: Box>, }, /// An error occurred when converting a function argument FunctionArgConversionError { @@ -353,11 +356,13 @@ impl InteropError { function_name: impl Display, on: Namespace, error: InteropError, + context: Option, ) -> Self { Self::FunctionInteropError { function_name: Box::new(function_name.to_string()), on: Box::new(on), error: Box::new(error), + context: Box::new(context), } } @@ -423,11 +428,16 @@ impl InteropError { } /// Creates a new missing function error. - pub fn missing_function(function_name: impl Display, on: Namespace) -> Self { + pub fn missing_function( + function_name: impl Display, + on: Namespace, + context: Option, + ) -> Self { Self::FunctionInteropError { function_name: Box::new(function_name.to_string()), on: Box::new(on), error: Box::new(InteropError::str("Function not found")), + context: Box::new(context), } } } @@ -563,10 +573,14 @@ impl DisplayWithTypeInfo for InteropError { function_name, on, error, + context, } => { write!( f, - "Error in function {} on {}: {}", + "Error {} in function {} on {}: {}", + context + .clone() + .unwrap_or(FunctionCallContext::new(Language::Unknown)), function_name, WithTypeInfo::new_with_opt_info(on, type_info_provider), error diff --git a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs index 65bac5f3a9..f642175e8f 100644 --- a/crates/bevy_mod_scripting_bindings/src/function/script_function.rs +++ b/crates/bevy_mod_scripting_bindings/src/function/script_function.rs @@ -40,16 +40,56 @@ pub trait ScriptFunctionMut<'env, Marker> { /// The caller context when calling a script function. /// Functions can choose to react to caller preferences such as converting 1-indexed numbers to 0-indexed numbers -#[derive(Clone, Debug, Reflect)] +#[derive(Clone, Reflect, DebugWithTypeInfo)] +#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] #[reflect(opaque)] pub struct FunctionCallContext { language: Language, + location_context: Option, +} + +impl std::fmt::Display for FunctionCallContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("in language: ")?; + self.language.fmt(f)?; + if let Some(context) = &self.location_context { + f.write_str(", at line: ")?; + context.line.fmt(f)?; + f.write_str(", at column: ")?; + context.col.fmt(f)?; + } + Ok(()) + } +} + +#[derive(Clone, Reflect, DebugWithTypeInfo)] +#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] +/// Describes a location within a script +pub struct LocationContext { + /// The line number + pub line: u32, + /// The column number + pub col: u32, } impl FunctionCallContext { /// Create a new FunctionCallContext with the given 1-indexing conversion preference pub const fn new(language: Language) -> Self { - Self { language } + Self { + language, + location_context: None, + } + } + + /// Creates a new function call context with location information + pub const fn new_with_location( + language: Language, + location_context: Option, + ) -> Self { + Self { + language, + location_context, + } } /// Tries to access the world, returning an error if the world is not available @@ -68,6 +108,11 @@ impl FunctionCallContext { pub fn language(&self) -> Language { self.language.clone() } + + /// Returns call location inside the script if available + pub fn location(&self) -> Option<&LocationContext> { + self.location_context.as_ref() + } } #[derive(Reflect, Clone, DebugWithTypeInfo)] @@ -158,12 +203,13 @@ impl DynamicScriptFunction { profiling::scope!("Dynamic Call ", self.name().deref()); let args = args.into_iter().collect::>(); // should we be inlining call errors into the return value? - let return_val = (self.func)(context, args); + let return_val = (self.func)(context.clone(), args); match return_val { ScriptValue::Error(e) => Err(InteropError::function_interop_error( self.name(), self.info.namespace, e, + Some(context), )), v => Ok(v), } @@ -195,12 +241,13 @@ impl DynamicScriptFunctionMut { let args = args.into_iter().collect::>(); // should we be inlining call errors into the return value? let mut write = self.func.write(); - let return_val = (write)(context, args); + let return_val = (write)(context.clone(), args); match return_val { ScriptValue::Error(e) => Err(InteropError::function_interop_error( self.name(), self.info.namespace, e, + Some(context), )), v => Ok(v), } @@ -737,6 +784,7 @@ mod test { function_name, on, error, + .. } = gotten { assert_eq!(*function_name, expected_function_name); diff --git a/crates/bevy_mod_scripting_bindings/src/world.rs b/crates/bevy_mod_scripting_bindings/src/world.rs index d52ddd66de..5b98d7eb8d 100644 --- a/crates/bevy_mod_scripting_bindings/src/world.rs +++ b/crates/bevy_mod_scripting_bindings/src/world.rs @@ -619,6 +619,7 @@ impl<'w> WorldAccessGuard<'w> { return Err(InteropError::missing_function( name.to_string(), Namespace::OnType(type_id), + Some(context.clone()), )); } }; @@ -660,6 +661,7 @@ impl WorldAccessGuard<'_> { "field missing and no default provided: '{}'", descriptor.into() )), + None, ) })?; return Ok(default_data.default().into_partial_reflect()); @@ -820,6 +822,7 @@ impl WorldAccessGuard<'_> { "construct", Namespace::OnType(TypeId::of::()), InteropError::str("missing 'variant' field in enum constructor payload"), + None, ) })?; @@ -834,6 +837,7 @@ impl WorldAccessGuard<'_> { variant_name, enum_info.type_path() )), + None, ) })?; 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 67d14f2e3f..e501e47183 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs @@ -325,7 +325,11 @@ impl UserData for LuaReflectReference { let iter_func = world .lookup_function([TypeId::of::()], "iter") .map_err(|f| { - InteropError::missing_function(f, TypeId::of::().into()) + InteropError::missing_function( + f, + TypeId::of::().into(), + Some(LUA_CALLER_CONTEXT), + ) }) .map_err(IntoMluaError::to_lua_error)?; @@ -347,7 +351,11 @@ impl UserData for LuaReflectReference { let func = world .lookup_function([TypeId::of::()], "display") .map_err(|f| { - InteropError::missing_function(f, TypeId::of::().into()) + InteropError::missing_function( + f, + TypeId::of::().into(), + Some(LUA_CALLER_CONTEXT), + ) }) .map_err(IntoMluaError::to_lua_error)?; let out = func @@ -389,10 +397,12 @@ impl UserData for LuaStaticReflectReference { }, Err(key) => key, }; - Err( - InteropError::missing_function(format!("{key:#?}"), type_id.into()) - .into_lua_err(), + Err(InteropError::missing_function( + format!("{key:#?}"), + type_id.into(), + Some(LUA_CALLER_CONTEXT), ) + .into_lua_err()) }, ); } diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs index 52a4f9971e..bd288cc538 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs @@ -5,7 +5,8 @@ use std::{ use bevy_mod_scripting_asset::Language; use bevy_mod_scripting_bindings::{ - error::InteropError, function::script_function::FunctionCallContext, script_value::ScriptValue, + LocationContext, error::InteropError, function::script_function::FunctionCallContext, + script_value::ScriptValue, }; use bevy_platform::collections::HashMap; use mlua::{FromLua, IntoLua, Value, Variadic}; @@ -140,18 +141,32 @@ impl IntoLua for LuaScriptValue { ScriptValue::Reference(r) => LuaReflectReference::from(r).into_lua(lua)?, ScriptValue::Error(script_error) => return Err(mlua::Error::external(script_error)), ScriptValue::Function(function) => lua - .create_function(move |_lua, args: Variadic| { + .create_function(move |lua, args: Variadic| { + let loc = lua.inspect_stack(1).map(|debug| LocationContext { + line: debug.curr_line().try_into().unwrap_or_default(), + col: 0, + }); let out = function - .call(args.into_iter().map(Into::into), LUA_CALLER_CONTEXT) + .call( + args.into_iter().map(Into::into), + FunctionCallContext::new_with_location(Language::Lua, loc), + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue::from(out)) })? .into_lua(lua)?, ScriptValue::FunctionMut(function) => lua - .create_function(move |_lua, args: Variadic| { + .create_function(move |lua, args: Variadic| { + let loc = lua.inspect_stack(0).map(|debug| LocationContext { + line: debug.curr_line() as u32, + col: 0, + }); let out = function - .call(args.into_iter().map(Into::into), LUA_CALLER_CONTEXT) + .call( + args.into_iter().map(Into::into), + FunctionCallContext::new_with_location(Language::Lua, loc), + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue::from(out)) 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 ecf80ce8f4..7337ef7da1 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs @@ -261,7 +261,11 @@ impl IntoIterator for RhaiReflectReference { let iter_func = world .lookup_function([TypeId::of::()], "iter") .map_err(|f| { - InteropError::missing_function(f, TypeId::of::().into()) + InteropError::missing_function( + f, + TypeId::of::().into(), + Some(RHAI_CALLER_CONTEXT), + ) })?; iter_func.call( @@ -562,6 +566,7 @@ impl CustomType for RhaiReflectReference { InteropError::missing_function( f, TypeId::of::().into(), + Some(RHAI_CALLER_CONTEXT), ) })?; @@ -592,6 +597,7 @@ impl CustomType for RhaiReflectReference { InteropError::missing_function( f, TypeId::of::().into(), + Some(RHAI_CALLER_CONTEXT), ) })?; @@ -639,8 +645,12 @@ impl CustomType for RhaiStaticReflectReference { }; Err::<_, Box>( - InteropError::missing_function(format!("{key:#?}"), type_id.into()) - .into_rhai_error(), + InteropError::missing_function( + format!("{key:#?}"), + type_id.into(), + Some(RHAI_CALLER_CONTEXT), + ) + .into_rhai_error(), ) }); }