perf(canvas): smooth first reveal snapshot bursts

This commit is contained in:
Kayshen-X 2026-06-15 08:08:25 +08:00
parent 12ca1334d8
commit 405fd46bb6

View file

@ -37,7 +37,7 @@ const REVEAL_MID_STAGGER_SIBLINGS: u64 = 72;
const REVEAL_COMPRESSED_STAGGER_MS: u64 = 32;
const REVEAL_TAIL_STAGGER_MS: u64 = 24;
const REVEAL_MAX_NEW_STARTS_PER_SNAPSHOT: usize = 1;
const REVEAL_BURST_RECOVERY_STAGGER_MS: u64 = 32;
const REVEAL_BURST_RECOVERY_STAGGER_MS: u64 = REVEAL_STAGGER_MS;
const CLOCK_REBASE_THRESHOLD_MS: u64 = 60_000;
const REVEAL_FRAME_MS: u64 = 16;
@ -313,10 +313,8 @@ fn rebase_external_clock_reveals(r: &mut AgentIndicators, now_ms: u64) {
}
fn smooth_overdue_reveal_burst(r: &mut AgentIndicators, now_ms: u64) {
let Some(prev_ms) = r.last_reveal_snapshot_ms.replace(now_ms) else {
return;
};
if now_ms <= prev_ms {
let prev_ms = r.last_reveal_snapshot_ms.replace(now_ms);
if prev_ms.is_some_and(|prev| now_ms <= prev) {
return;
}
let mut ordered: Vec<(String, u64)> = r
@ -327,7 +325,7 @@ fn smooth_overdue_reveal_burst(r: &mut AgentIndicators, now_ms: u64) {
ordered.sort_by(|a, b| a.1.cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
let newly_due = ordered
.iter()
.filter(|(_, started)| *started > prev_ms && *started <= now_ms)
.filter(|(_, started)| reveal_became_due(prev_ms, *started, now_ms))
.count();
if newly_due <= REVEAL_MAX_NEW_STARTS_PER_SNAPSHOT {
return;
@ -336,7 +334,7 @@ fn smooth_overdue_reveal_burst(r: &mut AgentIndicators, now_ms: u64) {
let mut reschedule_tail = false;
let mut next_slot = now_ms;
for (id, original_start) in ordered {
if original_start > prev_ms && original_start <= now_ms {
if reveal_became_due(prev_ms, original_start, now_ms) {
newly_due_seen += 1;
if newly_due_seen <= REVEAL_MAX_NEW_STARTS_PER_SNAPSHOT {
if let Some(started_at) = r.reveals.get_mut(&id) {
@ -360,6 +358,13 @@ fn smooth_overdue_reveal_burst(r: &mut AgentIndicators, now_ms: u64) {
}
}
fn reveal_became_due(prev_ms: Option<u64>, started_at: u64, now_ms: u64) -> bool {
match prev_ms {
Some(prev) => started_at > prev && started_at <= now_ms,
None => started_at <= now_ms,
}
}
fn has_active_indicators(r: &AgentIndicators) -> bool {
!r.nodes.is_empty() || !r.frames.is_empty() || !r.reveals.is_empty()
}
@ -605,6 +610,43 @@ mod tests {
end_if_epoch(epoch);
}
#[test]
fn first_snapshot_requeues_overdue_reveals_instead_of_showing_burst() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let epoch = begin();
for i in 0..8 {
add_reveal(
epoch,
&format!("n{i}"),
1_000 + i as u64 * REVEAL_STAGGER_MS,
);
}
let snap = snapshot_at(1_360);
let visible_count = snap
.reveals
.values()
.filter(|started_at| **started_at <= 1_360)
.count();
assert_eq!(
visible_count, 1,
"the first paint after a busy apply should not materialize every overdue node at once"
);
assert_eq!(
snap.reveals.get("n0"),
Some(&1_360),
"the first overdue node should replay from the current frame"
);
assert!(
snap.reveals
.get("n1")
.is_some_and(|started_at| *started_at >= 1_360 + REVEAL_STAGGER_MS),
"remaining overdue nodes should requeue at normal stream cadence"
);
end_if_epoch(epoch);
}
#[test]
fn snapshot_recovery_preserves_stream_order_after_frame_gap() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
@ -632,8 +674,8 @@ mod tests {
starts
.windows(2)
.skip(1)
.all(|pair| pair[1] - pair[0] >= REVEAL_FRAME_MS),
"recovered starts should stay frame-paced instead of clustering inside one frame"
.all(|pair| pair[1] - pair[0] >= REVEAL_STAGGER_MS),
"recovered starts should stay at the normal stream cadence instead of clustering"
);
end_if_epoch(epoch);
}