subtr_actor/collector/mod.rs
1//! The output layer: turn a processed replay into structured data, numeric
2//! features, or stat timelines.
3//!
4//! Everything here is built on the [`Collector`] trait — the core extension
5//! point. A collector observes the replay frame by frame (driven by a
6//! [`ReplayProcessor`]) and decides the sampling pace
7//! via the [`TimeAdvance`] it returns. Run one with
8//! [`Collector::process_replay`], or share a single pass across several with
9//! [`ReplayProcessor::process_all`](crate::ReplayProcessor::process_all).
10//!
11//! # Built-in collectors
12//!
13//! - [`ReplayDataCollector`] — a serde-friendly payload of frame data, metadata,
14//! and derived event streams for JSON export and playback UIs.
15//! - [`NDArrayCollector`] — a dense numeric feature matrix for ML; see the
16//! [`ndarray`] submodule for the feature-adder system.
17//! - Stats collectors in [`stats`] — graph-backed accumulated stats; see also
18//! [`crate::stats::timeline`] for cumulative-over-time stat timelines.
19//!
20//! # Wrappers
21//!
22//! - [`FrameRateDecorator`] downsamples any collector to a target FPS.
23//! - [`CallbackCollector`] attaches side-effecting hooks (progress, debugging)
24//! to a collector.
25
26pub mod callback;
27pub mod decorator;
28pub(crate) mod frame_resolution;
29pub mod ndarray;
30pub mod replay_data;
31pub mod stats;
32
33pub use self::ndarray::*;
34pub use callback::*;
35pub use decorator::*;
36pub use frame_resolution::StatsFrameResolution;
37pub use replay_data::*;
38pub use stats::*;
39
40use crate::*;
41use boxcars;
42
43/// Enum used to control the progress of time during replay processing.
44pub enum TimeAdvance {
45 /// Move forward in time by a specified amount.
46 Time(f32),
47 /// Advance to the next frame.
48 NextFrame,
49}
50
51/// Trait for types that collect data from a replay.
52///
53/// A `Collector` processes frames from a replay, potentially using a
54/// [`ReplayProcessor`] for access to additional replay data and context. It
55/// determines the pace of replay progression via the [`TimeAdvance`] return
56/// value.
57pub trait Collector {
58 /// Process a single frame from a replay.
59 ///
60 /// # Arguments
61 ///
62 /// * `processor` - The [`ReplayProcessor`] providing context for the replay.
63 /// * `frame` - The [`boxcars::Frame`] to process.
64 /// * `frame_number` - The number of the current frame.
65 /// * `current_time` - The current target time in the replay.
66 ///
67 /// # Returns
68 ///
69 /// Returns a [`TimeAdvance`] enum which determines the next step in replay
70 /// progression.
71 fn process_frame(
72 &mut self,
73 processor: &dyn ProcessorView,
74 frame: &boxcars::Frame,
75 frame_number: usize,
76 current_time: f32,
77 ) -> SubtrActorResult<TimeAdvance>;
78
79 /// Process an entire replay.
80 ///
81 /// # Arguments
82 ///
83 /// * `replay` - The [`boxcars::Replay`] to process.
84 ///
85 /// # Returns
86 ///
87 /// Returns the [`Collector`] itself, potentially modified by the processing
88 /// of the replay.
89 fn process_replay(mut self, replay: &boxcars::Replay) -> SubtrActorResult<Self>
90 where
91 Self: Sized,
92 {
93 ReplayProcessor::new(replay)?.process(&mut self)?;
94 Ok(self)
95 }
96
97 /// Finalize replay-derived state after the last frame has been processed.
98 ///
99 /// Collectors that aggregate state across frame boundaries can override
100 /// this to flush any in-progress segment once replay traversal is complete.
101 fn finish_replay(&mut self, _processor: &dyn ProcessorView) -> SubtrActorResult<()> {
102 Ok(())
103 }
104}
105
106impl<G> Collector for G
107where
108 G: FnMut(&dyn ProcessorView, &boxcars::Frame, usize, f32) -> SubtrActorResult<TimeAdvance>,
109{
110 fn process_frame(
111 &mut self,
112 processor: &dyn ProcessorView,
113 frame: &boxcars::Frame,
114 frame_number: usize,
115 current_time: f32,
116 ) -> SubtrActorResult<TimeAdvance> {
117 self(processor, frame, frame_number, current_time)
118 }
119}