mlua_luau_scheduler/
functions.rs1#![allow(clippy::too_many_lines)]
2
3use mlua::prelude::*;
4
5use crate::{
6 error_callback::ThreadErrorCallback,
7 queue::{DeferredThreadQueue, SpawnedThreadQueue},
8 threads::{ThreadId, ThreadMap},
9 traits::LuaSchedulerExt,
10 util::{LuaThreadOrFunction, is_poll_pending},
11};
12
13const ERR_METADATA_NOT_ATTACHED: &str = "\
14Lua state does not have scheduler metadata attached!\
15\nThis is most likely caused by creating functions outside of a scheduler.\
16\nScheduler functions must always be created from within an active scheduler.\
17";
18
19const EXIT_IMPL_LUA: &str = r"
20exit(...)
21yield()
22";
23
24const WRAP_IMPL_LUA: &str = r"
25local t = create(...)
26return function(...)
27 local r = { resume(t, ...) }
28 if r[1] then
29 return select(2, unpack(r))
30 else
31 error(r[2], 2)
32 end
33end
34";
35
36pub struct Functions {
43 pub resume: LuaFunction,
49 pub wrap: LuaFunction,
55 pub spawn: LuaFunction,
61 pub defer: LuaFunction,
67 pub cancel: LuaFunction,
71 pub exit: LuaFunction,
77}
78
79impl Functions {
80 pub fn new(lua: Lua) -> LuaResult<Self> {
92 let spawn_queue = lua
93 .app_data_ref::<SpawnedThreadQueue>()
94 .expect(ERR_METADATA_NOT_ATTACHED)
95 .clone();
96 let defer_queue = lua
97 .app_data_ref::<DeferredThreadQueue>()
98 .expect(ERR_METADATA_NOT_ATTACHED)
99 .clone();
100 let error_callback = lua
101 .app_data_ref::<ThreadErrorCallback>()
102 .expect(ERR_METADATA_NOT_ATTACHED)
103 .clone();
104 let thread_map = lua
105 .app_data_ref::<ThreadMap>()
106 .expect(ERR_METADATA_NOT_ATTACHED)
107 .clone();
108
109 let resume_queue = defer_queue.clone();
110 let resume_map = thread_map.clone();
111 let resume =
112 lua.create_function(move |lua, (thread, args): (LuaThread, LuaMultiValue)| {
113 let _span = tracing::trace_span!("Scheduler::fn_resume").entered();
114 match thread.resume::<LuaMultiValue>(args.clone()) {
115 Ok(v) => {
116 if v.front().is_some_and(is_poll_pending) {
117 resume_queue.push_item(lua, &thread, args)?;
119 (true, LuaValue::Nil).into_lua_multi(lua)
120 } else {
121 if thread.status() != LuaThreadStatus::Resumable {
123 let id = ThreadId::from(&thread);
124 if resume_map.is_tracked(id) {
125 resume_map.insert(id, Ok(v.clone()));
126 }
127 }
128 (true, v).into_lua_multi(lua)
129 }
130 }
131 Err(e) => {
132 let id = ThreadId::from(&thread);
134 if resume_map.is_tracked(id) {
135 resume_map.insert(id, Err(e.clone()));
136 }
137 (false, e.to_string()).into_lua_multi(lua)
138 }
139 }
140 })?;
141
142 let wrap_env = lua.create_table_from(vec![
143 ("resume", resume.clone()),
144 ("error", lua.globals().get::<LuaFunction>("error")?),
145 ("select", lua.globals().get::<LuaFunction>("select")?),
146 ("unpack", lua.globals().get::<LuaFunction>("unpack")?),
147 (
148 "create",
149 lua.globals()
150 .get::<LuaTable>("coroutine")?
151 .get::<LuaFunction>("create")?,
152 ),
153 ])?;
154 let wrap = lua
155 .load(WRAP_IMPL_LUA)
156 .set_name("=__scheduler_wrap")
157 .set_environment(wrap_env)
158 .into_function()?;
159
160 let spawn_map = thread_map.clone();
161 let spawn = lua.create_function(
162 move |lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {
163 let _span = tracing::trace_span!("Scheduler::fn_spawn").entered();
164 let thread = tof.into_thread(lua)?;
165 if thread.status() == LuaThreadStatus::Resumable {
166 match thread.resume::<LuaMultiValue>(args.clone()) {
169 Ok(v) => {
170 if v.front().is_some_and(is_poll_pending) {
171 spawn_queue.push_item(lua, &thread, args)?;
172 } else {
173 if thread.status() != LuaThreadStatus::Resumable {
175 let id = ThreadId::from(&thread);
176 if spawn_map.is_tracked(id) {
177 spawn_map.insert(id, Ok(v));
178 }
179 }
180 }
181 }
182 Err(e) => {
183 error_callback.call(&e);
184 let id = ThreadId::from(&thread);
186 if spawn_map.is_tracked(id) {
187 spawn_map.insert(id, Err(e));
188 }
189 }
190 }
191 }
192 Ok(thread)
193 },
194 )?;
195
196 let defer = lua.create_function(
197 move |lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {
198 let _span = tracing::trace_span!("Scheduler::fn_defer").entered();
199 let thread = tof.into_thread(lua)?;
200 if thread.status() == LuaThreadStatus::Resumable {
201 defer_queue.push_item(lua, &thread, args)?;
202 }
203 Ok(thread)
204 },
205 )?;
206
207 let close = lua
208 .globals()
209 .get::<LuaTable>("coroutine")?
210 .get::<LuaFunction>("close")?;
211 let close_key = lua.create_registry_value(close)?;
212 let cancel = lua.create_function(move |lua, thread: LuaThread| {
213 let _span = tracing::trace_span!("Scheduler::fn_cancel").entered();
214 let close: LuaFunction = lua.registry_value(&close_key)?;
215 match close.call(thread) {
216 Err(LuaError::CoroutineUnresumable) | Ok(()) => Ok(()),
217 Err(e) => Err(e),
218 }
219 })?;
220
221 let exit_env = lua.create_table_from(vec![
222 (
223 "exit",
224 lua.create_function(|lua, code: Option<u8>| {
225 let _span = tracing::trace_span!("Scheduler::fn_exit").entered();
226 let code = code.unwrap_or_default();
227 lua.set_exit_code(code);
228 Ok(())
229 })?,
230 ),
231 (
232 "yield",
233 lua.globals()
234 .get::<LuaTable>("coroutine")?
235 .get::<LuaFunction>("yield")?,
236 ),
237 ])?;
238 let exit = lua
239 .load(EXIT_IMPL_LUA)
240 .set_name("=__scheduler_exit")
241 .set_environment(exit_env)
242 .into_function()?;
243
244 Ok(Self {
245 resume,
246 wrap,
247 spawn,
248 defer,
249 cancel,
250 exit,
251 })
252 }
253}
254
255impl Functions {
256 pub fn inject_compat(&self, lua: &Lua) -> LuaResult<()> {
269 let co: LuaTable = lua.globals().get("coroutine")?;
270 co.set("resume", self.resume.clone())?;
271 co.set("wrap", self.wrap.clone())?;
272 Ok(())
273 }
274}