Not sure I like everything about this approach yet

This commit is contained in:
kyren 2017-06-25 01:47:55 -04:00
parent a609f709ee
commit 2c439f8097
4 changed files with 113 additions and 49 deletions

View file

@ -63,24 +63,6 @@ impl<'lua> FromLua<'lua> for LuaFunction<'lua> {
}
}
impl<'lua> ToLua<'lua> for LuaUserData<'lua> {
fn to_lua(self, _: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
Ok(LuaValue::UserData(self))
}
}
impl<'lua> FromLua<'lua> for LuaUserData<'lua> {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<LuaUserData<'lua>> {
match value {
LuaValue::UserData(ud) => Ok(ud),
_ => Err(
LuaConversionError::FromLua("cannot convert lua value to userdata".to_owned())
.into(),
),
}
}
}
impl<'lua> ToLua<'lua> for LuaThread<'lua> {
fn to_lua(self, _: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
Ok(LuaValue::Thread(self))
@ -99,6 +81,24 @@ impl<'lua> FromLua<'lua> for LuaThread<'lua> {
}
}
impl<'lua> ToLua<'lua> for LuaUserData<'lua> {
fn to_lua(self, _: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
Ok(LuaValue::UserData(self))
}
}
impl<'lua> FromLua<'lua> for LuaUserData<'lua> {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<LuaUserData<'lua>> {
match value {
LuaValue::UserData(ud) => Ok(ud),
_ => Err(
LuaConversionError::FromLua("cannot convert lua value to userdata".to_owned())
.into(),
),
}
}
}
impl<'lua, T: LuaUserDataType> ToLua<'lua> for T {
fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
lua.create_userdata(self).map(LuaValue::UserData)
@ -117,6 +117,29 @@ impl<'lua, T: LuaUserDataType + Copy> FromLua<'lua> for T {
}
}
impl<'lua> ToLua<'lua> for LuaErrorUserData<'lua> {
fn to_lua(self, _: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
Ok(LuaValue::Error(self))
}
}
impl<'lua> FromLua<'lua> for LuaErrorUserData<'lua> {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<LuaErrorUserData<'lua>> {
match value {
LuaValue::Error(err) => Ok(err),
_ => Err(
LuaConversionError::FromLua("cannot convert lua value to error".to_owned()).into(),
),
}
}
}
impl<'lua> ToLua<'lua> for LuaError {
fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
Ok(LuaValue::Error(lua.create_error(self)?))
}
}
impl<'lua> ToLua<'lua> for bool {
fn to_lua(self, _: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
Ok(LuaValue::Boolean(self))

View file

@ -38,10 +38,14 @@ pub enum LuaValue<'lua> {
Table(LuaTable<'lua>),
/// Reference to a Lua function (or closure).
Function(LuaFunction<'lua>),
/// Reference to a "full" userdata object.
UserData(LuaUserData<'lua>),
/// Reference to a Lua thread (or coroutine).
Thread(LuaThread<'lua>),
/// Reference to a userdata object that holds a custom type which implements
/// `LuaUserDataType`. Special builtin userdata types will be represented as
/// other `LuaValue` variants.
UserData(LuaUserData<'lua>),
/// `LuaError` is a special builtin userdata type.
Error(LuaErrorUserData<'lua>),
}
pub use self::LuaValue::Nil as LuaNil;
@ -122,15 +126,6 @@ pub trait FromLuaMulti<'a>: Sized {
fn from_lua_multi(values: LuaMultiValue<'a>, lua: &'a Lua) -> LuaResult<Self>;
}
impl<'lua> ToLua<'lua> for LuaError {
fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
unsafe {
push_wrapped_error(lua.state, self);
Ok(lua.pop_value(lua.state))
}
}
}
type LuaCallback = Box<
for<'lua> FnMut(&'lua Lua, LuaMultiValue<'lua>)
-> LuaResult<LuaMultiValue<'lua>>,
@ -889,16 +884,20 @@ pub trait LuaUserDataType: 'static + Sized {
fn add_methods(_methods: &mut LuaUserDataMethods<Self>) {}
}
/// Handle to an internal instance of custom userdata. All userdata in this API
/// is based around `RefCell`, to best match the mutable semantics of the Lua
/// language.
/// Handle to an internal Lua userdata for a type that implements
/// LuaUserDataType. Internally, instances are stored in a `RefCell`, to best
/// match the mutable semantics of the Lua language.
#[derive(Clone, Debug)]
pub struct LuaUserData<'lua>(LuaRef<'lua>);
impl<'lua> LuaUserData<'lua> {
/// Checks whether `T` is the type of this userdata.
pub fn is<T: LuaUserDataType>(&self) -> bool {
self.inspect(|_: &RefCell<T>| Ok(())).is_ok()
pub fn is<T: LuaUserDataType>(&self) -> LuaResult<bool> {
match self.inspect(|_: &RefCell<T>| Ok(())) {
Ok(_) => Ok(true),
Err(LuaError::UserDataError(LuaUserDataError::TypeMismatch)) => Ok(false),
Err(err) => Err(err),
}
}
/// Borrow this userdata out of the internal RefCell that is held in lua.
@ -910,7 +909,8 @@ impl<'lua> LuaUserData<'lua> {
})
}
/// Borrow mutably this userdata out of the internal RefCell that is held in lua.
/// Borrow mutably this userdata out of the internal RefCell that is held in
/// lua.
pub fn borrow_mut<T: LuaUserDataType>(&self) -> LuaResult<RefMut<T>> {
self.inspect(|cell| {
Ok(cell.try_borrow_mut().map_err(
@ -955,6 +955,27 @@ impl<'lua> LuaUserData<'lua> {
}
}
/// Handle to a `LuaError` that is held internally in Lua
#[derive(Clone, Debug)]
pub struct LuaErrorUserData<'lua>(LuaRef<'lua>);
impl<'lua> LuaErrorUserData<'lua> {
/// Gets a reference to the internally held `LuaError`.
pub fn get<T: LuaUserDataType>(&self) -> LuaResult<&LuaError> {
unsafe {
let lua = self.0.lua;
stack_guard(lua.state, 0, move || {
check_stack(lua.state, 1)?;
lua.push_ref(lua.state, &self.0);
let userdata = ffi::lua_touserdata(lua.state, -1);
let err = &*(userdata as *const WrappedError);
Ok(&err.0)
})
}
}
}
/// Top level Lua struct which holds the Lua state itself.
pub struct Lua {
state: *mut ffi::lua_State,
@ -1236,6 +1257,14 @@ impl Lua {
}
}
/// Create a userdata object from a LuaError
pub fn create_error(&self, err: LuaError) -> LuaResult<LuaErrorUserData> {
unsafe {
push_wrapped_error(self.state, err);
Ok(LuaErrorUserData(self.pop_ref(self.state)))
}
}
/// Returns a handle to the global environment.
pub fn globals(&self) -> LuaResult<LuaTable> {
unsafe {
@ -1436,12 +1465,16 @@ impl Lua {
self.push_ref(state, &f.0);
}
LuaValue::Thread(t) => {
self.push_ref(state, &t.0);
}
LuaValue::UserData(ud) => {
self.push_ref(state, &ud.0);
}
LuaValue::Thread(t) => {
self.push_ref(state, &t.0);
LuaValue::Error(e) => {
self.push_ref(state, &e.0);
}
}
}
@ -1483,7 +1516,17 @@ impl Lua {
ffi::LUA_TFUNCTION => LuaValue::Function(LuaFunction(self.pop_ref(state))),
ffi::LUA_TUSERDATA => LuaValue::UserData(LuaUserData(self.pop_ref(state))),
ffi::LUA_TUSERDATA => {
// It should not be possible to interact with userdata types
// other than custom LuaUserDataType types OR a WrappedError.
// WrappedPanic should never be able to be caught in lua, so it
// should never be here.
if is_wrapped_error(state, -1) {
LuaValue::Error(LuaErrorUserData(self.pop_ref(state)))
} else {
LuaValue::UserData(LuaUserData(self.pop_ref(state)))
}
}
ffi::LUA_TTHREAD => LuaValue::Thread(LuaThread(self.pop_ref(state))),

View file

@ -244,10 +244,10 @@ fn test_user_data() {
let userdata1 = lua.create_userdata(UserData1(1)).unwrap();
let userdata2 = lua.create_userdata(UserData2(Box::new(2))).unwrap();
assert!(userdata1.is::<UserData1>());
assert!(!userdata1.is::<UserData2>());
assert!(userdata2.is::<UserData2>());
assert!(!userdata2.is::<UserData1>());
assert!(userdata1.is::<UserData1>().unwrap());
assert!(!userdata1.is::<UserData2>().unwrap());
assert!(userdata2.is::<UserData2>().unwrap());
assert!(!userdata2.is::<UserData1>().unwrap());
assert_eq!(userdata1.borrow::<UserData1>().unwrap().0, 1);
assert_eq!(*userdata2.borrow::<UserData2>().unwrap().0, 2);

View file

@ -121,8 +121,8 @@ pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult<
if err == ffi::LUA_OK || err == ffi::LUA_YIELD {
Ok(())
} else {
if is_wrapped_error(state, -1) {
Err(pop_wrapped_error(state).unwrap())
if let Some(err) = pop_wrapped_error(state) {
Err(err)
} else if is_wrapped_panic(state, -1) {
let userdata = ffi::lua_touserdata(state, -1);
@ -200,7 +200,7 @@ pub unsafe extern "C" fn destructor<T>(state: *mut ffi::lua_State) -> c_int {
// In the context of a lua callback, this will call the given function and if the given function
// returns an error, *or if the given function panics*, this will result in a call to lua_error (a
// longjmp). The error or panic is wrapped in such a way that when calling pop_error back on
// longjmp). The error or panic is wrapped in such a way that when calling handle_error back on
// the rust side, it will resume the panic.
pub unsafe fn callback_error<R, F>(state: *mut ffi::lua_State, f: F) -> R
where
@ -228,8 +228,7 @@ pub unsafe fn pcall_with_traceback(
nresults: c_int,
) -> c_int {
unsafe extern "C" fn message_handler(state: *mut ffi::lua_State) -> c_int {
if is_wrapped_error(state, 1) {
let error = pop_wrapped_error(state).unwrap();
if let Some(error) = pop_wrapped_error(state) {
ffi::luaL_traceback(state, state, ptr::null(), 0);
let traceback = CStr::from_ptr(ffi::lua_tolstring(state, -1, ptr::null_mut()))
.to_str()
@ -262,8 +261,7 @@ pub unsafe fn resume_with_traceback(
) -> c_int {
let res = ffi::lua_resume(state, from, nargs);
if res != ffi::LUA_OK && res != ffi::LUA_YIELD {
if is_wrapped_error(state, 1) {
let error = pop_wrapped_error(state).unwrap();
if let Some(error) = pop_wrapped_error(state) {
ffi::luaL_traceback(from, state, ptr::null(), 0);
let traceback = CStr::from_ptr(ffi::lua_tolstring(from, -1, ptr::null_mut()))
.to_str()