source2_demo/parser/demo/runner.rs
1//! Demo runner implementations for parser execution.
2//!
3//! This module provides the [`DemoRunner`] trait which defines methods for
4//! controlling replay parsing execution.
5
6use crate::error::ParserError;
7use crate::parser::demo::DemoMessages;
8use crate::parser::Parser;
9use crate::proto::*;
10use crate::reader::*;
11use crate::Entity;
12use std::cmp::min;
13use std::mem;
14
15/// Trait for controlling replay parsing execution.
16///
17/// Provides methods to run the parser to completion, to a specific tick,
18/// or to jump to a tick without full processing.
19pub trait DemoRunner {
20 /// Processes the entire replay from start to finish.
21 ///
22 /// This method processes all demo commands sequentially, calling registered
23 /// observers for each event. The final packet is [`CDemoFileInfo`].
24 ///
25 /// # Errors
26 ///
27 /// Returns [`ParserError`] if parsing fails.
28 ///
29 /// # Examples
30 ///
31 /// ```no_run
32 /// use source2_demo::prelude::*;
33 ///
34 /// # fn main() -> anyhow::Result<()> {
35 /// let replay = std::fs::read("replay.dem")?;
36 /// let mut parser = Parser::new(&replay)?;
37 /// parser.register_observer::<MyObserver>();
38 /// parser.run_to_end()?;
39 /// # Ok(())
40 /// # }
41 /// # #[derive(Default)]
42 /// # struct MyObserver;
43 /// # impl Observer for MyObserver {}
44 /// ```
45 fn run_to_end(&mut self) -> Result<(), ParserError>;
46
47 /// Processes the replay up to a specific tick.
48 ///
49 /// Stops parsing when the specified tick is reached. All observers are
50 /// called for events up to and including the target tick.
51 ///
52 /// # Arguments
53 ///
54 /// * `target_tick` - The tick to parse up to (inclusive)
55 ///
56 /// # Errors
57 ///
58 /// Returns [`ParserError`] if parsing fails.
59 ///
60 /// # Examples
61 ///
62 /// ```no_run
63 /// use source2_demo::prelude::*;
64 ///
65 /// # fn main() -> anyhow::Result<()> {
66 /// let replay = std::fs::File::open("replay.dem")?;
67 /// let mut parser = Parser::from_reader(&replay)?;
68 ///
69 /// // Process first 5 minutes (30 ticks per second * 60 seconds * 5 minutes)
70 /// parser.run_to_tick(9000)?;
71 /// # Ok(())
72 /// # }
73 /// ```
74 fn run_to_tick(&mut self, target_tick: u32) -> Result<(), ParserError>;
75
76 /// Jumps to a specific tick without full processing.
77 ///
78 /// This is an optimized method that seeks to the target tick without
79 /// calling observers for intermediate events. Useful for jumping to
80 /// a specific point in a replay quickly.
81 ///
82 /// After jumping, you can continue parsing normally with observers active.
83 ///
84 /// # Arguments
85 ///
86 /// * `target_tick` - The tick to jump to
87 ///
88 /// # Errors
89 ///
90 /// Returns [`ParserError`] if seeking fails.
91 ///
92 /// # Examples
93 ///
94 /// ```no_run
95 /// use source2_demo::prelude::*;
96 ///
97 /// # fn main() -> anyhow::Result<()> {
98 /// let replay = std::fs::File::open("replay.dem")?;
99 /// let mut parser = Parser::from_reader(&replay)?;
100 ///
101 /// // Jump to 10 minutes in
102 /// parser.jump_to_tick(18000)?;
103 ///
104 /// // Now register observers and continue
105 /// parser.register_observer::<MyObserver>();
106 /// parser.run_to_tick(20000)?;
107 /// # Ok(())
108 /// # }
109 /// # #[derive(Default)]
110 /// # struct MyObserver;
111 /// # impl Observer for MyObserver {}
112 /// ```
113 fn jump_to_tick(&mut self, target_tick: u32) -> Result<(), ParserError>;
114}
115
116impl<'a, R> DemoRunner for Parser<'a, R>
117where
118 R: BitsReader + MessageReader,
119{
120 fn run_to_end(&mut self) -> Result<(), ParserError> {
121 self.prologue()?;
122
123 while let Some(message) = self.reader.read_next_message()? {
124 self.on_tick_start(message.tick)?;
125 self.on_demo_command(message.msg_type, message.buf.as_slice())?;
126 }
127 self.on_tick_end()?;
128
129 Ok(())
130 }
131
132 fn run_to_tick(&mut self, target_tick: u32) -> Result<(), ParserError> {
133 assert!(target_tick > self.context.tick || self.context.tick == u32::MAX);
134
135 self.prologue()?;
136
137 let target_tick = min(target_tick, self.last_tick);
138
139 while let Some(message) = self.reader.read_next_message()? {
140 self.on_tick_start(message.tick)?;
141 self.on_demo_command(message.msg_type, message.buf.as_slice())?;
142 if self.context.tick >= target_tick {
143 self.on_tick_end()?;
144 break;
145 }
146 }
147
148 Ok(())
149 }
150
151 fn jump_to_tick(&mut self, target_tick: u32) -> Result<(), ParserError> {
152 let fp_delta = if cfg!(feature = "deadlock") {
153 3600
154 } else {
155 1800
156 };
157
158 let target_tick = min(target_tick, self.last_tick);
159
160 if target_tick < self.context.tick {
161 self.context.last_full_packet_tick = u32::MAX;
162 self.context.tick = u32::MAX;
163 self.context.net_tick = u32::MAX;
164 self.reader.seek(16);
165
166 self.context.entities.entities_vec = vec![Entity::default(); 8192];
167
168 self.context.string_tables.tables.clear();
169 self.context.string_tables.name_to_table.clear();
170 self.context.game_events.list.clear();
171 }
172
173 self.prologue()?;
174
175 self.skip_deltas = true;
176 let observers = mem::take(&mut self.observers);
177
178 let mut first_fp_checked = self.context.last_full_packet_tick != u32::MAX;
179 let mut last_fp_checked = false;
180
181 while let Some(mut message) = self.reader.read_next_message()? {
182 self.context.previous_tick = self.context.tick;
183 self.context.tick = message.tick;
184
185 if message.msg_type == EDemoCommands::DemFullPacket {
186 self.context.last_full_packet_tick = self.context.tick;
187 }
188
189 let next_fp = self.context.last_full_packet_tick == u32::MAX
190 || self.context.last_full_packet_tick < target_tick
191 && (target_tick - self.context.last_full_packet_tick) > fp_delta;
192
193 if message.msg_type == EDemoCommands::DemFullPacket {
194 if next_fp && first_fp_checked {
195 message.msg_type = EDemoCommands::DemStringTables;
196 message.buf = CDemoFullPacket::decode(message.buf.as_slice())?
197 .string_table
198 .unwrap()
199 .encode_to_vec();
200 }
201
202 self.on_demo_command(message.msg_type, message.buf.as_slice())?;
203 }
204
205 if last_fp_checked {
206 self.on_demo_command(message.msg_type, message.buf.as_slice())?;
207 }
208
209 if message.msg_type == EDemoCommands::DemFullPacket && !first_fp_checked {
210 first_fp_checked = true;
211 }
212
213 if message.msg_type == EDemoCommands::DemFullPacket && !next_fp {
214 last_fp_checked = true;
215 self.skip_deltas = false;
216 }
217
218 if self.context.tick >= target_tick && first_fp_checked {
219 break;
220 }
221 }
222
223 self.skip_deltas = false;
224 self.observers = observers;
225
226 Ok(())
227 }
228}