Skip to main content

manabrew_engine/
perf.rs

1//! Opt-in hot-path instrumentation for the Rust engine.
2//!
3//! Disabled by default. Set `manabrew_engine_PERF=1` to collect counters and
4//! timings. Set `manabrew_engine_PERF_TRACE=1` to emit every increment, or
5//! `manabrew_engine_PERF_REPORT_EVERY=N` to print a summary every N continuous
6//! effect recomputations.
7
8use std::cell::{Cell, RefCell};
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::OnceLock;
11use std::time::{Duration, Instant};
12
13#[derive(Debug, Clone, Copy)]
14pub enum Metric {
15    ContinuousEffectsCalls,
16    ContinuousEffectsNs,
17    ContinuousStaticSourceClones,
18    ContinuousTargetVecs,
19    ParamsParses,
20    ParamsLookups,
21    ParamsUnscopedLookups,
22    ContinuousParamsLookups,
23    ActionSpaceParamsLookups,
24    ActionSpacePlayableParamsLookups,
25    ActionSpaceActivatableParamsLookups,
26    ActionSpaceManaSourceParamsLookups,
27    ActionSpaceUntapParamsLookups,
28    TriggerParamsLookups,
29    ReplacementParamsLookups,
30    CostParamsLookups,
31    TargetParamsLookups,
32    AbilityBuildParamsLookups,
33    StaticAbilityParamsLookups,
34    ValidFilterParamsLookups,
35    SelectorParses,
36    SelectorMatches,
37    SelectorRawPredicates,
38    GameLoopParamsLookups,
39    PhaseParamsLookups,
40    PriorityParamsLookups,
41    PrioritySbaParamsLookups,
42    PriorityTriggerParamsLookups,
43    PrioritySnapshotsTaken,
44    PrioritySnapshotParamsLookups,
45    PrioritySnapshotSyncParamsLookups,
46    PrioritySnapshotMetadataParamsLookups,
47    PrioritySnapshotAbilityParamsLookups,
48    PrioritySnapshotCardCloneParamsLookups,
49    PriorityChoiceParamsLookups,
50    PriorityExecutionParamsLookups,
51    StackResolutionParamsLookups,
52    CombatParamsLookups,
53    SpellAbilityClones,
54    StackEntryClones,
55    GameStateTargetingClones,
56    SnapshotClones,
57    CardStateCollectionClones,
58}
59
60static CONTINUOUS_EFFECTS_CALLS: AtomicU64 = AtomicU64::new(0);
61static CONTINUOUS_EFFECTS_NS: AtomicU64 = AtomicU64::new(0);
62static CONTINUOUS_STATIC_SOURCE_CLONES: AtomicU64 = AtomicU64::new(0);
63static CONTINUOUS_TARGET_VECS: AtomicU64 = AtomicU64::new(0);
64static PARAMS_PARSES: AtomicU64 = AtomicU64::new(0);
65static PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
66static PARAMS_UNSCOPED_LOOKUPS: AtomicU64 = AtomicU64::new(0);
67static CONTINUOUS_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
68static ACTION_SPACE_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
69static ACTION_SPACE_PLAYABLE_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
70static ACTION_SPACE_ACTIVATABLE_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
71static ACTION_SPACE_MANA_SOURCE_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
72static ACTION_SPACE_UNTAP_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
73static TRIGGER_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
74static REPLACEMENT_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
75static COST_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
76static TARGET_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
77static ABILITY_BUILD_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
78static STATIC_ABILITY_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
79static VALID_FILTER_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
80static SELECTOR_PARSES: AtomicU64 = AtomicU64::new(0);
81static SELECTOR_MATCHES: AtomicU64 = AtomicU64::new(0);
82static SELECTOR_RAW_PREDICATES: AtomicU64 = AtomicU64::new(0);
83static GAME_LOOP_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
84static PHASE_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
85static PRIORITY_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
86static PRIORITY_SBA_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
87static PRIORITY_TRIGGER_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
88static PRIORITY_SNAPSHOTS_TAKEN: AtomicU64 = AtomicU64::new(0);
89static PRIORITY_SNAPSHOT_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
90static PRIORITY_SNAPSHOT_SYNC_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
91static PRIORITY_SNAPSHOT_METADATA_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
92static PRIORITY_SNAPSHOT_ABILITY_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
93static PRIORITY_SNAPSHOT_CARD_CLONE_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
94static PRIORITY_CHOICE_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
95static PRIORITY_EXECUTION_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
96static STACK_RESOLUTION_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
97static COMBAT_PARAMS_LOOKUPS: AtomicU64 = AtomicU64::new(0);
98static SPELL_ABILITY_CLONES: AtomicU64 = AtomicU64::new(0);
99static STACK_ENTRY_CLONES: AtomicU64 = AtomicU64::new(0);
100static GAME_STATE_TARGETING_CLONES: AtomicU64 = AtomicU64::new(0);
101static SNAPSHOT_CLONES: AtomicU64 = AtomicU64::new(0);
102static CARD_STATE_COLLECTION_CLONES: AtomicU64 = AtomicU64::new(0);
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum ParamsLookupScope {
106    Continuous,
107    ActionSpace,
108    ActionSpacePlayable,
109    ActionSpaceActivatable,
110    ActionSpaceManaSource,
111    ActionSpaceUntap,
112    Trigger,
113    Replacement,
114    Cost,
115    Target,
116    AbilityBuild,
117    StaticAbility,
118    ValidFilter,
119    GameLoop,
120    Phase,
121    Priority,
122    PrioritySba,
123    PriorityTrigger,
124    PrioritySnapshot,
125    PrioritySnapshotSync,
126    PrioritySnapshotMetadata,
127    PrioritySnapshotAbility,
128    PrioritySnapshotCardClone,
129    PriorityChoice,
130    PriorityExecution,
131    StackResolution,
132    Combat,
133}
134
135thread_local! {
136    static PARAMS_LOOKUP_SCOPE: Cell<Option<ParamsLookupScope>> = const { Cell::new(None) };
137    static WALL_START: RefCell<Option<Instant>> = const { RefCell::new(None) };
138    static TURN_WALL_MS: RefCell<Vec<u64>> = const { RefCell::new(Vec::new()) };
139}
140
141fn enabled_cell() -> bool {
142    std::env::var_os("manabrew_engine_PERF").is_some()
143}
144
145pub fn enabled() -> bool {
146    static ENABLED: OnceLock<bool> = OnceLock::new();
147    *ENABLED.get_or_init(enabled_cell)
148}
149
150fn trace_enabled() -> bool {
151    static TRACE_ENABLED: OnceLock<bool> = OnceLock::new();
152    *TRACE_ENABLED.get_or_init(|| std::env::var_os("manabrew_engine_PERF_TRACE").is_some())
153}
154
155fn report_every() -> Option<u64> {
156    static REPORT_EVERY: OnceLock<Option<u64>> = OnceLock::new();
157    *REPORT_EVERY.get_or_init(|| {
158        std::env::var("manabrew_engine_PERF_REPORT_EVERY")
159            .ok()
160            .and_then(|v| v.parse::<u64>().ok())
161            .filter(|&n| n > 0)
162    })
163}
164
165fn counter(metric: Metric) -> &'static AtomicU64 {
166    match metric {
167        Metric::ContinuousEffectsCalls => &CONTINUOUS_EFFECTS_CALLS,
168        Metric::ContinuousEffectsNs => &CONTINUOUS_EFFECTS_NS,
169        Metric::ContinuousStaticSourceClones => &CONTINUOUS_STATIC_SOURCE_CLONES,
170        Metric::ContinuousTargetVecs => &CONTINUOUS_TARGET_VECS,
171        Metric::ParamsParses => &PARAMS_PARSES,
172        Metric::ParamsLookups => &PARAMS_LOOKUPS,
173        Metric::ParamsUnscopedLookups => &PARAMS_UNSCOPED_LOOKUPS,
174        Metric::ContinuousParamsLookups => &CONTINUOUS_PARAMS_LOOKUPS,
175        Metric::ActionSpaceParamsLookups => &ACTION_SPACE_PARAMS_LOOKUPS,
176        Metric::ActionSpacePlayableParamsLookups => &ACTION_SPACE_PLAYABLE_PARAMS_LOOKUPS,
177        Metric::ActionSpaceActivatableParamsLookups => &ACTION_SPACE_ACTIVATABLE_PARAMS_LOOKUPS,
178        Metric::ActionSpaceManaSourceParamsLookups => &ACTION_SPACE_MANA_SOURCE_PARAMS_LOOKUPS,
179        Metric::ActionSpaceUntapParamsLookups => &ACTION_SPACE_UNTAP_PARAMS_LOOKUPS,
180        Metric::TriggerParamsLookups => &TRIGGER_PARAMS_LOOKUPS,
181        Metric::ReplacementParamsLookups => &REPLACEMENT_PARAMS_LOOKUPS,
182        Metric::CostParamsLookups => &COST_PARAMS_LOOKUPS,
183        Metric::TargetParamsLookups => &TARGET_PARAMS_LOOKUPS,
184        Metric::AbilityBuildParamsLookups => &ABILITY_BUILD_PARAMS_LOOKUPS,
185        Metric::StaticAbilityParamsLookups => &STATIC_ABILITY_PARAMS_LOOKUPS,
186        Metric::ValidFilterParamsLookups => &VALID_FILTER_PARAMS_LOOKUPS,
187        Metric::SelectorParses => &SELECTOR_PARSES,
188        Metric::SelectorMatches => &SELECTOR_MATCHES,
189        Metric::SelectorRawPredicates => &SELECTOR_RAW_PREDICATES,
190        Metric::GameLoopParamsLookups => &GAME_LOOP_PARAMS_LOOKUPS,
191        Metric::PhaseParamsLookups => &PHASE_PARAMS_LOOKUPS,
192        Metric::PriorityParamsLookups => &PRIORITY_PARAMS_LOOKUPS,
193        Metric::PrioritySbaParamsLookups => &PRIORITY_SBA_PARAMS_LOOKUPS,
194        Metric::PriorityTriggerParamsLookups => &PRIORITY_TRIGGER_PARAMS_LOOKUPS,
195        Metric::PrioritySnapshotsTaken => &PRIORITY_SNAPSHOTS_TAKEN,
196        Metric::PrioritySnapshotParamsLookups => &PRIORITY_SNAPSHOT_PARAMS_LOOKUPS,
197        Metric::PrioritySnapshotSyncParamsLookups => &PRIORITY_SNAPSHOT_SYNC_PARAMS_LOOKUPS,
198        Metric::PrioritySnapshotMetadataParamsLookups => &PRIORITY_SNAPSHOT_METADATA_PARAMS_LOOKUPS,
199        Metric::PrioritySnapshotAbilityParamsLookups => &PRIORITY_SNAPSHOT_ABILITY_PARAMS_LOOKUPS,
200        Metric::PrioritySnapshotCardCloneParamsLookups => {
201            &PRIORITY_SNAPSHOT_CARD_CLONE_PARAMS_LOOKUPS
202        }
203        Metric::PriorityChoiceParamsLookups => &PRIORITY_CHOICE_PARAMS_LOOKUPS,
204        Metric::PriorityExecutionParamsLookups => &PRIORITY_EXECUTION_PARAMS_LOOKUPS,
205        Metric::StackResolutionParamsLookups => &STACK_RESOLUTION_PARAMS_LOOKUPS,
206        Metric::CombatParamsLookups => &COMBAT_PARAMS_LOOKUPS,
207        Metric::SpellAbilityClones => &SPELL_ABILITY_CLONES,
208        Metric::StackEntryClones => &STACK_ENTRY_CLONES,
209        Metric::GameStateTargetingClones => &GAME_STATE_TARGETING_CLONES,
210        Metric::SnapshotClones => &SNAPSHOT_CLONES,
211        Metric::CardStateCollectionClones => &CARD_STATE_COLLECTION_CLONES,
212    }
213}
214
215fn name(metric: Metric) -> &'static str {
216    match metric {
217        Metric::ContinuousEffectsCalls => "continuous_effects.calls",
218        Metric::ContinuousEffectsNs => "continuous_effects.ns",
219        Metric::ContinuousStaticSourceClones => "continuous_effects.static_source_clones",
220        Metric::ContinuousTargetVecs => "continuous_effects.target_vecs",
221        Metric::ParamsParses => "params.parses",
222        Metric::ParamsLookups => "params.lookups",
223        Metric::ParamsUnscopedLookups => "params.lookups.unscoped",
224        Metric::ContinuousParamsLookups => "params.lookups.continuous",
225        Metric::ActionSpaceParamsLookups => "params.lookups.action_space",
226        Metric::ActionSpacePlayableParamsLookups => "params.lookups.action_space_playable",
227        Metric::ActionSpaceActivatableParamsLookups => "params.lookups.action_space_activatable",
228        Metric::ActionSpaceManaSourceParamsLookups => "params.lookups.action_space_mana_source",
229        Metric::ActionSpaceUntapParamsLookups => "params.lookups.action_space_untap",
230        Metric::TriggerParamsLookups => "params.lookups.trigger",
231        Metric::ReplacementParamsLookups => "params.lookups.replacement",
232        Metric::CostParamsLookups => "params.lookups.cost",
233        Metric::TargetParamsLookups => "params.lookups.target",
234        Metric::AbilityBuildParamsLookups => "params.lookups.ability_build",
235        Metric::StaticAbilityParamsLookups => "params.lookups.static_ability",
236        Metric::ValidFilterParamsLookups => "params.lookups.valid_filter",
237        Metric::SelectorParses => "selector.parses",
238        Metric::SelectorMatches => "selector.matches",
239        Metric::SelectorRawPredicates => "selector.raw_predicates",
240        Metric::GameLoopParamsLookups => "params.lookups.game_loop",
241        Metric::PhaseParamsLookups => "params.lookups.phase",
242        Metric::PriorityParamsLookups => "params.lookups.priority",
243        Metric::PrioritySbaParamsLookups => "params.lookups.priority_sba",
244        Metric::PriorityTriggerParamsLookups => "params.lookups.priority_trigger",
245        Metric::PrioritySnapshotsTaken => "priority.snapshots_taken",
246        Metric::PrioritySnapshotParamsLookups => "params.lookups.priority_snapshot",
247        Metric::PrioritySnapshotSyncParamsLookups => "params.lookups.priority_snapshot_sync",
248        Metric::PrioritySnapshotMetadataParamsLookups => {
249            "params.lookups.priority_snapshot_metadata"
250        }
251        Metric::PrioritySnapshotAbilityParamsLookups => "params.lookups.priority_snapshot_ability",
252        Metric::PrioritySnapshotCardCloneParamsLookups => {
253            "params.lookups.priority_snapshot_card_clone"
254        }
255        Metric::PriorityChoiceParamsLookups => "params.lookups.priority_choice",
256        Metric::PriorityExecutionParamsLookups => "params.lookups.priority_execution",
257        Metric::StackResolutionParamsLookups => "params.lookups.stack_resolution",
258        Metric::CombatParamsLookups => "params.lookups.combat",
259        Metric::SpellAbilityClones => "spell_ability.clones",
260        Metric::StackEntryClones => "stack_entry.clones",
261        Metric::GameStateTargetingClones => "game_state.targeting_clones",
262        Metric::SnapshotClones => "snapshot.clones",
263        Metric::CardStateCollectionClones => "card_state.collection_clones",
264    }
265}
266
267pub fn increment(metric: Metric, amount: u64) {
268    if !enabled() || amount == 0 {
269        return;
270    }
271    let total = counter(metric).fetch_add(amount, Ordering::Relaxed) + amount;
272    if trace_enabled() {
273        eprintln!("[forge-perf] {} +{} total={}", name(metric), amount, total);
274    }
275    if matches!(metric, Metric::ContinuousEffectsCalls) {
276        if let Some(every) = report_every() {
277            if total % every == 0 {
278                print_summary();
279            }
280        }
281    }
282}
283
284pub fn increment_params_lookup() {
285    increment(Metric::ParamsLookups, 1);
286    if !enabled() {
287        return;
288    }
289    PARAMS_LOOKUP_SCOPE.with(|scope| match scope.get() {
290        Some(ParamsLookupScope::Continuous) => increment(Metric::ContinuousParamsLookups, 1),
291        Some(ParamsLookupScope::ActionSpace) => increment(Metric::ActionSpaceParamsLookups, 1),
292        Some(ParamsLookupScope::ActionSpacePlayable) => {
293            increment(Metric::ActionSpacePlayableParamsLookups, 1)
294        }
295        Some(ParamsLookupScope::ActionSpaceActivatable) => {
296            increment(Metric::ActionSpaceActivatableParamsLookups, 1)
297        }
298        Some(ParamsLookupScope::ActionSpaceManaSource) => {
299            increment(Metric::ActionSpaceManaSourceParamsLookups, 1)
300        }
301        Some(ParamsLookupScope::ActionSpaceUntap) => {
302            increment(Metric::ActionSpaceUntapParamsLookups, 1)
303        }
304        Some(ParamsLookupScope::Trigger) => increment(Metric::TriggerParamsLookups, 1),
305        Some(ParamsLookupScope::Replacement) => increment(Metric::ReplacementParamsLookups, 1),
306        Some(ParamsLookupScope::Cost) => increment(Metric::CostParamsLookups, 1),
307        Some(ParamsLookupScope::Target) => increment(Metric::TargetParamsLookups, 1),
308        Some(ParamsLookupScope::AbilityBuild) => increment(Metric::AbilityBuildParamsLookups, 1),
309        Some(ParamsLookupScope::StaticAbility) => increment(Metric::StaticAbilityParamsLookups, 1),
310        Some(ParamsLookupScope::ValidFilter) => increment(Metric::ValidFilterParamsLookups, 1),
311        Some(ParamsLookupScope::GameLoop) => increment(Metric::GameLoopParamsLookups, 1),
312        Some(ParamsLookupScope::Phase) => increment(Metric::PhaseParamsLookups, 1),
313        Some(ParamsLookupScope::Priority) => increment(Metric::PriorityParamsLookups, 1),
314        Some(ParamsLookupScope::PrioritySba) => increment(Metric::PrioritySbaParamsLookups, 1),
315        Some(ParamsLookupScope::PriorityTrigger) => {
316            increment(Metric::PriorityTriggerParamsLookups, 1)
317        }
318        Some(ParamsLookupScope::PrioritySnapshot) => {
319            increment(Metric::PrioritySnapshotParamsLookups, 1)
320        }
321        Some(ParamsLookupScope::PrioritySnapshotSync) => {
322            increment(Metric::PrioritySnapshotSyncParamsLookups, 1)
323        }
324        Some(ParamsLookupScope::PrioritySnapshotMetadata) => {
325            increment(Metric::PrioritySnapshotMetadataParamsLookups, 1)
326        }
327        Some(ParamsLookupScope::PrioritySnapshotAbility) => {
328            increment(Metric::PrioritySnapshotAbilityParamsLookups, 1)
329        }
330        Some(ParamsLookupScope::PrioritySnapshotCardClone) => {
331            increment(Metric::PrioritySnapshotCardCloneParamsLookups, 1)
332        }
333        Some(ParamsLookupScope::PriorityChoice) => {
334            increment(Metric::PriorityChoiceParamsLookups, 1)
335        }
336        Some(ParamsLookupScope::PriorityExecution) => {
337            increment(Metric::PriorityExecutionParamsLookups, 1)
338        }
339        Some(ParamsLookupScope::StackResolution) => {
340            increment(Metric::StackResolutionParamsLookups, 1)
341        }
342        Some(ParamsLookupScope::Combat) => increment(Metric::CombatParamsLookups, 1),
343        None => increment(Metric::ParamsUnscopedLookups, 1),
344    });
345}
346
347pub fn increment_params_parse() {
348    increment(Metric::ParamsParses, 1);
349}
350
351pub fn increment_priority_snapshot() {
352    increment(Metric::PrioritySnapshotsTaken, 1);
353}
354
355pub struct ParamsLookupScopeGuard {
356    previous: Option<ParamsLookupScope>,
357}
358
359impl ParamsLookupScopeGuard {
360    pub fn enter(scope: ParamsLookupScope) -> Option<Self> {
361        if !enabled() {
362            return None;
363        }
364        Some(PARAMS_LOOKUP_SCOPE.with(|current| {
365            let previous = current.replace(Some(scope));
366            Self { previous }
367        }))
368    }
369}
370
371pub fn current_params_lookup_scope() -> Option<ParamsLookupScope> {
372    if !enabled() {
373        return None;
374    }
375    PARAMS_LOOKUP_SCOPE.with(|current| current.get())
376}
377
378impl Drop for ParamsLookupScopeGuard {
379    fn drop(&mut self) {
380        PARAMS_LOOKUP_SCOPE.with(|current| current.set(self.previous));
381    }
382}
383
384pub fn add_duration(metric: Metric, duration: Duration) {
385    let nanos = duration.as_nanos().min(u128::from(u64::MAX)) as u64;
386    increment(metric, nanos);
387}
388
389fn reset_wall_start() {
390    if !enabled() {
391        return;
392    }
393    WALL_START.with(|start| *start.borrow_mut() = Some(Instant::now()));
394}
395
396fn wall_total_ms() -> u64 {
397    WALL_START.with(|start| {
398        start
399            .borrow()
400            .map(|instant| instant.elapsed().as_millis() as u64)
401            .unwrap_or(0)
402    })
403}
404
405pub fn record_turn_wall(duration: Duration) {
406    if !enabled() {
407        return;
408    }
409    TURN_WALL_MS.with(|turn_wall_ms| turn_wall_ms.borrow_mut().push(duration.as_millis() as u64));
410}
411
412fn turn_wall_ms_summary() -> String {
413    TURN_WALL_MS.with(|turn_wall_ms| {
414        let turn_wall_ms = turn_wall_ms.borrow();
415        let mut out = String::from("[");
416        for (idx, ms) in turn_wall_ms.iter().enumerate() {
417            if idx > 0 {
418                out.push(',');
419            }
420            out.push_str(&ms.to_string());
421        }
422        out.push(']');
423        out
424    })
425}
426
427pub struct ScopeTimer {
428    metric: Metric,
429    start: Instant,
430}
431
432impl ScopeTimer {
433    pub fn start(call_metric: Metric, duration_metric: Metric) -> Option<Self> {
434        if !enabled() {
435            return None;
436        }
437        increment(call_metric, 1);
438        Some(Self {
439            metric: duration_metric,
440            start: Instant::now(),
441        })
442    }
443}
444
445impl Drop for ScopeTimer {
446    fn drop(&mut self) {
447        add_duration(self.metric, self.start.elapsed());
448    }
449}
450
451pub struct SummaryGuard;
452
453impl SummaryGuard {
454    pub fn new() -> Option<Self> {
455        if !enabled() {
456            return None;
457        }
458        reset_wall_start();
459        Some(Self)
460    }
461}
462
463impl Drop for SummaryGuard {
464    fn drop(&mut self) {
465        print_summary();
466    }
467}
468
469pub fn print_summary() {
470    if !enabled() {
471        return;
472    }
473    let calls = CONTINUOUS_EFFECTS_CALLS.load(Ordering::Relaxed);
474    let continuous_ns = CONTINUOUS_EFFECTS_NS.load(Ordering::Relaxed);
475    let continuous_avg_us = if calls == 0 {
476        0
477    } else {
478        continuous_ns / calls / 1_000
479    };
480    let turn_wall_ms = turn_wall_ms_summary();
481    eprintln!(
482        concat!(
483            "[forge-perf] summary ",
484            "wall_total_ms={} turn_wall_ms={} ",
485            "continuous_calls={} continuous_total_us={} continuous_avg_us={} ",
486            "static_source_clones={} target_vecs={} params_parses={} params_lookups={} ",
487            "params_unscoped_lookups={} ",
488            "continuous_params_lookups={} action_space_params_lookups={} ",
489            "action_space_playable_params_lookups={} ",
490            "action_space_activatable_params_lookups={} ",
491            "action_space_mana_source_params_lookups={} ",
492            "action_space_untap_params_lookups={} ",
493            "trigger_params_lookups={} replacement_params_lookups={} ",
494            "cost_params_lookups={} target_params_lookups={} ",
495            "ability_build_params_lookups={} static_ability_params_lookups={} ",
496            "valid_filter_params_lookups={} selector_parses={} selector_matches={} ",
497            "selector_raw_predicates={} game_loop_params_lookups={} ",
498            "phase_params_lookups={} priority_params_lookups={} ",
499            "priority_sba_params_lookups={} priority_trigger_params_lookups={} ",
500            "priority_snapshots_taken={} ",
501            "priority_snapshot_params_lookups={} ",
502            "priority_snapshot_sync_params_lookups={} ",
503            "priority_snapshot_metadata_params_lookups={} ",
504            "priority_snapshot_ability_params_lookups={} ",
505            "priority_snapshot_card_clone_params_lookups={} ",
506            "priority_choice_params_lookups={} ",
507            "priority_execution_params_lookups={} ",
508            "stack_resolution_params_lookups={} combat_params_lookups={} ",
509            "spell_ability_clones={} stack_entry_clones={} ",
510            "game_state_targeting_clones={} snapshot_clones={} ",
511            "card_state_collection_clones={}"
512        ),
513        wall_total_ms(),
514        turn_wall_ms,
515        calls,
516        continuous_ns / 1_000,
517        continuous_avg_us,
518        CONTINUOUS_STATIC_SOURCE_CLONES.load(Ordering::Relaxed),
519        CONTINUOUS_TARGET_VECS.load(Ordering::Relaxed),
520        PARAMS_PARSES.load(Ordering::Relaxed),
521        PARAMS_LOOKUPS.load(Ordering::Relaxed),
522        PARAMS_UNSCOPED_LOOKUPS.load(Ordering::Relaxed),
523        CONTINUOUS_PARAMS_LOOKUPS.load(Ordering::Relaxed),
524        ACTION_SPACE_PARAMS_LOOKUPS.load(Ordering::Relaxed),
525        ACTION_SPACE_PLAYABLE_PARAMS_LOOKUPS.load(Ordering::Relaxed),
526        ACTION_SPACE_ACTIVATABLE_PARAMS_LOOKUPS.load(Ordering::Relaxed),
527        ACTION_SPACE_MANA_SOURCE_PARAMS_LOOKUPS.load(Ordering::Relaxed),
528        ACTION_SPACE_UNTAP_PARAMS_LOOKUPS.load(Ordering::Relaxed),
529        TRIGGER_PARAMS_LOOKUPS.load(Ordering::Relaxed),
530        REPLACEMENT_PARAMS_LOOKUPS.load(Ordering::Relaxed),
531        COST_PARAMS_LOOKUPS.load(Ordering::Relaxed),
532        TARGET_PARAMS_LOOKUPS.load(Ordering::Relaxed),
533        ABILITY_BUILD_PARAMS_LOOKUPS.load(Ordering::Relaxed),
534        STATIC_ABILITY_PARAMS_LOOKUPS.load(Ordering::Relaxed),
535        VALID_FILTER_PARAMS_LOOKUPS.load(Ordering::Relaxed),
536        SELECTOR_PARSES.load(Ordering::Relaxed),
537        SELECTOR_MATCHES.load(Ordering::Relaxed),
538        SELECTOR_RAW_PREDICATES.load(Ordering::Relaxed),
539        GAME_LOOP_PARAMS_LOOKUPS.load(Ordering::Relaxed),
540        PHASE_PARAMS_LOOKUPS.load(Ordering::Relaxed),
541        PRIORITY_PARAMS_LOOKUPS.load(Ordering::Relaxed),
542        PRIORITY_SBA_PARAMS_LOOKUPS.load(Ordering::Relaxed),
543        PRIORITY_TRIGGER_PARAMS_LOOKUPS.load(Ordering::Relaxed),
544        PRIORITY_SNAPSHOTS_TAKEN.load(Ordering::Relaxed),
545        PRIORITY_SNAPSHOT_PARAMS_LOOKUPS.load(Ordering::Relaxed),
546        PRIORITY_SNAPSHOT_SYNC_PARAMS_LOOKUPS.load(Ordering::Relaxed),
547        PRIORITY_SNAPSHOT_METADATA_PARAMS_LOOKUPS.load(Ordering::Relaxed),
548        PRIORITY_SNAPSHOT_ABILITY_PARAMS_LOOKUPS.load(Ordering::Relaxed),
549        PRIORITY_SNAPSHOT_CARD_CLONE_PARAMS_LOOKUPS.load(Ordering::Relaxed),
550        PRIORITY_CHOICE_PARAMS_LOOKUPS.load(Ordering::Relaxed),
551        PRIORITY_EXECUTION_PARAMS_LOOKUPS.load(Ordering::Relaxed),
552        STACK_RESOLUTION_PARAMS_LOOKUPS.load(Ordering::Relaxed),
553        COMBAT_PARAMS_LOOKUPS.load(Ordering::Relaxed),
554        SPELL_ABILITY_CLONES.load(Ordering::Relaxed),
555        STACK_ENTRY_CLONES.load(Ordering::Relaxed),
556        GAME_STATE_TARGETING_CLONES.load(Ordering::Relaxed),
557        SNAPSHOT_CLONES.load(Ordering::Relaxed),
558        CARD_STATE_COLLECTION_CLONES.load(Ordering::Relaxed),
559    );
560}
561
562pub fn reset_counters() {
563    if !enabled() {
564        return;
565    }
566    reset_wall_start();
567    TURN_WALL_MS.with(|turn_wall_ms| turn_wall_ms.borrow_mut().clear());
568    for metric in [
569        Metric::ContinuousEffectsCalls,
570        Metric::ContinuousEffectsNs,
571        Metric::ContinuousStaticSourceClones,
572        Metric::ContinuousTargetVecs,
573        Metric::ParamsParses,
574        Metric::ParamsLookups,
575        Metric::ParamsUnscopedLookups,
576        Metric::ContinuousParamsLookups,
577        Metric::ActionSpaceParamsLookups,
578        Metric::ActionSpacePlayableParamsLookups,
579        Metric::ActionSpaceActivatableParamsLookups,
580        Metric::ActionSpaceManaSourceParamsLookups,
581        Metric::ActionSpaceUntapParamsLookups,
582        Metric::TriggerParamsLookups,
583        Metric::ReplacementParamsLookups,
584        Metric::CostParamsLookups,
585        Metric::TargetParamsLookups,
586        Metric::AbilityBuildParamsLookups,
587        Metric::StaticAbilityParamsLookups,
588        Metric::ValidFilterParamsLookups,
589        Metric::SelectorParses,
590        Metric::SelectorMatches,
591        Metric::SelectorRawPredicates,
592        Metric::GameLoopParamsLookups,
593        Metric::PhaseParamsLookups,
594        Metric::PriorityParamsLookups,
595        Metric::PrioritySbaParamsLookups,
596        Metric::PriorityTriggerParamsLookups,
597        Metric::PrioritySnapshotsTaken,
598        Metric::PrioritySnapshotParamsLookups,
599        Metric::PrioritySnapshotSyncParamsLookups,
600        Metric::PrioritySnapshotMetadataParamsLookups,
601        Metric::PrioritySnapshotAbilityParamsLookups,
602        Metric::PrioritySnapshotCardCloneParamsLookups,
603        Metric::PriorityChoiceParamsLookups,
604        Metric::PriorityExecutionParamsLookups,
605        Metric::StackResolutionParamsLookups,
606        Metric::CombatParamsLookups,
607        Metric::SpellAbilityClones,
608        Metric::StackEntryClones,
609        Metric::GameStateTargetingClones,
610        Metric::SnapshotClones,
611        Metric::CardStateCollectionClones,
612    ] {
613        counter(metric).store(0, Ordering::Relaxed);
614    }
615}