subtr_actor/stats/export/
mod.rs1use serde::Serialize;
2
3mod ball_carry;
4mod boost;
5mod core;
6mod demo;
7mod dodge_reset;
8mod fifty_fifty;
9mod movement;
10mod musty_flick;
11mod positioning;
12mod possession;
13mod powerslide;
14mod pressure;
15mod rush;
16mod speed_flip;
17mod touch;
18
19pub const LEGACY_STAT_VARIANT: &str = "legacy";
20pub const LABELED_STAT_VARIANT: &str = "labeled";
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
23#[serde(rename_all = "snake_case")]
24pub enum StatUnit {
25 Seconds,
26 Percent,
27 UnrealUnits,
28 UnrealUnitsPerSecond,
29 Boost,
30 BoostPerMinute,
31 Count,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
35pub struct StatLabel {
36 pub key: &'static str,
37 pub value: &'static str,
38}
39
40impl StatLabel {
41 pub const fn new(key: &'static str, value: &'static str) -> Self {
42 Self { key, value }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
47pub struct StatDescriptor {
48 pub domain: &'static str,
49 pub name: &'static str,
50 pub variant: &'static str,
51 pub unit: StatUnit,
52 #[serde(skip_serializing_if = "Vec::is_empty")]
53 pub labels: Vec<StatLabel>,
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize)]
57#[serde(tag = "value_type", content = "value", rename_all = "snake_case")]
58pub enum StatValue {
59 Float(f32),
60 Unsigned(u32),
61 Signed(i32),
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
65pub struct LabeledCountEntry {
66 pub labels: Vec<StatLabel>,
67 pub count: u32,
68}
69
70#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
71pub struct LabeledCounts {
72 pub entries: Vec<LabeledCountEntry>,
73}
74
75impl LabeledCounts {
76 pub fn increment<I>(&mut self, labels: I)
77 where
78 I: IntoIterator<Item = StatLabel>,
79 {
80 let mut labels: Vec<_> = labels.into_iter().collect();
81 labels.sort();
82
83 if let Some(entry) = self.entries.iter_mut().find(|entry| entry.labels == labels) {
84 entry.count += 1;
85 return;
86 }
87
88 self.entries.push(LabeledCountEntry { labels, count: 1 });
89 self.entries
90 .sort_by(|left, right| left.labels.cmp(&right.labels));
91 }
92
93 pub fn count_matching(&self, required_labels: &[StatLabel]) -> u32 {
94 self.entries
95 .iter()
96 .filter(|entry| {
97 required_labels
98 .iter()
99 .all(|required_label| entry.labels.contains(required_label))
100 })
101 .map(|entry| entry.count)
102 .sum()
103 }
104
105 pub fn count_exact(&self, labels: &[StatLabel]) -> u32 {
106 let mut normalized_labels = labels.to_vec();
107 normalized_labels.sort();
108
109 self.entries
110 .iter()
111 .find(|entry| entry.labels == normalized_labels)
112 .map(|entry| entry.count)
113 .unwrap_or(0)
114 }
115
116 pub fn is_empty(&self) -> bool {
117 self.entries.is_empty()
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Serialize)]
122pub struct LabeledFloatSumEntry {
123 pub labels: Vec<StatLabel>,
124 pub value: f32,
125}
126
127#[derive(Debug, Clone, Default, PartialEq, Serialize)]
128pub struct LabeledFloatSums {
129 pub entries: Vec<LabeledFloatSumEntry>,
130}
131
132impl LabeledFloatSums {
133 pub fn add<I>(&mut self, labels: I, value: f32)
134 where
135 I: IntoIterator<Item = StatLabel>,
136 {
137 let mut labels: Vec<_> = labels.into_iter().collect();
138 labels.sort();
139
140 if let Some(entry) = self.entries.iter_mut().find(|entry| entry.labels == labels) {
141 entry.value += value;
142 return;
143 }
144
145 self.entries.push(LabeledFloatSumEntry { labels, value });
146 self.entries
147 .sort_by(|left, right| left.labels.cmp(&right.labels));
148 }
149
150 pub fn sum_matching(&self, required_labels: &[StatLabel]) -> f32 {
151 self.entries
152 .iter()
153 .filter(|entry| {
154 required_labels
155 .iter()
156 .all(|required_label| entry.labels.contains(required_label))
157 })
158 .map(|entry| entry.value)
159 .sum()
160 }
161
162 pub fn sum_exact(&self, labels: &[StatLabel]) -> f32 {
163 let mut normalized_labels = labels.to_vec();
164 normalized_labels.sort();
165
166 self.entries
167 .iter()
168 .find(|entry| entry.labels == normalized_labels)
169 .map(|entry| entry.value)
170 .unwrap_or(0.0)
171 }
172
173 pub fn is_empty(&self) -> bool {
174 self.entries.is_empty()
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Serialize)]
179pub struct ExportedStat {
180 #[serde(flatten)]
181 pub descriptor: StatDescriptor,
182 pub value: StatValue,
183}
184
185impl ExportedStat {
186 pub fn float(domain: &'static str, name: &'static str, unit: StatUnit, value: f32) -> Self {
187 Self {
188 descriptor: StatDescriptor {
189 domain,
190 name,
191 variant: LEGACY_STAT_VARIANT,
192 unit,
193 labels: Vec::new(),
194 },
195 value: StatValue::Float(value),
196 }
197 }
198
199 pub fn unsigned(domain: &'static str, name: &'static str, unit: StatUnit, value: u32) -> Self {
200 Self {
201 descriptor: StatDescriptor {
202 domain,
203 name,
204 variant: LEGACY_STAT_VARIANT,
205 unit,
206 labels: Vec::new(),
207 },
208 value: StatValue::Unsigned(value),
209 }
210 }
211
212 pub fn signed(domain: &'static str, name: &'static str, unit: StatUnit, value: i32) -> Self {
213 Self {
214 descriptor: StatDescriptor {
215 domain,
216 name,
217 variant: LEGACY_STAT_VARIANT,
218 unit,
219 labels: Vec::new(),
220 },
221 value: StatValue::Signed(value),
222 }
223 }
224
225 pub fn unsigned_labeled(
226 domain: &'static str,
227 name: &'static str,
228 unit: StatUnit,
229 labels: Vec<StatLabel>,
230 value: u32,
231 ) -> Self {
232 Self {
233 descriptor: StatDescriptor {
234 domain,
235 name,
236 variant: LABELED_STAT_VARIANT,
237 unit,
238 labels,
239 },
240 value: StatValue::Unsigned(value),
241 }
242 }
243
244 pub fn float_labeled(
245 domain: &'static str,
246 name: &'static str,
247 unit: StatUnit,
248 labels: Vec<StatLabel>,
249 value: f32,
250 ) -> Self {
251 Self {
252 descriptor: StatDescriptor {
253 domain,
254 name,
255 variant: LABELED_STAT_VARIANT,
256 unit,
257 labels,
258 },
259 value: StatValue::Float(value),
260 }
261 }
262}
263
264pub trait StatFieldProvider {
265 fn visit_stat_fields(&self, visitor: &mut dyn FnMut(ExportedStat));
266
267 fn stat_fields(&self) -> Vec<ExportedStat> {
268 let mut fields = Vec::new();
269 self.visit_stat_fields(&mut |field| fields.push(field));
270 fields
271 }
272}