From d14abcc3e592cff1b7d3f6f046a57ab4c8f615c2 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 12 Jan 2024 14:11:33 -0500 Subject: [PATCH] Get some math types and some ecs stuff exposed to lua --- lyra-game/Cargo.toml | 4 +- lyra-game/src/game.rs | 2 +- lyra-game/src/math/transform.rs | 3 +- lyra-scripting/Cargo.toml | 7 +- .../lyra-scripting-derive/Cargo.toml | 14 + .../lyra-scripting-derive/src/lib.rs | 606 ++++++++++++++++++ .../lyra-scripting-derive/src/mat_wrapper.rs | 142 ++++ lyra-scripting/scripts/lua/init.lua | 0 lyra-scripting/scripts/lua/math/quat.lua | 177 +++++ lyra-scripting/scripts/lua/math/transform.lua | 94 +++ lyra-scripting/scripts/lua/math/vec3.lua | 161 +++++ lyra-scripting/src/host.rs | 5 +- lyra-scripting/src/lib.rs | 21 +- lyra-scripting/src/lua/dynamic_iter.rs | 2 +- lyra-scripting/src/lua/mod.rs | 50 +- lyra-scripting/src/lua/providers/ecs.rs | 26 + lyra-scripting/src/lua/providers/math.rs | 108 ++++ lyra-scripting/src/lua/providers/mod.rs | 8 + .../lua/{modules/mod.rs => providers/util.rs} | 0 lyra-scripting/src/lua/script.rs | 4 +- lyra-scripting/src/lua/world.rs | 6 +- lyra-scripting/src/lua/wrappers/mod.rs | 365 +++++++++++ 22 files changed, 1784 insertions(+), 21 deletions(-) create mode 100644 lyra-scripting/lyra-scripting-derive/Cargo.toml create mode 100644 lyra-scripting/lyra-scripting-derive/src/lib.rs create mode 100644 lyra-scripting/lyra-scripting-derive/src/mat_wrapper.rs create mode 100644 lyra-scripting/scripts/lua/init.lua create mode 100644 lyra-scripting/scripts/lua/math/quat.lua create mode 100644 lyra-scripting/scripts/lua/math/transform.lua create mode 100644 lyra-scripting/scripts/lua/math/vec3.lua create mode 100644 lyra-scripting/src/lua/providers/ecs.rs create mode 100644 lyra-scripting/src/lua/providers/math.rs create mode 100644 lyra-scripting/src/lua/providers/mod.rs rename lyra-scripting/src/lua/{modules/mod.rs => providers/util.rs} (100%) create mode 100644 lyra-scripting/src/lua/wrappers/mod.rs diff --git a/lyra-game/Cargo.toml b/lyra-game/Cargo.toml index 87d853f..3579672 100644 --- a/lyra-game/Cargo.toml +++ b/lyra-game/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" [dependencies] lyra-resource = { path = "../lyra-resource" } -lyra-ecs = { path = "../lyra-ecs" } -lyra-reflect = { path = "../lyra-reflect" } +lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } +lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] } winit = "0.28.1" tracing = "0.1.37" diff --git a/lyra-game/src/game.rs b/lyra-game/src/game.rs index 0a49e45..a3d8666 100755 --- a/lyra-game/src/game.rs +++ b/lyra-game/src/game.rs @@ -342,7 +342,7 @@ impl Game { .with(fmt::layer().with_writer(stdout_layer)) .with(filter::Targets::new() // done by prefix, so it includes all lyra subpackages - .with_target("lyra", Level::TRACE) + .with_target("lyra", Level::DEBUG) .with_target("wgpu", Level::WARN) .with_default(Level::INFO)) .init(); diff --git a/lyra-game/src/math/transform.rs b/lyra-game/src/math/transform.rs index 9e02ae1..f2aa027 100755 --- a/lyra-game/src/math/transform.rs +++ b/lyra-game/src/math/transform.rs @@ -89,8 +89,7 @@ impl Transform { /// Performs a linear interpolation between `self` and `rhs` based on the value `alpha`. /// /// When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, the result - /// will be equal to `rhs`. When `alpha` is outside of range `[0, 1]`, the result is linearly - /// extrapolated. + /// will be equal to `rhs`. pub fn lerp(&self, rhs: Transform, alpha: f32) -> Self { if alpha.is_finite() { diff --git a/lyra-scripting/Cargo.toml b/lyra-scripting/Cargo.toml index 4e2aec6..46539a6 100644 --- a/lyra-scripting/Cargo.toml +++ b/lyra-scripting/Cargo.toml @@ -10,8 +10,9 @@ default = ["lua"] lua = ["dep:mlua"] [dependencies] -lyra-ecs = { path = "../lyra-ecs" } -lyra-reflect = { path = "../lyra-reflect" } +lyra-scripting-derive = { path = "lyra-scripting-derive" } +lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } +lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] } lyra-resource = { path = "../lyra-resource" } lyra-game = { path = "../lyra-game" } thiserror = "1.0.50" @@ -23,4 +24,4 @@ mlua = { version = "0.9.2", features = ["lua54"], optional = true } # luajit may [dev-dependencies] -tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } \ No newline at end of file +tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } diff --git a/lyra-scripting/lyra-scripting-derive/Cargo.toml b/lyra-scripting/lyra-scripting-derive/Cargo.toml new file mode 100644 index 0000000..9595a9d --- /dev/null +++ b/lyra-scripting/lyra-scripting-derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lyra-scripting-derive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.70" +quote = "1.0.33" +syn = "2.0.41" diff --git a/lyra-scripting/lyra-scripting-derive/src/lib.rs b/lyra-scripting/lyra-scripting-derive/src/lib.rs new file mode 100644 index 0000000..a0f40c1 --- /dev/null +++ b/lyra-scripting/lyra-scripting-derive/src/lib.rs @@ -0,0 +1,606 @@ +use proc_macro2::{Ident, Span}; +use quote::quote; +use syn::{parse_macro_input, Path, Token, token, parenthesized, punctuated::Punctuated, braced, bracketed}; + +mod mat_wrapper; +use mat_wrapper::MatWrapper; + +const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type"; +const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect"; + +pub(crate) struct MetaMethod { + pub ident: Ident, + pub mods: Vec, +} + +impl syn::parse::Parse for MetaMethod { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ident: Ident = input.parse()?; + + let mods = if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + content.parse_terminated(Ident::parse, Token![,])? + .into_iter().collect() + } else { vec![] }; + + Ok(Self { + ident, + mods, + }) + } +} + +pub(crate) struct VecWrapper { + +} + +impl VecWrapper { + fn vec_size(&self, wrapper_ident: &Ident) -> usize { + let name = wrapper_ident.to_string(); + name[name.len() - 1..].parse::() + .or_else(|_| name[name.len() - 2.. name.len() - 1].parse::()) + .expect("Failure to grab Vec size from ident name") + } + + /// Returns the token stream of the type of the axis of the vec (Vec2 vs IVec2 vs I64Vec2, etc.) + fn vec_axis_type(&self, wrapper_ident: &Ident) -> &'static str { + let name = wrapper_ident.to_string(); + let start = name.find("Vec").unwrap(); + + let before = &name[start - 1.. start]; + match before { + "D" => return "f64", + "I" => return "i32", + "U" => return "u32", + "B" => return "bool", + _ => {}, + } + //println!("before is {before}"); + + let three_before = &name[start - 3.. start]; + match three_before { + "I64" => return "i64", + "U64" => return "u64", + _ => {}, + } + //println!("three before is {three_before}"); + + "f32" + } + + pub fn to_field_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream { + let mut consts = vec![quote!(ZERO), quote!(ONE), quote!(X), + quote!(Y), ]; // , quote!(AXES) + + let vec_size = self.vec_size(wrapper_ident); + let axis_type_name = self.vec_axis_type(wrapper_ident); + + if axis_type_name.contains("b") { + return quote! { + fields.add_field("FALSE", #wrapper_ident(#wrapped_path::FALSE)); + fields.add_field("TRUE", #wrapper_ident(#wrapped_path::TRUE)); + }; + } + + if vec_size >= 3 { + consts.push(quote!(Z)); + + // no negative numbers for unsigned vecs + if !axis_type_name.contains("u") { + consts.push(quote!(NEG_Z)); + } + } + + if vec_size == 4 { + consts.push(quote!(W)); + + // no negative numbers for unsigned vecs + if !axis_type_name.contains("u") { + consts.push(quote!(NEG_W)); + } + } + + // no negative numbers for unsigned vecs + if !axis_type_name.contains("u") { + consts.push(quote!(NEG_X)); + consts.push(quote!(NEG_Y)); + consts.push(quote!(NEG_ONE)); + } + + if axis_type_name.contains("f") { + consts.push(quote!(NAN)) + } + + let const_tokens = consts.iter().map(|cnst| { + let const_name = cnst.to_string(); + + quote! { + fields.add_field(#const_name, #wrapper_ident(#wrapped_path::#cnst)); + } + }); + + quote! { + #(#const_tokens)* + } + } + + pub fn to_method_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream { + let vec_size = self.vec_size(wrapper_ident); + let axis_type_name = self.vec_axis_type(wrapper_ident); + // methods that only some vecs have + let mut optional_methods = vec![]; + + // boolean vectors dont have much :( + if axis_type_name.contains("b") { + return quote!(); // TODO: all, any, bitmask, splat + } + + if axis_type_name.contains("f") { + let type_id = Ident::new(axis_type_name, Span::call_site()); + + optional_methods.push( + quote! { + methods.add_method("clamp_length", + |_, this, (min, max): (#type_id, #type_id)| { + Ok(#wrapper_ident(this.clamp_length(min, max))) + }); + + methods.add_method("abs_diff_eq", + |_, this, (rhs, max_abs_diff): (#wrapper_ident, #type_id)| { + Ok(this.abs_diff_eq(rhs.0, max_abs_diff)) + }); + + methods.add_method("ceil", + |_, this, (): ()| { + Ok(#wrapper_ident(this.ceil())) + }); + } + ); + + if vec_size != 4 { + optional_methods.push( + quote! { + methods.add_method("angle_between", + |_, this, (rhs,): (#wrapper_ident,)| { + Ok(this.angle_between(rhs.0)) + }); + } + ) + } + } + + if !axis_type_name.contains("u") { + optional_methods.push( + quote! { + methods.add_method("abs", + |_, this, (): ()| { + Ok(#wrapper_ident(this.abs())) + }); + } + ) + } + + let optional_methods_iter = optional_methods.iter(); + quote! { + + + methods.add_method("clamp", + |_, this, (min, max): (#wrapper_ident, #wrapper_ident)| { + Ok(#wrapper_ident(this.clamp(min.0, max.0))) + }); + + // TODO: Not all Vecs have this + /* methods.add_method("clamp_length", + |_, this, (min, max): (f32, f32)| { + Ok(#wrapper_ident(this.clamp_length(min, max))) + }); */ + + + methods.add_method("to_array", + |_, this, (): ()| { + Ok(this.to_array()) + }); + + #(#optional_methods_iter)* + } + } +} + +pub(crate) struct WrapUsage { + pub type_path: Path, + /// The extra derives of the type. + pub derive_idents: Punctuated, + /// The field idents of the type that will be exposed with gets and sets + pub field_idents: Punctuated, + pub skip_new_fn: bool, + /// The identifiers that are taken as parameters in the types 'new' function + pub new_fn_idents: Punctuated, + pub meta_method_idents: Punctuated, + + pub matrix: Option, + pub vec: Option, +} + +impl syn::parse::Parse for WrapUsage { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let type_path: Path = input.parse()?; + let mut s = Self { + type_path, + derive_idents: Punctuated::default(), + field_idents: Punctuated::default(), + skip_new_fn: false, + new_fn_idents: Punctuated::default(), + meta_method_idents: Punctuated::default(), + matrix: None, + vec: None, + }; + /* let mut derive_idents = None; + let mut field_idents = None; + let mut new_fn_idents = None; */ + + while input.peek(Token![,]) { + let _: Token![,] = input.parse()?; + //println!("Peeked a , ({:?})", input); + + if input.peek(syn::Ident) { + let ident: Ident = input.parse()?; + let ident_str = ident.to_string(); + let ident_str = ident_str.as_str(); + + match ident_str { + "derives" => { + if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + + let derives: Punctuated = content.parse_terminated(Ident::parse, Token![,])?; + s.derive_idents = derives; + //println!("read derives: {:?}", s.derive_idents); + } + }, + "fields" => { + if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + + let fields: Punctuated = content.parse_terminated(Ident::parse, Token![,])?; + s.field_idents = fields; + //println!("read fields: {:?}", s.field_idents); + } + }, + "new" => { + if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + + let fields: Punctuated = content.parse_terminated(Ident::parse, Token![,])?; + s.new_fn_idents = fields; + //println!("read fields: {:?}", s.new_fn_idents); + } + }, + "no_new" => { + s.skip_new_fn = true; + }, + "matrix" => { + if input.peek(token::Brace) { + let content; + let _braces = braced!(content in input); + s.matrix = Some(content.parse()?); + } + }, + "metamethods" => { + if input.peek(token::Paren) { + let content; + let _bracket: token::Paren = parenthesized!(content in input); + + let meta_methods: Punctuated = content.parse_terminated(MetaMethod::parse, Token![,])?; + s.meta_method_idents = meta_methods; + } + }, + _ => { + return Err(syn::Error::new_spanned(ident, "unknown wrapper command")); + } + } + } + } + + Ok(s) + } +} + +/// Creates a wrapper type for a VecN from the engine math library. +#[proc_macro] +pub fn wrap_math_vec_copy(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as WrapUsage); + + let path: Path = input.type_path; + let type_name = &path.segments.last() + .expect("Failure to find typename in macro usage!") + .ident; + let wrapper_typename = Ident::new(&format!("Lua{}", type_name), Span::call_site()); + + let vec_wrapper = { + let name_str = type_name.to_string(); + if name_str.contains("Vec") { + Some(VecWrapper {}) + } else { + None + } + }; + /* let vec_wrapper_fields = vec_wrapper.as_ref().map(|vec| + vec.to_field_tokens(&path, &wrapper_typename)); */ + let vec_wrapper_fields: Option = None; + let vec_wrapper_methods = vec_wrapper.as_ref().map(|vec| + vec.to_method_tokens(&path, &wrapper_typename)); + + let derive_idents_iter = input.derive_idents.iter(); + + let field_get_set_pairs = input.field_idents.iter().map(|i| { + let is = i.to_string(); + quote! { + fields.add_field_method_get(#is, |_, this| { + Ok(this.#i) + }); + fields.add_field_method_set(#is, |_, this, #i| { + this.#i = #i; + Ok(()) + }); + } + }); + + let new_fn_idents = { + let idents = if input.new_fn_idents.is_empty() { + input.field_idents.iter() + } else { + input.new_fn_idents.iter() + }; + + let idents_c = idents.clone(); + + if !input.skip_new_fn { + quote! { + methods.add_function("new", |_, ( #(#idents_c),* )| { + Ok(#wrapper_typename(#path::new( #(#idents),* ))) + }); + } + } else { quote!() } + }; + + let matrix_wrapper_methods = input.matrix.as_ref().map(|m| + m.to_method_tokens(&path, &wrapper_typename)); + let matrix_wrapper_fields = input.matrix.as_ref().map(|m| + m.to_field_tokens(&path, &wrapper_typename)); + + let meta_method_idents = { + let idents = input.meta_method_idents.iter().map(|metamethod| { + let metamethod_ident = &metamethod.ident; + let mm_str = metamethod.ident.to_string(); + let mm_str = mm_str.as_str(); + match mm_str { + "Add" | "Sub" | "Div" | "Mul" | "Mod" => { + let symbol = match mm_str { + "Add" => quote!(+), + "Sub" => quote!(-), + "Div" => quote!(/), + "Mul" => quote!(*), + "Mod" => quote!(%), + _ => unreachable!(), + }; + + // create a temporary vec to chain with metamethod.mods. If no parameters + // were provided, add the wrapper to the list of parameters. + let t = if metamethod.mods.is_empty() { + vec![wrapper_typename.clone()] + } else { vec![] }; + + let mods = metamethod.mods.iter().chain(t.iter()).map(|param| { + let other = if param.to_string().starts_with("Lua") { + quote!(other.0) + } else { + quote!(other) + }; + + quote! { + methods.add_meta_method(mlua::MetaMethod::#metamethod_ident, + |_, this, (other,): (#param,)| { + Ok(#wrapper_typename(this.0 #symbol #other)) + }); + } + }); + + quote! { + #(#mods)* + } + }, + "Unm" => { + quote! { + methods.add_meta_method(mlua::MetaMethod::#metamethod_ident, |_, this, ()| { + Ok(#wrapper_typename(-this.0)) + }); + } + }, + // Eq meta method has a different implementation than the above methods. + "Eq" => { + quote! { + methods.add_meta_method(mlua::MetaMethod::#metamethod_ident, + |_, this, (other,): (#wrapper_typename,)| { + Ok(this.0 == other.0) + }); + } + }, + "Shl" => { + // create a temporary vec to chain with metamethod.mods. If no parameters + // were provided, add the wrapper to the list of parameters. + let t = if metamethod.mods.is_empty() { + vec![wrapper_typename.clone()] + } else { vec![] }; + + let mods = metamethod.mods.iter().chain(t.iter()).map(|param| { + let other = if param.to_string().starts_with("Lua") { + quote!(other.0) + } else { + quote!(other) + }; + + quote! { + methods.add_meta_method(mlua::MetaMethod::#metamethod_ident, + |_, this, (other,): (#param,)| { + Ok(#wrapper_typename(this.0 << #other)) + }); + } + }); + + quote! { + #(#mods)* + } + } + "Shr" => { + // create a temporary vec to chain with metamethod.mods. If no parameters + // were provided, add the wrapper to the list of parameters. + let t = if metamethod.mods.is_empty() { + vec![wrapper_typename.clone()] + } else { vec![] }; + + let mods = metamethod.mods.iter().chain(t.iter()).map(|param| { + let other = if param.to_string().starts_with("Lua") { + quote!(other.0) + } else { + quote!(other) + }; + + quote! { + methods.add_meta_method(mlua::MetaMethod::#metamethod_ident, + |_, this, (other,): (#param,)| { + Ok(#wrapper_typename(this.0 >> #other)) + }); + } + }); + + quote! { + #(#mods)* + } + }, + "BAnd" | "BOr" | "BXor" => { + let symbol = match mm_str { + "BAnd" => { + quote!(&) + }, + "BOr" => { + quote!(|) + }, + "BXor" => { + quote!(^) + }, + _ => unreachable!() // the string was just checked to be one of these + }; + + // create a temporary vec to chain with metamethod.mods. If no parameters + // were provided, add the wrapper to the list of parameters. + let t = if metamethod.mods.is_empty() { + vec![wrapper_typename.clone()] + } else { vec![] }; + + let mods = metamethod.mods.iter().chain(t.iter()).map(|param| { + let other = if param.to_string().starts_with("Lua") { + quote!(other.0) + } else { + quote!(other) + }; + + quote! { + methods.add_meta_method(mlua::MetaMethod::#metamethod_ident, + |_, this, (other,): (#param,)| { + Ok(#wrapper_typename(this.0 #symbol #other)) + }); + } + }); + + quote! { + #(#mods)* + } + }, + "BNot" => { + quote! { + methods.add_meta_method(mlua::MetaMethod::#metamethod_ident, |_, this, ()| { + Ok(#wrapper_typename(!this.0)) + }); + } + }, + "ToString" => { + quote! { + methods.add_meta_method(mlua::MetaMethod::ToString, |_, this, ()| { + Ok(format!("{:?}", this.0)) + }); + } + }, + _ => syn::Error::new_spanned(metamethod_ident, + "unsupported auto implementation of metamethod").to_compile_error(), + } + }); + + quote! { + #(#idents)* + } + }; + + proc_macro::TokenStream::from(quote! { + #[derive(Clone, Copy, lyra_reflect::Reflect, #(#derive_idents_iter),*)] + pub struct #wrapper_typename(#[reflect(skip)] pub(crate) #path); + + impl std::ops::Deref for #wrapper_typename { + type Target = #path; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl std::ops::DerefMut for #wrapper_typename { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl<'lua> mlua::FromLua<'lua> for #wrapper_typename { + fn from_lua(value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result { + match value { + mlua::Value::UserData(ud) => Ok(*ud.borrow::()?), + _ => unreachable!(), + } + } + } + + impl mlua::UserData for #wrapper_typename { + fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: &mut F) { + #(#field_get_set_pairs)* + + #matrix_wrapper_fields + #vec_wrapper_fields + } + + fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + #new_fn_idents + + methods.add_method(#FN_NAME_INTERNAL_REFLECT, |_, this, ()| { + Ok(crate::ScriptBorrow::from_component::<#path>(Some(this.0.clone()))) + }); + + methods.add_function(#FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| { + Ok(crate::ScriptBorrow::from_component::<#path>(None)) + }); + + #meta_method_idents + + #matrix_wrapper_methods + #vec_wrapper_methods + } + } + + impl lyra_scripting::lua::LuaWrapper for #wrapper_typename { + fn wrapped_type_id() -> std::any::TypeId { + std::any::TypeId::of::<#path>() + } + } + }) +} \ No newline at end of file diff --git a/lyra-scripting/lyra-scripting-derive/src/mat_wrapper.rs b/lyra-scripting/lyra-scripting-derive/src/mat_wrapper.rs new file mode 100644 index 0000000..fdb0e39 --- /dev/null +++ b/lyra-scripting/lyra-scripting-derive/src/mat_wrapper.rs @@ -0,0 +1,142 @@ +use proc_macro2::Ident; +use quote::quote; +use syn::{Path, Token}; + +pub(crate) struct MatWrapper { + pub column_type: Ident, +} + +impl MatWrapper { + pub fn to_field_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream { + quote! { + fields.add_field("ZERO", #wrapper_ident(#wrapped_path::ZERO)); + fields.add_field("IDENTITY", #wrapper_ident(#wrapped_path::IDENTITY)); + fields.add_field("NAN", #wrapper_ident(#wrapped_path::NAN)); + } + } + + pub fn to_method_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream { + let column_type = &self.column_type; + + let column_size = { + let ty_str = column_type.to_string(); + ty_str[ty_str.len() - 1..].parse::() + .expect("Failure to parse number from token type") + }; + let column_size_xtwo = column_size * 2; + + let element_ty = quote!(f32); + + quote! { + methods.add_function("from_cols", + |_, (x_axis, y_axis): (#column_type, #column_type)| { + Ok(#wrapper_ident(#wrapped_path::from_cols(x_axis.0, y_axis.0))) + }); + + methods.add_function("from_cols_array", + |_, (arr,): ([#element_ty; #column_size_xtwo],)| { + Ok(#wrapper_ident(#wrapped_path::from_cols_array(&arr))) + }); + + methods.add_function("from_cols_array_2d", + |_, (arr,): ([[#element_ty; #column_size]; #column_size],)| { + Ok(#wrapper_ident(#wrapped_path::from_cols_array_2d(&arr))) + }); + + methods.add_function("from_diagonal", + |_, (diag,): (#column_type,)| { + Ok(#wrapper_ident(#wrapped_path::from_diagonal(diag.0))) + }); + + methods.add_method("col", + |_, this, (idx,): (usize,)| { + Ok(#column_type(this.col(idx))) + }); + + methods.add_method("row", + |_, this, (idx,): (usize,)| { + Ok(#column_type(this.row(idx))) + }); + + methods.add_method_mut("set_col", + |_, this, (idx, newc): (usize, #column_type)| { + let col = this.col_mut(idx); + *col = newc.0; + + Ok(()) + }); + + methods.add_method("is_finite", + |_, this, (): ()| { + Ok(this.is_finite()) + }); + + methods.add_method("is_nan", + |_, this, (): ()| { + Ok(this.is_nan()) + }); + + methods.add_method("transpose", + |_, this, (): ()| { + Ok(#wrapper_ident(this.0.transpose())) + }); + + methods.add_method("determinant", + |_, this, (): ()| { + Ok(this.determinant()) + }); + + methods.add_method("inverse", + |_, this, (): ()| { + Ok(#wrapper_ident(this.inverse())) + }); + + methods.add_method("abs_diff_eq", + |_, this, (rhs, max_abs_diff): (#wrapper_ident, f32)| { + Ok(this.abs_diff_eq(rhs.0, max_abs_diff)) + }); + + // TODO: After all DMat's are implemented + /* methods.add_method("as_dmat", + |_, this, (rhs, max_abs_diff): (#wrapper_ident, f32)| { + Ok(D#wrapper_ident(this.as_dmat)) + }); */ + } + } +} + +impl syn::parse::Parse for MatWrapper { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut column_type = None; + + // cba to remove the use of this bool + let mut first = true; + + while input.peek(Token![,]) || first { + if !first { + let _: Token![,] = input.parse()?; + } + + if input.peek(syn::Ident) { + let ident: Ident = input.parse()?; + let ident_str = ident.to_string(); + let ident_str = ident_str.as_str(); + + match ident_str { + "col_type" => { + let _eq: Token![=] = input.parse()?; + column_type = Some(input.parse()?); + }, + _ => return Err(syn::Error::new_spanned(ident, "unknown matrix wrapper command")), + } + } + + first = false; + } + + Ok(Self { + column_type: column_type.ok_or_else(|| syn::Error::new(input.span(), + "expected `col_type`"))?, + }) + } +} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/init.lua b/lyra-scripting/scripts/lua/init.lua new file mode 100644 index 0000000..e69de29 diff --git a/lyra-scripting/scripts/lua/math/quat.lua b/lyra-scripting/scripts/lua/math/quat.lua new file mode 100644 index 0000000..c6aca90 --- /dev/null +++ b/lyra-scripting/scripts/lua/math/quat.lua @@ -0,0 +1,177 @@ +Quat = { x = 0.0, y = 0.0, z = 0.0, w = 0.0 } +Quat.__index = Quat +Quat.__name = "Quat" + +--- Constructs a new Quaternion from x, y, z, and w. +---@param x number +---@param y number +---@param z number +---@param w number +---@return Quat +function Quat:new(x, y, z, w) + local q = {} + setmetatable(q, Quat) + + q.x = x + q.y = y + q.z = z + q.w = w + + return q +end + +Quat.IDENTITY = Quat:new(0, 0, 0, 1) + +function Quat:copy() + return Quat:new(self.x, self.y, self.z, self.w) +end + +--- Creates a quaternion from the angle, in radians, around the x axis. +--- @param rad number +--- @return Quat +function Quat:from_rotation_x(rad) + local sin = math.sin(rad * 0.5) + local cos = math.cos(rad * 0.5) + return Quat:new(sin, 0, 0, cos) +end + +--- Creates a quaternion from the angle, in radians, around the y axis. +--- @param rad number +--- @return Quat +function Quat:from_rotation_y(rad) + local sin = math.sin(rad * 0.5) + local cos = math.cos(rad * 0.5) + return Quat:new(0, sin, 0, cos) +end + +--- Creates a quaternion from the angle, in radians, around the z axis. +--- @param rad number +--- @return Quat +function Quat:from_rotation_z(rad) + local sin = math.sin(rad * 0.5) + local cos = math.cos(rad * 0.5) + return Quat:new(0, 0, sin, cos) +end + +--- Computes the dot product of `self`. +---@param rhs Quat +---@return number +function Quat:dot(rhs) + return (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z) + (self.w * rhs.w) +end + +--- Computes the length of `self`. +---@return number +function Quat:length() + return math.sqrt(self:dot(self)) +end + +--- Compute the length of `self` squared. +---@return number +function Quat:length_squared() + return self:length() ^ 2 +end + +--- Normalizes `self` and returns the new Quat +---@return unknown +function Quat:normalize() + local length = self:length() + return self / length +end + +--- Multiplies two Quaternions together. Keep in mind that Quaternion multiplication is NOT +--- commutative so the order in which you multiply the quaternions matters. +---@param rhs Quat +---@return Quat +function Quat:mult_quat(rhs) + local x1, y1, z1, w1 = self.x, self.y, self.z, self.w + local x2, y2, z2, w2 = rhs.x, rhs.y, rhs.z, rhs.w + + local x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 + local y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2 + local z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2 + local w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * x2 + + return Quat:new(x, y, z, w) +end + +--- Multiplies `self` by a Vec3, returning the rotated Vec3 +---@param vec Vec3 +---@return Vec3 +function Quat:mult_vec3(vec) + local vec_quat = Quat:new(vec.x, vec.y, vec.z, 0) + local quat = self:mult_quat(vec_quat) + return Vec3:new(quat.x, quat.y, quat.z) +end + +--- Calculates the linear iterpolation between `self` and `rhs` based on the `alpha`. +--- When `alpha` is `0`, the result will be equal to `self`. When `s` is `1`, the result +--- will be equal to `rhs` +--- @param rhs Quat +--- @param alpha number +--- @return Quat +function Quat:lerp(rhs, alpha) + -- ensure alpha is [0, 1] + local alpha = math.max(0, math.min(1, alpha)) + + local x1, y1, z1, w1 = self.x, self.y, self.z, self.w + local x2, y2, z2, w2 = rhs.x, rhs.y, rhs.z, rhs.w + + local x = (1 - alpha) * x1 + alpha * x2 + local y = (1 - alpha) * y1 + alpha * y2 + local z = (1 - alpha) * z1 + alpha * z2 + local w = (1 - alpha) * w1 + alpha * w2 + + return Quat:new(x, y, z, w):normalize() +end + +function Quat:__add(rhs) + return Quat:new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z, self.w + rhs.w) +end + +function Quat:__sub(rhs) + return Quat:new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z, self.w - rhs.w) +end + +function Quat:__mul(rhs) + if type(rhs) == "number" then + return Quat:new(self.x * rhs, self.y * rhs, self.z * rhs, self.w * rhs) + elseif type(rhs) == "table" then + local name = rhs.__name + + if name == "Vec3" then + return self:mult_vec3(rhs) + elseif name == "Quat" then + return self:mult_quat(rhs) + else + assert(false, "Unknown usertype of rhs" .. name) + end + else + assert(false, "Unknown type of rhs" .. type(rhs)) + end +end + +function Quat:__div(rhs) + if type(rhs) == "number" then + return Quat:new(self.x / rhs, self.y / rhs, self.z / rhs, self.w / rhs) + else + assert(rhs.__name == "Quat", "Attempted to divide Quat by unknown type " .. rhs.__name) + return Quat:new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z, self.w / rhs.w) + end +end + +function Quat:__eq(rhs) + return self.x == rhs.x and self.y == rhs.y and self.z == rhs.z and self.w == rhs.w +end + +function Quat:__lt(rhs) + return self.x < rhs.x and self.y < rhs.y and self.z < rhs.z and self.w < rhs.w +end + +function Quat:__le(rhs) + return self.x <= rhs.x and self.y <= rhs.y and self.z <= rhs.z and self.w <= rhs.w +end + +function Quat:__tostring() + return "Quat(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ", " .. self.w .. ")" +end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/math/transform.lua b/lyra-scripting/scripts/lua/math/transform.lua new file mode 100644 index 0000000..76273e2 --- /dev/null +++ b/lyra-scripting/scripts/lua/math/transform.lua @@ -0,0 +1,94 @@ +--require("math.quat") +--require("math.vec3") + +Transform = { translation = Vec3.ZERO, rotation = Quat.IDENTITY, scale = Vec3.ONE } +Transform.__index = Transform +Transform.__name = "Transform" + +function Transform:new(translation, rotation, scale) + local t = {} + setmetatable(t, Transform) + + t.translation = translation + t.rotation = rotation + t.scale = scale + + return t +end + +function Transform:copy() + return Transform:new(self.translation:copy(), self.rotation:copy(), self.scale:copy()) +end + +--- Creates a new Transform with the translation at the vec3 +--- @param pos Vec3 +function Transform:from_vec3(pos) + local t = Transform:copy() -- copy of default transform + t.translation = pos + return t +end + +function Transform:from_xyz(x, y, z) + Transform:from_vec3(Vec3:new(x, y, z)) +end + +--- Calculates the forward vector of the Transform. +--- @return Vec3 +function Transform:forward() + return (self.rotation * Vec3.NEG_Z):normalize() +end + +--- Calculates the left vector of the Transform. +--- @return Vec3 +function Transform:left() + return (self.rotation * Vec3.X):normalize() +end + +--- Calculates the up vector of the Transform. +--- @return Vec3 +function Transform:up() + return (self.rotation * Vec3.Y):normalize() +end + +--- Rotates `self` using a Quaternion +--- @param quat Quat +function Transform:rotate(quat) + self.rotation = (quat * self.rotation):normalize() +end + +--- Rotates `self` around the x-axis +--- @param rad number +function Transform:rotate_x(rad) + self:rotate(Quat:from_rotation_x(rad)) +end + +--- Rotates `self` around the y-axis +--- @param rad number +function Transform:rotate_y(rad) + self:rotate(Quat:from_rotation_y(rad)) +end + +--- Rotates `self` around the z-axis +--- @param rad number +function Transform:rotate_z(rad) + self:rotate(Quat:from_rotation_z(rad)) +end + +--- Calculates the linear iterpolation between `self` and `rhs` based on the `alpha`. +--- When `alpha` is `0`, the result will be equal to `self`. When `s` is `1`, the result +--- will be equal to `rhs` +--- @param rhs Transform +--- @param alpha number +--- @return Transform +function Transform:lerp(rhs, alpha) + local res = self:copy() + res.translation = self.translation:lerp(rhs.translation, alpha) + res.rotation = self.rotation:lerp(rhs.rotation, alpha) + res.scale = self.scale:lerp(rhs.scale, alpha) + return res +end + +function Transform:__tostring() + return "Transform(pos=" .. tostring(self.translation) .. ", rot=" + .. tostring(self.rotation) .. ", scale=" .. tostring(self.scale) .. ")" +end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/math/vec3.lua b/lyra-scripting/scripts/lua/math/vec3.lua new file mode 100644 index 0000000..cf88af6 --- /dev/null +++ b/lyra-scripting/scripts/lua/math/vec3.lua @@ -0,0 +1,161 @@ +Vec3 = { x = 0.0, y = 0.0, z = 0.0 } +Vec3.__index = Vec3 +Vec3.__name = "Vec3" + +--- Constructs a new vector +---@param x number +---@param y number +---@param z number +---@return Vec3 +function Vec3:new(x, y, z) + local v = {} + setmetatable(v, Vec3) + + v.x = x + v.y = y + v.z = z + + return v +end + +function Vec3:copy() + return Vec3:new(self.x, self.y, self.z) +end + +--- Constructs a vector with all elements as parameter `x`. +---@param x number +---@return Vec3 +function Vec3:all(x) + return Vec3:new(x, x, x) +end + +--- A unit-length vector pointing alongside the positive X axis. +Vec3.X = Vec3:new(1, 0, 0) +--- A unit-length vector pointing alongside the positive Y axis. +Vec3.Y = Vec3:new(0, 1, 0) +--- A unit-length vector pointing alongside the positive Z axis. +Vec3.Z = Vec3:new(0, 0, 1) + +--- A unit-length vector pointing alongside the negative X axis. +Vec3.NEG_X = Vec3:new(-1, 0, 0) +--- A unit-length vector pointing alongside the negative Y axis. +Vec3.NEG_Y = Vec3:new(0, -1, 0) +--- A unit-length vector pointing alongside the negative Z axis. +Vec3.NEG_Z = Vec3:new(0, 0, -1) + +--- A vector of all zeros +Vec3.ZERO = Vec3:new(0, 0, 0) +--- A vector of all ones +Vec3.ONE = Vec3:new(1, 1, 1) + +--- Computes the absolute value of `self`. +---@return Vec3 +function Vec3:abs() + return Vec3:new(math.abs(self.x), math.abs(self.y), math.abs(self.z)) +end + +--- Computes the length of `self`. +---@return number +function Vec3:length() + return math.sqrt(self:dot(self)) +end + +--- Computes the dot product of `self` and `rhs`. +---@param rhs Vec3 +---@return number +function Vec3:dot(rhs) + assert(rhs.__name == "Vec3") + + return (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z) +end + +--- Returns a vector that has the minimum value of each element of `self` and `rhs` +---@param rhs Vec3 +---@return Vec3 +function Vec3:min(rhs) + local x = math.min(self.x, rhs.x) + local y = math.min(self.y, rhs.y) + local z = math.min(self.z, rhs.z) + + return Vec3:new(x, y, z) +end + +--- Returns `self` normalized to a length 1. +---@return unknown +function Vec3:normalize() + local len_recip = 1.0 / self:length() + return self * len_recip +end + +--- Calculates the linear iterpolation between `self` and `rhs` based on the `alpha`. +--- When `alpha` is `0`, the result will be equal to `self`. When `s` is `1`, the result +--- will be equal to `rhs` +--- @param rhs Vec3 +--- @param alpha number +--- @return Vec3 +function Vec3:lerp(rhs, alpha) + -- ensure alpha is [0, 1] + local alpha = math.max(0, math.min(1, alpha)) + + local res = self:copy() + res = res + ((rhs - res) * alpha) + return res +end + +function Vec3:__add(rhs) + return Vec3:new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) +end + +function Vec3:__sub(rhs) + return Vec3:new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) +end + +function Vec3:__mul(rhs) + if type(rhs) == "number" then + return Vec3:new(self.x * rhs, self.y * rhs, self.z * rhs) + else + return Vec3:new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z) + end +end + +function Vec3:__div(rhs) + if type(rhs) == "number" then + return Vec3:new(self.x / rhs, self.y / rhs, self.z / rhs) + else + return Vec3:new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z) + end +end + +function Vec3:__idiv(rhs) + if type(rhs) == "number" then + return Vec3:new(self.x // rhs, self.y // rhs, self.z // rhs) + else + return Vec3:new(self.x // rhs.x, self.y // rhs.y, self.z // rhs.z) + end +end + +function Vec3:__unm() + return Vec3:new(-self.x, -self.y, -self.z) +end + +function Vec3:__pow(rhs) + if type(rhs) == "number" then + return Vec3:new(self.x ^ rhs, self.y ^ rhs, self.z ^ rhs) + end +end + +function Vec3:__eq(rhs) + return self.x == rhs.x and self.y == rhs.y and self.z == rhs.z +end + +function Vec3:__lt(rhs) + return self.x < rhs.x and self.y < rhs.y and self.z < rhs.z +end + +function Vec3:__le(rhs) + return self.x <= rhs.x and self.y <= rhs.y and self.z <= rhs.z +end + +function Vec3:__tostring() + return "Vec3(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ")" +end \ No newline at end of file diff --git a/lyra-scripting/src/host.rs b/lyra-scripting/src/host.rs index b19e316..b9cb4b9 100644 --- a/lyra-scripting/src/host.rs +++ b/lyra-scripting/src/host.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use lyra_ecs::{ResourceObject, Entity}; +use lyra_ecs::{ResourceObject, Entity, World}; use crate::ScriptWorldPtr; @@ -42,6 +42,9 @@ pub trait ScriptApiProvider { /// The type used as the script's context. type ScriptContext; + /// Prepare the ECS world for this api. Things like registering types with the type registry happen here. + fn prepare_world(&mut self, world: &mut World) {} + /// Exposes an API in the provided script context. fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>; diff --git a/lyra-scripting/src/lib.rs b/lyra-scripting/src/lib.rs index f08e7b4..197e95e 100644 --- a/lyra-scripting/src/lib.rs +++ b/lyra-scripting/src/lib.rs @@ -2,6 +2,7 @@ pub mod lua; pub mod world; +use lyra_ecs::Component; pub use world::*; pub mod wrap; @@ -18,9 +19,10 @@ use lyra_game::game::Game; #[allow(unused_imports)] pub(crate) mod lyra_engine { pub use lyra_ecs as ecs; + pub use lyra_reflect as reflect; } -use lyra_reflect::{ReflectedComponent, Reflect}; +use lyra_reflect::{ReflectedComponent, Reflect, FromType}; #[derive(Clone)] pub enum ReflectBranch { @@ -60,6 +62,20 @@ impl Clone for ScriptBorrow { } } +impl ScriptBorrow { + pub fn from_component(data: Option) -> Self + where + T: Reflect + Component + Default + 'static + { + let data = data.map(|d| Box::new(d) as Box<(dyn Reflect + 'static)>); + + Self { + reflect_branch: ReflectBranch::Component(>::from_type()), + data, + } + } +} + /// An extension trait that adds some helpful methods that makes it easier to do scripting things pub trait GameScriptExt { fn add_script_api_provider(&mut self, provider: P) @@ -69,12 +85,13 @@ pub trait GameScriptExt { } impl GameScriptExt for Game { - fn add_script_api_provider(&mut self, provider: P) + fn add_script_api_provider(&mut self, mut provider: P) where T: ScriptHost, P: ScriptApiProvider + 'static { let world = self.world(); + provider.prepare_world(world); let mut providers = world.get_resource_mut::>(); providers.add_provider(provider); } diff --git a/lyra-scripting/src/lua/dynamic_iter.rs b/lyra-scripting/src/lua/dynamic_iter.rs index 5b6b300..4e6e2cc 100644 --- a/lyra-scripting/src/lua/dynamic_iter.rs +++ b/lyra-scripting/src/lua/dynamic_iter.rs @@ -149,7 +149,7 @@ impl ReflectedIterator { unsafe { self.reflected_components.as_ref().unwrap().as_ref() }; let reg_type = reflected_components.get_type(id) - .expect("Could not find type for dynamic view!"); + .expect("Requested type was not found in TypeRegistry"); let proxy = reg_type.get_data::() .expect("Type does not have ReflectLuaProxy as a TypeData"); diff --git a/lyra-scripting/src/lua/mod.rs b/lyra-scripting/src/lua/mod.rs index ded46ce..3b3d6fb 100644 --- a/lyra-scripting/src/lua/mod.rs +++ b/lyra-scripting/src/lua/mod.rs @@ -4,7 +4,7 @@ pub use dynamic_iter::*; pub mod world; use lyra_game::{plugin::Plugin, game::GameStages}; use lyra_resource::ResourceManager; -use tracing::{debug, error}; +use tracing::{debug, error, trace}; pub use world::*; pub mod script; @@ -13,16 +13,16 @@ pub use script::*; pub mod loader; pub use loader::*; -pub mod modules; -pub use modules::*; +pub mod providers; +pub mod wrappers; #[cfg(test)] mod test; -use std::{ptr::NonNull, sync::Mutex}; +use std::{ptr::NonNull, sync::Mutex, any::TypeId}; use lyra_ecs::{DynamicBundle, World, query::{ResMut, View, Entities}}; -use lyra_reflect::{Reflect, FromType}; +use lyra_reflect::{Reflect, FromType, RegisteredType, TypeRegistry}; use mlua::{Lua, AnyUserDataExt}; @@ -33,6 +33,35 @@ pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect"; use crate::{ScriptBorrow, ScriptDynamicBundle, ScriptApiProviders, ScriptContexts, ScriptWorldPtr, ScriptList, ScriptData, ScriptHost, ScriptError, GameScriptExt}; +use self::providers::{UtilityApiProvider, LyraMathApiProvider, LyraEcsApiProvider}; + +pub trait RegisterLuaType { + /// Register a lua type that **is not wrapped**. + fn register_lua_type<'a, T: Reflect + LuaProxy + Clone + mlua::FromLua<'a> + mlua::UserData>(&mut self); + /// Registers a wrapped lua type. + /// You provide the wrapper as `W`, and the type that the wrapper wraps, as `T`. + fn register_lua_wrapper<'a, W: Reflect + LuaProxy + LuaWrapper + Clone + mlua::FromLua<'a> + mlua::UserData>(&mut self); +} + +impl RegisterLuaType for World { + fn register_lua_type<'a, T: Reflect + LuaProxy + Clone + mlua::FromLua<'a> + mlua::UserData>(&mut self) { + let mut registry = self.get_resource_mut::(); + + let type_id = TypeId::of::(); + + let reg_type = registry.get_type_or_default(type_id); + reg_type.add_data(>::from_type()); + //reg_type.add_data(>::from_type()); + } + + fn register_lua_wrapper<'a, W: Reflect + LuaProxy + LuaWrapper + Clone + mlua::FromLua<'a> + mlua::UserData>(&mut self) { + let mut registry = self.get_resource_mut::(); + + let reg_type = registry.get_type_or_default(W::wrapped_type_id()); + reg_type.add_data(>::from_type()); + } +} + impl<'lua> mlua::FromLua<'lua> for ScriptBorrow { fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result { match value { @@ -49,6 +78,11 @@ pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow { .expect("Type does not implement '__internal_reflect' properly") } +pub trait LuaWrapper { + /// The type id of the wrapped type. + fn wrapped_type_id() -> TypeId; +} + pub trait LuaProxy { fn as_lua_value<'lua>(lua: &'lua mlua::Lua, this: &dyn Reflect) -> mlua::Result>; fn apply(lua: &mlua::Lua, this: &mut dyn Reflect, apply: &mlua::AnyUserData) -> mlua::Result<()>; @@ -176,7 +210,7 @@ fn lua_call_script_function(world: &mut World, stage_name: &str) -> anyhow::Resu }; if let Some(ctx) = contexts.get_context_mut(script.id()) { - debug!("Running '{}' function in script '{}'", stage_name, script.name()); + trace!("Running '{}' function in script '{}'", stage_name, script.name()); match host.call_script(world_ptr.clone(), &script_data, ctx, &mut providers, stage_name) { Ok(()) => {}, Err(e) => match e { @@ -230,6 +264,8 @@ impl Plugin for LuaScriptingPlugin { fn setup(&self, game: &mut lyra_game::game::Game) { let world = game.world(); + world.add_resource_default::(); + world.add_resource_default::(); world.add_resource_default::>(); world.add_resource_default::>(); @@ -240,6 +276,8 @@ impl Plugin for LuaScriptingPlugin { drop(loader); game.add_script_api_provider::(UtilityApiProvider); + game.add_script_api_provider::(LyraEcsApiProvider); + game.add_script_api_provider::(LyraMathApiProvider); game .add_system_to_stage(GameStages::First, "lua_create_contexts", lua_scripts_create_contexts, &[]) diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs new file mode 100644 index 0000000..def90e8 --- /dev/null +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -0,0 +1,26 @@ +use crate::{lua::LuaContext, ScriptApiProvider, ScriptWorldPtr, ScriptDynamicBundle}; + +#[derive(Default)] +pub struct LyraEcsApiProvider; + +impl ScriptApiProvider for LyraEcsApiProvider { + type ScriptContext = LuaContext; + + fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + let ctx = ctx.lock().unwrap(); + + let globals = ctx.globals(); + globals.set("World", ctx.create_proxy::()?)?; + globals.set("DynamicBundle", ctx.create_proxy::()?)?; + + Ok(()) + } + + fn setup_script(&mut self, data: &crate::ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } + + fn update_script_environment(&mut self, world: crate::ScriptWorldPtr, data: &crate::ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } +} \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/math.rs b/lyra-scripting/src/lua/providers/math.rs new file mode 100644 index 0000000..a4be794 --- /dev/null +++ b/lyra-scripting/src/lua/providers/math.rs @@ -0,0 +1,108 @@ +use lyra_ecs::World; +use lyra_game::math; +use crate::lua::RegisterLuaType; +use crate::lua::wrappers::LuaVec3; + +use crate::{ScriptApiProvider, lua::LuaContext}; + +#[derive(Default)] +pub struct LyraMathApiProvider; + +impl ScriptApiProvider for LyraMathApiProvider { + type ScriptContext = LuaContext; + + fn prepare_world(&mut self, world: &mut World) { + world.register_lua_wrapper::(); + } + + fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + let ctx = ctx.lock().unwrap(); + + /* let bytes = include_bytes!("../../../scripts/lua/math/vec3.lua"); + ctx.load(bytes.to_vec()).exec()?; + + let bytes = include_bytes!("../../../scripts/lua/math/quat.lua"); + ctx.load(bytes.to_vec()).exec()?; + + let bytes = include_bytes!("../../../scripts/lua/math/transform.lua"); + ctx.load(bytes.to_vec()).exec()?; */ + + let globals = ctx.globals(); + globals.set("Vec3", ctx.create_proxy::()?)?; + //globals.set("Vec3", LuaVec3(math::Vec3::ZERO).into_lua(&ctx)?)?; + + Ok(()) + } + + fn setup_script(&mut self, data: &crate::ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } + + fn update_script_environment(&mut self, world: crate::ScriptWorldPtr, data: &crate::ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } +} + +/* #[derive(Clone, Copy, PartialEq, Debug, lyra_reflect::Reflect)] +pub struct LuaVec3(#[reflect(skip)] math::Vec3); + +impl From for LuaVec3 { + fn from(value: math::Vec3) -> Self { + Self(value) + } +} + +impl<'lua> mlua::IntoLua<'lua> for LuaVec3 { + fn into_lua(self, lua: &'lua mlua::prelude::Lua) -> mlua::prelude::LuaResult> { + let globals = lua.globals(); + + let v3 = globals.get::<_, mlua::Table>("Vec3")?; + let v3_new = v3.get::<_, mlua::Function>("new")?; + v3_new.call::<_, mlua::Table>((v3, self.0.x, self.0.y, self.0.z)) + .and_then(|t| t.into_lua(lua)) + } +} + +#[derive(Clone, Copy, PartialEq, Debug, lyra_reflect::Reflect)] +pub struct LuaQuat(#[reflect(skip)] math::Quat); + +impl From for LuaQuat { + fn from(value: math::Quat) -> Self { + Self(value) + } +} + +impl<'lua> mlua::IntoLua<'lua> for LuaQuat { + fn into_lua(self, lua: &'lua mlua::prelude::Lua) -> mlua::prelude::LuaResult> { + let globals = lua.globals(); + + let q = globals.get::<_, mlua::Table>("Quat")?; + let q_new = q.get::<_, mlua::Function>("new")?; + q_new.call::<_, mlua::Table>((q, self.0.x, self.0.y, self.0.z, self.0.w)) + .and_then(|t| t.into_lua(lua)) + } +} + +#[derive(Clone, Copy, Debug, lyra_reflect::Reflect)] +pub struct LuaTransform(#[reflect(skip)] math::Transform); + +impl From for LuaTransform { + fn from(value: math::Transform) -> Self { + Self(value) + } +} + +impl<'lua> mlua::IntoLua<'lua> for LuaTransform { + fn into_lua(self, lua: &'lua mlua::prelude::Lua) -> mlua::prelude::LuaResult> { + let globals = lua.globals(); + + let translation = LuaVec3(self.0.translation).into_lua(lua)?; + let rot = LuaQuat(self.0.rotation).into_lua(lua)?; + let scale = LuaVec3(self.0.scale).into_lua(lua)?; + + let transf = globals.get::<_, mlua::Table>("Transform")?; + let transf_new = transf.get::<_, mlua::Function>("new")?; + transf_new.call::<_, mlua::Table>((transf, translation, rot, scale)) + .and_then(|t| t.into_lua(lua)) + } +} */ \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/mod.rs b/lyra-scripting/src/lua/providers/mod.rs new file mode 100644 index 0000000..d8a3f49 --- /dev/null +++ b/lyra-scripting/src/lua/providers/mod.rs @@ -0,0 +1,8 @@ +pub mod util; +pub use util::*; + +pub mod math; +pub use math::*; + +pub mod ecs; +pub use ecs::*; \ No newline at end of file diff --git a/lyra-scripting/src/lua/modules/mod.rs b/lyra-scripting/src/lua/providers/util.rs similarity index 100% rename from lyra-scripting/src/lua/modules/mod.rs rename to lyra-scripting/src/lua/providers/util.rs diff --git a/lyra-scripting/src/lua/script.rs b/lyra-scripting/src/lua/script.rs index 457042f..612772e 100644 --- a/lyra-scripting/src/lua/script.rs +++ b/lyra-scripting/src/lua/script.rs @@ -1,7 +1,7 @@ use std::sync::Mutex; use mlua::IntoLua; -use tracing::debug; +use tracing::{debug, trace}; use crate::{ScriptHost, ScriptError, ScriptWorldPtr, ScriptEntity}; @@ -17,7 +17,7 @@ fn try_call_lua_function(lua: &mlua::Lua, fn_name: &str) -> Result<(), ScriptErr .map_err(ScriptError::MluaError)?; }, Err(mlua::Error::FromLuaConversionError { from: "nil", to: "function", message: None }) => { - debug!("Function '{}' was not found, ignoring...", fn_name) + trace!("Function '{}' was not found, ignoring...", fn_name) // ignore }, Err(e) => { diff --git a/lyra-scripting/src/lua/world.rs b/lyra-scripting/src/lua/world.rs index d1521ca..7011d21 100644 --- a/lyra-scripting/src/lua/world.rs +++ b/lyra-scripting/src/lua/world.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use lyra_ecs::query::dynamic::QueryDynamicType; use lyra_reflect::TypeRegistry; use mlua::{AnyUserDataExt, IntoLua, IntoLuaMulti}; @@ -73,7 +75,9 @@ impl mlua::UserData for ScriptWorldPtr { methods.add_method("view", |lua, this, (system, queries): (mlua::Function, mlua::Variadic)| { if queries.is_empty() { - panic!("No components were provided!"); + return Err(mlua::Error::BadArgument { to: Some("world:view".to_string()), pos: 2, name: Some("...".to_string()), cause: + Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage("no component types provided".to_string()))) + }); } let world = unsafe { this.inner.as_ref() }; diff --git a/lyra-scripting/src/lua/wrappers/mod.rs b/lyra-scripting/src/lua/wrappers/mod.rs new file mode 100644 index 0000000..1ff5e93 --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/mod.rs @@ -0,0 +1,365 @@ +use lyra_game::math; +use lyra_scripting_derive::wrap_math_vec_copy; +use crate::lyra_engine; + +use crate as lyra_scripting; + +// f32 types +/* wrap_math_vec_copy!( + math::Vec2, + derives(PartialEq), + fields(x, y), + metamethods( + Add(LuaVec2, f32), + Sub(LuaVec2, f32), + Div(LuaVec2, f32), + Mul(LuaVec2, f32), + Mod(LuaVec2, f32), + Eq, Unm + ) +); */ +wrap_math_vec_copy!( + math::Vec3, + derives(PartialEq), + fields(x, y, z), + metamethods( + ToString, + Eq, Unm + ) + /* metamethods( + Add(LuaVec3, f32), + Sub(LuaVec3, f32), + Div(LuaVec3, f32), + Mul(LuaVec3, f32), + Mod(LuaVec3, f32), + Eq, Unm + ) */ +); +/* wrap_math_vec_copy!( + math::Vec3A, + derives(PartialEq), + fields(x, y, z), + metamethods( + Add(LuaVec3A, f32), + Sub(LuaVec3A, f32), + Div(LuaVec3A, f32), + Mul(LuaVec3A, f32), + Mod(LuaVec3A, f32), + Eq, Unm + ) +); +wrap_math_vec_copy!( + math::Vec4, + derives(PartialEq), + fields(w, x, y, z), + metamethods( + Add(LuaVec4, f32), + Sub(LuaVec4, f32), + Div(LuaVec4, f32), + Mul(LuaVec4, f32), + Mod(LuaVec4, f32), + Eq, Unm + ) +); + +// f64 types +wrap_math_vec_copy!( + math::DVec2, + derives(PartialEq), + fields(x, y), + metamethods( + Add(LuaDVec2, f64), + Sub(LuaDVec2, f64), + Div(LuaDVec2, f64), + Mul(LuaDVec2, f64), + Mod(LuaDVec2, f64), + Eq, Unm + ) +); +wrap_math_vec_copy!( + math::DVec3, + derives(PartialEq), + fields(x, y, z), + metamethods( + Add(LuaDVec3, f64), + Sub(LuaDVec3, f64), + Div(LuaDVec3, f64), + Mul(LuaDVec3, f64), + Mod(LuaDVec3, f64), + Eq, Unm + ) +); +wrap_math_vec_copy!( + math::DVec4, + derives(PartialEq), + fields(w, x, y, z), + metamethods( + Add(LuaDVec4, f64), + Sub(LuaDVec4, f64), + Div(LuaDVec4, f64), + Mul(LuaDVec4, f64), + Mod(LuaDVec4, f64), + Eq, Unm + ) +); + +// i32 types +wrap_math_vec_copy!( + math::IVec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods( + Add(LuaIVec2, i32), + Sub(LuaIVec2, i32), + Div(LuaIVec2, i32), + Mul(LuaIVec2, i32), + Mod(LuaIVec2, i32), + Shl(LuaIVec2, LuaUVec2, i32), + Shr(LuaIVec2, LuaUVec2, i32), + BAnd(LuaIVec2, i32), + BOr(LuaIVec2, i32), + BXor(LuaIVec2, i32), + Eq, Unm, BNot + ) +); +wrap_math_vec_copy!( + math::IVec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods( + Add(LuaIVec3, i32), + Sub(LuaIVec3, i32), + Div(LuaIVec3, i32), + Mul(LuaIVec3, i32), + Mod(LuaIVec3, i32), + Shl(LuaIVec3, LuaUVec3, i32), + Shr(LuaIVec3, LuaUVec3, i32), + BAnd(LuaIVec3, i32), + BOr(LuaIVec3, i32), + BXor(LuaIVec3, i32), + Eq, Unm, BNot + ) +); +wrap_math_vec_copy!( + math::IVec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods( + Add(LuaIVec4, i32), + Sub(LuaIVec4, i32), + Div(LuaIVec4, i32), + Mul(LuaIVec4, i32), + Mod(LuaIVec4, i32), + Shl(LuaIVec4, LuaUVec4, i32), + Shr(LuaIVec4, LuaUVec4, i32), + BAnd(LuaIVec4, i32), + BOr(LuaIVec4, i32), + BXor(LuaIVec4, i32), + Eq, Unm, BNot + ) +); + +// u32 types +wrap_math_vec_copy!( + math::UVec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods( + Add(LuaUVec2, u32), + Sub(LuaUVec2, u32), + Div(LuaUVec2, u32), + Mul(LuaUVec2, u32), + Mod(LuaUVec2, u32), + Shl(LuaUVec2, LuaIVec2, i32), + Shr(LuaUVec2, LuaIVec2, i32), + BAnd(LuaUVec2, u32), + BOr(LuaUVec2, u32), + BXor(LuaUVec2, u32), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::UVec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods( + Add(LuaUVec3, u32), + Sub(LuaUVec3, u32), + Div(LuaUVec3, u32), + Mul(LuaUVec3, u32), + Mod(LuaUVec3, u32), + Shl(LuaUVec3, LuaIVec3, i32), + Shr(LuaUVec3, LuaIVec3, i32), + BAnd(LuaUVec3, u32), + BOr(LuaUVec3, u32), + BXor(LuaUVec3, u32), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::UVec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods( + Add(LuaUVec4, u32), + Sub(LuaUVec4, u32), + Div(LuaUVec4, u32), + Mul(LuaUVec4, u32), + Mod(LuaUVec4, u32), + Shl(LuaUVec4, LuaIVec4, i32), + Shr(LuaUVec4, LuaIVec4, i32), + BAnd(LuaUVec4, u32), + BOr(LuaUVec4, u32), + BXor(LuaUVec4, u32), + Eq, BNot + ) +); + +// i64 types +wrap_math_vec_copy!( + math::I64Vec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods( + Add(LuaI64Vec2, i64), + Sub(LuaI64Vec2, i64), + Div(LuaI64Vec2, i64), + Mul(LuaI64Vec2, i64), + Mod(LuaI64Vec2, i64), + Shl(i64), + Shr(i64), + BAnd(LuaI64Vec2, i64), + BOr(LuaI64Vec2, i64), + BXor(LuaI64Vec2, i64), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::I64Vec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods( + Add(LuaI64Vec3, i64), + Sub(LuaI64Vec3, i64), + Div(LuaI64Vec3, i64), + Mul(LuaI64Vec3, i64), + Mod(LuaI64Vec3, i64), + Shl(i64), + Shr(i64), + BAnd(LuaI64Vec3, i64), + BOr(LuaI64Vec3, i64), + BXor(LuaI64Vec3, i64), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::I64Vec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods( + Add(LuaI64Vec4, i64), + Sub(LuaI64Vec4, i64), + Div(LuaI64Vec4, i64), + Mul(LuaI64Vec4, i64), + Mod(LuaI64Vec4, i64), + Shl(i64), + Shr(i64), + BAnd(LuaI64Vec4, i64), + BOr(LuaI64Vec4, i64), + BXor(LuaI64Vec4, i64), + Eq, BNot + ) +); + +// u64 types +wrap_math_vec_copy!( + math::U64Vec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods( + Add(LuaU64Vec2, u64), + Sub(LuaU64Vec2, u64), + Div(LuaU64Vec2, u64), + Mul(LuaU64Vec2, u64), + Mod(LuaU64Vec2, u64), + Shl(i64), + Shr(i64), + BAnd(LuaU64Vec2, u64), + BOr(LuaU64Vec2, u64), + BXor(LuaU64Vec2, u64), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::U64Vec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods( + Add(LuaU64Vec3, u64), + Sub(LuaU64Vec3, u64), + Div(LuaU64Vec3, u64), + Mul(LuaU64Vec3, u64), + Mod(LuaU64Vec3, u64), + Shl(i64), + Shr(i64), + BAnd(LuaU64Vec3, u64), + BOr(LuaU64Vec3, u64), + BXor(LuaU64Vec3, u64), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::U64Vec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods( + Add(LuaU64Vec4, u64), + Sub(LuaU64Vec4, u64), + Div(LuaU64Vec4, u64), + Mul(LuaU64Vec4, u64), + Mod(LuaU64Vec4, u64), + Shl(i64), + Shr(i64), + BAnd(LuaU64Vec4, u64), + BOr(LuaU64Vec4, u64), + BXor(LuaU64Vec4, u64), + Eq, BNot + ) +); + +// bool types +wrap_math_vec_copy!( + math::BVec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods(Eq, BAnd, BOr, BXor, BNot) +); +wrap_math_vec_copy!( + math::BVec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods(Eq, BAnd, BOr, BXor, BNot) +); +wrap_math_vec_copy!( + math::BVec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods(Eq, BAnd, BOr, BXor, BNot) +); + +// mat2 +wrap_math_vec_copy!( + math::Mat2, + derives(PartialEq), + no_new, + matrix { + col_type = LuaVec2 + }, + metamethods( + Eq, + Add, + Sub, + Mul(LuaMat2, f32), + Unm + ) +); */ \ No newline at end of file