1use std::collections::HashMap;
8
9use crate::event::Event;
10use crate::slug::Stage;
11use crate::token::Token;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct StageId(pub u16);
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct TokenId(pub u32);
20
21#[derive(Debug, Default)]
24pub struct InternTables {
25 stages: Vec<Stage>,
26 stage_index: HashMap<Stage, StageId>,
27 tokens: Vec<Token>,
28 token_index: HashMap<Token, TokenId>,
29}
30
31impl InternTables {
32 pub fn stage(&mut self, stage: &Stage) -> StageId {
34 if let Some(id) = self.stage_index.get(stage) {
35 return *id;
36 }
37 #[allow(clippy::cast_possible_truncation)] let id = StageId(self.stages.len() as u16);
39 self.stages.push(stage.clone());
40 self.stage_index.insert(stage.clone(), id);
41 id
42 }
43
44 pub fn token(&mut self, token: Token) -> TokenId {
46 if let Some(id) = self.token_index.get(&token) {
47 return *id;
48 }
49 #[allow(clippy::cast_possible_truncation)] let id = TokenId(self.tokens.len() as u32);
51 self.tokens.push(token);
52 self.token_index.insert(token, id);
53 id
54 }
55
56 #[must_use]
58 pub fn stage_name(&self, id: StageId) -> Option<&Stage> {
59 self.stages.get(id.0 as usize)
60 }
61
62 #[must_use]
64 pub fn token_id(&self, token: Token) -> Option<TokenId> {
65 self.token_index.get(&token).copied()
66 }
67}
68
69const NO_VARIANT: u8 = u8::MAX;
71
72#[derive(Debug, Default)]
78pub struct EventLog {
79 token_ids: Vec<TokenId>,
80 stage_ids: Vec<StageId>,
81 actors: Vec<u8>,
82 variants: Vec<u8>,
83 regions: Vec<u8>,
84 at_ms: Vec<u64>,
85 seqs: Vec<u32>,
86}
87
88impl EventLog {
89 pub fn push(&mut self, event: &Event, tables: &mut InternTables) {
91 self.token_ids.push(tables.token(event.token));
92 self.stage_ids.push(tables.stage(&event.stage));
93 self.actors.push(event.actor.code());
94 self.variants.push(event.variant.unwrap_or(NO_VARIANT));
95 self.regions.push(event.regions.unwrap_or(0));
96 self.at_ms.push(event.at.as_unix_ms());
97 self.seqs.push(event.seq.0);
98 }
99
100 #[must_use]
102 pub fn len(&self) -> usize {
103 self.token_ids.len()
104 }
105
106 #[must_use]
108 pub fn is_empty(&self) -> bool {
109 self.token_ids.is_empty()
110 }
111
112 #[must_use]
115 pub fn stage_counts(&self, token: TokenId, tables: &InternTables) -> Vec<(Stage, u64)> {
116 let mut counts = vec![0u64; tables.stages.len()];
117 for (t, s) in self.token_ids.iter().zip(&self.stage_ids) {
118 if *t == token {
119 counts[s.0 as usize] += 1;
120 }
121 }
122 counts
123 .into_iter()
124 .enumerate()
125 .filter(|(_, c)| *c > 0)
126 .filter_map(|(i, c)| {
127 #[allow(clippy::cast_possible_truncation)] tables.stage_name(StageId(i as u16)).map(|s| (s.clone(), c))
129 })
130 .collect()
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::event::{ActorClass, Seq};
138 use crate::{ResolverContext, Timestamp};
139
140 fn ev(token: Token, stage: Stage, seq: u32) -> Event {
141 Event {
142 token,
143 stage,
144 actor: ActorClass::from_context(&ResolverContext::human()),
145 at: Timestamp::from_unix_ms(u64::from(seq)),
146 seq: Seq(seq),
147 variant: if seq % 2 == 0 { Some(1) } else { None },
148 regions: None,
149 entry: None,
150 }
151 }
152
153 #[test]
154 fn interning_is_idempotent_and_dense() {
155 let mut tables = InternTables::default();
156 let a = tables.stage(&Stage::resolve());
157 let b = tables.stage(&Stage::resolve());
158 assert_eq!(a, b);
159 let c = tables.stage(&Stage::run());
160 assert_eq!(c.0, a.0 + 1, "dense, append-only ids");
161 }
162
163 #[test]
164 fn soa_counts_agree_with_naive_counting() {
165 let mut tables = InternTables::default();
166 let mut log = EventLog::default();
167 let t1 = Token::parse("one").unwrap();
168 let t2 = Token::parse("two").unwrap();
169 for i in 0..10 {
170 log.push(&ev(t1, Stage::resolve(), i), &mut tables);
171 }
172 for i in 0..3 {
173 log.push(&ev(t2, Stage::run(), i), &mut tables);
174 log.push(&ev(t1, Stage::run(), 100 + i), &mut tables);
175 }
176 let id1 = tables.token_id(t1).unwrap();
177 let counts = log.stage_counts(id1, &tables);
178 assert!(counts.contains(&(Stage::resolve(), 10)));
179 assert!(counts.contains(&(Stage::run(), 3)));
180 assert_eq!(log.len(), 16);
181 }
182}