Skip to main content

source2_demo/parser/
observer.rs

1use crate::parser::Context;
2use crate::proto::*;
3use crate::{Entity, EntityEvents, GameEvent, StringTable};
4
5#[cfg(feature = "dota")]
6use crate::event::CombatLogEntry;
7
8/// Result type for observers ([`anyhow::Result`])
9pub type ObserverResult = anyhow::Result<()>;
10
11
12bitflags::bitflags! {
13    /// Bitflags for declaring observer interests.
14    ///
15    /// Use these flags in the [`Observer::interests`] method to specify which
16    /// events your observer wants to receive. This allows the parser to skip
17    /// unnecessary processing for events no observer cares about.
18    ///
19    /// # Examples
20    ///
21    /// ```
22    /// use source2_demo::prelude::*;
23    ///
24    /// #[derive(Default)]
25    /// struct EntityTracker;
26    ///
27    /// impl Observer for EntityTracker {
28    ///     fn interests(&self) -> Interests {
29    ///         // Track entities and receive tick events
30    ///         Interests::ENABLE_ENTITY | Interests::TRACK_ENTITY | Interests::TICK_START
31    ///     }
32    ///
33    ///     fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
34    ///         // Handle entity updates
35    ///         Ok(())
36    ///     }
37    ///
38    ///     fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
39    ///         // Handle tick start
40    ///         Ok(())
41    ///     }
42    /// }
43    /// ```
44    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
45    pub struct Interests: u64 {
46        /// Interest in demo commands (EDemoCommands)
47        const DEMO       = 1 << 0;
48        /// Interest in net messages (NetMessages)
49        const NET        = 1 << 1;
50        /// Interest in SVC messages (SvcMessages)
51        const SVC        = 1 << 2;
52
53        /// Interest in base user messages (EBaseUserMessages)
54        const BASE_UM    = 1 << 3;
55        /// Interest in base game events (EBaseGameEvents) and game events
56        const BASE_GE    = 1 << 4;
57
58        /// Interest in tick start events
59        const TICK_START = 1 << 5;
60        /// Interest in tick end events
61        const TICK_END   = 1 << 6;
62
63        /// Enable entity tracking (required for entity callbacks)
64        const ENABLE_ENTITY     = 1 << 7;
65        /// Interest in entity create/update/delete events
66        const TRACK_ENTITY     = 1 << 8;
67        /// Enable string table tracking
68        const ENABLE_STRINGTAB  = 1 << 9;
69        /// Interest in string table update events
70        const TRACK_STRINGTAB  = 1 << 10;
71
72        /// Interest in replay end event
73        const STOP       = 1 << 11;
74
75        #[cfg(feature = "dota")]
76        /// Interest in Dota 2 user messages (EDotaUserMessages)
77        const DOTA_UM    = 1 << 12;
78
79        #[cfg(feature = "dota")]
80        /// Interest in combat log entries (Dota 2 only)
81        const COMBAT_LOG = 1 << 13;
82
83
84        #[cfg(feature = "deadlock")]
85        /// Interest in Citadel/Deadlock user messages (CitadelUserMessageIds)
86        const CITA_UM    = 1 << 14;
87
88        #[cfg(feature = "deadlock")]
89        /// Interest in Citadel/Deadlock game events (ECitadelGameEvents)
90        const CITA_GE    = 1 << 15;
91
92        #[cfg(feature = "cs2")]
93        /// Interest in CS2 user messages (ECstrike15UserMessages)
94        const CS2_UM     = 1 << 16;
95
96        #[cfg(feature = "cs2")]
97        /// Interest in CS2 game events (ECsgoGameEvents)
98        const CS2_GE     = 1 << 17;
99    }
100}
101
102
103/// Observer trait for handling demo file events.
104///
105/// Implement this trait to receive callbacks as the parser processes the demo file.
106/// You can either implement it manually or use the `#[observer]` attribute macro
107/// for a more convenient approach.
108///
109/// # Interest-based Filtering
110///
111/// The [`interests`](Observer::interests) method allows you to declare which events
112/// your observer wants to receive. This is crucial for performance as it allows the
113/// parser to skip processing events that no observer cares about.
114///
115/// # Examples
116///
117/// ## Using the `#[observer]` macro (recommended)
118///
119/// ```no_run
120/// use source2_demo::prelude::*;
121/// use source2_demo_protobufs::CDotaUserMsgChatMessage;
122///
123/// #[derive(Default)]
124/// struct GameStats {
125///     combat_logs: u32,
126///     messages: u32,
127/// }
128///
129/// #[observer]
130/// impl GameStats {
131///     #[on_message]
132///     fn on_chat_msg(&mut self, ctx: &Context, msg: CDotaUserMsgChatMessage) -> ObserverResult {
133///         self.messages += 1;
134///         Ok(())
135///     }
136///
137///     #[on_combat_log]
138///     fn on_combat_log(&mut self, ctx: &Context, entry: &CombatLogEntry) -> ObserverResult {
139///         self.combat_logs += 1;
140///         Ok(())
141///     }
142/// }
143/// ```
144///
145/// ## Manual implementation
146///
147/// ```no_run
148/// use source2_demo::prelude::*;
149///
150/// #[derive(Default)]
151/// struct EntityCounter {
152///     count: usize,
153/// }
154///
155/// impl Observer for EntityCounter {
156///     fn interests(&self) -> Interests {
157///         Interests::ENABLE_ENTITY | Interests::TRACK_ENTITY
158///     }
159///
160///     fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
161///         if event == EntityEvents::Created {
162///             self.count += 1;
163///         }
164///         Ok(())
165///     }
166/// }
167/// ```
168///
169/// ## Combining multiple interests
170///
171/// ```no_run
172/// use source2_demo::prelude::*;
173///
174/// #[derive(Default)]
175/// struct MultiObserver;
176///
177/// impl Observer for MultiObserver {
178///     fn interests(&self) -> Interests {
179///         Interests::TICK_START
180///             | Interests::TICK_END
181///             | Interests::ENABLE_ENTITY
182///             | Interests::TRACK_ENTITY
183///     }
184///
185///     fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
186///         println!("Tick {}", ctx.tick());
187///         Ok(())
188///     }
189///
190///     fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
191///         // Process entities
192///         Ok(())
193///     }
194/// }
195/// ```
196#[allow(unused_variables)]
197pub trait Observer {
198    /// Declares which events this observer is interested in.
199    ///
200    /// Return an empty [`Interests`] to receive no events, or combine flags
201    /// using the `|` operator. This method is called once when the observer
202    /// is registered.
203    ///
204    /// # Default
205    ///
206    /// Returns [`Interests::empty()`] by default (no events).
207    fn interests(&self) -> Interests {
208        Interests::empty()
209    }
210
211    /// Called when a demo command is received.
212    ///
213    /// Requires [`Interests::DEMO`] to be set.
214    #[cold]
215    #[inline(never)]
216    fn on_demo_command(
217        &mut self,
218        ctx: &Context,
219        msg_type: EDemoCommands,
220        msg: &[u8],
221    ) -> ObserverResult {
222        Ok(())
223    }
224
225    /// Called when a net message is received.
226    ///
227    /// Requires [`Interests::NET`] to be set.
228    #[cold]
229    #[inline(never)]
230    fn on_net_message(
231        &mut self,
232        ctx: &Context,
233        msg_type: NetMessages,
234        msg: &[u8],
235    ) -> ObserverResult {
236        Ok(())
237    }
238
239    /// Called when an SVC (server-to-client) message is received.
240    ///
241    /// Requires [`Interests::SVC`] to be set.
242    #[cold]
243    #[inline(never)]
244    fn on_svc_message(
245        &mut self,
246        ctx: &Context,
247        msg_type: SvcMessages,
248        msg: &[u8],
249    ) -> ObserverResult {
250        Ok(())
251    }
252
253    /// Called when a base user message is received.
254    ///
255    /// Requires [`Interests::BASE_UM`] to be set.
256    #[cold]
257    #[inline(never)]
258    fn on_base_user_message(
259        &mut self,
260        ctx: &Context,
261        msg_type: EBaseUserMessages,
262        msg: &[u8],
263    ) -> ObserverResult {
264        Ok(())
265    }
266
267    /// Called when a base game event is received.
268    ///
269    /// Requires [`Interests::BASE_GE`] to be set.
270    #[cold]
271    #[inline(never)]
272    fn on_base_game_event(
273        &mut self,
274        ctx: &Context,
275        msg_type: EBaseGameEvents,
276        msg: &[u8],
277    ) -> ObserverResult {
278        Ok(())
279    }
280
281    /// Called at the start of each tick.
282    ///
283    /// Requires [`Interests::TICK_START`] to be set.
284    #[cold]
285    #[inline(never)]
286    fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
287        Ok(())
288    }
289
290    /// Called at the end of each tick.
291    ///
292    /// Requires [`Interests::TICK_END`] to be set.
293    #[cold]
294    #[inline(never)]
295    fn on_tick_end(&mut self, ctx: &Context) -> ObserverResult {
296        Ok(())
297    }
298
299    /// Called when an entity is created, updated, or deleted.
300    ///
301    /// Requires [`Interests::TRACK_ENTITY`] and [`Interests::ENABLE_ENTITY`] to be set.
302    #[cold]
303    #[inline(never)]
304    fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
305        Ok(())
306    }
307
308    /// Called when a game event occurs.
309    ///
310    /// Requires [`Interests::BASE_GE`] to be set.
311    #[cold]
312    #[inline(never)]
313    fn on_game_event(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
314        Ok(())
315    }
316
317    /// Called when a string table is updated.
318    ///
319    /// Requires [`Interests::TRACK_STRINGTAB`] and [`Interests::ENABLE_STRINGTAB`] to be set.
320    #[cold]
321    #[inline(never)]
322    fn on_string_table(
323        &mut self,
324        ctx: &Context,
325        st: &StringTable,
326        modified: &[i32],
327    ) -> ObserverResult {
328        Ok(())
329    }
330
331    /// Called when the replay ends.
332    ///
333    /// Requires [`Interests::STOP`] to be set.
334    /// This is the last callback before parsing completes.
335    ///
336    /// # Arguments
337    ///
338    /// * `ctx` - Current replay context
339    #[cold]
340    #[inline(never)]
341    fn on_stop(&mut self, ctx: &Context) -> ObserverResult {
342        Ok(())
343    }
344
345    /// Called when a combat log entry is received (Dota 2 only).
346    ///
347    /// Combat log entries describe in-game events like damage, healing, kills, etc.
348    /// Only available with the `dota` feature enabled.
349    ///
350    /// Requires [`Interests::COMBAT_LOG`] to be set.
351    #[cold]
352    #[inline(never)]
353    #[cfg(feature = "dota")]
354    fn on_combat_log(&mut self, ctx: &Context, cle: &CombatLogEntry) -> ObserverResult {
355        Ok(())
356    }
357
358    /// Called when a Dota 2 user message is received.
359    ///
360    /// Dota 2 specific user messages. Only available with the `dota` feature enabled.
361    ///
362    /// Requires [`Interests::DOTA_UM`] to be set.
363    #[cold]
364    #[inline(never)]
365    #[cfg(feature = "dota")]
366    fn on_dota_user_message(
367        &mut self,
368        ctx: &Context,
369        msg_type: EDotaUserMessages,
370        msg: &[u8],
371    ) -> ObserverResult {
372        Ok(())
373    }
374
375    /// Called when a Citadel/Deadlock game event is received.
376    ///
377    /// Deadlock specific game events. Only available with the `deadlock` feature enabled.
378    ///
379    /// Requires [`Interests::CITA_GE`] to be set.
380    #[cold]
381    #[inline(never)]
382    #[cfg(feature = "deadlock")]
383    fn on_citadel_game_event(
384        &mut self,
385        ctx: &Context,
386        msg_type: ECitadelGameEvents,
387        msg: &[u8],
388    ) -> ObserverResult {
389        Ok(())
390    }
391
392    /// Called when a Citadel/Deadlock user message is received.
393    ///
394    /// Deadlock specific user messages. Only available with the `deadlock` feature enabled.
395    ///
396    /// Requires [`Interests::CITA_UM`] to be set.
397    #[cold]
398    #[inline(never)]
399    #[cfg(feature = "deadlock")]
400    fn on_citadel_user_message(
401        &mut self,
402        ctx: &Context,
403        msg_type: CitadelUserMessageIds,
404        msg: &[u8],
405    ) -> ObserverResult {
406        Ok(())
407    }
408
409    /// Called when a Counter-Strike 2 user message is received.
410    ///
411    /// CS2 specific user messages. Only available with the `cs2` feature enabled.
412    ///
413    /// Requires [`Interests::CS2_UM`] to be set.
414    #[cold]
415    #[inline(never)]
416    #[cfg(feature = "cs2")]
417    fn on_cs2_user_message(
418        &mut self,
419        ctx: &Context,
420        msg_type: ECstrike15UserMessages,
421        msg: &[u8],
422    ) -> ObserverResult {
423        Ok(())
424    }
425
426    /// Called when a Counter-Strike 2 game event is received.
427    ///
428    /// CS2 specific game events. Only available with the `cs2` feature enabled.
429    ///
430    /// Requires [`Interests::CS2_GE`] to be set.
431    #[cold]
432    #[inline(never)]
433    #[cfg(feature = "cs2")]
434    fn on_cs2_game_event(
435        &mut self,
436        ctx: &Context,
437        msg_type: ECsgoGameEvents,
438        msg: &[u8],
439    ) -> ObserverResult {
440        Ok(())
441    }
442}