atagen

monolith

public
Star0
Watch0

Commit 8a73f3b68818

tarball

atagen <boss@atagen.co>2026-10-19 07:51 UTC

1 file changed+37-30
drm: log render timers that fail to arm

A failed insert_source left `Surface::scheduled` empty with nothing in the log, so the output quietly stopped drawing. The three call sites now share one `arm` helper.
Modified file: crates/monolith/src/drm.rs+37 additions−30 deletions
@@ -618,19 +618,10 @@ fn schedule(state: &mut State, crtc: crtc::Handle) {
618618 draw(state, crtc, now);
619619 return;
620620 }
621- let wait = Timer::from_duration(Duration::from_nanos(CURSOR_FRAME_NS - since_flip));
622- let token = drm.handle.insert_source(wait, move |_, _, state| {
623- if let Some(s) = state
624- .backend
625- .drm_mut()
626- .and_then(|d| d.surfaces.get_mut(&crtc))
627- {
628- s.scheduled = None;
629- }
621+ let wait = Duration::from_nanos(CURSOR_FRAME_NS - since_flip);
622+ arm(&drm.handle, surface, crtc, wait, move |state| {
630623 draw(state, crtc, now_ns());
631- TimeoutAction::Drop
632624 });
633- surface.scheduled = token.ok();
634625 return;
635626 }
636627 let Some((vblank, start)) = surface.next_deadline(now) else {
@@ -638,19 +629,44 @@ fn schedule(state: &mut State, crtc: crtc::Handle) {
638629 return;
639630 };
640631 surface.target_ns = Some(vblank);
641- let wait = Timer::from_duration(Duration::from_nanos(start.saturating_sub(now)));
642- let token = drm.handle.insert_source(wait, move |_, _, state| {
643- let target = state.backend.drm_mut().and_then(|d| {
644- let s = d.surfaces.get_mut(&crtc)?;
645- s.scheduled = None;
646- s.target_ns
647- });
632+ let wait = Duration::from_nanos(start.saturating_sub(now));
633+ arm(&drm.handle, surface, crtc, wait, move |state| {
634+ let target = state
635+ .backend
636+ .drm()
637+ .and_then(|d| d.surfaces.get(&crtc)?.target_ns);
648638 if let Some(target) = target {
649639 draw(state, crtc, target);
650640 }
641+ });
642+}
643+
644+/// Runs `fire` once after `after`, tracked in `surface.scheduled` until then.
645+fn arm(
646+ handle: &LoopHandle<'static, State>,
647+ surface: &mut Surface,
648+ crtc: crtc::Handle,
649+ after: Duration,
650+ mut fire: impl FnMut(&mut State) + 'static,
651+) {
652+ let token = handle.insert_source(Timer::from_duration(after), move |_, _, state| {
653+ if let Some(s) = state
654+ .backend
655+ .drm_mut()
656+ .and_then(|d| d.surfaces.get_mut(&crtc))
657+ {
658+ s.scheduled = None;
659+ }
660+ fire(state);
651661 TimeoutAction::Drop
652662 });
653- surface.scheduled = token.ok();
663+ match token {
664+ Ok(token) => surface.scheduled = Some(token),
665+ Err(e) => tracing::warn!(
666+ output = surface.output.name(),
667+ "could not arm a render timer: {e}"
668+ ),
669+ }
654670 }
655671
656672 /// An animation whose frame had nothing to show, like one hidden behind a
@@ -670,19 +686,10 @@ fn draw(state: &mut State, crtc: crtc::Handle, target: u64) {
670686 if surface.scheduled.is_some() {
671687 return;
672688 }
673- let wait = Timer::from_duration(Duration::from_nanos(surface.refresh_ns.max(1_000_000)));
674- let token = drm.handle.insert_source(wait, move |_, _, state| {
675- if let Some(s) = state
676- .backend
677- .drm_mut()
678- .and_then(|d| d.surfaces.get_mut(&crtc))
679- {
680- s.scheduled = None;
681- }
689+ let wait = Duration::from_nanos(surface.refresh_ns.max(1_000_000));
690+ arm(&drm.handle, surface, crtc, wait, move |state| {
682691 schedule(state, crtc);
683- TimeoutAction::Drop
684692 });
685- surface.scheduled = token.ok();
686693 }
687694
688695 /// Renders every output that has something new and no frame in flight.