diff --git a/Cargo.toml b/Cargo.toml index 25aabad..6a26f75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ path = "src/lib.rs" actix = "0.7" futures = "0.1" tokio = "0.1" -rlua = "0.15" +rlua = "0.16" uuid = { version = "0.6", features = ["v4"] } regex = "1" diff --git a/src/actor.rs b/src/actor.rs index a92aa33..e284a6e 100644 --- a/src/actor.rs +++ b/src/actor.rs @@ -55,31 +55,34 @@ impl LuaActor { stopped: Option, ) -> Result { let prelude = include_str!("lua/prelude.lua"); - vm.eval::<_, ()>(prelude, Some("Prelude"))?; - { - let load: Function = vm.globals().get("__load")?; - if let Some(script) = started { - let res = load.call::<(String, String), ()>((script, "started".to_string())); - - if let Err(e) = res { - return Result::Err(e); + vm.context(|ctx| { + ctx.load(prelude).set_name("Prelude")?.exec()?; + { + let load: Function = ctx.globals().get("__load")?; + if let Some(script) = started { + let res = load.call::<(String, String), ()>((script, "started".to_string())); + + if let Err(e) = res { + return Result::Err(e); + } } - } - if let Some(script) = handle { - let res = load.call::<(String, String), ()>((script, "handle".to_string())); + if let Some(script) = handle { + let res = load.call::<(String, String), ()>((script, "handle".to_string())); - if let Err(e) = res { - return Result::Err(e); + if let Err(e) = res { + return Result::Err(e); + } } - } - if let Some(script) = stopped { - let res = load.call::<(String, String), ()>((script, "stopped".to_string())); + if let Some(script) = stopped { + let res = load.call::<(String, String), ()>((script, "stopped".to_string())); - if let Err(e) = res { - return Result::Err(e); + if let Err(e) = res { + return Result::Err(e); + } } } - } + Ok(()) + })?; Result::Ok(LuaActor { vm, @@ -122,89 +125,91 @@ fn invoke( let ctx = RefCell::new(ctx); let recs = RefCell::new(recs); - let iter = args - .into_iter() - .map(|msg| msg.to_lua(&vm).unwrap()) - .collect(); - let args = MultiValue::from_vec(iter); - // We can't create a function with references to `self` and is 'static since `self` already owns Lua. - // A function within Lua owning `self` creates self-borrowing cycle. - // - // Also, Lua requires all values passed to it is 'static because we can't know when will Lua GC our value. - // Therefore, we use scope to make sure these APIs are temporary and don't have to deal with 'static lifetime. - // - // (Quote from: https://github.com/kyren/rlua/issues/56#issuecomment-363928738 - // When the scope ends, the Lua function is 100% guaranteed (afaict!) to be "invalidated". - // This means that calling the function will cause an immediate Lua error with a message like "error, call of invalidated function".) - // - // for reference, check https://github.com/kyren/rlua/issues/73#issuecomment-370222198 - vm.scope(|scope| { - let globals = vm.globals(); - - let notify = scope.create_function_mut(|_, msg: LuaMessage| { - let mut ctx = ctx.borrow_mut(); - ctx.notify(msg); - Ok(()) - })?; - globals.set("notify", notify)?; - - let notify_later = scope.create_function_mut(|_, (msg, secs): (LuaMessage, u64)| { - let mut ctx = ctx.borrow_mut(); - ctx.notify_later(msg, Duration::new(secs, 0)); - Ok(()) - })?; - globals.set("notify_later", notify_later)?; - - let do_send = - scope.create_function_mut(|_, (recipient_name, msg): (String, LuaMessage)| { - let recs = recs.borrow_mut(); - let rec = recs.get(&recipient_name); - - // TODO: error handling? - if let Some(r) = rec { - r.do_send(msg).unwrap(); - } + vm.context(|lua_ctx| { + let iter = args + .into_iter() + .map(|msg| msg.to_lua(lua_ctx).unwrap()) + .collect(); + let args = MultiValue::from_vec(iter); + // We can't create a function with references to `self` and is 'static since `self` already owns Lua. + // A function within Lua owning `self` creates self-borrowing cycle. + // + // Also, Lua requires all values passed to it is 'static because we can't know when will Lua GC our value. + // Therefore, we use scope to make sure these APIs are temporary and don't have to deal with 'static lifetime. + // + // (Quote from: https://github.com/kyren/rlua/issues/56#issuecomment-363928738 + // When the scope ends, the Lua function is 100% guaranteed (afaict!) to be "invalidated". + // This means that calling the function will cause an immediate Lua error with a message like "error, call of invalidated function".) + // + // for reference, check https://github.com/kyren/rlua/issues/73#issuecomment-370222198 + lua_ctx.scope(|scope| { + let globals = lua_ctx.globals(); + + let notify = scope.create_function_mut(|_, msg: LuaMessage| { + let mut ctx = ctx.borrow_mut(); + ctx.notify(msg); Ok(()) })?; - globals.set("do_send", do_send)?; - - let send = scope.create_function_mut( - |_, (recipient_name, msg, cb_thread_id): (String, LuaMessage, i64)| { - // we can't create a lua function which owns `self` - // but `self` is needed for resolving `send` future. - // - // The workaround is we notify ourself with a `SendAttempt` Message - // and resolving `send` future in the `handle` function. - self_addr - .do_send(SendAttempt { - recipient_name, - msg, - cb_thread_id, - }) - .unwrap(); + globals.set("notify", notify)?; + let notify_later = scope.create_function_mut(|_, (msg, secs): (LuaMessage, u64)| { + let mut ctx = ctx.borrow_mut(); + ctx.notify_later(msg, Duration::new(secs, 0)); Ok(()) - }, - )?; - globals.set("send", send)?; + })?; + globals.set("notify_later", notify_later)?; - let terminate = scope.create_function_mut(|_, _: LuaMessage| { - let mut ctx = ctx.borrow_mut(); - ctx.terminate(); - Ok(()) - })?; - globals.set("terminate", terminate)?; + let do_send = + scope.create_function_mut(|_, (recipient_name, msg): (String, LuaMessage)| { + let recs = recs.borrow_mut(); + let rec = recs.get(&recipient_name); + + // TODO: error handling? + if let Some(r) = rec { + r.do_send(msg).unwrap(); + } + Ok(()) + })?; + globals.set("do_send", do_send)?; + + let send = scope.create_function_mut( + |_, (recipient_name, msg, cb_thread_id): (String, LuaMessage, i64)| { + // we can't create a lua function which owns `self` + // but `self` is needed for resolving `send` future. + // + // The workaround is we notify ourself with a `SendAttempt` Message + // and resolving `send` future in the `handle` function. + self_addr + .do_send(SendAttempt { + recipient_name, + msg, + cb_thread_id, + }) + .unwrap(); + + Ok(()) + }, + )?; + globals.set("send", send)?; + + let terminate = scope.create_function_mut(|_, _: LuaMessage| { + let mut ctx = ctx.borrow_mut(); + ctx.terminate(); + Ok(()) + })?; + globals.set("terminate", terminate)?; - let lua_handle: Result = globals.get(func_name); - if let Ok(f) = lua_handle { - match f.call::(args) { - Err(e) => panic!("{:?}", e), - Ok(ret) => Ok(LuaMessage::from_lua(ret, &vm).unwrap()), + let lua_handle: Result = globals.get(func_name); + if let Ok(f) = lua_handle { + match f.call::(args) { + Err(e) => panic!("{:?}", e), + Ok(ret) => Ok(LuaMessage::from_lua(ret, lua_ctx).unwrap()), + } + } else { + // return nil if handle is not defined + Ok(LuaMessage::Nil) } - } else { - // return nil if handle is not defined - Ok(LuaMessage::Nil) - } + }) }) } @@ -813,13 +818,15 @@ mod tests { let system = System::new("test"); let vm = Lua::new(); - vm.globals() - .set( - "greet", - vm.create_function(|_, name: String| Ok(format!("Hello, {}!", name))) - .unwrap(), - ) - .unwrap(); + vm.context(|ctx| { + ctx.globals() + .set( + "greet", + ctx.create_function(|_, name: String| Ok(format!("Hello, {}!", name))) + .unwrap(), + ) + .unwrap(); + }); let addr = LuaActorBuilder::new() .on_handle_with_lua( diff --git a/src/message.rs b/src/message.rs index a930471..9155e3c 100644 --- a/src/message.rs +++ b/src/message.rs @@ -2,7 +2,7 @@ use ::actix::dev::{MessageResponse, ResponseChannel}; use ::actix::prelude::*; use regex::Regex; use rlua::Result as LuaResult; -use rlua::{FromLua, Lua, ToLua, Value}; +use rlua::{Context, FromLua, ToLua, Value}; use std::collections::HashMap; @@ -101,23 +101,23 @@ lua_message_convert_float!(f32); lua_message_convert_float!(f64); impl<'lua> FromLua<'lua> for LuaMessage { - fn from_lua(v: Value, lua: &'lua Lua) -> LuaResult { + fn from_lua(v: Value<'lua>, ctx: Context<'lua>) -> LuaResult { match v { Value::String(x) => { let re = Regex::new(r"__suspended__(.+)").unwrap(); let s = Value::String(x); - if let Some(cap) = re.captures(&String::from_lua(s.clone(), lua)?) { + if let Some(cap) = re.captures(&String::from_lua(s.clone(), ctx)?) { let tid = cap.get(1).unwrap().as_str(); Ok(LuaMessage::ThreadYield(tid.to_string())) } else { - Ok(LuaMessage::String(String::from_lua(s.clone(), lua)?)) + Ok(LuaMessage::String(String::from_lua(s.clone(), ctx)?)) } } Value::Integer(n) => Ok(LuaMessage::Integer(n as i64)), Value::Number(n) => Ok(LuaMessage::Number(n as f64)), Value::Boolean(b) => Ok(LuaMessage::Boolean(b)), Value::Nil => Ok(LuaMessage::Nil), - Value::Table(t) => Ok(LuaMessage::Table(HashMap::from_lua(Value::Table(t), lua)?)), + Value::Table(t) => Ok(LuaMessage::Table(HashMap::from_lua(Value::Table(t), ctx)?)), Value::Error(err) => { panic!("Lua error: {:?}", err); } @@ -127,14 +127,14 @@ impl<'lua> FromLua<'lua> for LuaMessage { } impl<'lua> ToLua<'lua> for LuaMessage { - fn to_lua(self, lua: &'lua Lua) -> LuaResult> { + fn to_lua(self, ctx: Context<'lua>) -> LuaResult> { match self { - LuaMessage::String(x) => Ok(Value::String(lua.create_string(&x)?)), + LuaMessage::String(x) => Ok(Value::String(ctx.create_string(&x)?)), LuaMessage::Integer(x) => Ok(Value::Integer(x)), LuaMessage::Number(x) => Ok(Value::Number(x)), LuaMessage::Boolean(x) => Ok(Value::Boolean(x)), LuaMessage::Nil => Ok(Value::Nil), - LuaMessage::Table(x) => Ok(Value::Table(lua.create_table_from(x)?)), + LuaMessage::Table(x) => Ok(Value::Table(ctx.create_table_from(x)?)), // TODO: passing rust error to lua error? _ => unimplemented!(), @@ -145,6 +145,7 @@ impl<'lua> ToLua<'lua> for LuaMessage { #[cfg(test)] mod tests { use super::*; + use rlua::Lua; use std::mem::discriminant; #[test] @@ -169,71 +170,75 @@ mod tests { fn to_lua() { // we only check if they have the correct variant let lua = Lua::new(); - assert_eq!( - discriminant(&LuaMessage::Integer(42).to_lua(&lua).unwrap()), - discriminant(&Value::Integer(42)) - ); - assert_eq!( - discriminant(&LuaMessage::String("foo".to_string()).to_lua(&lua).unwrap()), - discriminant(&Value::String(lua.create_string("foo").unwrap())) - ); - assert_eq!( - discriminant(&LuaMessage::Number(42.5).to_lua(&lua).unwrap()), - discriminant(&Value::Number(42.5)) - ); - assert_eq!( - discriminant(&LuaMessage::Boolean(true).to_lua(&lua).unwrap()), - discriminant(&Value::Boolean(true)) - ); - assert_eq!( - discriminant(&LuaMessage::Nil.to_lua(&lua).unwrap()), - discriminant(&Value::Nil) - ); - - let mut t = HashMap::new(); - t.insert("bar".to_string(), LuaMessage::from("abc")); - assert_eq!( - discriminant(&LuaMessage::Table(t).to_lua(&lua).unwrap()), - discriminant(&Value::Table(lua.create_table().unwrap())) - ); + lua.context(|ctx| { + assert_eq!( + discriminant(&LuaMessage::Integer(42).to_lua(ctx).unwrap()), + discriminant(&Value::Integer(42)) + ); + assert_eq!( + discriminant(&LuaMessage::String("foo".to_string()).to_lua(ctx).unwrap()), + discriminant(&Value::String(ctx.create_string("foo").unwrap())) + ); + assert_eq!( + discriminant(&LuaMessage::Number(42.5).to_lua(ctx).unwrap()), + discriminant(&Value::Number(42.5)) + ); + assert_eq!( + discriminant(&LuaMessage::Boolean(true).to_lua(ctx).unwrap()), + discriminant(&Value::Boolean(true)) + ); + assert_eq!( + discriminant(&LuaMessage::Nil.to_lua(ctx).unwrap()), + discriminant(&Value::Nil) + ); + + let mut t = HashMap::new(); + t.insert("bar".to_string(), LuaMessage::from("abc")); + assert_eq!( + discriminant(&LuaMessage::Table(t).to_lua(ctx).unwrap()), + discriminant(&Value::Table(ctx.create_table().unwrap())) + ); + }) } #[test] fn from_lua() { // we only check if they have the correct variant let lua = Lua::new(); - assert_eq!( - discriminant(&LuaMessage::from_lua(Value::Integer(42), &lua).unwrap()), - discriminant(&LuaMessage::Integer(42)) - ); - assert_eq!( - discriminant(&LuaMessage::from_lua(Value::Number(42.5), &lua).unwrap()), - discriminant(&LuaMessage::Number(42.5)) - ); - assert_eq!( - discriminant( - &LuaMessage::from_lua(Value::String(lua.create_string("foo").unwrap()), &lua) - .unwrap() - ), - discriminant(&LuaMessage::String("foo".to_string())) - ); - assert_eq!( - discriminant(&LuaMessage::from_lua(Value::Boolean(true), &lua).unwrap()), - discriminant(&LuaMessage::Boolean(true)) - ); - assert_eq!( - discriminant(&LuaMessage::from_lua(Value::Nil, &lua).unwrap()), - discriminant(&LuaMessage::Nil) - ); - - let mut t = HashMap::new(); - t.insert("bar".to_string(), LuaMessage::from("abc")); - assert_eq!( - discriminant( - &LuaMessage::from_lua(Value::Table(lua.create_table().unwrap()), &lua).unwrap() - ), - discriminant(&LuaMessage::Table(t)) - ); + lua.context(|ctx| { + assert_eq!( + discriminant(&LuaMessage::from_lua(Value::Integer(42), ctx).unwrap()), + discriminant(&LuaMessage::Integer(42)) + ); + assert_eq!( + discriminant(&LuaMessage::from_lua(Value::Number(42.5), ctx).unwrap()), + discriminant(&LuaMessage::Number(42.5)) + ); + assert_eq!( + discriminant( + &LuaMessage::from_lua(Value::String(ctx.create_string("foo").unwrap()), ctx) + .unwrap() + ), + discriminant(&LuaMessage::String("foo".to_string())) + ); + assert_eq!( + discriminant(&LuaMessage::from_lua(Value::Boolean(true), ctx).unwrap()), + discriminant(&LuaMessage::Boolean(true)) + ); + assert_eq!( + discriminant(&LuaMessage::from_lua(Value::Nil, ctx).unwrap()), + discriminant(&LuaMessage::Nil) + ); + + let mut t = HashMap::new(); + t.insert("bar".to_string(), LuaMessage::from("abc")); + assert_eq!( + discriminant( + &LuaMessage::from_lua(Value::Table(ctx.create_table().unwrap()), ctx).unwrap() + ), + discriminant(&LuaMessage::Table(t)) + ); + }) } #[should_panic] @@ -242,6 +247,9 @@ mod tests { use rlua::Error; let lua = Lua::new(); - &LuaMessage::from_lua(Value::Error(Error::RuntimeError("foo".to_string())), &lua).unwrap(); + lua.context(|ctx| { + &LuaMessage::from_lua(Value::Error(Error::RuntimeError("foo".to_string())), ctx) + .unwrap(); + }) } }