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)]
165#[derive(Clone, Debug, PartialEq, Eq)]
167pub struct ServerFrame {
168 pub version: u16,
170 pub codec: Symbol,
172 pub msg_id: Option<u64>,
174 pub correlate: Option<u64>,
176 pub kind: FrameKind,
178 pub envelope: FrameEnvelope,
180 pub payload: Vec<u8>,
182}
183
184impl ServerFrame {
185 pub fn new(codec: Symbol, kind: FrameKind, envelope: FrameEnvelope, payload: Vec<u8>) -> Self {
187 Self {
188 version: SERVER_FRAME_VERSION,
189 codec,
190 msg_id: None,
191 correlate: None,
192 kind,
193 envelope,
194 payload,
195 }
196 }
197
198 pub fn from_expr(
203 cx: &mut Cx,
204 codec: Symbol,
205 kind: FrameKind,
206 expr: &Expr,
207 consistency: Consistency,
208 required_capabilities: Vec<CapabilityName>,
209 trace: bool,
210 ) -> Result<Self> {
211 let payload = encode_frame_payload(cx, &codec, expr, EncodeOptions::default())?;
212 Ok(Self::new(
213 codec,
214 kind,
215 FrameEnvelope {
216 consistency,
217 required_capabilities,
218 trace,
219 ..FrameEnvelope::default()
220 },
221 payload,
222 ))
223 }
224
225 pub fn decode_expr(&self, cx: &mut Cx, read_policy: ReadPolicy) -> Result<Expr> {
227 decode_frame_payload(
228 cx,
229 &self.codec,
230 &self.payload,
231 read_policy,
232 DecodeLimits::default(),
233 )
234 }
235}
236
237impl Object for ServerFrame {
238 fn display(&self, _cx: &mut Cx) -> Result<String> {
239 Ok(format!("#<server-frame {}>", self.kind.as_symbol()))
240 }
241
242 fn as_any(&self) -> &dyn std::any::Any {
243 self
244 }
245}
246
247impl sim_kernel::ObjectCompat for ServerFrame {
248 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
249 cx.factory().class_stub(
250 sim_kernel::ClassId(0),
251 Symbol::qualified("server", "ServerFrame"),
252 )
253 }
254 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
255 self.as_table(cx)?.object().as_expr(cx)
256 }
257 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
258 let msg_id = match self.msg_id {
259 Some(msg_id) => cx.factory().string(msg_id.to_string())?,
260 None => cx.factory().nil()?,
261 };
262 let correlate = match self.correlate {
263 Some(correlate) => cx.factory().string(correlate.to_string())?,
264 None => cx.factory().nil()?,
265 };
266 let envelope = self.envelope.as_value(cx)?;
267 cx.factory().table(vec![
268 (
269 Symbol::new("version"),
270 cx.factory().string(self.version.to_string())?,
271 ),
272 (
273 Symbol::new("codec"),
274 cx.factory().symbol(self.codec.clone())?,
275 ),
276 (Symbol::new("msg-id"), msg_id),
277 (Symbol::new("correlate"), correlate),
278 (
279 Symbol::new("kind"),
280 cx.factory().symbol(self.kind.as_symbol())?,
281 ),
282 (Symbol::new("envelope"), envelope),
283 (
284 Symbol::new("payload"),
285 cx.factory().bytes(self.payload.clone())?,
286 ),
287 ])
288 }
289}