Skip to main content

FrameInputs

Struct FrameInputs 

Source
pub struct FrameInputs {
    pub signals_dirty: bool,
    pub events_since_last_frame: bool,
    pub pointer_capture_active: bool,
    pub focus_or_ime_changed: bool,
    pub last_needs_frame: bool,
    pub last_needs_frame_paced_only: bool,
    pub change_flags_pending: bool,
    pub deferred_callbacks_pending: bool,
    pub theme_or_appearance_changed: bool,
    pub surface_changed_or_resized: bool,
    pub a11y_action_performed: bool,
    pub resumed_recently: bool,
}
Expand description

The per-frame OR-list a shell gathers and hands to FrameGate::decide.

Every field is a “something that needs this frame to run” signal; the gate runs the frame if any is true, with three deliberate deltas noted in the field docs:

  • focus_or_ime_changed is an edge, not a level: it reports that the focus/IME session moved since the shell last looked, so a steady focus session no longer forces a frame every vsync (and, alone among the wake inputs, it does not disqualify pacing).

  • Semantics adapter needs is not a field here: semantics-publish gating stays shell-side (both Android and iOS are generation-gated via AppTree::semantics_if_changed), so it never gates whole-frame production.

  • resumed_recently is the added input for the resume warmup (see WARMUP_FRAMES); the gate also drives this same condition internally via FrameGate::note_resumed, so a shell may leave the field false and rely on the counter (both force a Run).

Default is all-false (the “nothing changed” baseline a FrameGate::decide turns into a FrameDecision::Skip).

Fields§

§signals_dirty: bool

tracked-signal dirty. A tracked signal changed since the last frame. Source: frust_reactive::ReactiveRuntime::take_signals_dirty, read by the shell after its per-frame pump_local per that crate’s pump-first ordering contract.

§events_since_last_frame: bool

events dispatched since last frame. A pointer/scroll/key/IME event reached RenderRoot::event between frames. Source: a shell-side latch set by the nativeOnTouch/IME entry points and cleared each frame.

§pointer_capture_active: bool

active pointer capture. A gesture is mid-drag and the captured widget may animate/track the pointer. Source: AppTree/RenderRoot::is_pointer_captured.

§focus_or_ime_changed: bool

focus/IME session moved. The root’s focus flag or its published IME surface changed since the shell last produced a frame — an edge, not a level. Source: AppTree::focus_ime_generation compared against the shell’s cached copy, which the shell commits only once the gate has decided to run this frame (a peek at gather time — see the deferral note at the end of this doc).

Why an edge. This was a level input (RenderRoot::is_focus_active || ime_state().is_some()) — the original conservative default. Because it forces a Run through any_set and disqualified is_paced_only_frame, any screen holding root focus rendered every single vsync for as long as the focus lasted, and caret pacing was unreachable while a caret blinked: measured at 62–120 fps on a static screen whose only live input was focus (Xiaomi 12). As an edge it still forces one frame per transition (focus gained / lost, IME surface published / cleared) while a steady focus session leaves the gate free to idle or pace.

Why one frame is enough. Every IME event the platform delivers also trips events_since_last_frame (both shells latch it in their ime_apply entry points), and the per-frame platform IME reconcile (Kotlin doFrame / Swift renderFrame) polls the published Rust state on its own cadence, independent of whether Rust produced a frame — all it needs is that state to be current, which the edge guarantees by forcing the frame after every change.

Unlike the other wake inputs this one is not an is_paced_only_frame disqualifier, so an edge landing on a tick whose only other dirtiness is a paced loop is consumed by whichever tick’s pacing decision resolves to Run: the repaint then lands with the loop’s next paced frame (bounded by one cosmetic_loop_rate interval), and the platform’s IME poll is unaffected either way. This is exactly why the shells peek the generation rather than draining it at gather time — an edge that a Skip erased would make the next repaint wait out the loop’s full effective interval instead of the bound below. A per-request paced interval (FramePacing::requested_interval) never widens that bound — FrameGate::decide_paced tightens an edge-carrying tick back to the theme cap on purpose, so a 500ms caret cannot turn a focus transition into a 500ms lag.

§last_needs_frame: bool

last paint’s needs_frame. The previous paint advanced an animation/transition and asked for another frame. Source: PaintOutcome::needs_frame, latched by the shell from the prior frame’s AppTree::paint return.

§last_needs_frame_paced_only: bool

last paint’s needs_frame_paced_only: the prior frame’s frame request aggregated to frust_core::TickClass::CosmeticLoop alone — a pacable decorative loop with no concurrent transition. Latched by the shell from PaintOutcome::needs_frame_paced_only. Meaningful only alongside last_needs_frame; with every other input clear (is_paced_only_frame) it is the sole signal FrameGate::decide_paced throttles to the theme’s cadence. Any concurrent transition/input clears it, so the frame runs immediately.

§change_flags_pending: bool

pending ChangeFlags. A rebuild (or set_theme) left layout/ paint dirtiness undrained. Source: AppTree::has_pending_change_flags (a non-draining peek, so a skipped frame preserves the flags).

§deferred_callbacks_pending: bool

deferred callbacks owed a flush. A widget queued a state-bearing callback during a state-free pass and raised frust_core::mark_pending_result_flush, which only RenderRoot::rebuild can drain (it holds the &mut State the callback needs). Source: frust_core::has_pending_result_flush, the non-draining peek — draining stays the rebuild’s job on a frame that actually runs.

Hardening under the default-to-run rule rather than a reproduced stall: every mark raised today is also covered by another input (a mark from inside a rebuild is drained by that same rebuild, whose leftover pending |= PAINT reaches change_flags_pending and whose deferred_frame reaches last_needs_frame; frust-widgets’ paint-time long-press latch pairs its mark with a request_frame). This input closes the general case those two happen to cover — a mark raised with nothing else dirty must never wait for the next stray touch.

§theme_or_appearance_changed: bool

theme-override/appearance change. The app-facing theme override or the platform light/dark preference changed this tick. Source: the shell’s per-frame ThemeOverrideWatcher/appearance poll (see [crate::theme_override]).

§surface_changed_or_resized: bool

surface resize/recreation. The GPU surface was created, resized, or recreated (rotation/backgrounding). Source: the shell’s nativeOnSurfaceChanged/frust_resize path.

§a11y_action_performed: bool

accessibility actions. A platform accesskit_* action was performed this tick, mutating state. Source: the shell’s a11y-action drain feeding AppTree::perform_accessibility_action.

§resumed_recently: bool

Added input: resume warmup. The app resumed / the surface was (re)created within the last WARMUP_FRAMES frames. A shell may set this explicitly, or leave it false and let FrameGate::note_resumed drive the same condition through the gate’s own countdown — both force a Run.

Implementations§

Source§

impl FrameInputs

Source

pub fn any_set(&self) -> bool

Whether any input signals that this frame must run. The gate ORs the full field set — the single decision rule the whole type exists to feed (see FrameGate::decide).

Source

pub fn is_paced_only_frame(&self) -> bool

Whether the only dirtiness this frame is a paced (CosmeticLoop) frame request — the throttleable case FrameGate::decide_paced paces.

True iff last_needs_frame and last_needs_frame_paced_only are both set and every other OR-list input is clear. Any input/signal/change-flag/transition alongside it makes this false, so the gate runs the frame immediately rather than pacing it (the default-to-run rule — see docs/CODE_STANDARDS.md’s Frame-Gate conventions).

One deliberate exception: focus_or_ime_changed is not a disqualifier. Its level-input predecessor was one, and — being true for a whole focus session — that is what made caret pacing unreachable: a blinking caret in a focused field is precisely the paced loop this gate must be able to throttle (that field’s doc has the device measurement). The edge that replaced it reports one transition, not a session, so an edge arriving mid-loop is simply carried by the loop’s next paced frame instead of pre-empting it. It is one repaint per transition, not per tick: the shells peek it, so it keeps reporting across every skipped tick in between and is cleared by the frame that finally runs.

Trait Implementations§

Source§

impl Clone for FrameInputs

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for FrameInputs

Source§

impl Debug for FrameInputs

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for FrameInputs

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Eq for FrameInputs

Source§

impl PartialEq for FrameInputs

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for FrameInputs

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Brush for T
where T: Clone + PartialEq + Default + Debug,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> StorageAccess<T> for T

Source§

fn as_borrowed(&self) -> &T

Borrows the value.
Source§

fn into_taken(self) -> T

Takes the value.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.