Skip to content

Add support for lua yields and basic continuation support #588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6cfa226
mlua continuation yield
Jun 4, 2025
e01e45f
add missing xmove
Jun 4, 2025
bd6b65b
remove comment
Jun 4, 2025
c6f1ff2
add a f32 to test stack
Jun 4, 2025
875d6e8
cleanup
Jun 4, 2025
2a67b26
deal with warnings
Jun 4, 2025
d23f058
update warning
Jun 4, 2025
62330e7
allow yield on non-luau as well
Jun 4, 2025
1c7743c
fix
Jun 5, 2025
85416fd
Document continuations a bit more
Jun 5, 2025
b3a54ac
reuse same preallocatedfailure in both callback_error_ext and callbac…
Jun 5, 2025
085c62a
handle mainthread edgecase
Jun 5, 2025
198b857
fix import
Jun 5, 2025
2cd34db
support empty yield args
Jun 5, 2025
23b7705
add more tests for cont
Jun 5, 2025
dd3ea05
ensure check_stack of state
Jun 5, 2025
52176a8
rename to luau continuation status
Jun 6, 2025
10d2df4
add support for continuations outside luau
Jun 6, 2025
8400386
fix
Jun 6, 2025
5d86852
fix
Jun 6, 2025
9e77e5c
fix
Jun 6, 2025
eb63755
fix yieldable continuation on non-luau
Jun 6, 2025
9def001
fix luau compiler bug
Jun 6, 2025
e985f83
add note on why pop
Jun 6, 2025
3dcb131
fix docs which incorrectly state only luau supports yieldable continu…
Jun 6, 2025
3a079fe
make the api more ergonomic
Jun 6, 2025
3c005d7
rename set_yield_args to yield_with
Jun 6, 2025
c7ddbe6
partially revert due to compiler error
Jun 6, 2025
b44075f
fix
Jun 6, 2025
a2e8694
fix
Jun 6, 2025
4a37dc5
ensure wrappedfailure is returned to pool
Jun 6, 2025
a5c1ea1
fix wrap error case
Jun 6, 2025
94ca102
Merge branch 'mlua-rs:main' into mlua-cont-luau
cheesycod Jun 10, 2025
75f2493
Merge branch 'mlua-rs:main' into mlua-cont-luau
cheesycod Jun 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions mlua-sys/src/lua52/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
lua_yieldk(L, n, 0, None)
}

#[inline(always)]
pub unsafe fn lua_yieldc(L: *mut lua_State, n: c_int, k: lua_CFunction) -> c_int {
lua_yieldk(L, n, 0, Some(k))
}

//
// Garbage-collection function and options
//
Expand Down
5 changes: 5 additions & 0 deletions mlua-sys/src/lua53/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
lua_yieldk(L, n, 0, None)
}

#[inline(always)]
pub unsafe fn lua_yieldc(L: *mut lua_State, n: c_int, k: lua_KFunction) -> c_int {
lua_yieldk(L, n, 0, Some(k))
}

//
// Garbage-collection function and options
//
Expand Down
5 changes: 5 additions & 0 deletions mlua-sys/src/lua54/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
lua_yieldk(L, n, 0, None)
}

#[inline(always)]
pub unsafe fn lua_yieldc(L: *mut lua_State, n: c_int, k: lua_KFunction) -> c_int {
lua_yieldk(L, n, 0, Some(k))
}

//
// Warning-related functions
//
Expand Down
5 changes: 5 additions & 0 deletions mlua-sys/src/luau/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,11 @@ pub unsafe fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, nup: c_int)
lua_pushcclosurek(L, f, ptr::null(), nup, None)
}

#[inline(always)]
pub unsafe fn lua_pushcclosurec(L: *mut lua_State, f: lua_CFunction, cont: lua_Continuation, nup: c_int) {
lua_pushcclosurek(L, f, ptr::null(), nup, Some(cont))
}

#[inline(always)]
pub unsafe fn lua_pushcclosured(L: *mut lua_State, f: lua_CFunction, debugname: *const c_char, nup: c_int) {
lua_pushcclosurek(L, f, debugname, nup, None)
Expand Down
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ impl fmt::Display for Error {
Error::WithContext { context, cause } => {
writeln!(fmt, "{context}")?;
write!(fmt, "{cause}")
}
},
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
pub use crate::stdlib::StdLib;
pub use crate::string::{BorrowedBytes, BorrowedStr, String};
pub use crate::table::{Table, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::thread::{ContinuationStatus, Thread, ThreadStatus};
pub use crate::traits::{
FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike,
};
Expand Down
18 changes: 9 additions & 9 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger,
IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
Chunk as LuaChunk, ContinuationStatus as LuaContinuationStatus, Either as LuaEither, Error as LuaError,
ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult,
FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode,
Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn,
LuaNativeFnMut, LuaOptions, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil,
Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult,
StdLib as LuaStdLib, String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs,
TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus,
UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua,
Expand Down
78 changes: 78 additions & 0 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ use crate::stdlib::StdLib;
use crate::string::String;
use crate::table::Table;
use crate::thread::Thread;

#[cfg(all(not(feature = "lua51"), not(feature = "luajit")))]
use crate::thread::ContinuationStatus;

use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex,
Expand Down Expand Up @@ -1265,6 +1269,44 @@ impl Lua {
}))
}

/// Same as ``create_function`` but with an added continuation function.
///
/// The values passed to the continuation will be the yielded arguments
/// from the function for the initial continuation call. If yielding from a
/// continuation, the yielded results will be returned to the ``Thread::resume`` caller. The
/// arguments passed in the next ``Thread::resume`` call will then be the arguments passed
/// to the yielding continuation upon resumption.
///
/// Returning a value from a continuation without setting yield
/// arguments will then be returned as the final return value of the Lua function call.
/// Values returned in a function in which there is also yielding will be ignored
#[cfg(all(not(feature = "lua51"), not(feature = "luajit")))]
pub fn create_function_with_continuation<F, FC, A, AC, R, RC>(
&self,
func: F,
cont: FC,
) -> Result<Function>
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
FC: Fn(&Lua, ContinuationStatus, AC) -> Result<RC> + MaybeSend + 'static,
A: FromLuaMulti,
AC: FromLuaMulti,
R: IntoLuaMulti,
RC: IntoLuaMulti,
{
(self.lock()).create_callback_with_continuation(
Box::new(move |rawlua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, None, rawlua)?;
func(rawlua.lua(), args)?.push_into_stack_multi(rawlua)
}),
Box::new(move |rawlua, nargs, status| unsafe {
let args = AC::from_stack_args(nargs, 1, None, rawlua)?;
let status = ContinuationStatus::from_status(status);
cont(rawlua.lua(), status, args)?.push_into_stack_multi(rawlua)
}),
)
}

/// Wraps a Rust mutable closure, creating a callable Lua function handle to it.
///
/// This is a version of [`Lua::create_function`] that accepts a `FnMut` argument.
Expand Down Expand Up @@ -2080,6 +2122,42 @@ impl Lua {
pub(crate) unsafe fn raw_lua(&self) -> &RawLua {
&*self.raw.data_ptr()
}

/// Set the yield arguments. Note that Lua will not yield until you return from the function
///
/// This method is mostly useful with continuations and Rust-Rust yields
/// due to the Rust/Lua boundary
///
/// Example:
///
/// ```rust
/// fn test() -> mlua::Result<()> {
/// let lua = mlua::Lua::new();
/// let always_yield = lua.create_function(|lua, ()| lua.yield_with((42, "69420".to_string(), 45.6)))?;
///
/// let thread = lua.create_thread(always_yield)?;
/// assert_eq!(
/// thread.resume::<(i32, String, f32)>(())?,
/// (42, String::from("69420"), 45.6)
/// );
///
/// Ok(())
/// }
/// ```
pub fn yield_with(&self, args: impl IntoLuaMulti) -> Result<()> {
let raw = self.lock();
unsafe {
raw.extra.get().as_mut().unwrap_unchecked().yielded_values = Some(args.into_lua_multi(self)?);
}
Ok(())
}

/// Checks if Lua is be allowed to yield.
#[cfg(not(any(feature = "lua51", feature = "lua52", feature = "luajit")))]
#[inline]
pub fn is_yieldable(&self) -> bool {
self.lock().is_yieldable()
}
}

impl WeakLua {
Expand Down
6 changes: 6 additions & 0 deletions src/state/extra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ use crate::error::Result;
use crate::state::RawLua;
use crate::stdlib::StdLib;
use crate::types::{AppData, ReentrantMutex, XRc};

use crate::userdata::RawUserDataRegistry;
use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure};

#[cfg(any(feature = "luau", doc))]
use crate::chunk::Compiler;
use crate::MultiValue;

#[cfg(feature = "async")]
use {futures_util::task::noop_waker_ref, std::ptr::NonNull, std::task::Waker};
Expand Down Expand Up @@ -94,6 +96,9 @@ pub(crate) struct ExtraData {
pub(super) compiler: Option<Compiler>,
#[cfg(feature = "luau-jit")]
pub(super) enable_jit: bool,

// Values currently being yielded from Lua.yield()
pub(super) yielded_values: Option<MultiValue>,
}

impl Drop for ExtraData {
Expand Down Expand Up @@ -196,6 +201,7 @@ impl ExtraData {
enable_jit: true,
#[cfg(feature = "luau")]
running_gc: false,
yielded_values: None,
}));

// Store it in the registry
Expand Down
Loading