Skip to main content

source2_demo/parser/
observer.rs

1use crate::parser::Context;
2use crate::proto::*;
3use crate::{Entity, EntityEvents, GameEvent, StringTable};
4use std::cell::RefCell;
5use std::rc::Rc;
6
7#[cfg(feature = "dota")]
8use crate::event::CombatLogEntry;
9
10/// Result type for observer callbacks.
11///
12/// This is a convenience alias for `anyhow::Result<()>`.
13///
14/// Methods generated by the `#[observer]` macro may also return
15/// `Result<(), ParserError>` or another `Result` whose error can convert into
16/// `anyhow::Error`. Concrete error types, including `ParserError`, are kept
17/// inside the `anyhow::Error` and can be recovered with downcasting from
18/// [`ParserError::ObserverError`](crate::error::ParserError::ObserverError).
19///
20/// # Examples
21///
22/// ```no_run
23/// use source2_demo::prelude::*;
24///
25/// #[derive(Default)]
26/// struct FailingObserver;
27///
28/// #[observer]
29/// impl FailingObserver {
30///     #[on_tick_start]
31///     fn on_tick_start(&mut self) -> Result<(), ParserError> {
32///         Err(ParserError::WrongMagic)
33///     }
34/// }
35///
36/// let mut parser = Parser::from_reader(std::fs::File::open("replay.dem")?)?;
37/// parser.register_observer::<FailingObserver>();
38///
39/// match parser.run_to_end() {
40///     Err(ParserError::ObserverError(error)) => {
41///         if let Some(ParserError::WrongMagic) = error.downcast_ref::<ParserError>() {
42///             println!("observer returned ParserError::WrongMagic");
43///         }
44///     }
45///     Err(error) => {
46///         println!("parser error: {error}");
47///     }
48///     Ok(()) => {}
49/// }
50/// # Ok::<(), Box<dyn std::error::Error>>(())
51/// ```
52pub type ObserverResult = anyhow::Result<()>;
53
54bitflags::bitflags! {
55    /// Bitflags for declaring observer interests.
56    ///
57    /// Use these flags in the [`Observer::interests`] method to specify which
58    /// events your observer wants to receive. This allows the parser to skip
59    /// unnecessary processing for events no observer cares about.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use source2_demo::prelude::*;
65    ///
66    /// #[derive(Default)]
67    /// struct EntityTracker;
68    ///
69    /// impl Observer for EntityTracker {
70    ///     fn interests(&self) -> Interests {
71    ///         // Track entities and receive tick events
72    ///         Interests::ENTITY_STATE | Interests::ENTITY_EVENTS | Interests::TICK_START
73    ///     }
74    ///
75    ///     fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
76    ///         // Handle entity updates
77    ///         Ok(())
78    ///     }
79    ///
80    ///     fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
81    ///         // Handle tick start
82    ///         Ok(())
83    ///     }
84    /// }
85    /// ```
86    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
87    pub struct Interests: u64 {
88        /// Interest in outer demo messages (`EDemoCommands`).
89        const DEMO_MESSAGE = 1 << 0;
90        /// Interest in net messages (`NetMessages`).
91        const NET_MESSAGE = 1 << 1;
92        /// Interest in SVC messages (`SvcMessages`).
93        const SVC_MESSAGE = 1 << 2;
94
95        /// Interest in base user messages (`EBaseUserMessages`).
96        const BASE_USER_MESSAGE = 1 << 3;
97        /// Interest in base game events (`EBaseGameEvents`) and decoded game events.
98        const BASE_GAME_EVENT = 1 << 4;
99
100        /// Interest in tick start events.
101        const TICK_START = 1 << 5;
102        /// Interest in tick end events.
103        const TICK_END = 1 << 6;
104
105        /// Maintain entity state while parsing.
106        const ENTITY_STATE = 1 << 7;
107        /// Interest in entity create/update/delete events.
108        const ENTITY_EVENTS = 1 << 8;
109        /// Maintain string table state while parsing.
110        const STRING_TABLE_STATE = 1 << 9;
111        /// Interest in string table update events.
112        const STRING_TABLE_ENTRIES = 1 << 10;
113
114        /// Interest in the replay end event.
115        const REPLAY_END = 1 << 11;
116
117        #[cfg(feature = "dota")]
118        /// Interest in Dota 2 user messages (`EDotaUserMessages`).
119        const DOTA_USER_MESSAGE = 1 << 12;
120
121        #[cfg(feature = "dota")]
122        /// Interest in combat log entries (Dota 2 only).
123        const COMBAT_LOG_ENTRIES = 1 << 13;
124
125
126        #[cfg(feature = "deadlock")]
127        /// Interest in Citadel/Deadlock user messages (`CitadelUserMessageIds`).
128        const CITADEL_USER_MESSAGE = 1 << 14;
129
130        #[cfg(feature = "deadlock")]
131        /// Interest in Citadel/Deadlock game events (`ECitadelGameEvents`).
132        const CITADEL_GAME_EVENT = 1 << 15;
133
134        #[cfg(feature = "cs2")]
135        /// Interest in CS2 user messages (`ECstrike15UserMessages`).
136        const CS2_USER_MESSAGE = 1 << 16;
137
138        #[cfg(feature = "cs2")]
139        /// Interest in CS2 game events (`ECsgoGameEvents`).
140        const CS2_GAME_EVENT = 1 << 17;
141    }
142}
143
144/// Observer trait for handling demo file events.
145///
146/// Implement this trait to receive callbacks as the parser processes the demo
147/// file. You can either implement it manually or use the `#[observer]`
148/// attribute macro for a more convenient approach.
149///
150/// # Interest-based Filtering
151///
152/// The [`interests`](Observer::interests) method allows you to declare which
153/// events your observer wants to receive. This is crucial for performance as it
154/// allows the parser to skip processing events that no observer cares about.
155///
156/// # Examples
157///
158/// ## Using the `#[observer]` macro (recommended)
159///
160/// ```ignore
161/// use source2_demo::prelude::*;
162/// use source2_demo_protobufs::CDotaUserMsgChatMessage;
163///
164/// #[derive(Default)]
165/// struct GameStats {
166///     combat_logs: u32,
167///     messages: u32,
168/// }
169///
170/// #[observer]
171/// impl GameStats {
172///     #[on_message]
173///     fn on_chat_msg(&mut self, ctx: &Context, msg: CDotaUserMsgChatMessage) -> ObserverResult {
174///         self.messages += 1;
175///         Ok(())
176///     }
177///
178///     #[on_combat_log]
179///     fn on_combat_log(&mut self, ctx: &Context, entry: &CombatLogEntry) -> ObserverResult {
180///         self.combat_logs += 1;
181///         Ok(())
182///     }
183/// }
184/// ```
185///
186/// ## Manual implementation
187///
188/// ```no_run
189/// use source2_demo::prelude::*;
190///
191/// #[derive(Default)]
192/// struct EntityCounter {
193///     count: usize,
194/// }
195///
196/// impl Observer for EntityCounter {
197///     fn interests(&self) -> Interests {
198///         Interests::ENTITY_STATE | Interests::ENTITY_EVENTS
199///     }
200///
201///     fn on_entity(
202///         &mut self,
203///         ctx: &Context,
204///         event: EntityEvents,
205///         entity: &Entity,
206///     ) -> ObserverResult {
207///         if event == EntityEvents::Created {
208///             self.count += 1;
209///         }
210///         Ok(())
211///     }
212/// }
213/// ```
214///
215/// ## Combining multiple interests
216///
217/// ```no_run
218/// use source2_demo::prelude::*;
219///
220/// #[derive(Default)]
221/// struct MultiObserver;
222///
223/// impl Observer for MultiObserver {
224///     fn interests(&self) -> Interests {
225///         Interests::TICK_START
226///             | Interests::TICK_END
227///             | Interests::ENTITY_STATE
228///             | Interests::ENTITY_EVENTS
229///     }
230///
231///     fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
232///         println!("Tick {}", ctx.tick());
233///         Ok(())
234///     }
235///
236///     fn on_entity(
237///         &mut self,
238///         ctx: &Context,
239///         event: EntityEvents,
240///         entity: &Entity,
241///     ) -> ObserverResult {
242///         // Process entities
243///         Ok(())
244///     }
245/// }
246/// ```
247#[allow(unused_variables)]
248pub trait Observer {
249    /// Declares which events this observer is interested in.
250    ///
251    /// Return an empty [`Interests`] to receive no events, or combine flags
252    /// using the `|` operator. This method is called once when the observer
253    /// is registered.
254    ///
255    /// # Default
256    ///
257    /// Returns [`Interests::empty()`] by default (no events).
258    fn interests(&self) -> Interests {
259        Interests::empty()
260    }
261
262    /// Called when a demo command is received.
263    ///
264    /// Requires [`Interests::DEMO_MESSAGE`] to be set.
265    #[cold]
266    #[inline(never)]
267    fn on_demo_command(
268        &mut self,
269        ctx: &Context,
270        msg_type: EDemoCommands,
271        msg: &[u8],
272    ) -> ObserverResult {
273        Ok(())
274    }
275
276    /// Called when a net message is received.
277    ///
278    /// Requires [`Interests::NET_MESSAGE`] to be set.
279    #[cold]
280    #[inline(never)]
281    fn on_net_message(
282        &mut self,
283        ctx: &Context,
284        msg_type: NetMessages,
285        msg: &[u8],
286    ) -> ObserverResult {
287        Ok(())
288    }
289
290    /// Called when an SVC (server-to-client) message is received.
291    ///
292    /// Requires [`Interests::SVC_MESSAGE`] to be set.
293    #[cold]
294    #[inline(never)]
295    fn on_svc_message(
296        &mut self,
297        ctx: &Context,
298        msg_type: SvcMessages,
299        msg: &[u8],
300    ) -> ObserverResult {
301        Ok(())
302    }
303
304    /// Called when a base user message is received.
305    ///
306    /// Requires [`Interests::BASE_USER_MESSAGE`] to be set.
307    #[cold]
308    #[inline(never)]
309    fn on_base_user_message(
310        &mut self,
311        ctx: &Context,
312        msg_type: EBaseUserMessages,
313        msg: &[u8],
314    ) -> ObserverResult {
315        Ok(())
316    }
317
318    /// Called when a base game event is received.
319    ///
320    /// Requires [`Interests::BASE_GAME_EVENT`] to be set.
321    #[cold]
322    #[inline(never)]
323    fn on_base_game_event(
324        &mut self,
325        ctx: &Context,
326        msg_type: EBaseGameEvents,
327        msg: &[u8],
328    ) -> ObserverResult {
329        Ok(())
330    }
331
332    /// Called at the start of each tick.
333    ///
334    /// Requires [`Interests::TICK_START`] to be set.
335    #[cold]
336    #[inline(never)]
337    fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
338        Ok(())
339    }
340
341    /// Called at the end of each tick.
342    ///
343    /// Requires [`Interests::TICK_END`] to be set.
344    #[cold]
345    #[inline(never)]
346    fn on_tick_end(&mut self, ctx: &Context) -> ObserverResult {
347        Ok(())
348    }
349
350    /// Called when an entity is created, updated, or deleted.
351    ///
352    /// Requires [`Interests::ENTITY_EVENTS`] and [`Interests::ENTITY_STATE`] to
353    /// be set.
354    #[cold]
355    #[inline(never)]
356    fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
357        Ok(())
358    }
359
360    /// Called when a game event occurs.
361    ///
362    /// Requires [`Interests::BASE_GAME_EVENT`] to be set.
363    #[cold]
364    #[inline(never)]
365    fn on_game_event(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
366        Ok(())
367    }
368
369    /// Called when a string table is updated.
370    ///
371    /// Requires [`Interests::STRING_TABLE_ENTRIES`] and
372    /// [`Interests::STRING_TABLE_STATE`] to be set.
373    #[cold]
374    #[inline(never)]
375    fn on_string_table(
376        &mut self,
377        ctx: &Context,
378        st: &StringTable,
379        modified: &[i32],
380    ) -> ObserverResult {
381        Ok(())
382    }
383
384    /// Called when the replay ends.
385    ///
386    /// Requires [`Interests::REPLAY_END`] to be set.
387    /// This is the last callback before parsing completes.
388    ///
389    /// # Arguments
390    ///
391    /// * `ctx` - Current replay context
392    #[cold]
393    #[inline(never)]
394    fn on_stop(&mut self, ctx: &Context) -> ObserverResult {
395        Ok(())
396    }
397
398    /// Called when a combat log entry is received (Dota 2 only).
399    ///
400    /// Combat log entries describe in-game events like damage, healing, kills,
401    /// etc. Only available with the `dota` feature enabled.
402    ///
403    /// Requires [`Interests::COMBAT_LOG_ENTRIES`] to be set.
404    #[cold]
405    #[inline(never)]
406    #[cfg(feature = "dota")]
407    fn on_combat_log(&mut self, ctx: &Context, cle: &CombatLogEntry) -> ObserverResult {
408        Ok(())
409    }
410
411    /// Called when a Dota 2 user message is received.
412    ///
413    /// Dota 2 specific user messages. Only available with the `dota` feature
414    /// enabled.
415    ///
416    /// Requires [`Interests::DOTA_USER_MESSAGE`] to be set.
417    #[cold]
418    #[inline(never)]
419    #[cfg(feature = "dota")]
420    fn on_dota_user_message(
421        &mut self,
422        ctx: &Context,
423        msg_type: EDotaUserMessages,
424        msg: &[u8],
425    ) -> ObserverResult {
426        Ok(())
427    }
428
429    /// Called when a Citadel/Deadlock game event is received.
430    ///
431    /// Deadlock specific game events. Only available with the `deadlock`
432    /// feature enabled.
433    ///
434    /// Requires [`Interests::CITADEL_GAME_EVENT`] to be set.
435    #[cold]
436    #[inline(never)]
437    #[cfg(feature = "deadlock")]
438    fn on_citadel_game_event(
439        &mut self,
440        ctx: &Context,
441        msg_type: ECitadelGameEvents,
442        msg: &[u8],
443    ) -> ObserverResult {
444        Ok(())
445    }
446
447    /// Called when a Citadel/Deadlock user message is received.
448    ///
449    /// Deadlock specific user messages. Only available with the `deadlock`
450    /// feature enabled.
451    ///
452    /// Requires [`Interests::CITADEL_USER_MESSAGE`] to be set.
453    #[cold]
454    #[inline(never)]
455    #[cfg(feature = "deadlock")]
456    fn on_citadel_user_message(
457        &mut self,
458        ctx: &Context,
459        msg_type: CitadelUserMessageIds,
460        msg: &[u8],
461    ) -> ObserverResult {
462        Ok(())
463    }
464
465    /// Called when a Counter-Strike 2 user message is received.
466    ///
467    /// CS2 specific user messages. Only available with the `cs2` feature
468    /// enabled.
469    ///
470    /// Requires [`Interests::CS2_USER_MESSAGE`] to be set.
471    #[cold]
472    #[inline(never)]
473    #[cfg(feature = "cs2")]
474    fn on_cs2_user_message(
475        &mut self,
476        ctx: &Context,
477        msg_type: ECstrike15UserMessages,
478        msg: &[u8],
479    ) -> ObserverResult {
480        Ok(())
481    }
482
483    /// Called when a Counter-Strike 2 game event is received.
484    ///
485    /// CS2 specific game events. Only available with the `cs2` feature enabled.
486    ///
487    /// Requires [`Interests::CS2_GAME_EVENT`] to be set.
488    #[cold]
489    #[inline(never)]
490    #[cfg(feature = "cs2")]
491    fn on_cs2_game_event(
492        &mut self,
493        ctx: &Context,
494        msg_type: ECsgoGameEvents,
495        msg: &[u8],
496    ) -> ObserverResult {
497        Ok(())
498    }
499}
500
501impl<T> Observer for Rc<RefCell<T>>
502where
503    T: Observer,
504{
505    fn interests(&self) -> Interests {
506        self.borrow().interests()
507    }
508
509    fn on_demo_command(
510        &mut self,
511        ctx: &Context,
512        msg_type: EDemoCommands,
513        msg: &[u8],
514    ) -> ObserverResult {
515        self.borrow_mut().on_demo_command(ctx, msg_type, msg)
516    }
517
518    fn on_net_message(
519        &mut self,
520        ctx: &Context,
521        msg_type: NetMessages,
522        msg: &[u8],
523    ) -> ObserverResult {
524        self.borrow_mut().on_net_message(ctx, msg_type, msg)
525    }
526
527    fn on_svc_message(
528        &mut self,
529        ctx: &Context,
530        msg_type: SvcMessages,
531        msg: &[u8],
532    ) -> ObserverResult {
533        self.borrow_mut().on_svc_message(ctx, msg_type, msg)
534    }
535
536    fn on_base_user_message(
537        &mut self,
538        ctx: &Context,
539        msg_type: EBaseUserMessages,
540        msg: &[u8],
541    ) -> ObserverResult {
542        self.borrow_mut().on_base_user_message(ctx, msg_type, msg)
543    }
544
545    fn on_base_game_event(
546        &mut self,
547        ctx: &Context,
548        msg_type: EBaseGameEvents,
549        msg: &[u8],
550    ) -> ObserverResult {
551        self.borrow_mut().on_base_game_event(ctx, msg_type, msg)
552    }
553
554    fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
555        self.borrow_mut().on_tick_start(ctx)
556    }
557
558    fn on_tick_end(&mut self, ctx: &Context) -> ObserverResult {
559        self.borrow_mut().on_tick_end(ctx)
560    }
561
562    fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
563        self.borrow_mut().on_entity(ctx, event, entity)
564    }
565
566    fn on_game_event(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
567        self.borrow_mut().on_game_event(ctx, ge)
568    }
569
570    fn on_string_table(
571        &mut self,
572        ctx: &Context,
573        st: &StringTable,
574        modified: &[i32],
575    ) -> ObserverResult {
576        self.borrow_mut().on_string_table(ctx, st, modified)
577    }
578
579    fn on_stop(&mut self, ctx: &Context) -> ObserverResult {
580        self.borrow_mut().on_stop(ctx)
581    }
582
583    #[cfg(feature = "dota")]
584    fn on_combat_log(&mut self, ctx: &Context, cle: &CombatLogEntry) -> ObserverResult {
585        self.borrow_mut().on_combat_log(ctx, cle)
586    }
587
588    #[cfg(feature = "dota")]
589    fn on_dota_user_message(
590        &mut self,
591        ctx: &Context,
592        msg_type: EDotaUserMessages,
593        msg: &[u8],
594    ) -> ObserverResult {
595        self.borrow_mut().on_dota_user_message(ctx, msg_type, msg)
596    }
597
598    #[cfg(feature = "deadlock")]
599    fn on_citadel_game_event(
600        &mut self,
601        ctx: &Context,
602        msg_type: ECitadelGameEvents,
603        msg: &[u8],
604    ) -> ObserverResult {
605        self.borrow_mut().on_citadel_game_event(ctx, msg_type, msg)
606    }
607
608    #[cfg(feature = "deadlock")]
609    fn on_citadel_user_message(
610        &mut self,
611        ctx: &Context,
612        msg_type: CitadelUserMessageIds,
613        msg: &[u8],
614    ) -> ObserverResult {
615        self.borrow_mut()
616            .on_citadel_user_message(ctx, msg_type, msg)
617    }
618
619    #[cfg(feature = "cs2")]
620    fn on_cs2_user_message(
621        &mut self,
622        ctx: &Context,
623        msg_type: ECstrike15UserMessages,
624        msg: &[u8],
625    ) -> ObserverResult {
626        self.borrow_mut().on_cs2_user_message(ctx, msg_type, msg)
627    }
628
629    #[cfg(feature = "cs2")]
630    fn on_cs2_game_event(
631        &mut self,
632        ctx: &Context,
633        msg_type: ECsgoGameEvents,
634        msg: &[u8],
635    ) -> ObserverResult {
636        self.borrow_mut().on_cs2_game_event(ctx, msg_type, msg)
637    }
638}