Skip to main content

subtr_actor/stats/export/
mod.rs

1use 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}