Add meta methods, methods, functions, and field setters to UserData
This commit is contained in:
parent
9ef4203619
commit
a8db62fe08
|
@ -61,7 +61,7 @@ impl<'a> Function<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AsLua<'a> for Function<'a> {
|
impl<'a> AsLua<'a> for Function<'a> {
|
||||||
fn as_lua(&self, lua: &'a State) -> crate::Result<crate::Value<'a>> {
|
fn as_lua(&self, _lua: &'a State) -> crate::Result<crate::Value<'a>> {
|
||||||
Ok(crate::Value::Function(self.clone()))
|
Ok(crate::Value::Function(self.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,6 +135,7 @@ macro_rules! impl_function_arg_tuple {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
impl<'a, $first: FromLuaStack<'a>, $($name: FromLuaStack<'a>,)+> FromLuaStackMulti<'a> for ($first, $($name,)+) {
|
impl<'a, $first: FromLuaStack<'a>, $($name: FromLuaStack<'a>,)+> FromLuaStackMulti<'a> for ($first, $($name,)+) {
|
||||||
fn len() -> usize {
|
fn len() -> usize {
|
||||||
$count
|
$count
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{any::type_name, ffi::CStr, marker::PhantomData, mem, ptr, str::Utf8Error, sync::Arc};
|
use std::{marker::PhantomData, sync::Arc};
|
||||||
|
|
||||||
use lua::{lua_typename, lua_type};
|
|
||||||
use mlua_sys as lua;
|
use mlua_sys as lua;
|
||||||
|
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
@ -90,8 +89,18 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
print("Vec2: " .. dump_table(Vec2))
|
print("Vec2: " .. dump_table(Vec2))
|
||||||
print("Meta Vec2: " .. dump_table(getmetatable(Vec2)))
|
print("Meta Vec2: " .. dump_table(getmetatable(Vec2)))
|
||||||
--local vec2 = Vec2.new(50, 50)
|
|
||||||
print("Vec2 is (" .. Vec2.x .. ", " .. Vec2.y .. ")")
|
print("Vec2 is (" .. Vec2.x .. ", " .. Vec2.y .. ")")
|
||||||
|
|
||||||
|
local v1 = Vec2.new(50, 50)
|
||||||
|
print("v1 is (" .. v1.x .. ", " .. v1.y .. ")")
|
||||||
|
|
||||||
|
local v2 = Vec2.new(500, 500)
|
||||||
|
print("v2 is (" .. v2.x .. ", " .. v2.y .. ")")
|
||||||
|
|
||||||
|
local v_add = v1 + v2
|
||||||
|
v_add.x = 90
|
||||||
|
print("v_add is (" .. v_add.x .. ", " .. v_add.y .. ")")
|
||||||
"#).unwrap();
|
"#).unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -198,6 +207,12 @@ pub trait FromLuaStack<'a>: Sized {
|
||||||
unsafe fn from_lua_stack(state: &'a State) -> Result<Self>;
|
unsafe fn from_lua_stack(state: &'a State) -> Result<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> PushToLuaStack<'a> for () {
|
||||||
|
unsafe fn push_to_lua_stack(&self, _state: &'a State) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Implements PushToLuaStack for a number
|
/// Implements PushToLuaStack for a number
|
||||||
macro_rules! impl_as_lua_number {
|
macro_rules! impl_as_lua_number {
|
||||||
($ty: ident) => {
|
($ty: ident) => {
|
||||||
|
@ -207,7 +222,7 @@ macro_rules! impl_as_lua_number {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromLua for $ty {
|
impl<'a> FromLua<'a> for $ty {
|
||||||
fn from_lua(_lua: &State, val: Value) -> crate::Result<Self> {
|
fn from_lua(_lua: &State, val: Value) -> crate::Result<Self> {
|
||||||
match val {
|
match val {
|
||||||
Value::Number(v) => Ok(v as $ty),
|
Value::Number(v) => Ok(v as $ty),
|
||||||
|
@ -253,7 +268,7 @@ impl<'a> PushToLuaStack<'a> for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FromLuaStack<'a> for String {
|
/* impl<'a> FromLuaStack<'a> for String {
|
||||||
unsafe fn from_lua_stack(state: &'a State) -> Result<Self> {
|
unsafe fn from_lua_stack(state: &'a State) -> Result<Self> {
|
||||||
let s = state.state_ptr();
|
let s = state.state_ptr();
|
||||||
|
|
||||||
|
@ -263,6 +278,12 @@ impl<'a> FromLuaStack<'a> for String {
|
||||||
let cstr = CStr::from_ptr(cstr);
|
let cstr = CStr::from_ptr(cstr);
|
||||||
Ok(cstr.to_str().unwrap().to_string())
|
Ok(cstr.to_str().unwrap().to_string())
|
||||||
}
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
impl<'a> FromLua<'a> for String {
|
||||||
|
fn from_lua(_lua: &State, val: Value) -> crate::Result<Self> {
|
||||||
|
val.as_string().cloned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PushToLuaStack<'a> for &str {
|
impl<'a> PushToLuaStack<'a> for &str {
|
||||||
|
@ -286,7 +307,23 @@ impl Userdata for Vec2 {
|
||||||
fn build<'a>(builder: &mut UserdataBuilder<'a, Vec2>) -> crate::Result<()> {
|
fn build<'a>(builder: &mut UserdataBuilder<'a, Vec2>) -> crate::Result<()> {
|
||||||
builder.name("Vec2")
|
builder.name("Vec2")
|
||||||
.field_getter("x", |_, this| Ok(this.x))
|
.field_getter("x", |_, this| Ok(this.x))
|
||||||
.field_getter("y", |_, this| Ok(this.y));
|
.field_getter("y", |_, this| Ok(this.y))
|
||||||
|
|
||||||
|
.field_setter("x", |_, this, x: f32| this.x = x)
|
||||||
|
.field_setter("y", |_, this, y: f32| this.y = y)
|
||||||
|
|
||||||
|
.function("new", |lua, (x, y)| {
|
||||||
|
lua.create_userdata("Vec2", Vec2 { x, y, })
|
||||||
|
})
|
||||||
|
.meta_method(MetaMethod::Add, |lua, lhs: &Vec2, (rhs,): (&Vec2,)| {
|
||||||
|
let lx = lhs.x;
|
||||||
|
let ly = lhs.y;
|
||||||
|
|
||||||
|
let rx = rhs.x;
|
||||||
|
let ry = rhs.y;
|
||||||
|
|
||||||
|
lua.create_userdata("Vec2", Vec2 { x: lx + rx, y: ly + ry, })
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -301,7 +338,7 @@ impl<T: Userdata> UserdataProxy<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: Userdata> AsLua<'a> for UserdataProxy<T> {
|
impl<'a, T: Userdata> AsLua<'a> for UserdataProxy<T> {
|
||||||
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>> {
|
fn as_lua(&self, _lua: &'a State) -> crate::Result<Value<'a>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,12 +351,12 @@ impl<T: Userdata> Userdata for UserdataProxy<T> {
|
||||||
|
|
||||||
builder.name(&other.name.unwrap());
|
builder.name(&other.name.unwrap());
|
||||||
|
|
||||||
for (lbl, getters) in other.field_getters.into_iter() {
|
/* for (lbl, getters) in other.field_getters.into_iter() {
|
||||||
let wrap = |state: &State, data: &Self| {
|
let wrap = |state: &State, data: &Self| {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
builder.field_getter(&lbl, wrap);
|
builder.field_getter(&lbl, wrap);
|
||||||
}
|
} */
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use core::ffi;
|
use core::ffi;
|
||||||
use std::{ffi::{CString, CStr}, mem, ptr::{self, NonNull}, str::Utf8Error};
|
use std::{collections::HashMap, ffi::{CString, CStr}, mem, ptr::{self, NonNull}, str::Utf8Error};
|
||||||
|
|
||||||
use mlua_sys as lua;
|
use mlua_sys as lua;
|
||||||
|
|
||||||
use crate::{ensure_type, lua_error_guard, AnyUserdata, Error, FromLuaStack, FromLuaStackMulti, Function, LuaRef, PushToLuaStack, PushToLuaStackMulti, Result, StackGuard, Table, Userdata, UserdataBuilder, Value};
|
use crate::{lua_error_guard, AnyUserdata, AsLua, Error, FromLuaStack, FromLuaVec, Function, LuaRef, MetaMethod, PushToLuaStack, PushToLuaStackMulti, Result, StackGuard, Table, Userdata, UserdataBuilder, Value, ValueVec};
|
||||||
|
|
||||||
pub fn ptr_to_string(ptr: *const i8) -> std::result::Result<String, Utf8Error> {
|
pub fn ptr_to_string(ptr: *const i8) -> std::result::Result<String, Utf8Error> {
|
||||||
let c = unsafe { CStr::from_ptr(ptr) };
|
let c = unsafe { CStr::from_ptr(ptr) };
|
||||||
|
@ -117,7 +117,12 @@ impl State {
|
||||||
if lua::luaL_dostring(lua, text_c) != 0 {
|
if lua::luaL_dostring(lua, text_c) != 0 {
|
||||||
let error_c = CStr::from_ptr(lua::lua_tostring(lua, -1));
|
let error_c = CStr::from_ptr(lua::lua_tostring(lua, -1));
|
||||||
let error_str = error_c.to_str()
|
let error_str = error_c.to_str()
|
||||||
.expect("Error bytes are invalid!");
|
.unwrap_or_else(|_| {
|
||||||
|
let b = error_c.to_bytes();
|
||||||
|
std::str::from_utf8_unchecked(b)
|
||||||
|
});
|
||||||
|
//.expect("Error bytes are invalid!");
|
||||||
|
//std::str::from
|
||||||
return Err(Error::runtime(error_str));
|
return Err(Error::runtime(error_str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,6 +182,7 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) unsafe fn print_stack(state: &State) {
|
pub(crate) unsafe fn print_stack(state: &State) {
|
||||||
let s = state.state_ptr();
|
let s = state.state_ptr();
|
||||||
let t = lua::lua_gettop(s);
|
let t = lua::lua_gettop(s);
|
||||||
|
@ -192,7 +198,7 @@ impl State {
|
||||||
|
|
||||||
pub fn create_function<'a, A, R, F>(&self, f: F) -> Result<Function>
|
pub fn create_function<'a, A, R, F>(&self, f: F) -> Result<Function>
|
||||||
where
|
where
|
||||||
A: FromLuaStackMulti<'a>,
|
A: FromLuaVec<'a>,
|
||||||
R: PushToLuaStackMulti<'a>,
|
R: PushToLuaStackMulti<'a>,
|
||||||
F: Fn(&'a State, A) -> Result<R> + 'static,
|
F: Fn(&'a State, A) -> Result<R> + 'static,
|
||||||
{
|
{
|
||||||
|
@ -236,17 +242,13 @@ impl State {
|
||||||
state: &'a State,
|
state: &'a State,
|
||||||
}
|
}
|
||||||
|
|
||||||
let wrapper_fn = move |lua: &State, narg: i32| -> i32 {
|
let wrapper_fn = move |lua: &State, _narg: i32| -> i32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
let lua: &State = mem::transmute(lua); // transmute lifetimes
|
let lua: &State = mem::transmute(lua); // transmute lifetimes
|
||||||
|
|
||||||
if narg != A::len() as i32 {
|
|
||||||
Error::Runtime(format!("incorrect number of arguments provided to lua function, expected {}", A::len()))
|
|
||||||
.throw_lua(lua.state_ptr());
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_error_guard(lua, || {
|
lua_error_guard(lua, || {
|
||||||
let args = A::results_from_lua_stack(lua)?;
|
let vec = ValueVec::from_lua_stack(lua)?;
|
||||||
|
let args = A::from_lua_value_vec(lua, vec)?;
|
||||||
|
|
||||||
let r = f(lua, args)?;
|
let r = f(lua, args)?;
|
||||||
r.push_args_to_lua_stack(lua)?;
|
r.push_args_to_lua_stack(lua)?;
|
||||||
|
@ -297,7 +299,6 @@ impl State {
|
||||||
|
|
||||||
// if the metatable is not made yet, make it
|
// if the metatable is not made yet, make it
|
||||||
let mt = self.create_userdata_metatable::<T>()?;
|
let mt = self.create_userdata_metatable::<T>()?;
|
||||||
//let mt = mt.get_metatable().unwrap(); // the table 100% has a metatable
|
|
||||||
mt.push_to_lua_stack(self)?;
|
mt.push_to_lua_stack(self)?;
|
||||||
} else if ty != lua::LUA_TTABLE {
|
} else if ty != lua::LUA_TTABLE {
|
||||||
return Err(Error::RegistryConflict(name.to_string()));
|
return Err(Error::RegistryConflict(name.to_string()));
|
||||||
|
@ -314,21 +315,63 @@ impl State {
|
||||||
T::build(&mut builder)?;
|
T::build(&mut builder)?;
|
||||||
|
|
||||||
let getters = builder.field_getters;
|
let getters = builder.field_getters;
|
||||||
|
let setters = builder.field_setters;
|
||||||
let name = builder.name.unwrap();
|
let name = builder.name.unwrap();
|
||||||
|
|
||||||
|
let mut fns_table = HashMap::new();
|
||||||
|
for (func_name, func) in builder.functions.into_iter() {
|
||||||
|
let lua_func = self.create_function(move |lua: &State, vals: ValueVec| {
|
||||||
|
func(lua, vals)
|
||||||
|
})?;
|
||||||
|
// Safety: This will be alive for as long as the lua state is alive
|
||||||
|
let lua_func: Function<'_> = unsafe { mem::transmute(lua_func) };
|
||||||
|
fns_table.insert(func_name, lua_func);
|
||||||
|
}
|
||||||
|
|
||||||
let mt = self.create_meta_table(&name)?;
|
let mt = self.create_meta_table(&name)?;
|
||||||
|
|
||||||
if !getters.is_empty() {
|
// dont create an index function if there are no getters,
|
||||||
|
// or if an index metamethod was defined
|
||||||
|
if !getters.is_empty() || !builder.meta_methods.contains_key("__index") {
|
||||||
let index_fn = self.create_function(move |lua: &State, (ud, key): (AnyUserdata, String)| {
|
let index_fn = self.create_function(move |lua: &State, (ud, key): (AnyUserdata, String)| {
|
||||||
if let Some(getter) = getters.get(&key) {
|
if let Some(getter) = getters.get(&key) {
|
||||||
let r = getter(lua, Value::Userdata(ud))?;
|
let r = getter(lua, ValueVec::from(Value::Userdata(ud)))?;
|
||||||
Ok(r)
|
Ok(r)
|
||||||
|
} else if let Some(function) = fns_table.get(&key) {
|
||||||
|
Ok(Value::Function(function.clone()))
|
||||||
} else {
|
} else {
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
mt.set_meta("__index", index_fn)?;
|
mt.set_meta(MetaMethod::Index, index_fn)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dont create an index function if there are no setters,
|
||||||
|
// or if a new index metamethod was defined
|
||||||
|
if !setters.is_empty() || !builder.meta_methods.contains_key("__newindex") {
|
||||||
|
let index_fn = self.create_function(move |lua: &State, (ud, key, val): (AnyUserdata, String, Value)| {
|
||||||
|
if let Some(setter) = setters.get(&key) {
|
||||||
|
let mut vec = ValueVec::from(ud.as_lua(lua)?);
|
||||||
|
vec.push_back(val);
|
||||||
|
|
||||||
|
// ignore value result, the wrapper function doesn't return anything
|
||||||
|
setter(lua, vec)?;
|
||||||
|
|
||||||
|
Ok(Value::None)
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nil)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
mt.set_meta(MetaMethod::NewIndex, index_fn)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (mm_name, mm) in builder.meta_methods.into_iter() {
|
||||||
|
let mm_func = self.create_function(move |lua: &State, vals: ValueVec| {
|
||||||
|
mm(lua, vals)
|
||||||
|
})?;
|
||||||
|
mt.set_meta(mm_name, mm_func)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(mt)
|
Ok(mt)
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
|
|
||||||
use std::{ffi::CStr, ops::Deref, sync::Arc};
|
|
||||||
|
|
||||||
use mlua_sys as lua;
|
use mlua_sys as lua;
|
||||||
|
|
||||||
use crate::{ensure_type, FromLuaStack, LuaRef, PushToLuaStack, Result, StackGuard, State};
|
use crate::{ensure_type, FromLuaStack, LuaRef, PushToLuaStack, Result, StackGuard, State};
|
||||||
|
@ -165,7 +163,6 @@ impl<'a> Table<'a> {
|
||||||
val.push_to_lua_stack(self.state)?;
|
val.push_to_lua_stack(self.state)?;
|
||||||
|
|
||||||
lua::lua_rawset(s, -3);
|
lua::lua_rawset(s, -3);
|
||||||
lua::lua_pop(self.state.state_ptr(), -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,12 +1,89 @@
|
||||||
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
|
use std::{collections::HashMap, marker::PhantomData};
|
||||||
|
|
||||||
use crate::{ensure_type, AsLua, FromLua, FromLuaStack, LuaRef, PushToLuaStack, State, Table, Value};
|
use crate::{ensure_type, AsLua, FromLua, FromLuaStack, FromLuaVec, LuaRef, PushToLuaStack, State, Value, ValueVec};
|
||||||
|
|
||||||
use mlua_sys as lua;
|
use mlua_sys as lua;
|
||||||
|
|
||||||
//pub type FieldSetter<T> = fn(lua: &State, this: &T);
|
//pub type FieldSetter<T> = fn(lua: &State, this: &T);
|
||||||
//pub type FieldGetter<T, U> = fn(lua: &State, this: &T, val: &U);
|
//pub type FieldGetter<T, U> = fn(lua: &State, this: &T, val: &U);
|
||||||
|
|
||||||
|
/// An enum representing all Lua MetaMethods
|
||||||
|
/// https://gist.github.com/oatmealine/655c9e64599d0f0dd47687c1186de99f
|
||||||
|
pub enum MetaMethod {
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Unm,
|
||||||
|
Mod,
|
||||||
|
Pow,
|
||||||
|
IDiv,
|
||||||
|
BAnd,
|
||||||
|
BOr,
|
||||||
|
BXOr,
|
||||||
|
BNot,
|
||||||
|
Shl,
|
||||||
|
Shr,
|
||||||
|
Eq,
|
||||||
|
Lt,
|
||||||
|
Le,
|
||||||
|
Concat,
|
||||||
|
Len,
|
||||||
|
Index,
|
||||||
|
NewIndex,
|
||||||
|
Call,
|
||||||
|
Mode,
|
||||||
|
Close,
|
||||||
|
Gc,
|
||||||
|
ToString,
|
||||||
|
Metatable,
|
||||||
|
Name,
|
||||||
|
Pairs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for MetaMethod {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
MetaMethod::Add => "__add",
|
||||||
|
MetaMethod::Sub => "__sub",
|
||||||
|
MetaMethod::Mul => "__mul",
|
||||||
|
MetaMethod::Div => "__div",
|
||||||
|
MetaMethod::Unm => "__unm",
|
||||||
|
MetaMethod::Mod => "__mod",
|
||||||
|
MetaMethod::Pow => "__pow",
|
||||||
|
MetaMethod::IDiv => "__idiv",
|
||||||
|
MetaMethod::BAnd => "__band",
|
||||||
|
MetaMethod::BOr => "__bor",
|
||||||
|
MetaMethod::BXOr => "__bxor",
|
||||||
|
MetaMethod::BNot => "__bnot",
|
||||||
|
MetaMethod::Shl => "__shl",
|
||||||
|
MetaMethod::Shr => "__shr",
|
||||||
|
MetaMethod::Eq => "__eq",
|
||||||
|
MetaMethod::Lt => "__lt",
|
||||||
|
MetaMethod::Le => "__le",
|
||||||
|
MetaMethod::Concat => "__concat",
|
||||||
|
MetaMethod::Len => "__len",
|
||||||
|
MetaMethod::Index => "__index",
|
||||||
|
MetaMethod::NewIndex => "__newindex",
|
||||||
|
MetaMethod::Call => "__call",
|
||||||
|
MetaMethod::Mode => "__mode",
|
||||||
|
MetaMethod::Close => "__close",
|
||||||
|
MetaMethod::Gc => "__gc",
|
||||||
|
MetaMethod::ToString => "__tostring",
|
||||||
|
MetaMethod::Metatable => "__metatable",
|
||||||
|
MetaMethod::Name => "__name",
|
||||||
|
MetaMethod::Pairs => "__pairs",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PushToLuaStack<'a> for MetaMethod {
|
||||||
|
unsafe fn push_to_lua_stack(&self, state: &'a State) -> crate::Result<()> {
|
||||||
|
let s = self.as_ref().to_string();
|
||||||
|
s.push_to_lua_stack(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait FieldSetter {
|
pub trait FieldSetter {
|
||||||
fn set_field(&self, val: Value);
|
fn set_field(&self, val: Value);
|
||||||
}
|
}
|
||||||
|
@ -15,11 +92,14 @@ pub trait FieldGetter {
|
||||||
fn get_field(&self) -> Value;
|
fn get_field(&self) -> Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserdataFn<'a> = Box<dyn Fn(&'a State, Value<'a>) -> crate::Result<Value<'a>>>;
|
type UserdataFn<'a> = Box<dyn Fn(&'a State, ValueVec<'a>) -> crate::Result<Value<'a>>>;
|
||||||
|
|
||||||
pub struct UserdataBuilder<'a, T> {
|
pub struct UserdataBuilder<'a, T> {
|
||||||
pub(crate) name: Option<String>,
|
pub(crate) name: Option<String>,
|
||||||
pub(crate) field_getters: HashMap<String, UserdataFn<'a>>,
|
pub(crate) field_getters: HashMap<String, UserdataFn<'a>>,
|
||||||
|
pub(crate) field_setters: HashMap<String, UserdataFn<'a>>,
|
||||||
|
pub(crate) functions: HashMap<String, UserdataFn<'a>>,
|
||||||
|
pub(crate) meta_methods: HashMap<String, UserdataFn<'a>>,
|
||||||
_marker: PhantomData<T>,
|
_marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +108,9 @@ impl<'a, T> UserdataBuilder<'a, T> {
|
||||||
Self {
|
Self {
|
||||||
name: None,
|
name: None,
|
||||||
field_getters: HashMap::new(),
|
field_getters: HashMap::new(),
|
||||||
|
field_setters: HashMap::new(),
|
||||||
|
functions: HashMap::new(),
|
||||||
|
meta_methods: HashMap::new(),
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +125,8 @@ impl<'a, T> UserdataBuilder<'a, T> {
|
||||||
F: Fn(&'a State, &T) -> crate::Result<R> + 'static,
|
F: Fn(&'a State, &T) -> crate::Result<R> + 'static,
|
||||||
R: AsLua<'a>,
|
R: AsLua<'a>,
|
||||||
{
|
{
|
||||||
let wrap = move |lua: &'a State, val: Value<'a>| {
|
let wrap = move |lua: &'a State, mut val: ValueVec<'a>| {
|
||||||
|
let val = val.pop_front().unwrap();
|
||||||
let this = val.as_userdata().unwrap(); // if this panics, its a bug
|
let this = val.as_userdata().unwrap(); // if this panics, its a bug
|
||||||
let this = this.as_ref::<T>()?;
|
let this = this.as_ref::<T>()?;
|
||||||
f(lua, this).and_then(|r| r.as_lua(lua))
|
f(lua, this).and_then(|r| r.as_lua(lua))
|
||||||
|
@ -52,68 +136,81 @@ impl<'a, T> UserdataBuilder<'a, T> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_meta_table(self, state: &'a State, table: &Table<'a>) -> crate::Result<()> {
|
pub fn field_setter<F, V>(&mut self, name: &str, f: F) -> &mut Self
|
||||||
//let this: Self = unsafe { std::mem::transmute(self) };
|
|
||||||
//let getters = *self.field_getters;
|
|
||||||
|
|
||||||
/* let index_fn = state.create_function(move |lua: &'a State, (ud, key): (AnyUserdata<'a>, String)| {
|
|
||||||
let ud_ref = ud.as_ref::<T>()?;
|
|
||||||
|
|
||||||
if let Some(getter) = self.field_getters.get(&key) {
|
|
||||||
let r = getter(lua, ome(ud_ref), Value::Nil)?;
|
|
||||||
|
|
||||||
let r = unsafe { std::mem::transmute(r) };
|
|
||||||
|
|
||||||
Ok(r)
|
|
||||||
} else {
|
|
||||||
//ud.get::<_, Value<'a>>(key)
|
|
||||||
Ok(Value::Nil)
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
table.set("__index", index_fn)?; */
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/* pub fn wrap_builder<U, F>(this: &mut UserdataBuilder<'a, T>, other: UserdataBuilder<'a, U>, to_proxied: F)
|
|
||||||
where
|
where
|
||||||
F: Fn(&'a T) -> &'a U + 'a,
|
F: Fn(&'a State, &mut T, V) -> () + 'static,
|
||||||
|
V: FromLua<'a>,
|
||||||
{
|
{
|
||||||
let to_proxied = Arc::new(to_proxied);
|
let wrap = move |lua: &'a State, mut val: ValueVec<'a>| {
|
||||||
for (lbl, getter) in other.field_getters.into_iter() {
|
let lua_val = val.pop_front().unwrap();
|
||||||
let to_proxied_cl = to_proxied.clone();
|
let this = lua_val.as_userdata().unwrap(); // if this panics, its a bug
|
||||||
let new_getter = move |lua: &'a State, this: Option<&T>, val: Value<'a>| {
|
let this = this.as_mut::<T>()?;
|
||||||
let proxy = this.map(|t| to_proxied_cl(t));
|
|
||||||
getter(lua, proxy, val)
|
let lua_val = val.pop_front().unwrap();
|
||||||
|
let v_arg = V::from_lua(lua, lua_val)?;
|
||||||
|
|
||||||
|
f(lua, this, v_arg).as_lua(lua)
|
||||||
};
|
};
|
||||||
this.field_getters.insert(lbl, Box::new(new_getter));
|
self.field_setters.insert(name.to_string(), Box::new(wrap));
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* pub fn into_userdata(self, state: &'a State, data: T) -> crate::Result<Table<'a>> {
|
self
|
||||||
let name = self.name
|
|
||||||
.expect("No name was set for userdata!");
|
|
||||||
|
|
||||||
let getters = self.field_getters.clone();
|
|
||||||
let index_fn = state.create_function(move |lua: &'a State, (ud, key): (AnyUserdata<'a>, String)| {
|
|
||||||
let ud_ref = ud.as_ref::<T>()?;
|
|
||||||
|
|
||||||
if let Some(getter) = getters.get(&key) {
|
|
||||||
getter(lua, Some(ud_ref), Value::Nil)
|
|
||||||
} else {
|
|
||||||
//ud.get::<_, Value<'a>>(key)
|
|
||||||
Ok(Value::Nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo!()
|
pub fn function<F, R, A>(&mut self, name: &str, f: F) -> &mut Self
|
||||||
})?;
|
where
|
||||||
|
F: Fn(&'a State, A) -> crate::Result<R> + 'static,
|
||||||
|
A: FromLuaVec<'a>,
|
||||||
|
R: AsLua<'a>,
|
||||||
|
{
|
||||||
|
let wrap = move |lua: &'a State, val: ValueVec<'a>| {
|
||||||
|
let args = A::from_lua_value_vec(lua, val)?;
|
||||||
|
f(lua, args).and_then(|r| r.as_lua(lua))
|
||||||
|
};
|
||||||
|
self.functions.insert(name.to_string(), Box::new(wrap));
|
||||||
|
|
||||||
let mt = state.create_meta_table(&name)?;
|
self
|
||||||
mt.set("__index", index_fn)?;
|
}
|
||||||
|
|
||||||
Ok(mt)
|
pub fn method<F, R, A>(&mut self, name: &str, f: F) -> &mut Self
|
||||||
} */
|
where
|
||||||
|
F: Fn(&'a State, &T, A) -> crate::Result<R> + 'static,
|
||||||
|
A: FromLuaVec<'a>,
|
||||||
|
R: AsLua<'a>,
|
||||||
|
{
|
||||||
|
let wrap = move |lua: &'a State, mut val: ValueVec<'a>| {
|
||||||
|
let this_val = val.pop_front().unwrap();
|
||||||
|
let this = this_val.as_userdata().unwrap(); // if this panics, its a bug
|
||||||
|
let this = this.as_ref::<T>()?;
|
||||||
|
|
||||||
|
let args = A::from_lua_value_vec(lua, val)?;
|
||||||
|
|
||||||
|
f(lua, this, args).and_then(|r| r.as_lua(lua))
|
||||||
|
};
|
||||||
|
self.functions.insert(name.to_string(), Box::new(wrap));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn meta_method<N, F, R, A>(&mut self, name: N, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
F: Fn(&'a State, &T, A) -> crate::Result<R> + 'static,
|
||||||
|
A: FromLuaVec<'a>,
|
||||||
|
R: AsLua<'a>,
|
||||||
|
{
|
||||||
|
let wrap = move |lua: &'a State, mut val: ValueVec<'a>| {
|
||||||
|
let this_val = val.pop_front().unwrap();
|
||||||
|
let this = this_val.as_userdata().unwrap(); // if this panics, its a bug
|
||||||
|
let this = this.as_ref::<T>()?;
|
||||||
|
|
||||||
|
let args = A::from_lua_value_vec(lua, val)?;
|
||||||
|
|
||||||
|
f(lua, this, args).and_then(|r| r.as_lua(lua))
|
||||||
|
};
|
||||||
|
self.meta_methods.insert(name.as_ref().to_string(), Box::new(wrap));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Userdata: Sized {
|
pub trait Userdata: Sized {
|
||||||
|
@ -141,7 +238,17 @@ impl<'a> AnyUserdata<'a> {
|
||||||
|
|
||||||
self.lref.push_to_lua_stack(self.state)?;
|
self.lref.push_to_lua_stack(self.state)?;
|
||||||
let cptr = lua::lua_touserdata(s, -1);
|
let cptr = lua::lua_touserdata(s, -1);
|
||||||
Ok(cptr.cast::<T>().as_ref().unwrap())
|
Ok(cptr.cast::<T>().as_ref().unwrap()) // TODO: Ensure this userdata matches the type of T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_mut<T>(&self) -> crate::Result<&'a mut T> {
|
||||||
|
unsafe {
|
||||||
|
let s = self.state.state_ptr();
|
||||||
|
|
||||||
|
self.lref.push_to_lua_stack(self.state)?;
|
||||||
|
let cptr = lua::lua_touserdata(s, -1);
|
||||||
|
Ok(cptr.cast::<T>().as_mut().unwrap()) // TODO: Ensure this userdata matches the type of T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,3 +269,22 @@ impl<'a> PushToLuaStack<'a> for AnyUserdata<'a> {
|
||||||
self.lref.push_to_lua_stack(state)
|
self.lref.push_to_lua_stack(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> AsLua<'a> for AnyUserdata<'a> {
|
||||||
|
fn as_lua(&self, _lua: &'a State) -> crate::Result<Value<'a>> {
|
||||||
|
Ok(Value::Userdata(self.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromLua<'a> for AnyUserdata<'a> {
|
||||||
|
fn from_lua(_lua: &State, val: Value<'a>) -> crate::Result<Self> {
|
||||||
|
val.into_userdata()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Userdata> FromLua<'a> for &'a T {
|
||||||
|
fn from_lua(_lua: &'a State, val: Value<'a>) -> crate::Result<Self> {
|
||||||
|
let ud = val.into_userdata()?;
|
||||||
|
ud.as_ref::<T>()
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,8 +22,6 @@ pub unsafe fn ensure_type(state: &State, typ: i32, idx: i32) -> crate::Result<()
|
||||||
let s = cstr.to_str()
|
let s = cstr.to_str()
|
||||||
.expect("Lua type has invalid bytes!");
|
.expect("Lua type has invalid bytes!");
|
||||||
|
|
||||||
println!("ty = {}, typ = {}", lua_type, typ);
|
|
||||||
|
|
||||||
Err(crate::Error::UnexpectedType(exp_s.to_string(), s.to_string()))
|
Err(crate::Error::UnexpectedType(exp_s.to_string(), s.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use std::ffi::CStr;
|
use std::{collections::VecDeque, ffi::CStr, ops::{Deref, DerefMut}};
|
||||||
|
|
||||||
use crate::{AnyUserdata, FromLuaStack, Function, PushToLuaStack, State, Table};
|
use crate::{AnyUserdata, FromLuaStack, Function, PushToLuaStack, State, Table};
|
||||||
|
|
||||||
|
@ -6,6 +6,9 @@ use mlua_sys as lua;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Value<'a> {
|
pub enum Value<'a> {
|
||||||
|
/// A None value means that nothing will be pushed to the lua stack.
|
||||||
|
/// This is different than Nil, which does get pushed to the stack.
|
||||||
|
None,
|
||||||
Nil,
|
Nil,
|
||||||
Number(f64),
|
Number(f64),
|
||||||
String(String),
|
String(String),
|
||||||
|
@ -21,6 +24,7 @@ impl<'a> Value<'a> {
|
||||||
|
|
||||||
pub fn type_name(&self) -> &'static str {
|
pub fn type_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
Value::None => "None",
|
||||||
Value::Nil => "Nil",
|
Value::Nil => "Nil",
|
||||||
Value::Number(_) => "Number",
|
Value::Number(_) => "Number",
|
||||||
Value::String(_) => "String",
|
Value::String(_) => "String",
|
||||||
|
@ -38,6 +42,27 @@ impl<'a> Value<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes self, and attempts to get `AnyUserdata`.
|
||||||
|
///
|
||||||
|
/// Returns an error if this value is not userdata
|
||||||
|
pub fn into_userdata(self) -> crate::Result<AnyUserdata<'a>> {
|
||||||
|
match self {
|
||||||
|
Value::Userdata(ud) => Ok(ud),
|
||||||
|
_ => {
|
||||||
|
Err(crate::Error::UnexpectedType("Userdata".to_string(), self.type_name().to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_string(&self) -> crate::Result<&String> {
|
||||||
|
match self {
|
||||||
|
Value::String(s) => Ok(s),
|
||||||
|
_ => {
|
||||||
|
Err(crate::Error::UnexpectedType("String".to_string(), self.type_name().to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PushToLuaStack<'a> for Value<'a> {
|
impl<'a> PushToLuaStack<'a> for Value<'a> {
|
||||||
|
@ -45,6 +70,9 @@ impl<'a> PushToLuaStack<'a> for Value<'a> {
|
||||||
let s = state.state_ptr();
|
let s = state.state_ptr();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
Value::None => {
|
||||||
|
|
||||||
|
},
|
||||||
Value::Nil => {
|
Value::Nil => {
|
||||||
state.ensure_stack(1)?;
|
state.ensure_stack(1)?;
|
||||||
lua::lua_pushnil(s);
|
lua::lua_pushnil(s);
|
||||||
|
@ -103,6 +131,10 @@ impl<'a> FromLuaStack<'a> for Value<'a> {
|
||||||
Table::from_lua_stack(state)
|
Table::from_lua_stack(state)
|
||||||
.map(|t| Value::Table(t))
|
.map(|t| Value::Table(t))
|
||||||
},
|
},
|
||||||
|
lua::LUA_TUSERDATA => {
|
||||||
|
AnyUserdata::from_lua_stack(state)
|
||||||
|
.map(|ud| Value::Userdata(ud))
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let s = lua::lua_typename(s, ty);
|
let s = lua::lua_typename(s, ty);
|
||||||
let s = CStr::from_ptr(s);
|
let s = CStr::from_ptr(s);
|
||||||
|
@ -119,32 +151,121 @@ pub trait AsLua<'a> {
|
||||||
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>>;
|
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FromLua: Sized {
|
pub trait FromLua<'a>: Sized {
|
||||||
fn from_lua(lua: &State, val: Value) -> crate::Result<Self>;
|
fn from_lua(lua: &'a State, val: Value<'a>) -> crate::Result<Self>;
|
||||||
}
|
|
||||||
|
|
||||||
/* impl<'a> AsLua<'a> for Value<'a> {
|
|
||||||
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>> {
|
|
||||||
Ok(self.clone())
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* impl<'a, T: AsLua<'a>> PushToLuaStack<'a> for T {
|
|
||||||
unsafe fn push_to_lua_stack(&self, state: &'a State) -> crate::Result<()> {
|
|
||||||
let v = self.as_lua(state)?;
|
|
||||||
v.push_to_lua_stack(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
impl<'a, T: FromLua> FromLuaStack<'a> for T {
|
|
||||||
unsafe fn from_lua_stack(state: &'a State) -> crate::Result<Self> {
|
|
||||||
let v = Value::from_lua_stack(state)?;
|
|
||||||
T::from_lua(state, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AsLua<'a> for () {
|
impl<'a> AsLua<'a> for () {
|
||||||
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>> {
|
fn as_lua(&self, _lua: &'a State) -> crate::Result<Value<'a>> {
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> FromLua<'a> for Value<'a> {
|
||||||
|
fn from_lua(_lua: &'a State, val: Value<'a>) -> crate::Result<Self> {
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ValueVec<'a>(VecDeque<Value<'a>>);
|
||||||
|
|
||||||
|
impl<'a> Deref for ValueVec<'a> {
|
||||||
|
type Target = VecDeque<Value<'a>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DerefMut for ValueVec<'a> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromLuaStack<'a> for ValueVec<'a> {
|
||||||
|
unsafe fn from_lua_stack(state: &'a State) -> crate::Result<Self> {
|
||||||
|
let s = state.state_ptr();
|
||||||
|
|
||||||
|
let top = lua::lua_gettop(s);
|
||||||
|
|
||||||
|
let mut vec = VecDeque::new();
|
||||||
|
for _ in 1..(top + 1) {
|
||||||
|
let v = Value::from_lua_stack(state)?;
|
||||||
|
vec.push_front(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ValueVec(vec))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ValueVec<'a> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Value<'a>> for ValueVec<'a> {
|
||||||
|
fn from(value: Value<'a>) -> Self {
|
||||||
|
let mut v = VecDeque::new();
|
||||||
|
v.push_back(value);
|
||||||
|
ValueVec(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for getting something from a ValueVec from Lua
|
||||||
|
pub trait FromLuaVec<'a>: Sized {
|
||||||
|
fn from_lua_value_vec(state: &'a State, values: ValueVec<'a>) -> crate::Result<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromLuaVec<'a> for () {
|
||||||
|
fn from_lua_value_vec(_state: &'a State, _values: ValueVec<'a>) -> crate::Result<Self> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromLuaVec<'a> for ValueVec<'a> {
|
||||||
|
fn from_lua_value_vec(_state: &'a State, values: ValueVec<'a>) -> crate::Result<Self> {
|
||||||
|
Ok(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_from_lua_vec_tuple {
|
||||||
|
( $count: expr, $first: tt, $( $name: tt ),+ ) => (
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
impl<'a, $first: FromLua<'a>, $($name: FromLua<'a>,)+> FromLuaVec<'a> for ($first, $($name,)+) {
|
||||||
|
|
||||||
|
fn from_lua_value_vec(state: &'a State, mut values: ValueVec<'a>) -> crate::Result<Self> {
|
||||||
|
if values.len() != $count {
|
||||||
|
panic!("Not same length"); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
let f = $first::from_lua(state, values.pop_front().unwrap())?;
|
||||||
|
let ($( $name, )+) = ( $( $name::from_lua(state, values.pop_front().unwrap())?, )+ );
|
||||||
|
|
||||||
|
Ok( (f, $( $name, )+) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_from_lua_vec_tuple!( $count - 1, $( $name ),+ );
|
||||||
|
);
|
||||||
|
|
||||||
|
( $count: expr, $only: tt ) => {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
impl<'a, $only: FromLua<'a>> FromLuaVec<'a> for ($only,) {
|
||||||
|
fn from_lua_value_vec(state: &'a State, mut values: ValueVec<'a>) -> crate::Result<Self> {
|
||||||
|
if values.len() != 1 {
|
||||||
|
panic!("Not same length"); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok( ( $only::from_lua(state, values.pop_front().unwrap())?, ) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// hopefully 16 is more than enough
|
||||||
|
// if you have 16 function results, and need more than 16, you NEED help
|
||||||
|
impl_from_lua_vec_tuple! { 16, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16 }
|
||||||
|
|
Loading…
Reference in New Issue