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 PossessionNode,
131 PossessionStateNode,
132 BallHalfNode,
133 BallThirdNode,
134 TerritorialPressureNode,
135 RotationNode,
136 RushNode,
137 TouchNode,
138 TouchStateNode,
139 WallAerialNode,
140 WallAerialShotNode,
141 WhiffNode,
142 WavedashNode,
143 FlipImpulseNode,
144 SpeedFlipNode,
145 HalfFlipNode,
146 HalfVolleyNode,
147 FlickNode,
148 AerialGoalNode,
149 HighAerialGoalNode,
150 LongDistanceGoalNode,
151 OwnHalfGoalNode,
152 EmptyNetGoalNode,
153 CounterAttackGoalNode,
154 SustainedPressureGoalNode,
155 KickoffGoalNode,
156 FlickGoalNode,
157 CeilingShotGoalNode,
158 DoubleTapGoalNode,
159 OneTimerGoalNode,
160 PassingGoalNode,
161 AirDribbleGoalNode,
162 FlipResetGoalNode,
163 FlipIntoBallGoalNode,
164 BumpGoalNode,
165 DemoGoalNode,
166 HalfVolleyGoalNode, OneTimerNode,
167 PassNode,
168 DodgeResetNode,
169 BallCarryNode,
170 BoostNode,
171 BumpNode,
172 MovementNode,
173 PositioningNode,
174 PowerslideNode,
175 PlayerVerticalStateNode,
176 DemoNode,
177 SettingsNode,
178 StatsProjectionNode,
179 StatsTimelineFrameNode,
180 StatsTimelineEventsNode,
181}
182
183fn builtin_analysis_node_registry() -> &'static [(&'static str, BuiltinNodeCtor)] {
186 static REGISTRY: OnceLock<Vec<(&'static str, BuiltinNodeCtor)>> = OnceLock::new();
187 REGISTRY.get_or_init(|| {
188 BUILTIN_ANALYSIS_NODE_CTORS
189 .iter()
190 .map(|&ctor| (ctor().name(), ctor))
191 .collect()
192 })
193}
194
195pub fn builtin_analysis_node_names() -> &'static [&'static str] {
196 static NAMES: OnceLock<Vec<&'static str>> = OnceLock::new();
197 NAMES.get_or_init(|| {
198 builtin_analysis_node_registry()
199 .iter()
200 .map(|(name, _)| *name)
201 .collect()
202 })
203}
204
205pub(crate) fn boxed_analysis_node_by_name(name: &str) -> Option<Box<dyn AnalysisNodeDyn>> {
206 builtin_analysis_node_registry()
207 .iter()
208 .find(|(candidate, _)| *candidate == name)
209 .map(|(_, ctor)| ctor())
210}
211
212pub fn graph_with_builtin_analysis_nodes<I, S>(names: I) -> SubtrActorResult<AnalysisGraph>
213where
214 I: IntoIterator<Item = S>,
215 S: AsRef<str>,
216{
217 let mut graph = AnalysisGraph::new().with_input_state_type::<FrameInput>();
218 let mut seen = HashSet::new();
219 for name in names {
220 let name = name.as_ref();
221 let node = boxed_analysis_node_by_name(name).ok_or_else(|| {
222 SubtrActorError::new(SubtrActorErrorVariant::UnknownStatsModuleName(
223 name.to_owned(),
224 ))
225 })?;
226 if !seen.insert(node.name()) {
227 continue;
228 }
229 graph.push_boxed_node(node);
230 }
231 Ok(graph)
232}
233
234pub fn collect_analysis_graph_for_replay(
235 replay: &boxcars::Replay,
236 graph: AnalysisGraph,
237) -> SubtrActorResult<AnalysisGraph> {
238 let collector = collector::AnalysisNodeCollector::new(graph).process_replay(replay)?;
239 Ok(collector.into_graph())
240}
241
242pub fn collect_builtin_analysis_graph_for_replay<I, S>(
243 replay: &boxcars::Replay,
244 names: I,
245) -> SubtrActorResult<AnalysisGraph>
246where
247 I: IntoIterator<Item = S>,
248 S: AsRef<str>,
249{
250 collect_analysis_graph_for_replay(replay, graph_with_builtin_analysis_nodes(names)?)
251}
252
253pub fn all_analysis_nodes() -> Vec<Box<dyn AnalysisNodeDyn>> {
254 builtin_analysis_node_registry()
255 .iter()
256 .map(|(_, ctor)| ctor())
257 .collect()
258}
259
260pub fn graph_with_all_analysis_nodes() -> AnalysisGraph {
261 let mut graph = AnalysisGraph::new().with_input_state_type::<FrameInput>();
262 for node in all_analysis_nodes() {
263 graph.push_boxed_node(node);
264 }
265 graph
266}
267
268#[cfg(test)]
269#[path = "module_tests.rs"]
270mod tests;