1use std::sync::{Arc, Mutex};
2
3use sim_kernel::{
4 Args, Callable, ClassRef, Cx, Error, Expr, Object, ObjectCompat, Result, Symbol, Value,
5};
6use sim_lib_control::{CoroutineFrame, CoroutineFrameStep};
7use sim_lib_standard_core::{Arity, SharedOrganRuntime};
8
9use crate::{LuaEvalPolicy, call::call_lua_value, lua_core_profile, lua_table_from_values};
10
11#[derive(Clone, Copy)]
12pub(crate) enum LuaCoroutineKind {
13 Create,
14 Resume,
15 Yield,
16 Status,
17 Wrap,
18 IsYieldable,
19 Running,
20}
21
22impl LuaCoroutineKind {
23 const ALL: [Self; 7] = [
24 Self::Create,
25 Self::Resume,
26 Self::Yield,
27 Self::Status,
28 Self::Wrap,
29 Self::IsYieldable,
30 Self::Running,
31 ];
32
33 fn env_name(self) -> &'static str {
34 match self {
35 Self::Create => "create",
36 Self::Resume => "resume",
37 Self::Yield => "yield",
38 Self::Status => "status",
39 Self::Wrap => "wrap",
40 Self::IsYieldable => "isyieldable",
41 Self::Running => "running",
42 }
43 }
44
45 fn function_symbol(self) -> Symbol {
46 Symbol::qualified("lua/coroutine", self.env_name())
47 }
48}
49
50#[derive(Clone)]
51pub(crate) struct LuaCoroutineFunction {
52 kind: LuaCoroutineKind,
53}
54
55impl LuaCoroutineFunction {
56 fn new(kind: LuaCoroutineKind) -> Self {
57 Self { kind }
58 }
59
60 pub(crate) fn kind(&self) -> LuaCoroutineKind {
61 self.kind
62 }
63}
64
65impl Object for LuaCoroutineFunction {
66 fn display(&self, _cx: &mut Cx) -> Result<String> {
67 Ok(format!(
68 "#<lua-coroutine-function {}>",
69 self.kind.env_name()
70 ))
71 }
72
73 fn as_any(&self) -> &dyn std::any::Any {
74 self
75 }
76}
77
78impl ObjectCompat for LuaCoroutineFunction {
79 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
80 cx.resolve_class(&Symbol::qualified("core", "Function"))
81 }
82
83 fn as_callable(&self) -> Option<&dyn Callable> {
84 Some(self)
85 }
86}
87
88impl Callable for LuaCoroutineFunction {
89 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
90 let policy = LuaEvalPolicy::new(cx)?;
91 let values = run_lua_coroutine_function(cx, &policy, self.kind, args.into_vec())?;
92 Ok(policy
93 .kit()
94 .adjust_values(values, sim_lib_standard_core::Arity::AtLeastOne)
95 .into_iter()
96 .next()
97 .unwrap_or_else(|| policy.kit().nil.clone()))
98 }
99}
100
101pub struct LuaThread {
103 state: Mutex<LuaThreadState>,
104}
105
106#[derive(Clone)]
107enum LuaThreadState {
108 New(Value),
109 Frame(CoroutineFrame<Value>),
110 Dead,
111}
112
113impl LuaThread {
114 fn new(function: Value) -> Self {
115 Self {
116 state: Mutex::new(LuaThreadState::New(function)),
117 }
118 }
119
120 fn frame(produced: Vec<Value>, consumed: Vec<Value>) -> Self {
121 Self {
122 state: Mutex::new(LuaThreadState::Frame(CoroutineFrame::new(
123 produced, consumed,
124 ))),
125 }
126 }
127
128 fn status(&self) -> Result<&'static str> {
129 let state = self
130 .state
131 .lock()
132 .map_err(|_| Error::PoisonedLock("lua coroutine"))?;
133 Ok(match &*state {
134 LuaThreadState::New(_) | LuaThreadState::Frame(_) => "suspended",
135 LuaThreadState::Dead => "dead",
136 })
137 }
138
139 fn resume(&self, cx: &mut Cx, policy: &LuaEvalPolicy, args: Vec<Value>) -> Result<Vec<Value>> {
140 let state = {
141 let mut guard = self
142 .state
143 .lock()
144 .map_err(|_| Error::PoisonedLock("lua coroutine"))?;
145 std::mem::replace(&mut *guard, LuaThreadState::Dead)
146 };
147 match state {
148 LuaThreadState::New(function) => match call_lua_value(cx, policy, function, args) {
149 Ok(values) => {
150 let mut out = vec![cx.factory().bool(true)?];
151 out.extend(values);
152 Ok(out)
153 }
154 Err(error) => Ok(vec![
155 cx.factory().bool(false)?,
156 cx.factory().string(error.to_string())?,
157 ]),
158 },
159 LuaThreadState::Frame(mut frame) => match frame.resume() {
160 CoroutineFrameStep::Produced(value) | CoroutineFrameStep::Consumed(value) => {
161 let done = frame.is_complete();
162 *self
163 .state
164 .lock()
165 .map_err(|_| Error::PoisonedLock("lua coroutine"))? = if done {
166 LuaThreadState::Dead
167 } else {
168 LuaThreadState::Frame(frame)
169 };
170 Ok(vec![cx.factory().bool(true)?, value])
171 }
172 CoroutineFrameStep::Complete => Ok(vec![cx.factory().bool(true)?]),
173 },
174 LuaThreadState::Dead => Ok(vec![
175 cx.factory().bool(false)?,
176 cx.factory()
177 .string("cannot resume dead coroutine".to_owned())?,
178 ]),
179 }
180 }
181}
182
183impl Object for LuaThread {
184 fn display(&self, _cx: &mut Cx) -> Result<String> {
185 Ok(format!("#<lua-thread {}>", self.status()?))
186 }
187
188 fn as_any(&self) -> &dyn std::any::Any {
189 self
190 }
191}
192
193impl ObjectCompat for LuaThread {
194 fn truth(&self, _cx: &mut Cx) -> Result<bool> {
195 Ok(true)
196 }
197}
198
199#[derive(Clone)]
200pub(crate) struct LuaCoroutineWrapper {
201 thread: Arc<LuaThread>,
202}
203
204impl LuaCoroutineWrapper {
205 fn new(function: Value) -> Self {
206 Self {
207 thread: Arc::new(LuaThread::new(function)),
208 }
209 }
210}
211
212impl Object for LuaCoroutineWrapper {
213 fn display(&self, _cx: &mut Cx) -> Result<String> {
214 Ok(format!(
215 "#<lua-coroutine-wrapper {}>",
216 self.thread.status()?
217 ))
218 }
219
220 fn as_any(&self) -> &dyn std::any::Any {
221 self
222 }
223}
224
225impl ObjectCompat for LuaCoroutineWrapper {
226 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
227 cx.resolve_class(&Symbol::qualified("core", "Function"))
228 }
229
230 fn as_callable(&self) -> Option<&dyn Callable> {
231 Some(self)
232 }
233}
234
235impl Callable for LuaCoroutineWrapper {
236 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
237 let policy = LuaEvalPolicy::new(cx)?;
238 let values = call_lua_coroutine_wrapper(cx, &policy, self, args.into_vec())?;
239 Ok(policy
240 .kit()
241 .adjust_values(values, Arity::AtLeastOne)
242 .into_iter()
243 .next()
244 .unwrap_or_else(|| policy.kit().nil.clone()))
245 }
246}
247
248pub fn lua_coroutine_frame_value(
250 cx: &mut Cx,
251 produced: Vec<Value>,
252 consumed: Vec<Value>,
253) -> Result<Value> {
254 cx.factory()
255 .opaque(Arc::new(LuaThread::frame(produced, consumed)))
256}
257
258pub(crate) fn install_lua_coroutine_stdlib(
259 cx: &mut Cx,
260 policy: &LuaEvalPolicy,
261 env: &mut crate::LuaEnv,
262) -> Result<()> {
263 let mut runtime = SharedOrganRuntime::new();
264 let profile = lua_core_profile();
265 let profile_symbol = profile.symbol.clone();
266 runtime.register_profile(profile)?;
267 runtime.register_kit(&profile_symbol, policy.kit().clone())?;
268
269 let mut table_entries = Vec::new();
270 for kind in LuaCoroutineKind::ALL {
271 let function = cx
272 .factory()
273 .opaque(Arc::new(LuaCoroutineFunction::new(kind)))?;
274 runtime.define_function(
275 &profile_symbol,
276 sim_lib_control::control_organ_symbol(),
277 kind.function_symbol(),
278 function.clone(),
279 )?;
280 table_entries.push((
281 cx.factory().string(kind.env_name().to_owned())?,
282 function.clone(),
283 ));
284 define_or_assign(
285 env,
286 Symbol::new(format!("coroutine.{}", kind.env_name())),
287 function,
288 )?;
289 }
290 let table = lua_table_from_values(cx, table_entries)?;
291 define_or_assign(env, Symbol::new("coroutine"), table)
292}
293
294pub(crate) fn run_lua_coroutine_function(
295 cx: &mut Cx,
296 policy: &LuaEvalPolicy,
297 kind: LuaCoroutineKind,
298 args: Vec<Value>,
299) -> Result<Vec<Value>> {
300 match kind {
301 LuaCoroutineKind::Create => {
302 let function = first_arg(args, "coroutine.create")?;
303 cx.factory()
304 .opaque(Arc::new(LuaThread::new(function)))
305 .map(|value| vec![value])
306 }
307 LuaCoroutineKind::Resume => {
308 let mut args = args;
309 let thread = required_arg(&mut args, "coroutine.resume")?;
310 let thread = lua_thread_value(&thread)?;
311 thread.resume(cx, policy, args)
312 }
313 LuaCoroutineKind::Yield => Ok(args),
314 LuaCoroutineKind::Status => {
315 let value = first_arg(args, "coroutine.status")?;
316 let thread = lua_thread_value(&value)?;
317 cx.factory()
318 .string(thread.status()?.to_owned())
319 .map(|value| vec![value])
320 }
321 LuaCoroutineKind::Wrap => {
322 let function = first_arg(args, "coroutine.wrap")?;
323 cx.factory()
324 .opaque(Arc::new(LuaCoroutineWrapper::new(function)))
325 .map(|value| vec![value])
326 }
327 LuaCoroutineKind::IsYieldable => cx.factory().bool(true).map(|value| vec![value]),
328 LuaCoroutineKind::Running => Ok(vec![policy.kit().nil.clone(), cx.factory().bool(false)?]),
329 }
330}
331
332pub(crate) fn call_lua_coroutine_wrapper(
333 cx: &mut Cx,
334 policy: &LuaEvalPolicy,
335 wrapper: &LuaCoroutineWrapper,
336 args: Vec<Value>,
337) -> Result<Vec<Value>> {
338 let mut values = wrapper.thread.resume(cx, policy, args)?.into_iter();
339 let status = values
340 .next()
341 .ok_or_else(|| Error::Eval("coroutine wrapper resume returned no status".to_owned()))?;
342 match status.object().as_expr(cx)? {
343 Expr::Bool(true) => Ok(values.collect()),
344 Expr::Bool(false) => {
345 let message = values
346 .next()
347 .map(|value| value.object().display(cx))
348 .transpose()?
349 .unwrap_or_else(|| "coroutine error".to_owned());
350 Err(Error::Eval(message))
351 }
352 _ => Err(Error::Eval(
353 "coroutine wrapper resume returned non-boolean status".to_owned(),
354 )),
355 }
356}
357
358fn lua_thread_value(value: &Value) -> Result<&LuaThread> {
359 value
360 .object()
361 .downcast_ref::<LuaThread>()
362 .ok_or(Error::TypeMismatch {
363 expected: "lua coroutine thread",
364 found: "non-thread",
365 })
366}
367
368fn first_arg(args: Vec<Value>, context: &str) -> Result<Value> {
369 args.into_iter()
370 .next()
371 .ok_or_else(|| Error::Eval(format!("{context} requires a value")))
372}
373
374fn required_arg(args: &mut Vec<Value>, context: &str) -> Result<Value> {
375 if args.is_empty() {
376 return Err(Error::Eval(format!("{context} requires a value")));
377 }
378 Ok(args.remove(0))
379}
380
381fn define_or_assign(env: &mut crate::LuaEnv, name: Symbol, value: Value) -> Result<()> {
382 if env.contains(&name) {
383 env.assign(&name, value)?;
384 } else {
385 env.define(name, value)?;
386 }
387 Ok(())
388}