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
7 changed files with 1693 additions and 172 deletions
Showing only changes of commit 8e56ee1f0f - Show all commits

File diff suppressed because it is too large Load Diff

View File

@ -4,3 +4,5 @@ pub use plugin::*;
mod window;
pub use window::*;
pub use winit::dpi as dpi;

View File

@ -13,6 +13,9 @@ use winit::{
window::{Window, WindowAttributes, WindowId},
};
pub use winit::event::{DeviceId, DeviceEvent, MouseScrollDelta, ElementState};
pub use winit::keyboard::PhysicalKey;
use crate::{
game::{App, WindowState},
plugin::Plugin,
@ -26,9 +29,9 @@ use super::WindowOptions;
#[derive(Debug, Clone, Reflect)]
pub struct DeviceEventPair {
#[reflect(skip)]
pub device_src: winit::event::DeviceId,
pub device_src: DeviceId,
#[reflect(skip)]
pub event: winit::event::DeviceEvent,
pub event: DeviceEvent,
}
pub struct WinitPlugin {

View File

@ -10,6 +10,7 @@ use crate::{field::{Field, FieldType}, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNA
enum SkipType {
/// Skips implementing
LuaReflect,
LuaWrapper,
}
impl syn::parse::Parse for SkipType {
@ -19,6 +20,7 @@ impl syn::parse::Parse for SkipType {
match name_str.as_str() {
"lua_reflect" => Ok(Self::LuaReflect),
"lua_wrapper" => Ok(Self::LuaWrapper),
_ => Err(syn::Error::new_spanned(name, "unknown skip type")),
}
}
@ -483,6 +485,24 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token
}
};
let lua_wrapper = if input.skips.contains(&SkipType::LuaWrapper) {
quote!()
} else {
quote! {
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
}
}
}
};
for field in input.auto_fields.iter() {
if field.field_ty.is_unknown() && !field.skip_setter {
return syn::Error::new(
@ -583,6 +603,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) {
use mlua::IntoLua;
use mlua::FromLua;
#(#fields)*
@ -591,6 +612,7 @@ pub fn wrap_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::Token
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
use mlua::IntoLua;
use mlua::FromLua;
#lua_reflects
@ -600,16 +622,6 @@ 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
}
}
#lua_wrapper
})
}

View File

@ -12,7 +12,11 @@ pub trait LuaWrapper {
type Wrap: Reflect + 'static;
/// The type id of the wrapped type.
fn wrapped_type_id() -> TypeId;
#[inline(always)]
fn wrapped_type_id() -> TypeId {
TypeId::of::<Self::Wrap>()
}
fn into_wrapped(self) -> Self::Wrap;
}

View File

@ -0,0 +1,318 @@
use crate::lua::LuaWrapper;
use lyra_game::{
input::{KeyCode, NativeKeyCode},
math::Vec2,
winit::{
dpi::PhysicalPosition, DeviceEvent, DeviceEventPair, DeviceId, MouseScrollDelta,
PhysicalKey,
},
};
use lyra_scripting_derive::wrap_lua_struct;
use crate::lyra_engine;
use super::LuaVec2;
wrap_lua_struct!(DeviceId, skip(lua_reflect, lua_wrapper));
/// Wraps [`DeviceEvent`] and implements [`IntoLua`]
#[derive(Clone)]
pub struct LuaDeviceEventRaw(pub(crate) DeviceEvent);
impl std::ops::Deref for LuaDeviceEventRaw {
type Target = DeviceEvent;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for LuaDeviceEventRaw {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl mlua::FromLua for LuaDeviceEventRaw {
fn from_lua(v: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let ty = v.type_name();
let table = v.as_table().ok_or(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some("expected Lua Table".into()),
})?;
let type_str: String = table.get("type")?;
let type_str = type_str.as_str();
let ret = match type_str {
"added" => Self(DeviceEvent::Added),
"removed" => Self(DeviceEvent::Removed),
"mouse_motion" => {
let delta: LuaVec2 = table.get("delta")?;
Self(DeviceEvent::MouseMotion {
delta: (delta.x as _, delta.y as _),
})
}
"mouse_wheel" => {
let delta = if table.contains_key("line_delta")? {
let ld: LuaVec2 = table.get("line_delta")?;
MouseScrollDelta::LineDelta(ld.x, ld.y)
} else if table.contains_key("pixel_delta")? {
let pd: LuaVec2 = table.get("pixel_delta")?;
let pos = PhysicalPosition::new(pd.x as f64, pd.y as f64);
MouseScrollDelta::PixelDelta(pos)
} else {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(
"could not find line_delta or pixel_delta in 'mouse_wheel' \
device event"
.into(),
),
});
};
Self(DeviceEvent::MouseWheel { delta })
}
"motion" => {
let axis: u32 = table.get("axis")?;
let value: f64 = table.get("value")?;
Self(DeviceEvent::Motion { axis, value })
}
"button" => {
let button: u32 = table.get("button")?;
let state_str: String = table.get("state")?;
let state_str = state_str.as_str();
let state = match state_str {
"pressed" => lyra_game::winit::ElementState::Pressed,
"released" => lyra_game::winit::ElementState::Released,
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(format!("unknown button state: '{}'", state_str)),
});
}
};
Self(DeviceEvent::Button { button, state })
}
"key" => {
let state_str: String = table.get("state")?;
let state_str = state_str.as_str();
let state = match state_str {
"pressed" => lyra_game::winit::ElementState::Pressed,
"released" => lyra_game::winit::ElementState::Released,
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(format!("unknown key state: '{}'", state_str)),
});
}
};
if table.contains_key("code")? {
let code_str: String = table.get("code")?;
let code = KeyCode::from_str(&code_str);
if code.is_none() {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(format!("unknown key code: '{}'", code_str)),
});
}
let phy = PhysicalKey::Code(code);
Self(DeviceEvent::Key(RawKeyEvent {
physical_key: phy,
state,
}))
} else if table.contains_key("native")? {
let native_str: String = table.get("native")?;
let native_str = native_str.as_str();
let native_code: u32 = table.get("native_code")?;
let native_code = match native_str {
"unknown" => NativeKeyCode::Unidentified,
"android" => NativeKeyCode::Android(native_code),
"macos" => NativeKeyCode::MacOS(native_code as u16),
"windows" => NativeKeyCode::Windows(native_code),
"xkb" => NativeKeyCode::Xkb(native_code),
};
Self(DeviceEvent::Key(RawKeyEvent {
physical_key: PhysicalKey::Unidentified(native_code),
state,
}))
} else {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(
"malformed Table for LuaDeviceEventRaw, missing key \
code"
.into(),
),
});
}
}
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(format!("unknown device event type '{}'", type_str)),
});
}
};
Ok(ret)
}
}
impl mlua::IntoLua for LuaDeviceEventRaw {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
let table = lua.create_table()?;
match self.0 {
DeviceEvent::Added => {
table.set("type", "added")?;
}
DeviceEvent::Removed => {
table.set("type", "removed")?;
}
DeviceEvent::MouseMotion { delta } => {
table.set("type", "mouse_motion")?;
table.set("delta", LuaVec2(Vec2::new(delta.0 as _, delta.1 as _)))?;
}
DeviceEvent::MouseWheel { delta } => {
table.set("type", "mouse_wheel")?;
match delta {
MouseScrollDelta::LineDelta(x, y) => {
let d = LuaVec2(Vec2::new(x, y));
table.set("line_delta", d)?;
}
MouseScrollDelta::PixelDelta(delta) => {
let d = delta.cast::<f32>();
let d = LuaVec2(Vec2::new(d.x, d.y));
table.set("pixel_delta", d)?;
}
}
}
DeviceEvent::Motion { axis, value } => {
table.set("type", "motion")?;
table.set("axis", axis)?;
table.set("value", value)?;
}
DeviceEvent::Button { button, state } => {
table.set("type", "button")?;
table.set("button", button)?;
let state = match state {
lyra_game::winit::ElementState::Pressed => "pressed",
lyra_game::winit::ElementState::Released => "released",
};
table.set("state", state)?;
}
DeviceEvent::Key(raw_key_event) => {
table.set("type", "key")?;
let state = match raw_key_event.state {
lyra_game::winit::ElementState::Pressed => "pressed",
lyra_game::winit::ElementState::Released => "released",
};
table.set("state", state)?;
let e = KeyCode::from_raw_event(&raw_key_event);
let s = e.as_str();
if let Some(s) = s {
table.set("code", s)?;
} else if let Some(un) = e.get_unknown() {
match un {
lyra_game::input::NativeKeyCode::Unidentified => {
table.set("native", "unknown")?;
}
lyra_game::input::NativeKeyCode::Android(v) => {
table.set("native", "android")?;
table.set("native_code", *v)?;
}
lyra_game::input::NativeKeyCode::MacOS(v) => {
table.set("native", "macos")?;
table.set("native_code", *v)?;
}
lyra_game::input::NativeKeyCode::Windows(v) => {
table.set("native", "windows")?;
table.set("native_code", *v)?;
}
lyra_game::input::NativeKeyCode::Xkb(v) => {
table.set("native", "xkb")?;
table.set("native_code", *v)?;
}
}
}
}
}
table.into_lua(lua)
}
}
#[derive(Clone)]
pub struct LuaDeviceEvent(pub(crate) DeviceEventPair);
impl std::ops::Deref for LuaDeviceEvent {
type Target = DeviceEventPair;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for LuaDeviceEvent {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl mlua::FromLua for LuaDeviceEvent {
fn from_lua(v: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let ty = v.type_name();
let table = v.as_table().ok_or(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEvent".into(),
message: Some("expected Lua Table".into()),
})?;
let id: LuaDeviceId = table.get("device")?;
let ev: LuaDeviceEventRaw = table.get("event")?;
Ok(Self(DeviceEventPair {
device_src: id,
event: ev,
}))
}
}
impl mlua::IntoLua for LuaDeviceEvent {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
let table = lua.create_table()?;
table.set("device", LuaDeviceId(self.device_src))?;
table.set("event", LuaDeviceEventRaw(self.event.clone()))?;
table.into_lua(lua)
}
}
impl LuaWrapper for LuaDeviceEvent {
type Wrap = DeviceEventPair;
#[inline(always)]
fn into_wrapped(self) -> Self::Wrap {
self.0
}
}

View File

@ -18,3 +18,6 @@ pub use camera::*;
mod free_fly_camera;
pub use free_fly_camera::*;
mod events;
pub use events::*;