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