Expose structs to Lua and write Lua type annotations #28

Merged
SeanOMik merged 15 commits from feat/lua-type-defs into main 2024-10-19 15:17:00 +00:00
8 changed files with 219 additions and 76 deletions
Showing only changes of commit 49dfb38da3 - Show all commits

View File

@ -1,10 +1,10 @@
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{braced, parenthesized, parse_macro_input, token, Ident, Path, Token}; use syn::{braced, parenthesized, parse_macro_input, token, Ident, Path, Token};
struct FieldGetter { pub(crate) struct FieldGetter {
field: Ident, pub field: Ident,
body: Option<syn::Block>, pub body: Option<syn::Block>,
wrapper_type: Option<syn::Path>, pub wrapper_type: Option<syn::Path>,
} }
impl FieldGetter { impl FieldGetter {
@ -131,6 +131,7 @@ impl syn::parse::Parse for HandleWrapUsage {
pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as HandleWrapUsage); let input = parse_macro_input!(input as HandleWrapUsage);
let handle_path = &input.type_path;
let handle_name = &input.type_path.segments.last().unwrap().ident; let handle_name = &input.type_path.segments.last().unwrap().ident;
let base_name = input.override_name.unwrap_or_else(|| handle_name.clone()); let base_name = input.override_name.unwrap_or_else(|| handle_name.clone());
@ -145,7 +146,7 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro
let field_creator = match &g.wrapper_type { let field_creator = match &g.wrapper_type {
Some(wrap) => { Some(wrap) => {
quote!(#wrap(data.#field).into_lua(lua)) quote!(#wrap(data.#field.clone()).into_lua(lua))
}, },
None => match &g.body { None => match &g.body {
Some(body) => { Some(body) => {
@ -170,18 +171,18 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro
quote! { quote! {
#[derive(Clone, Reflect)] #[derive(Clone, Reflect)]
pub struct #wrapper_name(pub ResHandle<#handle_name>); pub struct #wrapper_name(pub ResHandle<#handle_path>);
impl Deref for #wrapper_name { impl Deref for #wrapper_name {
type Target = ResHandle<#handle_name>; type Target = ResHandle<#handle_path>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }
} }
impl From<ResHandle<#handle_name>> for #wrapper_name { impl From<ResHandle<#handle_path>> for #wrapper_name {
fn from(value: ResHandle<#handle_name>) -> Self { fn from(value: ResHandle<#handle_path>) -> Self {
#wrapper_name(value) #wrapper_name(value)
} }
} }
@ -224,7 +225,7 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro
}); });
methods.add_function(FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| { methods.add_function(FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| {
Ok(ScriptBorrow::from_component::<ResHandle<#handle_name>>(None)) Ok(ScriptBorrow::from_component::<ResHandle<#handle_path>>(None))
}); });
methods.add_method(FN_NAME_INTERNAL_REFLECT, |_, this, ()| { methods.add_method(FN_NAME_INTERNAL_REFLECT, |_, this, ()| {
Ok(ScriptBorrow::from_component(Some(this.0.clone()))) Ok(ScriptBorrow::from_component(Some(this.0.clone())))
@ -247,7 +248,7 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro
impl LuaWrapper for #wrapper_name { impl LuaWrapper for #wrapper_name {
fn wrapped_type_id() -> std::any::TypeId { fn wrapped_type_id() -> std::any::TypeId {
TypeId::of::<ResHandle<#handle_name>>() TypeId::of::<ResHandle<#handle_path>>()
} }
} }
}.into() }.into()

View File

@ -1,8 +1,8 @@
use proc_macro2::Span; use proc_macro2::Span;
use quote::quote; use quote::quote;
use syn::{parenthesized, parse_macro_input, punctuated::Punctuated, token, Ident, Path, Token}; use syn::{braced, parenthesized, parse_macro_input, punctuated::Punctuated, token, Ident, Path, Token};
use crate::{FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}; use crate::{handle_macro::FieldGetter, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE};
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
enum SkipType { enum SkipType {
@ -297,6 +297,7 @@ struct WrapUsage {
/// The extra derives of the type. /// The extra derives of the type.
override_name: Option<Ident>, override_name: Option<Ident>,
auto_fields: Vec<Ident>, auto_fields: Vec<Ident>,
manual_field_getters: Vec<FieldGetter>,
auto_derives: Vec<Ident>, auto_derives: Vec<Ident>,
auto_new: bool, auto_new: bool,
meta_methods: Vec<MetaMethod>, meta_methods: Vec<MetaMethod>,
@ -314,6 +315,7 @@ impl syn::parse::Parse for WrapUsage {
override_name: None, override_name: None,
auto_fields: vec![], auto_fields: vec![],
auto_derives: vec![], auto_derives: vec![],
manual_field_getters: vec![],
extra_fields: None, extra_fields: None,
extra_methods: None, extra_methods: None,
auto_new: false, auto_new: false,
@ -384,6 +386,17 @@ impl syn::parse::Parse for WrapUsage {
let _eq: Token![=] = input.parse()?; let _eq: Token![=] = input.parse()?;
s.extra_methods = Some(input.parse::<syn::Block>()?); s.extra_methods = Some(input.parse::<syn::Block>()?);
}, },
"field_getters" => {
let _eq: Token![=] = input.parse()?;
if input.peek(token::Brace) {
let content;
let _braced: token::Brace = braced!(content in input);
let terminated = content.parse_terminated(FieldGetter::parse, Token![,])?;
s.manual_field_getters = terminated.into_iter().collect();
}
}
_ => { _ => {
return Err(syn::Error::new_spanned(ident, format!("unknown wrapper command: '{}'", ident_str))); return Err(syn::Error::new_spanned(ident, format!("unknown wrapper command: '{}'", ident_str)));
} }
@ -469,6 +482,30 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token
} }
}; };
let custom_getters = input.manual_field_getters.iter().map(|g| {
let field = &g.field;
let field_creator = match &g.wrapper_type {
Some(wrap) => {
quote!(#wrap(this.#field).into_lua(lua))
},
None => match &g.body {
Some(body) => {
quote!(#body)
},
None => {
quote!(this.#field.clone().into_lua(lua))
}
}
};
quote! {
fields.add_field_method_get(stringify!($field), |lua, this| {
#field_creator
});
}
});
proc_macro::TokenStream::from(quote! { proc_macro::TokenStream::from(quote! {
#[derive(Clone, lyra_reflect::Reflect, #(#derive_idents_iter),*)] #[derive(Clone, lyra_reflect::Reflect, #(#derive_idents_iter),*)]
pub struct #wrapper_typename(#[reflect(skip)] pub(crate) #path); pub struct #wrapper_typename(#[reflect(skip)] pub(crate) #path);
@ -499,6 +536,7 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token
impl mlua::UserData for #wrapper_typename { impl mlua::UserData for #wrapper_typename {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
#(#field_get_set_pairs)* #(#field_get_set_pairs)*
#(#custom_getters)*
#extra_fields #extra_fields
} }

View File

@ -1,9 +0,0 @@
---@enum ActionState
ActionState = {
IDLE = "idle",
PRESSED = "pressed",
JUST_PRESSED = "just_pressed",
JUST_RELEASED = "just_released",
AXIS = "axis",
OTHER = "other",
}

View File

@ -1,12 +0,0 @@
---@enum HandleState
HandleState = {
LOADING = "loading",
READY = "ready",
ERROR = "error",
}
---@enum ActionKind
ActionKind = {
BUTTON = "button",
AXIS = "axis",
}

View File

@ -0,0 +1,63 @@
---@enum WindowMode
WindowMode = {
WNDOWED = "windowed",
BORDERLESS_FULLSCREEN = "borderless_fullscreen",
SIZED_FULLSCREEN = "sized_fullscreen",
FULLSCREEN = "fullscreen",
}
---@enum CursorGrabMode
CursorGrabMode = {
NONE = "none",
CONFINED = "confined",
LOCKED = "locked",
}
---@enum WindowTheme
WindowTheme = {
LIGHT = "light",
DARK = "dark",
}
---@enum WindowLevel
WindowLevel = {
ALWAYS_ON_BOTTOM = "always_on_bottom",
NORMAL = "normal",
ALWAYS_ON_TOP = "always_on_top",
}
---@enum HandleState
HandleState = {
LOADING = "loading",
READY = "ready",
ERROR = "error",
}
---@enum ActionKind
ActionKind = {
BUTTON = "button",
AXIS = "axis",
}
---@enum ActionState
ActionState = {
IDLE = "idle",
PRESSED = "pressed",
JUST_PRESSED = "just_pressed",
JUST_RELEASED = "just_released",
AXIS = "axis",
OTHER = "other",
}
---@enum FilterMode
FilterMode = {
NEAREST = "nearest",
LINEAR = "linear",
}
---@enum WrappingMode
WrappingMode = {
CLAMP_TO_EDGE = "clamp_to_edge",
MIRRORED_REPEAT = "mirrored_repeat",
REPEAT = "repeat",
}

View File

@ -1,27 +0,0 @@
---@enum WindowMode
WindowMode = {
WNDOWED = "windowed",
BORDERLESS_FULLSCREEN = "borderless_fullscreen",
SIZED_FULLSCREEN = "sized_fullscreen",
FULLSCREEN = "fullscreen",
}
---@enum CursorGrabMode
CursorGrabMode = {
NONE = "none",
CONFINED = "confined",
LOCKED = "locked",
}
---@enum WindowTheme
WindowTheme = {
LIGHT = "light",
DARK = "dark",
}
---@enum WindowLevel
WindowLevel = {
ALWAYS_ON_BOTTOM = "always_on_bottom",
NORMAL = "normal",
ALWAYS_ON_TOP = "always_on_top",
}

View File

@ -40,12 +40,8 @@ impl ScriptApiProvider for LyraEcsApiProvider {
fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
let ctx = ctx.lock().unwrap(); let ctx = ctx.lock().unwrap();
// load window enums // load enums
let bytes = include_str!("../../../scripts/lua/window.lua"); let bytes = include_str!("../../../scripts/lua/enums.lua");
ctx.load(bytes).exec().unwrap();
// load asset handle enums
let bytes = include_str!("../../../scripts/lua/asset_handle.lua");
ctx.load(bytes).exec().unwrap(); ctx.load(bytes).exec().unwrap();
let globals = ctx.globals(); let globals = ctx.globals();

View File

@ -1,6 +1,6 @@
use std::{any::TypeId, ops::Deref}; use std::{any::TypeId, ops::Deref};
//use mlua::{AnyUserData, IntoLua, FromLua, Lua, Value}; //use mlua::{AnyUserData, IntoLua, FromLua, Lua, Value};
use lyra_resource::{gltf::{Gltf, Material, Mesh}, Texture, ResHandle, UntypedResHandle}; use lyra_resource::{gltf::{Gltf, Material, Mesh}, FilterMode, ResHandle, Texture, UntypedResHandle, WrappingMode};
use lyra_game::scene::SceneGraph; use lyra_game::scene::SceneGraph;
use lyra_reflect::{Reflect, TypeData}; use lyra_reflect::{Reflect, TypeData};
use lyra_scripting_derive::{lua_wrap_handle, wrap_lua_struct}; use lyra_scripting_derive::{lua_wrap_handle, wrap_lua_struct};
@ -111,15 +111,87 @@ impl mlua::FromLua for LuaResHandle {
} }
} }
// TODO: fields fn filter_mode_to_str(fm: FilterMode) -> &'static str {
wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness, skip(lua_reflect)); // doesn't need internal lua reflection methods match fm {
// TODO: fields FilterMode::Nearest => "nearest",
wrap_lua_struct!(lyra_resource::gltf::Specular, skip(lua_reflect)); // doesn't need internal lua reflection methods FilterMode::Linear => "linear",
}
}
fn wrapping_mode_to_str(wm: WrappingMode) -> &'static str {
match wm {
WrappingMode::ClampToEdge => "clamp_to_edge",
WrappingMode::MirroredRepeat => "mirrored_repeat",
WrappingMode::Repeat => "repeat",
}
}
wrap_lua_struct!(lyra_resource::TextureSampler,
// this can be safely skipped since it wont be a component or resource.
skip(lua_reflect),
field_getters={
(mag_filter, {
this.mag_filter.map(|f| filter_mode_to_str(f))
.into_lua(lua)
}),
(min_filter, {
this.min_filter.map(|f| filter_mode_to_str(f))
.into_lua(lua)
}),
(mipmap_filter, {
this.mipmap_filter.map(|f| filter_mode_to_str(f))
.into_lua(lua)
}),
(wrap_u, {
wrapping_mode_to_str(this.wrap_u)
.into_lua(lua)
}),
(wrap_v, {
wrapping_mode_to_str(this.wrap_v)
.into_lua(lua)
}),
(wrap_w, {
wrapping_mode_to_str(this.wrap_w)
.into_lua(lua)
}),
}
);
wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness,
// this can be safely skipped since it wont be a component or resource.
skip(lua_reflect),
field_getters={
(diffuse_color, wrapper=crate::lua::wrappers::LuaVec4),
(specular, wrapper=crate::lua::wrappers::LuaVec3),
glossiness,
}
);
wrap_lua_struct!(lyra_resource::gltf::Specular,
// this can be safely skipped since it wont be a component or resource.
skip(lua_reflect),
field_getters={
factor,
(color_factor, wrapper=crate::lua::wrappers::LuaVec3),
},
extra_fields={
fields.add_field_method_get("texture", |lua, this| {
this.texture.clone()
.map(|t| LuaTextureHandle(t))
.into_lua(lua)
});
fields.add_field_method_get("color_texture", |lua, this| {
this.color_texture.clone()
.map(|t| LuaTextureHandle(t))
.into_lua(lua)
});
}
);
// TODO: fields // TODO: fields
lua_wrap_handle!(SceneGraph, name=Scene, {}); lua_wrap_handle!(SceneGraph, name=Scene, {});
lua_wrap_handle!(Mesh, lua_wrap_handle!(Mesh,
field_getters={ field_getters={
(material, { (material, {
data.material.clone() data.material.clone()
@ -154,10 +226,32 @@ lua_wrap_handle!(Mesh,
} }
}); });
// TODO: attributes // TODO: mesh attributes
} }
); );
lua_wrap_handle!(Texture, {});
lua_wrap_handle!(lyra_resource::Image,
field_getters={
(width, {
data.width().into_lua(lua)
}),
(height, {
data.height().into_lua(lua)
})
}
);
lua_wrap_handle!(Texture,
field_getters={
(image, wrapper=LuaImageHandle),
(sampler, {
data.sampler.clone()
.map(|s| LuaTextureSampler(s))
.into_lua(lua)
})
}
);
lua_wrap_handle!(Material, lua_wrap_handle!(Material,
field_getters={ field_getters={
@ -185,7 +279,6 @@ lua_wrap_handle!(Material,
alpha_cutoff, alpha_cutoff,
(alpha_mode, { (alpha_mode, {
match data.alpha_mode { match data.alpha_mode {
// TODO: Lua enums
lyra_resource::gltf::AlphaMode::Opaque => "opaque", lyra_resource::gltf::AlphaMode::Opaque => "opaque",
lyra_resource::gltf::AlphaMode::Mask => "mask", lyra_resource::gltf::AlphaMode::Mask => "mask",
lyra_resource::gltf::AlphaMode::Blend => "blend", lyra_resource::gltf::AlphaMode::Blend => "blend",