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 syn::{braced, parenthesized, parse_macro_input, token, Ident, Path, Token};
struct FieldGetter {
field: Ident,
body: Option<syn::Block>,
wrapper_type: Option<syn::Path>,
pub(crate) struct FieldGetter {
pub field: Ident,
pub body: Option<syn::Block>,
pub wrapper_type: Option<syn::Path>,
}
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 {
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 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 {
Some(wrap) => {
quote!(#wrap(data.#field).into_lua(lua))
quote!(#wrap(data.#field.clone()).into_lua(lua))
},
None => match &g.body {
Some(body) => {
@ -170,18 +171,18 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro
quote! {
#[derive(Clone, Reflect)]
pub struct #wrapper_name(pub ResHandle<#handle_name>);
pub struct #wrapper_name(pub ResHandle<#handle_path>);
impl Deref for #wrapper_name {
type Target = ResHandle<#handle_name>;
type Target = ResHandle<#handle_path>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<ResHandle<#handle_name>> for #wrapper_name {
fn from(value: ResHandle<#handle_name>) -> Self {
impl From<ResHandle<#handle_path>> for #wrapper_name {
fn from(value: ResHandle<#handle_path>) -> Self {
#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, |_, ()| {
Ok(ScriptBorrow::from_component::<ResHandle<#handle_name>>(None))
Ok(ScriptBorrow::from_component::<ResHandle<#handle_path>>(None))
});
methods.add_method(FN_NAME_INTERNAL_REFLECT, |_, this, ()| {
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 {
fn wrapped_type_id() -> std::any::TypeId {
TypeId::of::<ResHandle<#handle_name>>()
TypeId::of::<ResHandle<#handle_path>>()
}
}
}.into()

View File

@ -1,8 +1,8 @@
use proc_macro2::Span;
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)]
enum SkipType {
@ -297,6 +297,7 @@ struct WrapUsage {
/// The extra derives of the type.
override_name: Option<Ident>,
auto_fields: Vec<Ident>,
manual_field_getters: Vec<FieldGetter>,
auto_derives: Vec<Ident>,
auto_new: bool,
meta_methods: Vec<MetaMethod>,
@ -314,6 +315,7 @@ impl syn::parse::Parse for WrapUsage {
override_name: None,
auto_fields: vec![],
auto_derives: vec![],
manual_field_getters: vec![],
extra_fields: None,
extra_methods: None,
auto_new: false,
@ -384,6 +386,17 @@ impl syn::parse::Parse for WrapUsage {
let _eq: Token![=] = input.parse()?;
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)));
}
@ -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! {
#[derive(Clone, lyra_reflect::Reflect, #(#derive_idents_iter),*)]
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 {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
#(#field_get_set_pairs)*
#(#custom_getters)*
#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> {
let ctx = ctx.lock().unwrap();
// load window enums
let bytes = include_str!("../../../scripts/lua/window.lua");
ctx.load(bytes).exec().unwrap();
// load asset handle enums
let bytes = include_str!("../../../scripts/lua/asset_handle.lua");
// load enums
let bytes = include_str!("../../../scripts/lua/enums.lua");
ctx.load(bytes).exec().unwrap();
let globals = ctx.globals();

View File

@ -1,6 +1,6 @@
use std::{any::TypeId, ops::Deref};
//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_reflect::{Reflect, TypeData};
use lyra_scripting_derive::{lua_wrap_handle, wrap_lua_struct};
@ -111,10 +111,82 @@ impl mlua::FromLua for LuaResHandle {
}
}
// TODO: fields
wrap_lua_struct!(lyra_resource::gltf::PbrGlossiness, skip(lua_reflect)); // doesn't need internal lua reflection methods
// TODO: fields
wrap_lua_struct!(lyra_resource::gltf::Specular, skip(lua_reflect)); // doesn't need internal lua reflection methods
fn filter_mode_to_str(fm: FilterMode) -> &'static str {
match fm {
FilterMode::Nearest => "nearest",
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
lua_wrap_handle!(SceneGraph, name=Scene, {});
@ -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,
field_getters={
@ -185,7 +279,6 @@ lua_wrap_handle!(Material,
alpha_cutoff,
(alpha_mode, {
match data.alpha_mode {
// TODO: Lua enums
lyra_resource::gltf::AlphaMode::Opaque => "opaque",
lyra_resource::gltf::AlphaMode::Mask => "mask",
lyra_resource::gltf::AlphaMode::Blend => "blend",