sim_lib_server/
coroutine.rs1use std::sync::{
2 Arc, Mutex,
3 atomic::{AtomicU8, AtomicU64, Ordering},
4};
5
6use sim_citizen_derive::non_citizen;
7use sim_kernel::{Args, ClassRef, Cx, Error, Object, Result, Symbol, Value};
8
9use crate::ServerAddress;
10
11static NEXT_COROUTINE_ID: AtomicU64 = AtomicU64::new(1);
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum CoroutineStatus {
16 Running,
18 Suspended,
20 Done,
22}
23
24impl CoroutineStatus {
25 fn as_u8(self) -> u8 {
26 match self {
27 Self::Running => 0,
28 Self::Suspended => 1,
29 Self::Done => 2,
30 }
31 }
32
33 fn from_u8(value: u8) -> Self {
34 match value {
35 0 => Self::Running,
36 1 => Self::Suspended,
37 2 => Self::Done,
38 _ => Self::Done,
39 }
40 }
41
42 pub fn as_symbol(self) -> Symbol {
44 Symbol::new(match self {
45 Self::Running => "running",
46 Self::Suspended => "suspended",
47 Self::Done => "done",
48 })
49 }
50}
51
52#[derive(Default)]
53struct CoroutineState {
54 mailbox: Option<Value>,
55 yielded: Option<Value>,
56 cancelled: bool,
57}
58
59struct CoroutineInner {
60 id: u64,
61 address: ServerAddress,
62 handler: Value,
63 status: AtomicU8,
64 state: Mutex<CoroutineState>,
65}
66
67#[derive(Clone)]
68#[non_citizen(
69 reason = "live coroutine handle; inspect through server/Frame descriptor and coroutine ops",
70 kind = "handle",
71 descriptor = "server/Frame"
72)]
73pub struct Coroutine {
77 inner: Arc<CoroutineInner>,
78}
79
80impl Coroutine {
81 pub fn new(address: ServerAddress, handler: Value) -> Self {
83 Self {
84 inner: Arc::new(CoroutineInner {
85 id: NEXT_COROUTINE_ID.fetch_add(1, Ordering::Relaxed),
86 address,
87 handler,
88 status: AtomicU8::new(CoroutineStatus::Suspended.as_u8()),
89 state: Mutex::new(CoroutineState::default()),
90 }),
91 }
92 }
93
94 pub fn id(&self) -> u64 {
96 self.inner.id
97 }
98
99 pub fn address(&self) -> &ServerAddress {
101 &self.inner.address
102 }
103
104 pub fn status(&self) -> CoroutineStatus {
106 CoroutineStatus::from_u8(self.inner.status.load(Ordering::Relaxed))
107 }
108
109 pub fn resume(&self, cx: &mut Cx, input: Value) -> Result<Value> {
116 if self.status() == CoroutineStatus::Done {
117 return Err(Error::Eval("coroutine is done".to_owned()));
118 }
119
120 {
121 let mut state = self
122 .inner
123 .state
124 .lock()
125 .map_err(|_| Error::PoisonedLock("coroutine"))?;
126 if state.cancelled {
127 self.inner
128 .status
129 .store(CoroutineStatus::Done.as_u8(), Ordering::Relaxed);
130 return Err(Error::Eval("coroutine is cancelled".to_owned()));
131 }
132 state.mailbox = Some(input.clone());
133 state.yielded = None;
134 }
135
136 self.inner
137 .status
138 .store(CoroutineStatus::Running.as_u8(), Ordering::Relaxed);
139
140 let handle = cx.factory().opaque(Arc::new(self.clone()))?;
141 let result = cx.call_value(self.inner.handler.clone(), Args::new(vec![handle, input]));
142
143 let yielded = {
144 let mut state = self
145 .inner
146 .state
147 .lock()
148 .map_err(|_| Error::PoisonedLock("coroutine"))?;
149 state.mailbox = None;
150 state.yielded.take()
151 };
152
153 match result {
154 Ok(value) => {
155 if let Some(yielded) = yielded {
156 self.inner
157 .status
158 .store(CoroutineStatus::Suspended.as_u8(), Ordering::Relaxed);
159 Ok(yielded)
160 } else {
161 self.inner
162 .status
163 .store(CoroutineStatus::Done.as_u8(), Ordering::Relaxed);
164 Ok(value)
165 }
166 }
167 Err(err) => {
168 self.inner
169 .status
170 .store(CoroutineStatus::Done.as_u8(), Ordering::Relaxed);
171 Err(err)
172 }
173 }
174 }
175
176 pub fn yield_value(&self, value: Value) -> Result<Value> {
180 let mut state = self
181 .inner
182 .state
183 .lock()
184 .map_err(|_| Error::PoisonedLock("coroutine"))?;
185 if self.status() != CoroutineStatus::Running {
186 return Err(Error::Eval(
187 "server/yield may only be used from a running coroutine".to_owned(),
188 ));
189 }
190 state.yielded = Some(value.clone());
191 Ok(value)
192 }
193
194 pub fn cancel(&self) -> Result<()> {
196 let mut state = self
197 .inner
198 .state
199 .lock()
200 .map_err(|_| Error::PoisonedLock("coroutine"))?;
201 state.cancelled = true;
202 state.mailbox = None;
203 state.yielded = None;
204 self.inner
205 .status
206 .store(CoroutineStatus::Done.as_u8(), Ordering::Relaxed);
207 Ok(())
208 }
209}
210
211impl Object for Coroutine {
212 fn display(&self, _cx: &mut Cx) -> Result<String> {
213 Ok("#<server-coroutine>".to_owned())
214 }
215
216 fn as_any(&self) -> &dyn std::any::Any {
217 self
218 }
219}
220
221impl sim_kernel::ObjectCompat for Coroutine {
222 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
223 cx.factory().class_stub(
224 sim_kernel::ClassId(0),
225 Symbol::qualified("server", "Coroutine"),
226 )
227 }
228 fn as_expr(&self, cx: &mut Cx) -> Result<sim_kernel::Expr> {
229 self.as_table(cx)?.object().as_expr(cx)
230 }
231 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
232 let state = self
233 .inner
234 .state
235 .lock()
236 .map_err(|_| Error::PoisonedLock("coroutine"))?;
237 let mailbox = match &state.mailbox {
238 Some(mailbox) => mailbox.clone(),
239 None => cx.factory().nil()?,
240 };
241 let cancelled = state.cancelled;
242 drop(state);
243 let address = self.inner.address.as_value(cx)?;
244 cx.factory().table(vec![
245 (
246 Symbol::new("kind"),
247 cx.factory().symbol(Symbol::new("coroutine"))?,
248 ),
249 (
250 Symbol::new("id"),
251 cx.factory().string(self.inner.id.to_string())?,
252 ),
253 (
254 Symbol::new("status"),
255 cx.factory().symbol(self.status().as_symbol())?,
256 ),
257 (Symbol::new("address"), address),
258 (Symbol::new("mailbox"), mailbox),
259 (Symbol::new("cancelled"), cx.factory().bool(cancelled)?),
260 ])
261 }
262}