Skip to main content

sim_kernel/
stream_surface.rs

1//! Stream metadata and packet events: the contract for stream transport.
2//!
3//! The kernel defines the stream predicate vocabulary and the packet/frame
4//! event shapes; concrete stream transports and clocks live in library crates.
5
6use crate::{
7    card::card_kind_predicate,
8    claim::{Claim, ClaimPattern},
9    env::Cx,
10    error::{Error, Result},
11    event::{Event, EventKind, Tick},
12    id::Symbol,
13    ref_id::Ref,
14};
15
16/// Card-kind symbol marking a ref as a stream (`core/stream`).
17pub fn stream_kind() -> Symbol {
18    Symbol::qualified("core", "stream")
19}
20
21/// Predicate symbol naming a stream's clock (`stream/clock`).
22pub fn stream_clock_predicate() -> Symbol {
23    Symbol::qualified("stream", "clock")
24}
25
26/// Predicate symbol naming a stream's payload codec (`stream/codec`).
27pub fn stream_codec_predicate() -> Symbol {
28    Symbol::qualified("stream", "codec")
29}
30
31/// Predicate symbol naming a stream's transport (`stream/transport`).
32pub fn stream_transport_predicate() -> Symbol {
33    Symbol::qualified("stream", "transport")
34}
35
36/// Records the stream kind plus the given metadata predicates as facts, once.
37pub fn publish_stream_metadata_claims(
38    cx: &mut Cx,
39    stream: Ref,
40    metadata: impl IntoIterator<Item = (Symbol, Ref)>,
41) -> Result<()> {
42    insert_once(
43        cx,
44        stream.clone(),
45        card_kind_predicate(),
46        Ref::Symbol(stream_kind()),
47    )?;
48    for (predicate, object) in metadata {
49        insert_once(cx, stream.clone(), predicate, object)?;
50    }
51    Ok(())
52}
53
54/// Builds a stream packet event whose payload is a content or handle ref.
55pub fn stream_packet_event(run: Ref, seq: u64, ticks: Vec<Tick>, payload: Ref) -> Result<Event> {
56    match payload {
57        Ref::Content(_) | Ref::Handle(_) => {
58            Event::new(run, seq, ticks, EventKind::Chunk { payload })
59        }
60        Ref::Symbol(_) | Ref::Coord(_) => Err(Error::Eval(
61            "stream packet payload must be a content or handle ref".to_owned(),
62        )),
63    }
64}
65
66/// Builds a remote stream frame event; an alias for [`stream_packet_event`].
67pub fn remote_stream_frame_event(
68    run: Ref,
69    seq: u64,
70    ticks: Vec<Tick>,
71    frame: Ref,
72) -> Result<Event> {
73    stream_packet_event(run, seq, ticks, frame)
74}
75
76fn insert_once(cx: &mut Cx, subject: Ref, predicate: Symbol, object: Ref) -> Result<()> {
77    let exists = !cx
78        .query_facts(ClaimPattern::exact(
79            subject.clone(),
80            predicate.clone(),
81            object.clone(),
82        ))?
83        .is_empty();
84    if !exists {
85        cx.insert_fact(Claim::public(subject, predicate, object))?;
86    }
87    Ok(())
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::{ContentId, Coordinate, DefaultFactory, Expr, NoopEvalPolicy, card::card_for_ref};
94    use std::sync::Arc;
95
96    #[test]
97    fn stream_metadata_and_packet_events_use_claims_and_refs() {
98        let mut cx = Cx::new(
99            Arc::new(NoopEvalPolicy),
100            Arc::new(DefaultFactory),
101            crate::HandleSeed::new(7),
102        );
103        let stream = Ref::Handle(cx.fresh_handle());
104        publish_stream_metadata_claims(
105            &mut cx,
106            stream.clone(),
107            [(
108                stream_clock_predicate(),
109                Ref::Symbol(Symbol::qualified("clock", "sample")),
110            )],
111        )
112        .unwrap();
113
114        let claims = cx
115            .query_facts(ClaimPattern::exact(
116                stream,
117                stream_clock_predicate(),
118                Ref::Symbol(Symbol::qualified("clock", "sample")),
119            ))
120            .unwrap();
121        assert_eq!(claims.len(), 1);
122
123        let payload = Ref::Content(ContentId::from_bytes(
124            Symbol::qualified("core", "sha256"),
125            [7; 32],
126        ));
127        let event = stream_packet_event(
128            Ref::Symbol(Symbol::qualified("run", "one")),
129            0,
130            Vec::new(),
131            payload.clone(),
132        )
133        .unwrap();
134        assert!(matches!(event.kind, EventKind::Chunk { payload: actual } if actual == payload));
135    }
136
137    #[test]
138    fn stream_metadata_and_payload_rules_project_to_cards() {
139        let mut cx = Cx::new(
140            Arc::new(NoopEvalPolicy),
141            Arc::new(DefaultFactory),
142            crate::HandleSeed::new(7),
143        );
144        let stream = Ref::Handle(cx.fresh_handle());
145        publish_stream_metadata_claims(
146            &mut cx,
147            stream.clone(),
148            [
149                (
150                    stream_clock_predicate(),
151                    Ref::Symbol(Symbol::qualified("clock", "sample")),
152                ),
153                (
154                    stream_codec_predicate(),
155                    Ref::Symbol(Symbol::qualified("codec", "binary-base64")),
156                ),
157                (
158                    stream_transport_predicate(),
159                    Ref::Symbol(Symbol::qualified("stream", "memory")),
160                ),
161            ],
162        )
163        .unwrap();
164
165        let card = card_expr(&mut cx, stream.clone());
166        assert_eq!(
167            table_value(&card, "kind"),
168            Some(&Expr::Symbol(stream_kind()))
169        );
170
171        let run = Ref::Symbol(Symbol::qualified("run", "one"));
172        let ordinal = ContentId::from_bytes(Symbol::qualified("core", "sha256"), [3; 32]);
173        let coordinate = Ref::Coord(Coordinate {
174            space: Symbol::qualified("rank", "space"),
175            ordinal,
176        });
177        assert!(
178            stream_packet_event(run.clone(), 0, Vec::new(), Ref::Symbol(Symbol::new("bad")))
179                .is_err()
180        );
181        assert!(stream_packet_event(run, 0, Vec::new(), coordinate).is_err());
182    }
183
184    fn card_expr(cx: &mut Cx, subject: Ref) -> Expr {
185        card_for_ref(cx, subject)
186            .unwrap()
187            .object()
188            .as_expr(cx)
189            .unwrap()
190    }
191
192    fn table_value<'a>(expr: &'a Expr, key: &str) -> Option<&'a Expr> {
193        let Expr::Map(entries) = expr else {
194            return None;
195        };
196        entries.iter().find_map(|(entry_key, entry_value)| {
197            let Expr::Symbol(entry_key) = entry_key else {
198                return None;
199            };
200            (entry_key == &Symbol::new(key)).then_some(entry_value)
201        })
202    }
203}