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}