1use std::time::Duration;
2
3use sim_codec::DecodeLimits;
4use sim_kernel::{
5 CapabilityName, ClassRef, Consistency, Cx, EncodeOptions, Expr, Object, ReadPolicy, Result,
6 Symbol, Value,
7};
8
9use crate::codecio::{decode_frame_payload, encode_frame_payload};
10use crate::helpers::format_duration;
11
12const SERVER_FRAME_VERSION: u16 = 1;
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum FrameKind {
17 Request,
19 Response,
21 Error,
23 Notify,
25 StreamStart,
27 StreamChunk,
29 StreamEnd,
31 Negotiate {
33 codecs: Vec<Symbol>,
35 },
36 Ping,
38 Pong,
40 Lifecycle {
42 command: LifecycleCommand,
44 },
45 Trigger {
47 source: Symbol,
49 when_ms: u64,
51 },
52 Role {
54 role: Symbol,
56 hop: u32,
58 },
59}
60
61impl FrameKind {
62 pub fn as_symbol(&self) -> Symbol {
64 Symbol::new(match self {
65 Self::Request => "request",
66 Self::Response => "response",
67 Self::Error => "error",
68 Self::Notify => "notify",
69 Self::StreamStart => "stream-start",
70 Self::StreamChunk => "stream-chunk",
71 Self::StreamEnd => "stream-end",
72 Self::Negotiate { .. } => "negotiate",
73 Self::Ping => "ping",
74 Self::Pong => "pong",
75 Self::Lifecycle { .. } => "lifecycle",
76 Self::Trigger { .. } => "trigger",
77 Self::Role { .. } => "role",
78 })
79 }
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum LifecycleCommand {
85 Start,
87 Stop,
89 Suspend,
91 Resume,
93 Health,
95}
96
97#[derive(Clone, Debug, Default, PartialEq, Eq)]
99pub struct FrameEnvelope {
100 pub deadline: Option<Duration>,
102 pub consistency: Consistency,
104 pub trace: bool,
106 pub required_capabilities: Vec<CapabilityName>,
108 pub reply_codec_hint: Option<Symbol>,
110 pub role: Option<Symbol>,
112 pub hop: u32,
114 pub trigger_source: Option<Symbol>,
116}
117
118impl FrameEnvelope {
119 fn as_value(&self, cx: &mut Cx) -> Result<Value> {
120 let deadline = match self.deadline {
121 Some(deadline) => cx.factory().string(format_duration(deadline))?,
122 None => cx.factory().nil()?,
123 };
124 let required_capabilities = cx.factory().list(
125 self.required_capabilities
126 .iter()
127 .map(|capability| cx.factory().string(capability.as_str().to_owned()))
128 .collect::<Result<Vec<_>>>()?,
129 )?;
130 let reply_codec_hint = match &self.reply_codec_hint {
131 Some(codec) => cx.factory().symbol(codec.clone())?,
132 None => cx.factory().nil()?,
133 };
134 let role = match &self.role {
135 Some(role) => cx.factory().symbol(role.clone())?,
136 None => cx.factory().nil()?,
137 };
138 let trigger_source = match &self.trigger_source {
139 Some(source) => cx.factory().symbol(source.clone())?,
140 None => cx.factory().nil()?,
141 };
142 cx.factory().table(vec![
143 (Symbol::new("deadline"), deadline),
144 (
145 Symbol::new("consistency"),
146 cx.factory().symbol(self.consistency.as_symbol())?,
147 ),
148 (Symbol::new("trace"), cx.factory().bool(self.trace)?),
149 (Symbol::new("requires"), required_capabilities),
150 (Symbol::new("reply-codec-hint"), reply_codec_hint),
151 (Symbol::new("role"), role),
152 (
153 Symbol::new("hop"),
154 cx.factory().string(self.hop.to_string())?,
155 ),
156 (Symbol::new("trigger-source"), trigger_source),
157 ])
158 }
159}
160
161#[sim_citizen_derive::non_citizen(
162 reason = "server frame runtime shell; class-backed descriptor is server/Frame",
163 kind = "marker",
164 descriptor = "server/Frame"
165)]
166#[derive(Clone, Debug, PartialEq, Eq)]
168pub struct ServerFrame {
169 pub version: u16,
171 pub codec: Symbol,
173 pub msg_id: Option<u64>,
175 pub correlate: Option<u64>,
177 pub kind: FrameKind,
179 pub envelope: FrameEnvelope,
181 pub payload: Vec<u8>,
183}
184
185impl ServerFrame {
186 pub fn new(codec: Symbol, kind: FrameKind, envelope: FrameEnvelope, payload: Vec<u8>) -> Self {
188 Self {
189 version: SERVER_FRAME_VERSION,
190 codec,
191 msg_id: None,
192 correlate: None,
193 kind,
194 envelope,
195 payload,
196 }
197 }
198
199 pub fn from_expr(
204 cx: &mut Cx,
205 codec: Symbol,
206 kind: FrameKind,
207 expr: &Expr,
208 consistency: Consistency,
209 required_capabilities: Vec<CapabilityName>,
210 trace: bool,
211 ) -> Result<Self> {
212 let payload = encode_frame_payload(cx, &codec, expr, EncodeOptions::default())?;
213 Ok(Self::new(
214 codec,
215 kind,
216 FrameEnvelope {
217 consistency,
218 required_capabilities,
219 trace,
220 ..FrameEnvelope::default()
221 },
222 payload,
223 ))
224 }
225
226 pub fn decode_expr(&self, cx: &mut Cx, read_policy: ReadPolicy) -> Result<Expr> {
228 decode_frame_payload(
229 cx,
230 &self.codec,
231 &self.payload,
232 read_policy,
233 DecodeLimits::default(),
234 )
235 }
236}
237
238impl Object for ServerFrame {
239 fn display(&self, _cx: &mut Cx) -> Result<String> {
240 Ok(format!("#<server-frame {}>", self.kind.as_symbol()))
241 }
242
243 fn as_any(&self) -> &dyn std::any::Any {
244 self
245 }
246}
247
248impl sim_kernel::ObjectCompat for ServerFrame {
249 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
250 cx.factory().class_stub(
251 sim_kernel::ClassId(0),
252 Symbol::qualified("server", "ServerFrame"),
253 )
254 }
255 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
256 self.as_table(cx)?.object().as_expr(cx)
257 }
258 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
259 let msg_id = match self.msg_id {
260 Some(msg_id) => cx.factory().string(msg_id.to_string())?,
261 None => cx.factory().nil()?,
262 };
263 let correlate = match self.correlate {
264 Some(correlate) => cx.factory().string(correlate.to_string())?,
265 None => cx.factory().nil()?,
266 };
267 let envelope = self.envelope.as_value(cx)?;
268 cx.factory().table(vec![
269 (
270 Symbol::new("version"),
271 cx.factory().string(self.version.to_string())?,
272 ),
273 (
274 Symbol::new("codec"),
275 cx.factory().symbol(self.codec.clone())?,
276 ),
277 (Symbol::new("msg-id"), msg_id),
278 (Symbol::new("correlate"), correlate),
279 (
280 Symbol::new("kind"),
281 cx.factory().symbol(self.kind.as_symbol())?,
282 ),
283 (Symbol::new("envelope"), envelope),
284 (
285 Symbol::new("payload"),
286 cx.factory().bytes(self.payload.clone())?,
287 ),
288 ])
289 }
290}