mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b46b476f80 | |||
| 70e16b51ae | |||
| 9596b97faa | |||
| 8cbb3d8fae | |||
| e7f494530f | |||
| 3746c3614f | |||
| 9af1aaf889 | |||
| c20eb20a59 | |||
| 04ba93137c | |||
| 113f91ace3 | |||
| ff0d923aae |
@@ -1,3 +1,9 @@
|
||||
## v0.8.1
|
||||
|
||||
- Added `Lua::create_proxy` for accessing to UserData static fields and functions without instance
|
||||
- Added `Table::to_pointer()` and `String::to_pointer()` functions
|
||||
- Bugfixes and improvements (#176 #179)
|
||||
|
||||
## v0.8.0
|
||||
Changes since 0.7.4
|
||||
- Roblox Luau support
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.8.0" # remember to update html_root_url and mlua_derive
|
||||
version = "0.8.1" # remember to update html_root_url and mlua_derive
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
|
||||
edition = "2021"
|
||||
repository = "https://github.com/khvzak/mlua"
|
||||
|
||||
+1
-1
@@ -484,7 +484,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
|
||||
return ChunkMode::Binary;
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
if *source.get(0).unwrap_or(&u8::MAX) < b'\n' {
|
||||
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
|
||||
return ChunkMode::Binary;
|
||||
}
|
||||
ChunkMode::Text
|
||||
|
||||
+1
-1
@@ -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.8.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/mlua/0.8.1")]
|
||||
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
|
||||
// warnings at all.
|
||||
#![doc(test(attr(deny(warnings))))]
|
||||
|
||||
+56
-8
@@ -29,7 +29,7 @@ use crate::types::{
|
||||
Number, RegistryKey,
|
||||
};
|
||||
use crate::userdata::{AnyUserData, UserData, UserDataCell};
|
||||
use crate::userdata_impl::{StaticUserDataFields, StaticUserDataMethods};
|
||||
use crate::userdata_impl::{StaticUserDataFields, StaticUserDataMethods, UserDataProxy};
|
||||
use crate::util::{
|
||||
self, assert_stack, callback_error, check_stack, get_destructed_userdata_metatable,
|
||||
get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry,
|
||||
@@ -126,7 +126,7 @@ pub(crate) struct ExtraData {
|
||||
sandboxed: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(any(feature = "lua51", feature = "luajit"), allow(dead_code))]
|
||||
#[derive(Default)]
|
||||
struct MemoryInfo {
|
||||
used_memory: isize,
|
||||
memory_limit: isize,
|
||||
@@ -430,12 +430,16 @@ impl Lua {
|
||||
new_ptr
|
||||
}
|
||||
|
||||
let mem_info = Box::into_raw(Box::new(MemoryInfo {
|
||||
used_memory: 0,
|
||||
memory_limit: 0,
|
||||
}));
|
||||
// Skip Rust allocator for non-vendored LuaJIT (see https://github.com/khvzak/mlua/issues/176)
|
||||
let use_rust_allocator = !(cfg!(feature = "luajit") && cfg!(not(feature = "vendored")));
|
||||
|
||||
let state = ffi::lua_newstate(allocator, mem_info as *mut c_void);
|
||||
let (state, mem_info) = if use_rust_allocator {
|
||||
let mem_info = Box::into_raw(Box::new(MemoryInfo::default()));
|
||||
let state = ffi::lua_newstate(allocator, mem_info as *mut c_void);
|
||||
(state, mem_info)
|
||||
} else {
|
||||
(ffi::luaL_newstate(), ptr::null_mut())
|
||||
};
|
||||
|
||||
ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1);
|
||||
ffi::lua_pop(state, 1);
|
||||
@@ -1717,6 +1721,9 @@ impl Lua {
|
||||
}
|
||||
|
||||
/// Create a Lua userdata object from a custom userdata type.
|
||||
///
|
||||
/// All userdata instances of type `T` shares the same metatable.
|
||||
#[inline]
|
||||
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
|
||||
where
|
||||
T: 'static + MaybeSend + UserData,
|
||||
@@ -1729,6 +1736,7 @@ impl Lua {
|
||||
/// Requires `feature = "serialize"`
|
||||
#[cfg(feature = "serialize")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
|
||||
#[inline]
|
||||
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
|
||||
where
|
||||
T: 'static + MaybeSend + UserData + Serialize,
|
||||
@@ -1736,6 +1744,46 @@ impl Lua {
|
||||
unsafe { self.make_userdata(UserDataCell::new_ser(data)) }
|
||||
}
|
||||
|
||||
/// Create a Lua userdata "proxy" object from a custom userdata type.
|
||||
///
|
||||
/// Proxy object is an empty userdata object that has `T` metatable attached.
|
||||
/// The main purpose of this object is to provide access to static fields and functions
|
||||
/// without creating an instance of type `T`.
|
||||
///
|
||||
/// You can get or set uservalues on this object but you cannot borrow any Rust type.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use mlua::{Lua, Result, UserData, UserDataFields, UserDataMethods};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// # let lua = Lua::new();
|
||||
/// struct MyUserData(i32);
|
||||
///
|
||||
/// impl UserData for MyUserData {
|
||||
/// fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
/// fields.add_field_method_get("val", |_, this| Ok(this.0));
|
||||
/// }
|
||||
///
|
||||
/// fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
/// methods.add_function("new", |_, value: i32| Ok(MyUserData(value)));
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// lua.globals().set("MyUserData", lua.create_proxy::<MyUserData>()?)?;
|
||||
///
|
||||
/// lua.load("assert(MyUserData.new(321).val == 321)").exec()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn create_proxy<T>(&self) -> Result<AnyUserData>
|
||||
where
|
||||
T: 'static + UserData,
|
||||
{
|
||||
unsafe { self.make_userdata(UserDataCell::new(UserDataProxy::<T>(PhantomData))) }
|
||||
}
|
||||
|
||||
/// Returns a handle to the global environment.
|
||||
pub fn globals(&self) -> Table {
|
||||
unsafe {
|
||||
@@ -2866,7 +2914,7 @@ impl Lua {
|
||||
let _sg = StackGuard::new(state);
|
||||
assert_stack(state, 1);
|
||||
let extra = extra_data(state)?;
|
||||
let inner = &*(*extra.get()).inner.as_ref().unwrap();
|
||||
let inner = (*extra.get()).inner.as_ref().unwrap();
|
||||
Some(Lua(Arc::clone(inner)))
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -85,10 +85,12 @@ fn lua_require(lua: &Lua, name: Option<std::string::String>) -> Result<Value> {
|
||||
search_path = "?.luau;?.lua".into();
|
||||
}
|
||||
|
||||
let mut source = None;
|
||||
let (mut source, mut source_name) = (None, String::new());
|
||||
for path in search_path.split(';') {
|
||||
if let Ok(buf) = std::fs::read(path.replacen('?', &name, 1)) {
|
||||
let file_path = path.replacen('?', &name, 1);
|
||||
if let Ok(buf) = std::fs::read(&file_path) {
|
||||
source = Some(buf);
|
||||
source_name = file_path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -96,7 +98,7 @@ fn lua_require(lua: &Lua, name: Option<std::string::String>) -> Result<Value> {
|
||||
|
||||
let value = lua
|
||||
.load(&source)
|
||||
.set_name(&format!("={}", name))?
|
||||
.set_name(&format!("={}", source_name))?
|
||||
.set_mode(ChunkMode::Text)
|
||||
.call::<_, Value>(())?;
|
||||
|
||||
|
||||
@@ -597,6 +597,7 @@ impl<'lua, 'scope> Drop for Scope<'lua, 'scope> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
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>>>),
|
||||
|
||||
+2
-7
@@ -8,7 +8,6 @@ use rustc_hash::FxHashSet;
|
||||
use serde::de::{self, IntoDeserializer};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ffi;
|
||||
use crate::table::{Table, TablePairs, TableSequence};
|
||||
use crate::value::Value;
|
||||
|
||||
@@ -563,9 +562,7 @@ impl RecursionGuard {
|
||||
#[inline]
|
||||
fn new(table: &Table, visited: &Rc<RefCell<FxHashSet<*const c_void>>>) -> Self {
|
||||
let visited = Rc::clone(visited);
|
||||
let lua = table.0.lua;
|
||||
let ptr =
|
||||
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, table.0.index)) };
|
||||
let ptr = table.to_pointer();
|
||||
visited.borrow_mut().insert(ptr);
|
||||
RecursionGuard { ptr, visited }
|
||||
}
|
||||
@@ -585,9 +582,7 @@ fn check_value_if_skip(
|
||||
) -> Result<bool> {
|
||||
match value {
|
||||
Value::Table(table) => {
|
||||
let lua = table.0.lua;
|
||||
let ptr =
|
||||
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, table.0.index)) };
|
||||
let ptr = table.to_pointer();
|
||||
if visited.borrow().contains(&ptr) {
|
||||
if options.deny_recursive_tables {
|
||||
return Err(de::Error::custom("recursive table detected"));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::os::raw::c_void;
|
||||
use std::string::String as StdString;
|
||||
use std::{slice, str};
|
||||
|
||||
@@ -112,6 +113,17 @@ impl<'lua> String<'lua> {
|
||||
slice::from_raw_parts(data as *const u8, size + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the string to a generic C pointer.
|
||||
///
|
||||
/// There is no way to convert the pointer back to its original value.
|
||||
///
|
||||
/// Typically this function is used only for hashing and debug information.
|
||||
#[inline]
|
||||
pub fn to_pointer(&self) -> *const c_void {
|
||||
let lua = self.0.lua;
|
||||
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> AsRef<[u8]> for String<'lua> {
|
||||
|
||||
+15
-3
@@ -1,10 +1,11 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
use {
|
||||
rustc_hash::FxHashSet,
|
||||
serde::ser::{self, Serialize, SerializeMap, SerializeSeq, Serializer},
|
||||
std::{cell::RefCell, os::raw::c_void, result::Result as StdResult},
|
||||
std::{cell::RefCell, result::Result as StdResult},
|
||||
};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
@@ -382,6 +383,18 @@ impl<'lua> Table<'lua> {
|
||||
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_getreadonly(refthr, self.0.index) != 0) }
|
||||
}
|
||||
|
||||
/// Converts the table to a generic C pointer.
|
||||
///
|
||||
/// Different tables will give different pointers.
|
||||
/// There is no way to convert the pointer back to its original value.
|
||||
///
|
||||
/// Typically this function is used only for hashing and debug information.
|
||||
#[inline]
|
||||
pub fn to_pointer(&self) -> *const c_void {
|
||||
let lua = self.0.lua;
|
||||
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) }
|
||||
}
|
||||
|
||||
/// Consume this table and return an iterator over the pairs of the table.
|
||||
///
|
||||
/// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod.
|
||||
@@ -699,8 +712,7 @@ impl<'lua> Serialize for Table<'lua> {
|
||||
static VISITED: RefCell<FxHashSet<*const c_void>> = RefCell::new(FxHashSet::default());
|
||||
}
|
||||
|
||||
let lua = self.0.lua;
|
||||
let ptr = unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) };
|
||||
let ptr = self.to_pointer();
|
||||
let res = VISITED.with(|visited| {
|
||||
{
|
||||
let mut visited = visited.borrow_mut();
|
||||
|
||||
@@ -622,3 +622,8 @@ lua_userdata_impl!(Arc<RwLock<T>>);
|
||||
lua_userdata_impl!(Arc<parking_lot::Mutex<T>>);
|
||||
#[cfg(feature = "parking_lot")]
|
||||
lua_userdata_impl!(Arc<parking_lot::RwLock<T>>);
|
||||
|
||||
// A special proxy object for UserData
|
||||
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
|
||||
|
||||
lua_userdata_impl!(UserDataProxy<T>);
|
||||
|
||||
+18
-7
@@ -1,4 +1,5 @@
|
||||
use std::iter::{self, FromIterator};
|
||||
use std::ops::Index;
|
||||
use std::os::raw::c_void;
|
||||
use std::{ptr, slice, str, vec};
|
||||
|
||||
@@ -103,13 +104,14 @@ impl<'lua> Value<'lua> {
|
||||
/// There is no way to convert the pointer back to its original value.
|
||||
///
|
||||
/// Typically this function is used only for hashing and debug information.
|
||||
#[inline]
|
||||
pub fn to_pointer(&self) -> *const c_void {
|
||||
unsafe {
|
||||
match self {
|
||||
Value::LightUserData(ud) => ud.0,
|
||||
Value::String(String(v))
|
||||
| Value::Table(Table(v))
|
||||
| Value::Function(Function(v))
|
||||
Value::Table(t) => t.to_pointer(),
|
||||
Value::String(s) => s.to_pointer(),
|
||||
Value::Function(Function(v))
|
||||
| Value::Thread(Thread(v))
|
||||
| Value::UserData(AnyUserData(v)) => v
|
||||
.lua
|
||||
@@ -241,6 +243,15 @@ impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> Index<usize> for MultiValue<'lua> {
|
||||
type Output = Value<'lua>;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[self.0.len() - index - 1]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> MultiValue<'lua> {
|
||||
#[inline]
|
||||
pub fn from_vec(mut v: Vec<Value<'lua>>) -> MultiValue<'lua> {
|
||||
@@ -261,13 +272,13 @@ impl<'lua> MultiValue<'lua> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push_front(&mut self, value: Value<'lua>) {
|
||||
self.0.push(value);
|
||||
pub fn pop_front(&mut self) -> Option<Value<'lua>> {
|
||||
self.0.pop()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn pop_front(&mut self) -> Option<Value<'lua>> {
|
||||
self.0.pop()
|
||||
pub fn push_front(&mut self, value: Value<'lua>) {
|
||||
self.0.push(value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
+10
-4
@@ -15,8 +15,11 @@ fn test_require() -> Result<()> {
|
||||
fs::write(
|
||||
temp_dir.path().join("module.luau"),
|
||||
r#"
|
||||
counter = counter or 0
|
||||
return counter + 1
|
||||
counter = (counter or 0) + 1
|
||||
return {
|
||||
counter = counter,
|
||||
error = function() error("test") end,
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
@@ -24,9 +27,12 @@ fn test_require() -> Result<()> {
|
||||
lua.load(
|
||||
r#"
|
||||
local module = require("module")
|
||||
assert(module == 1)
|
||||
assert(module.counter == 1)
|
||||
module = require("module")
|
||||
assert(module == 1)
|
||||
assert(module.counter == 1)
|
||||
|
||||
local ok, err = pcall(module.error)
|
||||
assert(not ok and string.find(err, "module.luau") ~= nil)
|
||||
"#,
|
||||
)
|
||||
.exec()
|
||||
|
||||
@@ -656,3 +656,44 @@ fn test_userdata_wrapped() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_userdata_proxy() -> Result<()> {
|
||||
struct MyUserData(i64);
|
||||
|
||||
impl UserData for MyUserData {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_function_get("static_field", |_, _| Ok(123));
|
||||
fields.add_field_method_get("n", |_, this| Ok(this.0));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("new", |_, n| Ok(Self(n)));
|
||||
|
||||
methods.add_method("plus", |_, this, n: i64| Ok(this.0 + n));
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
globals.set("MyUserData", lua.create_proxy::<MyUserData>()?)?;
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
assert(MyUserData.static_field == 123)
|
||||
local data = MyUserData.new(321)
|
||||
assert(data.static_field == 123)
|
||||
assert(data.n == 321)
|
||||
assert(data:plus(1) == 322)
|
||||
|
||||
-- Error when accessing the proxy object fields and methods that require instance
|
||||
|
||||
local ok = pcall(function() return MyUserData.n end)
|
||||
assert(not ok)
|
||||
|
||||
ok = pcall(function() return MyUserData:plus(1) end)
|
||||
assert(not ok)
|
||||
"#,
|
||||
)
|
||||
.exec()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user