use std::{any::{Any, TypeId}, ffi::CString, marker::PhantomData, mem, ptr};
use libc::c_char;
use crate::{lua_State, lua_call, lua_getfield, lua_pop, lua_pushvalue, push_lightuserdata, sys, Lua, LuaPush, LuaRead, LuaTable};
#[inline]
extern "C" fn destructor_wrapper<T>(lua: *mut sys::lua_State) -> libc::c_int {
unsafe {
let obj = sys::lua_touserdata(lua, -1);
ptr::drop_in_place(obj as *mut T);
0
}
}
extern "C" fn constructor_wrapper<T>(lua: *mut sys::lua_State) -> libc::c_int
where
T: Default + Any,
{
let t = T::default();
let lua_data_raw =
unsafe { sys::lua_newuserdata(lua, mem::size_of::<T>() as libc::size_t) };
unsafe {
ptr::write(lua_data_raw as *mut _, t);
}
let typeid = CString::new(format!("{:?}", TypeId::of::<T>())).unwrap();
unsafe {
sys::lua_getglobal(lua, typeid.as_ptr());
sys::lua_setmetatable(lua, -2);
}
1
}
fn get_metatable_base_key<T: Any>() -> CString {
CString::new(format!("{:?}", TypeId::of::<T>())).unwrap()
}
fn get_metatable_real_key<T: Any>() -> CString {
CString::new(format!("{:?}_real", TypeId::of::<T>())).unwrap()
}
fn check_is_field_key(name: &str) -> CString {
CString::new(format!("{}__isfield", name)).unwrap()
}
fn check_set_field_key(name: &str) -> CString {
CString::new(format!("{}__set", name)).unwrap()
}
extern "C" fn index_metatable<'a, T>(lua: *mut sys::lua_State) -> libc::c_int
where
T: Default + Any,
&'a mut T: LuaRead,
{
if let Some(key) = String::lua_read_with_pop(lua, 2, 0) {
let typeid = get_metatable_real_key::<T>();
unsafe {
sys::lua_getglobal(lua, typeid.as_ptr());
let check_key = check_is_field_key(&key);
let t = lua_getfield(lua, -1, check_key.as_ptr());
lua_pop(lua, 1);
let is_field = t == sys::LUA_TBOOLEAN;
let key = CString::new(key).unwrap();
let t = lua_getfield(lua, -1, key.as_ptr());
if t != sys::LUA_TFUNCTION || !is_field {
return 1;
}
lua_pushvalue(lua, 1);
lua_call(lua, 1, 1);
1
}
} else {
0
}
}
extern "C" fn newindex_metatable<'a, T>(lua: *mut sys::lua_State) -> libc::c_int
where
T: Default + Any,
&'a mut T: LuaRead,
{
println!("newindex_metatable!!!!!!!!!!!!!!!!");
if let Some(mut key) = String::lua_read_with_pop(lua, 2, 0) {
key.push_str("__set");
let typeid = get_metatable_real_key::<T>();
unsafe {
sys::lua_getglobal(lua, typeid.as_ptr());
let key = CString::new(key).unwrap();
let t = lua_getfield(lua, -1, key.as_ptr());
if t != sys::LUA_TFUNCTION {
return 0;
}
lua_pushvalue(lua, 1);
lua_pushvalue(lua, 3);
lua_call(lua, 2, 1);
1
}
} else {
0
}
}
extern "C" fn constructor_light_wrapper<T>(lua: *mut sys::lua_State) -> libc::c_int
where
T: Default + Any,
{
let t = Box::into_raw(Box::new(T::default()));
push_lightuserdata(unsafe { &mut *t }, lua, |_| {});
let typeid = get_metatable_base_key::<T>();
unsafe {
sys::lua_getglobal(lua, typeid.as_ptr());
sys::lua_setmetatable(lua, -2);
}
1
}
pub struct LuaObject<'a, T>
where T: Default + Any,
&'a mut T: LuaRead {
lua: *mut lua_State,
light: bool,
name: &'static str,
marker: PhantomData<&'a T>,
}
impl<'a, T> LuaObject<'a, T>
where
T: Default + Any,
&'a mut T: LuaRead
{
pub fn new(lua: *mut lua_State, name: &'static str) -> LuaObject<'a, T> {
LuaObject {
lua,
light: false,
name,
marker: PhantomData,
}
}
pub fn new_light(lua: *mut lua_State, name: &'static str) -> LuaObject<'a, T> {
LuaObject {
lua,
light: true,
name,
marker: PhantomData,
}
}
pub fn ensure_matetable(&mut self) -> bool {
let typeid = get_metatable_base_key::<T>();
let mut lua = Lua::from_existing_state(self.lua, false);
match lua.queryc::<LuaTable>(&typeid) {
Some(_) => {
true
}
None => unsafe {
sys::lua_newtable(self.lua);
"__typeid".push_to_lua(self.lua);
(&typeid).push_to_lua(self.lua);
sys::lua_settable(self.lua, -3);
if !self.light {
"__gc".push_to_lua(self.lua);
sys::lua_pushcfunction(self.lua, destructor_wrapper::<T>);
sys::lua_settable(self.lua, -3);
}
"__index".push_to_lua(self.lua);
sys::lua_pushcfunction(self.lua, index_metatable::<T>);
sys::lua_rawset(self.lua, -3);
"__newindex".push_to_lua(self.lua);
sys::lua_pushcfunction(self.lua, newindex_metatable::<T>);
sys::lua_rawset(self.lua, -3);
sys::lua_setglobal(self.lua, typeid.as_ptr() as *const c_char);
let typeid = get_metatable_real_key::<T>();
sys::lua_newtable(self.lua);
sys::lua_setglobal(self.lua, typeid.as_ptr());
false
},
}
}
pub fn create(&mut self) -> &mut LuaObject<'a, T> {
self.ensure_matetable();
unsafe {
let name = CString::new(self.name).unwrap();
if self.light {
sys::lua_pushcfunction(self.lua, constructor_light_wrapper::<T>);
} else {
sys::lua_pushcfunction(self.lua, constructor_wrapper::<T>);
}
sys::lua_setglobal(self.lua, name.as_ptr());
}
self
}
pub fn add_method_get<P>(&mut self, name: &str, param: P) -> &mut LuaObject<'a, T>
where
P: LuaPush,
{
let typeid = get_metatable_real_key::<T>();
let mut lua = Lua::from_existing_state(self.lua, false);
match lua.queryc::<LuaTable>(&typeid) {
Some(mut table) => {
table.set(name, param);
}
None => (),
};
self
}
pub fn add_method_set<P>(&mut self, name: &str, param: P) -> &mut LuaObject<'a, T>
where
P: LuaPush,
{
let typeid = get_metatable_real_key::<T>();
let mut lua = Lua::from_existing_state(self.lua, false);
match lua.queryc::<LuaTable>(&typeid) {
Some(mut table) => {
table.set(check_set_field_key(name), param);
}
None => (),
};
self
}
pub fn mark_field(&mut self, name: &str) -> &mut LuaObject<'a, T>
{
let typeid = get_metatable_real_key::<T>();
let mut lua = Lua::from_existing_state(self.lua, false);
match lua.queryc::<LuaTable>(&typeid) {
Some(mut table) => {
table.set(check_is_field_key(name), true);
}
None => (),
};
self
}
pub fn def<P>(&mut self, name: &str, param: P) -> &mut LuaObject<'a, T>
where
P: LuaPush,
{
let typeid = get_metatable_real_key::<T>();
let mut lua = Lua::from_existing_state(self.lua, false);
match lua.queryc::<LuaTable>(&typeid) {
Some(mut table) => {
table.set(name, param);
}
None => (),
};
self
}
pub fn register(
&mut self,
name: &str,
func: extern "C" fn(*mut sys::lua_State) -> libc::c_int,
) -> &mut LuaObject<'a, T> {
let typeid = get_metatable_real_key::<T>();
let mut lua = Lua::from_existing_state(self.lua, false);
match lua.queryc::<LuaTable>(&typeid) {
Some(mut table) => {
table.register(name, func);
}
None => (),
};
self
}
}
#[macro_export]
macro_rules! add_object_field {
($userdata: expr, $name: ident, $t: ty, $field_type: ty) => {
$userdata.add_method_get(&format!("{}", stringify!($name)), wmlua::function1(|obj: &mut $t| -> &$field_type { println!("aaaa"); &obj.$name }) );
$userdata.add_method_set(stringify!($name), wmlua::function2(|obj: &mut $t, val: $field_type| { println!("bbbb {}", val); obj.$name = val; }));
$userdata.mark_field(stringify!($name));
};
}
#[macro_export]
macro_rules! object_impl {
($t: ty) => {
impl<'a> LuaRead for &'a mut $t {
fn lua_read_with_pop_impl(lua: *mut lua_State, index: i32, _pop: i32) -> Option<&'a mut $t> {
wmlua::userdata::read_userdata(lua, index)
}
}
impl LuaPush for $t {
fn push_to_lua(self, lua: *mut lua_State) -> i32 {
unsafe {
let obj = std::boxed::Box::into_raw(std::boxed::Box::new(self));
wmlua::userdata::push_lightuserdata(&mut *obj, lua, |_| {});
let typeid = std::ffi::CString::new(format!("{:?}", std::any::TypeId::of::<$t>())).unwrap();
wmlua::lua_getglobal(lua, typeid.as_ptr());
if wmlua::lua_istable(lua, -1) {
wmlua::lua_setmetatable(lua, -2);
} else {
wmlua::lua_pop(lua, 1);
}
1
}
}
}
};
}