Compare commits

..

13 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
Alex Orlenko afaa0eb639 0.3.0 release 2020-04-17 23:55:58 +01:00
Alex Orlenko db5ad6bc60 Include async build/test to github workflow 2020-04-17 23:55:58 +01:00
Alex Orlenko ef06c5eec9 Update Cargo.toml 2020-04-17 23:55:57 +01:00
Alex Orlenko f7dc9da107 Update README
Cargo fmt and minor changes
2020-04-17 22:52:34 +01:00
Alex Orlenko 47e8a80c1c v0.3.0-alpha.1 with async support
Squashed commit of the async branch.
2020-04-17 22:39:50 +01:00
Alex Orlenko 1a788c48f1 Cherry-pick changes from rlua:
- Make Value::type_name() public
- Update CallbackError and ExternalError Display impl
2020-04-15 21:23:00 +01:00
41 changed files with 1649 additions and 1380 deletions
+11 -2
View File
@@ -48,11 +48,11 @@ jobs:
if: matrix.thing == 'aarch64-linux'
shell: bash
- run: rustup target add ${{ matrix.target }}
- name: Build (Lua 5.3/5.2/5.1 and LuaJIT vendored)
- name: Build (Lua 5.3/5.2/5.1 and LuaJIT vendored+async)
run: |
for FEATURE in lua53 lua52 lua51 luajit; do
echo "Building $FEATURE"
cargo build --target ${{ matrix.target }} --release --no-default-features --features "$FEATURE vendored"
cargo build --target ${{ matrix.target }} --release --no-default-features --features "$FEATURE vendored async"
done
shell: bash
- name: Build (Lua 5.3/5.2/5.1 and LuaJIT via pkg-config)
@@ -76,15 +76,24 @@ jobs:
- name: Run tests (Lua 5.3 vendored)
run: cargo test --release --no-default-features --features "lua53 vendored"
shell: bash
- name: Run tests (Lua 5.3 vendored+async)
run: cargo test --release --no-default-features --features "lua53 vendored async"
shell: bash
- name: Run tests (Lua 5.2 vendored)
run: cargo test --release --no-default-features --features "lua52 vendored"
shell: bash
- name: Run tests (Lua 5.1 vendored)
run: cargo test --release --no-default-features --features "lua51 vendored"
shell: bash
- name: Run tests (Lua 5.1 vendored+async)
run: cargo test --release --no-default-features --features "lua51 vendored async"
shell: bash
- name: Run tests (LuaJIT vendored)
run: cargo test --release --no-default-features --features "luajit vendored"
shell: bash
- name: Run tests (LuaJIT vendored+async)
run: cargo test --release --no-default-features --features "luajit vendored async"
shell: bash
- name: Run compile test (Lua 5.3)
run: |
sudo apt-get update -y
+27 -8
View File
@@ -1,24 +1,27 @@
[package]
name = "mlua"
version = "0.2.2"
version = "0.3.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
documentation = "https://docs.rs/mlua"
readme = "README.md"
keywords = ["lua", "luajit"]
categories = ["api-bindings"]
keywords = ["lua", "luajit", "async", "futures"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
High level bindings to Lua 5.1/5.2/5.3 (including LuaJIT)
with support of writing native lua modules in Rust.
with async/await features and support of writing native lua modules in Rust.
"""
[badges]
# github-actions = { repository = "khvzak/mlua", workflow = "CI" }
[package.metadata.docs.rs]
features = ["async"]
[workspace]
members = [
"mlua_derive",
@@ -31,22 +34,38 @@ lua52 = []
lua51 = []
luajit = []
vendored = ["lua-src", "luajit-src"]
async = ["futures-core", "futures-task", "futures-util"]
[dependencies]
num-traits = { version = "0.2.6" }
bstr = { version = "0.2", features = ["std"], default_features = false }
num-traits = { version = "0.2.11" }
futures-core = { version = "0.3.4", optional = true }
futures-task = { version = "0.3.4", optional = true }
futures-util = { version = "0.3.4", optional = true }
[build-dependencies]
cc = { version = "1.0" }
pkg-config = { version = "0.3.11" }
pkg-config = { version = "0.3.17" }
lua-src = { version = "535.0.1", optional = true }
luajit-src = { version = "210.0.0", optional = true }
[dev-dependencies]
rustyline = "5.0"
criterion = "0.2.0"
rustyline = "6.0"
criterion = "0.3"
trybuild = "1.0"
futures = "0.3.4"
hyper = "0.13"
tokio = { version = "0.2.18", features = ["full"] }
futures-timer = "3.0"
[[bench]]
name = "benchmark"
harness = false
[[example]]
name = "async_tcp_server"
required-features = ["async"]
[[example]]
name = "async_http_client"
required-features = ["async"]
+11 -3
View File
@@ -18,6 +18,14 @@ modules in Rust.
## Usage
### 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`.
**Examples**:
- [HTTP Client](examples/async_http_client.rs)
- [TCP Server](examples/async_tcp_server.rs)
### Choosing Lua version
The following features could be used to choose Lua version: `lua53` (default), `lua52`, `lua51` and `luajit`.
@@ -41,7 +49,7 @@ Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = "0.2"
mlua = "0.3"
```
`main.rs`
@@ -73,8 +81,8 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = "0.2"
mlua_derive = "0.2"
mlua = "0.3"
mlua_derive = "0.3"
```
`lib.rs` :
+83
View File
@@ -0,0 +1,83 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use bstr::BString;
use hyper::{body::Body as HyperBody, Client as HyperClient};
use tokio::stream::StreamExt;
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<()> {
let lua = Lua::new();
let fetch_url = lua.create_async_function(|lua, uri: String| async move {
let client = HyperClient::new();
let uri = uri.parse().map_err(Error::external)?;
let resp = client.get(uri).await.map_err(Error::external)?;
let lua_resp = lua.create_table()?;
lua_resp.set("status", resp.status().as_u16())?;
let mut headers = HashMap::new();
for (key, value) in resp.headers().iter() {
headers
.entry(key.as_str())
.or_insert(Vec::new())
.push(value.to_str().unwrap());
}
lua_resp.set("headers", headers)?;
lua_resp.set("body", BodyReader::new(resp.into_body()))?;
Ok(lua_resp)
})?;
let globals = lua.globals();
globals.set("fetch_url", fetch_url)?;
let f = lua
.load(
r#"
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
end
repeat
local body = res.body:read()
if body then
print(body)
end
until not body
"#,
)
.into_function()?;
f.call_async("http://httpbin.org/ip").await
}
+107
View File
@@ -0,0 +1,107 @@
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::task;
use mlua::{Function, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct LuaTcp;
#[derive(Clone)]
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_method("accept", |_, listener, ()| async move {
let (stream, _) = listener.0.borrow_mut().accept().await?;
Ok(LuaTcpStream(Rc::new(RefCell::new(stream))))
});
}
}
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.borrow().peer_addr()?.to_string())
});
methods.add_async_method("read", |_, stream, size: usize| async move {
let mut buf = vec![0; size];
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 n = stream.0.borrow_mut().write(&data).await?;
Ok(n)
});
methods.add_method("close", |_, stream, ()| {
stream.0.borrow().shutdown(Shutdown::Both)?;
Ok(())
});
}
}
#[tokio::main]
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", LuaTcp)?;
globals.set("spawn", spawn)?;
let server = lua
.load(
r#"
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
"#,
)
.into_function()?;
task::LocalSet::new()
.run_until(server.call_async::<_, ()>("0.0.0.0:1234"))
.await
}
-28
View File
@@ -164,34 +164,6 @@ fn main() -> Result<()> {
< f32::EPSILON
);
// Normally, Rust types passed to `Lua` must be `Send`, because `Lua` itself is `Send`, and
// must be `'static`, because there is no way to be sure of their lifetime inside the Lua
// state. There is, however, a limited way to lift both of these requirements. You can
// call `Lua::scope` to create userdata and callbacks types that only live for as long
// as the call to scope, but do not have to be `Send` OR `'static`.
{
let mut rust_val = 0;
lua.scope(|scope| {
// We create a 'sketchy' Lua callback that holds a mutable reference to the variable
// `rust_val`. Outside of a `Lua::scope` call, this would not be allowed
// because it could be unsafe.
lua.globals().set(
"sketchy",
scope.create_function_mut(|_, ()| {
rust_val = 42;
Ok(())
})?,
)?;
lua.load("sketchy()").exec()
})?;
assert_eq!(rust_val, 42);
}
// We were able to run our 'sketchy' function inside the scope just fine. However, if we
// try to run our 'sketchy' function outside of the scope, the function we created will have
// been invalidated and we will generate an error. If our function wasn't invalidated, we
+2 -2
View File
@@ -1,7 +1,7 @@
[package]
name = "mlua_derive"
version = "0.2.0"
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
version = "0.3.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/khvzak/mlua"
+3 -3
View File
@@ -122,7 +122,7 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
}
}
impl<'lua, T: 'static + Send + UserData> ToLua<'lua> for T {
impl<'lua, T: 'static + UserData> ToLua<'lua> for T {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(lua.create_userdata(self)?))
}
@@ -167,7 +167,7 @@ impl<'lua> ToLua<'lua> for bool {
}
impl<'lua> FromLua<'lua> for bool {
fn from_lua(v: Value, _: &'lua Lua) -> Result<Self> {
fn from_lua(v: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match v {
Value::Nil => Ok(false),
Value::Boolean(b) => Ok(b),
@@ -183,7 +183,7 @@ impl<'lua> ToLua<'lua> for LightUserData {
}
impl<'lua> FromLua<'lua> for LightUserData {
fn from_lua(value: Value, _: &'lua Lua) -> Result<Self> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value {
Value::LightUserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
+30 -9
View File
@@ -1,8 +1,11 @@
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
use std::net::AddrParseError;
use std::rc::Rc;
use std::result::Result as StdResult;
use std::str::Utf8Error;
use std::string::String as StdString;
use std::sync::Arc;
/// Error type returned by `mlua` methods.
#[derive(Debug, Clone)]
@@ -115,7 +118,7 @@ pub enum Error {
/// Lua call stack backtrace.
traceback: StdString,
/// Original error returned by the Rust code.
cause: Arc<Error>,
cause: Rc<Error>,
},
/// A custom error.
///
@@ -124,7 +127,7 @@ pub enum Error {
/// Returning `Err(ExternalError(...))` from a Rust callback will raise the error as a Lua
/// error. The Rust code that originally invoked the Lua code then receives a `CallbackError`,
/// from which the original error (and a stack traceback) can be recovered.
ExternalError(Arc<dyn StdError + Send + Sync>),
ExternalError(Rc<dyn StdError>),
}
/// A specialized `Result` type used by `mlua`'s API.
@@ -184,10 +187,10 @@ impl fmt::Display for Error {
Error::MismatchedRegistryKey => {
write!(fmt, "RegistryKey used from different Lua state")
}
Error::CallbackError { ref traceback, ref cause } => {
write!(fmt, "callback error: {}: {}", cause, traceback)
Error::CallbackError { ref traceback, .. } => {
write!(fmt, "callback error: {}", traceback)
}
Error::ExternalError(ref err) => write!(fmt, "external error: {}", err),
Error::ExternalError(ref err) => write!(fmt, "{}", err),
}
}
}
@@ -196,14 +199,14 @@ impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
Error::CallbackError { ref cause, .. } => Some(cause.as_ref()),
Error::ExternalError(ref err) => Some(err.as_ref()),
Error::ExternalError(ref err) => err.source(),
_ => None,
}
}
}
impl Error {
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> Error {
pub fn external<T: Into<Box<dyn StdError>>>(err: T) -> Error {
Error::ExternalError(err.into().into())
}
}
@@ -214,7 +217,7 @@ pub trait ExternalError {
impl<E> ExternalError for E
where
E: Into<Box<dyn StdError + Send + Sync>>,
E: Into<Box<dyn StdError>>,
{
fn to_lua_err(self) -> Error {
Error::external(self)
@@ -233,3 +236,21 @@ where
self.map_err(|e| e.to_lua_err())
}
}
impl std::convert::From<AddrParseError> for Error {
fn from(err: AddrParseError) -> Self {
Error::external(err)
}
}
impl std::convert::From<IoError> for Error {
fn from(err: IoError) -> Self {
Error::external(err)
}
}
impl std::convert::From<Utf8Error> for Error {
fn from(err: Utf8Error) -> Self {
Error::external(err)
}
}
+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.
///
+14 -3
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.
@@ -49,7 +59,6 @@ mod ffi;
mod function;
mod lua;
mod multi;
mod scope;
mod stdlib;
mod string;
mod table;
@@ -65,13 +74,15 @@ pub use crate::error::{Error, ExternalError, ExternalResult, Result};
pub use crate::function::Function;
pub use crate::lua::{Chunk, Lua};
pub use crate::multi::Variadic;
pub use crate::scope::Scope;
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};
pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
pub use crate::thread::AsyncThread;
pub mod prelude;
+427 -143
View File
@@ -3,34 +3,43 @@ use std::cell::{RefCell, UnsafeCell};
use std::collections::HashMap;
use std::ffi::CString;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int, c_void};
use std::sync::{Arc, Mutex};
use std::os::raw::{c_char, c_int};
use std::rc::Rc;
use std::{mem, ptr, str};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::scope::Scope;
use crate::stdlib::StdLib;
use crate::string::String;
use crate::table::Table;
use crate::thread::Thread;
use crate::types::{Callback, Integer, LightUserData, LuaRef, Number, RegistryKey};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
#[cfg(any(feature = "lua51", feature = "luajit"))]
use crate::util::set_main_state;
use crate::util::{
assert_stack, callback_error, check_stack, get_main_state, get_userdata, get_wrapped_error,
init_error_registry, init_userdata_metatable, pop_error, protect_lua, protect_lua_closure,
push_string, push_userdata, push_wrapped_error, userdata_destructor, StackGuard,
assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state,
get_meta_gc_userdata, get_wrapped_error, init_error_registry, init_gc_metatable_for,
init_userdata_metatable, pop_error, protect_lua, protect_lua_closure, push_gc_userdata,
push_meta_gc_userdata, push_string, push_userdata, push_wrapped_error, StackGuard,
};
use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
use {
crate::types::AsyncCallback,
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.
pub struct Lua {
pub(crate) state: *mut ffi::lua_State,
main_state: *mut ffi::lua_State,
extra: Arc<RefCell<ExtraData>>,
extra: Rc<RefCell<ExtraData>>,
ephemeral: bool,
// Lua has lots of interior mutability, should not be RefUnwindSafe
_no_ref_unwind_safe: PhantomData<UnsafeCell<()>>,
@@ -39,7 +48,7 @@ pub struct Lua {
// Data associated with the lua_State.
struct ExtraData {
registered_userdata: HashMap<TypeId, c_int>,
registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
registry_unref_list: Rc<RefCell<Option<Vec<c_int>>>>,
ref_thread: *mut ffi::lua_State,
ref_stack_size: c_int,
@@ -47,7 +56,10 @@ struct ExtraData {
ref_free: Vec<c_int>,
}
unsafe impl Send for Lua {}
#[cfg(feature = "async")]
pub(crate) struct AsyncPollPending;
#[cfg(feature = "async")]
pub(crate) static WAKER_REGISTRY_KEY: u8 = 0;
impl Drop for Lua {
fn drop(&mut self) {
@@ -59,7 +71,10 @@ impl Drop for Lua {
&& extra.ref_stack_max as usize == extra.ref_free.len(),
"reference leak detected"
);
*mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned") = None;
*mlua_expect!(
extra.registry_unref_list.try_borrow_mut(),
"unref list borrowed"
) = None;
ffi::lua_close(self.state);
}
}
@@ -113,55 +128,25 @@ impl Lua {
/// Constructs a new Lua instance from the existing state.
pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua {
#[cfg(any(feature = "lua53", feature = "lua52"))]
let main_state = get_main_state(state);
#[cfg(any(feature = "lua51", feature = "luajit"))]
let main_state = {
set_main_state(state);
state
};
let main_state_top = ffi::lua_gettop(state);
let ref_thread = mlua_expect!(
protect_lua_closure(main_state, 0, 0, |state| {
init_error_registry(state);
// Create the function metatables and place them in the registry
// Create the internal metatables and place them in the registry
// to prevent them from being garbage collected.
ffi::lua_pushlightuserdata(
state,
&FUNCTION_CALLBACK_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__gc"));
ffi::lua_pushcfunction(state, userdata_destructor::<Callback>);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
ffi::lua_pushlightuserdata(
state,
&FUNCTION_EXTRA_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__gc"));
ffi::lua_pushcfunction(state, userdata_destructor::<Arc<RefCell<ExtraData>>>);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
init_gc_metatable_for::<Callback>(state, None);
init_gc_metatable_for::<Lua>(state, None);
#[cfg(feature = "async")]
{
init_gc_metatable_for::<AsyncCallback>(state, None);
init_gc_metatable_for::<LocalBoxFuture<Result<MultiValue>>>(state, None);
init_gc_metatable_for::<AsyncPollPending>(state, None);
init_gc_metatable_for::<Waker>(state, None);
}
// Create ref stack thread and place it in the registry to prevent it from being garbage
// collected.
@@ -175,9 +160,9 @@ impl Lua {
// Create ExtraData
let extra = Arc::new(RefCell::new(ExtraData {
let extra = Rc::new(RefCell::new(ExtraData {
registered_userdata: HashMap::new(),
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
registry_unref_list: Rc::new(RefCell::new(Some(Vec::new()))),
ref_thread,
// We need 1 extra stack space to move values in and out of the ref stack.
ref_stack_size: ffi::LUA_MINSTACK - 1,
@@ -206,7 +191,7 @@ impl Lua {
pub fn entrypoint1<'lua, 'callback, R, F>(&'lua self, func: F) -> Result<c_int>
where
R: ToLua<'callback>,
F: 'static + Send + Fn(&'callback Lua) -> Result<R>,
F: 'static + Fn(&'callback Lua) -> Result<R>,
{
let cb = self.create_callback(Box::new(move |lua, _| func(lua)?.to_lua_multi(lua)))?;
unsafe { self.push_value(cb.call(())?).map(|_| 1) }
@@ -463,7 +448,7 @@ impl Lua {
where
A: FromLuaMulti<'callback>,
R: ToLuaMulti<'callback>,
F: 'static + Send + Fn(&'callback Lua, A) -> Result<R>,
F: 'static + Fn(&'callback Lua, A) -> Result<R>,
{
self.create_callback(Box::new(move |lua, args| {
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
@@ -483,7 +468,7 @@ impl Lua {
where
A: FromLuaMulti<'callback>,
R: ToLuaMulti<'callback>,
F: 'static + Send + FnMut(&'callback Lua, A) -> Result<R>,
F: 'static + FnMut(&'callback Lua, A) -> Result<R>,
{
let func = RefCell::new(func);
self.create_function(move |lua, args| {
@@ -493,6 +478,64 @@ 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
/// `yield()` passing internal representation of a `Poll::Pending` value.
///
/// 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_timer::Delay;
/// use mlua::{Lua, Result};
///
/// async fn sleep(_lua: &Lua, n: u64) -> Result<&'static str> {
/// Delay::new(Duration::from_millis(n)).await;
/// Ok("done")
/// }
///
/// #[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
/// [`ThreadStream`]: struct.ThreadStream.html
#[cfg(feature = "async")]
pub fn create_async_function<'lua, 'callback, A, R, F, FR>(
&'lua self,
func: F,
) -> Result<Function<'lua>>
where
A: FromLuaMulti<'callback>,
R: ToLuaMulti<'callback>,
F: 'static + Fn(&'callback Lua, A) -> FR,
FR: 'static + Future<Output = Result<R>>,
{
self.create_async_callback(Box::new(move |lua, args| {
let args = match A::from_lua_multi(args, lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
Box::pin(func(lua, args).and_then(move |ret| future::ready(ret.to_lua_multi(lua))))
}))
}
/// Wraps a Lua function into a new thread (or coroutine).
///
/// Equivalent to `coroutine.create`.
@@ -513,7 +556,7 @@ impl Lua {
/// Create a Lua userdata object from a custom userdata type.
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + Send + UserData,
T: 'static + UserData,
{
unsafe { self.make_userdata(data) }
}
@@ -540,33 +583,6 @@ impl Lua {
}
}
/// Calls the given function with a `Scope` parameter, giving the function the ability to create
/// userdata and callbacks from rust types that are !Send or non-'static.
///
/// The lifetime of any function or userdata created through `Scope` lasts only until the
/// completion of this method call, on completion all such created values are automatically
/// dropped and Lua references to them are invalidated. If a script accesses a value created
/// through `Scope` outside of this method, a Lua error will result. Since we can ensure the
/// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another
/// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only
/// outlive the scope lifetime.
///
/// Inside the scope callback, all handles created through Scope will share the same unique 'lua
/// lifetime of the parent `Lua`. This allows scoped and non-scoped values to be mixed in
/// API calls, which is very useful (e.g. passing a scoped userdata to a non-scoped function).
/// However, this also enables handles to scoped values to be trivially leaked from the given
/// callback. This is not dangerous, though! After the callback returns, all scoped values are
/// invalidated, which means that though references may exist, the Rust types backing them have
/// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It
/// would be impossible to prevent handles to scoped values from escaping anyway, since you
/// would always be able to smuggle them through Lua state.
pub fn scope<'scope, 'lua: 'scope, F, R>(&'lua self, f: F) -> R
where
F: FnOnce(&Scope<'lua, 'scope>) -> R,
{
f(&Scope::new(self))
}
/// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal
/// behavior.
///
@@ -806,7 +822,7 @@ impl Lua {
/// `Error::MismatchedRegistryKey` if passed a `RegistryKey` that was not created with a
/// matching `Lua` state.
pub fn owns_registry_value(&self, key: &RegistryKey) -> bool {
Arc::ptr_eq(&key.unref_list, &self.extra.borrow().registry_unref_list)
Rc::ptr_eq(&key.unref_list, &self.extra.borrow().registry_unref_list)
}
/// Remove any registry values whose `RegistryKey`s have all been dropped.
@@ -818,8 +834,8 @@ impl Lua {
unsafe {
let unref_list = mem::replace(
&mut *mlua_expect!(
self.extra.borrow().registry_unref_list.lock(),
"unref list poisoned"
self.extra.borrow().registry_unref_list.try_borrow_mut(),
"unref list borrowed"
),
Some(Vec::new()),
);
@@ -1009,7 +1025,12 @@ impl Lua {
})?;
}
if methods.methods.is_empty() {
#[cfg(feature = "async")]
let no_methods = methods.methods.is_empty() && methods.async_methods.is_empty();
#[cfg(not(feature = "async"))]
let no_methods = methods.methods.is_empty();
if no_methods {
init_userdata_metatable::<RefCell<T>>(self.state, -1, None)?;
} else {
protect_lua_closure(self.state, 0, 1, |state| {
@@ -1022,6 +1043,14 @@ impl Lua {
ffi::lua_rawset(state, -3);
})?;
}
#[cfg(feature = "async")]
for (k, m) in methods.async_methods {
push_string(self.state, &k)?;
self.push_value(Value::Function(self.create_async_callback(m)?))?;
protect_lua_closure(self.state, 3, 1, |state| {
ffi::lua_rawset(state, -3);
})?;
}
init_userdata_metatable::<RefCell<T>>(self.state, -2, Some(-1))?;
ffi::lua_pop(self.state, 1);
@@ -1053,10 +1082,10 @@ impl Lua {
) -> Result<Function<'lua>> {
unsafe extern "C" fn call_callback(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |nargs| {
if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL {
return Err(Error::CallbackDestructed);
}
if ffi::lua_type(state, ffi::lua_upvalueindex(2)) == ffi::LUA_TNIL {
let func =
get_meta_gc_userdata::<Callback, Callback>(state, ffi::lua_upvalueindex(1));
let lua = get_gc_userdata::<Lua>(state, ffi::lua_upvalueindex(2));
if func.is_null() || lua.is_null() {
return Err(Error::CallbackDestructed);
}
@@ -1064,16 +1093,8 @@ impl Lua {
check_stack(state, ffi::LUA_MINSTACK - nargs)?;
}
let extra =
get_userdata::<Arc<RefCell<ExtraData>>>(state, ffi::lua_upvalueindex(2));
let lua = Lua {
state: state,
main_state: get_main_state(state),
extra: (*extra).clone(),
ephemeral: true,
_no_ref_unwind_safe: PhantomData,
};
let lua = &mut *lua;
lua.state = state;
let mut args = MultiValue::new();
args.reserve(nargs as usize);
@@ -1081,9 +1102,7 @@ impl Lua {
args.push_front(lua.pop_value());
}
let func = get_userdata::<Callback>(state, ffi::lua_upvalueindex(1));
let results = (*func)(&lua, args)?;
let results = (*func)(lua, args)?;
let nresults = results.len() as c_int;
check_stack(state, nresults)?;
@@ -1099,21 +1118,8 @@ impl Lua {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 6);
push_userdata::<Callback>(self.state, func)?;
ffi::lua_pushlightuserdata(
self.state,
&FUNCTION_CALLBACK_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX);
ffi::lua_setmetatable(self.state, -2);
push_userdata::<Arc<RefCell<ExtraData>>>(self.state, self.extra.clone())?;
ffi::lua_pushlightuserdata(
self.state,
&FUNCTION_EXTRA_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX);
ffi::lua_setmetatable(self.state, -2);
push_meta_gc_userdata::<Callback, _>(self.state, func)?;
push_gc_userdata(self.state, self.clone())?;
protect_lua_closure(self.state, 2, 1, |state| {
ffi::lua_pushcclosure(state, call_callback, 2);
@@ -1123,7 +1129,140 @@ impl Lua {
}
}
// Does not require Send bounds, which can lead to unsafety.
#[cfg(feature = "async")]
pub(crate) fn create_async_callback<'lua, 'callback>(
&'lua self,
func: AsyncCallback<'callback, 'static>,
) -> Result<Function<'lua>> {
#[cfg(any(feature = "lua53", feature = "lua52"))]
self.load_from_std_lib(StdLib::COROUTINE)?;
unsafe extern "C" fn call_callback(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |nargs| {
let func = get_meta_gc_userdata::<AsyncCallback, AsyncCallback>(
state,
ffi::lua_upvalueindex(1),
);
let lua = get_gc_userdata::<Lua>(state, ffi::lua_upvalueindex(2));
if func.is_null() || lua.is_null() {
return Err(Error::CallbackDestructed);
}
if nargs < ffi::LUA_MINSTACK {
check_stack(state, ffi::LUA_MINSTACK - nargs)?;
}
let lua = &mut *lua;
lua.state = state;
let mut args = MultiValue::new();
args.reserve(nargs as usize);
for _ in 0..nargs {
args.push_front(lua.pop_value());
}
let fut = (*func)(lua, args);
push_gc_userdata(state, fut)?;
push_gc_userdata(state, lua.clone())?;
ffi::lua_pushcclosure(state, poll_future, 2);
Ok(1)
})
}
unsafe extern "C" fn poll_future(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |nargs| {
let fut = get_gc_userdata::<LocalBoxFuture<Result<MultiValue>>>(
state,
ffi::lua_upvalueindex(1),
);
let lua = get_gc_userdata::<Lua>(state, ffi::lua_upvalueindex(2));
if fut.is_null() || lua.is_null() {
return Err(Error::CallbackDestructed);
}
if nargs < ffi::LUA_MINSTACK {
check_stack(state, ffi::LUA_MINSTACK - nargs)?;
}
let lua = &mut *lua;
let mut waker = noop_waker();
// Try to get an outer poll waker
ffi::lua_pushlightuserdata(
state,
&WAKER_REGISTRY_KEY as *const u8 as *mut ::std::os::raw::c_void,
);
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
if let Some(w) = get_gc_userdata::<Waker>(state, -1).as_ref() {
waker = (*w).clone();
}
ffi::lua_pop(state, 1);
let mut ctx = Context::from_waker(&waker);
match (*fut).as_mut().poll(&mut ctx) {
Poll::Pending => {
check_stack(state, 6)?;
ffi::lua_pushboolean(state, 0);
push_gc_userdata(state, AsyncPollPending)?;
Ok(2)
}
Poll::Ready(results) => {
let results = lua.create_sequence_from(results?)?;
check_stack(state, 2)?;
ffi::lua_pushboolean(state, 1);
lua.push_value(Value::Table(results))?;
Ok(2)
}
}
})
}
let get_poll = unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 6);
push_meta_gc_userdata::<AsyncCallback, _>(self.state, func)?;
push_gc_userdata(self.state, self.clone())?;
protect_lua_closure(self.state, 2, 1, |state| {
ffi::lua_pushcclosure(state, call_callback, 2);
})?;
Function(self.pop_ref())
};
let env = self.create_table()?;
env.set("get_poll", get_poll)?;
env.set("coroutine", self.globals().get::<_, Value>("coroutine")?)?;
env.set(
"unpack",
self.create_function(|_, tbl: Table| {
Ok(MultiValue::from_vec(
tbl.sequence_values().collect::<Result<Vec<Value>>>()?,
))
})?,
)?;
self.load(
r#"
local poll = get_poll(...)
while true do
ready, res = poll()
if ready then
return unpack(res)
end
coroutine.yield(res)
end
"#,
)
.set_name("_mlua_async_poll")?
.set_environment(env)?
.into_function()
}
pub(crate) unsafe fn make_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + UserData,
@@ -1143,6 +1282,16 @@ impl Lua {
Ok(AnyUserData(self.pop_ref()))
}
pub(crate) fn clone(&self) -> Self {
Lua {
state: self.state,
main_state: self.main_state,
extra: self.extra.clone(),
ephemeral: true,
_no_ref_unwind_safe: PhantomData,
}
}
}
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
@@ -1193,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
@@ -1202,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.
@@ -1221,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.
@@ -1228,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) {
@@ -1343,11 +1551,10 @@ unsafe fn ref_stack_pop(extra: &mut ExtraData) -> c_int {
}
}
static FUNCTION_CALLBACK_METATABLE_REGISTRY_KEY: u8 = 0;
static FUNCTION_EXTRA_METATABLE_REGISTRY_KEY: u8 = 0;
struct StaticUserDataMethods<'lua, T: 'static + UserData> {
methods: Vec<(Vec<u8>, Callback<'lua, 'static>)>,
#[cfg(feature = "async")]
async_methods: Vec<(Vec<u8>, AsyncCallback<'lua, 'static>)>,
meta_methods: Vec<(MetaMethod, Callback<'lua, 'static>)>,
_type: PhantomData<T>,
}
@@ -1356,6 +1563,8 @@ impl<'lua, T: 'static + UserData> Default for StaticUserDataMethods<'lua, T> {
fn default() -> StaticUserDataMethods<'lua, T> {
StaticUserDataMethods {
methods: Vec::new(),
#[cfg(feature = "async")]
async_methods: Vec::new(),
meta_methods: Vec::new(),
_type: PhantomData,
}
@@ -1368,7 +1577,7 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>,
M: 'static + Fn(&'lua Lua, &T, A) -> Result<R>,
{
self.methods
.push((name.as_ref().to_vec(), Self::box_method(method)));
@@ -1379,18 +1588,32 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
M: 'static + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
{
self.methods
.push((name.as_ref().to_vec(), Self::box_method_mut(method)));
}
#[cfg(feature = "async")]
fn add_async_method<S, A, R, M, MR>(&mut self, name: &S, method: M)
where
T: Clone,
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Fn(&'lua Lua, T, A) -> MR,
MR: 'static + Future<Output = Result<R>>,
{
self.async_methods
.push((name.as_ref().to_vec(), Self::box_async_method(method)));
}
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
where
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>,
F: 'static + Fn(&'lua Lua, A) -> Result<R>,
{
self.methods
.push((name.as_ref().to_vec(), Self::box_function(function)));
@@ -1401,17 +1624,31 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
F: 'static + FnMut(&'lua Lua, A) -> Result<R>,
{
self.methods
.push((name.as_ref().to_vec(), Self::box_function_mut(function)));
}
#[cfg(feature = "async")]
fn add_async_function<S, A, R, F, FR>(&mut self, name: &S, function: F)
where
T: Clone,
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Fn(&'lua Lua, A) -> FR,
FR: 'static + Future<Output = Result<R>>,
{
self.async_methods
.push((name.as_ref().to_vec(), Self::box_async_function(function)));
}
fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M)
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>,
M: 'static + Fn(&'lua Lua, &T, A) -> Result<R>,
{
self.meta_methods.push((meta, Self::box_method(method)));
}
@@ -1420,7 +1657,7 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
M: 'static + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
{
self.meta_methods.push((meta, Self::box_method_mut(method)));
}
@@ -1429,7 +1666,7 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>,
F: 'static + Fn(&'lua Lua, A) -> Result<R>,
{
self.meta_methods.push((meta, Self::box_function(function)));
}
@@ -1438,7 +1675,7 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
F: 'static + FnMut(&'lua Lua, A) -> Result<R>,
{
self.meta_methods
.push((meta, Self::box_function_mut(function)));
@@ -1450,7 +1687,7 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>,
M: 'static + Fn(&'lua Lua, &T, A) -> Result<R>,
{
Box::new(move |lua, mut args| {
if let Some(front) = args.pop_front() {
@@ -1471,7 +1708,7 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
M: 'static + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
{
let method = RefCell::new(method);
Box::new(move |lua, mut args| {
@@ -1492,11 +1729,41 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
})
}
#[cfg(feature = "async")]
fn box_async_method<A, R, M, MR>(method: M) -> AsyncCallback<'lua, 'static>
where
T: Clone,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Fn(&'lua Lua, T, A) -> MR,
MR: 'static + Future<Output = Result<R>>,
{
Box::new(move |lua, mut args| {
let fut_res = || {
if let Some(front) = args.pop_front() {
let userdata = AnyUserData::from_lua(front, lua)?;
let userdata = userdata.borrow::<T>()?.clone();
Ok(method(lua, userdata, A::from_lua_multi(args, lua)?))
} else {
Err(Error::FromLuaConversionError {
from: "missing argument",
to: "userdata",
message: None,
})
}
};
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)),
}
})
}
fn box_function<A, R, F>(function: F) -> Callback<'lua, 'static>
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>,
F: 'static + Fn(&'lua Lua, A) -> Result<R>,
{
Box::new(move |lua, args| function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua))
}
@@ -1505,7 +1772,7 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
F: 'static + FnMut(&'lua Lua, A) -> Result<R>,
{
let function = RefCell::new(function);
Box::new(move |lua, args| {
@@ -1515,4 +1782,21 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})
}
#[cfg(feature = "async")]
fn box_async_function<A, R, F, FR>(function: F) -> AsyncCallback<'lua, 'static>
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Fn(&'lua Lua, A) -> FR,
FR: 'static + Future<Output = Result<R>>,
{
Box::new(move |lua, args| {
let args = match A::from_lua_multi(args, lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
Box::pin(function(lua, args).and_then(move |ret| future::ready(ret.to_lua_multi(lua))))
})
}
}
+1 -1
View File
@@ -146,7 +146,7 @@ macro_rules! impl_tuple {
}
impl<'lua> FromLuaMulti<'lua> for () {
fn from_lua_multi(_: MultiValue, _: &'lua Lua) -> Result<Self> {
fn from_lua_multi(_: MultiValue<'lua>, _: &'lua Lua) -> Result<Self> {
Ok(())
}
}
+5 -2
View File
@@ -5,8 +5,11 @@ pub use crate::{
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
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, Scope as LuaScope, String as LuaString,
Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
RegistryKey as LuaRegistryKey, Result as LuaResult, String as LuaString, Table as LuaTable,
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")]
pub use crate::AsyncThread as LuaAsyncThread;
-484
View File
@@ -1,484 +0,0 @@
use std::any::Any;
use std::cell::Cell;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_void;
use std::rc::Rc;
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::types::{Callback, LuaRef};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use crate::util::{
assert_stack, init_userdata_metatable, protect_lua_closure, push_string, push_userdata,
take_userdata, StackGuard,
};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti, Value};
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
/// callbacks that are not required to be Send or 'static.
///
/// See [`Lua::scope`] for more details.
///
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub struct Scope<'lua, 'scope> {
lua: &'lua Lua,
destructors: RefCell<Vec<(LuaRef<'lua>, fn(LuaRef<'lua>) -> Box<dyn Any>)>>,
_scope_invariant: PhantomData<Cell<&'scope ()>>,
}
impl<'lua, 'scope> Scope<'lua, 'scope> {
pub(crate) fn new(lua: &'lua Lua) -> Scope<'lua, 'scope> {
Scope {
lua,
destructors: RefCell::new(Vec::new()),
_scope_invariant: PhantomData,
}
}
/// Wraps a Rust function or closure, creating a callable Lua function handle to it.
///
/// This is a version of [`Lua::create_function`] that creates a callback which expires on
/// scope drop. See [`Lua::scope`] for more details.
///
/// [`Lua::create_function`]: struct.Lua.html#method.create_function
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub fn create_function<'callback, A, R, F>(&'callback self, func: F) -> Result<Function<'lua>>
where
A: FromLuaMulti<'callback>,
R: ToLuaMulti<'callback>,
F: 'scope + Fn(&'callback Lua, A) -> Result<R>,
{
// Safe, because 'scope must outlive 'callback (due to Self containing 'scope), however the
// callback itself must be 'scope lifetime, so the function should not be able to capture
// anything of 'callback lifetime. 'scope can't be shortened due to being invariant, and
// the 'callback lifetime here can't be enlarged due to coming from a universal
// quantification in Lua::scope.
//
// I hope I got this explanation right, but in any case this is tested with compiletest_rs
// to make sure callbacks can't capture handles with lifetime outside the scope, inside the
// scope, and owned inside the callback itself.
unsafe {
self.create_callback(Box::new(move |lua, args| {
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}))
}
}
/// Wraps a Rust mutable closure, creating a callable Lua function handle to it.
///
/// This is a version of [`Lua::create_function_mut`] that creates a callback which expires
/// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details.
///
/// [`Lua::create_function_mut`]: struct.Lua.html#method.create_function_mut
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Scope::create_function`]: #method.create_function
pub fn create_function_mut<'callback, A, R, F>(
&'callback self,
func: F,
) -> Result<Function<'lua>>
where
A: FromLuaMulti<'callback>,
R: ToLuaMulti<'callback>,
F: 'scope + FnMut(&'callback Lua, A) -> Result<R>,
{
let func = RefCell::new(func);
self.create_function(move |lua, args| {
(&mut *func
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?)(lua, args)
})
}
/// Create a Lua userdata object from a custom userdata type.
///
/// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
/// scope drop, and does not require that the userdata type be Send (but still requires that the
/// UserData be 'static). See [`Lua::scope`] for more details.
///
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub fn create_static_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
{
// Safe even though T may not be Send, because the parent Lua cannot be sent to another
// thread while the Scope is alive (or the returned AnyUserData handle even).
unsafe {
let u = self.lua.make_userdata(data)?;
self.destructors.borrow_mut().push((u.0.clone(), |u| {
let state = u.lua.state;
assert_stack(state, 2);
u.lua.push_ref(&u);
// We know the destructor has not run yet because we hold a reference to the
// userdata.
Box::new(take_userdata::<RefCell<T>>(state))
}));
Ok(u)
}
}
/// Create a Lua userdata object from a custom userdata type.
///
/// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
/// scope drop, and does not require that the userdata type be Send or 'static. See
/// [`Lua::scope`] for more details.
///
/// Lifting the requirement that the UserData type be 'static comes with some important
/// limitations, so if you only need to eliminate the Send requirement, it is probably better to
/// use [`Scope::create_static_userdata`] instead.
///
/// The main limitation that comes from using non-'static userdata is that the produced userdata
/// will no longer have a `TypeId` associated with it, becuase `TypeId` can only work for
/// 'static types. This means that it is impossible, once the userdata is created, to get a
/// reference to it back *out* of an `AnyUserData` handle. This also implies that the
/// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept
/// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use
/// a single metatable for multiple non-'static types, so there is a higher cost associated with
/// creating the userdata metatable each time a new userdata is created.
///
/// [`create_static_userdata`]: #method.create_static_userdata
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`UserDataMethods`]: trait.UserDataMethods.html
pub fn create_nonstatic_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'scope + UserData,
{
let data = Rc::new(RefCell::new(data));
// 'callback outliving 'scope is a lie to make the types work out, required due to the
// inability to work with the more correct callback type that is universally quantified over
// 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua
// lifetime, so none of the static methods UserData types can add can possibly capture
// parameters.
fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
scope: &Scope<'lua, 'scope>,
data: Rc<RefCell<T>>,
method: NonStaticMethod<'callback, T>,
) -> Result<Function<'lua>> {
// On methods that actually receive the userdata, we fake a type check on the passed in
// userdata, where we pretend there is a unique type per call to
// `Scope::create_nonstatic_userdata`. You can grab a method from a userdata and call
// it on a mismatched userdata type, which when using normal 'static userdata will fail
// with a type mismatch, but here without this check would proceed as though you had
// called the method on the original value (since we otherwise completely ignore the
// first argument).
let check_data = data.clone();
let check_ud_type = move |lua: &'callback Lua, value| {
if let Some(value) = value {
if let Value::UserData(u) = value {
unsafe {
assert_stack(lua.state, 1);
lua.push_ref(&u.0);
ffi::lua_getuservalue(lua.state, -1);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
{
ffi::lua_pushinteger(lua.state, 1);
ffi::lua_gettable(lua.state, -2);
ffi::lua_remove(lua.state, -2);
}
return ffi::lua_touserdata(lua.state, -1)
== check_data.as_ptr() as *mut c_void;
}
}
}
false
};
match method {
NonStaticMethod::Method(method) => {
let method_data = data.clone();
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
if !check_ud_type(lua, args.pop_front()) {
return Err(Error::UserDataTypeMismatch);
}
let data = method_data
.try_borrow()
.map_err(|_| Error::UserDataBorrowError)?;
method(lua, &*data, args)
});
unsafe { scope.create_callback(f) }
}
NonStaticMethod::MethodMut(method) => {
let method = RefCell::new(method);
let method_data = data.clone();
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
if !check_ud_type(lua, args.pop_front()) {
return Err(Error::UserDataTypeMismatch);
}
let mut method = method
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
let mut data = method_data
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
(&mut *method)(lua, &mut *data, args)
});
unsafe { scope.create_callback(f) }
}
NonStaticMethod::Function(function) => unsafe { scope.create_callback(function) },
NonStaticMethod::FunctionMut(function) => {
let function = RefCell::new(function);
let f = Box::new(move |lua, args| {
(&mut *function
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?)(
lua, args
)
});
unsafe { scope.create_callback(f) }
}
}
}
let mut ud_methods = NonStaticUserDataMethods::default();
T::add_methods(&mut ud_methods);
unsafe {
let lua = self.lua;
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
push_userdata(lua.state, ())?;
#[cfg(feature = "lua53")]
ffi::lua_pushlightuserdata(lua.state, data.as_ptr() as *mut c_void);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
protect_lua_closure(lua.state, 0, 1, |state| {
// Lua 5.2/5.1 allows to store only table. Then we will wrap the value.
ffi::lua_createtable(state, 1, 0);
ffi::lua_pushinteger(state, 1);
ffi::lua_pushlightuserdata(state, data.as_ptr() as *mut c_void);
ffi::lua_settable(state, -3);
})?;
ffi::lua_setuservalue(lua.state, -2);
protect_lua_closure(lua.state, 0, 1, move |state| {
ffi::lua_newtable(state);
})?;
for (k, m) in ud_methods.meta_methods {
push_string(lua.state, k.name())?;
lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?;
protect_lua_closure(lua.state, 3, 1, |state| {
ffi::lua_rawset(state, -3);
})?;
}
if ud_methods.methods.is_empty() {
init_userdata_metatable::<()>(lua.state, -1, None)?;
} else {
protect_lua_closure(lua.state, 0, 1, |state| {
ffi::lua_newtable(state);
})?;
for (k, m) in ud_methods.methods {
push_string(lua.state, &k)?;
lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?;
protect_lua_closure(lua.state, 3, 1, |state| {
ffi::lua_rawset(state, -3);
})?;
}
init_userdata_metatable::<()>(lua.state, -2, Some(-1))?;
ffi::lua_pop(lua.state, 1);
}
ffi::lua_setmetatable(lua.state, -2);
Ok(AnyUserData(lua.pop_ref()))
}
}
// Unsafe, because the callback can improperly capture any value with 'callback scope, such as
// improperly capturing an argument. Since the 'callback lifetime is chosen by the user and the
// lifetime of the callback itself is 'scope (non-'static), the borrow checker will happily pick
// a 'callback that outlives 'scope to allow this. In order for this to be safe, the callback
// must NOT capture any parameters.
unsafe fn create_callback<'callback>(
&self,
f: Callback<'callback, 'scope>,
) -> Result<Function<'lua>> {
let f = mem::transmute::<Callback<'callback, 'scope>, Callback<'lua, 'static>>(f);
let f = self.lua.create_callback(f)?;
let mut destructors = self.destructors.borrow_mut();
destructors.push((f.0.clone(), |f| {
let state = f.lua.state;
assert_stack(state, 3);
f.lua.push_ref(&f);
ffi::lua_getupvalue(state, -1, 1);
// We know the destructor has not run yet because we hold a reference to the callback.
let ud = take_userdata::<Callback>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 1);
ffi::lua_pop(state, 1);
Box::new(ud)
}));
Ok(f)
}
}
impl<'lua, 'scope> Drop for Scope<'lua, 'scope> {
fn drop(&mut self) {
// We separate the action of invalidating the userdata in Lua and actually dropping the
// userdata type into two phases. This is so that, in the event a userdata drop panics, we
// can be sure that all of the userdata in Lua is actually invalidated.
// All destructors are non-panicking, so this is fine
let to_drop = self
.destructors
.get_mut()
.drain(..)
.map(|(r, dest)| dest(r))
.collect::<Vec<_>>();
drop(to_drop);
}
}
enum NonStaticMethod<'lua, T> {
Method(Box<dyn Fn(&'lua Lua, &T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
MethodMut(Box<dyn FnMut(&'lua Lua, &mut T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
Function(Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
FunctionMut(Box<dyn FnMut(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
}
struct NonStaticUserDataMethods<'lua, T: UserData> {
methods: Vec<(Vec<u8>, NonStaticMethod<'lua, T>)>,
meta_methods: Vec<(MetaMethod, NonStaticMethod<'lua, T>)>,
}
impl<'lua, T: UserData> Default for NonStaticUserDataMethods<'lua, T> {
fn default() -> NonStaticUserDataMethods<'lua, T> {
NonStaticUserDataMethods {
methods: Vec::new(),
meta_methods: Vec::new(),
}
}
}
impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'lua, T> {
fn add_method<S, A, R, M>(&mut self, name: &S, method: M)
where
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
NonStaticMethod::Method(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_method_mut<S, A, R, M>(&mut self, name: &S, mut method: M)
where
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
where
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
NonStaticMethod::Function(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_function_mut<S, A, R, F>(&mut self, name: &S, mut function: F)
where
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M)
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>,
{
self.meta_methods.push((
meta,
NonStaticMethod::Method(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, mut method: M)
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
{
self.meta_methods.push((
meta,
NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F)
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>,
{
self.meta_methods.push((
meta,
NonStaticMethod::Function(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, mut function: F)
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
{
self.meta_methods.push((
meta,
NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
}
+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.
+208
View File
@@ -8,6 +8,24 @@ use crate::util::{
};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(feature = "async")]
use {
crate::{
error::ExternalError,
lua::{AsyncPollPending, Lua, WAKER_REGISTRY_KEY},
util::{get_gc_userdata, push_gc_userdata},
value::Value,
},
futures_core::{future::Future, stream::Stream},
std::{
cell::RefCell,
marker::PhantomData,
os::raw::c_void,
pin::Pin,
task::{Context, Poll, Waker},
},
};
/// Status of a Lua thread (or coroutine).
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ThreadStatus {
@@ -27,6 +45,18 @@ pub enum ThreadStatus {
#[derive(Clone, Debug)]
pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
/// 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> {
thread: Thread<'lua>,
args0: RefCell<Option<Result<MultiValue<'lua>>>>,
ret: PhantomData<R>,
}
impl<'lua> Thread<'lua> {
/// Resumes execution of this thread.
///
@@ -142,6 +172,59 @@ impl<'lua> Thread<'lua> {
}
}
}
/// Converts Thread to an AsyncThread which implements Future and Stream traits.
///
/// `args` are passed as arguments to the thread function for first call.
/// The object call `resume()` while polling and also allows to run rust futures
/// to completion using an executor.
///
/// Using AsyncThread as a Stream allows to iterate through `coroutine.yield()`
/// values whereas Future version discards that values and poll until the final
/// one (returned from the thread function).
///
/// # Examples
///
/// ```
/// # 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)
/// 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(())
/// # }
/// ```
#[cfg(feature = "async")]
pub fn into_async<A, R>(self, args: A) -> AsyncThread<'lua, R>
where
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let args = args.to_lua_multi(&self.0.lua);
AsyncThread {
thread: self,
args0: RefCell::new(Some(args)),
ret: PhantomData,
}
}
}
impl<'lua> PartialEq for Thread<'lua> {
@@ -149,3 +232,128 @@ impl<'lua> PartialEq for Thread<'lua> {
self.0 == other.0
}
}
#[cfg(feature = "async")]
impl<'lua, R> Stream for AsyncThread<'lua, R>
where
R: FromLuaMulti<'lua>,
{
type Item = Result<R>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let lua = self.thread.0.lua;
match self.thread.status() {
ThreadStatus::Resumable => {}
_ => return Poll::Ready(None),
};
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
} else {
self.thread.resume(())?
};
if is_poll_pending(lua, &ret) {
return Poll::Pending;
}
cx.waker().wake_by_ref();
Poll::Ready(Some(R::from_lua_multi(ret, lua)))
}
}
#[cfg(feature = "async")]
impl<'lua, R> Future for AsyncThread<'lua, R>
where
R: FromLuaMulti<'lua>,
{
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let lua = self.thread.0.lua;
match self.thread.status() {
ThreadStatus::Resumable => {}
_ => return Poll::Ready(Err("Thread already finished".to_lua_err())),
};
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
} else {
self.thread.resume(())?
};
if is_poll_pending(lua, &ret) {
return Poll::Pending;
}
if let ThreadStatus::Resumable = self.thread.status() {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
return Poll::Pending;
}
Poll::Ready(R::from_lua_multi(ret, lua))
}
}
#[cfg(feature = "async")]
fn is_poll_pending(lua: &Lua, val: &MultiValue) -> bool {
if val.len() != 1 {
return false;
}
if let Some(Value::UserData(ud)) = val.iter().next() {
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
lua.push_ref(&ud.0);
let is_pending = get_gc_userdata::<AsyncPollPending>(lua.state, -1)
.as_ref()
.is_some();
ffi::lua_pop(lua.state, 1);
return is_pending;
}
}
false
}
#[cfg(feature = "async")]
struct WakerGuard(*mut ffi::lua_State);
#[cfg(feature = "async")]
impl WakerGuard {
pub fn new(state: *mut ffi::lua_State, waker: Waker) -> Result<WakerGuard> {
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 6);
ffi::lua_pushlightuserdata(state, &WAKER_REGISTRY_KEY as *const u8 as *mut c_void);
push_gc_userdata(state, waker)?;
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
Ok(WakerGuard(state))
}
}
}
#[cfg(feature = "async")]
impl Drop for WakerGuard {
fn drop(&mut self) {
unsafe {
let state = self.0;
let _sg = StackGuard::new(state);
assert_stack(state, 2);
ffi::lua_pushlightuserdata(state, &WAKER_REGISTRY_KEY as *const u8 as *mut c_void);
ffi::lua_pushnil(state);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
}
}
}
+16 -12
View File
@@ -1,7 +1,11 @@
use std::cell::RefCell;
use std::os::raw::{c_int, c_void};
use std::sync::{Arc, Mutex};
use std::rc::Rc;
use std::{fmt, mem, ptr};
#[cfg(feature = "async")]
use futures_core::future::LocalBoxFuture;
use crate::error::Result;
use crate::ffi;
use crate::lua::Lua;
@@ -20,29 +24,28 @@ pub struct LightUserData(pub *mut c_void);
pub(crate) type Callback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>;
#[cfg(feature = "async")]
pub(crate) type AsyncCallback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> LocalBoxFuture<'lua, Result<MultiValue<'lua>>> + 'a>;
/// An auto generated key into the Lua registry.
///
/// This is a handle to a value stored inside the Lua registry. It is not directly usable like the
/// `Table` or `Function` handle types, but since it doesn't hold a reference to a parent Lua and is
/// Send + Sync + 'static, it is much more flexible and can be used in many situations where it is
/// impossible to directly store a normal handle type. It is not automatically garbage collected on
/// Drop, but it can be removed with [`Lua::remove_registry_value`], and instances not manually
/// removed can be garbage collected with [`Lua::expire_registry_values`].
/// This is a handle to a value stored inside the Lua registry. It is not automatically
/// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`],
/// and instances not manually removed can be garbage collected with [`Lua::expire_registry_values`].
///
/// Be warned, If you place this into Lua via a `UserData` type or a rust callback, it is *very
/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve.
/// Instead of placing a `RegistryKey` into a `UserData` type, prefer instead to use
/// [`UserData::set_user_value`] / [`UserData::get_user_value`], and instead of moving a RegistryKey
/// into a callback, prefer [`Lua::scope`].
/// [`UserData::set_user_value`] / [`UserData::get_user_value`].
///
/// [`Lua::remove_registry_value`]: struct.Lua.html#method.remove_registry_value
/// [`Lua::expire_registry_values`]: struct.Lua.html#method.expire_registry_values
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`UserData::set_user_value`]: struct.UserData.html#method.set_user_value
/// [`UserData::get_user_value`]: struct.UserData.html#method.get_user_value
pub struct RegistryKey {
pub(crate) registry_id: c_int,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
pub(crate) unref_list: Rc<RefCell<Option<Vec<c_int>>>>,
}
impl fmt::Debug for RegistryKey {
@@ -53,7 +56,8 @@ impl fmt::Debug for RegistryKey {
impl Drop for RegistryKey {
fn drop(&mut self) {
if let Some(list) = mlua_expect!(self.unref_list.lock(), "unref_list poisoned").as_mut() {
let mut unref_list = mlua_expect!(self.unref_list.try_borrow_mut(), "unref list borrowed");
if let Some(list) = unref_list.as_mut() {
list.push(self.registry_id);
}
}
+44 -9
View File
@@ -1,5 +1,8 @@
use std::cell::{Ref, RefCell, RefMut};
#[cfg(feature = "async")]
use std::future::Future;
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
@@ -134,7 +137,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>;
M: 'static + Fn(&'lua Lua, &T, A) -> Result<R>;
/// Add a regular method which accepts a `&mut T` as the first parameter.
///
@@ -146,7 +149,23 @@ pub trait UserDataMethods<'lua, T: UserData> {
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
M: 'static + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
/// Add an async method which accepts a `T` as the first parameter and returns Future.
/// The passed `T` is cloned from the original value.
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// [`add_method`]: #method.add_method
#[cfg(feature = "async")]
fn add_async_method<S, A, R, M, MR>(&mut self, name: &S, method: M)
where
T: Clone,
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Fn(&'lua Lua, T, A) -> MR,
MR: 'static + Future<Output = Result<R>>;
/// Add a regular method as a function which accepts generic arguments, the first argument will
/// be a `UserData` of type T if the method is called with Lua method syntax:
@@ -162,7 +181,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>;
F: 'static + Fn(&'lua Lua, A) -> Result<R>;
/// Add a regular method as a mutable function which accepts generic arguments.
///
@@ -174,7 +193,23 @@ pub trait UserDataMethods<'lua, T: UserData> {
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>;
F: 'static + FnMut(&'lua Lua, A) -> Result<R>;
/// Add a regular method as an async function which accepts generic arguments
/// and returns Future.
///
/// This is an async version of [`add_function`].
///
/// [`add_function`]: #method.add_function
#[cfg(feature = "async")]
fn add_async_function<S, A, R, F, FR>(&mut self, name: &S, function: F)
where
T: Clone,
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Fn(&'lua Lua, A) -> FR,
FR: 'static + Future<Output = Result<R>>;
/// Add a metamethod which accepts a `&T` as the first parameter.
///
@@ -188,7 +223,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>;
M: 'static + Fn(&'lua Lua, &T, A) -> Result<R>;
/// Add a metamethod as a function which accepts a `&mut T` as the first parameter.
///
@@ -202,7 +237,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
M: 'static + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
/// Add a metamethod which accepts generic arguments.
///
@@ -213,7 +248,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>;
F: 'static + Fn(&'lua Lua, A) -> Result<R>;
/// Add a metamethod as a mutable function which accepts generic arguments.
///
@@ -224,7 +259,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>;
F: 'static + FnMut(&'lua Lua, A) -> Result<R>;
}
/// Trait for custom userdata types.
@@ -293,7 +328,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// [`UserDataMethods`]: trait.UserDataMethods.html
pub trait UserData: Sized {
/// Adds custom methods and operators specific to this userdata.
fn add_methods<'lua, T: UserDataMethods<'lua, Self>>(_methods: &mut T) {}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(_methods: &mut M) {}
}
/// Handle to an internal Lua userdata for any type that implements [`UserData`].
+102 -122
View File
@@ -1,14 +1,20 @@
use std::any::Any;
use std::any::{Any, TypeId};
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Write;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::sync::Arc;
use std::rc::Rc;
use std::{mem, ptr, slice};
use crate::error::{Error, Result};
use crate::ffi;
thread_local! {
static METATABLE_CACHE: RefCell<HashMap<TypeId, c_int>> = RefCell::new(HashMap::new());
}
// Checks that Lua has enough free stack space for future stack operations. On failure, this will
// panic with an internal error message.
pub unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) {
@@ -175,8 +181,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
if let Some(err) = get_wrapped_error(state, -1).as_ref() {
ffi::lua_pop(state, 1);
err.clone()
} else if is_wrapped_panic(state, -1) {
let panic = get_userdata::<WrappedPanic>(state, -1);
} else if let Some(panic) = get_gc_userdata::<WrappedPanic>(state, -1).as_mut() {
if let Some(p) = (*panic).0.take() {
resume_unwind(p);
} else {
@@ -255,6 +260,41 @@ pub unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
ptr::read(ud)
}
// Pushes the userdata and attaches a metatable with __gc method
// Internally uses 5 stack spaces, does not call checkstack
pub unsafe fn push_gc_userdata<T: Any>(state: *mut ffi::lua_State, t: T) -> Result<()> {
push_meta_gc_userdata::<T, T>(state, t)
}
pub unsafe fn push_meta_gc_userdata<MT: Any, T>(state: *mut ffi::lua_State, t: T) -> Result<()> {
let ud = protect_lua_closure(state, 0, 1, move |state| {
ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T
})?;
ptr::write(ud, t);
get_gc_metatable_for::<MT>(state);
ffi::lua_setmetatable(state, -2);
Ok(())
}
// Uses 2 stack spaces, does not call checkstack
pub unsafe fn get_gc_userdata<T: Any>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
get_meta_gc_userdata::<T, T>(state, index)
}
pub unsafe fn get_meta_gc_userdata<MT: Any, T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;
if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 {
return ptr::null_mut();
}
get_gc_metatable_for::<MT>(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
if !res {
return ptr::null_mut();
}
ud
}
// Populates the given table with the appropriate members to be a userdata metatable for the given
// type. This function takes the given table at the `metatable` index, and adds an appropriate __gc
// member to it for the given type and a __metatable entry to protect the table from script access.
@@ -380,14 +420,14 @@ where
Ok(Err(err)) => {
ffi::lua_settop(state, 1);
ptr::write(ud as *mut WrappedError, WrappedError(err));
get_error_metatable(state);
get_gc_metatable_for::<WrappedError>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
Err(p) => {
ffi::lua_settop(state, 1);
ptr::write(ud as *mut WrappedPanic, WrappedPanic(Some(p)));
get_panic_metatable(state);
get_gc_metatable_for::<WrappedPanic>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
@@ -428,12 +468,12 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
ud,
WrappedError(Error::CallbackError {
traceback,
cause: Arc::new(error),
cause: Rc::new(error),
}),
);
get_error_metatable(state);
get_gc_metatable_for::<WrappedError>(state);
ffi::lua_setmetatable(state, -2);
} else if !is_wrapped_panic(state, -1) {
} else if let None = get_gc_userdata::<WrappedPanic>(state, -1).as_ref() {
if ffi::lua_checkstack(state, LUA_TRACEBACK_STACK) != 0 {
let s = ffi::luaL_tolstring(state, -1, ptr::null_mut());
ffi::luaL_traceback(state, state, s, 0);
@@ -443,68 +483,72 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
1
}
// Does not call lua_checkstack, uses 2 stack spaces.
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub unsafe fn set_main_state(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(state, &MAIN_THREAD_REGISTRY_KEY as *const u8 as *mut c_void);
ffi::lua_pushthread(state);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
}
// Does not call lua_checkstack, uses 1 stack space.
pub unsafe fn get_main_state(state: *mut ffi::lua_State) -> *mut ffi::lua_State {
#[cfg(any(feature = "lua53", feature = "lua52"))]
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
#[cfg(any(feature = "lua51", feature = "luajit"))]
{
ffi::lua_pushlightuserdata(state, &MAIN_THREAD_REGISTRY_KEY as *const u8 as *mut c_void);
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
let main_state = ffi::lua_tothread(state, -1);
ffi::lua_pop(state, 1);
main_state
}
let main_state = ffi::lua_tothread(state, -1);
ffi::lua_pop(state, 1);
main_state
#[cfg(any(feature = "lua51", feature = "luajit"))]
state
}
// Pushes a WrappedError to the top of the stack. Uses two stack spaces and does not call
// lua_checkstack.
pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: Error) -> Result<()> {
let ud = protect_lua_closure(state, 0, 1, move |state| {
ffi::lua_newuserdata(state, mem::size_of::<WrappedError>()) as *mut WrappedError
})?;
ptr::write(ud, WrappedError(err));
get_error_metatable(state);
ffi::lua_setmetatable(state, -2);
Ok(())
push_gc_userdata::<WrappedError>(state, WrappedError(err))
}
// Checks if the value at the given index is a WrappedError, and if it is returns a pointer to it,
// otherwise returns null. Uses 2 stack spaces and does not call lua_checkstack.
pub unsafe fn get_wrapped_error(state: *mut ffi::lua_State, index: c_int) -> *const Error {
let userdata = ffi::lua_touserdata(state, index);
if userdata.is_null() {
let ud = get_gc_userdata::<WrappedError>(state, index);
if ud.is_null() {
return ptr::null();
}
&(*ud).0
}
if ffi::lua_getmetatable(state, index) == 0 {
return ptr::null();
// Initialize the internal (with __gc) metatable for a type T
pub unsafe fn init_gc_metatable_for<T: Any>(
state: *mut ffi::lua_State,
customize_fn: Option<fn(*mut ffi::lua_State)>,
) {
let type_id = TypeId::of::<T>();
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__gc"));
ffi::lua_pushcfunction(state, userdata_destructor::<T>);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
if let Some(f) = customize_fn {
f(state)
}
get_error_metatable(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
let ref_addr = ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX);
METATABLE_CACHE.with(|mc| mc.borrow_mut().insert(type_id, ref_addr));
}
if res {
&(*get_userdata::<WrappedError>(state, -1)).0
} else {
ptr::null()
}
pub unsafe fn get_gc_metatable_for<T: Any>(state: *mut ffi::lua_State) {
let type_id = TypeId::of::<T>();
let ref_addr = METATABLE_CACHE
.with(|mc| *mlua_expect!(mc.borrow().get(&type_id), "gc metatable does not exist"));
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ref_addr as ffi::lua_Integer);
}
// Initialize the error, panic, and destructed userdata metatables.
pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
assert_stack(state, 8);
// Create error metatable
// Create error and panic metatables
unsafe extern "C" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
let err_buf = callback_error(state, |_| {
@@ -524,8 +568,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
// kind of recursive error structure?)
let _ = write!(&mut (*err_buf), "{}", error);
Ok(err_buf)
} else if is_wrapped_panic(state, -1) {
let panic = get_userdata::<WrappedPanic>(state, -1);
} else if let Some(panic) = get_gc_userdata::<WrappedPanic>(state, -1).as_ref() {
if let Some(ref p) = (*panic).0 {
ffi::lua_pushlightuserdata(
state,
@@ -564,56 +607,31 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
1
}
ffi::lua_pushlightuserdata(
init_gc_metatable_for::<WrappedError>(
state,
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
Some(|state| {
ffi::lua_pushstring(state, cstr!("__tostring"));
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_rawset(state, -3);
}),
);
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__gc"));
ffi::lua_pushcfunction(state, userdata_destructor::<WrappedError>);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__tostring"));
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
// Create panic metatable
ffi::lua_pushlightuserdata(
init_gc_metatable_for::<WrappedPanic>(
state,
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
Some(|state| {
ffi::lua_pushstring(state, cstr!("__tostring"));
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_rawset(state, -3);
}),
);
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__gc"));
ffi::lua_pushcfunction(state, userdata_destructor::<WrappedPanic>);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__tostring"));
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
// Create destructed userdata metatable
unsafe extern "C" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_checkstack(state, 2, ptr::null());
let ud = ffi::lua_newuserdata(state, mem::size_of::<WrappedError>()) as *mut WrappedError;
ptr::write(ud, WrappedError(Error::CallbackDestructed));
get_error_metatable(state);
get_gc_metatable_for::<WrappedError>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
@@ -709,40 +727,6 @@ unsafe fn to_string<'a>(state: *mut ffi::lua_State, index: c_int) -> Cow<'a, str
}
}
// Checks if the value at the given index is a WrappedPanic. Uses 2 stack spaces and does not call
// lua_checkstack.
unsafe fn is_wrapped_panic(state: *mut ffi::lua_State, index: c_int) -> bool {
let userdata = ffi::lua_touserdata(state, index);
if userdata.is_null() {
return false;
}
if ffi::lua_getmetatable(state, index) == 0 {
return false;
}
get_panic_metatable(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
res
}
unsafe fn get_error_metatable(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
}
unsafe fn get_panic_metatable(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
}
unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
@@ -751,9 +735,5 @@ unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
static MAIN_THREAD_REGISTRY_KEY: u8 = 0;
static ERROR_METATABLE_REGISTRY_KEY: u8 = 0;
static PANIC_METATABLE_REGISTRY_KEY: u8 = 0;
static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
static ERROR_PRINT_BUFFER_KEY: u8 = 0;
+4 -3
View File
@@ -48,18 +48,19 @@ pub enum Value<'lua> {
pub use self::Value::Nil;
impl<'lua> Value<'lua> {
pub(crate) fn type_name(&self) -> &'static str {
pub fn type_name(&self) -> &'static str {
match *self {
Value::Nil => "nil",
Value::Boolean(_) => "boolean",
Value::LightUserData(_) => "light userdata",
Value::LightUserData(_) => "lightuserdata",
Value::Integer(_) => "integer",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Table(_) => "table",
Value::Function(_) => "function",
Value::Thread(_) => "thread",
Value::UserData(_) | Value::Error(_) => "userdata",
Value::UserData(_) => "userdata",
Value::Error(_) => "error",
}
}
+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(())
}
+21 -6
View File
@@ -10,6 +10,21 @@ error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutabili
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
error[E0277]: the type `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<usize>`
= note: required because it appears within the type `std::cell::Cell<usize>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
@@ -18,9 +33,9 @@ error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
@@ -34,9 +49,9 @@ error[E0277]: the type `std::cell::UnsafeCell<isize>` may contain interior mutab
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<isize>`
= note: required because it appears within the type `std::cell::Cell<isize>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
+23 -6
View File
@@ -12,6 +12,23 @@ error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutabili
= note: required because it appears within the type `mlua::table::Table<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
error[E0277]: the type `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<usize>`
= note: required because it appears within the type `std::cell::Cell<usize>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
= note: required because it appears within the type `mlua::table::Table<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
@@ -20,9 +37,9 @@ error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
@@ -38,9 +55,9 @@ error[E0277]: the type `std::cell::UnsafeCell<isize>` may contain interior mutab
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<isize>`
= note: required because it appears within the type `std::cell::Cell<isize>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<alloc::sync::ArcInner<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
@@ -1,22 +0,0 @@
use mlua::{Lua, Table, Result};
struct Test {
field: i32,
}
fn main() {
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let mut inner: Option<Table> = None;
let f = scope
.create_function_mut(move |lua, t: Table| {
if let Some(old) = inner.take() {
// Access old callback `Lua`.
}
inner = Some(t);
Ok(())
})?;
f.call::<_, ()>(lua.create_table()?)?;
Ok(())
});
}
@@ -1,45 +0,0 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_capture.rs:12:14
|
12 | .create_function_mut(move |lua, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 9:15...
--> $DIR/scope_callback_capture.rs:9:15
|
9 | lua.scope(|scope| -> Result<()> {
| _______________^
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(move |lua, t: Table| {
... |
20 | | Ok(())
21 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
--> $DIR/scope_callback_capture.rs:11:17
|
11 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 9:5...
--> $DIR/scope_callback_capture.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(move |lua, t: Table| {
... |
20 | | Ok(())
21 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_capture.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(move |lua, t: Table| {
... |
20 | | Ok(())
21 | | });
| |______^
@@ -1,19 +0,0 @@
use mlua::{Lua, Table, Result};
struct Test {
field: i32,
}
fn main() {
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let mut inner: Option<Table> = None;
let f = scope
.create_function_mut(|_, t: Table| {
inner = Some(t);
Ok(())
})?;
f.call::<_, ()>(lua.create_table()?)?;
Ok(())
});
}
@@ -1,45 +0,0 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_inner.rs:12:14
|
12 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 9:15...
--> $DIR/scope_callback_inner.rs:9:15
|
9 | lua.scope(|scope| -> Result<()> {
| _______________^
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(|_, t: Table| {
... |
17 | | Ok(())
18 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
--> $DIR/scope_callback_inner.rs:11:17
|
11 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 9:5...
--> $DIR/scope_callback_inner.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(|_, t: Table| {
... |
17 | | Ok(())
18 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_inner.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(|_, t: Table| {
... |
17 | | Ok(())
18 | | });
| |______^
@@ -1,19 +0,0 @@
use mlua::{Lua, Table, Result};
struct Test {
field: i32,
}
fn main() {
let lua = Lua::new();
let mut outer: Option<Table> = None;
lua.scope(|scope| -> Result<()> {
let f = scope
.create_function_mut(|_, t: Table| {
outer = Some(t);
Ok(())
})?;
f.call::<_, ()>(lua.create_table()?)?;
Ok(())
});
}
@@ -1,11 +0,0 @@
error: borrowed data cannot be stored outside of its closure
--> $DIR/scope_callback_outer.rs:11:17
|
9 | let mut outer: Option<Table> = None;
| --------- ...so that variable is valid at time of its declaration
10 | lua.scope(|scope| -> Result<()> {
| --------------------- borrowed data cannot outlive this closure
11 | let f = scope
| ^^^^^ cannot be stored outside of its closure
12 | .create_function_mut(|_, t: Table| {
| ------------------- cannot infer an appropriate lifetime...
-23
View File
@@ -1,23 +0,0 @@
use mlua::{Lua, Result};
struct Test {
field: i32,
}
fn main() {
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let f = {
let mut test = Test { field: 0 };
scope
.create_function_mut(|_, ()| {
test.field = 42;
//~^ error: `test` does not live long enough
Ok(())
})?
};
f.call::<_, ()>(())
});
}
@@ -1,25 +0,0 @@
error[E0373]: closure may outlive the current function, but it borrows `test`, which is owned by the current function
--> $DIR/scope_invariance.rs:14:38
|
9 | lua.scope(|scope| -> Result<()> {
| ----- has type `&mlua::scope::Scope<'_, '1>`
...
14 | .create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test`
15 | test.field = 42;
| ---- `test` is borrowed here
|
note: function requires argument type to outlive `'1`
--> $DIR/scope_invariance.rs:13:13
|
13 | / scope
14 | | .create_function_mut(|_, ()| {
15 | | test.field = 42;
16 | | //~^ error: `test` does not live long enough
17 | | Ok(())
18 | | })?
| |__________________^
help: to force the closure to take ownership of `test` (and any other referenced variables), use the `move` keyword
|
14 | .create_function_mut(move |_, ()| {
| ^^^^^^^^^^^^
@@ -1,15 +0,0 @@
use mlua::{Lua, UserData, Result};
struct MyUserData<'a>(&'a mut i32);
impl<'a> UserData for MyUserData<'a> {}
fn main() {
let mut i = 1;
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let _a = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
let _b = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
Ok(())
});
}
@@ -1,9 +0,0 @@
error[E0499]: cannot borrow `i` as mutable more than once at a time
--> $DIR/scope_mutable_aliasing.rs:12:61
|
11 | let _a = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
| ------ first mutable borrow occurs here
12 | let _b = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
| ------------------------- ^^^^^^ second mutable borrow occurs here
| |
| first borrow later used by call
@@ -1,20 +0,0 @@
use mlua::{Lua, UserData, Result};
struct MyUserData<'a>(&'a i32);
impl<'a> UserData for MyUserData<'a> {}
fn main() {
// Should not allow userdata borrow to outlive lifetime of AnyUserData handle
let igood = 1;
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let _ugood = scope.create_nonstatic_userdata(MyUserData(&igood))?;
let _ubad = {
let ibad = 42;
scope.create_nonstatic_userdata(MyUserData(&ibad))?;
};
Ok(())
});
}
@@ -1,13 +0,0 @@
error[E0597]: `ibad` does not live long enough
--> $DIR/scope_userdata_borrow.rs:16:56
|
12 | lua.scope(|scope| -> Result<()> {
| ----- has type `&mlua::scope::Scope<'_, '1>`
...
16 | scope.create_nonstatic_userdata(MyUserData(&ibad))?;
| -------------------------------------------^^^^^--
| | |
| | borrowed value does not live long enough
| argument requires that `ibad` is borrowed for `'1`
17 | };
| - `ibad` dropped here while still borrowed
+5 -5
View File
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::rc::Rc;
use mlua::{Lua, Result, UserData};
@@ -16,17 +16,17 @@ fn test_gc_control() -> Result<()> {
assert!(lua.gc_is_running());
}
struct MyUserdata(Arc<()>);
struct MyUserdata(Rc<()>);
impl UserData for MyUserdata {}
let rc = Arc::new(());
let rc = Rc::new(());
globals.set("userdata", lua.create_userdata(MyUserdata(rc.clone()))?)?;
globals.raw_remove("userdata")?;
assert_eq!(Arc::strong_count(&rc), 2);
assert_eq!(Rc::strong_count(&rc), 2);
lua.gc_collect()?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&rc), 1);
assert_eq!(Rc::strong_count(&rc), 1);
Ok(())
}
-231
View File
@@ -1,231 +0,0 @@
use std::cell::Cell;
use std::rc::Rc;
use mlua::{Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods};
#[test]
fn scope_func() -> Result<()> {
let lua = Lua::new();
let rc = Rc::new(Cell::new(0));
lua.scope(|scope| {
let r = rc.clone();
let f = scope.create_function(move |_, ()| {
r.set(42);
Ok(())
})?;
lua.globals().set("bad", f.clone())?;
f.call::<_, ()>(())?;
assert_eq!(Rc::strong_count(&rc), 2);
Ok(())
})?;
assert_eq!(rc.get(), 42);
assert_eq!(Rc::strong_count(&rc), 1);
match lua.globals().get::<_, Function>("bad")?.call::<_, ()>(()) {
Err(Error::CallbackError { .. }) => {}
r => panic!("improper return for destructed function: {:?}", r),
};
Ok(())
}
#[test]
fn scope_drop() -> Result<()> {
let lua = Lua::new();
struct MyUserdata(Rc<()>);
impl UserData for MyUserdata {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("method", |_, _, ()| Ok(()));
}
}
let rc = Rc::new(());
lua.scope(|scope| {
lua.globals().set(
"test",
scope.create_static_userdata(MyUserdata(rc.clone()))?,
)?;
assert_eq!(Rc::strong_count(&rc), 2);
Ok(())
})?;
assert_eq!(Rc::strong_count(&rc), 1);
match lua.load("test:method()").exec() {
Err(Error::CallbackError { .. }) => {}
r => panic!("improper return for destructed userdata: {:?}", r),
};
Ok(())
}
#[test]
fn scope_capture() -> Result<()> {
let lua = Lua::new();
let mut i = 0;
lua.scope(|scope| {
scope
.create_function_mut(|_, ()| {
i = 42;
Ok(())
})?
.call::<_, ()>(())
})?;
assert_eq!(i, 42);
Ok(())
}
#[test]
fn outer_lua_access() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?;
lua.scope(|scope| {
scope
.create_function_mut(|_, ()| table.set("a", "b"))?
.call::<_, ()>(())
})?;
assert_eq!(table.get::<_, String>("a")?, "b");
Ok(())
}
#[test]
fn scope_userdata_methods() -> Result<()> {
struct MyUserData<'a>(&'a Cell<i64>);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
Ok(())
});
methods.add_method("dec", |_, data, ()| {
data.0.set(data.0.get() - 1);
Ok(())
});
}
}
let lua = Lua::new();
let i = Cell::new(42);
let f: Function = lua
.load(
r#"
function(u)
u:inc()
u:inc()
u:inc()
u:dec()
end
"#,
)
.eval()?;
lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&i))?))?;
assert_eq!(i.get(), 44);
Ok(())
}
#[test]
fn scope_userdata_functions() -> Result<()> {
struct MyUserData<'a>(&'a i64);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_function(MetaMethod::Add, |lua, ()| {
let globals = lua.globals();
globals.set("i", globals.get::<_, i64>("i")? + 1)?;
Ok(())
});
methods.add_meta_function(MetaMethod::Sub, |lua, ()| {
let globals = lua.globals();
globals.set("i", globals.get::<_, i64>("i")? + 1)?;
Ok(())
});
}
}
let lua = Lua::new();
let dummy = 0;
let f = lua
.load(
r#"
i = 0
return function(u)
_ = u + u
_ = u - 1
_ = 1 + u
end
"#,
)
.eval::<Function>()?;
lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&dummy))?))?;
assert_eq!(lua.globals().get::<_, i64>("i")?, 3);
Ok(())
}
#[test]
fn scope_userdata_mismatch() -> Result<()> {
struct MyUserData<'a>(&'a Cell<i64>);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
Ok(())
});
}
}
let lua = Lua::new();
lua.load(
r#"
function okay(a, b)
a.inc(a)
b.inc(b)
end
function bad(a, b)
a.inc(b)
end
"#,
)
.exec()?;
let a = Cell::new(1);
let b = Cell::new(1);
let okay: Function = lua.globals().get("okay")?;
let bad: Function = lua.globals().get("bad")?;
lua.scope(|scope| {
let au = scope.create_nonstatic_userdata(MyUserData(&a))?;
let bu = scope.create_nonstatic_userdata(MyUserData(&b))?;
assert!(okay.call::<_, ()>((au.clone(), bu.clone())).is_ok());
match bad.call::<_, ()>((au, bu)) {
Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() {
Error::UserDataTypeMismatch => {}
ref other => panic!("wrong error type {:?}", other),
},
Err(other) => panic!("wrong error type {:?}", other),
Ok(_) => panic!("incorrectly returned Ok"),
}
Ok(())
})?;
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(())
}
+5 -5
View File
@@ -1,6 +1,6 @@
use std::iter::FromIterator;
use std::panic::catch_unwind;
use std::sync::Arc;
use std::rc::Rc;
use std::{error, f32, f64, fmt};
use mlua::{
@@ -584,22 +584,22 @@ fn test_registry_value() -> Result<()> {
#[test]
fn test_drop_registry_value() -> Result<()> {
struct MyUserdata(Arc<()>);
struct MyUserdata(Rc<()>);
impl UserData for MyUserdata {}
let lua = Lua::new();
let rc = Arc::new(());
let rc = Rc::new(());
let r = lua.create_registry_value(MyUserdata(rc.clone()))?;
assert_eq!(Arc::strong_count(&rc), 2);
assert_eq!(Rc::strong_count(&rc), 2);
drop(r);
lua.expire_registry_values();
lua.load(r#"collectgarbage("collect")"#).exec()?;
assert_eq!(Arc::strong_count(&rc), 1);
assert_eq!(Rc::strong_count(&rc), 1);
Ok(())
}
+6 -5
View File
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::rc::Rc;
use mlua::{
AnyUserData, ExternalError, Function, Lua, MetaMethod, Result, String, UserData,
@@ -196,22 +196,22 @@ fn test_gc_userdata() -> Result<()> {
#[test]
fn detroys_userdata() -> Result<()> {
struct MyUserdata(Arc<()>);
struct MyUserdata(Rc<()>);
impl UserData for MyUserdata {}
let rc = Arc::new(());
let rc = Rc::new(());
let lua = Lua::new();
lua.globals().set("userdata", MyUserdata(rc.clone()))?;
assert_eq!(Arc::strong_count(&rc), 2);
assert_eq!(Rc::strong_count(&rc), 2);
// should destroy all objects
let _ = lua.globals().raw_remove("userdata")?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&rc), 1);
assert_eq!(Rc::strong_count(&rc), 1);
Ok(())
}
@@ -219,6 +219,7 @@ fn detroys_userdata() -> Result<()> {
#[test]
fn user_value() -> Result<()> {
struct MyUserData;
impl UserData for MyUserData {}
let lua = Lua::new();