Skip to main content

subtr_actor/stats/export/
mod.rs

1use serde::{Deserialize, Deserializer, Serialize};
2
3mod air_dribble;
4mod backboard;
5mod ball_carry;
6mod ball_half;
7mod boost;
8mod bump;
9mod ceiling_shot;
10mod center;
11mod controlled_play;
12mod core;
13mod demo;
14mod dodge_reset;
15mod double_tap;
16mod fifty_fifty;
17mod flick;
18mod half_flip;
19mod half_volley;
20mod kickoff;
21mod movement;
22mod musty_flick;
23mod one_timer;
24mod pass;
25mod positioning;
26mod possession;
27mod powerslide;
28mod rotation;
29mod rush;
30mod speed_flip;
31mod territorial_pressure;
32mod touch;
33mod wall_aerial;
34mod wall_aerial_shot;
35mod wavedash;
36mod whiff;
37
38pub const LEGACY_STAT_VARIANT: &str = "legacy";
39pub const LABELED_STAT_VARIANT: &str = "labeled";
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum StatUnit {
44    Seconds,
45    Percent,
46    UnrealUnits,
47    UnrealUnitsPerSecond,
48    Boost,
49    BoostPerMinute,
50    Count,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, ts_rs::TS)]
54#[ts(export)]
55pub struct StatLabel {
56    pub key: &'static str,
57    pub value: &'static str,
58}
59
60impl StatLabel {
61    pub const fn new(key: &'static str, value: &'static str) -> Self {
62        Self { key, value }
63    }
64}
65
66impl<'de> Deserialize<'de> for StatLabel {
67    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
68    where
69        D: Deserializer<'de>,
70    {
71        #[derive(Deserialize)]
72        struct OwnedStatLabel {
73            key: String,
74            value: String,
75        }
76
77        let owned = OwnedStatLabel::deserialize(deserializer)?;
78        Ok(Self {
79            key: leak_string(owned.key),
80            value: leak_string(owned.value),
81        })
82    }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
86pub struct StatDescriptor {
87    pub domain: &'static str,
88    pub name: &'static str,
89    pub variant: &'static str,
90    pub unit: StatUnit,
91    #[serde(default, skip_serializing_if = "Vec::is_empty")]
92    pub labels: Vec<StatLabel>,
93}
94
95impl<'de> Deserialize<'de> for StatDescriptor {
96    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97    where
98        D: Deserializer<'de>,
99    {
100        #[derive(Deserialize)]
101        struct OwnedStatDescriptor {
102            domain: String,
103            name: String,
104            variant: String,
105            unit: StatUnit,
106            #[serde(default)]
107            labels: Vec<StatLabel>,
108        }
109
110        let owned = OwnedStatDescriptor::deserialize(deserializer)?;
111        Ok(Self {
112            domain: leak_string(owned.domain),
113            name: leak_string(owned.name),
114            variant: leak_string(owned.variant),
115            unit: owned.unit,
116            labels: owned.labels,
117        })
118    }
119}
120
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122#[serde(tag = "value_type", content = "value", rename_all = "snake_case")]
123pub enum StatValue {
124    Float(f32),
125    Unsigned(u32),
126    Signed(i32),
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
130#[ts(export)]
131pub struct LabeledCountEntry {
132    pub labels: Vec<StatLabel>,
133    pub count: u32,
134}
135
136#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
137#[ts(export)]
138pub struct LabeledCounts {
139    pub entries: Vec<LabeledCountEntry>,
140}
141
142impl LabeledCounts {
143    pub fn increment<I>(&mut self, labels: I)
144    where
145        I: IntoIterator<Item = StatLabel>,
146    {
147        let mut labels: Vec<_> = labels.into_iter().collect();
148        labels.sort();
149
150        if let Some(entry) = self.entries.iter_mut().find(|entry| entry.labels == labels) {
151            entry.count += 1;
152            return;
153        }
154
155        self.entries.push(LabeledCountEntry { labels, count: 1 });
156        self.entries
157            .sort_by(|left, right| left.labels.cmp(&right.labels));
158    }
159
160    pub fn count_matching(&self, required_labels: &[StatLabel]) -> u32 {
161        self.entries
162            .iter()
163            .filter(|entry| {
164                required_labels
165                    .iter()
166                    .all(|required_label| entry.labels.contains(required_label))
167            })
168            .map(|entry| entry.count)
169            .sum()
170    }
171
172    pub fn count_exact(&self, labels: &[StatLabel]) -> u32 {
173        let mut normalized_labels = labels.to_vec();
174        normalized_labels.sort();
175
176        self.entries
177            .iter()
178            .find(|entry| entry.labels == normalized_labels)
179            .map(|entry| entry.count)
180            .unwrap_or(0)
181    }
182
183    pub fn total(&self) -> u32 {
184        self.entries.iter().map(|entry| entry.count).sum()
185    }
186
187    pub fn complete_from_label_sets(label_sets: &[&[StatLabel]], counts: &Self) -> Self {
188        fn append_entries(
189            label_sets: &[&[StatLabel]],
190            index: usize,
191            labels: &mut Vec<StatLabel>,
192            counts: &LabeledCounts,
193            entries: &mut Vec<LabeledCountEntry>,
194        ) {
195            if index == label_sets.len() {
196                let mut normalized_labels = labels.clone();
197                normalized_labels.sort();
198                entries.push(LabeledCountEntry {
199                    count: counts.count_matching(&normalized_labels),
200                    labels: normalized_labels,
201                });
202                return;
203            }
204
205            for label in label_sets[index] {
206                labels.push(label.clone());
207                append_entries(label_sets, index + 1, labels, counts, entries);
208                labels.pop();
209            }
210        }
211
212        let mut entries = Vec::new();
213        append_entries(label_sets, 0, &mut Vec::new(), counts, &mut entries);
214        entries.sort_by(|left, right| left.labels.cmp(&right.labels));
215        Self { entries }
216    }
217
218    pub fn is_empty(&self) -> bool {
219        self.entries.is_empty()
220    }
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ts_rs::TS)]
224#[ts(export)]
225pub struct LabeledFloatSumEntry {
226    pub labels: Vec<StatLabel>,
227    pub value: f32,
228}
229
230#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
231#[ts(export)]
232pub struct LabeledFloatSums {
233    pub entries: Vec<LabeledFloatSumEntry>,
234}
235
236impl LabeledFloatSums {
237    pub fn add<I>(&mut self, labels: I, value: f32)
238    where
239        I: IntoIterator<Item = StatLabel>,
240    {
241        let mut labels: Vec<_> = labels.into_iter().collect();
242        labels.sort();
243
244        if let Some(entry) = self.entries.iter_mut().find(|entry| entry.labels == labels) {
245            entry.value += value;
246            return;
247        }
248
249        self.entries.push(LabeledFloatSumEntry { labels, value });
250        self.entries
251            .sort_by(|left, right| left.labels.cmp(&right.labels));
252    }
253
254    pub fn sum_matching(&self, required_labels: &[StatLabel]) -> f32 {
255        self.entries
256            .iter()
257            .filter(|entry| {
258                required_labels
259                    .iter()
260                    .all(|required_label| entry.labels.contains(required_label))
261            })
262            .map(|entry| entry.value)
263            .sum()
264    }
265
266    pub fn sum_exact(&self, labels: &[StatLabel]) -> f32 {
267        let mut normalized_labels = labels.to_vec();
268        normalized_labels.sort();
269
270        self.entries
271            .iter()
272            .find(|entry| entry.labels == normalized_labels)
273            .map(|entry| entry.value)
274            .unwrap_or(0.0)
275    }
276
277    pub fn is_empty(&self) -> bool {
278        self.entries.is_empty()
279    }
280}
281
282#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
283pub struct ExportedStat {
284    #[serde(flatten)]
285    pub descriptor: StatDescriptor,
286    pub value: StatValue,
287}
288
289impl ExportedStat {
290    pub fn float(domain: &'static str, name: &'static str, unit: StatUnit, value: f32) -> Self {
291        Self {
292            descriptor: StatDescriptor {
293                domain,
294                name,
295                variant: LEGACY_STAT_VARIANT,
296                unit,
297                labels: Vec::new(),
298            },
299            value: StatValue::Float(value),
300        }
301    }
302
303    pub fn unsigned(domain: &'static str, name: &'static str, unit: StatUnit, value: u32) -> Self {
304        Self {
305            descriptor: StatDescriptor {
306                domain,
307                name,
308                variant: LEGACY_STAT_VARIANT,
309                unit,
310                labels: Vec::new(),
311            },
312            value: StatValue::Unsigned(value),
313        }
314    }
315
316    pub fn signed(domain: &'static str, name: &'static str, unit: StatUnit, value: i32) -> Self {
317        Self {
318            descriptor: StatDescriptor {
319                domain,
320                name,
321                variant: LEGACY_STAT_VARIANT,
322                unit,
323                labels: Vec::new(),
324            },
325            value: StatValue::Signed(value),
326        }
327    }
328
329    pub fn unsigned_labeled(
330        domain: &'static str,
331        name: &'static str,
332        unit: StatUnit,
333        labels: Vec<StatLabel>,
334        value: u32,
335    ) -> Self {
336        Self {
337            descriptor: StatDescriptor {
338                domain,
339                name,
340                variant: LABELED_STAT_VARIANT,
341                unit,
342                labels,
343            },
344            value: StatValue::Unsigned(value),
345        }
346    }
347
348    pub fn float_labeled(
349        domain: &'static str,
350        name: &'static str,
351        unit: StatUnit,
352        labels: Vec<StatLabel>,
353        value: f32,
354    ) -> Self {
355        Self {
356            descriptor: StatDescriptor {
357                domain,
358                name,
359                variant: LABELED_STAT_VARIANT,
360                unit,
361                labels,
362            },
363            value: StatValue::Float(value),
364        }
365    }
366}
367
368pub trait StatFieldProvider {
369    fn visit_stat_fields(&self, visitor: &mut dyn FnMut(ExportedStat));
370
371    fn stat_fields(&self) -> Vec<ExportedStat> {
372        let mut fields = Vec::new();
373        self.visit_stat_fields(&mut |field| fields.push(field));
374        fields
375    }
376}
377
378fn leak_string(value: String) -> &'static str {
379    Box::leak(value.into_boxed_str())
380}