pub struct FrameGate { /* private fields */ }Expand description
The per-shell skip-frame gate: a plain struct — no
globals — a shell constructs once and drives each frame via
decide.
Owns two pieces of state: whether the gate is enabled at all (the
NO_FRAME_GATE_VAR kill switch, resolved once at construction) and the
resume-warmup countdown (WARMUP_FRAMES, seeded by
note_resumed) — the standalone, host-testable
decision type the mobile shells wire in.
Implementations§
Source§impl FrameGate
impl FrameGate
Sourcepub fn new() -> Self
pub fn new() -> Self
A gate honoring the NO_FRAME_GATE_VAR kill switch — what every
shell constructs. When the variable is set (compile-time --define or
runtime env, any non-"0" value), this is equivalent to
disabled.
Sourcepub fn disabled() -> Self
pub fn disabled() -> Self
A gate that always Runs regardless of inputs —
the explicit disabled/kill-switch form (and a test seam bypassing the
env read). Mirrors FrameGate::new’s behavior when
NO_FRAME_GATE_VAR is set.
Sourcepub fn with_enabled(enabled: bool) -> Self
pub fn with_enabled(enabled: bool) -> Self
Construct with an explicit enabled flag, bypassing the env read — the
test/advanced seam (mirrors crate::perf::FrameStats::new_enabled).
Animation pacing follows enabled (a disabled gate never paces because
it never skips); use with_flags to vary the two
independently.
Sourcepub fn with_flags(enabled: bool, anim_pacing: bool) -> Self
pub fn with_flags(enabled: bool, anim_pacing: bool) -> Self
Construct with explicit enabled (whole-frame skip) and anim_pacing
(paced-loop throttling) flags, bypassing both env reads — the test seam
for the pacing behavior in isolation.
Sourcepub fn is_enabled(&self) -> bool
pub fn is_enabled(&self) -> bool
Whether the gate is active (can ever return FrameDecision::Skip).
false for a disabled gate or when the kill switch
is engaged.
Sourcepub fn note_resumed(&mut self)
pub fn note_resumed(&mut self)
Open the resume-warmup window: the next WARMUP_FRAMES
decide calls force a FrameDecision::Run.
A shell calls this on resume and on surface (re)creation, where the
first tick’s change signals may not yet be observable (see
WARMUP_FRAMES).
Sourcepub fn warmup_remaining(&self) -> u8
pub fn warmup_remaining(&self) -> u8
Frames left in the resume-warmup window (0 when not warming up).
Exposed for tests/diagnostics.
Sourcepub fn decide(&mut self, inputs: FrameInputs) -> FrameDecision
pub fn decide(&mut self, inputs: FrameInputs) -> FrameDecision
Decide whether this frame runs.
Returns FrameDecision::Run when any of:
- the gate is disabled (kill switch /
disabled), - the resume warmup is active (decrementing it by one), or
- any
FrameInputsfield is set (FrameInputs::any_set) —
otherwise FrameDecision::Skip. See FrameInputs’s docs for what
each input signal means.
Takes &mut self because it advances the resume-warmup countdown.
This is the non-paced entry: a paced-only frame runs on every tick (no
throttling), the conservative pre-pacing behavior. Use
decide_paced to honor the theme’s cosmetic-loop
cadence.
Sourcepub fn decide_paced(
&mut self,
inputs: FrameInputs,
pacing: FramePacing,
) -> FrameDecision
pub fn decide_paced( &mut self, inputs: FrameInputs, pacing: FramePacing, ) -> FrameDecision
Decide whether this frame runs, honoring animation pacing.
Identical to decide except that when the only
dirtiness is a paced (CosmeticLoop) frame request
(FrameInputs::is_paced_only_frame) and pacing is enabled, the frame
is throttled to FramePacing::effective_interval (the theme cap folded
with the previous paint’s requested interval): it runs only once
pacing.now - last_paced_run >= interval, otherwise Skips. A skip
leaves last_needs_frame alive (the shell doesn’t repaint, so it never
re-latches), so the gate keeps waking and never starves the loop; the
interval is re-anchored to the clock of every produced frame (whatever
its cause), so a transition frame mid-loop resets the cadence.
Any non-paced input (an event, a signal write, a transition request,
pending change flags, …) makes is_paced_only_frame false, so the
frame runs immediately — pacing never delays real work.
The focus/IME edge tightens the tick back to the theme cap. The one
wake input that rides inside a paced decision
(FrameInputs::focus_or_ime_changed) has its deferral bounded by the
interval in force, so honoring a long per-request interval on that tick
would stretch a focus/IME transition’s repaint out to (say) a caret’s
500ms. Instead a tick carrying the edge paces at
FramePacing::interval — the theme’s own cap — leaving the edge’s
worst-case deferral exactly what it was before per-request intervals
existed: one cosmetic_loop_rate interval (33ms at the 30Hz default;
≤100ms at CosmeticLoopRate::FLOOR_HZ). The cost is at most one extra
frame per focus/IME transition — an edge reporting one transition, not
a per-tick level (see docs/LIMITATIONS.md’s
focus-ime-edge-paced-deferral).
That bound only holds end-to-end because the shells peek the edge:
the tightening itself resolves most edge-carrying ticks to Skip (the
anchor is typically one vsync old, well inside the cap), so a shell that
drained its generation cache at gather time would erase the edge on the
very tick the tightening deferred it, and the repaint would fall back to
the full FramePacing::effective_interval. The edge must keep being
reported until a tick actually runs — see
FrameInputs::focus_or_ime_changed.