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
23 changed files with 1021 additions and 163 deletions
Showing only changes of commit e9cbb48653 - Show all commits

View File

@ -69,12 +69,23 @@ function on_update()
---@type number
local dt = world:resource(DeltaTime)
world:view(
--[[ world:view(
---@param t Transform
function (t)
t:translate(0, 0.15 * dt, 0)
return t
end, Transform
) ]]
world:view(
---@param c Camera
function (c)
c.transform:translate(0, 0.15 * dt, 0)
print("Moving camera to: " .. tostring(c.transform))
return c
end, Camera
)
end

View File

@ -1,8 +1,9 @@
use lyra_reflect::Reflect;
use winit::dpi::PhysicalSize;
use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX}, scene::CameraComponent};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub enum CameraProjectionMode {
/// 3d camera projection
Perspective,

View File

@ -1,8 +1,9 @@
use lyra_ecs::Component;
use lyra_reflect::Reflect;
use crate::{math::{Angle, Transform}, render::camera::CameraProjectionMode};
#[derive(Clone, Component)]
#[derive(Clone, Component, Reflect)]
pub struct CameraComponent {
pub transform: Transform,
pub fov: Angle,

View File

@ -1,6 +1,7 @@
use lyra_math::Angle;
use lyra_reflect_derive::{impl_reflect_simple_struct, impl_reflect_trait_value};
use crate::{lyra_engine, Method, Reflect};
use crate::{lyra_engine, Enum, Method, Reflect, ReflectMut, ReflectRef};
impl_reflect_simple_struct!(lyra_math::Vec2, fields(x = f32, y = f32));
impl_reflect_simple_struct!(lyra_math::Vec3, fields(x = f32, y = f32, z = f32));
@ -17,3 +18,131 @@ impl_reflect_simple_struct!(
);
impl_reflect_trait_value!(lyra_math::Mat4);
impl Reflect for Angle {
fn name(&self) -> String {
"Angle".into()
}
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Self>()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn as_boxed_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
fn apply(&mut self, val: &dyn Reflect) {
if let ReflectRef::Enum(e) = val.reflect_ref() {
let s = e.as_any().downcast_ref::<Self>()
.expect("cannot apply mismatched reflected enum");
*self = *s;
} else {
panic!("Provided value was not an enum!");
}
}
fn clone_inner(&self) -> Box<dyn Reflect> {
Box::new(self.clone())
}
fn reflect_ref(&self) -> crate::ReflectRef {
ReflectRef::Enum(self)
}
fn reflect_mut(&mut self) -> crate::ReflectMut {
ReflectMut::Enum(self)
}
fn reflect_val(&self) -> &dyn Reflect {
self
}
fn reflect_val_mut(&mut self) -> &mut dyn Reflect {
self
}
}
impl Enum for Angle {
fn field(&self, _: &str) -> Option<&dyn Reflect> {
// no struct variants
None
}
fn field_mut(&mut self, _: &str) -> Option<&mut dyn Reflect> {
// no struct variants
None
}
fn field_at(&self, idx: usize) -> Option<&dyn Reflect> {
// all variants only have one tuple field
if idx != 0 {
return None;
}
match self {
Angle::Degrees(v) => Some(v),
Angle::Radians(v) => Some(v),
}
}
fn field_at_mut(&mut self, idx: usize) -> Option<&mut dyn Reflect> {
// all variants only have one tuple field
if idx != 0 {
return None;
}
match self {
Angle::Degrees(v) => Some(v),
Angle::Radians(v) => Some(v),
}
}
fn field_name_at(&self, _: usize) -> Option<String> {
// no struct variants
None
}
fn has_field(&self, _: &str) -> bool {
// no struct variants
false
}
fn fields_len(&self) -> usize {
1
}
fn variants_len(&self) -> usize {
2
}
fn variant_name(&self) -> String {
match self {
Angle::Degrees(_) => "degrees".into(),
Angle::Radians(_) => "radians".into(),
}
}
fn variant_index(&self) -> usize {
match self {
Angle::Degrees(_) => 0,
Angle::Radians(_) => 1,
}
}
fn is_variant_name(&self, name: &str) -> bool {
self.variant_name() == name
}
fn variant_type(&self) -> crate::EnumType {
crate::EnumType::Tuple
}
}

View File

@ -0,0 +1,143 @@
use syn::{parenthesized, token, Token};
pub(crate) enum FieldType {
Unknown,
Type(syn::Path),
Wrapped(syn::Path),
}
impl FieldType {
pub fn is_unknown(&self) -> bool {
matches!(self, FieldType::Unknown)
}
pub fn is_wrapped(&self) -> bool {
matches!(self, FieldType::Wrapped(_))
}
pub fn get_type_path(&self) -> Option<&syn::Path> {
match self {
FieldType::Unknown => None,
FieldType::Type(path) => Some(path),
FieldType::Wrapped(path) => Some(path),
}
}
}
pub(crate) struct Field {
pub field: syn::Ident,
pub field_ty: FieldType,
pub skip_setter: bool,
pub setter: Option<syn::Block>,
pub getter: Option<syn::Block>,
}
impl Field {
fn parse_extended(input: syn::parse::ParseStream) -> syn::Result<Self> {
let field_name = input.parse()?;
let fty = if input.peek(Token![:]) {
let _col: Token![:] = input.parse()?;
let s: syn::Path = input.parse()?;
let mut fty = FieldType::Type(s.clone());
if let Some(ident) = s.get_ident() {
if ident.to_string() == "wrap" {
let content;
let _parens: token::Paren = parenthesized!(content in input);
fty = FieldType::Wrapped(content.parse()?);
}
}
fty
} else {
FieldType::Unknown
};
let mut s = Self {
field: field_name,
field_ty: fty,
skip_setter: false,
setter: None,
getter: None,
};
while input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
if input.peek(syn::Ident) {
let ident: syn::Ident = input.parse()?;
let ident_str = ident.to_string();
let ident_str = ident_str.as_str();
match ident_str {
"skip_set" => {
s.skip_setter = true;
}
"set" => {
let _eq: Token![=] = input.parse()?;
s.setter = Some(input.parse()?);
}
"get" => {
let _eq: Token![=] = input.parse()?;
s.getter = Some(input.parse()?);
}
_ => {
return Err(syn::Error::new_spanned(ident, "unknown wrapper command"));
}
}
}
}
if (s.getter.is_some() || s.setter.is_some()) && s.field_ty.is_wrapped() {
return Err(syn::Error::new(
input.span(),
"cannot specify custom getter or setter \
with wrapped type",
));
}
Ok(s)
}
}
impl syn::parse::Parse for Field {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
Self::parse_extended(&content)
} else {
let field_name = input.parse()?;
let fty = if input.peek(Token![:]) {
let _col: Token![:] = input.parse()?;
let s: syn::Path = input.parse()?;
let mut fty = FieldType::Type(s.clone());
if let Some(ident) = s.get_ident() {
if ident.to_string() == "wrap" {
let content;
let _parens: token::Paren = parenthesized!(content in input);
fty = FieldType::Wrapped(content.parse()?);
}
}
fty
} else {
FieldType::Unknown
};
Ok(Self {
field: field_name,
field_ty: fty,
skip_setter: false,
setter: None,
getter: None,
})
}
}
}

View File

@ -247,9 +247,15 @@ pub(crate) fn lua_wrap_handle_impl(input: proc_macro::TokenStream) -> proc_macro
}
impl LuaWrapper for #wrapper_name {
type Wrap = ResHandle<#handle_path>;
fn wrapped_type_id() -> std::any::TypeId {
TypeId::of::<ResHandle<#handle_path>>()
}
fn into_wrapped(self) -> Self::Wrap {
self.0
}
}
}.into()
}

View File

@ -5,9 +5,12 @@ use syn::{parse_macro_input, Token};
mod mat_wrapper;
mod vec_wrapper;
use to_lua_macro::to_lua_struct_impl;
use vec_wrapper::VecWrapper;
mod lua_macro;
mod to_lua_macro;
mod field;
mod handle_macro;
@ -24,6 +27,11 @@ pub fn lua_wrap_handle(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
lua_wrap_handle_impl(input)
}
#[proc_macro]
pub fn to_lua_convert(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
to_lua_struct_impl(input)
}
pub(crate) struct VecExtensionInputs {
#[allow(dead_code)]
pub type_path: syn::Path,

View File

@ -1,13 +1,15 @@
use proc_macro2::Span;
use quote::quote;
use syn::{braced, 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::{handle_macro::FieldGetter, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE};
use crate::{field::{Field, FieldType}, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE};
#[derive(Clone, Copy, Debug, PartialEq)]
enum SkipType {
/// Skips implementing
LuaReflect
LuaReflect,
}
impl syn::parse::Parse for SkipType {
@ -17,9 +19,7 @@ impl syn::parse::Parse for SkipType {
match name_str.as_str() {
"lua_reflect" => Ok(Self::LuaReflect),
_ => {
Err(syn::Error::new_spanned(name, "unknown skip type"))
}
_ => Err(syn::Error::new_spanned(name, "unknown skip type")),
}
}
}
@ -43,22 +43,21 @@ impl MetaMethod {
let mm_str = mm_str.as_str();
match mm_str {
"Add" | "Sub" | "Div" | "Mul" | "Mod" | "Eq" | "Shl" | "Shr" | "BAnd" | "BOr"
| "BXor" => {
true
},
"Unm" | "BNot" | "ToString" => {
false
},
| "BXor" => true,
"Unm" | "BNot" | "ToString" => false,
_ => todo!(),
}
}
/// returns the tokens of the body of the metamethod
///
///
/// Parameters
/// * `metamethod` - The ident of the metamethod that is being implemented.
/// * `other` - The tokens of the argument used in the metamethod.
fn get_method_body(metamethod: &Ident, other: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
fn get_method_body(
metamethod: &Ident,
other: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let mm_str = metamethod.to_string();
let mm_str = mm_str.as_str();
match mm_str {
@ -75,17 +74,17 @@ impl MetaMethod {
quote! {
Ok(Self(this.0 #symbol #other))
}
},
}
"Unm" => {
quote! {
Ok(Self(-this.0))
}
},
}
"Eq" => {
quote! {
Ok(this.0 == #other)
}
},
}
"Shl" => {
quote! {
Ok(Self(this.0 << #other))
@ -95,41 +94,47 @@ impl MetaMethod {
quote! {
Ok(Self(this.0 >> #other))
}
},
}
"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
}
_ => unreachable!(), // the string was just checked to be one of these
};
quote! {
Ok(Self(this.0 #symbol #other))
}
},
}
"BNot" => {
quote! {
Ok(Self(!this.0))
}
},
}
"ToString" => {
quote! {
Ok(format!("{:?}", this.0))
}
},
_ => syn::Error::new_spanned(metamethod,
"unsupported auto implementation of metamethod").to_compile_error(),
}
_ => {
syn::Error::new_spanned(metamethod, "unsupported auto implementation of metamethod")
.to_compile_error()
}
}
}
fn get_body_for_arg(mt_ident: &Ident, arg_ident: &Ident, arg_param: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
fn get_body_for_arg(
mt_ident: &Ident,
arg_ident: &Ident,
arg_param: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let other: proc_macro2::TokenStream = if Self::is_arg_wrapper(arg_ident) {
// Lua wrappers must be dereferenced
quote! {
@ -167,7 +172,6 @@ impl MetaMethod {
});
}
}
} else if self.arg.len() == 1 {
let first = self.arg.iter().next().unwrap();
let body = Self::get_body_for_arg(&self.name, first, quote!(v));
@ -184,38 +188,41 @@ impl MetaMethod {
let is = i.to_string();
let is = is.as_str();
match is {
"u8" | "u16" | "u32" | "u64" | "u128"
| "i8" | "i16" | "i32" | "i64" | "i128"
| "f32" | "f64" => true,
"u8" | "u16" | "u32" | "u64" | "u128" | "i8" | "i16" | "i32" | "i64"
| "i128" | "f32" | "f64" => true,
_ => false,
}
});
if let Some(num_ident) = num_ident {
let body = Self::get_body_for_arg(&self.name, num_ident, quote!(n as #num_ident));
let body =
Self::get_body_for_arg(&self.name, num_ident, quote!(n as #num_ident));
quote! {
mlua::Value::Number(n) => {
#body
},
}
} else { quote!() }
} else {
quote!()
}
};
let userdata_arm = {
let wrappers: Vec<&Ident> = self.arg.iter()
let wrappers: Vec<&Ident> = self
.arg
.iter()
.filter(|i| Self::is_arg_wrapper(i))
.collect();
let if_statements = wrappers.iter().map(|i| {
let body = Self::get_method_body(&self.name, quote!(other.0));
quote! {
if let Ok(other) = ud.borrow::<#i>() {
#body
}
}
});
quote! {
@ -226,7 +233,7 @@ impl MetaMethod {
// try to get the name of the userdata for the error message
if let Ok(mt) = ud.metatable() {
if let Ok(name) = mt.get::<String>("__name") {
return Err(mlua::Error::BadArgument {
return Err(mlua::Error::BadArgument {
to: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)),
pos: 2,
name: Some("rhs".to_string()),
@ -236,8 +243,8 @@ impl MetaMethod {
});
}
}
Err(mlua::Error::BadArgument {
Err(mlua::Error::BadArgument {
to: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)),
pos: 2,
name: Some("rhs".to_string()),
@ -249,13 +256,13 @@ impl MetaMethod {
},
}
};
quote! {
methods.add_meta_method(mlua::MetaMethod::#mt_ident, |_, this, (v,): (mlua::Value,)| {
match v {
#number_arm
#userdata_arm
_ => Err(mlua::Error::BadArgument {
_ => Err(mlua::Error::BadArgument {
to: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)),
pos: 2,
name: Some("rhs".to_string()),
@ -274,17 +281,15 @@ impl syn::parse::Parse for MetaMethod {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let name: Ident = input.parse()?;
let mut s = Self {
name,
arg: vec![],
};
let mut s = Self { name, arg: vec![] };
// try to parse args
if input.peek(syn::token::Paren) {
let content;
let _parens: syn::token::Paren = parenthesized!(content in input);
let arg: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse, Token![,])?;
let arg: Punctuated<Ident, Token![,]> =
content.parse_terminated(Ident::parse, Token![,])?;
s.arg = arg.into_iter().collect(); // convert to Vec<Ident>
}
@ -296,8 +301,7 @@ struct WrapUsage {
type_path: syn::Path,
/// The extra derives of the type.
override_name: Option<Ident>,
auto_fields: Vec<Ident>,
manual_field_getters: Vec<FieldGetter>,
auto_fields: Vec<Field>,
auto_derives: Vec<Ident>,
auto_new: bool,
meta_methods: Vec<MetaMethod>,
@ -315,7 +319,6 @@ 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,
@ -324,7 +327,7 @@ impl syn::parse::Parse for WrapUsage {
};
let mut new_ident = None;
while input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
@ -339,73 +342,72 @@ impl syn::parse::Parse for WrapUsage {
let name: Ident = input.parse()?;
s.override_name = Some(name);
},
}
"new" => {
s.auto_new = true;
new_ident = Some(ident.clone());
},
}
"skip" => {
let content;
let _parens: token::Paren = parenthesized!(content in input);
let terminated = content.parse_terminated(SkipType::parse, Token![,])?;
s.skips = terminated.into_iter().collect();
},
}
"derives" => {
if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
let derives: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse, Token![,])?;
let derives: Punctuated<Ident, Token![,]> =
content.parse_terminated(Ident::parse, Token![,])?;
s.auto_derives = derives.into_iter().collect();
}
},
}
"fields" => {
if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
let fields: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse, Token![,])?;
s.auto_fields = fields.into_iter().collect();
}
},
"metamethods" => {
if input.peek(token::Paren) {
let content;
let _bracket: token::Paren = parenthesized!(content in input);
let meta_methods: Punctuated<MetaMethod, Token![,]> = content.parse_terminated(MetaMethod::parse, Token![,])?;
s.meta_methods = meta_methods.into_iter().collect();
}
},
"extra_fields" => {
let _eq: Token![=] = input.parse()?;
s.extra_fields = Some(input.parse::<syn::Block>()?);
},
"extra_methods" => {
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();
let terminated = content.parse_terminated(Field::parse, Token![,])?;
s.auto_fields.extend(terminated.into_iter());
}
}
"metamethods" => {
if input.peek(token::Paren) {
let content;
let _bracket: token::Paren = parenthesized!(content in input);
let meta_methods: Punctuated<MetaMethod, Token![,]> =
content.parse_terminated(MetaMethod::parse, Token![,])?;
s.meta_methods = meta_methods.into_iter().collect();
}
}
"extra_fields" => {
let _eq: Token![=] = input.parse()?;
s.extra_fields = Some(input.parse::<syn::Block>()?);
}
"extra_methods" => {
let _eq: Token![=] = input.parse()?;
s.extra_methods = Some(input.parse::<syn::Block>()?);
}
_ => {
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),
));
}
}
}
}
if s.auto_new && s.auto_fields.is_empty() {
return Err(syn::Error::new_spanned(new_ident.unwrap(), "must specify 'fields' when auto creating new function"));
return Err(syn::Error::new_spanned(
new_ident.unwrap(),
"must specify 'fields' when auto creating new function",
));
}
Ok(s)
@ -417,51 +419,50 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token
let input = parse_macro_input!(input as WrapUsage);
let path: Path = input.type_path;
let type_name = &path.segments.last()
let type_name = &path
.segments
.last()
.expect("Failure to find typename in macro usage!")
.ident;
let wrapper_typename = input.override_name
let wrapper_typename = input
.override_name
.unwrap_or_else(|| Ident::new(&format!("Lua{}", type_name), Span::call_site()));
let derive_idents_iter = input.auto_derives.iter();
let extra_fields = input.extra_fields;
let extra_methods = input.extra_methods;
let field_get_set_pairs = input.auto_fields.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(())
});
}
});
// the tokens for the new function
let new_func_tokens = if input.auto_new {
let arg_names = input.auto_fields.iter().map(|i| {
Ident::new(&i.to_string().to_lowercase(), Span::call_site())
});
let arg_names = input
.auto_fields
.iter()
.map(|i| Ident::new(&i.field.to_string().to_lowercase(), Span::call_site()));
let arg_types = input
.auto_fields
.iter()
.map(|i| i.field_ty.get_type_path().unwrap());
let arg_names_clone = arg_names.clone();
let arg_types_clone = arg_types.clone();
quote! {
// arguments for function are not specified since they can be implied from the call
// to new(...)
methods.add_function("new", |_, ( #(#arg_names_clone),* )| {
methods.add_function("new", |_, ( #(#arg_names_clone),* ): ( #(#arg_types_clone),* ) | {
Ok(#wrapper_typename(#path::new( #(#arg_names),* )))
});
}
} else { quote!() };
} else {
quote!()
};
let meta_methods_tokens = {
let method_tokens = input.meta_methods.iter().map(|mm| {
mm.to_tokens(&wrapper_typename)
});
let method_tokens = input
.meta_methods
.iter()
.map(|mm| mm.to_tokens(&wrapper_typename));
quote! {
#(#method_tokens)*
@ -475,34 +476,80 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token
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))
});
}
};
let custom_getters = input.manual_field_getters.iter().map(|g| {
for field in input.auto_fields.iter() {
if field.field_ty.is_unknown() && !field.skip_setter {
return syn::Error::new(
field.field.span(),
"missing type of field, must be specified to generate setters",
)
.to_compile_error()
.into();
}
}
let fields = input.auto_fields.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 {
let field_getter = match &g.field_ty {
FieldType::Wrapped(wrap) => {
quote!(#wrap(this.#field.clone()).into_lua(lua))
}
_ => match &g.getter {
Some(body) => {
quote!(#body)
},
}
None => {
quote!(this.#field.clone().into_lua(lua))
}
}
};
let field_setter = if g.skip_setter {
quote! {}
} else {
let fty = g
.field_ty
.get_type_path()
// should be unreachable due to the checks right before this closure
.expect("no field type specified");
let s = if g.field_ty.is_wrapped() {
quote! {
this.#field = #field.0.clone();
Ok(())
}
} else {
match &g.setter {
Some(body) => {
quote!(#body)
}
None => {
quote! {
this.#field = #field.clone();
Ok(())
}
}
}
};
quote! {
fields.add_field_method_set(stringify!(#field), |_, this, #field: #fty| {
#s
});
}
};
quote! {
fields.add_field_method_get(stringify!($field), |lua, this| {
#field_creator
fields.add_field_method_get(stringify!(#field), |lua, this| {
#field_getter
});
#field_setter
}
});
@ -512,12 +559,12 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token
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
@ -535,13 +582,16 @@ 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)*
use mlua::IntoLua;
#(#fields)*
#extra_fields
}
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
use mlua::IntoLua;
#lua_reflects
#new_func_tokens
@ -551,9 +601,15 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token
}
impl lyra_scripting::lua::LuaWrapper for #wrapper_typename {
type Wrap = #path;
fn wrapped_type_id() -> std::any::TypeId {
std::any::TypeId::of::<#path>()
}
fn into_wrapped(self) -> Self::Wrap {
self.0
}
}
})
}
}

View File

@ -0,0 +1,317 @@
use syn::{braced, parenthesized, parse_macro_input, punctuated::Punctuated, token, Token};
use quote::{quote, ToTokens};
use crate::{field::Field, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE};
fn field_table_setter(field: &Field) -> proc_macro2::TokenStream {
let ident = &field.field;
match &field.setter {
Some(set) => {
quote! {
table.set(stringify!(#ident), #set)?;
}
},
None => {
let ty = field.field_ty.get_type_path()
.expect("no field type specified");
let arg = if field.field_ty.is_wrapped() {
quote!(#ty(self.#ident))
} else { quote!(self.#ident) };
quote! {
table.set(stringify!(#ident), #arg)?;
}
},
}
}
fn field_table_getter(field: &Field) -> proc_macro2::TokenStream {
let ident = &field.field;
match &field.getter {
Some(get) => {
quote! {
let #ident = #get;
}
},
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(wrapper: &syn::Ident, type_path: &syn::Path, create: Option<&syn::Block>, fields: &Vec<Field>) -> proc_macro2::TokenStream {
match create {
Some(b) => quote!(#b),
None => {
/* let field_iter = fields.iter().map(|f| match &f.field_ty {
crate::field::FieldType::Type(path) => quote!(#path),
crate::field::FieldType::Wrapped(path) => quote!(*#path),
_ => todo!()
}); */
let field_iter = fields.iter().map(|f| {
let ident = &f.field;
if f.field_ty.is_wrapped() {
quote!(#ident: (*#ident).clone())
} else {
quote!(#ident)
}
});
quote! {
#wrapper(#type_path {
#(
#field_iter
),*
})
}
}
}
}
fn get_reflect_lua_functions(ty: &ReflectType, type_path: &syn::Path, set_data: bool) -> proc_macro2::TokenStream {
let data = if set_data {
quote!(Some(this.into_wrapped()))
} else { quote!(None) };
match ty {
ReflectType::Component => {
quote! {
Ok(ScriptBorrow::from_component::<#type_path>(#data))
}
},
ReflectType::Resource => {
quote! {
Ok(ScriptBorrow::from_component::<#type_path>(#data))
}
},
}
}
#[derive(Debug, Clone, Copy)]
enum ReflectType {
//Unknown,
Component,
Resource,
}
struct IntoLuaUsage {
type_path: syn::Path,
override_name: Option<syn::Ident>,
table_name: String,
derives: Vec<syn::Ident>,
fields: Vec<Field>,
create: Option<syn::Block>,
reflection_type: Option<ReflectType>,
}
impl syn::parse::Parse for IntoLuaUsage {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let type_path: syn::Path = input.parse()?;
let mut s = Self {
type_path,
override_name: None,
table_name: String::new(),
derives: vec![],
fields: vec![],
create: None,
reflection_type: None,
};
while input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
if input.peek(syn::Ident) {
let ident: syn::Ident = input.parse()?;
let ident_str = ident.to_string();
let ident_str = ident_str.as_str();
match ident_str {
"name" => {
let _eq: Token![=] = input.parse()?;
let name: syn::Ident = input.parse()?;
s.override_name = Some(name);
},
"lua_name" => {
let _eq: Token![=] = input.parse()?;
s.table_name = input.parse::<syn::LitStr>()?.value();
},
"derives" => {
if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
let derives: Punctuated<syn::Ident, Token![,]> =
content.parse_terminated(syn::Ident::parse, Token![,])?;
s.derives = derives.into_iter().collect();
}
},
"fields" => {
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(Field::parse, Token![,])?;
s.fields.extend(terminated.into_iter());
}
},
"create" => {
let _eq: Token![=] = input.parse()?;
s.create = Some(input.parse()?);
},
"reflect" => {
let _eq: Token![=] = input.parse()?;
let ty: syn::Ident = input.parse()?;
let ty_str = ty.to_string();
let ty_str = ty_str.as_str();
let ty = match ty_str {
"component" => ReflectType::Component,
"resource" => ReflectType::Resource,
_ => return Err(syn::Error::new_spanned(
ident,
format!("unknown wrapper type: '{}', expected 'component' or 'resource'", ty_str),
)),
};
s.reflection_type = Some(ty);
},
_ => {
return Err(syn::Error::new_spanned(
ident,
format!("unknown wrapper command: '{}'", ident_str),
));
}
}
}
}
if s.reflection_type.is_none() {
return Err(syn::Error::new(
input.span(),
format!("Wrapper type not specified! Expected 'type=component' or 'type=resource'"),
));
}
if s.table_name.is_empty() {
return Err(syn::Error::new(
input.span(),
format!("No lua table specified. Use 'lua_name=\"Camera\"'"),
))
}
Ok(s)
}
}
pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as IntoLuaUsage);
// unwrap is fine since `Some` is ensured in parse impl
let reflect_type = input.reflection_type.as_ref().unwrap();
let type_path = &input.type_path;
let type_name = &type_path
.segments
.last()
.expect("Failure to find typename in macro usage!")
.ident;
let wrapper = input.override_name.unwrap_or_else(|| syn::Ident::new(format!("Lua{}", type_name.to_string()).as_str(), type_name.span()));
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| field_table_setter(f));
let struct_creator = wrapper_creation(&wrapper, type_path, input.create.as_ref(), &input.fields);
let reflect_fn = get_reflect_lua_functions(reflect_type, &input.type_path, true);
let reflect_type_fn = get_reflect_lua_functions(reflect_type, &input.type_path, false);
quote! {
#[derive(Clone, #(#derives_iter),*)]
pub struct #wrapper(pub(crate) #type_path);
impl std::ops::Deref for #wrapper {
type Target = #type_path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for #wrapper {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl mlua::FromLua for #wrapper {
fn from_lua(val: mlua::Value, _: &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 #wrapper {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
let table = lua.create_table()?;
#(
#field_setters_iter
)*
table.set(
#FN_NAME_INTERNAL_REFLECT,
lua.create_function(|_, this: Self| {
#reflect_fn
})?,
)?;
table.set(
#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 LuaWrapper for #wrapper {
type Wrap = #type_path;
#[inline(always)]
fn wrapped_type_id() -> std::any::TypeId {
std::any::TypeId::of::<#type_path>()
}
#[inline(always)]
fn into_wrapped(self) -> Self::Wrap {
self.0
}
}
}.into_token_stream().into()
}

View File

@ -60,4 +60,10 @@ WrappingMode = {
CLAMP_TO_EDGE = "clamp_to_edge",
MIRRORED_REPEAT = "mirrored_repeat",
REPEAT = "repeat",
}
---@enum CameraProjectionMode
CameraProjectionMode = {
PERSPECTIVE = "perspective",
ORTHOGRAPHIC = "orthographic",
}

View File

@ -0,0 +1,18 @@
---@meta
---@class Camera: userdata
Camera = {
---The position of the camera
---@type Transform
transform = nil,
---The field of view of the camera
---@type Angle
fov = nil,
---The projection mode the camera.
---Can be used to specify if the camera is 2D (orthographic), or 3D (perspective).
---@type CameraProjectionMode
mode = nil,
---Flag to enable some debug rendering stuff.
---@type boolean
debug = nil,
}

View File

@ -0,0 +1,25 @@
---@meta
---@class Angle: userdata
Angle = {}
---Create a new angle in degrees.
---@param deg number
function Angle.new_degrees(deg) end
---Create a new angle in radians.
---@param rad number
function Angle.new_radians(rad) end
---Get the angle in radians.
---
---Will convert from degrees automatically.
---
---@return number radians
function Angle:radians() end
---Get the angle in degrees.
---
---Will convert from radians automatically.
---
---@return number degrees
function Angle:degrees() end

View File

@ -131,6 +131,11 @@ pub trait RegisterLuaType {
fn register_lua_convert<T>(&mut self)
where
T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static;
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;
}
impl RegisterLuaType for World {
@ -165,6 +170,18 @@ impl RegisterLuaType for World {
let reg_type = registry.get_type_or_default(T::wrapped_type_id());
reg_type.add_data(ReflectLuaProxy::from_as_and_from_lua::<T>());
}
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
{
self.register_lua_convert::<T>();
let mut lookup = self.get_resource_or_default::<LuaTableProxyLookup>();
lookup.comp_info_from_name.insert(name.into(), lyra_ecs::ComponentInfo::new::<T::Wrap>());
lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::<T::Wrap>());
}
}
impl mlua::FromLua for ScriptBorrow {

View File

@ -4,7 +4,9 @@ use lyra_ecs::ResourceObject;
use lyra_reflect::{Reflect, TypeRegistry};
use lyra_resource::gltf::Gltf;
use crate::{lua::{wrappers::{LuaActionHandler, LuaDeltaTime, LuaGltfHandle, LuaResHandleToComponent, LuaSceneHandle, LuaWindow}, LuaContext, ReflectLuaProxy, RegisterLuaType, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr};
use crate::{lua::{wrappers::*, LuaContext, LuaTableProxyLookup, LuaWrapper, ReflectLuaProxy, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr};
//fn register_lua_proxy::<T:
#[derive(Default)]
pub struct LyraEcsApiProvider;
@ -17,6 +19,12 @@ impl ScriptApiProvider for LyraEcsApiProvider {
world.register_lua_wrapper::<LuaSceneHandle>();
world.register_lua_wrapper::<LuaActionHandler>();
world.register_lua_wrapper::<LuaWindow>();
world.register_lua_convert::<LuaCamera>();
let mut lookup = world.get_resource_or_default::<LuaTableProxyLookup>();
lookup.comp_info_from_name.insert("Camera".into(), lyra_ecs::ComponentInfo::new::<lyra_game::scene::CameraComponent>());
lookup.typeid_from_name.insert("Camera".into(), std::any::TypeId::of::<lyra_game::scene::CameraComponent>());
drop(lookup);
let mut registry = world.get_resource_mut::<TypeRegistry>().unwrap();
@ -33,8 +41,6 @@ impl ScriptApiProvider for LyraEcsApiProvider {
}
);
reg_type.add_data(l);
//reg_type.add_data(ReflectLuaProxy::from_lua_proxy::<LuaGltfHandle>());
}
fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
@ -51,6 +57,9 @@ impl ScriptApiProvider for LyraEcsApiProvider {
globals.set("ActionHandler", ctx.create_proxy::<LuaActionHandler>()?)?;
globals.set("Window", ctx.create_proxy::<LuaWindow>()?)?;
let cam_table = create_reflect_comp_table::<LuaCamera>(&ctx, "Camera")?;
globals.set("Camera", cam_table)?;
let dt_table = create_reflect_table::<lyra_game::DeltaTime>(&ctx)?;
globals.set("DeltaTime", dt_table)?;
@ -72,5 +81,25 @@ fn create_reflect_table<T: Reflect + ResourceObject + Default + 'static>(lua: &m
Ok(ScriptBorrow::from_resource::<T>(None))
})?)?;
Ok(table)
}
fn create_reflect_comp_table<T>(lua: &mlua::Lua, name: &str) -> mlua::Result<mlua::Table>
where
T: LuaWrapper + mlua::FromLua,
T::Wrap: lyra_ecs::Component + Reflect
{
let table = lua.create_table()?;
table.set(FN_NAME_INTERNAL_REFLECT, lua.create_function(|_, this: T| {
Ok(ScriptBorrow::from_component::<T::Wrap>(Some(this.into_wrapped())))
})?)?;
table.set(FN_NAME_INTERNAL_REFLECT_TYPE, lua.create_function(|_, ()| {
Ok(ScriptBorrow::from_component::<T::Wrap>(None))
})?)?;
table.set(mlua::MetaMethod::Type.name(), name)?;
Ok(table)
}

View File

@ -1,5 +1,5 @@
use lyra_ecs::World;
use crate::lua::wrappers::{LuaQuat, LuaTransform, LuaVec3, LuaVec2};
use crate::lua::wrappers::{LuaAngle, LuaQuat, LuaTransform, LuaVec2, LuaVec3};
use crate::ScriptData;
use crate::lua::RegisterLuaType;
@ -16,6 +16,7 @@ impl ScriptApiProvider for LyraMathApiProvider {
world.register_lua_wrapper::<LuaVec3>();
world.register_lua_wrapper::<LuaQuat>();
world.register_lua_wrapper::<LuaTransform>();
world.register_lua_wrapper::<LuaAngle>();
}
fn expose_api(&mut self, _data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
@ -29,6 +30,7 @@ impl ScriptApiProvider for LyraMathApiProvider {
globals.set("Vec3", ctx.create_proxy::<LuaVec3>()?)?;
globals.set("Quat", ctx.create_proxy::<LuaQuat>()?)?;
globals.set("Transform", ctx.create_proxy::<LuaTransform>()?)?;
globals.set("Angle", ctx.create_proxy::<LuaAngle>()?)?;
Ok(())
}

View File

@ -9,8 +9,11 @@ use crate::{ScriptBorrow, ScriptDynamicBundle};
use super::{Error, FN_NAME_INTERNAL_REFLECT};
pub trait LuaWrapper {
type Wrap: Reflect + 'static;
/// The type id of the wrapped type.
fn wrapped_type_id() -> TypeId;
fn into_wrapped(self) -> Self::Wrap;
}
/// A trait that used to convert something into lua, or to set something to a value from lua.

View File

@ -129,28 +129,29 @@ fn wrapping_mode_to_str(wm: WrappingMode) -> &'static str {
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, {
fields={
// don't need to specify field types since setters are skipped
(mag_filter, skip_set, get={
this.mag_filter.map(|f| filter_mode_to_str(f))
.into_lua(lua)
}),
(min_filter, {
(min_filter, skip_set, get={
this.min_filter.map(|f| filter_mode_to_str(f))
.into_lua(lua)
}),
(mipmap_filter, {
(mipmap_filter, skip_set, get={
this.mipmap_filter.map(|f| filter_mode_to_str(f))
.into_lua(lua)
}),
(wrap_u, {
(wrap_u, skip_set, get={
wrapping_mode_to_str(this.wrap_u)
.into_lua(lua)
}),
(wrap_v, {
(wrap_v, skip_set, get={
wrapping_mode_to_str(this.wrap_v)
.into_lua(lua)
}),
(wrap_w, {
(wrap_w, skip_set, get={
wrapping_mode_to_str(this.wrap_w)
.into_lua(lua)
}),
@ -160,31 +161,29 @@ wrap_lua_struct!(lyra_resource::TextureSampler,
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,
fields={
(diffuse_color: wrap(crate::lua::wrappers::LuaVec4), skip_set),
(specular: wrap(crate::lua::wrappers::LuaVec3), skip_set),
(glossiness, skip_set),
}
);
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| {
fields={
(factor, skip_set),
(color_factor: wrap(crate::lua::wrappers::LuaVec3), skip_set),
(texture, skip_set, get={
this.texture.clone()
.map(|t| LuaTextureHandle(t))
.into_lua(lua)
});
fields.add_field_method_get("color_texture", |lua, this| {
}),
(color_texture, skip_set, get={
this.color_texture.clone()
.map(|t| LuaTextureHandle(t))
.into_lua(lua)
});
})
}
);

View File

@ -0,0 +1,46 @@
use crate::{
lua::{
wrappers::{LuaAngle, LuaTransform},
LuaWrapper
},
ScriptBorrow,
};
use lyra_game::{render::camera::CameraProjectionMode, scene::CameraComponent};
use lyra_reflect::Enum;
use lyra_scripting_derive::to_lua_convert;
fn projection_mode_from_str(s: &str) -> Option<CameraProjectionMode> {
match s {
"perspective" => Some(CameraProjectionMode::Perspective),
"orthographic" => Some(CameraProjectionMode::Orthographic),
_ => None,
}
}
to_lua_convert!(
// Struct that is being wrapped[]
CameraComponent,
// Name of wrapping struct
name=LuaCamera,
// The name of the type in Lua
lua_name="Camera",
// Reflection type, can be 'component' or 'resource'
reflect=component,
fields={
transform: wrap(LuaTransform),
fov: wrap(LuaAngle),
(
mode,
// Get the table from the value, result must be `CameraProjectionMode`
get={
let mode: String = table.get("mode")?;
projection_mode_from_str(&mode).unwrap()
},
// Get the value from self, result must be the type in Lua, here its `String`.
set={
self.mode.variant_name().to_lowercase()
}
),
debug: bool
}
);

View File

@ -34,7 +34,15 @@ impl mlua::IntoLua for LuaDeltaTime {
}
impl LuaWrapper for LuaDeltaTime {
type Wrap = DeltaTime;
#[inline(always)]
fn wrapped_type_id() -> std::any::TypeId {
TypeId::of::<DeltaTime>()
}
#[inline(always)]
fn into_wrapped(self) -> Self::Wrap {
self.0
}
}

View File

@ -173,9 +173,15 @@ impl mlua::FromLua for LuaActionHandler {
}
impl LuaWrapper for LuaActionHandler {
type Wrap = ActionHandler;
fn wrapped_type_id() -> std::any::TypeId {
std::any::TypeId::of::<ActionHandler>()
}
fn into_wrapped(self) -> Self::Wrap {
self.handler
}
}
fn process_keyboard_string(key_name: &str) -> Option<ActionSource> {

View File

@ -11,7 +11,7 @@ wrap_lua_struct!(
math::Vec2,
derives(PartialEq, Copy),
new,
fields(x, y),
fields={x: f32, y: f32},
metamethods(
Add(LuaVec2, f32),
Sub(LuaVec2, f32),
@ -50,7 +50,7 @@ wrap_lua_struct!(
math::Vec3,
derives(PartialEq, Copy),
new,
fields(x, y, z),
fields={x: f32, y: f32, z: f32},
metamethods(
Add(LuaVec3, f32),
Sub(LuaVec3, f32),
@ -90,7 +90,7 @@ wrap_lua_struct!(
math::Vec4,
derives(PartialEq, Copy),
new,
fields(x, y, z, w),
fields={x: f32, y: f32, z: f32, w: f32},
metamethods(
Add(LuaVec4, f32),
Sub(LuaVec4, f32),
@ -108,7 +108,7 @@ wrap_lua_struct!(
math::Quat,
derives(PartialEq, Copy),
//new,
fields(x, y, z, w),
fields={x: f32, y: f32, z: f32, w: f32},
metamethods(
Eq,
Add,
@ -360,4 +360,27 @@ wrap_lua_struct!(
Ok(t)
});
}
);
wrap_lua_struct!(
math::Angle,
derives(Copy),
skip(lua_reflect),
extra_methods = {
methods.add_function("new_degrees", |_, deg: f32| {
Ok(Self(math::Angle::Degrees(deg)))
});
methods.add_function("new_radians", |_, rad: f32| {
Ok(Self(math::Angle::Radians(rad)))
});
methods.add_method("radians", |_, this, ()| {
Ok(this.to_radians())
});
methods.add_method("degrees", |_, this, ()| {
Ok(this.to_degrees())
});
},
);

View File

@ -11,4 +11,7 @@ mod delta_time;
pub use delta_time::*;
mod window;
pub use window::*;
pub use window::*;
mod camera;
pub use camera::*;

View File

@ -1,7 +1,6 @@
use lyra_scripting_derive::wrap_lua_struct;
use lyra_game::winit::{WindowOptions, FullscreenMode, Theme, CursorGrabMode, WindowLevel};
use mlua::IntoLua;
use crate::lyra_engine;
use crate as lyra_scripting;
@ -15,7 +14,9 @@ wrap_lua_struct!(
WindowOptions,
//derives(Clone),
name=LuaWindow,
fields(focused),
fields={
(focused, skip_set)
},
extra_fields={
fields.add_field_method_get("window_mode", |lua, this| {
let s = match &this.fullscreen_mode {