Compare commits

..

7 Commits

Author SHA1 Message Date
Alex Orlenko 0f32e9cb43 0.3.1 release 2020-04-20 01:55:57 +01:00
Alex Orlenko 0efa0fcb6a Update documentation 2020-04-20 01:52:01 +01:00
Alex Orlenko 4e19ae6ccf Update tests (async and table) 2020-04-20 01:14:34 +01:00
Alex Orlenko c826798a6d Minor refactor 2020-04-19 16:51:35 +01:00
Alex Orlenko ee08050c1f Add TableExt trait with call_method/function methods 2020-04-19 16:15:16 +01:00
Alex Orlenko d8897d867b Update examples 2020-04-19 01:23:42 +01:00
Alex Orlenko 222f4df668 Add family of call_async function
Update documentation
Move async tests to a separate file
2020-04-18 21:26:12 +01:00
14 changed files with 721 additions and 261 deletions
+2 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.3.0"
version = "0.3.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
@@ -53,10 +53,9 @@ luajit-src = { version = "210.0.0", optional = true }
rustyline = "6.0"
criterion = "0.3"
trybuild = "1.0"
futures = "0.3.4"
hyper = "0.13"
tokio = { version = "0.2.18", features = ["full"] }
futures-executor = "0.3.4"
futures-util = "0.3.4"
futures-timer = "3.0"
[[bench]]
+1 -1
View File
@@ -18,7 +18,7 @@ modules in Rust.
## Usage
### Async
### Async/await support
Starting from 0.3, mlua supports async/await for all Lua versions. This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `async` feature in `Cargo.toml`.
+45 -17
View File
@@ -1,8 +1,34 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use hyper::Client as HyperClient;
use bstr::BString;
use hyper::{body::Body as HyperBody, Client as HyperClient};
use tokio::stream::StreamExt;
use mlua::{Error, Lua, Result, Thread};
use mlua::{Error, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct BodyReader(Rc<RefCell<HyperBody>>);
impl BodyReader {
fn new(body: HyperBody) -> Self {
BodyReader(Rc::new(RefCell::new(body)))
}
}
impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("read", |_, reader, ()| async move {
let mut reader = reader.0.borrow_mut();
let bytes = reader.try_next().await.map_err(Error::external)?;
if let Some(bytes) = bytes {
return Ok(Some(BString::from(bytes.as_ref())));
}
Ok(None)
});
}
}
#[tokio::main]
async fn main() -> Result<()> {
@@ -23,10 +49,9 @@ async fn main() -> Result<()> {
.or_insert(Vec::new())
.push(value.to_str().unwrap());
}
lua_resp.set("headers", headers)?;
let buf = hyper::body::to_bytes(resp).await.map_err(Error::external)?;
lua_resp.set("body", String::from_utf8_lossy(&buf).into_owned())?;
lua_resp.set("headers", headers)?;
lua_resp.set("body", BodyReader::new(resp.into_body()))?;
Ok(lua_resp)
})?;
@@ -34,22 +59,25 @@ async fn main() -> Result<()> {
let globals = lua.globals();
globals.set("fetch_url", fetch_url)?;
let thread = lua
let f = lua
.load(
r#"
coroutine.create(function ()
local res = fetch_url("http://httpbin.org/ip");
print(res.status)
for key, vals in pairs(res.headers) do
for _, val in ipairs(vals) do
print(key..": "..val)
end
local res = fetch_url(...);
print(res.status)
for key, vals in pairs(res.headers) do
for _, val in ipairs(vals) do
print(key..": "..val)
end
print(res.body)
end)
end
repeat
local body = res.body:read()
if body then
print(body)
end
until not body
"#,
)
.eval::<Thread>()?;
.into_function()?;
thread.into_async(()).await
f.call_async("http://httpbin.org/ip").await
}
+55 -51
View File
@@ -1,30 +1,37 @@
use std::cell::RefCell;
use std::net::Shutdown;
use std::rc::Rc;
use bstr::BString;
use tokio::net::{TcpListener, TcpStream};
use tokio::prelude::*;
use tokio::sync::Mutex;
use tokio::task;
use mlua::{Function, Lua, Result, Thread, UserData, UserDataMethods};
use mlua::{Function, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct LuaTcpListener(Option<Rc<Mutex<TcpListener>>>);
struct LuaTcp;
#[derive(Clone)]
struct LuaTcpStream(Rc<Mutex<TcpStream>>);
struct LuaTcpListener(Rc<RefCell<TcpListener>>);
#[derive(Clone)]
struct LuaTcpStream(Rc<RefCell<TcpStream>>);
impl UserData for LuaTcp {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_function("bind", |_, addr: String| async move {
let listener = TcpListener::bind(addr).await?;
Ok(LuaTcpListener(Rc::new(RefCell::new(listener))))
});
}
}
impl UserData for LuaTcpListener {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_function("bind", |_, addr: String| async {
let listener = TcpListener::bind(addr).await?;
Ok(LuaTcpListener(Some(Rc::new(Mutex::new(listener)))))
});
methods.add_async_method("accept", |_, listener, ()| async {
let (stream, _) = listener.0.unwrap().lock().await.accept().await?;
Ok(LuaTcpStream(Rc::new(Mutex::new(stream))))
methods.add_async_method("accept", |_, listener, ()| async move {
let (stream, _) = listener.0.borrow_mut().accept().await?;
Ok(LuaTcpStream(Rc::new(RefCell::new(stream))))
});
}
}
@@ -32,25 +39,23 @@ impl UserData for LuaTcpListener {
impl UserData for LuaTcpStream {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("peer_addr", |_, stream, ()| async move {
Ok(stream.0.lock().await.peer_addr()?.to_string())
Ok(stream.0.borrow().peer_addr()?.to_string())
});
methods.add_async_method("read", |_, stream, size: usize| async move {
let mut buf = vec![0; size];
let mut stream = stream.0.lock().await;
let n = stream.read(&mut buf).await?;
let n = stream.0.borrow_mut().read(&mut buf).await?;
buf.truncate(n);
Ok(BString::from(buf))
});
methods.add_async_method("write", |_, stream, data: BString| async move {
let mut stream = stream.0.lock().await;
let n = stream.write(&data).await?;
let n = stream.0.borrow_mut().write(&data).await?;
Ok(n)
});
methods.add_async_method("close", |_, stream, ()| async move {
stream.0.lock().await.shutdown(Shutdown::Both)?;
methods.add_method("close", |_, stream, ()| {
stream.0.borrow().shutdown(Shutdown::Both)?;
Ok(())
});
}
@@ -60,44 +65,43 @@ impl UserData for LuaTcpStream {
async fn main() -> Result<()> {
let lua = Lua::new();
let spawn = lua.create_function(move |_, func: Function| {
task::spawn_local(async move { func.call_async::<_, ()>(()).await.unwrap() });
Ok(())
})?;
let globals = lua.globals();
globals.set("tcp", LuaTcpListener(None))?;
globals.set("tcp", LuaTcp)?;
globals.set("spawn", spawn)?;
globals.set(
"spawn",
lua.create_function(move |lua: &Lua, func: Function| {
let fut = lua.create_thread(func)?.into_async::<_, ()>(());
task::spawn_local(async move { fut.await.unwrap() });
Ok(())
})?,
)?;
let thread = lua
let server = lua
.load(
r#"
coroutine.create(function ()
local listener = tcp.bind("0.0.0.0:1234")
print("listening on 0.0.0.0:1234")
while true do
local stream = listener:accept()
print("connected from " .. stream:peer_addr())
spawn(function()
while true do
local data = stream:read(100)
data = data:match("^%s*(.-)%s*$") -- trim
print(data)
stream:write("got: "..data.."\n")
if data == "exit" then
stream:close()
break
end
local addr = ...
local listener = tcp.bind(addr)
print("listening on "..addr)
while true do
local stream = listener:accept()
local peer_addr = stream:peer_addr()
print("connected from "..peer_addr)
spawn(function()
while true do
local data = stream:read(100)
data = data:match("^%s*(.-)%s*$") -- trim
print("["..peer_addr.."] "..data)
stream:write("got: "..data.."\n")
if data == "exit" then
stream:close()
break
end
end)
end
end)
end
end)
end
"#,
)
.eval::<Thread>()?;
.into_function()?;
thread.into_async(()).await
task::LocalSet::new()
.run_until(server.call_async::<_, ()>("0.0.0.0:1234"))
.await
}
+44
View File
@@ -9,6 +9,9 @@ use crate::util::{
};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
/// Handle to an internal Lua function.
#[derive(Clone, Debug)]
pub struct Function<'lua>(pub(crate) LuaRef<'lua>);
@@ -86,6 +89,47 @@ impl<'lua> Function<'lua> {
R::from_lua_multi(results, lua)
}
/// Returns a Feature that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// Internaly it wraps the function to an [`AsyncThread`].
///
/// # Examples
///
/// ```
/// use std::time::Duration;
/// use futures_timer::Delay;
/// # use mlua::{Lua, Result};
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// # let lua = Lua::new();
///
/// let sleep = lua.create_async_function(move |_lua, n: u64| async move {
/// Delay::new(Duration::from_millis(n)).await;
/// Ok(())
/// })?;
///
/// sleep.call_async(10).await?;
///
/// # Ok(())
/// # }
/// ```
///
/// [`AsyncThread`]: struct.AsyncThread.html
#[cfg(feature = "async")]
pub fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let lua = self.0.lua;
match lua.create_thread(self.clone()) {
Ok(t) => Box::pin(t.into_async(args)),
Err(e) => Box::pin(future::err(e)),
}
}
/// Returns a function that, when called, calls `self`, passing `args` as the first set of
/// arguments.
///
+11 -1
View File
@@ -24,6 +24,12 @@
//! The [`UserData`] trait can be implemented by user-defined types to make them available to Lua.
//! Methods and operators to be used from Lua can be added using the [`UserDataMethods`] API.
//!
//! # Async/await support
//!
//! The [`create_async_function`] allows creating non-blocking functions that returns [`Future`].
//! Lua code with async capabilities can be executed by [`call_async`] family of functions or polling
//! [`AsyncThread`] using any runtime (eg. Tokio).
//!
//! [Lua programming language]: https://www.lua.org/
//! [`Lua`]: struct.Lua.html
//! [executing]: struct.Lua.html#method.exec
@@ -35,6 +41,10 @@
//! [`FromLuaMulti`]: trait.FromLuaMulti.html
//! [`UserData`]: trait.UserData.html
//! [`UserDataMethods`]: trait.UserDataMethods.html
//! [`create_async_function`]: struct.Lua.html#method.create_async_function
//! [`call_async`]: struct.Function.html#method.call_async
//! [`AsyncThread`]: struct.AsyncThread.html
//! [`Future`]: ../futures_core/future/trait.Future.html
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
@@ -66,7 +76,7 @@ pub use crate::lua::{Chunk, Lua};
pub use crate::multi::Variadic;
pub use crate::stdlib::StdLib;
pub use crate::string::String;
pub use crate::table::{Table, TablePairs, TableSequence};
pub use crate::table::{Table, TableExt, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::types::{Integer, LightUserData, Number, RegistryKey};
pub use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
+94 -44
View File
@@ -27,13 +27,12 @@ use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Va
#[cfg(feature = "async")]
use {
crate::types::AsyncCallback,
futures_core::future::LocalBoxFuture,
futures_task::noop_waker,
futures_util::future::{self, FutureExt, TryFutureExt},
std::{
future::Future,
futures_core::{
future::{Future, LocalBoxFuture},
task::{Context, Poll, Waker},
},
futures_task::noop_waker,
futures_util::future::{self, TryFutureExt},
};
/// Top level Lua struct which holds the Lua state itself.
@@ -482,39 +481,37 @@ impl Lua {
/// Wraps a Rust async function or closure, creating a callable Lua function handle to it.
///
/// While executing the function Rust will poll Future and if the result is not ready, call
/// `lua_yield()` returning internal representation of a `Poll::Pending` value.
/// `yield()` passing internal representation of a `Poll::Pending` value.
///
/// The function must be called inside [`Thread`] coroutine to be able to suspend its execution.
/// An executor could be used together with [`ThreadStream`] and mlua will use a provided Waker
/// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its execution.
/// An executor should be used to poll [`AsyncThread`] and mlua will take a provided Waker
/// in that case. Otherwise noop waker will be used if try to call the function outside of Rust
/// executors.
///
/// The family of `call_async()` functions takes care about creating [`Thread`].
///
/// # Examples
///
/// Non blocking sleep:
///
/// ```
/// use std::time::Duration;
/// use futures_executor::block_on;
/// use futures_timer::Delay;
/// # use mlua::{Lua, Result, Thread};
/// use mlua::{Lua, Result};
///
/// async fn sleep(_lua: &Lua, n: u64) -> Result<&'static str> {
/// Delay::new(Duration::from_secs(n)).await;
/// Delay::new(Duration::from_millis(n)).await;
/// Ok("done")
/// }
///
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// lua.globals().set("async_sleep", lua.create_async_function(sleep)?)?;
/// let thr = lua.load("coroutine.create(function(n) return async_sleep(n) end)").eval::<Thread>()?;
/// let res: String = block_on(async {
/// thr.into_async(1).await // Sleep 1 second
/// })?;
///
/// assert_eq!(res, "done");
/// # Ok(())
/// # }
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("sleep", lua.create_async_function(sleep)?)?;
/// let res: String = lua.load("return sleep(...)").call_async(100).await?; // Sleep 100ms
/// assert_eq!(res, "done");
/// Ok(())
/// }
/// ```
///
/// [`Thread`]: struct.Thread.html
@@ -532,12 +529,10 @@ impl Lua {
{
self.create_async_callback(Box::new(move |lua, args| {
let args = match A::from_lua_multi(args, lua) {
Ok(x) => x,
Err(e) => return future::err(e).boxed_local(),
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
func(lua, args)
.and_then(move |x| future::ready(x.to_lua_multi(lua)))
.boxed_local()
Box::pin(func(lua, args).and_then(move |ret| future::ready(ret.to_lua_multi(lua))))
}))
}
@@ -1347,6 +1342,19 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
Ok(())
}
/// Asynchronously execute this chunk of code.
///
/// See [`Chunk::exec`] for more details.
///
/// [`Chunk::exec`]: struct.Chunk.html#method.exec
#[cfg(feature = "async")]
pub fn exec_async<'fut>(self) -> LocalBoxFuture<'fut, Result<()>>
where
'lua: 'fut,
{
self.call_async(())
}
/// Evaluate the chunk as either an expression or block.
///
/// If the chunk can be parsed as an expression, this loads and executes the chunk and returns
@@ -1356,18 +1364,39 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
// First, try interpreting the lua as an expression by adding
// "return", then as a statement. This is the same thing the
// actual lua repl does.
let mut expression_source = b"return ".to_vec();
expression_source.extend(self.source);
if let Ok(function) =
self.lua
.load_chunk(&expression_source, self.name.as_ref(), self.env.clone())
{
if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
) {
function.call(())
} else {
self.call(())
}
}
/// Asynchronously evaluate the chunk as either an expression or block.
///
/// See [`Chunk::eval`] for more details.
///
/// [`Chunk::eval`]: struct.Chunk.html#method.eval
#[cfg(feature = "async")]
pub fn eval_async<'fut, R>(self) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
R: FromLuaMulti<'lua> + 'fut,
{
if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
) {
function.call_async(())
} else {
self.call_async(())
}
}
/// Load the chunk function and call it with the given arguemnts.
///
/// This is equivalent to `into_function` and calling the resulting function.
@@ -1375,6 +1404,24 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
self.into_function()?.call(args)
}
/// Load the chunk function and asynchronously call it with the given arguemnts.
///
/// See [`Chunk::call`] for more details.
///
/// [`Chunk::call`]: struct.Chunk.html#method.call
#[cfg(feature = "async")]
pub fn call_async<'fut, A, R>(self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
match self.into_function() {
Ok(func) => func.call_async(args),
Err(e) => Box::pin(future::err(e)),
}
}
/// Load this chunk into a regular `Function`.
///
/// This simply compiles the chunk without actually executing it.
@@ -1382,6 +1429,13 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
self.lua
.load_chunk(self.source, self.name.as_ref(), self.env)
}
fn expression_source(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(b"return ".len() + self.source.len());
buf.extend(b"return ");
buf.extend(self.source);
buf
}
}
unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) {
@@ -1685,7 +1739,7 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
MR: 'static + Future<Output = Result<R>>,
{
Box::new(move |lua, mut args| {
let fut = || {
let fut_res = || {
if let Some(front) = args.pop_front() {
let userdata = AnyUserData::from_lua(front, lua)?;
let userdata = userdata.borrow::<T>()?.clone();
@@ -1698,11 +1752,9 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
})
}
};
match fut() {
Ok(f) => f
.and_then(move |fr| future::ready(fr.to_lua_multi(lua)))
.boxed_local(),
Err(e) => future::err(e).boxed_local(),
match fut_res() {
Ok(fut) => Box::pin(fut.and_then(move |ret| future::ready(ret.to_lua_multi(lua)))),
Err(e) => Box::pin(future::err(e)),
}
})
}
@@ -1741,12 +1793,10 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
{
Box::new(move |lua, args| {
let args = match A::from_lua_multi(args, lua) {
Ok(x) => x,
Err(e) => return future::err(e).boxed_local(),
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
function(lua, args)
.and_then(move |x| future::ready(x.to_lua_multi(lua)))
.boxed_local()
Box::pin(function(lua, args).and_then(move |ret| future::ready(ret.to_lua_multi(lua))))
})
}
}
+3 -3
View File
@@ -6,9 +6,9 @@ pub use crate::{
Function as LuaFunction, Integer as LuaInteger, LightUserData as LuaLightUserData, Lua,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
RegistryKey as LuaRegistryKey, Result as LuaResult, String as LuaString, Table as LuaTable,
TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread,
ThreadStatus as LuaThreadStatus, ToLua, ToLuaMulti, UserData as LuaUserData,
UserDataMethods as LuaUserDataMethods, Value as LuaValue,
TableExt as LuaTableExt, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
Thread as LuaThread, ThreadStatus as LuaThreadStatus, ToLua, ToLuaMulti,
UserData as LuaUserData, UserDataMethods as LuaUserDataMethods, Value as LuaValue,
};
#[cfg(feature = "async")]
+124 -21
View File
@@ -8,6 +8,9 @@ use crate::types::{Integer, LuaRef};
use crate::util::{assert_stack, protect_lua, protect_lua_closure, StackGuard};
use crate::value::{FromLua, FromLuaMulti, Nil, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
/// Handle to an internal Lua table.
#[derive(Clone, Debug)]
pub struct Table<'lua>(pub(crate) LuaRef<'lua>);
@@ -134,26 +137,15 @@ impl<'lua> Table<'lua> {
}
/// Gets the function associated to `key` from the table and executes it,
/// passing the table itself as the first argument.
/// passing the table itself along with `args` as function arguments.
///
/// # Examples
///
/// Execute the table method with name "concat":
///
/// ```
/// # use mlua::{Lua, Result, Table};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// # let object = lua.create_table()?;
/// # let concat = lua.create_function(|_, (_, a, b): (Table, String, String)| Ok(a + &b))?;
/// # object.set("concat", concat)?;
/// // simiar to: object:concat("param1", "param2")
/// object.call("concat", ("param1", "param2"))?;
/// # Ok(())
/// # }
/// ```
/// This function is deprecated since 0.3.1 in favor of [`call_method`]
/// in the `TableExt` trait.
///
/// This might invoke the `__index` metamethod.
///
/// [`call_method`]: trait.TableExt.html#tymethod.call_method
#[deprecated(since = "0.3.1", note = "Please use `call_method` instead")]
pub fn call<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
@@ -262,7 +254,7 @@ impl<'lua> Table<'lua> {
V::from_lua(value, lua)
}
/// Inserts element value at position idx to the table, shifting up the elements from table[idx].
/// Inserts element value at position `idx` to the table, shifting up the elements from `table[idx]`.
/// The worst case complexity is O(n), where n is the table length.
pub fn raw_insert<V: ToLua<'lua>>(&self, idx: Integer, value: V) -> Result<()> {
let lua = self.0.lua;
@@ -292,11 +284,11 @@ impl<'lua> Table<'lua> {
/// Removes a key from the table.
///
/// If `key` is an integer, mlua shifts down the elements from table[key+1],
/// and erases element table[key]. The complexity is O(n) in worst case,
/// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
/// and erases element `table[key]`. The complexity is O(n) in worst case,
/// where n is the table length.
///
/// For othey key types this is equivalent to setting table[key] = nil.
/// For othey key types this is equivalent to setting `table[key] = nil`.
pub fn raw_remove<K: ToLua<'lua>>(&self, key: K) -> Result<()> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
@@ -494,6 +486,117 @@ impl<'lua> AsRef<Table<'lua>> for Table<'lua> {
}
}
/// An extension trait for `Table`s that provides a variety of convenient functionality.
pub trait TableExt<'lua> {
/// Gets the function associated to `key` from the table and executes it,
/// passing the table itself along with `args` as function arguments.
///
/// This is a shortcut for
/// `table.get::<_, Function>(key)?.call((table.clone(), arg1, ..., argN))`
///
/// This might invoke the `__index` metamethod.
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and executes it,
/// passing `args` as function arguments.
///
/// This is a shortcut for
/// `table.get::<_, Function>(key)?.call(args)`
///
/// This might invoke the `__index` metamethod.
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing the table itself along with `args` as function arguments and returning Future.
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing `args` as function arguments and returning Future.
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
fn call_async_function<'fut, K, A, R>(
&self,
key: K,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
}
impl<'lua> TableExt<'lua> for Table<'lua> {
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let lua = self.0.lua;
let mut args = args.to_lua_multi(lua)?;
args.push_front(Value::Table(self.clone()));
self.get::<_, Function>(key)?.call(args)
}
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.get::<_, Function>(key)?.call(args)
}
#[cfg(feature = "async")]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let lua = self.0.lua;
let mut args = match args.to_lua_multi(lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
args.push_front(Value::Table(self.clone()));
self.call_async_function(key, args)
}
#[cfg(feature = "async")]
fn call_async_function<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
match self.get::<_, Function>(key) {
Ok(func) => func.call_async(args),
Err(e) => Box::pin(future::err(e)),
}
}
}
/// An iterator over the pairs of a Lua table.
///
/// This struct is created by the [`Table::pairs`] method.
+15 -15
View File
@@ -45,7 +45,10 @@ pub enum ThreadStatus {
#[derive(Clone, Debug)]
pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
/// Thread (coroutine) representation as an async Future or Stream.
/// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
///
/// [`Future`]: ../futures_core/future/trait.Future.html
/// [`Stream`]: ../futures_core/stream/trait.Stream.html
#[cfg(feature = "async")]
#[derive(Debug)]
pub struct AsyncThread<'lua, R> {
@@ -183,13 +186,13 @@ impl<'lua> Thread<'lua> {
/// # Examples
///
/// ```
/// # use mlua::{Error, Lua, Result, Thread};
/// use futures_executor::block_on;
/// use futures_util::stream::TryStreamExt;
/// # fn main() -> Result<()> {
/// # use mlua::{Lua, Result, Thread};
/// use futures::stream::TryStreamExt;
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// # let lua = Lua::new();
/// let thread: Thread = lua.load(r#"
/// coroutine.create(function(sum)
/// coroutine.create(function (sum)
/// for i = 1,10 do
/// sum = sum + i
/// coroutine.yield(sum)
@@ -198,16 +201,13 @@ impl<'lua> Thread<'lua> {
/// end)
/// "#).eval()?;
///
/// let result = block_on(async {
/// let mut s = thread.into_async::<_, i64>(1);
/// let mut sum = 0;
/// while let Some(n) = s.try_next().await? {
/// sum += n;
/// }
/// Ok::<_, Error>(sum)
/// })?;
/// let mut stream = thread.into_async::<_, i64>(1);
/// let mut sum = 0;
/// while let Some(n) = stream.try_next().await? {
/// sum += n;
/// }
///
/// assert_eq!(result, 286);
/// assert_eq!(sum, 286);
///
/// # Ok(())
/// # }
+295
View File
@@ -0,0 +1,295 @@
#![cfg(feature = "async")]
use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration;
use futures_timer::Delay;
use futures_util::stream::TryStreamExt;
use mlua::{Error, Function, Lua, Result, Table, TableExt, UserData, UserDataMethods};
#[tokio::test]
async fn test_async_function() -> Result<()> {
let lua = Lua::new();
let f = lua
.create_async_function(|_lua, (a, b, c): (i64, i64, i64)| async move { Ok((a + b) * c) })?;
lua.globals().set("f", f)?;
let res: i64 = lua.load("f(1, 2, 3)").eval_async().await?;
assert_eq!(res, 9);
Ok(())
}
#[tokio::test]
async fn test_async_sleep() -> Result<()> {
let lua = Lua::new();
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
})?;
lua.globals().set("sleep", sleep)?;
let res: String = lua.load(r"return sleep(...)").call_async(100).await?;
assert_eq!(res, "elapsed:100ms");
Ok(())
}
#[tokio::test]
async fn test_async_call() -> Result<()> {
let lua = Lua::new();
let hello = lua.create_async_function(|_lua, name: String| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(format!("hello, {}!", name))
})?;
match hello.call::<_, ()>("alex") {
Err(Error::RuntimeError(_)) => {}
_ => panic!(
"non-async executing async function must fail on the yield stage with RuntimeError"
),
};
assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!");
// Executing non-async functions using async call is allowed
let sum = lua.create_function(|_lua, (a, b): (i64, i64)| return Ok(a + b))?;
assert_eq!(sum.call_async::<_, i64>((5, 1)).await?, 6);
Ok(())
}
#[tokio::test]
async fn test_async_bind_call() -> Result<()> {
let lua = Lua::new();
let sum = lua.create_async_function(|_lua, (a, b): (i64, i64)| async move { Ok(a + b) })?;
let plus_10 = sum.bind(10)?;
lua.globals().set("plus_10", plus_10)?;
assert_eq!(lua.load("plus_10(-1)").eval_async::<i64>().await?, 9);
assert_eq!(lua.load("plus_10(1)").eval_async::<i64>().await?, 11);
Ok(())
}
#[tokio::test]
async fn test_async_handle_yield() -> Result<()> {
let lua = Lua::new();
let sum = lua.create_async_function(|_lua, (a, b): (i64, i64)| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(a + b)
})?;
lua.globals().set("sleep_sum", sum)?;
let res: String = lua
.load(
r#"
sum = sleep_sum(6, 7)
assert(sum == 13)
coroutine.yield("in progress")
return "done"
"#,
)
.call_async(())
.await?;
assert_eq!(res, "done");
let min = lua
.load(
r#"
function (a, b)
coroutine.yield("ignore me")
if a < b then return a else return b end
end
"#,
)
.eval::<Function>()?;
assert_eq!(min.call_async::<_, i64>((-1, 1)).await?, -1);
Ok(())
}
#[tokio::test]
async fn test_async_return_async_closure() -> Result<()> {
let lua = Lua::new();
let f = lua.create_async_function(|lua, a: i64| async move {
Delay::new(Duration::from_millis(10)).await;
let g = lua.create_async_function(move |_, b: i64| async move {
Delay::new(Duration::from_millis(10)).await;
return Ok(a + b);
})?;
Ok(g)
})?;
lua.globals().set("f", f)?;
let res: i64 = lua
.load("local g = f(1); return g(2) + g(3)")
.call_async(())
.await?;
assert_eq!(res, 7);
Ok(())
}
#[tokio::test]
async fn test_async_thread_stream() -> Result<()> {
let lua = Lua::new();
let thread = lua.create_thread(
lua.load(
r#"
function (sum)
for i = 1,10 do
sum = sum + i
coroutine.yield(sum)
end
return sum
end
"#,
)
.eval()?,
)?;
let mut stream = thread.into_async::<_, i64>(1);
let mut sum = 0;
while let Some(n) = stream.try_next().await? {
sum += n;
}
assert_eq!(sum, 286);
Ok(())
}
#[tokio::test]
async fn test_async_thread() -> Result<()> {
let lua = Lua::new();
let cnt = Rc::new(10); // sleep 10ms
let cnt2 = cnt.clone();
let f = lua.create_async_function(move |_lua, ()| {
let cnt3 = cnt2.clone();
async move {
Delay::new(Duration::from_millis(*cnt3.as_ref())).await;
Ok("done")
}
})?;
let res: String = lua.create_thread(f)?.into_async(()).await?;
assert_eq!(res, "done");
assert_eq!(Rc::strong_count(&cnt), 2);
lua.gc_collect()?; // thread_s is non-resumable and subject to garbage collection
assert_eq!(Rc::strong_count(&cnt), 1);
Ok(())
}
#[tokio::test]
async fn test_async_table() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?;
table.set("val", 10)?;
let get_value = lua.create_async_function(|_, table: Table| async move {
Delay::new(Duration::from_millis(10)).await;
table.get::<_, i64>("val")
})?;
table.set("get_value", get_value)?;
let set_value = lua.create_async_function(|_, (table, n): (Table, i64)| async move {
Delay::new(Duration::from_millis(10)).await;
table.set("val", n)
})?;
table.set("set_value", set_value)?;
let sleep = lua.create_async_function(|_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
})?;
table.set("sleep", sleep)?;
assert_eq!(
table
.call_async_method::<_, _, i64>("get_value", ())
.await?,
10
);
table.call_async_method("set_value", 15).await?;
assert_eq!(
table
.call_async_method::<_, _, i64>("get_value", ())
.await?,
15
);
assert_eq!(
table
.call_async_function::<_, _, String>("sleep", 7)
.await?,
"elapsed:7ms"
);
Ok(())
}
#[tokio::test]
async fn test_async_userdata() -> Result<()> {
#[derive(Clone)]
struct MyUserData(Rc<Cell<i64>>);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(data.0.get())
});
methods.add_async_method("set_value", |_, data, n| async move {
Delay::new(Duration::from_millis(10)).await;
data.0.set(n);
Ok(())
});
methods.add_async_function("sleep", |_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
});
}
}
let lua = Lua::new();
let globals = lua.globals();
let userdata = lua.create_userdata(MyUserData(Rc::new(Cell::new(11))))?;
globals.set("userdata", userdata.clone())?;
lua.load(
r#"
assert(userdata:get_value() == 11)
userdata:set_value(12)
assert(userdata:get_value() == 12)
assert(userdata.sleep(5) == "elapsed:5ms")
"#,
)
.exec_async()
.await?;
Ok(())
}
+1 -38
View File
@@ -1,10 +1,4 @@
#![allow(unused_imports)]
use std::{string::String as StdString, time::Duration};
use futures_executor::block_on;
use mlua::{Error, Function, Lua, Result, String, Thread};
use mlua::{Function, Lua, Result, String};
#[test]
fn test_function() -> Result<()> {
@@ -81,34 +75,3 @@ fn test_rust_function() -> Result<()> {
Ok(())
}
#[cfg(feature = "async")]
#[tokio::test]
async fn test_async_function() -> Result<()> {
let lua = Lua::new();
let f = lua.create_async_function(move |_lua, n: u64| async move {
futures_timer::Delay::new(Duration::from_secs(n)).await;
Ok("hello")
})?;
lua.globals().set("rust_async_sleep", f)?;
let thread = lua
.load(
r#"
coroutine.create(function ()
ret = rust_async_sleep(1)
assert(ret == "hello")
coroutine.yield()
return "world"
end)
"#,
)
.eval::<Thread>()?;
let fut = thread.into_async(());
let ret: StdString = fut.await?;
assert_eq!(ret, "world");
Ok(())
}
+31 -1
View File
@@ -1,4 +1,4 @@
use mlua::{Lua, Nil, Result, Table, Value};
use mlua::{Lua, Nil, Result, Table, TableExt, Value};
#[test]
fn test_set_get() -> Result<()> {
@@ -226,3 +226,33 @@ fn test_table_error() -> Result<()> {
Ok(())
}
#[test]
fn test_table_call() -> Result<()> {
let lua = Lua::new();
lua.load(
r#"
table = {a = 1}
function table.func(key)
return "func_"..key
end
function table:method(key)
return "method_"..self[key]
end
"#,
)
.exec()?;
let table: Table = lua.globals().get("table")?;
assert_eq!(table.call_function::<_, _, String>("func", "a")?, "func_a");
assert_eq!(
table.call_method::<_, _, String>("method", "a")?,
"method_1"
);
Ok(())
}
-66
View File
@@ -1,11 +1,4 @@
#![allow(unused_imports)]
use std::panic::catch_unwind;
use std::rc::Rc;
use std::time::Duration;
use futures_executor::block_on;
use futures_util::stream::TryStreamExt;
use mlua::{Error, Function, Lua, Result, Thread, ThreadStatus};
@@ -100,38 +93,6 @@ fn test_thread() -> Result<()> {
Ok(())
}
#[cfg(feature = "async")]
#[tokio::test]
async fn test_thread_stream() -> Result<()> {
let lua = Lua::new();
let thread = lua.create_thread(
lua.load(
r#"
function (s)
local sum = s
for i = 1,10 do
sum = sum + i
coroutine.yield(sum)
end
return sum
end
"#,
)
.eval()?,
)?;
let mut s = thread.into_async::<_, i64>(0);
let mut sum = 0;
while let Some(n) = s.try_next().await? {
sum += n;
}
assert_eq!(sum, 275);
Ok(())
}
#[test]
fn coroutine_from_closure() -> Result<()> {
let lua = Lua::new();
@@ -167,30 +128,3 @@ fn coroutine_panic() {
Err(p) => assert!(*p.downcast::<&str>().unwrap() == "test_panic"),
}
}
#[cfg(feature = "async")]
#[tokio::test]
async fn test_thread_async() -> Result<()> {
let lua = Lua::new();
let cnt = Rc::new(1); // sleep 1 second
let cnt2 = cnt.clone();
let f = lua.create_async_function(move |_lua, ()| {
let cnt3 = cnt2.clone();
async move {
futures_timer::Delay::new(Duration::from_secs(*cnt3.as_ref())).await;
Ok("hello")
}
})?;
let mut thread_s = lua.create_thread(f)?.into_async(());
let val: String = thread_s.try_next().await?.unwrap_or_default();
// thread_s is non-resumable and subject to garbage collection
lua.gc_collect()?;
assert_eq!(Rc::strong_count(&cnt), 1);
assert_eq!(val, "hello");
Ok(())
}