Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions crates/neomacs-display-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,)+
Expand Down
14 changes: 7 additions & 7 deletions crates/neomacs-display-runtime/src/render_thread/frame_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<DemandReason, u64>,
}

static WINDOW_DEMAND: Mutex<BTreeMap<u64, WindowDemandStats>> = Mutex::new(BTreeMap::new());
Expand All @@ -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 });
}
}
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -288,7 +288,7 @@ pub fn window_snapshots() -> Vec<WindowFrameSnapshot> {
.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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Loading