sim_lib_stream_combinators/
recording.rs1use std::ops::RangeBounds;
2use std::sync::{Arc, Mutex};
3
4use sim_kernel::{
5 Cx, Error, Event, EventKind, EventLedger, Ref, Result, Severity, Symbol, Tick, value_from_ref,
6};
7use sim_lib_stream_core::{
8 StreamCassette, StreamDiagnostic, StreamItem, StreamMetadata, StreamPacket, StreamStats,
9 TransportProfile,
10};
11
12use crate::stream::{Stream, StreamNode};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
48pub struct StreamRecording {
49 metadata: StreamMetadata,
50 items: Vec<StreamItem>,
51}
52
53impl StreamRecording {
54 pub fn new(metadata: StreamMetadata, items: Vec<StreamItem>) -> Self {
56 Self { metadata, items }
57 }
58
59 pub fn metadata(&self) -> &StreamMetadata {
61 &self.metadata
62 }
63
64 pub fn items(&self) -> &[StreamItem] {
66 &self.items
67 }
68
69 pub fn len(&self) -> usize {
71 self.items.len()
72 }
73
74 pub fn is_empty(&self) -> bool {
76 self.items.is_empty()
77 }
78
79 pub fn replay(&self) -> Stream {
81 replay(self)
82 }
83
84 pub fn seek(&self, target: SeekTarget) -> Stream {
86 seek(self.replay(), target)
87 }
88
89 pub fn cassette(&self, profile: TransportProfile) -> Result<StreamCassette> {
91 StreamCassette::from_items(
92 self.metadata.clone(),
93 self.items.clone(),
94 profile,
95 StreamStats {
96 yielded: self.items.len() as u64,
97 ..StreamStats::default()
98 },
99 )
100 }
101}
102
103#[derive(Clone, Debug, PartialEq, Eq)]
114pub enum SeekTarget {
115 PacketIndex(usize),
117 ClockIndex {
119 clock: Symbol,
121 index: Ref,
123 },
124}
125
126impl SeekTarget {
127 pub fn packet_index(index: usize) -> Self {
129 Self::PacketIndex(index)
130 }
131
132 pub fn clock_index(clock: Symbol, index: Ref) -> Self {
134 Self::ClockIndex { clock, index }
135 }
136}
137
138pub const DEFAULT_RECORD_ITEM_LIMIT: usize = 1 << 20;
146
147pub fn record_bang(source: &Stream) -> Result<StreamRecording> {
153 record_bang_bounded(source, DEFAULT_RECORD_ITEM_LIMIT)
154}
155
156pub fn record_bang_bounded(source: &Stream, max_items: usize) -> Result<StreamRecording> {
163 let mut items = Vec::new();
164 while let Some(item) = source.next_packet()? {
165 if items.len() >= max_items {
166 return Err(Error::Eval(format!(
167 "cannot record more than {max_items} packets; source may be live or unbounded"
168 )));
169 }
170 items.push(item);
171 }
172 if !source.is_done()? {
173 return Err(Error::Eval(
174 "cannot record a stream that has not reached done".to_owned(),
175 ));
176 }
177 Ok(StreamRecording::new(source.metadata().clone(), items))
178}
179
180pub fn replay(recording: &StreamRecording) -> Stream {
182 Stream::pull(recording.metadata.clone(), recording.items.clone())
183}
184
185pub fn record_cassette_bang(source: &Stream, profile: TransportProfile) -> Result<StreamCassette> {
189 record_bang(source)?.cassette(profile)
190}
191
192pub fn record_cassette_bang_bounded(
197 source: &Stream,
198 profile: TransportProfile,
199 max_items: usize,
200) -> Result<StreamCassette> {
201 record_bang_bounded(source, max_items)?.cassette(profile)
202}
203
204pub fn replay_cassette(cassette: &StreamCassette) -> Result<Stream> {
206 Ok(Stream::from_value(Arc::new(
207 cassette.replay_stream_value()?,
208 )))
209}
210
211pub fn seek(source: Stream, target: SeekTarget) -> Stream {
217 Stream::new(SeekNode {
218 source,
219 target,
220 state: Mutex::new(SeekState::Pending { skipped: 0 }),
221 })
222}
223
224pub fn record_ledger_run(
228 cx: &mut Cx,
229 metadata: StreamMetadata,
230 ledger: &EventLedger,
231 run: &Ref,
232) -> Result<StreamRecording> {
233 record_events(cx, metadata, ledger.events_for_run(run))
234}
235
236pub fn record_ledger_slice<R>(
241 cx: &mut Cx,
242 metadata: StreamMetadata,
243 ledger: &EventLedger,
244 run: &Ref,
245 seq_range: R,
246) -> Result<StreamRecording>
247where
248 R: RangeBounds<u64>,
249{
250 record_events(
251 cx,
252 metadata,
253 ledger
254 .events_for_run(run)
255 .iter()
256 .filter(|event| seq_range.contains(&event.seq)),
257 )
258}
259
260pub fn record_events<'a>(
266 cx: &mut Cx,
267 metadata: StreamMetadata,
268 events: impl IntoIterator<Item = &'a Event>,
269) -> Result<StreamRecording> {
270 let mut items = Vec::new();
271 for event in events {
272 match &event.kind {
273 EventKind::Chunk { payload } => {
274 items.push(item_from_payload(cx, payload, event.ticks.clone())?);
275 }
276 EventKind::Diagnostic(diagnostic) => {
277 items.push(StreamItem::new(StreamPacket::Diagnostic(
278 diagnostic_packet(diagnostic),
279 )));
280 }
281 EventKind::Done => break,
282 EventKind::Failed(_) => {
283 return Err(Error::Eval(
284 "cannot record a failed stream event slice".to_owned(),
285 ));
286 }
287 EventKind::Started { .. }
288 | EventKind::Claim { .. }
289 | EventKind::Trace(_)
290 | EventKind::EffectRequested { .. }
291 | EventKind::EffectResolved { .. }
292 | EventKind::Capture { .. }
293 | EventKind::Card { .. }
294 | EventKind::Final(_) => {}
295 }
296 }
297 Ok(StreamRecording::new(metadata, items))
298}
299
300fn item_from_payload(cx: &mut Cx, payload: &Ref, ticks: Vec<Tick>) -> Result<StreamItem> {
301 let value = value_from_ref(cx, payload)?;
302 let packet = StreamPacket::try_from(value.object().as_expr(cx)?)?;
303 StreamItem::with_ticks(packet, ticks)
304}
305
306fn diagnostic_packet(diagnostic: &sim_kernel::Diagnostic) -> StreamDiagnostic {
307 let kind = diagnostic
308 .code
309 .clone()
310 .unwrap_or_else(|| Symbol::qualified("stream/combinator", "Diagnostic"));
311 let prefix = match diagnostic.severity {
312 Severity::Error => "error",
313 Severity::Warning => "warning",
314 Severity::Info => "info",
315 Severity::Note => "note",
316 };
317 StreamDiagnostic::new(kind, format!("{prefix}: {}", diagnostic.message))
318}
319
320struct SeekNode {
321 source: Stream,
322 target: SeekTarget,
323 state: Mutex<SeekState>,
324}
325
326enum SeekState {
327 Pending { skipped: usize },
328 Ready,
329 Drained,
330}
331
332enum SeekPoll {
333 Found(StreamItem),
334 Pending,
335 Drained,
336}
337
338impl StreamNode for SeekNode {
339 fn metadata(&self) -> &StreamMetadata {
340 self.source.metadata()
341 }
342
343 fn next_packet(&self) -> Result<Option<StreamItem>> {
344 let mut state = self
345 .state
346 .lock()
347 .map_err(|_| Error::PoisonedLock("seek stream"))?;
348 match *state {
349 SeekState::Ready => self.source.next_packet(),
350 SeekState::Drained => Ok(None),
351 SeekState::Pending { ref mut skipped } => {
352 let poll = seek_first(&self.source, &self.target, skipped)?;
353 match poll {
354 SeekPoll::Found(item) => {
355 *state = SeekState::Ready;
356 Ok(Some(item))
357 }
358 SeekPoll::Pending => Ok(None),
359 SeekPoll::Drained => {
360 *state = SeekState::Drained;
361 Ok(None)
362 }
363 }
364 }
365 }
366 }
367
368 fn is_done(&self) -> Result<bool> {
369 let state = self
370 .state
371 .lock()
372 .map_err(|_| Error::PoisonedLock("seek stream"))?;
373 match *state {
374 SeekState::Drained => Ok(true),
375 SeekState::Pending { .. } | SeekState::Ready => self.source.is_done(),
376 }
377 }
378}
379
380fn seek_first(source: &Stream, target: &SeekTarget, skipped: &mut usize) -> Result<SeekPoll> {
381 match target {
382 SeekTarget::PacketIndex(index) => {
383 while *skipped < *index {
384 match source.next_packet()? {
385 Some(_) => *skipped += 1,
386 None => {
387 return if source.is_done()? {
388 Ok(SeekPoll::Drained)
389 } else {
390 Ok(SeekPoll::Pending)
391 };
392 }
393 }
394 }
395 match source.next_packet()? {
396 Some(item) => Ok(SeekPoll::Found(item)),
397 None => {
398 if source.is_done()? {
399 Ok(SeekPoll::Drained)
400 } else {
401 Ok(SeekPoll::Pending)
402 }
403 }
404 }
405 }
406 SeekTarget::ClockIndex { clock, index } => loop {
407 match source.next_packet()? {
408 Some(item) => {
409 if item
410 .ticks()
411 .iter()
412 .any(|tick| &tick.clock == clock && &tick.index == index)
413 {
414 return Ok(SeekPoll::Found(item));
415 }
416 }
417 None => {
418 return if source.is_done()? {
419 Ok(SeekPoll::Drained)
420 } else {
421 Ok(SeekPoll::Pending)
422 };
423 }
424 }
425 },
426 }
427}