1use std::fmt;
2use std::os::raw::{c_int, c_void};
3
4use crate::error::{Error, Result};
5use crate::function::Function;
6use crate::state::RawLua;
7use crate::traits::{FromLuaMulti, IntoLuaMulti};
8use crate::types::{LuaType, ValueRef};
9use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
10
11#[cfg(not(feature = "luau"))]
12use crate::{
13 debug::{Debug, HookTriggers},
14 types::HookKind,
15};
16
17#[cfg(feature = "async")]
18use {
19 futures_util::stream::Stream,
20 std::{
21 future::Future,
22 marker::PhantomData,
23 pin::Pin,
24 ptr::NonNull,
25 task::{Context, Poll, Waker},
26 },
27};
28
29#[derive(Debug, Copy, Clone, Eq, PartialEq)]
31pub enum ThreadStatus {
32 Resumable,
36 Running,
38 Finished,
40 Error,
42}
43
44#[derive(Clone, Copy)]
49enum ThreadStatusInner {
50 New(c_int),
51 Running,
52 Yielded(c_int),
53 Finished,
54 Error,
55}
56
57impl ThreadStatusInner {
58 #[cfg(feature = "async")]
59 #[inline(always)]
60 fn is_resumable(self) -> bool {
61 matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_))
62 }
63
64 #[cfg(feature = "async")]
65 #[inline(always)]
66 fn is_yielded(self) -> bool {
67 matches!(self, ThreadStatusInner::Yielded(_))
68 }
69}
70
71#[derive(Clone)]
73pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
74
75#[cfg(feature = "send")]
76unsafe impl Send for Thread {}
77#[cfg(feature = "send")]
78unsafe impl Sync for Thread {}
79
80#[cfg(feature = "async")]
85#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
86#[must_use = "futures do nothing unless you `.await` or poll them"]
87pub struct AsyncThread<R> {
88 thread: Thread,
89 ret: PhantomData<fn() -> R>,
90 recycle: bool,
91}
92
93impl Thread {
94 #[doc(hidden)]
96 #[inline(always)]
97 pub fn state(&self) -> *mut ffi::lua_State {
98 self.1
99 }
100
101 pub fn resume<R>(&self, args: impl IntoLuaMulti) -> Result<R>
146 where
147 R: FromLuaMulti,
148 {
149 let lua = self.0.lua.lock();
150 let mut pushed_nargs = match self.status_inner(&lua) {
151 ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
152 _ => return Err(Error::CoroutineUnresumable),
153 };
154
155 let state = lua.state();
156 let thread_state = self.state();
157 unsafe {
158 let _sg = StackGuard::new(state);
159
160 let nargs = args.push_into_stack_multi(&lua)?;
161 if nargs > 0 {
162 check_stack(thread_state, nargs)?;
163 ffi::lua_xmove(state, thread_state, nargs);
164 pushed_nargs += nargs;
165 }
166
167 let _thread_sg = StackGuard::with_top(thread_state, 0);
168 let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
169 check_stack(state, nresults + 1)?;
170 ffi::lua_xmove(thread_state, state, nresults);
171
172 R::from_stack_multi(nresults, &lua)
173 }
174 }
175
176 #[cfg(feature = "luau")]
180 #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
181 pub fn resume_error<R>(&self, error: impl crate::IntoLua) -> Result<R>
182 where
183 R: FromLuaMulti,
184 {
185 let lua = self.0.lua.lock();
186 match self.status_inner(&lua) {
187 ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => {}
188 _ => return Err(Error::CoroutineUnresumable),
189 };
190
191 let state = lua.state();
192 let thread_state = self.state();
193 unsafe {
194 let _sg = StackGuard::new(state);
195
196 check_stack(state, 1)?;
197 error.push_into_stack(&lua)?;
198 ffi::lua_xmove(state, thread_state, 1);
199
200 let _thread_sg = StackGuard::with_top(thread_state, 0);
201 let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
202 check_stack(state, nresults + 1)?;
203 ffi::lua_xmove(thread_state, state, nresults);
204
205 R::from_stack_multi(nresults, &lua)
206 }
207 }
208
209 unsafe fn resume_inner(&self, lua: &RawLua, nargs: c_int) -> Result<(ThreadStatusInner, c_int)> {
213 let state = lua.state();
214 let thread_state = self.state();
215 let mut nresults = 0;
216 #[cfg(not(feature = "luau"))]
217 let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
218 #[cfg(feature = "luau")]
219 let ret = ffi::lua_resumex(thread_state, state, nargs, &mut nresults as *mut c_int);
220 match ret {
221 ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)),
222 ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)),
223 ffi::LUA_ERRMEM => {
224 Err(pop_error(thread_state, ret))
226 }
227 _ => {
228 check_stack(state, 3)?;
229 protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?;
230 Err(pop_error(state, ret))
231 }
232 }
233 }
234
235 pub fn status(&self) -> ThreadStatus {
237 match self.status_inner(&self.0.lua.lock()) {
238 ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => ThreadStatus::Resumable,
239 ThreadStatusInner::Running => ThreadStatus::Running,
240 ThreadStatusInner::Finished => ThreadStatus::Finished,
241 ThreadStatusInner::Error => ThreadStatus::Error,
242 }
243 }
244
245 fn status_inner(&self, lua: &RawLua) -> ThreadStatusInner {
247 let thread_state = self.state();
248 if thread_state == lua.state() {
249 return ThreadStatusInner::Running;
251 }
252 let status = unsafe { ffi::lua_status(thread_state) };
253 let top = unsafe { ffi::lua_gettop(thread_state) };
254 match status {
255 ffi::LUA_YIELD => ThreadStatusInner::Yielded(top),
256 ffi::LUA_OK if top > 0 => ThreadStatusInner::New(top - 1),
257 ffi::LUA_OK => ThreadStatusInner::Finished,
258 _ => ThreadStatusInner::Error,
259 }
260 }
261
262 #[cfg(not(feature = "luau"))]
271 #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
272 pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
273 where
274 F: Fn(&crate::Lua, &Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
275 {
276 let lua = self.0.lua.lock();
277 unsafe {
278 lua.set_thread_hook(
279 self.state(),
280 HookKind::Thread(triggers, crate::types::XRc::new(callback)),
281 )
282 }
283 }
284
285 #[cfg(not(feature = "luau"))]
287 #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
288 pub fn remove_hook(&self) {
289 let _lua = self.0.lua.lock();
290 unsafe {
291 ffi::lua_sethook(self.state(), None, 0, 0);
292 }
293 }
294
295 pub fn reset(&self, func: Function) -> Result<()> {
310 let lua = self.0.lua.lock();
311 let thread_state = self.state();
312 unsafe {
313 let status = self.status_inner(&lua);
314 self.reset_inner(status)?;
315
316 ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
318
319 #[cfg(feature = "luau")]
320 {
321 ffi::lua_xpush(lua.main_state(), thread_state, ffi::LUA_GLOBALSINDEX);
323 ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX);
324 }
325
326 Ok(())
327 }
328 }
329
330 unsafe fn reset_inner(&self, status: ThreadStatusInner) -> Result<()> {
331 match status {
332 ThreadStatusInner::New(_) => {
333 ffi::lua_settop(self.state(), 0);
335 Ok(())
336 }
337 ThreadStatusInner::Running => Err(Error::runtime("cannot reset a running thread")),
338 ThreadStatusInner::Finished => Ok(()),
339 #[cfg(not(any(feature = "lua54", feature = "luau")))]
340 ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
341 Err(Error::runtime("cannot reset non-finished thread"))
342 }
343 #[cfg(any(feature = "lua54", feature = "luau"))]
344 ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
345 let thread_state = self.state();
346
347 #[cfg(all(feature = "lua54", not(feature = "vendored")))]
348 let status = ffi::lua_resetthread(thread_state);
349 #[cfg(all(feature = "lua54", feature = "vendored"))]
350 let status = {
351 let lua = self.0.lua.lock();
352 ffi::lua_closethread(thread_state, lua.state())
353 };
354 #[cfg(feature = "lua54")]
355 if status != ffi::LUA_OK {
356 return Err(pop_error(thread_state, status));
357 }
358 #[cfg(feature = "luau")]
359 ffi::lua_resetthread(thread_state);
360
361 Ok(())
362 }
363 }
364 }
365
366 #[cfg(feature = "async")]
413 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
414 pub fn into_async<R>(self, args: impl IntoLuaMulti) -> Result<AsyncThread<R>>
415 where
416 R: FromLuaMulti,
417 {
418 let lua = self.0.lua.lock();
419 if !self.status_inner(&lua).is_resumable() {
420 return Err(Error::CoroutineUnresumable);
421 }
422
423 let state = lua.state();
424 let thread_state = self.state();
425 unsafe {
426 let _sg = StackGuard::new(state);
427
428 let nargs = args.push_into_stack_multi(&lua)?;
429 if nargs > 0 {
430 check_stack(thread_state, nargs)?;
431 ffi::lua_xmove(state, thread_state, nargs);
432 }
433
434 Ok(AsyncThread {
435 thread: self,
436 ret: PhantomData,
437 recycle: false,
438 })
439 }
440 }
441
442 #[cfg(any(feature = "luau", doc))]
476 #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
477 pub fn sandbox(&self) -> Result<()> {
478 let lua = self.0.lua.lock();
479 let state = lua.state();
480 let thread_state = self.state();
481 unsafe {
482 check_stack(thread_state, 3)?;
483 check_stack(state, 3)?;
484 protect_lua!(state, 0, 0, |_| ffi::luaL_sandboxthread(thread_state))
485 }
486 }
487
488 #[inline]
494 pub fn to_pointer(&self) -> *const c_void {
495 self.0.to_pointer()
496 }
497}
498
499impl fmt::Debug for Thread {
500 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
501 fmt.debug_tuple("Thread").field(&self.0).finish()
502 }
503}
504
505impl PartialEq for Thread {
506 fn eq(&self, other: &Self) -> bool {
507 self.0 == other.0
508 }
509}
510
511impl LuaType for Thread {
512 const TYPE_ID: c_int = ffi::LUA_TTHREAD;
513}
514
515#[cfg(feature = "async")]
516impl<R> AsyncThread<R> {
517 #[inline(always)]
518 pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
519 self.recycle = recyclable;
520 }
521}
522
523#[cfg(feature = "async")]
524impl<R> Drop for AsyncThread<R> {
525 fn drop(&mut self) {
526 if self.recycle {
527 if let Some(lua) = self.thread.0.lua.try_lock() {
528 unsafe {
529 let mut status = self.thread.status_inner(&lua);
530 if matches!(status, ThreadStatusInner::Yielded(0)) {
531 ffi::lua_pushlightuserdata(self.thread.1, crate::Lua::poll_terminate().0);
533 if let Ok((new_status, _)) = self.thread.resume_inner(&lua, 1) {
534 status = new_status;
536 }
537 }
538
539 if self.thread.reset_inner(status).is_ok() {
541 lua.recycle_thread(&mut self.thread);
542 }
543 }
544 }
545 }
546 }
547}
548
549#[cfg(feature = "async")]
550impl<R: FromLuaMulti> Stream for AsyncThread<R> {
551 type Item = Result<R>;
552
553 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
554 let lua = self.thread.0.lua.lock();
555 let nargs = match self.thread.status_inner(&lua) {
556 ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
557 _ => return Poll::Ready(None),
558 };
559
560 let state = lua.state();
561 let thread_state = self.thread.state();
562 unsafe {
563 let _sg = StackGuard::new(state);
564 let _thread_sg = StackGuard::with_top(thread_state, 0);
565 let _wg = WakerGuard::new(&lua, cx.waker());
566
567 let (status, nresults) = (self.thread).resume_inner(&lua, nargs)?;
568
569 if status.is_yielded() {
570 if nresults == 1 && is_poll_pending(thread_state) {
571 return Poll::Pending;
572 }
573 cx.waker().wake_by_ref();
575 }
576
577 check_stack(state, nresults + 1)?;
578 ffi::lua_xmove(thread_state, state, nresults);
579
580 Poll::Ready(Some(R::from_stack_multi(nresults, &lua)))
581 }
582 }
583}
584
585#[cfg(feature = "async")]
586impl<R: FromLuaMulti> Future for AsyncThread<R> {
587 type Output = Result<R>;
588
589 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
590 let lua = self.thread.0.lua.lock();
591 let nargs = match self.thread.status_inner(&lua) {
592 ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
593 _ => return Poll::Ready(Err(Error::CoroutineUnresumable)),
594 };
595
596 let state = lua.state();
597 let thread_state = self.thread.state();
598 unsafe {
599 let _sg = StackGuard::new(state);
600 let _thread_sg = StackGuard::with_top(thread_state, 0);
601 let _wg = WakerGuard::new(&lua, cx.waker());
602
603 let (status, nresults) = self.thread.resume_inner(&lua, nargs)?;
604
605 if status.is_yielded() {
606 if !(nresults == 1 && is_poll_pending(thread_state)) {
607 cx.waker().wake_by_ref();
609 }
610 return Poll::Pending;
611 }
612
613 check_stack(state, nresults + 1)?;
614 ffi::lua_xmove(thread_state, state, nresults);
615
616 Poll::Ready(R::from_stack_multi(nresults, &lua))
617 }
618 }
619}
620
621#[cfg(feature = "async")]
622#[inline(always)]
623unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool {
624 ffi::lua_tolightuserdata(state, -1) == crate::Lua::poll_pending().0
625}
626
627#[cfg(feature = "async")]
628struct WakerGuard<'lua, 'a> {
629 lua: &'lua RawLua,
630 prev: NonNull<Waker>,
631 _phantom: PhantomData<&'a ()>,
632}
633
634#[cfg(feature = "async")]
635impl<'lua, 'a> WakerGuard<'lua, 'a> {
636 #[inline]
637 pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
638 let prev = lua.set_waker(NonNull::from(waker));
639 Ok(WakerGuard {
640 lua,
641 prev,
642 _phantom: PhantomData,
643 })
644 }
645}
646
647#[cfg(feature = "async")]
648impl Drop for WakerGuard<'_, '_> {
649 fn drop(&mut self) {
650 self.lua.set_waker(self.prev);
651 }
652}
653
654#[cfg(test)]
655mod assertions {
656 use super::*;
657
658 #[cfg(not(feature = "send"))]
659 static_assertions::assert_not_impl_any!(Thread: Send);
660 #[cfg(feature = "send")]
661 static_assertions::assert_impl_all!(Thread: Send, Sync);
662 #[cfg(all(feature = "async", not(feature = "send")))]
663 static_assertions::assert_not_impl_any!(AsyncThread<()>: Send);
664 #[cfg(all(feature = "async", feature = "send"))]
665 static_assertions::assert_impl_all!(AsyncThread<()>: Send, Sync);
666}