mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 13ff0ca798 | |||
| 44f49e35d6 | |||
| e1ee4058a6 | |||
| f06d0020ea | |||
| d399559d30 | |||
| 30735d5ff1 | |||
| 75c23e5853 | |||
| 347856b806 | |||
| 774a63bece | |||
| c481c87eac | |||
| 85b280a9d6 | |||
| db7b782d3c | |||
| 5f38445558 | |||
| df0a44d405 | |||
| f0806a6d62 |
@@ -1,3 +1,12 @@
|
||||
## v0.11.3 (Aug 30, 2025)
|
||||
|
||||
- Add `Lua::yield_with` to use as `coroutine.yield` functional replacement in async functions for any Lua
|
||||
- Do not try to yield at non-yielable points in Luau interrupt (#632)
|
||||
- Add `Buffer::cursor` method (Luau)
|
||||
- Add `Lua::create_buffer_with_capacity` method (Luau)
|
||||
- Make Lua reference values cheap to clone (only increments ref count)
|
||||
- Fix panic on large (>67M entries) table creation
|
||||
|
||||
## v0.11.2 (Aug 10, 2025)
|
||||
|
||||
- Faster stack push for `Variadic<T>`
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.11.2" # remember to update mlua_derive
|
||||
version = "0.11.3" # remember to update mlua_derive
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
|
||||
rust-version = "1.79.0"
|
||||
edition = "2021"
|
||||
|
||||
@@ -128,6 +128,22 @@ fn table_traversal_sequence(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn table_ref_clone(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
let t = lua.create_table().unwrap();
|
||||
|
||||
c.bench_function("table [ref clone]", |b| {
|
||||
b.iter_batched(
|
||||
|| collect_gc_twice(&lua),
|
||||
|_| {
|
||||
let _t2 = t.clone();
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn function_create(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
@@ -350,6 +366,39 @@ fn userdata_call_method(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
// A userdata method call that goes through an implicit `__index` function
|
||||
fn userdata_call_method_complex(c: &mut Criterion) {
|
||||
struct UserData(u64);
|
||||
impl LuaUserData for UserData {
|
||||
fn register(registry: &mut LuaUserDataRegistry<Self>) {
|
||||
registry.add_field_method_get("val", |_, this| Ok(this.0));
|
||||
registry.add_method_mut("inc_by", |_, this, by: u64| {
|
||||
this.0 += by;
|
||||
Ok(this.0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
let ud = lua.create_userdata(UserData(0)).unwrap();
|
||||
let inc_by = lua
|
||||
.load("function(ud, s) return ud:inc_by(s) end")
|
||||
.eval::<LuaFunction>()
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("userdata [call method complex]", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
collect_gc_twice(&lua);
|
||||
},
|
||||
|_| {
|
||||
inc_by.call::<()>((&ud, 1)).unwrap();
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn userdata_async_call_method(c: &mut Criterion) {
|
||||
struct UserData(i64);
|
||||
impl LuaUserData for UserData {
|
||||
@@ -399,6 +448,7 @@ criterion_group! {
|
||||
table_traversal_pairs,
|
||||
table_traversal_for_each,
|
||||
table_traversal_sequence,
|
||||
table_ref_clone,
|
||||
|
||||
function_create,
|
||||
function_call_sum,
|
||||
@@ -413,6 +463,7 @@ criterion_group! {
|
||||
userdata_create,
|
||||
userdata_call_index,
|
||||
userdata_call_method,
|
||||
userdata_call_method_complex,
|
||||
userdata_async_call_method,
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ cfg-if = "1.0"
|
||||
pkg-config = "0.3.17"
|
||||
lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true }
|
||||
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
|
||||
luau0-src = { version = "0.15.4", optional = true }
|
||||
luau0-src = { version = "0.15.6", optional = true }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
|
||||
|
||||
@@ -235,6 +235,7 @@ unsafe extern "C-unwind" {
|
||||
) -> c_int;
|
||||
pub fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int);
|
||||
pub fn lua_pcall(L: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int;
|
||||
pub fn lua_cpcall(L: *mut lua_State, f: lua_CFunction, ud: *mut c_void) -> c_int;
|
||||
|
||||
//
|
||||
// Coroutine functions
|
||||
|
||||
+79
-4
@@ -1,3 +1,5 @@
|
||||
use std::io;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
|
||||
@@ -50,13 +52,18 @@ impl Buffer {
|
||||
#[track_caller]
|
||||
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
|
||||
let lua = self.0.lua.lock();
|
||||
let data = unsafe {
|
||||
let (buf, size) = self.as_raw_parts(&lua);
|
||||
std::slice::from_raw_parts_mut(buf, size)
|
||||
};
|
||||
let data = self.as_slice_mut(&lua);
|
||||
data[offset..offset + bytes.len()].copy_from_slice(bytes);
|
||||
}
|
||||
|
||||
/// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
|
||||
/// buffer.
|
||||
///
|
||||
/// Buffer operations are infallible, none of the read/write functions will return a Err.
|
||||
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
|
||||
BufferCursor(self, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] {
|
||||
unsafe {
|
||||
let (buf, size) = self.as_raw_parts(lua);
|
||||
@@ -64,6 +71,14 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
fn as_slice_mut(&self, lua: &RawLua) -> &mut [u8] {
|
||||
unsafe {
|
||||
let (buf, size) = self.as_raw_parts(lua);
|
||||
std::slice::from_raw_parts_mut(buf, size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
|
||||
let mut size = 0usize;
|
||||
@@ -78,6 +93,66 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
struct BufferCursor(Buffer, usize);
|
||||
|
||||
impl io::Read for BufferCursor {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let lua = self.0 .0.lua.lock();
|
||||
let data = self.0.as_slice(&lua);
|
||||
if self.1 == data.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let len = buf.len().min(data.len() - self.1);
|
||||
buf[..len].copy_from_slice(&data[self.1..self.1 + len]);
|
||||
self.1 += len;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for BufferCursor {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let lua = self.0 .0.lua.lock();
|
||||
let data = self.0.as_slice_mut(&lua);
|
||||
if self.1 == data.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let len = buf.len().min(data.len() - self.1);
|
||||
data[self.1..self.1 + len].copy_from_slice(&buf[..len]);
|
||||
self.1 += len;
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Seek for BufferCursor {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||
let lua = self.0 .0.lua.lock();
|
||||
let data = self.0.as_slice(&lua);
|
||||
let new_offset = match pos {
|
||||
io::SeekFrom::Start(offset) => offset as i64,
|
||||
io::SeekFrom::End(offset) => data.len() as i64 + offset,
|
||||
io::SeekFrom::Current(offset) => self.1 as i64 + offset,
|
||||
};
|
||||
if new_offset < 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid seek to a negative position",
|
||||
));
|
||||
}
|
||||
if new_offset as usize > data.len() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid seek to a position beyond the end of the buffer",
|
||||
));
|
||||
}
|
||||
self.1 = new_offset as usize;
|
||||
Ok(self.1 as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for Buffer {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
|
||||
|
||||
+121
-17
@@ -37,6 +37,7 @@ use crate::{buffer::Buffer, chunk::Compiler};
|
||||
use {
|
||||
crate::types::LightUserData,
|
||||
std::future::{self, Future},
|
||||
std::task::Poll,
|
||||
};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
@@ -631,13 +632,13 @@ impl Lua {
|
||||
///
|
||||
/// Any Luau code is guaranteed to call this handler "eventually"
|
||||
/// (in practice this can happen at any function call or at any loop iteration).
|
||||
/// This is similar to `Lua::set_hook` but in more simplified form.
|
||||
///
|
||||
/// The provided interrupt function can error, and this error will be propagated through
|
||||
/// the Luau code that was executing at the time the interrupt was triggered.
|
||||
/// Also this can be used to implement continuous execution limits by instructing Luau VM to
|
||||
/// yield by returning [`VmState::Yield`].
|
||||
///
|
||||
/// This is similar to `Lua::set_hook` but in more simplified form.
|
||||
/// yield by returning [`VmState::Yield`]. The yield will happen only at yieldable points
|
||||
/// of execution (not across metamethod/C-call boundaries).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -695,7 +696,10 @@ impl Lua {
|
||||
match result {
|
||||
VmState::Continue => {}
|
||||
VmState::Yield => {
|
||||
ffi::lua_yield(state, 0);
|
||||
// We can yield only at yieldable points, otherwise ignore and continue
|
||||
if ffi::lua_isyieldable(state) != 0 {
|
||||
ffi::lua_yield(state, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1150,7 +1154,7 @@ impl Lua {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create and return an interned Lua string.
|
||||
/// Creates and returns an interned Lua string.
|
||||
///
|
||||
/// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str`
|
||||
/// and `&String`, you can also pass plain `&[u8]` here.
|
||||
@@ -1159,27 +1163,32 @@ impl Lua {
|
||||
unsafe { self.lock().create_string(s) }
|
||||
}
|
||||
|
||||
/// Create and return a Luau [buffer] object from a byte slice of data.
|
||||
/// Creates and returns a Luau [buffer] object from a byte slice of data.
|
||||
///
|
||||
/// [buffer]: https://luau.org/library#buffer-library
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result<Buffer> {
|
||||
pub fn create_buffer(&self, data: impl AsRef<[u8]>) -> Result<Buffer> {
|
||||
let lua = self.lock();
|
||||
let state = lua.state();
|
||||
let data = data.as_ref();
|
||||
unsafe {
|
||||
if lua.unlikely_memory_error() {
|
||||
crate::util::push_buffer(state, buf.as_ref(), false)?;
|
||||
return Ok(Buffer(lua.pop_ref()));
|
||||
}
|
||||
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
crate::util::push_buffer(state, buf.as_ref(), true)?;
|
||||
Ok(Buffer(lua.pop_ref()))
|
||||
let (ptr, buffer) = lua.create_buffer_with_capacity(data.len())?;
|
||||
ptr.copy_from_nonoverlapping(data.as_ptr(), data.len());
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates and returns a Luau [buffer] object with the specified size.
|
||||
///
|
||||
/// Size limit is 1GB. All bytes will be initialized to zero.
|
||||
///
|
||||
/// [buffer]: https://luau.org/library#buffer-library
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn create_buffer_with_capacity(&self, size: usize) -> Result<Buffer> {
|
||||
unsafe { Ok(self.lock().create_buffer_with_capacity(size)?.1) }
|
||||
}
|
||||
|
||||
/// Creates and returns a new empty table.
|
||||
#[inline]
|
||||
pub fn create_table(&self) -> Result<Table> {
|
||||
@@ -2071,6 +2080,101 @@ impl Lua {
|
||||
LightUserData(&ASYNC_POLL_TERMINATE as *const u8 as *mut std::os::raw::c_void)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline(always)]
|
||||
pub(crate) fn poll_yield() -> LightUserData {
|
||||
static ASYNC_POLL_YIELD: u8 = 0;
|
||||
LightUserData(&ASYNC_POLL_YIELD as *const u8 as *mut std::os::raw::c_void)
|
||||
}
|
||||
|
||||
/// Suspends the current async function, returning the provided arguments to caller.
|
||||
///
|
||||
/// This function is similar to [`coroutine.yield`] but allow yeilding Rust functions
|
||||
/// and passing values to the caller.
|
||||
/// Please note that you cannot cross [`Thread`] boundaries (e.g. calling `yield_with` on one
|
||||
/// thread and resuming on another).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Async iterator:
|
||||
///
|
||||
/// ```
|
||||
/// # use mlua::{Lua, Result};
|
||||
///
|
||||
/// async fn generator(lua: Lua, _: ()) -> Result<()> {
|
||||
/// for i in 0..10 {
|
||||
/// lua.yield_with::<()>(i).await?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn main() -> Result<()> {
|
||||
/// let lua = Lua::new();
|
||||
/// lua.globals().set("generator", lua.create_async_function(generator)?)?;
|
||||
///
|
||||
/// lua.load(r#"
|
||||
/// local n = 0
|
||||
/// for i in coroutine.wrap(generator) do
|
||||
/// n = n + i
|
||||
/// end
|
||||
/// assert(n == 45)
|
||||
/// "#)
|
||||
/// .exec()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Exchange values on yield:
|
||||
///
|
||||
/// ```
|
||||
/// # use mlua::{Lua, Result, Value};
|
||||
///
|
||||
/// async fn pingpong(lua: Lua, mut val: i32) -> Result<()> {
|
||||
/// loop {
|
||||
/// val = lua.yield_with::<i32>(val).await? + 1;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let lua = Lua::new();
|
||||
///
|
||||
/// let co = lua.create_thread(lua.create_async_function(pingpong)?)?;
|
||||
/// assert_eq!(co.resume::<i32>(1)?, 1);
|
||||
/// assert_eq!(co.resume::<i32>(2)?, 3);
|
||||
/// assert_eq!(co.resume::<i32>(3)?, 4);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
pub async fn yield_with<R: FromLuaMulti>(&self, args: impl IntoLuaMulti) -> Result<R> {
|
||||
let mut args = Some(args.into_lua_multi(self)?);
|
||||
future::poll_fn(move |_cx| match args.take() {
|
||||
Some(args) => unsafe {
|
||||
let lua = self.lock();
|
||||
lua.push(Self::poll_yield())?; // yield marker
|
||||
if args.len() <= 1 {
|
||||
lua.push(args.front())?;
|
||||
} else {
|
||||
lua.push(lua.create_sequence_from(&args)?)?;
|
||||
}
|
||||
lua.push(args.len())?;
|
||||
Poll::Pending
|
||||
},
|
||||
None => unsafe {
|
||||
let lua = self.lock();
|
||||
let state = lua.state();
|
||||
let _sg = StackGuard::with_top(state, 0);
|
||||
let nvals = ffi::lua_gettop(state);
|
||||
Poll::Ready(R::from_stack_multi(nvals, &lua))
|
||||
},
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns a weak reference to the Lua instance.
|
||||
///
|
||||
/// This is useful for creating a reference to the Lua instance that does not prevent it from
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ pub(crate) struct ExtraData {
|
||||
pub(super) wrapped_failure_top: usize,
|
||||
// Pool of `Thread`s (coroutines) for async execution
|
||||
#[cfg(feature = "async")]
|
||||
pub(super) thread_pool: Vec<c_int>,
|
||||
pub(super) thread_pool: Vec<crate::types::ValueRefIndex>,
|
||||
|
||||
// Address of `WrappedFailure` metatable
|
||||
pub(super) wrapped_failure_mt_ptr: *const c_void,
|
||||
|
||||
+44
-18
@@ -523,6 +523,20 @@ impl RawLua {
|
||||
Ok(String(self.pop_ref()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
pub(crate) unsafe fn create_buffer_with_capacity(&self, size: usize) -> Result<(*mut u8, crate::Buffer)> {
|
||||
let state = self.state();
|
||||
if self.unlikely_memory_error() {
|
||||
let ptr = crate::util::push_buffer(state, size, false)?;
|
||||
return Ok((ptr, crate::Buffer(self.pop_ref())));
|
||||
}
|
||||
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
let ptr = crate::util::push_buffer(state, size, true)?;
|
||||
Ok((ptr, crate::Buffer(self.pop_ref())))
|
||||
}
|
||||
|
||||
/// See [`Lua::create_table_with_capacity`]
|
||||
pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result<Table> {
|
||||
let state = self.state();
|
||||
@@ -624,7 +638,7 @@ impl RawLua {
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
|
||||
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
|
||||
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
|
||||
let thread_state = ffi::lua_tothread(self.ref_thread(), *index.0);
|
||||
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
@@ -645,8 +659,9 @@ impl RawLua {
|
||||
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
|
||||
let extra = &mut *self.extra.get();
|
||||
if extra.thread_pool.len() < extra.thread_pool.capacity() {
|
||||
extra.thread_pool.push(thread.0.index);
|
||||
thread.0.drop = false; // Prevent thread from being garbage collected
|
||||
if let Some(index) = thread.0.index_count.take() {
|
||||
extra.thread_pool.push(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,13 +842,6 @@ impl RawLua {
|
||||
ValueRef::new(self, index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef {
|
||||
ffi::lua_pushvalue(self.ref_thread(), vref.index);
|
||||
let index = (*self.extra.get()).ref_stack_pop();
|
||||
ValueRef::new(self, index)
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) {
|
||||
let ref_thread = self.ref_thread();
|
||||
mlua_debug_assert!(
|
||||
@@ -1270,6 +1278,13 @@ impl RawLua {
|
||||
let mut ctx = Context::from_waker(rawlua.waker());
|
||||
match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) {
|
||||
Some(Poll::Pending) => {
|
||||
let fut_nvals = ffi::lua_gettop(state);
|
||||
if fut_nvals >= 3 && ffi::lua_tolightuserdata(state, -3) == Lua::poll_yield().0 {
|
||||
// We have some values to yield
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_replace(state, -4);
|
||||
return Ok(3);
|
||||
}
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_pushlightuserdata(state, Lua::poll_pending().0);
|
||||
Ok(2)
|
||||
@@ -1340,6 +1355,7 @@ impl RawLua {
|
||||
local poll = get_poll(...)
|
||||
local nres, res, res2 = poll()
|
||||
while true do
|
||||
-- Poll::Ready branch, `nres` is the number of results
|
||||
if nres ~= nil then
|
||||
if nres == 0 then
|
||||
return
|
||||
@@ -1355,10 +1371,20 @@ impl RawLua {
|
||||
return unpack(res, nres)
|
||||
end
|
||||
end
|
||||
-- `res` is a "pending" value
|
||||
-- `yield` can return a signal to drop the future that we should propagate
|
||||
-- to the poller
|
||||
nres, res, res2 = poll(yield(res))
|
||||
|
||||
-- Poll::Pending branch
|
||||
if res2 == nil then
|
||||
-- `res` is a "pending" value
|
||||
-- `yield` can return a signal to drop the future that we should propagate
|
||||
-- to the poller
|
||||
nres, res, res2 = poll(yield(res))
|
||||
elseif res2 == 0 then
|
||||
nres, res, res2 = poll(yield())
|
||||
elseif res2 == 1 then
|
||||
nres, res, res2 = poll(yield(res))
|
||||
else
|
||||
nres, res, res2 = poll(yield(unpack(res, res2)))
|
||||
end
|
||||
end
|
||||
"#,
|
||||
)
|
||||
@@ -1370,14 +1396,14 @@ impl RawLua {
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn waker(&self) -> &Waker {
|
||||
(*self.extra.get()).waker.as_ref()
|
||||
pub(crate) fn waker(&self) -> &Waker {
|
||||
unsafe { (*self.extra.get()).waker.as_ref() }
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
|
||||
mem::replace(&mut (*self.extra.get()).waker, waker)
|
||||
pub(crate) fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
|
||||
unsafe { mem::replace(&mut (*self.extra.get()).waker, waker) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -884,7 +884,7 @@ impl ObjectLike for Table {
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
// Convert table to a function and call via pcall that respects the `__call` metamethod.
|
||||
Function(self.0.copy()).call(args)
|
||||
Function(self.0.clone()).call(args)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
@@ -893,7 +893,7 @@ impl ObjectLike for Table {
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
Function(self.0.copy()).call_async(args)
|
||||
Function(self.0.clone()).call_async(args)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -941,7 +941,7 @@ impl ObjectLike for Table {
|
||||
|
||||
#[inline]
|
||||
fn to_string(&self) -> Result<StdString> {
|
||||
Value::Table(Table(self.0.copy())).to_string()
|
||||
Value::Table(Table(self.0.clone())).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -156,7 +156,6 @@ impl Thread {
|
||||
let thread_state = self.state();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
|
||||
let nargs = args.push_into_stack_multi(&lua)?;
|
||||
if nargs > 0 {
|
||||
@@ -165,6 +164,7 @@ impl Thread {
|
||||
pushed_nargs += nargs;
|
||||
}
|
||||
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
|
||||
check_stack(state, nresults + 1)?;
|
||||
ffi::lua_xmove(thread_state, state, nresults);
|
||||
@@ -192,12 +192,12 @@ impl Thread {
|
||||
let thread_state = self.state();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
|
||||
check_stack(state, 1)?;
|
||||
error.push_into_stack(&lua)?;
|
||||
ffi::lua_xmove(state, thread_state, 1);
|
||||
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
|
||||
check_stack(state, nresults + 1)?;
|
||||
ffi::lua_xmove(thread_state, state, nresults);
|
||||
@@ -604,7 +604,7 @@ impl<R: FromLuaMulti> Future for AsyncThread<R> {
|
||||
|
||||
if status.is_yielded() {
|
||||
if !(nresults == 1 && is_poll_pending(thread_state)) {
|
||||
// Ignore value returned via yield()
|
||||
// Ignore values returned via yield()
|
||||
cx.waker().wake_by_ref();
|
||||
}
|
||||
return Poll::Pending;
|
||||
@@ -635,7 +635,7 @@ struct WakerGuard<'lua, 'a> {
|
||||
impl<'lua, 'a> WakerGuard<'lua, 'a> {
|
||||
#[inline]
|
||||
pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
|
||||
let prev = unsafe { lua.set_waker(NonNull::from(waker)) };
|
||||
let prev = lua.set_waker(NonNull::from(waker));
|
||||
Ok(WakerGuard {
|
||||
lua,
|
||||
prev,
|
||||
@@ -647,7 +647,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> {
|
||||
#[cfg(feature = "async")]
|
||||
impl Drop for WakerGuard<'_, '_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.lua.set_waker(self.prev) };
|
||||
self.lua.set_waker(self.prev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ pub use either::Either;
|
||||
pub use registry_key::RegistryKey;
|
||||
pub(crate) use value_ref::ValueRef;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) use value_ref::ValueRefIndex;
|
||||
|
||||
/// Type of Lua integer numbers.
|
||||
pub type Integer = ffi::lua_Integer;
|
||||
/// Type of Lua floating point numbers.
|
||||
|
||||
+28
-23
@@ -1,22 +1,39 @@
|
||||
use std::fmt;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
|
||||
use super::XRc;
|
||||
use crate::state::{RawLua, WeakLua};
|
||||
|
||||
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
|
||||
#[derive(Clone)]
|
||||
pub struct ValueRef {
|
||||
pub(crate) lua: WeakLua,
|
||||
// Keep index separate to avoid additional indirection when accessing it.
|
||||
pub(crate) index: c_int,
|
||||
pub(crate) drop: bool,
|
||||
// If `index_count` is `None`, the value does not need to be destroyed.
|
||||
pub(crate) index_count: Option<ValueRefIndex>,
|
||||
}
|
||||
|
||||
/// A reference to a Lua value index in the auxiliary thread.
|
||||
/// It's cheap to clone and can be used to track the number of references to a value.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ValueRefIndex(pub(crate) XRc<c_int>);
|
||||
|
||||
impl From<c_int> for ValueRefIndex {
|
||||
#[inline]
|
||||
fn from(index: c_int) -> Self {
|
||||
ValueRefIndex(XRc::new(index))
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueRef {
|
||||
#[inline]
|
||||
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self {
|
||||
pub(crate) fn new(lua: &RawLua, index: impl Into<ValueRefIndex>) -> Self {
|
||||
let index = index.into();
|
||||
ValueRef {
|
||||
lua: lua.weak().clone(),
|
||||
index,
|
||||
drop: true,
|
||||
index: *index.0,
|
||||
index_count: Some(index),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +42,6 @@ impl ValueRef {
|
||||
let lua = self.lua.lock();
|
||||
unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) }
|
||||
}
|
||||
|
||||
/// Returns a copy of the value, which is valid as long as the original value is held.
|
||||
#[inline]
|
||||
pub(crate) fn copy(&self) -> Self {
|
||||
ValueRef {
|
||||
lua: self.lua.clone(),
|
||||
index: self.index,
|
||||
drop: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ValueRef {
|
||||
@@ -43,17 +50,15 @@ impl fmt::Debug for ValueRef {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ValueRef {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe { self.lua.lock().clone_ref(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ValueRef {
|
||||
fn drop(&mut self) {
|
||||
if self.drop {
|
||||
if let Some(lua) = self.lua.try_lock() {
|
||||
unsafe { lua.drop_ref(self) };
|
||||
if let Some(ValueRefIndex(index)) = self.index_count.take() {
|
||||
// It's guaranteed that the inner value returns exactly once.
|
||||
// This means in particular that the value is not dropped.
|
||||
if XRc::into_inner(index).is_some() {
|
||||
if let Some(lua) = self.lua.try_lock() {
|
||||
unsafe { lua.drop_ref(self) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@ impl ObjectLike for AnyUserData {
|
||||
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
|
||||
// `lua_gettable` method used under the hood can work with any Lua value
|
||||
// that has `__index` metamethod
|
||||
Table(self.0.copy()).get_protected(key)
|
||||
Table(self.0.clone()).get_protected(key)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
|
||||
// `lua_settable` method used under the hood can work with any Lua value
|
||||
// that has `__newindex` metamethod
|
||||
Table(self.0.copy()).set_protected(key, value)
|
||||
Table(self.0.clone()).set_protected(key, value)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -30,7 +30,7 @@ impl ObjectLike for AnyUserData {
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
Function(self.0.copy()).call(args)
|
||||
Function(self.0.clone()).call(args)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
@@ -39,7 +39,7 @@ impl ObjectLike for AnyUserData {
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
Function(self.0.copy()).call_async(args)
|
||||
Function(self.0.clone()).call_async(args)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -88,6 +88,6 @@ impl ObjectLike for AnyUserData {
|
||||
|
||||
#[inline]
|
||||
fn to_string(&self) -> Result<StdString> {
|
||||
Value::UserData(AnyUserData(self.0.copy())).to_string()
|
||||
Value::UserData(AnyUserData(self.0.clone())).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
+6
-8
@@ -101,15 +101,13 @@ pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect:
|
||||
// Uses 3 stack spaces (when protect), does not call checkstack.
|
||||
#[cfg(feature = "luau")]
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> {
|
||||
let data = if protect {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))?
|
||||
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, size: usize, protect: bool) -> Result<*mut u8> {
|
||||
let data = if protect || size > const { 1024 * 1024 * 1024 } {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, size))?
|
||||
} else {
|
||||
ffi::lua_newbuffer(state, b.len())
|
||||
ffi::lua_newbuffer(state, size)
|
||||
};
|
||||
let buf = slice::from_raw_parts_mut(data as *mut u8, b.len());
|
||||
buf.copy_from_slice(b);
|
||||
Ok(())
|
||||
Ok(data as *mut u8)
|
||||
}
|
||||
|
||||
// Uses 3 stack spaces, does not call checkstack.
|
||||
@@ -122,7 +120,7 @@ pub(crate) unsafe fn push_table(
|
||||
) -> Result<()> {
|
||||
let narr: c_int = narr.try_into().unwrap_or(c_int::MAX);
|
||||
let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX);
|
||||
if protect || narr >= const { 1 << 30 } || nrec >= const { 1 << 27 } {
|
||||
if protect || narr >= const { 1 << 26 } || nrec >= const { 1 << 26 } {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
|
||||
} else {
|
||||
ffi::lua_createtable(state, narr, nrec);
|
||||
|
||||
+18
-8
@@ -1,6 +1,6 @@
|
||||
//! Mostly copied from [bevy_utils]
|
||||
//! Inspired by bevy's [disqualified]
|
||||
//!
|
||||
//! [bevy_utils]: https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs
|
||||
//! [disqualified]: https://github.com/bevyengine/disqualified/blob/main/src/short_name.rs
|
||||
|
||||
use std::any::type_name;
|
||||
|
||||
@@ -23,8 +23,7 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
|
||||
while index < end_of_string {
|
||||
let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
|
||||
|
||||
// Collapse everything up to the next special character,
|
||||
// then skip over it
|
||||
// Collapse everything up to the next special character, then skip over it
|
||||
if let Some(special_character_index) =
|
||||
rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c))
|
||||
{
|
||||
@@ -32,11 +31,16 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
|
||||
parsed_name += collapse_type_name(segment_to_collapse);
|
||||
// Insert the special character
|
||||
let special_character = &rest_of_string[special_character_index..=special_character_index];
|
||||
parsed_name.push_str(special_character);
|
||||
parsed_name += special_character;
|
||||
|
||||
// Remove lifetimes like <'_> or <'_, '_, ...>
|
||||
if parsed_name.ends_with("<'_>") || parsed_name.ends_with("<'_, ") {
|
||||
_ = parsed_name.split_off(parsed_name.len() - 4);
|
||||
}
|
||||
|
||||
match special_character {
|
||||
">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => {
|
||||
parsed_name.push_str("::");
|
||||
parsed_name += "::";
|
||||
// Move the index past the "::"
|
||||
index += special_character_index + 3;
|
||||
}
|
||||
@@ -53,14 +57,18 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn collapse_type_name(string: &str) -> &str {
|
||||
string.rsplit("::").next().unwrap()
|
||||
fn collapse_type_name(segment: &str) -> &str {
|
||||
segment.rsplit("::").next().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::short_type_name;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
struct MyData<'a, 'b>(PhantomData<&'a &'b ()>);
|
||||
struct MyDataT<'a, T>(PhantomData<&'a T>);
|
||||
|
||||
#[test]
|
||||
fn tests() {
|
||||
@@ -73,5 +81,7 @@ mod tests {
|
||||
"HashMap<String, Option<[i32; 3]>>"
|
||||
);
|
||||
assert_eq!(short_type_name::<dyn Fn(i32) -> i32>(), "dyn Fn(i32) -> i32");
|
||||
assert_eq!(short_type_name::<MyDataT<&str>>(), "MyDataT<&str>");
|
||||
assert_eq!(short_type_name::<(&MyData, [MyData])>(), "(MyData, [MyData])");
|
||||
}
|
||||
}
|
||||
|
||||
+33
-1
@@ -8,7 +8,7 @@ use futures_util::stream::TryStreamExt;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use mlua::{
|
||||
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData,
|
||||
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData,
|
||||
UserDataMethods, UserDataRef, Value,
|
||||
};
|
||||
|
||||
@@ -667,3 +667,35 @@ async fn test_async_hook() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_yield_with() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let func = lua.create_async_function(|lua, (mut a, mut b): (i32, i32)| async move {
|
||||
let zero = lua.yield_with::<MultiValue>(()).await?;
|
||||
assert!(zero.is_empty());
|
||||
let one = lua.yield_with::<MultiValue>(a + b).await?;
|
||||
assert_eq!(one.len(), 1);
|
||||
|
||||
for _ in 0..3 {
|
||||
(a, b) = lua.yield_with((a + b, a * b)).await?;
|
||||
}
|
||||
Ok((0, 0))
|
||||
})?;
|
||||
|
||||
let thread = lua.create_thread(func)?;
|
||||
|
||||
let zero = thread.resume::<MultiValue>((2, 3))?; // function arguments
|
||||
assert!(zero.is_empty());
|
||||
let one = thread.resume::<i32>(())?; // value of "zero" is passed here
|
||||
assert_eq!(one, 5);
|
||||
|
||||
assert_eq!(thread.resume::<(i32, i32)>(1)?, (5, 6)); // value of "one" is passed here
|
||||
assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110));
|
||||
assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132));
|
||||
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
|
||||
assert_eq!(thread.status(), ThreadStatus::Finished);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+68
-2
@@ -1,5 +1,7 @@
|
||||
#![cfg(feature = "luau")]
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use mlua::{Lua, Result, Value};
|
||||
|
||||
#[test]
|
||||
@@ -41,7 +43,7 @@ fn test_buffer() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "range end index 14 out of range for slice of length 13")]
|
||||
#[should_panic(expected = "out of range for slice of length 13")]
|
||||
fn test_buffer_out_of_bounds_read() {
|
||||
let lua = Lua::new();
|
||||
let buf = lua.create_buffer(b"hello, world!").unwrap();
|
||||
@@ -49,9 +51,73 @@ fn test_buffer_out_of_bounds_read() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "range end index 16 out of range for slice of length 13")]
|
||||
#[should_panic(expected = "out of range for slice of length 13")]
|
||||
fn test_buffer_out_of_bounds_write() {
|
||||
let lua = Lua::new();
|
||||
let buf = lua.create_buffer(b"hello, world!").unwrap();
|
||||
buf.write_bytes(14, b"!!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_large_buffer() {
|
||||
let lua = Lua::new();
|
||||
let err = lua.create_buffer_with_capacity(1_073_741_824 + 1).unwrap_err(); // 1GB
|
||||
assert!(err.to_string().contains("memory allocation error"));
|
||||
|
||||
// Normal buffer is okay
|
||||
let buf = lua.create_buffer_with_capacity(1024 * 1024).unwrap();
|
||||
assert_eq!(buf.len(), 1024 * 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_cursor() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
let mut cursor = lua.create_buffer(b"hello, world")?.cursor();
|
||||
|
||||
let mut data = Vec::new();
|
||||
cursor.read_to_end(&mut data)?;
|
||||
assert_eq!(data, b"hello, world");
|
||||
|
||||
// No more data to read
|
||||
let mut one = [0u8; 1];
|
||||
assert_eq!(cursor.read(&mut one)?, 0);
|
||||
|
||||
// Seek to start
|
||||
cursor.seek(SeekFrom::Start(0))?;
|
||||
cursor.read_exact(&mut one)?;
|
||||
assert_eq!(one, [b'h']);
|
||||
|
||||
// Seek to end -5
|
||||
cursor.seek(SeekFrom::End(-5))?;
|
||||
let mut five = [0u8; 5];
|
||||
cursor.read_exact(&mut five)?;
|
||||
assert_eq!(&five, b"world");
|
||||
|
||||
// Seek to current -1
|
||||
cursor.seek(SeekFrom::Current(-1))?;
|
||||
cursor.read_exact(&mut one)?;
|
||||
assert_eq!(one, [b'd']);
|
||||
|
||||
// Invalid seek
|
||||
assert!(cursor.seek(SeekFrom::Current(-100)).is_err());
|
||||
assert!(cursor.seek(SeekFrom::End(1)).is_err());
|
||||
|
||||
// Write data
|
||||
let buf = lua.create_buffer_with_capacity(100)?;
|
||||
cursor = buf.clone().cursor();
|
||||
|
||||
cursor.write_all(b"hello, ...")?;
|
||||
cursor.seek(SeekFrom::Current(-3))?;
|
||||
cursor.write_all(b"Rust!")?;
|
||||
|
||||
assert_eq!(&buf.read_bytes::<12>(0), b"hello, Rust!");
|
||||
|
||||
// Writing beyond the end of the buffer does nothing
|
||||
cursor.seek(SeekFrom::End(0))?;
|
||||
assert_eq!(cursor.write(b".")?, 0);
|
||||
|
||||
// Flush is no-op
|
||||
cursor.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -330,6 +330,15 @@ fn test_interrupts() -> Result<()> {
|
||||
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
|
||||
assert_eq!(co.status(), ThreadStatus::Finished);
|
||||
|
||||
// Test no yielding at non-yieldable points
|
||||
yield_count.store(0, Ordering::Relaxed);
|
||||
let co = lua.create_thread(lua.create_function(|lua, arg: Value| {
|
||||
(lua.load("return (function(x) return x end)(...)")).call::<Value>(arg)
|
||||
})?)?;
|
||||
let res = co.resume::<String>("abc")?;
|
||||
assert_eq!(res, "abc".to_string());
|
||||
assert_eq!(yield_count.load(Ordering::Relaxed), 3);
|
||||
|
||||
//
|
||||
// Test errors in interrupts
|
||||
//
|
||||
|
||||
+4
-2
@@ -382,7 +382,8 @@ fn test_scope_userdata_ref() -> Result<()> {
|
||||
modify_userdata(&lua, &ud)?;
|
||||
|
||||
// We can only borrow userdata scoped
|
||||
assert!((matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch))));
|
||||
#[rustfmt::skip]
|
||||
assert!(matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
|
||||
ud.borrow_scoped::<MyUserData, ()>(|ud_inst| {
|
||||
assert_eq!(ud_inst.0.get(), 2);
|
||||
})?;
|
||||
@@ -419,7 +420,8 @@ fn test_scope_userdata_ref_mut() -> Result<()> {
|
||||
let ud = scope.create_userdata_ref_mut(&mut data)?;
|
||||
modify_userdata(&lua, &ud)?;
|
||||
|
||||
assert!((matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch))));
|
||||
#[rustfmt::skip]
|
||||
assert!(matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
|
||||
ud.borrow_mut_scoped::<MyUserData, ()>(|ud_inst| {
|
||||
ud_inst.0 += 10;
|
||||
})?;
|
||||
|
||||
@@ -61,6 +61,15 @@ fn test_table() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")] // Linux allow overcommiting the memory (relevant for CI)
|
||||
fn test_table_with_large_capacity() {
|
||||
let lua = Lua::new();
|
||||
|
||||
let t = lua.create_table_with_capacity(1 << 26, 1 << 26);
|
||||
assert!(t.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_push_pop() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
+22
-1
@@ -1,6 +1,6 @@
|
||||
use std::panic::catch_unwind;
|
||||
|
||||
use mlua::{Error, Function, Lua, Result, Thread, ThreadStatus};
|
||||
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadStatus, Value};
|
||||
|
||||
#[test]
|
||||
fn test_thread() -> Result<()> {
|
||||
@@ -252,3 +252,24 @@ fn test_thread_resume_error() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_thread_resume_bad_arg() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
struct BadArg;
|
||||
|
||||
impl IntoLua for BadArg {
|
||||
fn into_lua(self, _lua: &Lua) -> Result<Value> {
|
||||
Err(Error::runtime("bad arg"))
|
||||
}
|
||||
}
|
||||
|
||||
let f = lua.create_thread(lua.create_function(|_, ()| Ok("okay"))?)?;
|
||||
let res = f.resume::<()>((123, BadArg));
|
||||
assert!(matches!(res, Err(Error::RuntimeError(msg)) if msg == "bad arg"));
|
||||
let res = f.resume::<String>(()).unwrap();
|
||||
assert_eq!(res, "okay");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -525,6 +525,11 @@ fn test_fields() -> Result<()> {
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Field that emulates method
|
||||
fields.add_field_function_get("val_fget", |lua, ud| {
|
||||
lua.create_function(move |_, ()| Ok(ud.borrow::<MyUserData>()?.0))
|
||||
});
|
||||
|
||||
// Use userdata "uservalue" storage
|
||||
fields.add_field_function_get("uval", |_, ud| ud.user_value::<Option<String>>());
|
||||
fields.add_field_function_set("uval", |_, ud, s: Option<String>| ud.set_user_value(s));
|
||||
@@ -537,6 +542,10 @@ fn test_fields() -> Result<()> {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("dummy", |_, _, ()| Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
globals.set("ud", MyUserData(7))?;
|
||||
@@ -546,6 +555,7 @@ fn test_fields() -> Result<()> {
|
||||
assert(ud.val == 7)
|
||||
ud.val = 10
|
||||
assert(ud.val == 10)
|
||||
assert(ud:val_fget() == 10)
|
||||
|
||||
assert(ud.uval == nil)
|
||||
ud.uval = "hello"
|
||||
|
||||
Reference in New Issue
Block a user