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 }
150 }
151
152 #[test]
153 fn interning_is_idempotent_and_dense() {
154 let mut tables = InternTables::default();
155 let a = tables.stage(&Stage::resolve());
156 let b = tables.stage(&Stage::resolve());
157 assert_eq!(a, b);
158 let c = tables.stage(&Stage::run());
159 assert_eq!(c.0, a.0 + 1, "dense, append-only ids");
160 }
161
162 #[test]
163 fn soa_counts_agree_with_naive_counting() {
164 let mut tables = InternTables::default();
165 let mut log = EventLog::default();
166 let t1 = Token::parse("one").unwrap();
167 let t2 = Token::parse("two").unwrap();
168 for i in 0..10 {
169 log.push(&ev(t1, Stage::resolve(), i), &mut tables);
170 }
171 for i in 0..3 {
172 log.push(&ev(t2, Stage::run(), i), &mut tables);
173 log.push(&ev(t1, Stage::run(), 100 + i), &mut tables);
174 }
175 let id1 = tables.token_id(t1).unwrap();
176 let counts = log.stage_counts(id1, &tables);
177 assert!(counts.contains(&(Stage::resolve(), 10)));
178 assert!(counts.contains(&(Stage::run(), 3)));
179 assert_eq!(log.len(), 16);
180 }
181}