Compare commits

...

9 Commits

Author SHA1 Message Date
Alex Orlenko 0fe898c0dd v0.6.0-beta.3 2021-06-04 12:38:09 +01:00
Alex Orlenko 821f1125b6 Add String::to_string_lossy 2021-06-04 00:16:40 +01:00
Alex Orlenko 6c0096d8ac Update examples 2021-06-03 23:52:29 +01:00
Alex Orlenko abb95c3c56 Remove T: Clone requirement from add_async_function 2021-06-03 23:21:00 +01:00
Alex Orlenko 64faebf407 Add __ipairs metamethod (again) for Lua 5.2 only 2021-06-03 18:43:29 +01:00
Alex Orlenko a944f4ad6f Implement UserData for Rc<RefCell>/Arc<Mutex>/Arc<RwLock> wrappers 2021-06-03 16:42:55 +01:00
Alex Orlenko bae424672a Treat errors as Send + Sync to be compatible with anyhow crate 2021-05-31 11:05:51 +01:00
Alex Orlenko 6f9eb82649 Update README 2021-05-18 20:13:28 +01:00
Alex Orlenko e8de2a458a Allow multiple entrypoints in a single module share the same Lua state.
Previously it would initialize different Lua instances.
Fixes #49.
2021-05-18 20:07:34 +01:00
18 changed files with 488 additions and 145 deletions
+8
View File
@@ -1,3 +1,11 @@
## v0.6.0-beta.3
- Errors are always `Send + Sync` to be compatible with anyhow crate
- Implement `UserData` for `Rc<RefCell>`/`Arc<Mutex>`/`Arc<RwLock>`
- Added `__ipairs` metamethod for Lua 5.2
- Added `String::to_string_lossy`
- Various bugfixes and improvements
## v0.6.0-beta.2
- [**Breaking**] Removed `AnyUserData::has_metamethod()`
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.6.0-beta.2" # remember to update html_root_url and mlua_derive
version = "0.6.0-beta.3" # remember to update html_root_url and mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
+3 -2
View File
@@ -38,6 +38,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
* `send`: make `mlua::Lua` transferable across thread boundaries (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`)
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
* `macros`: enable procedural macros (such as `chunk!`)
[5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html
@@ -96,7 +97,7 @@ Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.5", features = ["lua53", "vendored"] }
mlua = { version = "0.6.0-beta", features = ["lua53", "vendored"] }
```
`main.rs`
@@ -131,7 +132,7 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.5", features = ["lua53", "vendored", "module"] }
mlua = { version = "0.6.0-beta", features = ["lua53", "vendored", "module"] }
```
`lib.rs` :
+8 -9
View File
@@ -1,12 +1,11 @@
use std::collections::HashMap;
use std::sync::Arc;
use bstr::BString;
use hyper::body::{Body as HyperBody, HttpBody as _};
use hyper::Client as HyperClient;
use tokio::sync::Mutex;
use mlua::{Error, Lua, Result, UserData, UserDataMethods};
use mlua::{ExternalResult, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct BodyReader(Arc<Mutex<HyperBody>>);
@@ -19,11 +18,11 @@ impl BodyReader {
impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("read", |_, reader, ()| async move {
methods.add_async_method("read", |lua, reader, ()| async move {
let mut reader = reader.0.lock().await;
if let Some(bytes) = reader.data().await {
let bytes = bytes.map_err(Error::external)?;
return Ok(Some(BString::from(bytes.as_ref())));
let bytes = bytes.to_lua_err()?;
return Some(lua.create_string(&bytes)).transpose();
}
Ok(None)
});
@@ -36,18 +35,18 @@ async fn main() -> Result<()> {
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 uri = uri.parse().to_lua_err()?;
let resp = client.get(uri).await.to_lua_err()?;
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() {
for (key, value) in resp.headers() {
headers
.entry(key.as_str())
.or_insert(Vec::new())
.push(value.to_str().unwrap());
.push(value.to_str().to_lua_err()?);
}
lua_resp.set("headers", headers)?;
+3 -6
View File
@@ -1,4 +1,4 @@
use mlua::{Error, Lua, LuaSerdeExt, Result};
use mlua::{ExternalResult, Lua, LuaSerdeExt, Result};
#[tokio::main]
async fn main() -> Result<()> {
@@ -10,11 +10,8 @@ async fn main() -> Result<()> {
let resp = reqwest::get(&uri)
.await
.and_then(|resp| resp.error_for_status())
.map_err(Error::external)?;
let json = resp
.json::<serde_json::Value>()
.await
.map_err(Error::external)?;
.to_lua_err()?;
let json = resp.json::<serde_json::Value>().await.to_lua_err()?;
lua.to_value(&json)
})?;
globals.set("fetch_json", fetch_json)?;
+5 -7
View File
@@ -1,5 +1,4 @@
use std::net::SocketAddr;
use std::sync::Arc;
use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
@@ -7,13 +6,12 @@ use hyper::{Body, Request, Response, Server};
use mlua::{Error, Function, Lua, Result, Table, UserData, UserDataMethods};
#[derive(Clone)]
struct LuaRequest(Arc<(SocketAddr, Request<Body>)>);
struct LuaRequest(SocketAddr, Request<Body>);
impl UserData for LuaRequest {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("remote_addr", |_lua, req, ()| Ok((req.0).0.to_string()));
methods.add_method("method", |_lua, req, ()| Ok((req.0).1.method().to_string()));
methods.add_method("remote_addr", |_lua, req, ()| Ok((req.0).to_string()));
methods.add_method("method", |_lua, req, ()| Ok((req.1).method().to_string()));
}
}
@@ -25,7 +23,7 @@ async fn run_server(handler: Function<'static>) -> Result<()> {
Ok::<_, Error>(service_fn(move |req: Request<Body>| {
let handler = handler.clone();
async move {
let lua_req = LuaRequest(Arc::new((remote_addr, req)));
let lua_req = LuaRequest(remote_addr, req);
let lua_resp: Table = handler.call_async(lua_req).await?;
let body = lua_resp
.get::<_, Option<String>>("body")?
@@ -72,7 +70,7 @@ async fn main() -> Result<()> {
["X-Req-Method"] = req:method(),
["X-Remote-Addr"] = req:remote_addr(),
},
body = "Hello, World!"
body = "Hello, World!\n"
}
end
"#,
+5 -7
View File
@@ -1,14 +1,12 @@
use std::sync::Arc;
use bstr::BString;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::Mutex;
use tokio::task;
use mlua::{Function, Lua, Result, UserData, UserDataMethods};
use mlua::{Function, Lua, Result, String as LuaString, UserData, UserDataMethods};
#[derive(Clone)]
struct LuaTcp;
#[derive(Clone)]
@@ -41,15 +39,15 @@ impl UserData for LuaTcpStream {
Ok(stream.0.lock().await.peer_addr()?.to_string())
});
methods.add_async_method("read", |_, stream, size: usize| async move {
methods.add_async_method("read", |lua, stream, size: usize| async move {
let mut buf = vec![0; size];
let n = stream.0.lock().await.read(&mut buf).await?;
buf.truncate(n);
Ok(BString::from(buf))
lua.create_string(&buf)
});
methods.add_async_method("write", |_, stream, data: BString| async move {
let n = stream.0.lock().await.write(&data).await?;
methods.add_async_method("write", |_, stream, data: LuaString| async move {
let n = stream.0.lock().await.write(&data.as_bytes()).await?;
Ok(n)
});
+17
View File
@@ -8,10 +8,27 @@ fn used_memory(lua: &Lua, _: ()) -> LuaResult<usize> {
Ok(lua.used_memory())
}
fn check_userdata(_: &Lua, ud: MyUserData) -> LuaResult<i32> {
Ok(ud.0)
}
#[mlua::lua_module]
fn rust_module(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("sum", lua.create_function(sum)?)?;
exports.set("used_memory", lua.create_function(used_memory)?)?;
exports.set("check_userdata", lua.create_function(check_userdata)?)?;
Ok(exports)
}
#[derive(Clone, Copy)]
struct MyUserData(i32);
impl LuaUserData for MyUserData {}
#[mlua::lua_module]
fn rust_module_second(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("userdata", lua.create_userdata(MyUserData(123))?)?;
Ok(exports)
}
-17
View File
@@ -174,10 +174,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.
#[cfg(feature = "send")]
ExternalError(Arc<dyn StdError + Send + Sync>),
#[cfg(not(feature = "send"))]
ExternalError(Arc<dyn StdError>),
}
/// A specialized `Result` type used by `mlua`'s API.
@@ -277,35 +274,21 @@ impl StdError for Error {
}
impl Error {
#[cfg(feature = "send")]
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> Error {
Error::ExternalError(err.into().into())
}
#[cfg(not(feature = "send"))]
pub fn external<T: Into<Box<dyn StdError>>>(err: T) -> Error {
Error::ExternalError(err.into().into())
}
}
pub trait ExternalError {
fn to_lua_err(self) -> Error;
}
#[cfg(feature = "send")]
impl<E: Into<Box<dyn StdError + Send + Sync>>> ExternalError for E {
fn to_lua_err(self) -> Error {
Error::external(self)
}
}
#[cfg(not(feature = "send"))]
impl<E: Into<Box<dyn StdError>>> ExternalError for E {
fn to_lua_err(self) -> Error {
Error::external(self)
}
}
pub trait ExternalResult<T> {
fn to_lua_err(self) -> Result<T>;
}
+1 -1
View File
@@ -174,7 +174,7 @@ pub unsafe extern "C" fn mlua_hook_proc(state: *mut lua_State, ar: *mut lua_Debu
_phantom: PhantomData,
};
let lua = Lua::make_from_ptr(state);
let lua = mlua_expect!(Lua::make_from_ptr(state), "cannot make Lua instance");
let hook_cb = mlua_expect!(lua.hook_callback(), "no hook callback set in hook_proc");
#[allow(clippy::match_wild_err_arm)]
+1 -1
View File
@@ -72,7 +72,7 @@
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// mlua types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/mlua/0.6.0-beta.2")]
#![doc(html_root_url = "https://docs.rs/mlua/0.6.0-beta.3")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
+135 -14
View File
@@ -6,7 +6,7 @@ use std::fmt;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::resume_unwind;
use std::sync::{Arc, Mutex, MutexGuard, Weak};
use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak};
use std::{mem, ptr, str};
use crate::error::{Error, Result};
@@ -32,6 +32,9 @@ use crate::util::{
};
use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
#[cfg(not(feature = "send"))]
use std::rc::Rc;
#[cfg(feature = "async")]
use {
crate::types::AsyncCallback,
@@ -369,6 +372,10 @@ impl Lua {
let main_state = maybe_main_state.unwrap_or(state);
let main_state_top = ffi::lua_gettop(main_state);
if let Some(lua) = Lua::make_from_ptr(state) {
return lua;
}
let ref_thread = mlua_expect!(
(|state| {
// Before initializing the error registry, we must set Error/Panic size.
@@ -437,8 +444,8 @@ impl Lua {
);
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
mlua_expect!(
ffi::safe::lua_rawsetp(main_state, ffi::LUA_REGISTRYINDEX, extra_key,),
"Error while storing extra data"
ffi::safe::lua_rawsetp(main_state, ffi::LUA_REGISTRYINDEX, extra_key),
"Error while storing extra data",
);
mlua_debug_assert!(
@@ -1603,6 +1610,11 @@ impl Lua {
self.push_value(f(self)?)?;
ffi::safe::lua_rawsetfield(self.state, -2, k.validate()?.name())?;
}
// Add special `__mlua_type_id` field
let type_id_ptr =
ffi::safe::lua_newuserdata(self.state, mem::size_of::<TypeId>())? as *mut TypeId;
ptr::write(type_id_ptr, type_id);
ffi::safe::lua_rawsetfield(self.state, -2, "__mlua_type_id")?;
let metatable_index = ffi::lua_absindex(self.state, -1);
let mut extra_tables_count = 0;
@@ -1686,7 +1698,7 @@ impl Lua {
// Pushes a LuaRef value onto the stack, checking that it's a registered
// and not destructed UserData.
// Uses 3 stack spaces, does not call checkstack.
pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef) -> Result<()> {
pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef, with_mt: bool) -> Result<()> {
self.push_ref(lref);
if ffi::lua_getmetatable(self.state, -1) == 0 {
return Err(Error::UserDataTypeMismatch);
@@ -1695,7 +1707,9 @@ impl Lua {
let ptr = ffi::lua_topointer(self.state, -1);
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
if extra.registered_userdata_mt.contains(&(ptr as isize)) {
ffi::lua_pop(self.state, 1);
if !with_mt {
ffi::lua_pop(self.state, 1);
}
return Ok(());
}
// Maybe userdata was destructed?
@@ -1972,12 +1986,14 @@ impl Lua {
Ok(())
}
pub(crate) unsafe fn make_from_ptr(state: *mut ffi::lua_State) -> Self {
pub(crate) unsafe fn make_from_ptr(state: *mut ffi::lua_State) -> Option<Self> {
let _sg = StackGuard::new(state);
assert_stack(state, 1);
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key);
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA {
return None;
}
let extra = mlua_expect!(
(*get_gc_userdata::<Weak<Mutex<ExtraData>>>(state, -1)).upgrade(),
"extra is destroyed"
@@ -1986,14 +2002,14 @@ impl Lua {
let safe = mlua_expect!(extra.lock(), "extra is poisoned").safe;
Lua {
Some(Lua {
state,
main_state: get_main_state(state),
extra,
ephemeral: true,
safe,
_no_ref_unwind_safe: PhantomData,
}
})
}
pub(crate) unsafe fn hook_callback(&self) -> Option<HookCallback> {
@@ -2428,7 +2444,6 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
#[cfg(feature = "async")]
fn add_async_function<S, A, R, F, FR>(&mut self, name: &S, function: F)
where
T: Clone,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
@@ -2482,6 +2497,21 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
self.meta_methods
.push((meta.into(), Self::box_function_mut(function)));
}
// Below are internal methods used in generated code
fn add_callback(&mut self, name: Vec<u8>, callback: Callback<'lua, 'static>) {
self.methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_callback(&mut self, name: Vec<u8>, callback: AsyncCallback<'lua, 'static>) {
self.async_methods.push((name, callback));
}
fn add_meta_callback(&mut self, meta: MetaMethod, callback: Callback<'lua, 'static>) {
self.meta_methods.push((meta, callback));
}
}
impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
@@ -2494,8 +2524,29 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
Box::new(move |lua, mut args| {
if let Some(front) = args.pop_front() {
let userdata = AnyUserData::from_lua(front, lua)?;
let userdata = userdata.borrow::<T>()?;
method(lua, &userdata, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
match userdata.type_id()? {
id if id == TypeId::of::<T>() => {
let ud = userdata.borrow::<T>()?;
method(lua, &ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}
#[cfg(not(feature = "send"))]
id if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = userdata.borrow::<Rc<RefCell<T>>>()?;
let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
method(lua, &ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}
id if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud = userdata.borrow::<Arc<Mutex<T>>>()?;
let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?;
method(lua, &ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}
id if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud = userdata.borrow::<Arc<RwLock<T>>>()?;
let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?;
method(lua, &ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}
_ => Err(Error::UserDataTypeMismatch),
}
} else {
Err(Error::FromLuaConversionError {
from: "missing argument",
@@ -2516,11 +2567,34 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
Box::new(move |lua, mut args| {
if let Some(front) = args.pop_front() {
let userdata = AnyUserData::from_lua(front, lua)?;
let mut userdata = userdata.borrow_mut::<T>()?;
let mut method = method
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
(&mut *method)(lua, &mut userdata, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
match userdata.type_id()? {
id if id == TypeId::of::<T>() => {
let mut ud = userdata.borrow_mut::<T>()?;
method(lua, &mut ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}
#[cfg(not(feature = "send"))]
id if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = userdata.borrow::<Rc<RefCell<T>>>()?;
let mut ud = ud
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
method(lua, &mut ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}
id if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud = userdata.borrow::<Arc<Mutex<T>>>()?;
let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?;
method(lua, &mut ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}
id if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud = userdata.borrow::<Arc<RwLock<T>>>()?;
let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?;
method(lua, &mut ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}
_ => Err(Error::UserDataTypeMismatch),
}
} else {
Err(Error::FromLuaConversionError {
from: "missing argument",
@@ -2703,4 +2777,51 @@ impl<'lua, T: 'static + UserData> UserDataFields<'lua, T> for StaticUserDataFiel
}),
));
}
// Below are internal methods
fn add_field_getter(&mut self, name: Vec<u8>, callback: Callback<'lua, 'static>) {
self.field_getters.push((name, callback));
}
fn add_field_setter(&mut self, name: Vec<u8>, callback: Callback<'lua, 'static>) {
self.field_setters.push((name, callback));
}
}
macro_rules! lua_userdata_impl {
($type:ty) => {
impl<T: 'static + UserData> UserData for $type {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
let mut orig_fields = StaticUserDataFields::default();
T::add_fields(&mut orig_fields);
for (name, callback) in orig_fields.field_getters {
fields.add_field_getter(name, callback);
}
for (name, callback) in orig_fields.field_setters {
fields.add_field_setter(name, callback);
}
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
let mut orig_methods = StaticUserDataMethods::default();
T::add_methods(&mut orig_methods);
for (name, callback) in orig_methods.methods {
methods.add_callback(name, callback);
}
#[cfg(feature = "async")]
for (name, callback) in orig_methods.async_methods {
methods.add_async_callback(name, callback);
}
for (meta, callback) in orig_methods.meta_methods {
methods.add_meta_callback(meta, callback);
}
}
}
};
}
#[cfg(not(feature = "send"))]
lua_userdata_impl!(Rc<RefCell<T>>);
lua_userdata_impl!(Arc<Mutex<T>>);
lua_userdata_impl!(Arc<RwLock<T>>);
+10 -13
View File
@@ -1,8 +1,9 @@
use std::any::Any;
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::cell::{Cell, RefCell};
use std::marker::PhantomData;
use std::mem;
use std::os::raw::{c_int, c_void};
use std::rc::Rc;
#[cfg(feature = "serialize")]
use serde::Serialize;
@@ -238,7 +239,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
where
T: 'scope + UserData,
{
let data = UserDataCell::new_arc(data);
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
@@ -247,7 +248,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// parameters.
fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
scope: &Scope<'lua, 'scope>,
data: UserDataCell<T>,
data: Rc<RefCell<T>>,
data_ptr: *mut c_void,
method: NonStaticMethod<'callback, T>,
) -> Result<Function<'lua>> {
@@ -263,7 +264,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&ud.0)?;
lua.push_userdata_ref(&ud.0, false)?;
if get_userdata(lua.state, -1) == data_ptr {
return Ok(());
}
@@ -276,10 +277,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
NonStaticMethod::Method(method) => {
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
check_ud_type(lua, args.pop_front())?;
let data = data
.try_borrow()
.map(|cell| Ref::map(cell, AsRef::as_ref))
.map_err(|_| Error::UserDataBorrowError)?;
let data = data.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
method(lua, &*data, args)
});
unsafe { scope.create_callback(f) }
@@ -293,7 +291,6 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
.map_err(|_| Error::RecursiveMutCallback)?;
let mut data = data
.try_borrow_mut()
.map(|cell| RefMut::map(cell, AsMut::as_mut))
.map_err(|_| Error::UserDataBorrowMutError)?;
(&mut *method)(lua, &mut *data, args)
});
@@ -324,7 +321,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 13)?;
push_userdata(lua.state, data.clone())?;
push_userdata(lua.state, UserDataCell::new(data.clone()))?;
let data_ptr = ffi::lua_touserdata(lua.state, -1);
// Prepare metatable, add meta methods first and then meta fields
@@ -379,7 +376,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
methods_index = Some(ffi::lua_absindex(lua.state, -1));
}
init_userdata_metatable::<()>(
init_userdata_metatable::<UserDataCell<Rc<RefCell<T>>>>(
lua.state,
metatable_index,
field_getters_index,
@@ -427,7 +424,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
mem::transmute(f)
}
vec![Box::new(seal(take_userdata::<UserDataCell<T>>(state)))]
let ud = Box::new(seal(take_userdata::<UserDataCell<Rc<RefCell<T>>>>(state)));
vec![ud]
});
self.destructors
.borrow_mut()
@@ -665,7 +663,6 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
#[cfg(feature = "async")]
fn add_async_function<S, A, R, F, FR>(&mut self, _name: &S, _function: F)
where
T: Clone,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
+24
View File
@@ -1,3 +1,5 @@
use std::borrow::Cow;
use std::string::String as StdString;
use std::{slice, str};
#[cfg(feature = "serialize")]
@@ -44,6 +46,28 @@ impl<'lua> String<'lua> {
})
}
/// Converts this string to a [`Cow<str>`].
///
/// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
///
/// [U+FFFD]: std::char::REPLACEMENT_CHARACTER
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, Result};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
///
/// let s = lua.create_string(b"test\xff")?;
/// assert_eq!(s.to_string_lossy(), "test\u{fffd}");
/// # Ok(())
/// # }
/// ```
pub fn to_string_lossy(&self) -> Cow<'_, str> {
StdString::from_utf8_lossy(self.as_bytes())
}
/// Get the bytes that make up this string.
///
/// The returned slice will not contain the terminating nul byte, but will contain any nul
+196 -66
View File
@@ -1,9 +1,9 @@
use std::any::TypeId;
use std::cell::{Ref, RefCell, RefMut};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::ops::{Deref, DerefMut};
use std::string::String as StdString;
use std::sync::Arc;
#[cfg(feature = "async")]
use std::future::Future;
@@ -19,13 +19,16 @@ use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::table::{Table, TablePairs};
use crate::types::{LuaRef, MaybeSend};
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::util::{check_stack, get_destructed_userdata_metatable, get_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
use crate::value::Value;
#[cfg(feature = "async")]
use crate::types::AsyncCallback;
/// Kinds of metamethods that can be overridden.
///
/// Currently, this mechanism does not allow overriding the `__gc` metamethod, since there is
@@ -101,6 +104,15 @@ pub enum MetaMethod {
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
Pairs,
/// The `__ipairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in [`ipairs`] function.
///
/// Requires `feature = "lua52"`
///
/// [`ipairs`]: https://www.lua.org/manual/5.2/manual.html#pdf-ipairs
#[cfg(any(feature = "lua52", doc))]
IPairs,
/// The `__close` metamethod.
///
/// Executed when a variable, that marked as to-be-closed, goes out of scope.
@@ -178,6 +190,8 @@ impl MetaMethod {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
MetaMethod::Pairs => "__pairs",
#[cfg(feature = "lua52")]
MetaMethod::IPairs => "__ipairs",
#[cfg(feature = "lua54")]
MetaMethod::Close => "__close",
@@ -238,6 +252,8 @@ impl From<StdString> for MetaMethod {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
"__pairs" => MetaMethod::Pairs,
#[cfg(feature = "lua52")]
"__ipairs" => MetaMethod::IPairs,
#[cfg(feature = "lua54")]
"__close" => MetaMethod::Close,
@@ -343,7 +359,6 @@ pub trait UserDataMethods<'lua, T: UserData> {
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_function<S, A, R, F, FR>(&mut self, name: &S, function: F)
where
T: Clone,
S: AsRef<[u8]> + ?Sized,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
@@ -403,6 +418,20 @@ pub trait UserDataMethods<'lua, T: UserData> {
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>;
//
// Below are internal methods used in generated code
//
#[doc(hidden)]
fn add_callback(&mut self, _name: Vec<u8>, _callback: Callback<'lua, 'static>) {}
#[doc(hidden)]
#[cfg(feature = "async")]
fn add_async_callback(&mut self, _name: Vec<u8>, _callback: AsyncCallback<'lua, 'static>) {}
#[doc(hidden)]
fn add_meta_callback(&mut self, _meta: MetaMethod, _callback: Callback<'lua, 'static>) {}
}
/// Field registry for [`UserData`] implementors.
@@ -474,6 +503,16 @@ pub trait UserDataFields<'lua, T: UserData> {
S: Into<MetaMethod>,
F: 'static + MaybeSend + Fn(&'lua Lua) -> Result<R>,
R: ToLua<'lua>;
//
// Below are internal methods used in generated code
//
#[doc(hidden)]
fn add_field_getter(&mut self, _name: Vec<u8>, _callback: Callback<'lua, 'static>) {}
#[doc(hidden)]
fn add_field_setter(&mut self, _name: Vec<u8>, _callback: Callback<'lua, 'static>) {}
}
/// Trait for custom userdata types.
@@ -550,26 +589,11 @@ pub trait UserData: Sized {
}
// Wraps UserData in a way to always implement `serde::Serialize` trait.
pub(crate) enum UserDataCell<T> {
Arc(Arc<RefCell<UserDataWrapped<T>>>),
Plain(RefCell<UserDataWrapped<T>>),
}
pub(crate) struct UserDataCell<T>(RefCell<UserDataWrapped<T>>);
impl<T> UserDataCell<T> {
pub(crate) fn new(data: T) -> Self {
UserDataCell::Plain(RefCell::new(UserDataWrapped {
data: Box::into_raw(Box::new(data)),
#[cfg(feature = "serialize")]
ser: Box::into_raw(Box::new(UserDataSerializeError)),
}))
}
pub(crate) fn new_arc(data: T) -> Self {
UserDataCell::Arc(Arc::new(RefCell::new(UserDataWrapped {
data: Box::into_raw(Box::new(data)),
#[cfg(feature = "serialize")]
ser: Box::into_raw(Box::new(UserDataSerializeError)),
})))
UserDataCell(RefCell::new(UserDataWrapped::new(data)))
}
#[cfg(feature = "serialize")]
@@ -577,40 +601,135 @@ impl<T> UserDataCell<T> {
where
T: 'static + Serialize,
{
let data_raw = Box::into_raw(Box::new(data));
UserDataCell::Plain(RefCell::new(UserDataWrapped {
data: data_raw,
ser: data_raw,
}))
UserDataCell(RefCell::new(UserDataWrapped::new_ser(data)))
}
// Immutably borrows the wrapped value.
fn try_borrow(&self) -> Result<UserDataRef<T>> {
self.0
.try_borrow()
.map(|r| UserDataRef(UserDataRefInner::Ref(r)))
.map_err(|_| Error::UserDataBorrowError)
}
// Mutably borrows the wrapped value.
fn try_borrow_mut(&self) -> Result<UserDataRefMut<T>> {
self.0
.try_borrow_mut()
.map(|r| UserDataRefMut(UserDataRefMutInner::Ref(r)))
.map_err(|_| Error::UserDataBorrowMutError)
}
}
impl<T> Deref for UserDataCell<T> {
type Target = RefCell<UserDataWrapped<T>>;
#[cfg(feature = "serialize")]
impl Serialize for UserDataCell<()> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
let ser = self
.0
.try_borrow()
.map_err(|_| ser::Error::custom(Error::UserDataBorrowError))?
.ser;
unsafe { (&*ser).serialize(serializer) }
}
}
/// A wrapper type for an immutably borrowed value from an `AnyUserData`.
pub struct UserDataRef<'a, T>(UserDataRefInner<'a, T>);
enum UserDataRefInner<'a, T> {
Ref(Ref<'a, UserDataWrapped<T>>),
}
/// A wrapper type for a mutably borrowed value from an `AnyUserData`.
pub struct UserDataRefMut<'a, T>(UserDataRefMutInner<'a, T>);
enum UserDataRefMutInner<'a, T> {
Ref(RefMut<'a, UserDataWrapped<T>>),
}
impl<T> Deref for UserDataRef<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
UserDataCell::Arc(t) => &*t,
UserDataCell::Plain(t) => &*t,
match &self.0 {
UserDataRefInner::Ref(x) => &*x,
}
}
}
impl<T> Clone for UserDataCell<T> {
fn clone(&self) -> Self {
match self {
UserDataCell::Arc(t) => UserDataCell::Arc(t.clone()),
UserDataCell::Plain(_) => mlua_panic!("cannot clone non-arc userdata"),
impl<T> Deref for UserDataRefMut<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match &self.0 {
UserDataRefMutInner::Ref(x) => &*x,
}
}
}
impl<T> DerefMut for UserDataRefMut<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match &mut self.0 {
UserDataRefMutInner::Ref(x) => &mut *x,
}
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&*self as &T, f)
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&*self as &T, f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self as &T, f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self as &T, f)
}
}
pub(crate) struct UserDataWrapped<T> {
pub(crate) data: *mut T,
#[cfg(feature = "serialize")]
ser: *mut dyn erased_serde::Serialize,
}
impl<T> UserDataWrapped<T> {
fn new(data: T) -> Self {
UserDataWrapped {
data: Box::into_raw(Box::new(data)),
#[cfg(feature = "serialize")]
ser: Box::into_raw(Box::new(UserDataSerializeError)),
}
}
#[cfg(feature = "serialize")]
fn new_ser(data: T) -> Self
where
T: 'static + Serialize,
{
let data_raw = Box::into_raw(Box::new(data));
UserDataWrapped {
data: data_raw,
ser: data_raw,
}
}
}
impl<T> Drop for UserDataWrapped<T> {
fn drop(&mut self) {
unsafe {
@@ -623,20 +742,22 @@ impl<T> Drop for UserDataWrapped<T> {
}
}
impl<T> AsRef<T> for UserDataWrapped<T> {
fn as_ref(&self) -> &T {
impl<T> Deref for UserDataWrapped<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.data }
}
}
impl<T> AsMut<T> for UserDataWrapped<T> {
fn as_mut(&mut self) -> &mut T {
impl<T> DerefMut for UserDataWrapped<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.data }
}
}
#[cfg(feature = "serialize")]
pub(crate) struct UserDataSerializeError;
struct UserDataSerializeError;
#[cfg(feature = "serialize")]
impl Serialize for UserDataSerializeError {
@@ -683,26 +804,18 @@ impl<'lua> AnyUserData<'lua> {
///
/// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow<T: 'static + UserData>(&self) -> Result<Ref<T>> {
self.inspect(|cell| {
let cell_ref = cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
Ok(Ref::map(cell_ref, |x| unsafe { &*x.data }))
})
pub fn borrow<T: 'static + UserData>(&self) -> Result<UserDataRef<T>> {
self.inspect(|cell| cell.try_borrow())
}
/// Borrow this userdata mutably if it is of type `T`.
///
/// # Errors
///
/// Returns a `UserDataBorrowMutError` if the userdata is already borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow_mut<T: 'static + UserData>(&self) -> Result<RefMut<T>> {
self.inspect(|cell| {
let cell_ref = cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
Ok(RefMut::map(cell_ref, |x| unsafe { &mut *x.data }))
})
/// Returns a `UserDataBorrowMutError` if the userdata cannot be mutably borrowed.
/// Returns a `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow_mut<T: 'static + UserData>(&self) -> Result<UserDataRefMut<T>> {
self.inspect(|cell| cell.try_borrow_mut())
}
/// Sets an associated value to this `AnyUserData`.
@@ -726,7 +839,7 @@ impl<'lua> AnyUserData<'lua> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0)?;
lua.push_userdata_ref(&self.0, false)?;
lua.push_value(v)?;
ffi::lua_setuservalue(lua.state, -2);
@@ -745,7 +858,7 @@ impl<'lua> AnyUserData<'lua> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0)?;
lua.push_userdata_ref(&self.0, false)?;
ffi::lua_getuservalue(lua.state, -1);
lua.pop_value()
};
@@ -776,7 +889,7 @@ impl<'lua> AnyUserData<'lua> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0)?;
lua.push_userdata_ref(&self.0, false)?;
ffi::lua_getmetatable(lua.state, -1); // Checked that non-empty on the previous call
Ok(Table(lua.pop_ref()))
}
@@ -803,6 +916,25 @@ impl<'lua> AnyUserData<'lua> {
Ok(false)
}
pub(crate) fn type_id(&self) -> Result<TypeId> {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
// Push userdata with metatable
lua.push_userdata_ref(&self.0, true)?;
// Get the special `__mlua_type_id`
ffi::safe::lua_pushstring(lua.state, "__mlua_type_id")?;
if ffi::lua_rawget(lua.state, -2) != ffi::LUA_TUSERDATA {
return Err(Error::UserDataTypeMismatch);
}
Ok(*(ffi::lua_touserdata(lua.state, -1) as *const TypeId))
}
}
fn inspect<'a, T, R, F>(&'a self, func: F) -> Result<R>
where
T: 'static + UserData,
@@ -928,17 +1060,15 @@ impl<'lua> Serialize for AnyUserData<'lua> {
where
S: Serializer,
{
let res = (|| unsafe {
unsafe {
let lua = self.0.lua;
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
check_stack(lua.state, 3).map_err(ser::Error::custom)?;
lua.push_userdata_ref(&self.0)?;
lua.push_userdata_ref(&self.0, false)
.map_err(ser::Error::custom)?;
let ud = &*get_userdata::<UserDataCell<()>>(lua.state, -1);
(*ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?.ser)
.serialize(serializer)
.map_err(|err| Error::SerializeError(err.to_string()))
})();
res.map_err(ser::Error::custom)
ud.serialize(serializer)
}
}
}
+13
View File
@@ -15,6 +15,19 @@ fn test_module() -> Result<()> {
.exec()
}
#[test]
fn test_module_multi() -> Result<()> {
let lua = make_lua()?;
lua.load(
r#"
local mod = require("rust_module")
local mod2 = require("rust_module.second")
assert(mod.check_userdata(mod2.userdata) == 123)
"#,
)
.exec()
}
#[cfg(any(
feature = "lua54",
feature = "lua53",
+4
View File
@@ -39,6 +39,10 @@ fn string_views() -> Result<()> {
let empty: String = globals.get("empty")?;
assert_eq!(ok.to_str()?, "null bytes are valid utf-8, wh\0 knew?");
assert_eq!(
ok.to_string_lossy(),
"null bytes are valid utf-8, wh\0 knew?"
);
assert_eq!(
ok.as_bytes(),
&b"null bytes are valid utf-8, wh\0 knew?"[..]
+54 -1
View File
@@ -1,4 +1,7 @@
use std::sync::Arc;
use std::sync::{Arc, Mutex, RwLock};
#[cfg(not(feature = "send"))]
use std::{cell::RefCell, rc::Rc};
#[cfg(feature = "lua54")]
use std::sync::atomic::{AtomicI64, Ordering};
@@ -451,3 +454,53 @@ fn test_metatable() -> Result<()> {
Ok(())
}
#[test]
fn test_userdata_wrapped() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("data", |_, this| Ok(this.0));
fields.add_field_method_set("data", |_, this, val| {
this.0 = val;
Ok(())
})
}
}
let lua = Lua::new();
let globals = lua.globals();
#[cfg(not(feature = "send"))]
{
globals.set("rc_refcell_ud", Rc::new(RefCell::new(MyUserData(1))))?;
lua.load(
r#"
rc_refcell_ud.data = rc_refcell_ud.data + 1
assert(rc_refcell_ud.data == 2)
"#,
)
.exec()?;
}
globals.set("arc_mutex_ud", Arc::new(Mutex::new(MyUserData(2))))?;
lua.load(
r#"
arc_mutex_ud.data = arc_mutex_ud.data + 1
assert(arc_mutex_ud.data == 3)
"#,
)
.exec()?;
globals.set("arc_rwlock_ud", Arc::new(RwLock::new(MyUserData(3))))?;
lua.load(
r#"
arc_rwlock_ud.data = arc_rwlock_ud.data + 1
assert(arc_rwlock_ud.data == 4)
"#,
)
.exec()?;
Ok(())
}