subtr_actor/stats/analysis_graph/
mod.rs1use std::collections::HashSet;
65use std::sync::OnceLock;
66
67use crate::Collector;
68use crate::{SubtrActorError, SubtrActorErrorVariant, SubtrActorResult};
69
70pub mod graph;
71pub use graph::{
72 AnalysisDependency, AnalysisGraph, AnalysisNode, AnalysisNodeDyn, AnalysisStateContext,
73 AnalysisStateRef,
74};
75
76#[macro_use]
77mod node_macros;
78
79mod collector;
80mod nodes;
81
82use crate::stats::calculators::FrameInput;
83#[allow(unused_imports)]
84pub use collector::AnalysisNodeCollector;
85#[allow(unused_imports)]
86pub use nodes::*;
87
88type BuiltinNodeCtor = fn() -> Box<dyn AnalysisNodeDyn>;
90
91fn boxed_node<N>() -> Box<dyn AnalysisNodeDyn>
97where
98 N: AnalysisNode + Default,
99{
100 Box::new(N::default())
101}
102
103macro_rules! builtin_analysis_nodes {
104 ($($node:ty),+ $(,)?) => {
105 const BUILTIN_ANALYSIS_NODE_CTORS: &[BuiltinNodeCtor] = &[$(boxed_node::<$node>),+];
108 };
109}
110
111builtin_analysis_nodes! {
112 FrameInfoNode,
113 GameplayStateNode,
114 BallFrameStateNode,
115 PlayerFrameStateNode,
116 FrameEventsStateNode,
117 LivePlayNode,
118 MatchStatsNode,
119 BackboardNode,
120 BackboardBounceStateNode,
121 CeilingShotNode,
122 CenterNode,
123 ControlledPlayNode,
124 ContinuousBallControlNode,
125 DoubleTapNode,
126 FiftyFiftyNode,
127 FiftyFiftyStateNode,
128 KickoffNode,
129 PlayerPossessionNode,
130 LoosePossessionNode,
131 PossessionNode,
132 PossessionStateNode,
133 BallHalfNode,
134 BallThirdNode,
135 TerritorialPressureNode,
136 RotationNode,
137 RushNode,
138 TouchNode,
139 TouchStateNode,
140 WallAerialNode,
141 WallAerialShotNode,
142 WhiffNode,
143 WavedashNode,
144 FlipImpulseNode,
145 SpeedFlipNode,
146 HalfFlipNode,
147 HalfVolleyNode,
148 FlickNode,
149 AerialGoalNode,
150 HighAerialGoalNode,
151 LongDistanceGoalNode,
152 OwnHalfGoalNode,
153 EmptyNetGoalNode,
154 CounterAttackGoalNode,
155 SustainedPressureGoalNode,
156 KickoffGoalNode,
157 FlickGoalNode,
158 CeilingShotGoalNode,
159 DoubleTapGoalNode,
160 OneTimerGoalNode,
161 PassingGoalNode,
162 AirDribbleGoalNode,
163 FlipResetGoalNode,
164 FlipIntoBallGoalNode,
165 BumpGoalNode,
166 DemoGoalNode,
167 HalfVolleyGoalNode, OneTimerNode,
168 PassNode,
169 DodgeResetNode,
170 BallCarryNode,
171 AirDribbleNode,
172 BoostNode,
173 BumpNode,
174 MovementNode,
175 PositioningNode,
176 PowerslideNode,
177 PlayerVerticalStateNode,
178 DemoNode,
179 SettingsNode,
180 StatsProjectionNode,
181 StatsTimelineFrameNode,
182 StatsTimelineEventsNode,
183}
184
185fn builtin_analysis_node_registry() -> &'static [(&'static str, BuiltinNodeCtor)] {
188 static REGISTRY: OnceLock<Vec<(&'static str, BuiltinNodeCtor)>> = OnceLock::new();
189 REGISTRY.get_or_init(|| {
190 BUILTIN_ANALYSIS_NODE_CTORS
191 .iter()
192 .map(|&ctor| (ctor().name(), ctor))
193 .collect()
194 })
195}
196
197pub fn builtin_analysis_node_names() -> &'static [&'static str] {
198 static NAMES: OnceLock<Vec<&'static str>> = OnceLock::new();
199 NAMES.get_or_init(|| {
200 builtin_analysis_node_registry()
201 .iter()
202 .map(|(name, _)| *name)
203 .collect()
204 })
205}
206
207pub(crate) fn boxed_analysis_node_by_name(name: &str) -> Option<Box<dyn AnalysisNodeDyn>> {
208 builtin_analysis_node_registry()
209 .iter()
210 .find(|(candidate, _)| *candidate == name)
211 .map(|(_, ctor)| ctor())
212}
213
214pub fn graph_with_builtin_analysis_nodes<I, S>(names: I) -> SubtrActorResult<AnalysisGraph>
215where
216 I: IntoIterator<Item = S>,
217 S: AsRef<str>,
218{
219 let mut graph = AnalysisGraph::new().with_input_state_type::<FrameInput>();
220 let mut seen = HashSet::new();
221 for name in names {
222 let name = name.as_ref();
223 let node = boxed_analysis_node_by_name(name).ok_or_else(|| {
224 SubtrActorError::new(SubtrActorErrorVariant::UnknownStatsModuleName(
225 name.to_owned(),
226 ))
227 })?;
228 if !seen.insert(node.name()) {
229 continue;
230 }
231 graph.push_boxed_node(node);
232 }
233 Ok(graph)
234}
235
236pub fn collect_analysis_graph_for_replay(
237 replay: &boxcars::Replay,
238 graph: AnalysisGraph,
239) -> SubtrActorResult<AnalysisGraph> {
240 let collector = collector::AnalysisNodeCollector::new(graph).process_replay(replay)?;
241 Ok(collector.into_graph())
242}
243
244pub fn collect_builtin_analysis_graph_for_replay<I, S>(
245 replay: &boxcars::Replay,
246 names: I,
247) -> SubtrActorResult<AnalysisGraph>
248where
249 I: IntoIterator<Item = S>,
250 S: AsRef<str>,
251{
252 collect_analysis_graph_for_replay(replay, graph_with_builtin_analysis_nodes(names)?)
253}
254
255pub fn all_analysis_nodes() -> Vec<Box<dyn AnalysisNodeDyn>> {
256 builtin_analysis_node_registry()
257 .iter()
258 .map(|(_, ctor)| ctor())
259 .collect()
260}
261
262pub fn graph_with_all_analysis_nodes() -> AnalysisGraph {
263 let mut graph = AnalysisGraph::new().with_input_state_type::<FrameInput>();
264 for node in all_analysis_nodes() {
265 graph.push_boxed_node(node);
266 }
267 graph
268}
269
270#[cfg(test)]
271#[path = "module_tests.rs"]
272mod tests;
273
274#[cfg(test)]
275#[path = "possession_invariant_tests.rs"]
276mod possession_invariant_tests;