lua: create LuaConvert derive macro

This commit is contained in:
SeanOMik 2025-01-10 11:18:14 -05:00
parent 8248c4918a
commit 2ecb48c89e
Signed by: SeanOMik
GPG key ID: FEC9E2FC15235964
12 changed files with 766 additions and 187 deletions
crates
lyra-reflect/lyra-reflect-derive/src
lyra-scripting

View file

@ -1,7 +1,9 @@
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::{parse_macro_input, Attribute, DeriveInput, GenericParam, Generics, Path, TypeParamBound};
use syn::{
parse_macro_input, Attribute, DeriveInput, GenericParam, Generics, Path, TypeParamBound,
};
mod enum_derive;
#[allow(unused_imports)]
@ -24,15 +26,21 @@ impl FieldAttributes {
/// Searches for a usage of the 'reflect' attribute and returns a list of the
/// things used in the usage.
pub fn from_vec(v: &Vec<syn::Attribute>) -> Result<Self, syn::Error> {
let s: Result<Vec<ReflectAttribute>, _> = v.iter().filter_map(|att| match &att.meta {
syn::Meta::Path(_) => None,
syn::Meta::List(l) => {
Some(syn::parse::<ReflectAttribute>(l.tokens.clone().into()))
}
syn::Meta::NameValue(_) => None
}).collect();
let mut attrs = vec![];
Ok(Self(s?))
for attr in v {
if attr.path().is_ident("reflect") {
match &attr.meta {
syn::Meta::List(l) => {
let a = syn::parse::<ReflectAttribute>(l.tokens.clone().into())?;
attrs.push(a);
}
_ => {}
}
}
}
Ok(Self(attrs))
}
pub fn has_skip(&self) -> bool {
@ -42,7 +50,7 @@ impl FieldAttributes {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReflectAttribute {
Skip
Skip,
}
impl syn::parse::Parse for ReflectAttribute {
@ -52,7 +60,10 @@ impl syn::parse::Parse for ReflectAttribute {
match ident_str.as_str() {
"skip" => Ok(Self::Skip),
_ => Err(syn::Error::new(ident.span(), "Unknown reflect attribute flag"))
_ => Err(syn::Error::new(
ident.span(),
format!("Unknown reflect attribute flag: '{}'", ident_str),
)),
}
}
}
@ -62,7 +73,7 @@ pub(crate) struct ReflectDef {
//pub ident: Ident,
pub type_path: Path,
pub generics: Generics,
pub attributes: Vec<Attribute>
pub attributes: Vec<Attribute>,
}
impl syn::parse::Parse for ReflectDef {
@ -89,14 +100,10 @@ pub fn derive_reflect(input: proc_macro::TokenStream) -> proc_macro::TokenStream
let input = parse_macro_input!(input as DeriveInput);
let stream = match &input.data {
syn::Data::Enum(e) => {
enum_derive::derive_reflect_enum(&input, e)
},
syn::Data::Struct(s) => {
struct_derive::derive_reflect_struct(&input, s)
},
syn::Data::Enum(e) => enum_derive::derive_reflect_enum(&input, e),
syn::Data::Struct(s) => struct_derive::derive_reflect_struct(&input, s),
//syn::Data::Union(_) => todo!(),
_ => todo!()
_ => todo!(),
};
proc_macro::TokenStream::from(stream)
@ -111,7 +118,9 @@ pub fn impl_reflect_trait_value(input: TokenStream) -> TokenStream {
let type_path = reflect.type_path;
// convert the type path to a string. This would not create a leading separator
let type_path_str = {
let idents: Vec<String> = type_path.segments.iter()
let idents: Vec<String> = type_path
.segments
.iter()
.map(|segment| segment.ident.to_string())
.collect();
idents.join("::")
@ -124,15 +133,15 @@ pub fn impl_reflect_trait_value(input: TokenStream) -> TokenStream {
fn name(&self) -> ::std::string::String {
#type_path_str.to_string()
}
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<#type_path #ty_generics>()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
@ -140,7 +149,7 @@ pub fn impl_reflect_trait_value(input: TokenStream) -> TokenStream {
fn as_boxed_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
fn apply(&mut self, val: &dyn lyra_engine::reflect::Reflect) {
let val = val.as_any().downcast_ref::<Self>()
.expect("The type of `val` is not the same as `self`");
@ -170,7 +179,10 @@ pub fn impl_reflect_trait_value(input: TokenStream) -> TokenStream {
})
}
pub(crate) fn add_trait_bounds(mut generics: Generics, add_bounds: Vec<TypeParamBound>) -> Generics {
pub(crate) fn add_trait_bounds(
mut generics: Generics,
add_bounds: Vec<TypeParamBound>,
) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(ref mut type_param) = *param {
for bound in add_bounds.iter() {
@ -184,4 +196,4 @@ pub(crate) fn add_trait_bounds(mut generics: Generics, add_bounds: Vec<TypeParam
#[proc_macro]
pub fn impl_reflect_simple_struct(input: TokenStream) -> TokenStream {
struct_macro::impl_reflect_simple_struct(input)
}
}

View file

@ -1,3 +1,4 @@
use quote::quote;
use syn::{parenthesized, token, Token};
pub(crate) enum FieldType {
@ -107,6 +108,76 @@ impl Field {
Ok(s)
}
pub fn table_setter(&self) -> proc_macro2::TokenStream {
if self.skip_setter {
return quote!();
}
let ident = &self.field;
match &self.setter {
Some(set) => quote! {
table.set(stringify!(#ident), #set)?;
},
None => {
if let Some(wrap_with) = &self.wrap_with {
quote! {
let v = #wrap_with::into_lua(lua, &self.#ident)?;
table.set(stringify!(#ident), v)?;
}
} else if let Some(ty) = self.field_ty.get_type_path() {
let arg = if self.field_ty.is_wrapped() {
quote!(#ty(self.#ident.clone()))
} else {
quote!(self.#ident.clone())
};
quote! {
table.set(stringify!(#ident), #arg)?;
}
} else {
syn::Error::new_spanned(
ident,
format!("field type not specified: '{}'", ident.to_string()),
)
.into_compile_error()
}
}
}
}
pub fn table_getter(&self) -> proc_macro2::TokenStream {
let ident = &self.field;
match &self.getter {
Some(get) => {
quote! {
let #ident = #get;
}
}
None => match &self.wrap_with {
Some(wrap_with) => {
quote! {
let #ident = {
let t = table.get(stringify!(#ident))?;
#wrap_with::from_lua(_lua, &t)?
};
}
}
None => {
let ty = self
.field_ty
.get_type_path()
.expect("no field type specified");
quote! {
let #ident: #ty = table.get(stringify!(#ident))?;
}
}
},
}
}
}
impl syn::parse::Parse for Field {

View file

@ -7,11 +7,13 @@ use quote::quote;
use syn::{parse_macro_input, Token};
mod vec_wrapper;
use to_lua_derive::derive_lua_convert_impl;
use to_lua_macro::to_lua_struct_impl;
use vec_wrapper::VecWrapper;
mod field;
mod lua_macro;
mod to_lua_derive;
mod to_lua_macro;
mod handle_macro;
@ -20,15 +22,18 @@ pub(crate) const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_
pub(crate) const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect";
fn script_crate_path() -> proc_macro2::TokenStream {
let name =
proc_macro_crate::crate_name("lyra-scripting").expect("lyra-scripting is not a depdency");
let name = proc_macro_crate::crate_name("lyra-scripting"); //.expect("lyra-scripting is not a depdency");
match name {
proc_macro_crate::FoundCrate::Itself => quote!(crate),
proc_macro_crate::FoundCrate::Name(n) => {
Ok(proc_macro_crate::FoundCrate::Itself) => quote!(crate),
Ok(proc_macro_crate::FoundCrate::Name(n)) => {
let ident = syn::Ident::new(&n, Span::call_site());
quote!(#ident)
}
// assume that this crate is being used through an import of lyra_engine
Err(_) => {
quote!(lyra_engine::script)
}
}
}
@ -50,6 +55,11 @@ pub fn to_lua_convert(input: proc_macro::TokenStream) -> proc_macro::TokenStream
to_lua_struct_impl(input)
}
#[proc_macro_derive(LuaConvert, attributes(lua))]
pub fn derive_lua_convert(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_lua_convert_impl(input)
}
pub(crate) struct VecExtensionInputs {
#[allow(dead_code)]
pub type_path: syn::Path,

View file

@ -585,7 +585,7 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token
proc_macro::TokenStream::from(quote! {
#[derive(Clone, lyra_engine::reflect::Reflect, #(#derive_idents_iter),*)]
pub struct #wrapper_typename(#[reflect(skip)] pub(crate) #path);
pub struct #wrapper_typename(#[reflect(skip)] pub #path);
impl std::ops::Deref for #wrapper_typename {
type Target = #path;

View file

@ -0,0 +1,468 @@
use quote::{quote, ToTokens};
use syn::{parse_macro_input, spanned::Spanned};
use crate::{
field::{Field, FieldType},
to_lua_macro::{ReflectType, StructType},
};
pub(crate) fn derive_lua_convert_impl(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(item as syn::DeriveInput);
match &input.data {
syn::Data::Struct(s) => lua_convert_struct_impl(&input, s),
_ => todo!(),
}
}
fn lua_convert_struct_impl(
input: &syn::DeriveInput,
struc: &syn::DataStruct,
) -> proc_macro::TokenStream {
let crate_path = quote!(lyra_engine::script);
let mlua = quote!(#crate_path::lua::mlua);
let dattrs = DeriveAttrs::new(&input.attrs);
let ty = &input.ident;
let lua_name = dattrs.rename().cloned().unwrap_or(ty.to_string());
let reflect_type = dattrs.reflect_type();
let struct_type = match struc.fields {
syn::Fields::Named(_) => StructType::Fields,
syn::Fields::Unnamed(_) => StructType::Tuple,
syn::Fields::Unit => todo!("Handle unit structs"),
};
let fields = struc
.fields
.iter()
.map(|f| {
let attrs = FieldAttrs::new(&f.attrs);
let ident = f.ident.clone().expect("TODO: support enum structs");
let ty = if let Some(wrap) = attrs.wrapper() {
FieldType::Wrapped(wrap.clone())
} else {
match &f.ty {
syn::Type::Path(path) => FieldType::Type(path.path.clone()),
_ => todo!("struc.fields.map handle invalid field type"),
}
};
Field {
field: ident,
field_ty: ty,
skip_setter: false,
getter: attrs.get().cloned(),
setter: attrs.set().cloned(),
wrap_with: attrs.wrap_with().cloned(),
}
})
.collect::<Vec<Field>>();
let field_getters_iter = fields.iter().map(Field::table_getter);
let field_setters_iter = fields.iter().map(Field::table_setter);
let struct_creator = wrapper_creation(ty, struct_type, None, &fields);
let reflect_fn = get_reflect_lua_functions(&crate_path, &reflect_type, ty, true);
let reflect_type_fn = get_reflect_lua_functions(&crate_path, &reflect_type, ty, false);
quote! {
impl #mlua::FromLua for #ty {
fn from_lua(val: #mlua::Value, _lua: &#mlua::Lua) -> #mlua::Result<Self> {
let ty = val.type_name();
let table = val.as_table().ok_or(#mlua::Error::FromLuaConversionError {
from: ty,
to: "Table".into(),
message: Some("expected Table".into()),
})?;
#(
#field_getters_iter
)*
Ok(#struct_creator)
}
}
impl #mlua::IntoLua for #ty {
fn into_lua(self, lua: &#mlua::Lua) -> #mlua::Result<#mlua::Value> {
use #crate_path::lua::LuaWrapper;
let table = lua.create_table()?;
#(
#field_setters_iter
)*
table.set(
#crate_path::lua::FN_NAME_INTERNAL_REFLECT,
lua.create_function(|_, this: Self| {
#reflect_fn
})?,
)?;
table.set(
#crate_path::lua::FN_NAME_INTERNAL_REFLECT_TYPE,
lua.create_function(|_, ()| {
#reflect_type_fn
})?,
)?;
table.set(#mlua::MetaMethod::Type.name(), #lua_name)?;
Ok(#mlua::Value::Table(table))
}
}
impl #crate_path::lua::LuaWrapper for #ty {
type Wrap = #ty;
#[inline(always)]
fn wrapped_type_id() -> std::any::TypeId {
std::any::TypeId::of::<#ty>()
}
#[inline(always)]
fn into_wrapped(self) -> Self::Wrap {
self
}
}
}
.into_token_stream()
.into()
}
#[derive(Copy, Clone, PartialEq, Debug)]
enum LuaFuncKind {
Function,
Method,
MethodMut,
MetaMethod,
MetaMethodMut,
}
#[derive(Clone)]
struct LuaFunc {
kind: LuaFuncKind,
name: String,
// name: type
args: Vec<(syn::Ident, proc_macro2::TokenStream)>,
block: proc_macro2::TokenStream,
}
impl LuaFunc {
fn as_tokens(&self, builder: &syn::Ident) -> proc_macro2::TokenStream {
let fn_name = &self.name;
let fn_block = &self.block;
let (arg_names, arg_types): (Vec<_>, Vec<_>) = self.args.iter().cloned().unzip();
let arg_names_iter = arg_names.into_iter();
let arg_types_iter = arg_types.into_iter();
let closure_args = quote!( (#( #arg_names_iter ),*): (#( #arg_types_iter ),*) );
match self.kind {
LuaFuncKind::Function => quote! {
#builder.add_function(#fn_name, |lua, #closure_args| {
#fn_block
});
},
LuaFuncKind::Method => quote! {
#builder.add_method(#fn_name, |lua, this, #closure_args| {
#fn_block
});
},
LuaFuncKind::MethodMut => quote! {
#builder.add_method_mut(#fn_name, |lua, this, #closure_args| {
#fn_block
});
},
LuaFuncKind::MetaMethod => quote! {
#builder.add_meta_method(#fn_name, |lua, this, #closure_args| {
#fn_block
});
},
LuaFuncKind::MetaMethodMut => quote! {
#builder.add_meta_method_mut(#fn_name, |lua, this, #closure_args| {
#fn_block
});
},
}
}
}
#[derive(Clone)]
enum DeriveAttr {
Rename(String),
ReflectType(ReflectType),
}
impl syn::parse::Parse for DeriveAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident: syn::Ident = input.parse()?;
let ident_str = ident.to_string().to_lowercase();
let _eq: syn::Token![=] = input.parse()?;
match ident_str.as_str() {
"rename" => {
let s: syn::LitStr = input.parse()?;
Ok(Self::Rename(s.value()))
}
"reflect" => {
let tyid: syn::Ident = input.parse()?;
let id = tyid.to_string().to_lowercase();
let id = id.as_str();
let ty = match id {
"component" => Ok(ReflectType::Component),
"resource" => Ok(ReflectType::Resource),
_ => Err(syn::Error::new(
tyid.span(),
"unknown reflect type, expected 'component' or 'resource'",
)),
}?;
Ok(Self::ReflectType(ty))
}
_ => Err(syn::Error::new(
ident.span(),
format!("Unknown field attribute flag: '{}'", ident_str),
)),
}
}
}
#[derive(Default, Clone)]
struct DeriveAttrs(Vec<DeriveAttr>);
impl DeriveAttrs {
fn new(attrs: &Vec<syn::Attribute>) -> Self {
let mut s = Self::default();
for value in attrs {
if !value.path().is_ident("lua") {
continue;
}
match &value.meta {
syn::Meta::Path(_) => todo!("handle invalid path field attribute"),
syn::Meta::List(list) => {
let f = list
.parse_args_with(
syn::punctuated::Punctuated::<_, syn::Token![,]>::parse_terminated,
)
.unwrap();
s.0 = f.into_iter().collect();
}
syn::Meta::NameValue(_) => todo!("handle invalid name value field attribute"),
}
}
s
}
fn rename(&self) -> Option<&String> {
self.0.iter().find_map(|a| {
if let DeriveAttr::Rename(s) = a {
Some(s)
} else {
None
}
})
}
fn reflect_type(&self) -> ReflectType {
self.0
.iter()
.find_map(|a| {
if let DeriveAttr::ReflectType(r) = a {
Some(*r)
} else {
None
}
})
.unwrap_or_default()
}
}
#[derive(Clone)]
enum FieldAttr {
Wrapper(syn::Path),
Set(syn::Block),
Get(syn::Block),
WrapWith(syn::Path),
}
impl syn::parse::Parse for FieldAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident: syn::Ident = input.parse()?;
let ident_str = ident.to_string().to_lowercase();
let _eq: syn::Token![=] = input.parse()?;
match ident_str.as_str() {
"wrap" => Ok(Self::Wrapper(input.parse()?)),
"set" => Ok(Self::Set(input.parse()?)),
"get" => Ok(Self::Get(input.parse()?)),
"wrap_with" => Ok(Self::WrapWith(input.parse()?)),
_ => Err(syn::Error::new(
ident.span(),
format!("Unknown field attribute flag: '{}'", ident_str),
)),
}
}
}
#[derive(Default)]
struct FieldAttrs(Vec<FieldAttr>);
impl FieldAttrs {
fn new(attrs: &Vec<syn::Attribute>) -> Self {
let mut s = Self::default();
for value in attrs {
if !value.path().is_ident("lua") {
continue;
}
match &value.meta {
syn::Meta::Path(_) => todo!("handle invalid path field attribute"),
syn::Meta::List(list) => {
let f = list
.parse_args_with(
syn::punctuated::Punctuated::<_, syn::Token![,]>::parse_terminated,
)
.unwrap();
s.0 = f.into_iter().collect();
}
syn::Meta::NameValue(_) => todo!("handle invalid name value field attribute"),
}
}
s
}
fn wrapper(&self) -> Option<&syn::Path> {
self.0.iter().find_map(|a| {
if let FieldAttr::Wrapper(p) = a {
Some(p)
} else {
None
}
})
}
fn set(&self) -> Option<&syn::Block> {
self.0.iter().find_map(|a| {
if let FieldAttr::Set(b) = a {
Some(b)
} else {
None
}
})
}
fn get(&self) -> Option<&syn::Block> {
self.0.iter().find_map(|a| {
if let FieldAttr::Get(b) = a {
Some(b)
} else {
None
}
})
}
fn wrap_with(&self) -> Option<&syn::Path> {
self.0.iter().find_map(|a| {
if let FieldAttr::WrapWith(p) = a {
Some(p)
} else {
None
}
})
}
}
// impl From<&Vec<syn::Attribute>> for FieldAttrs {
// fn from(attrs: &Vec<syn::Attribute>) -> Self {
// let mut s = Self::default();
// for value in attrs {
// if !value.path().is_ident("lua") {
// continue;
// }
// match &value.meta {
// syn::Meta::Path(_) => todo!("handle invalid path field attribute"),
// syn::Meta::List(list) => {
// let f = list.parse_args_with(
// syn::punctuated::Punctuated::<FieldAttr, syn::Token![,]>::parse_terminated,
// ).unwrap();
// s.0 = f.into_iter().collect();
// }
// syn::Meta::NameValue(_) => todo!("handle invalid name value field attribute"),
// }
// s
// }
// }
pub(crate) fn get_reflect_lua_functions(
crate_path: &proc_macro2::TokenStream,
reflect_ty: &ReflectType,
ty: &syn::Ident,
set_data: bool,
) -> proc_macro2::TokenStream {
let data = if set_data {
quote!(Some(this.clone()))
} else {
quote!(None)
};
match reflect_ty {
ReflectType::Component => {
quote! {
Ok(#crate_path::ScriptBorrow::from_component::<#ty>(#data))
}
}
ReflectType::Resource => {
quote! {
Ok(#crate_path::ScriptBorrow::from_resource::<#ty>(#data))
}
}
}
}
pub(crate) fn wrapper_creation(
ty: &syn::Ident,
struct_type: StructType,
create: Option<&syn::Block>,
fields: &Vec<Field>,
) -> proc_macro2::TokenStream {
match create {
Some(b) => quote!(#b),
None => {
let field_iter = fields.iter().map(|f| {
let ident = &f.field;
if f.field_ty.is_wrapped() && struct_type == StructType::Fields {
quote!(#ident: (*#ident).clone())
} else {
quote!(#ident)
}
});
match struct_type {
StructType::Fields => {
quote! {
#ty {
#(
#field_iter
),*
}
}
}
StructType::Tuple => {
quote! {
#ty( #(#field_iter),* )
}
}
}
}
}
}

View file

@ -4,75 +4,7 @@ use syn::{braced, parenthesized, parse_macro_input, punctuated::Punctuated, toke
use crate::{field::Field, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE};
fn field_table_setter(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
let ident = &field.field;
match &field.setter {
Some(set) => Ok(quote! {
table.set(stringify!(#ident), #set)?;
}),
None => {
if let Some(ty) = field.field_ty.get_type_path() {
let arg = if field.field_ty.is_wrapped() {
quote!(#ty(self.#ident.clone()))
} else {
quote!(self.#ident.clone())
};
Ok(quote! {
table.set(stringify!(#ident), #arg)?;
})
} else {
match &field.wrap_with {
Some(wrap_with) => Ok(quote! {
let v = #wrap_with::into_lua(lua, &self.#ident)?;
table.set(stringify!(#ident), v)?;
}),
None => {
return Err(syn::Error::new_spanned(
ident,
format!("field type not specified: '{}'", ident.to_string()),
));
}
}
}
}
}
}
fn field_table_getter(field: &Field) -> proc_macro2::TokenStream {
let ident = &field.field;
match &field.getter {
Some(get) => {
quote! {
let #ident = #get;
}
}
None => match &field.wrap_with {
Some(wrap_with) => {
quote! {
let #ident = {
let t = table.get(stringify!(#ident))?;
#wrap_with::from_lua(_lua, &t)?
};
}
}
None => {
let ty = field
.field_ty
.get_type_path()
.expect("no field type specified");
quote! {
let #ident: #ty = table.get(stringify!(#ident))?;
}
}
},
}
}
fn wrapper_creation(
pub(crate) fn wrapper_creation(
wrapper: &syn::Ident,
type_path: &syn::Path,
struct_type: StructType,
@ -116,7 +48,7 @@ fn wrapper_creation(
}
}
fn get_reflect_lua_functions(
pub(crate) fn get_reflect_lua_functions(
crate_path: &proc_macro2::TokenStream,
ty: &ReflectType,
type_path: &syn::Path,
@ -142,16 +74,17 @@ fn get_reflect_lua_functions(
}
}
#[derive(Debug, Clone, Copy)]
enum ReflectType {
#[derive(Debug, Default, Clone, Copy)]
pub(crate) enum ReflectType {
//Unknown,
#[default]
Component,
Resource,
}
/// The type of the wrapping struct
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
enum StructType {
pub(crate) enum StructType {
#[default]
Fields,
Tuple,
@ -303,6 +236,7 @@ impl syn::parse::Parse for IntoLuaUsage {
pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as IntoLuaUsage);
let crate_path = crate::CRATE_PATH.clone();
let mlua = quote!(#crate_path::lua::mlua);
// unwrap is fine since `Some` is ensured in parse impl
let reflect_type = input.reflection_type.as_ref().unwrap();
@ -319,17 +253,8 @@ pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenSt
let derives_iter = input.derives.into_iter();
let lua_name = &input.table_name;
let field_getters_iter = input.fields.iter().map(|f| field_table_getter(f));
let field_setters_iter = input.fields.iter().map(|f| {
if f.skip_setter {
quote! {}
} else {
match field_table_setter(f) {
Ok(stream) => stream,
Err(e) => e.into_compile_error(),
}
}
});
let field_getters_iter = input.fields.iter().map(Field::table_getter);
let field_setters_iter = input.fields.iter().map(Field::table_setter);
let struct_creator = wrapper_creation(
&wrapper,
type_path,
@ -359,10 +284,10 @@ pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenSt
}
}
impl mlua::FromLua for #wrapper {
fn from_lua(val: mlua::Value, _lua: &mlua::Lua) -> mlua::Result<Self> {
impl #mlua::FromLua for #wrapper {
fn from_lua(val: #mlua::Value, _lua: &#mlua::Lua) -> #mlua::Result<Self> {
let ty = val.type_name();
let table = val.as_table().ok_or(mlua::Error::FromLuaConversionError {
let table = val.as_table().ok_or(#mlua::Error::FromLuaConversionError {
from: ty,
to: "Table".into(),
message: Some("expected Table".into()),
@ -376,8 +301,8 @@ pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenSt
}
}
impl mlua::IntoLua for #wrapper {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
impl #mlua::IntoLua for #wrapper {
fn into_lua(self, lua: &#mlua::Lua) -> #mlua::Result<#mlua::Value> {
use #crate_path::lua::LuaWrapper;
let table = lua.create_table()?;
@ -399,9 +324,9 @@ pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenSt
})?,
)?;
table.set(mlua::MetaMethod::Type.name(), #lua_name)?;
table.set(#mlua::MetaMethod::Type.name(), #lua_name)?;
Ok(mlua::Value::Table(table))
Ok(#mlua::Value::Table(table))
}
}

View file

@ -1,6 +1,9 @@
use std::{collections::HashMap, sync::{Arc, Mutex}};
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use lyra_ecs::{ResourceObject, Entity, World};
use lyra_ecs::{Entity, ResourceObject, World};
use crate::ScriptWorldPtr;
@ -55,27 +58,47 @@ pub trait ScriptApiProvider: Send + Sync {
}
/// Exposes an API in the provided script context.
fn expose_api(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
fn expose_api(
&mut self,
data: &ScriptData,
ctx: &mut Self::ScriptContext,
) -> Result<(), ScriptError>;
/// Setup a script right before its 'init' method is called.
fn setup_script(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
fn setup_script(
&mut self,
data: &ScriptData,
ctx: &mut Self::ScriptContext,
) -> Result<(), ScriptError>;
/// A function that is used to update a script's environment.
///
///
/// This is used to update stuff like the world pointer in the script context.
fn update_script_environment(&mut self, world: ScriptWorldPtr, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
fn update_script_environment(
&mut self,
world: ScriptWorldPtr,
data: &ScriptData,
ctx: &mut Self::ScriptContext,
) -> Result<(), ScriptError>;
}
/// A storage for a [`ScriptHost`]'s api providers
#[derive(Default)]
pub struct ScriptApiProviders<T: ScriptHost> {
pub apis: Vec<Arc<Mutex<dyn ScriptApiProvider<ScriptContext = T::ScriptContext>>>>,
}
impl<T: ScriptHost> Default for ScriptApiProviders<T> {
fn default() -> Self {
Self {
apis: Default::default(),
}
}
}
impl<T: ScriptHost> ScriptApiProviders<T> {
pub fn add_provider<P>(&mut self, provider: P)
where
P: ScriptApiProvider<ScriptContext = T::ScriptContext> + 'static
P: ScriptApiProvider<ScriptContext = T::ScriptContext> + 'static,
{
self.apis.push(Arc::new(Mutex::new(provider)));
}
@ -86,20 +109,32 @@ pub trait ScriptHost: Default + ResourceObject {
type ScriptContext;
/// Loads a script and creates a context for it.
///
///
/// Before the script is executed, the API providers are exposed to the script.
fn load_script(&mut self, script: &[u8], script_data: &ScriptData,
providers: &mut crate::ScriptApiProviders<Self>)
-> Result<Self::ScriptContext, ScriptError>;
fn load_script(
&mut self,
script: &[u8],
script_data: &ScriptData,
providers: &mut crate::ScriptApiProviders<Self>,
) -> Result<Self::ScriptContext, ScriptError>;
/// Setup a script for execution.
fn setup_script(&mut self, script_data: &ScriptData, ctx: &mut Self::ScriptContext,
providers: &mut ScriptApiProviders<Self>) -> Result<(), ScriptError>;
fn setup_script(
&mut self,
script_data: &ScriptData,
ctx: &mut Self::ScriptContext,
providers: &mut ScriptApiProviders<Self>,
) -> Result<(), ScriptError>;
/// Calls a event function in the script.
fn call_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData,
ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders<Self>,
event_fn_name: &str) -> Result<(), ScriptError>;
fn call_script(
&mut self,
world: ScriptWorldPtr,
script_data: &crate::ScriptData,
ctx: &mut Self::ScriptContext,
providers: &mut crate::ScriptApiProviders<Self>,
event_fn_name: &str,
) -> Result<(), ScriptError>;
}
#[derive(Default)]
@ -109,9 +144,7 @@ pub struct ScriptContexts<T> {
impl<T> ScriptContexts<T> {
pub fn new(contexts: HashMap<u64, T>) -> Self {
Self {
contexts,
}
Self { contexts }
}
pub fn add_context(&mut self, script_id: u64, context: T) {
@ -137,4 +170,4 @@ impl<T> ScriptContexts<T> {
pub fn len(&self) -> usize {
self.contexts.len()
}
}
}

View file

@ -18,6 +18,8 @@ pub use script::*;
use lyra_game::game::App;
pub use lyra_scripting_derive::*;
// required for some proc macros :(
#[allow(unused_imports)]
pub(crate) mod lyra_engine {
@ -146,7 +148,7 @@ impl GameScriptExt for App {
{
let world = &mut self.world;
provider.prepare_world(world);
let mut providers = world.get_resource_mut::<ScriptApiProviders<T>>().unwrap();
let mut providers = world.get_resource_or_default::<ScriptApiProviders<T>>();
providers.add_provider(provider);
}
}

View file

@ -29,19 +29,18 @@ use wrappers::{LuaHandleWrapper, LuaResHandleToComponent, LuaWrappedEventProxy};
use std::{any::TypeId, sync::Mutex};
use crate::ScriptBorrow;
use lyra_ecs::World;
use lyra_reflect::{FromType, Reflect, TypeRegistry};
use crate::ScriptBorrow;
pub use mlua;
pub type LuaContext = Mutex<mlua::Lua>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("mismatched type, expected `{expected}`, got: `{got}`")]
TypeMismatch {
expected: String,
got: String,
},
TypeMismatch { expected: String, got: String },
#[error("received nil value from Lua")]
Nil,
#[error("{0}")]
@ -65,14 +64,17 @@ impl From<Error> for mlua::Error {
fn from(value: Error) -> Self {
match value {
Error::Mlua(error) => error,
_ => mlua::Error::external(value)
_ => mlua::Error::external(value),
}
}
}
impl Error {
pub fn type_mismatch(expected: &str, got: &str) -> Self {
Self::TypeMismatch { expected: expected.into(), got: got.into() }
Self::TypeMismatch {
expected: expected.into(),
got: got.into(),
}
}
pub fn unimplemented(msg: &str) -> Self {
@ -81,55 +83,55 @@ impl Error {
}
/// Name of a Lua function that is used to Reflect the Userdata, but without a value.
///
///
/// This is used for reflecting the userdata as an ECS Component or Resource. This **function**
/// returns a [`ScriptBorrow`] with data as `None`.
/// returns a [`ScriptBorrow`] with data as `None`.
pub const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type";
/// Name of a Lua function that is used to Reflect the Userdata.
///
///
/// This is used for reflecting the userdata as an ECS Component or Resource. This **method**
/// returns a [`ScriptBorrow`] with data as `Some`. **Anything that calls this expects the
/// method to return data**.
pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect";
/// Name of a Lua function to retrieve the query result from a Userdata, or Table.
///
///
/// The function must match the following definition: `fn(ScriptWorldPtr, Entity) -> LuaValue`.
///
///
/// When `nil` is returned, its considered that the query will not result in anything for this
/// [`View`], **no matter the entity**. When the query is used in a [`View`] and returns `nil`,
/// it will NOT check for other entities. This is used in the [`ResQuery`] Lua query. If the
/// resource is missing, it will always be missing for the [`View`], no matter the entity.
///
///
/// If it returns a boolean, the query will act as a filter. The boolean value will not be in the
/// result. When the boolean is `false`, other entities will be checked by the [`View`].
///
///
/// Any other value will be included in the result.
pub const FN_NAME_INTERNAL_ECS_QUERY_RESULT: &str = "__lyra_internal_ecs_query_result";
/// Name of a Lua function implemented for Userdata types that can be made into components.
///
///
/// This is used for types that can be converted into components. When implementing this function,
/// you must return a [`ScriptBorrow`] that contains the component for this userdata.
/// You can return [`mlua::Value::Nil`] if for some reason the type could not be converted
/// into a component.
///
///
/// A good example of this is `LuaResHandle`. The resource handle is requested from the
/// world, and could be a 3d model. The 3d model could then be manually wrapped as
/// [`LuaModelComponent`] with its `new` function. But for quality of life, this internal
/// function was created so that the userdata can be converted into its component
/// type without having to wrap it.
///
///
/// Without implementing this function:
/// ```lua
/// local cube = world:request_res("assets/cube-texture-embedded.gltf")
/// local cube_comp = ModelComponent.new(cube) -- annoying to write
///
///
/// local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
/// world:spawn(pos, cube_comp)
/// ```
///
///
/// With this function:
/// ```lua
/// local cube = world:request_res("assets/cube-texture-embedded.gltf")
@ -157,7 +159,7 @@ pub trait RegisterLuaType {
T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static;
/// Registers a type that can be converted to and from lua and adds a lookup entry.
///
///
/// This is a shortcut for `register_lua_convert` and `add_component_lookup_entry`.
fn register_lua_convert_component<T>(&mut self, name: &str)
where
@ -190,7 +192,7 @@ pub trait RegisterLuaType {
impl RegisterLuaType for World {
fn register_lua_type<'a, T>(&mut self)
where
T: Reflect + LuaProxy + Clone + mlua::FromLua + mlua::UserData
T: Reflect + LuaProxy + Clone + mlua::FromLua + mlua::UserData,
{
let mut registry = self.get_resource_mut::<TypeRegistry>().unwrap();
@ -202,13 +204,13 @@ impl RegisterLuaType for World {
fn register_lua_wrapper<'a, W>(&mut self)
where
W: Reflect + LuaProxy + LuaWrapper + Clone + mlua::FromLua + mlua::UserData
W: Reflect + LuaProxy + LuaWrapper + Clone + mlua::FromLua + mlua::UserData,
{
let mut registry = self.get_resource_mut::<TypeRegistry>().unwrap();
let reg_type = registry.get_type_or_default(W::wrapped_type_id());
reg_type.add_data(ReflectLuaProxy::from_lua_proxy::<W>());
}
}
fn register_lua_convert<T>(&mut self)
where
@ -223,7 +225,7 @@ impl RegisterLuaType for World {
fn register_lua_convert_component<T>(&mut self, name: &str)
where
T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static,
T::Wrap: lyra_ecs::Component + Reflect
T::Wrap: lyra_ecs::Component + Reflect,
{
self.register_lua_convert::<T>();
self.add_component_lookup_entry::<T::Wrap>(name);
@ -232,7 +234,7 @@ impl RegisterLuaType for World {
fn register_asset_handle<T>(&mut self, name: &str)
where
T: LuaHandleWrapper + Reflect + LuaProxy,
T::ResourceType: ResourceData
T::ResourceType: ResourceData,
{
{
let mut registry = self.get_resource_mut::<TypeRegistry>().unwrap();
@ -248,7 +250,7 @@ impl RegisterLuaType for World {
fn add_lua_event<T>(&mut self, name: &str)
where
T: Reflect + LuaWrapper + mlua::FromLua + mlua::IntoLua + Send + Sync,
T::Wrap: Clone + lyra_game::Event
T::Wrap: Clone + lyra_game::Event,
{
{
let mut registry = self.get_resource_mut::<TypeRegistry>().unwrap();
@ -262,19 +264,25 @@ impl RegisterLuaType for World {
fn add_component_lookup_entry<T>(&mut self, name: &str)
where
T: lyra_ecs::Component
T: lyra_ecs::Component,
{
let mut lookup = self.get_resource_or_default::<TypeLookup>();
lookup.comp_info_from_name.insert(name.into(), lyra_ecs::ComponentInfo::new::<T>());
lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::<T>());
lookup
.comp_info_from_name
.insert(name.into(), lyra_ecs::ComponentInfo::new::<T>());
lookup
.typeid_from_name
.insert(name.into(), std::any::TypeId::of::<T>());
}
fn add_lookup_entry<T>(&mut self, name: &str)
where
T: 'static
T: 'static,
{
let mut lookup = self.get_resource_or_default::<TypeLookup>();
lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::<T>());
lookup
.typeid_from_name
.insert(name.into(), std::any::TypeId::of::<T>());
}
}
@ -287,22 +295,34 @@ impl mlua::FromLua for ScriptBorrow {
}
}
impl mlua::UserData for ScriptBorrow {
}
impl mlua::UserData for ScriptBorrow {}
/// Helper function used for reflecting userdata as a ScriptBorrow
pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
let ud_name = ud.metatable().and_then(|mt| mt.get::<String>(mlua::MetaMethod::Type))
let ud_name = ud
.metatable()
.and_then(|mt| mt.get::<String>(mlua::MetaMethod::Type))
.unwrap_or("Unknown".to_string());
ud.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
.unwrap_or_else(|_| panic!("UserData of name '{}' does not implement internal reflect method properly", ud_name))
.unwrap_or_else(|_| {
panic!(
"UserData of name '{}' does not implement internal reflect method properly",
ud_name
)
})
}
/// Helper function used for reflecting userdata type as a ScriptBorrow
pub fn reflect_type_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
let ud_name = ud.metatable().and_then(|mt| mt.get::<String>(mlua::MetaMethod::Type))
let ud_name = ud
.metatable()
.and_then(|mt| mt.get::<String>(mlua::MetaMethod::Type))
.unwrap_or("Unknown".to_string());
ud.call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.unwrap_or_else(|_| panic!("UserData of name '{}' does not implement internal reflect type function properly", ud_name))
}
.unwrap_or_else(|_| {
panic!(
"UserData of name '{}' does not implement internal reflect type function properly",
ud_name
)
})
}

View file

@ -1,8 +1,8 @@
use lyra_ecs::ResourceObject;
use lyra_reflect::Reflect;
use sprite::{
LuaActiveAtlasAnimation, LuaAtlasAnimations, LuaAtlasSprite, LuaSprite, LuaSpriteAnimation,
LuaTextureAtlasHandle, LuaTile, LuaTileMap, LuaTileMapPos,
LuaActiveAtlasAnimation, LuaAtlasAnimations, LuaAtlasAnimationsHandle, LuaAtlasSprite,
LuaSprite, LuaSpriteAnimation, LuaTextureAtlasHandle, LuaTile, LuaTileMap, LuaTileMapPos,
};
use crate::{
@ -47,6 +47,7 @@ impl ScriptApiProvider for LyraEcsApiProvider {
world.register_lua_convert_component::<LuaTileMapPos>("TileMapPos");
world.register_lua_convert_component::<LuaSpriteAnimation>("SpriteAnimation");
world.register_lua_convert_component::<LuaAtlasAnimations>("AtlasAnimations");
world.register_asset_handle::<LuaAtlasAnimationsHandle>("AtlasAnimationsHandle");
world.register_lua_wrapper::<LuaActiveAtlasAnimation>();
world.register_lua_convert_component::<LuaTile>("Tile");
world.register_asset_handle::<LuaTextureAtlasHandle>("TextureAtlas");
@ -175,7 +176,7 @@ where
///
/// This creates the reflection functions on a table specified in globals.
/// The table name is set to `name`, which is also how the script will use the table.
fn expose_comp_table_wrapper<T>(
pub fn expose_comp_table_wrapper<T>(
lua: &mlua::Lua,
globals: &mlua::Table,
name: &str,

View file

@ -32,6 +32,21 @@ pub mod wrap_uvec2_using_luavec2 {
}
}
pub mod wrap_ivec2_using_luavec2 {
pub type FromType = LuaVec2;
type BaseTy = lyra_game::math::IVec2;
use crate::lua::wrappers::LuaVec2;
use mlua::IntoLua;
pub fn from_lua(_: &mlua::Lua, val: &FromType) -> mlua::Result<BaseTy> {
Ok(val.0.as_ivec2())
}
pub fn into_lua(lua: &mlua::Lua, val: &BaseTy) -> mlua::Result<mlua::Value> {
LuaVec2(val.as_vec2()).into_lua(lua)
}
}
/// Wrap a field of type `URect` from a `LuaRect`.
pub mod wrap_urect_using_luarect {
pub type FromType = LuaRect;

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use lyra_game::math::URect;
use lyra_game::sprite::{ActiveAtlasAnimation, AtlasAnimations, SpriteAnimation};
use lyra_scripting_derive::{to_lua_convert, wrap_lua_struct};
use lyra_scripting_derive::{lua_wrap_handle, to_lua_convert, wrap_lua_struct};
use mlua::FromLuaMulti;
use super::LuaTextureAtlasHandle;
@ -41,6 +41,28 @@ to_lua_convert!(
}
);
lua_wrap_handle!(
AtlasAnimations,
field_getters={
// ResHandle<TextureAtlas>
(atlas, {
LuaTextureAtlasHandle(data.atlas.clone()).into_lua(lua)
}),
// HashMap<String, SpriteAnimation>
(animations, {
let anims = lua.create_table()?;
for (key, val) in &data.animations {
anims.raw_set(key.clone(), LuaSpriteAnimation(val.clone()))?;
}
Ok(mlua::Value::Table(anims))
}),
(sprite_pivot, {
super::wrap_pivot_enum::into_lua(lua, &data.sprite_pivot)
})
}
);
// TODO: create AtlasAnimations::from_animations and `get_active` in Lua
to_lua_convert!(
// Struct that is being wrapped