From 009080501c3b746d999119fdad7d82f45f2f9b12 Mon Sep 17 00:00:00 2001 From: Eval Exec Date: Thu, 8 Oct 2026 10:00:43 +0800 Subject: [PATCH] refactor(runtime): key window demand counters by enum --- Cargo.lock | 29 ++++++++++++-- Cargo.toml | 1 + crates/neomacs-display-runtime/Cargo.toml | 1 + .../src/render_thread/frame_sched.rs | 5 ++- .../src/render_thread/frame_stats.rs | 14 +++---- .../render_thread/tests/frame_sched_test.rs | 39 +++++++++++++++++++ 6 files changed, 77 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d86430f4bb..855383079d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1759,6 +1759,26 @@ dependencies = [ "cfg-if 1.0.4", ] +[[package]] +name = "enum-map" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4532960034c15ba2f55e9eee631040ecc473c32e6c4b200828c759840f13a06" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7127272f64b3e098f7d81f350d672081b879bff896d219c42370dc858b1af9ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "enum_dispatch" version = "0.3.13" @@ -3889,6 +3909,7 @@ dependencies = [ "cosmic-text", "crossbeam-channel", "crossterm", + "enum-map", "enumset", "fontdb", "gio", @@ -5453,9 +5474,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -5659,9 +5680,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] diff --git a/Cargo.toml b/Cargo.toml index 3efcd154a6..b9bd5c19f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -175,6 +175,7 @@ hashbrown = "0.17" num_enum = "0.7.6" enum_dispatch = "0.3.13" enumset = "1.1.14" +enum-map = "3.1.0" strum = { version = "0.28", features = ["derive"] } unicode_names2 = "3.1.0" unicode-script = "0.5" diff --git a/crates/neomacs-display-runtime/Cargo.toml b/crates/neomacs-display-runtime/Cargo.toml index 9d5d89a618..953f92dd4d 100644 --- a/crates/neomacs-display-runtime/Cargo.toml +++ b/crates/neomacs-display-runtime/Cargo.toml @@ -38,6 +38,7 @@ tracing-subscriber.workspace = true serde_json.workspace = true strum.workspace = true enumset.workspace = true +enum-map.workspace = true num_enum.workspace = true thiserror.workspace = true bitflags.workspace = true diff --git a/crates/neomacs-display-runtime/src/render_thread/frame_sched.rs b/crates/neomacs-display-runtime/src/render_thread/frame_sched.rs index 4f05267666..7d71b04d51 100644 --- a/crates/neomacs-display-runtime/src/render_thread/frame_sched.rs +++ b/crates/neomacs-display-runtime/src/render_thread/frame_sched.rs @@ -135,7 +135,10 @@ macro_rules! demand_reasons { // Interface variants/fields defined by the scheduling plan; consumed as // later stages migrate effects onto the coordinator. #[allow(dead_code)] - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, enumset::EnumSetType)] + #[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, + enumset::EnumSetType, enum_map::Enum, + )] #[enumset(no_super_impls, no_ops)] pub(crate) enum DemandReason { $($(#[$variant_meta])* $variant,)+ diff --git a/crates/neomacs-display-runtime/src/render_thread/frame_stats.rs b/crates/neomacs-display-runtime/src/render_thread/frame_stats.rs index a5729c508b..8a42779993 100644 --- a/crates/neomacs-display-runtime/src/render_thread/frame_stats.rs +++ b/crates/neomacs-display-runtime/src/render_thread/frame_stats.rs @@ -14,6 +14,7 @@ //! All counters are relaxed atomics: every writer runs on the render thread, //! and readers only need eventually-consistent totals. +use enum_map::EnumMap; use std::collections::BTreeMap; use std::sync::Mutex; use std::sync::atomic::{AtomicU64, Ordering}; @@ -139,9 +140,8 @@ pub(super) fn count(counter: &AtomicU64) { struct WindowDemandStats { /// Demand reasons active as of the last reconciliation. active: DemandReasonSet, - /// Planned frames per demand reason for this window, indexed like - /// [`FrameSchedSnapshot::demand_reasons`]. - plans: [u64; DemandReason::COUNT], + /// Planned frames for each demand reason, keyed by the reason itself. + plans: EnumMap, } static WINDOW_DEMAND: Mutex> = Mutex::new(BTreeMap::new()); @@ -166,7 +166,7 @@ pub(super) fn publish_window_demand( let plans = old .remove(&id.0) .map(|stats| stats.plans) - .unwrap_or([0; DemandReason::COUNT]); + .unwrap_or_default(); map.insert(id.0, WindowDemandStats { active: set, plans }); } } @@ -190,10 +190,10 @@ pub(super) fn count_plan(window: NativeWindowId, plan: &FramePlan) { let mut map = WINDOW_DEMAND.lock().unwrap(); let stats = map.entry(window.0).or_insert_with(|| WindowDemandStats { active: DemandReasonSet::empty(), - plans: [0; DemandReason::COUNT], + plans: EnumMap::default(), }); for reason in plan.reasons.iter() { - stats.plans[reason.index()] += 1; + stats.plans[reason] += 1; } } @@ -288,7 +288,7 @@ pub fn window_snapshots() -> Vec { .map(|(id, stats)| WindowFrameSnapshot { window: *id, active_reasons: stats.active.iter().map(DemandReason::name).collect(), - demand_reasons: stats.plans, + demand_reasons: stats.plans.into_array(), }) .collect() } diff --git a/crates/neomacs-display-runtime/src/render_thread/tests/frame_sched_test.rs b/crates/neomacs-display-runtime/src/render_thread/tests/frame_sched_test.rs index 705902ebf4..0cbac03b06 100644 --- a/crates/neomacs-display-runtime/src/render_thread/tests/frame_sched_test.rs +++ b/crates/neomacs-display-runtime/src/render_thread/tests/frame_sched_test.rs @@ -1270,3 +1270,42 @@ fn demand_reason_sets_deduplicate_and_iterate_in_declaration_order() { assert!(empty.iter().next().is_none()); assert!(!empty.contains(DemandReason::EditorCommit)); } + +#[test] +fn per_window_counters_preserve_every_reason_in_diagnostic_order() { + use super::super::frame_stats; + let window = win(99); + let reasons: DemandReasonSet = DemandReason::ALL.into_iter().collect(); + frame_stats::publish_window_demand(std::iter::once((window, reasons))); + let initial = frame_stats::window_snapshots(); + assert_eq!(initial.len(), 1); + assert_eq!(initial[0].demand_reasons, [0; DemandReason::COUNT]); + + for (index, reason) in DemandReason::ALL.into_iter().enumerate() { + let plan = FramePlan { + tick: tick_at(t0()), + work: RenderWork::CompositeOnly { + layers: LayerMask::CURSOR_EFFECTS, + }, + should_present: true, + reasons: std::iter::once(reason).collect(), + }; + for _ in 0..=index { + frame_stats::count_plan(window, &plan); + } + } + let expected = std::array::from_fn(|index| index as u64 + 1); + let snapshot = frame_stats::window_snapshots(); + assert_eq!(snapshot[0].window, window.0); + assert_eq!(snapshot[0].active_reasons, frame_stats::DEMAND_REASON_NAMES); + assert_eq!(snapshot[0].demand_reasons, expected); + + // Reconciliation changes active demand without discarding accumulated counts. + frame_stats::publish_window_demand(std::iter::once((window, DemandReasonSet::empty()))); + let reconciled = frame_stats::window_snapshots(); + assert!(reconciled[0].active_reasons.is_empty()); + assert_eq!(reconciled[0].demand_reasons, expected); + + frame_stats::publish_window_demand(std::iter::empty()); + assert!(frame_stats::window_snapshots().is_empty()); +}