From ba4acba638b3ef44c44de2d745ff2fc2fcba44a6 Mon Sep 17 00:00:00 2001 From: SeanOMik <seanomik@gmail.com> Date: Tue, 7 Jan 2025 19:18:00 -0500 Subject: [PATCH] lua: remove ReflectBranch::as_component_unchecked and improve error handling --- Cargo.lock | 1 + crates/lyra-scripting/src/lib.rs | 12 ---- crates/lyra-scripting/src/lua/ecs/view.rs | 41 ++++++++---- crates/lyra-scripting/src/lua/ecs/view_one.rs | 63 ++++++++++++----- crates/lyra-scripting/src/lua/entity_ref.rs | 39 ++++++----- crates/lyra-scripting/src/lua/proxy.rs | 67 ++++++++++--------- crates/lyra-scripting/src/lua/world.rs | 11 ++- 7 files changed, 139 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56ee634..df77544 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1949,6 +1949,7 @@ name = "lyra-scripting-derive" version = "0.1.0" dependencies = [ "paste", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.77", diff --git a/crates/lyra-scripting/src/lib.rs b/crates/lyra-scripting/src/lib.rs index f6d0a75..442e0f5 100644 --- a/crates/lyra-scripting/src/lib.rs +++ b/crates/lyra-scripting/src/lib.rs @@ -35,18 +35,6 @@ pub enum ReflectBranch { } impl ReflectBranch { - /// Gets self as a [`ReflectedComponent`]. - /// - /// # Panics - /// If `self` is not a variant of [`ReflectBranch::Component`]. - #[deprecated(note = "use ReflectBranch::as_component instead")] - pub fn as_component_unchecked(&self) -> &ReflectedComponent { - match self { - ReflectBranch::Component(c) => c, - _ => panic!("`self` is not an instance of `ReflectBranch::Component`"), - } - } - /// Gets self as a [`ReflectedComponent`]. /// /// Returns `None` if `self` is not a component. diff --git a/crates/lyra-scripting/src/lua/ecs/view.rs b/crates/lyra-scripting/src/lua/ecs/view.rs index bb051ed..242a7ce 100644 --- a/crates/lyra-scripting/src/lua/ecs/view.rs +++ b/crates/lyra-scripting/src/lua/ecs/view.rs @@ -53,7 +53,7 @@ impl ViewQueryItem { } /// Returns `true` if self is a Query. - /// + /// /// If self is a function, it will return true. Else, it checks for a function with the /// name of [`FN_NAME_INTERNAL_ECS_QUERY_RESULT`] on the table or userdata. If the function /// is found, it returns true. @@ -105,7 +105,7 @@ impl mlua::UserData for View { } /// Results of queries in a View. -/// +/// /// Represents the results of multiple queries. #[derive(Debug, Clone)] pub(crate) enum ViewQueryResult { @@ -163,16 +163,31 @@ impl ViewResult { ViewQueryItem::UserData(ud) => { let reflect = ud .call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ()) - .expect("Type does not implement 'reflect_type' properly"); - let refl_comp = reflect.reflect_branch.as_component() - .expect("`self` is not an instance of `ReflectBranch::Component`"); + .map_err(|_| mlua::Error::BadArgument { + to: Some("ViewResult.new".into()), + pos: 2 + idx, + name: Some("query...".into()), + cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage( + format!("userdata does not implement type reflection"), + ))), + })?; + let refl_comp = reflect.reflect_branch.as_component().ok_or_else(|| { + mlua::Error::BadArgument { + to: Some("ViewResult.new".into()), + pos: 2 + idx, + name: Some("query...".into()), + cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage( + format!("userdata is not a Component"), + ))), + } + })?; let dyn_type = QueryDynamicType::from_info(refl_comp.info); view.push(dyn_type); } // functions are queries, the if statement at the start would cause this to // be unreachable. - ViewQueryItem::Function(_) => unreachable!() + ViewQueryItem::Function(_) => unreachable!(), } } @@ -223,19 +238,19 @@ impl ViewResult { let mut index_mod = 0; for (query, i) in &self.queries { let qres = query.get_query_result(self.world.clone(), entity)?; - + match qres { LuaQueryResult::None => return Ok(ViewQueryResult::None), LuaQueryResult::AlwaysNone => return Ok(ViewQueryResult::AlwaysNone), LuaQueryResult::FilterPass => { // do not push a boolean to values, its considered a filter index_mod += 1; - }, + } LuaQueryResult::FilterDeny => return Ok(ViewQueryResult::FilterDeny), LuaQueryResult::Some(value) => { let idx = (*i - index_mod).max(0); query_vals.push((value, idx)); - }, + } } } @@ -268,11 +283,11 @@ impl mlua::UserData for ViewResult { ViewQueryResult::Some(v) => v, ViewQueryResult::AlwaysNone => { return mlua::Value::Nil.into_lua_multi(lua); - }, + } ViewQueryResult::None | ViewQueryResult::FilterDeny => { // try to get it next loop continue; - }, + } }; // insert query values to the result row @@ -309,11 +324,11 @@ impl mlua::UserData for ViewResult { ViewQueryResult::Some(v) => v, ViewQueryResult::AlwaysNone => { return mlua::Value::Nil.into_lua_multi(lua); - }, + } ViewQueryResult::None | ViewQueryResult::FilterDeny => { // try to get it next loop continue; - }, + } }; // insert query values to the result row diff --git a/crates/lyra-scripting/src/lua/ecs/view_one.rs b/crates/lyra-scripting/src/lua/ecs/view_one.rs index b22b259..de6acd6 100644 --- a/crates/lyra-scripting/src/lua/ecs/view_one.rs +++ b/crates/lyra-scripting/src/lua/ecs/view_one.rs @@ -1,12 +1,21 @@ use std::sync::Arc; -use lyra_ecs::{query::dynamic::{DynamicViewOneOwned, QueryDynamicType}, Entity}; +use lyra_ecs::{ + query::dynamic::{DynamicViewOneOwned, QueryDynamicType}, + Entity, +}; use lyra_reflect::TypeRegistry; use mlua::{IntoLua, IntoLuaMulti, ObjectLike}; -use crate::{lua::{ReflectLuaProxy, TypeLookup, WorldError, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptBorrow, ScriptWorldPtr}; +use crate::{ + lua::{ReflectLuaProxy, TypeLookup, WorldError, FN_NAME_INTERNAL_REFLECT_TYPE}, + ScriptBorrow, ScriptWorldPtr, +}; -use super::{query::{LuaQuery, LuaQueryResult}, View, ViewQueryItem, ViewQueryResult}; +use super::{ + query::{LuaQuery, LuaQueryResult}, + View, ViewQueryItem, ViewQueryResult, +}; /// The result of an ecs world View of a single entity. #[derive(Clone)] @@ -69,16 +78,31 @@ impl ViewOneResult { ViewQueryItem::UserData(ud) => { let reflect = ud .call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ()) - .expect("Type does not implement 'reflect_type' properly"); - let refl_comp = reflect.reflect_branch.as_component() - .expect("`self` is not an instance of `ReflectBranch::Component`"); + .ok_or_else(|| mlua::Error::BadArgument { + to: Some("ViewOneResult.new".into()), + pos: 2 + idx, + name: Some("query...".into()), + cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage( + format!("userdata does not implement type reflection"), + ))), + })?; + let refl_comp = reflect.reflect_branch.as_component().ok_or_else(|| { + mlua::Error::BadArgument { + to: Some("ViewOneResult.new".into()), + pos: 2 + idx, + name: Some("query...".into()), + cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage( + format!("userdata is not a Component"), + ))), + } + })?; let dyn_type = QueryDynamicType::from_info(refl_comp.info); view.queries.push(dyn_type); } // functions are queries, the if statement at the start would cause this to // be unreachable. - ViewQueryItem::Function(_) => unreachable!() + ViewQueryItem::Function(_) => unreachable!(), } } @@ -103,19 +127,19 @@ impl ViewOneResult { let mut index_mod = 0; for (query, i) in &self.queries { let qres = query.get_query_result(self.world.clone(), entity)?; - + match qres { LuaQueryResult::None => return Ok(ViewQueryResult::None), LuaQueryResult::AlwaysNone => return Ok(ViewQueryResult::AlwaysNone), LuaQueryResult::FilterPass => { // do not push a boolean to values, its considered a filter index_mod += 1; - }, + } LuaQueryResult::FilterDeny => return Ok(ViewQueryResult::FilterDeny), LuaQueryResult::Some(value) => { let idx = (*i - index_mod).max(0); query_vals.push((value, idx)); - }, + } } } @@ -139,14 +163,19 @@ impl ViewOneResult { let mut vals = vec![]; for d in row.iter() { let id = d.info.type_id().as_rust(); - - let reg_type = reg.get_type(id) + + let reg_type = reg + .get_type(id) .expect("Requested type was not found in TypeRegistry"); - let proxy = reg_type.get_data::<ReflectLuaProxy>() + let proxy = reg_type + .get_data::<ReflectLuaProxy>() // TODO: properly handle this error .expect("Type does not have ReflectLuaProxy as a TypeData"); - let value = proxy.as_lua(lua, d.ptr.cast()).unwrap() - .into_lua(lua).unwrap(); + let value = proxy + .as_lua(lua, d.ptr.cast()) + .unwrap() + .into_lua(lua) + .unwrap(); vals.push(value); } @@ -165,9 +194,7 @@ impl ViewOneResult { impl mlua::UserData for ViewOneResult { fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) { - methods.add_method_mut("get", |lua, this, ()| { - this.get_res_impl(lua) - }); + methods.add_method_mut("get", |lua, this, ()| this.get_res_impl(lua)); methods.add_meta_method(mlua::MetaMethod::Call, |lua, this, ()| { this.get_res_impl(lua) }); diff --git a/crates/lyra-scripting/src/lua/entity_ref.rs b/crates/lyra-scripting/src/lua/entity_ref.rs index 2444413..bdc7ed1 100644 --- a/crates/lyra-scripting/src/lua/entity_ref.rs +++ b/crates/lyra-scripting/src/lua/entity_ref.rs @@ -6,7 +6,9 @@ use mlua::{IntoLua, ObjectLike}; use crate::{ScriptBorrow, ScriptWorldPtr}; -use super::{reflect_type_user_data, Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_REFLECT_TYPE}; +use super::{ + reflect_type_user_data, Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_REFLECT_TYPE, +}; #[derive(Clone)] pub enum LuaComponent { @@ -50,14 +52,14 @@ impl LuaComponent { } Self::UserData(ud) => { let lua_comp = reflect_type_user_data(ud); - let refl_comp = lua_comp.reflect_branch.as_component_unchecked(); + let refl_comp = lua_comp.reflect_branch.as_component()?; Some(refl_comp.info.type_id().as_rust()) } } } /// Call the internal reflect type function and return the result. - /// + /// /// This calls the [`FN_NAME_INTERNAL_REFLECT_TYPE`] function on the Component. pub fn reflect_type(&self) -> Result<ScriptBorrow, Error> { self.call_function(FN_NAME_INTERNAL_REFLECT_TYPE, ()) @@ -65,9 +67,13 @@ impl LuaComponent { } /// Call a Lua function on the Component. - /// + /// /// This is a helper function so you don't have to match on the component. - pub fn call_function<R: mlua::FromLuaMulti>(&self, name: &str, args: impl mlua::IntoLuaMulti) -> mlua::Result<R> { + pub fn call_function<R: mlua::FromLuaMulti>( + &self, + name: &str, + args: impl mlua::IntoLuaMulti, + ) -> mlua::Result<R> { match self { LuaComponent::UserData(ud) => ud.call_function(name, args), LuaComponent::Table(t) => t.call_function(name, args), @@ -75,9 +81,13 @@ impl LuaComponent { } /// Call a Lua method on the Component. - /// + /// /// This is a helper function so you don't have to match on the component. - pub fn call_method<R: mlua::FromLuaMulti>(&self, name: &str, args: impl mlua::IntoLuaMulti) -> mlua::Result<R> { + pub fn call_method<R: mlua::FromLuaMulti>( + &self, + name: &str, + args: impl mlua::IntoLuaMulti, + ) -> mlua::Result<R> { match self { LuaComponent::UserData(ud) => ud.call_method(name, args), LuaComponent::Table(t) => t.call_method(name, args), @@ -87,12 +97,8 @@ impl LuaComponent { /// Returns `true` if the Component has a function of `name`. pub fn has_function(&self, name: &str) -> mlua::Result<bool> { match self { - LuaComponent::UserData(ud) => { - ud.get::<mlua::Value>(name).map(|v| !v.is_nil()) - }, - LuaComponent::Table(t) => { - t.contains_key(name) - }, + LuaComponent::UserData(ud) => ud.get::<mlua::Value>(name).map(|v| !v.is_nil()), + LuaComponent::Table(t) => t.contains_key(name), } } } @@ -107,10 +113,7 @@ pub struct LuaEntityRef { impl LuaEntityRef { pub fn new(world: ScriptWorldPtr, en: Entity) -> Self { - Self { - en, - world, - } + Self { en, world } } } @@ -139,7 +142,7 @@ impl mlua::UserData for LuaEntityRef { let arch_idx = *arch.entity_indexes().get(&this.en).unwrap(); let col = arch.get_column_mut(tid).unwrap(); let col_ptr = col.component_ptr(*arch_idx as usize, &world_tick).cast(); - + // get the type registry to apply the new value let reg = world.get_resource::<TypeRegistry>().unwrap(); let reg_type = reg.get_type(tid).unwrap(); diff --git a/crates/lyra-scripting/src/lua/proxy.rs b/crates/lyra-scripting/src/lua/proxy.rs index 758a080..67eda65 100644 --- a/crates/lyra-scripting/src/lua/proxy.rs +++ b/crates/lyra-scripting/src/lua/proxy.rs @@ -1,12 +1,12 @@ -use std::{any::TypeId, collections::HashMap, ptr::NonNull}; +use std::{any::TypeId, collections::HashMap, ptr::NonNull, sync::Arc}; -use mlua::ObjectLike; use lyra_ecs::{ComponentInfo, DynamicBundle}; use lyra_reflect::Reflect; +use mlua::ObjectLike; use crate::{ScriptBorrow, ScriptDynamicBundle}; -use super::{Error, FN_NAME_INTERNAL_REFLECT}; +use super::{Error, WorldError, FN_NAME_INTERNAL_REFLECT}; pub trait LuaWrapper: Sized { type Wrap: 'static; @@ -18,7 +18,7 @@ pub trait LuaWrapper: Sized { } fn into_wrapped(self) -> Self::Wrap; - + #[inline(always)] fn from_wrapped(wrap: Self::Wrap) -> Option<Self> { let _ = wrap; @@ -28,35 +28,21 @@ pub trait LuaWrapper: Sized { /// A trait that used to convert something into lua, or to set something to a value from lua. pub trait LuaProxy { - fn as_lua_value( - lua: &mlua::Lua, - this: &dyn Reflect, - ) -> mlua::Result<mlua::Value>; + fn as_lua_value(lua: &mlua::Lua, this: &dyn Reflect) -> mlua::Result<mlua::Value>; - fn apply( - lua: &mlua::Lua, - this: &mut dyn Reflect, - value: &mlua::Value, - ) -> mlua::Result<()>; + fn apply(lua: &mlua::Lua, this: &mut dyn Reflect, value: &mlua::Value) -> mlua::Result<()>; } impl<'a, T> LuaProxy for T where - T: Reflect + Clone + mlua::FromLua + mlua::IntoLua + T: Reflect + Clone + mlua::FromLua + mlua::IntoLua, { - fn as_lua_value( - lua: &mlua::Lua, - this: &dyn Reflect, - ) -> mlua::Result<mlua::Value> { + fn as_lua_value(lua: &mlua::Lua, this: &dyn Reflect) -> mlua::Result<mlua::Value> { let this = this.as_any().downcast_ref::<T>().unwrap(); this.clone().into_lua(lua) } - fn apply( - lua: &mlua::Lua, - this: &mut dyn Reflect, - apply: &mlua::Value, - ) -> mlua::Result<()> { + fn apply(lua: &mlua::Lua, this: &mut dyn Reflect, apply: &mlua::Value) -> mlua::Result<()> { let this = this.as_any_mut().downcast_mut::<T>().unwrap(); let apply = T::from_lua(apply.clone(), lua)?; @@ -67,7 +53,7 @@ where } /// ECS resource that can be used to lookup types via name. -/// +/// /// You can get the [`TypeId`] of the type via name, or the [`ComponentInfo`]. #[derive(Default)] pub struct TypeLookup { @@ -78,8 +64,7 @@ pub struct TypeLookup { /// A struct used for Proxying types to and from Lua. #[derive(Clone)] pub struct ReflectLuaProxy { - fn_as_lua: - for<'a> fn(lua: &'a mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result<mlua::Value>, + fn_as_lua: for<'a> fn(lua: &'a mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result<mlua::Value>, fn_apply: for<'a> fn( lua: &'a mlua::Lua, this_ptr: NonNull<()>, @@ -90,8 +75,8 @@ pub struct ReflectLuaProxy { impl ReflectLuaProxy { /// Create from a type that implements LuaProxy (among some other required traits) pub fn from_lua_proxy<'a, T>() -> Self - where - T: Reflect + LuaProxy + where + T: Reflect + LuaProxy, { Self { fn_as_lua: |lua, this| -> mlua::Result<mlua::Value> { @@ -108,7 +93,7 @@ impl ReflectLuaProxy { /// Create from a type that implements FromLua and AsLua pub fn from_as_and_from_lua<T>() -> Self where - T: mlua::FromLua + mlua::IntoLua + Clone + T: mlua::FromLua + mlua::IntoLua + Clone, { Self { fn_as_lua: |lua, this| -> mlua::Result<mlua::Value> { @@ -120,7 +105,7 @@ impl ReflectLuaProxy { let new_val = T::from_lua(value.clone(), lua)?; *this = new_val; - + Ok(()) }, } @@ -132,7 +117,12 @@ impl ReflectLuaProxy { } /// Set the contents in the pointer to a Lua value. - pub fn apply(&self, lua: &mlua::Lua, this_ptr: NonNull<()>, value: &mlua::Value) -> mlua::Result<()> { + pub fn apply( + &self, + lua: &mlua::Lua, + this_ptr: NonNull<()>, + value: &mlua::Value, + ) -> mlua::Result<()> { (self.fn_apply)(lua, this_ptr, value) } } @@ -152,7 +142,18 @@ impl mlua::UserData for ScriptDynamicBundle { methods.add_function("new", |_, ()| Ok(ScriptDynamicBundle(DynamicBundle::new()))); methods.add_method_mut("push", |_, this, comp: mlua::AnyUserData| { let script_brw = comp.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?; - let reflect = script_brw.reflect_branch.as_component_unchecked(); + let reflect = + script_brw + .reflect_branch + .as_component() + .ok_or(mlua::Error::BadArgument { + to: Some("DynamicBundle:push".into()), + pos: 2, + name: Some("component...".into()), + cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage( + "userdata is not a Component".into(), + ))), + })?; let refl_data = script_brw.data.unwrap(); reflect.bundle_insert(&mut this.0, refl_data); @@ -160,4 +161,4 @@ impl mlua::UserData for ScriptDynamicBundle { Ok(()) }); } -} \ No newline at end of file +} diff --git a/crates/lyra-scripting/src/lua/world.rs b/crates/lyra-scripting/src/lua/world.rs index f926d17..2a2a1da 100644 --- a/crates/lyra-scripting/src/lua/world.rs +++ b/crates/lyra-scripting/src/lua/world.rs @@ -83,7 +83,16 @@ impl mlua::UserData for ScriptWorldPtr { } }; - let reflect = comp_borrow.reflect_branch.as_component_unchecked(); + let reflect = + comp_borrow + .reflect_branch + .as_component() + .ok_or(mlua::Error::BadArgument { + to: Some("World:spawn".into()), + pos: 2 + i, // i starts at 0 + name: Some("components...".into()), + cause: Arc::new(mlua::Error::runtime("userdata is not a Component")), + })?; let refl_data = comp_borrow.data.unwrap(); reflect.bundle_insert(&mut bundle, refl_data); }