1use std::{
2 sync::{
3 Arc, Mutex,
4 atomic::{AtomicU8, AtomicU64, Ordering},
5 },
6 time::Instant,
7};
8
9use sim_citizen_derive::non_citizen;
10use sim_kernel::{ClassRef, Cx, Expr, Object, Result, Symbol, Value};
11
12use crate::{
13 EvalSite, FrameRouter, IsolationPolicy, ServerAddress, ServerFrame, ServerRuntime,
14 TriggerHandle, symbol_list_value,
15};
16
17static NEXT_SERVER_ID: AtomicU64 = AtomicU64::new(1);
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum ThreadMode {
22 Main,
24 Coop,
26 Spawn,
28 Pool,
30 Coroutine(Box<ThreadMode>),
32}
33
34impl ThreadMode {
35 pub fn from_expr(expr: &Expr) -> Result<Self> {
38 match expr {
39 Expr::Symbol(symbol) => match symbol.name.as_ref() {
40 "main" => Ok(Self::Main),
41 "coop" => Ok(Self::Coop),
42 "spawn" => Ok(Self::Spawn),
43 "pool" => Ok(Self::Pool),
44 other => Err(sim_kernel::Error::Eval(format!(
45 "unsupported thread mode {other}"
46 ))),
47 },
48 Expr::List(items) | Expr::Vector(items) => {
49 let Some(Expr::Symbol(head)) = items.first() else {
50 return Err(sim_kernel::Error::TypeMismatch {
51 expected: "thread mode list starting with a symbol",
52 found: "non-symbol",
53 });
54 };
55 if head.name.as_ref() != "coroutine" {
56 return Err(sim_kernel::Error::Eval(format!(
57 "unsupported thread mode {}",
58 head
59 )));
60 }
61 let base = match items.get(1) {
62 Some(expr) => Self::from_expr(expr)?,
63 None => Self::Coop,
64 };
65 Ok(Self::Coroutine(Box::new(base)))
66 }
67 _ => Err(sim_kernel::Error::TypeMismatch {
68 expected: "thread mode expression",
69 found: "non-thread-mode",
70 }),
71 }
72 }
73
74 pub fn as_expr(&self) -> Expr {
76 match self {
77 Self::Main => Expr::Symbol(Symbol::new("main")),
78 Self::Coop => Expr::Symbol(Symbol::new("coop")),
79 Self::Spawn => Expr::Symbol(Symbol::new("spawn")),
80 Self::Pool => Expr::Symbol(Symbol::new("pool")),
81 Self::Coroutine(base) => {
82 Expr::List(vec![Expr::Symbol(Symbol::new("coroutine")), base.as_expr()])
83 }
84 }
85 }
86
87 pub fn is_available_now(&self) -> bool {
89 match self {
90 Self::Main | Self::Coop | Self::Spawn | Self::Pool => true,
91 Self::Coroutine(base) => matches!(base.as_ref(), Self::Main | Self::Coop),
92 }
93 }
94
95 pub fn is_coroutine(&self) -> bool {
97 matches!(self, Self::Coroutine(_))
98 }
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub enum ServerStatus {
104 Running,
106 Suspended,
108 Stopped,
110}
111
112impl ServerStatus {
113 fn as_u8(self) -> u8 {
114 match self {
115 Self::Running => 0,
116 Self::Suspended => 1,
117 Self::Stopped => 2,
118 }
119 }
120
121 fn from_u8(value: u8) -> Self {
122 match value {
123 0 => Self::Running,
124 1 => Self::Suspended,
125 2 => Self::Stopped,
126 _ => Self::Stopped,
127 }
128 }
129
130 fn as_symbol(self) -> Symbol {
131 Symbol::new(match self {
132 Self::Running => "running",
133 Self::Suspended => "suspended",
134 Self::Stopped => "stopped",
135 })
136 }
137}
138
139#[non_citizen(
140 reason = "live server handle; reconstruct configuration via server/Address descriptor and start ops",
141 kind = "handle",
142 descriptor = "server/Address"
143)]
144pub struct Server {
147 id: u64,
148 name: Option<Symbol>,
149 address: ServerAddress,
150 default_codec: Symbol,
151 supported_codecs: Vec<Symbol>,
152 thread: ThreadMode,
153 isolation: IsolationPolicy,
154 status: AtomicU8,
155 site: Arc<dyn EvalSite>,
156 spec: Vec<(Symbol, Expr)>,
157 router: Arc<FrameRouter>,
158 triggers: Arc<Mutex<Vec<Arc<TriggerHandle>>>>,
159 runtime: Option<Arc<ServerRuntime>>,
160 started_at: Instant,
161}
162
163impl Server {
164 #[allow(clippy::too_many_arguments)]
166 pub fn new(
167 address: ServerAddress,
168 default_codec: Symbol,
169 supported_codecs: Vec<Symbol>,
170 thread: ThreadMode,
171 isolation: IsolationPolicy,
172 name: Option<Symbol>,
173 site: Arc<dyn EvalSite>,
174 spec: Vec<(Symbol, Expr)>,
175 ) -> Result<Self> {
176 Self::with_runtime(
177 address,
178 default_codec,
179 supported_codecs,
180 thread,
181 isolation,
182 name,
183 site,
184 spec,
185 None,
186 )
187 }
188
189 #[allow(clippy::too_many_arguments)]
192 pub fn with_runtime(
193 address: ServerAddress,
194 default_codec: Symbol,
195 supported_codecs: Vec<Symbol>,
196 thread: ThreadMode,
197 isolation: IsolationPolicy,
198 name: Option<Symbol>,
199 site: Arc<dyn EvalSite>,
200 spec: Vec<(Symbol, Expr)>,
201 runtime: Option<Arc<ServerRuntime>>,
202 ) -> Result<Self> {
203 address.ensure_transport_available()?;
204 Ok(Self {
205 id: NEXT_SERVER_ID.fetch_add(1, Ordering::Relaxed),
206 name,
207 address,
208 default_codec,
209 supported_codecs,
210 thread,
211 isolation,
212 status: AtomicU8::new(ServerStatus::Running.as_u8()),
213 site,
214 spec,
215 router: Arc::new(FrameRouter::default()),
216 triggers: Arc::new(Mutex::new(Vec::new())),
217 runtime,
218 started_at: Instant::now(),
219 })
220 }
221
222 pub fn id(&self) -> u64 {
224 self.id
225 }
226
227 pub fn name(&self) -> Option<&Symbol> {
229 self.name.as_ref()
230 }
231
232 pub fn address(&self) -> &ServerAddress {
234 &self.address
235 }
236
237 pub fn default_codec(&self) -> &Symbol {
239 &self.default_codec
240 }
241
242 pub fn supported_codecs(&self) -> &[Symbol] {
244 &self.supported_codecs
245 }
246
247 pub fn thread(&self) -> &ThreadMode {
249 &self.thread
250 }
251
252 pub fn site(&self) -> &Arc<dyn EvalSite> {
254 &self.site
255 }
256
257 pub fn isolation(&self) -> &IsolationPolicy {
259 &self.isolation
260 }
261
262 pub fn spec(&self) -> &[(Symbol, Expr)] {
264 &self.spec
265 }
266
267 pub fn runtime(&self) -> Option<&Arc<ServerRuntime>> {
269 self.runtime.as_ref()
270 }
271
272 pub fn status(&self) -> ServerStatus {
274 ServerStatus::from_u8(self.status.load(Ordering::Relaxed))
275 }
276
277 pub fn set_status(&self, status: ServerStatus) {
279 self.status.store(status.as_u8(), Ordering::Relaxed);
280 }
281
282 pub fn uptime_millis(&self) -> u64 {
284 self.started_at.elapsed().as_millis() as u64
285 }
286
287 pub fn register_trigger(&self, trigger: Arc<TriggerHandle>) -> Result<()> {
289 self.triggers
290 .lock()
291 .map_err(|_| sim_kernel::Error::PoisonedLock("server triggers"))?
292 .push(trigger);
293 Ok(())
294 }
295
296 pub fn stop_triggers(&self) -> Result<()> {
298 for trigger in self.trigger_snapshots()? {
299 trigger.stop()?;
300 }
301 Ok(())
302 }
303
304 pub fn deliver_trigger_frame(&self, cx: &mut Cx, frame: ServerFrame) -> Result<()> {
306 self.router.push_inbound(frame.clone())?;
307 let _ = self.site.answer(cx, frame)?;
308 Ok(())
309 }
310
311 pub fn trigger_snapshots(&self) -> Result<Vec<Arc<TriggerHandle>>> {
313 Ok(self
314 .triggers
315 .lock()
316 .map_err(|_| sim_kernel::Error::PoisonedLock("server triggers"))?
317 .clone())
318 }
319
320 pub fn reflect_value(&self, cx: &mut Cx) -> Result<Value> {
322 let mut entries = table_entries(self, cx)?;
323 entries.extend(live_state_entries(self, cx)?);
324 cx.factory().table(entries)
325 }
326
327 pub fn health_value(&self, cx: &mut Cx) -> Result<Value> {
329 let (sessions, connections, messages_sent, messages_received) = self
330 .runtime
331 .as_ref()
332 .map(|runtime| {
333 (
334 runtime.session_count(),
335 runtime.connection_count(),
336 runtime.messages_sent(),
337 runtime.messages_received(),
338 )
339 })
340 .unwrap_or((0, 0, 0, 0));
341 cx.factory().table(vec![
342 (
343 Symbol::new("status"),
344 cx.factory().symbol(self.status().as_symbol())?,
345 ),
346 (
347 Symbol::new("uptime"),
348 cx.factory().string(self.uptime_millis().to_string())?,
349 ),
350 (
351 Symbol::new("sessions"),
352 cx.factory().string(sessions.to_string())?,
353 ),
354 (
355 Symbol::new("connections"),
356 cx.factory().string(connections.to_string())?,
357 ),
358 (
359 Symbol::new("messages-sent"),
360 cx.factory().string(messages_sent.to_string())?,
361 ),
362 (
363 Symbol::new("messages-received"),
364 cx.factory().string(messages_received.to_string())?,
365 ),
366 ])
367 }
368
369 pub fn sessions_value(&self, cx: &mut Cx) -> Result<Value> {
371 let Some(runtime) = &self.runtime else {
372 return cx.factory().list(Vec::new());
373 };
374 let sessions = runtime
375 .sessions()?
376 .into_iter()
377 .map(|session| session.as_value(cx))
378 .collect::<Result<Vec<_>>>()?;
379 cx.factory().list(sessions)
380 }
381}
382
383impl Object for Server {
384 fn display(&self, _cx: &mut Cx) -> Result<String> {
385 Ok("#<server>".to_owned())
386 }
387
388 fn as_any(&self) -> &dyn std::any::Any {
389 self
390 }
391}
392
393impl sim_kernel::ObjectCompat for Server {
394 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
395 cx.factory().class_stub(
396 sim_kernel::ClassId(0),
397 Symbol::qualified("server", "Server"),
398 )
399 }
400 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
401 self.as_table(cx)?.object().as_expr(cx)
402 }
403 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
404 let mut entries = table_entries(self, cx)?;
405 entries.extend(live_state_entries(self, cx)?);
406 cx.factory().table(entries)
407 }
408}
409
410impl Clone for Server {
411 fn clone(&self) -> Self {
412 Self {
413 id: self.id,
414 name: self.name.clone(),
415 address: self.address.clone(),
416 default_codec: self.default_codec.clone(),
417 supported_codecs: self.supported_codecs.clone(),
418 thread: self.thread.clone(),
419 isolation: self.isolation.clone(),
420 status: AtomicU8::new(self.status().as_u8()),
421 site: self.site.clone(),
422 spec: self.spec.clone(),
423 router: self.router.clone(),
424 triggers: self.triggers.clone(),
425 runtime: self.runtime.clone(),
426 started_at: self.started_at,
427 }
428 }
429}
430
431fn table_entries(server: &Server, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
432 let name = match server.name() {
433 Some(name) => cx.factory().symbol(name.clone())?,
434 None => cx.factory().nil()?,
435 };
436 let spec_entries = server
437 .spec()
438 .iter()
439 .map(|(key, value)| {
440 cx.factory()
441 .expr(Expr::List(vec![Expr::Symbol(key.clone()), value.clone()]))
442 })
443 .collect::<Result<Vec<_>>>()?;
444 let spec = cx.factory().list(spec_entries)?;
445 let address = server.address.as_value(cx)?;
446 let default_codec = cx.factory().symbol(server.default_codec.clone())?;
447 let supported_codecs = symbol_list_value(cx, &server.supported_codecs)?;
448 let thread = cx.factory().expr(server.thread.as_expr())?;
449 let site_kind = cx.factory().string(server.site.site_kind().to_owned())?;
450 let site_address = server.site.address().as_value(cx)?;
451 let site_codecs = symbol_list_value(cx, server.site.codecs())?;
452 let isolation = server.isolation.as_value(cx)?;
453 let listening = cx.factory().bool(server.runtime.is_some())?;
454 let next_msg_id = cx
455 .factory()
456 .string(server.router.peek_next_msg_id().to_string())?;
457 Ok(vec![
458 (
459 Symbol::new("kind"),
460 cx.factory().symbol(Symbol::new("server"))?,
461 ),
462 (
463 Symbol::new("id"),
464 cx.factory().string(server.id.to_string())?,
465 ),
466 (Symbol::new("name"), name),
467 (Symbol::new("address"), address),
468 (Symbol::new("default-codec"), default_codec),
469 (Symbol::new("supported-codecs"), supported_codecs),
470 (Symbol::new("thread"), thread),
471 (Symbol::new("site-kind"), site_kind),
472 (Symbol::new("site-address"), site_address),
473 (Symbol::new("site-codecs"), site_codecs),
474 (Symbol::new("isolation"), isolation),
475 (Symbol::new("listening"), listening),
476 (Symbol::new("spec"), spec),
477 (Symbol::new("next-msg-id"), next_msg_id),
478 ])
479}
480
481fn live_state_entries(server: &Server, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
482 let trigger_values = server
483 .trigger_snapshots()?
484 .into_iter()
485 .map(|trigger| trigger.reflect_value(cx))
486 .collect::<Result<Vec<_>>>()?;
487 let triggers = cx.factory().list(trigger_values)?;
488 let (sessions, connections, messages_sent, messages_received) = server
489 .runtime
490 .as_ref()
491 .map(|runtime| {
492 (
493 runtime.session_count(),
494 runtime.connection_count(),
495 runtime.messages_sent(),
496 runtime.messages_received(),
497 )
498 })
499 .unwrap_or((0, 0, 0, 0));
500 Ok(vec![
501 (
502 Symbol::new("status"),
503 cx.factory().symbol(server.status().as_symbol())?,
504 ),
505 (
506 Symbol::new("uptime"),
507 cx.factory().string(server.uptime_millis().to_string())?,
508 ),
509 (
510 Symbol::new("sessions"),
511 cx.factory().string(sessions.to_string())?,
512 ),
513 (
514 Symbol::new("connections"),
515 cx.factory().string(connections.to_string())?,
516 ),
517 (
518 Symbol::new("messages-sent"),
519 cx.factory().string(messages_sent.to_string())?,
520 ),
521 (
522 Symbol::new("messages-received"),
523 cx.factory().string(messages_received.to_string())?,
524 ),
525 (Symbol::new("triggers"), triggers),
526 (Symbol::new("line-driver"), cx.factory().nil()?),
527 ])
528}