Skip to main content

sim_lib_stream_core/cassette/
timing.rs

1use sim_kernel::{Expr, Result, Symbol};
2
3use crate::{
4    StreamEnvelope, StreamMetadata, StreamPacket,
5    buffer::{expr_kind, field, symbol_field},
6};
7
8use super::{
9    StreamCassetteTiming, bool_field, ensure_fields, optional_u64, optional_u64_expr, parse_usize,
10};
11
12impl StreamCassetteTiming {
13    /// Serializes the timing summary to an [`Expr`] map keyed by field symbol.
14    pub fn to_expr(&self) -> Expr {
15        Expr::Map(vec![
16            (
17                Expr::Symbol(Symbol::new("clock")),
18                Expr::Symbol(self.clock.clone()),
19            ),
20            (
21                Expr::Symbol(Symbol::new("packet-count")),
22                Expr::String(self.packet_count.to_string()),
23            ),
24            (
25                Expr::Symbol(Symbol::new("first-sequence")),
26                optional_u64_expr(self.first_sequence),
27            ),
28            (
29                Expr::Symbol(Symbol::new("last-sequence")),
30                optional_u64_expr(self.last_sequence),
31            ),
32            (Expr::Symbol(Symbol::new("finite")), Expr::Bool(self.finite)),
33        ])
34    }
35
36    /// Deserializes a timing summary from an [`Expr`] map produced by
37    /// [`to_expr`](StreamCassetteTiming::to_expr).
38    ///
39    /// Validates the field set and fails closed on missing or unexpected
40    /// fields or type mismatches.
41    pub fn from_expr(expr: &Expr) -> Result<Self> {
42        let Expr::Map(entries) = expr else {
43            return Err(sim_kernel::Error::TypeMismatch {
44                expected: "stream cassette timing map",
45                found: expr_kind(expr),
46            });
47        };
48        ensure_fields(
49            entries,
50            &[
51                "clock",
52                "packet-count",
53                "first-sequence",
54                "last-sequence",
55                "finite",
56            ],
57        )?;
58        Ok(Self {
59            clock: symbol_field(entries, "clock")?.clone(),
60            packet_count: parse_usize(entries, "packet-count")?,
61            first_sequence: optional_u64(field(entries, "first-sequence")?)?,
62            last_sequence: optional_u64(field(entries, "last-sequence")?)?,
63            finite: bool_field(entries, "finite")?,
64        })
65    }
66}
67
68pub(super) fn timing_from_envelopes(
69    metadata: &StreamMetadata,
70    envelopes: &[StreamEnvelope],
71) -> StreamCassetteTiming {
72    StreamCassetteTiming {
73        clock: metadata.clock().clone(),
74        packet_count: envelopes.len(),
75        first_sequence: envelopes.first().map(StreamEnvelope::sequence),
76        last_sequence: envelopes.last().map(StreamEnvelope::sequence),
77        finite: true,
78    }
79}
80
81pub(super) fn diagnostics_from_envelopes(envelopes: &[StreamEnvelope]) -> Vec<Symbol> {
82    let mut diagnostics = Vec::new();
83    for envelope in envelopes {
84        for diagnostic in envelope.diagnostics() {
85            push_unique(&mut diagnostics, diagnostic.clone());
86        }
87        if let StreamPacket::Diagnostic(packet) = envelope.packet() {
88            push_unique(&mut diagnostics, packet.kind().clone());
89        }
90    }
91    diagnostics
92}
93
94fn push_unique(symbols: &mut Vec<Symbol>, symbol: Symbol) {
95    if !symbols.contains(&symbol) {
96        symbols.push(symbol);
97    }
98}