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