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