diff --git a/src/table.rs b/src/table.rs index a7f9de5..4aa1cd2 100755 --- a/src/table.rs +++ b/src/table.rs @@ -279,6 +279,14 @@ impl<'a> Table<'a> { Ok(()) } + + pub fn try_into(self) -> Result { + T::from_table(self.state, self) + } + + pub fn try_from(state: &'a State, proxy: T) -> Result> { + proxy.as_table(state) + } } impl<'a> PushToLuaStack<'a> for Table<'a> { @@ -298,7 +306,159 @@ impl<'a> FromLuaStack<'a> for Table<'a> { } impl<'a> FromLua<'a> for Table<'a> { - fn from_lua(_lua: &'a State, val: Value<'a>) -> crate::Result { + fn from_lua(_: &'a State, val: Value<'a>) -> crate::Result { val.into_table() } +} + +impl<'a> AsLua<'a> for Table<'a> { + fn as_lua(&self, _: &'a State) -> crate::Result> { + Ok(Value::Table(self.clone())) + } +} + +/// This trait is used for proxying a Rust type to and from Lua as a Lua table. In Lua, you can +/// make a representation of your Rust type, then implement this trait on the Rust type. This +/// trait allows you to put your Rust type into Lua as the type you created in Lua, and you can +/// also retrieve an instance of your Rust type from an instance of the Lua type. This could help +/// with minimizing the amount of calls to and from Rust. +pub trait TableProxy: Sized { + /// Create an instance of `Self` from the table + fn from_table<'a>(state: &'a State, table: Table<'a>) -> Result; + /// Creates a Lua instance from `Self` + fn as_table<'a>(&self, state: &'a State) -> Result>; +} + +#[cfg(test)] +mod tests { + use crate::{tests::Vec2, Function, State, StdLibrary, Table, TableProxy}; + + impl TableProxy for Vec2 { + fn from_table<'a>(_state: &'a crate::State, table: crate::Table<'a>) -> crate::Result { + let x: f32 = table.get("x")?; + let y: f32 = table.get("y")?; + + Ok(Vec2 { + x, + y, + }) + } + + fn as_table<'a>(&self, state: &'a crate::State) -> crate::Result> { + let globals = state.globals()?; + let vec2: Table = globals.get("Vec2")?; + let new_fn: Function = vec2.get("new")?; + new_fn.exec((vec2, self.x, self.y)) + } + } + + #[test] + fn table_get() -> crate::Result<()> { + let lua = State::new(); + lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]); + + let res = lua.load( + "test.lua", + r#" + text = "Hello, World" + "#)?.execute::<_, ()>(()); + + // pretty print the error + if let Err(err) = res { + panic!("{}", err); + } + + let globals = lua.globals()?; + let text: String = globals.get("text")?; + assert_eq!(text, "Hello, World".to_string()); + + Ok(()) + } + + #[test] + fn table_set() -> crate::Result<()> { + let lua = State::new(); + lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]); + + let globals = lua.globals()?; + globals.set("text", "Hello, World")?; + + let res = lua.load( + "test.lua", + r#" + assert(text == "Hello, World", "The text was not set correctly") + "#)?.execute::<_, ()>(()); + + // pretty print the error + if let Err(err) = res { + panic!("{}", err); + } + + Ok(()) + } + + #[test] + fn table_proxy() -> crate::Result<()> { + let lua = State::new(); + lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]); + + let res = lua.load( + "test.lua", + r#" + Vec2 = { x = 0.0, y = 0.0 } + Vec2.__index = Vec2 + Vec2.__name = "Vec2" + + function Vec2:new(x, y) + local v = {} + setmetatable(v, Vec2) + + v.x = x + v.y = y + + return v + end + + function Vec2:__tostring() + return "Vec2(" .. self.x .. ", " .. self.y .. ")" + end + + function do_math() + return Vec2:new(15, 20) + end + "#)?.execute::<_, ()>(()); + + // pretty print the error + if let Err(err) = res { + panic!("{}", err); + } + + let globals = lua.globals()?; + let math_fn: Function = globals.get("do_math")?; + let lua_vec2: Table = math_fn.exec(())?; + let mut vec2 = Vec2::from_table(&lua, lua_vec2)?; + assert_eq!(vec2.x, 15.0); + assert_eq!(vec2.y, 20.0); + + vec2.x *= 2.0; + vec2.y *= 2.0; + + globals.set("pos", vec2.as_table(&lua)?)?; + + let res = lua.load( + "test.lua", + r#" + -- Vec2 stuff is included from last chunk + + assert(type(pos) == "table", "The global 'pos' is not a table like expected of 'as_table'!!") + print("pos is " .. tostring(pos)) + "#)?.execute::<_, ()>(()); + + // pretty print the error + if let Err(err) = res { + panic!("{}", err); + } + + Ok(()) + } } \ No newline at end of file