Skip to main content

subtr_actor/stats/analysis_graph/
mod.rs

1#![allow(dead_code)]
2
3use std::collections::HashSet;
4
5use crate::Collector;
6use crate::{SubtrActorError, SubtrActorErrorVariant, SubtrActorResult};
7
8pub mod graph;
9pub use graph::{
10    AnalysisDependency, AnalysisGraph, AnalysisNode, AnalysisNodeDyn, AnalysisStateContext,
11    AnalysisStateRef,
12};
13
14#[macro_use]
15mod node_macros;
16
17mod collector;
18mod nodes;
19
20use crate::stats::calculators::FrameInput;
21
22#[allow(unused_imports)]
23pub use collector::AnalysisNodeCollector;
24#[allow(unused_imports)]
25pub use nodes::*;
26
27pub(crate) fn boxed_analysis_node_by_name(name: &str) -> Option<Box<dyn AnalysisNodeDyn>> {
28    match name {
29        "core" => Some(nodes::match_stats::boxed_default()),
30        "backboard" => Some(nodes::backboard::boxed_default()),
31        "ceiling_shot" => Some(nodes::ceiling_shot::boxed_default()),
32        "center" => Some(nodes::center::boxed_default()),
33        "double_tap" => Some(nodes::double_tap::boxed_default()),
34        "fifty_fifty" => Some(nodes::fifty_fifty::boxed_default()),
35        "possession" => Some(nodes::possession::boxed_default()),
36        "pressure" => Some(nodes::pressure::boxed_default()),
37        "rotation" => Some(nodes::rotation::boxed_default()),
38        "rush" => Some(nodes::rush::boxed_default()),
39        "touch" => Some(nodes::touch::boxed_default()),
40        "wall_aerial" => Some(nodes::wall_aerial::boxed_default()),
41        "wall_aerial_shot" => Some(nodes::wall_aerial_shot::boxed_default()),
42        "whiff" => Some(nodes::whiff::boxed_default()),
43        "wavedash" => Some(nodes::wavedash::boxed_default()),
44        "speed_flip" => Some(nodes::speed_flip::boxed_default()),
45        "half_flip" => Some(nodes::half_flip::boxed_default()),
46        "half_volley" => Some(nodes::half_volley::boxed_default()),
47        "flick" => Some(nodes::flick::boxed_default()),
48        "aerial_goal" => Some(nodes::goal_tags::boxed_aerial_goal()),
49        "high_aerial_goal" => Some(nodes::goal_tags::boxed_high_aerial_goal()),
50        "long_distance_goal" => Some(nodes::goal_tags::boxed_long_distance_goal()),
51        "own_half_goal" => Some(nodes::goal_tags::boxed_own_half_goal()),
52        "empty_net_goal" => Some(nodes::goal_tags::boxed_empty_net_goal()),
53        "counter_attack_goal" => Some(nodes::goal_tags::boxed_counter_attack_goal()),
54        "flick_goal" => Some(nodes::goal_tags::boxed_flick_goal()),
55        "double_tap_goal" => Some(nodes::goal_tags::boxed_double_tap_goal()),
56        "one_timer_goal" => Some(nodes::goal_tags::boxed_one_timer_goal()),
57        "passing_goal" => Some(nodes::goal_tags::boxed_passing_goal()),
58        "air_dribble_goal" => Some(nodes::goal_tags::boxed_air_dribble_goal()),
59        "flip_reset_goal" => Some(nodes::goal_tags::boxed_flip_reset_goal()),
60        "half_volley_goal" => Some(nodes::goal_tags::boxed_half_volley_goal()),
61        "musty_flick" => Some(nodes::musty_flick::boxed_default()),
62        "one_timer" => Some(nodes::one_timer::boxed_default()),
63        "pass" => Some(nodes::pass::boxed_default()),
64        "dodge_reset" => Some(nodes::dodge_reset::boxed_default()),
65        "ball_carry" => Some(nodes::ball_carry::boxed_default()),
66        "boost" => Some(nodes::boost::boxed_default()),
67        "bump" => Some(nodes::bump::boxed_default()),
68        "movement" => Some(nodes::movement::boxed_default()),
69        "positioning" => Some(nodes::positioning::boxed_default()),
70        "powerslide" => Some(nodes::powerslide::boxed_default()),
71        "demo" => Some(nodes::demo::boxed_default()),
72        _ => None,
73    }
74}
75
76pub fn graph_with_builtin_analysis_nodes<I, S>(names: I) -> SubtrActorResult<AnalysisGraph>
77where
78    I: IntoIterator<Item = S>,
79    S: AsRef<str>,
80{
81    let mut graph = AnalysisGraph::new().with_input_state_type::<FrameInput>();
82    graph.push_boxed_node(nodes::live_play::boxed_default());
83    let mut seen = HashSet::new();
84    for name in names {
85        let name = name.as_ref();
86        if !seen.insert(name.to_owned()) {
87            continue;
88        }
89        graph.push_boxed_node(boxed_analysis_node_by_name(name).ok_or_else(|| {
90            SubtrActorError::new(SubtrActorErrorVariant::UnknownStatsModuleName(
91                name.to_owned(),
92            ))
93        })?);
94    }
95    Ok(graph)
96}
97
98pub fn collect_analysis_graph_for_replay(
99    replay: &boxcars::Replay,
100    graph: AnalysisGraph,
101) -> SubtrActorResult<AnalysisGraph> {
102    let collector = collector::AnalysisNodeCollector::new(graph).process_replay(replay)?;
103    Ok(collector.into_graph())
104}
105
106pub fn collect_builtin_analysis_graph_for_replay<I, S>(
107    replay: &boxcars::Replay,
108    names: I,
109) -> SubtrActorResult<AnalysisGraph>
110where
111    I: IntoIterator<Item = S>,
112    S: AsRef<str>,
113{
114    collect_analysis_graph_for_replay(replay, graph_with_builtin_analysis_nodes(names)?)
115}
116
117pub fn all_analysis_nodes() -> Vec<Box<dyn AnalysisNodeDyn>> {
118    vec![
119        nodes::backboard::boxed_default(),
120        nodes::ball_carry::boxed_default(),
121        nodes::boost::boxed_default(),
122        nodes::bump::boxed_default(),
123        nodes::ceiling_shot::boxed_default(),
124        nodes::center::boxed_default(),
125        nodes::demo::boxed_default(),
126        nodes::dodge_reset::boxed_default(),
127        nodes::double_tap::boxed_default(),
128        nodes::fifty_fifty::boxed_default(),
129        nodes::match_stats::boxed_default(),
130        nodes::movement::boxed_default(),
131        nodes::flick::boxed_default(),
132        nodes::goal_tags::boxed_aerial_goal(),
133        nodes::goal_tags::boxed_high_aerial_goal(),
134        nodes::goal_tags::boxed_long_distance_goal(),
135        nodes::goal_tags::boxed_own_half_goal(),
136        nodes::goal_tags::boxed_empty_net_goal(),
137        nodes::goal_tags::boxed_counter_attack_goal(),
138        nodes::goal_tags::boxed_flick_goal(),
139        nodes::goal_tags::boxed_double_tap_goal(),
140        nodes::goal_tags::boxed_one_timer_goal(),
141        nodes::goal_tags::boxed_passing_goal(),
142        nodes::goal_tags::boxed_air_dribble_goal(),
143        nodes::goal_tags::boxed_flip_reset_goal(),
144        nodes::goal_tags::boxed_half_volley_goal(),
145        nodes::musty_flick::boxed_default(),
146        nodes::one_timer::boxed_default(),
147        nodes::pass::boxed_default(),
148        nodes::positioning::boxed_default(),
149        nodes::possession::boxed_default(),
150        nodes::powerslide::boxed_default(),
151        nodes::pressure::boxed_default(),
152        nodes::rotation::boxed_default(),
153        nodes::rush::boxed_default(),
154        nodes::settings::boxed_default(),
155        nodes::speed_flip::boxed_default(),
156        nodes::half_flip::boxed_default(),
157        nodes::half_volley::boxed_default(),
158        nodes::wavedash::boxed_default(),
159        nodes::touch::boxed_default(),
160        nodes::wall_aerial::boxed_default(),
161        nodes::wall_aerial_shot::boxed_default(),
162        nodes::whiff::boxed_default(),
163    ]
164}
165
166pub fn graph_with_all_analysis_nodes() -> AnalysisGraph {
167    let mut graph = AnalysisGraph::new().with_input_state_type::<FrameInput>();
168    graph.push_boxed_node(nodes::live_play::boxed_default());
169    for node in all_analysis_nodes() {
170        graph.push_boxed_node(node);
171    }
172    graph
173}
174
175#[cfg(test)]
176#[path = "module_tests.rs"]
177mod tests;