wowlab_engine_sim/telemetry/
merge.rs1use wowlab_types::stats::simd;
4
5use super::TelemetryAccumulator;
6
7impl TelemetryAccumulator {
8 pub fn merge(&mut self, mut other: Self) {
10 let combined_sum = self.dps_sum + other.dps_sum;
12 let combined_count = self.iteration_count + other.iteration_count;
13 let n_a = f64::from(self.iteration_count);
15 let n_b = f64::from(other.iteration_count);
16
17 if combined_count > 0 {
18 let n = f64::from(combined_count);
19 let mean_delta = other.dps_welford_mean - self.dps_welford_mean;
20
21 self.dps_m2 += other.dps_m2 + mean_delta * mean_delta * n_a * n_b / n;
22 self.dps_welford_mean =
23 (n_a * self.dps_welford_mean + n_b * other.dps_welford_mean) / n;
24 }
25
26 self.representative_candidates
27 .append(&mut other.representative_candidates);
28 self.representative_dps = None;
30 self.representative_sink = None;
31
32 self.iteration_count = combined_count;
33 self.dps_sum = combined_sum;
34 self.dps_min = self.dps_min.min(other.dps_min);
35 self.dps_max = self.dps_max.max(other.dps_max);
36
37 for (k, v) in other.spell_totals {
38 let entry = self.spell_totals.entry(k).or_default();
39
40 entry.damage += v.damage;
41 entry.casts += v.casts;
42 entry.crits += v.crits;
43 entry.hits += v.hits;
44 entry.ticks += v.ticks;
45 }
46
47 if let Err(e) = self.dps_histogram.add(&other.dps_histogram) {
48 tracing::warn!(error = %e, "failed to merge DPS histogram across chunks");
49 }
50
51 for (k, v) in other.aura_totals {
52 let entry = self.aura_totals.entry(k).or_default();
53
54 entry.uptime_ms += v.uptime_ms;
55 entry.applications += v.applications;
56 entry.refreshes += v.refreshes;
57 }
58
59 for (k, v) in other.resource_totals {
60 let entry = self.resource_totals.entry(k).or_default();
61
62 entry.total_gained += v.total_gained;
63 entry.total_spent += v.total_spent;
64 entry.total_wasted += v.total_wasted;
65
66 for (source_spell_id, source) in v.gained_by_source {
67 let merged_source = entry.gained_by_source.entry(source_spell_id).or_default();
68
69 merged_source.gained += source.gained;
70 merged_source.wasted += source.wasted;
71 }
72 }
73
74 for (k, v) in other.cooldown_totals {
75 let entry = self.cooldown_totals.entry(k).or_default();
76
77 entry.total_uses += v.total_uses;
78 }
79
80 self.total_duration_ms += other.total_duration_ms;
81 self.gcd_locked_ms_total += other.gcd_locked_ms_total;
82 self.direct_damage_total += other.direct_damage_total;
83 self.periodic_damage_total += other.periodic_damage_total;
84 self.pet_damage_total += other.pet_damage_total;
85
86 for (target, damage) in other.damage_by_target {
87 *self.damage_by_target.entry(target).or_insert(0.0) += damage;
88 }
89
90 for (target, metadata) in other.targets {
91 self.targets.entry(target).or_insert(metadata);
92 }
93
94 if other.bucket_sums.len() > self.bucket_sums.len() {
95 self.bucket_sums.resize(other.bucket_sums.len(), 0.0);
96 }
97
98 simd::add_f64_slices(&mut self.bucket_sums, &other.bucket_sums);
99
100 if other.bucket_samples.len() > self.bucket_samples.len() {
101 self.bucket_samples.resize(other.bucket_samples.len(), 0);
102 }
103
104 for (dst, src) in self.bucket_samples.iter_mut().zip(&other.bucket_samples) {
105 *dst += *src;
106 }
107
108 self.trace_extras_enabled = self.trace_extras_enabled || other.trace_extras_enabled;
109 }
110}