Skip to main content

FramePairing

Struct FramePairing 

Source
pub struct FramePairing { /* private fields */ }
Expand description

The release gate’s bookkeeping: which frust frame produced each command batch, and therefore which batches may be handed to the native side yet.

A shell records (generation, frame_id) for every batch PlatformViewState::ingest produces (the frame that is about to be submitted carries the geometry the batch describes), reads back the id of the last frame the render side actually presented, and serves PlatformViewState::commands_up_to the resulting boundary. Pure logic: no clock, no platform types, no knowledge of how a shell obtains the two cursors — which is what makes the ordering testable on the host, since a mobile shell’s own frame loop is not.

§Why the frame id, not a count

The UI→render scene channel is depth-1 latest-wins, so submissions and presents are not the same clock — under the measured 120 Hz-submit / 60 Hz-present regime they diverge by half the frames. Pairing against a presented count over-delays by exactly the dropped frames; pairing against the id of the frame that actually presented does not. The price of the id is that a dropped frame’s id never arrives, which is what MAX_FRAMES_IN_FLIGHT exists for.

§Lifecycle

The pairing is a smoothing device, not a correctness barrier: a shell clears it whenever the frames it refers to stop being meaningful (backgrounding, surface recreation), after which the whole backlog releases immediately — a hide or a full replay must reach the native side even though no further frame will ever present to unlock it.

Going idle is the third such moment, and the only one with no lifecycle callback to hang a clear on: the loop simply stops producing frames while the display keeps ticking. A shell reports those ticks (note_idle_tick) so the staleness hatch stays reachable there — without them a batch whose frame never presented is held for the process lifetime (see that method for the full failure mode).

Implementations§

Source§

impl FramePairing

Source

pub fn new() -> Self

Fresh, empty pairing — releases everything until a batch is recorded.

Source

pub fn record(&mut self, generation: u64, frame_id: u64)

Remember that the batch published under generation describes geometry painted by frust frame frame_id.

Source

pub fn note_idle_tick(&mut self)

Report one display tick on which the frame loop produced no frust frame — the tick a shell’s frame gate skipped. Ages every held batch exactly as a submission does (see releasable_generation).

Why the release gate needs an idle clock at all. A batch is released on one of two events: its own frame is confirmed presented, or the submission cursor climbs MAX_FRAMES_IN_FLIGHT past it. A present is recorded only for a Rendered render outcome, so any other one — an encode or acquire skipped against a surface that is not ready, a swapchain reconfigure, a lost surface, an encode/acquire error — leaves the batch’s frame permanently unconfirmed. That is survivable while frames keep flowing, because the submission cursor walks past it within twelve frames. Once the app settles, though, the submission cursor stops too, and neither arm can ever fire again: the settled geometry is held for the process lifetime and the native sibling stays parked at whatever mid-animation rect it last applied — device-observed as a camera preview stuck black behind correct-but-never-delivered geometry, healed only by a surface recreate (which clears the pairing). The display clock is the one cursor still moving at idle, and an idle tick carries exactly the evidence the submission cursor does: that frame is not coming.

Why an idle bound and not an immediate release. Releasing the whole backlog on the last painted frame is not expressible: a touch-driven drag paints with needs_frame == false every frame, so “this paint asked for no continuation frame” cannot tell a settle frame from a mid-drag one, and keying the release on it would turn the gate off for exactly the scrolling case it was built to smooth. Aging by idle ticks costs nothing on any path where frames still flow — a present that does arrive still releases the batch first, unchanged — and bounds the broken path to MAX_FRAMES_IN_FLIGHT display ticks (~100 ms at 120 Hz).

Source

pub fn releasable_generation( &self, presented_frame_id: u64, submitted_frame_id: u64, ) -> u64

The highest generation releasable right now, given the id of the last presented frame and of the last submitted one.

A batch is releasable once its own frame is on screen, or once that frame has fallen MAX_FRAMES_IN_FLIGHT behind the staleness cursor (it was dropped by the latest-wins channel, or never presented at all, and will never reach the screen). The first batch that is neither caps the boundary at its own generation minus one, so everything published before it — including a lifecycle batch that was never paired with a frame at all — still goes out; an empty queue releases everything.

The staleness cursor is the submission cursor plus the current idle stretch (note_idle_tick): the two are the same “frames have moved on past this one” evidence, and with no idle ticks reported this is bit-for-bit the submission-only rule.

Source

pub fn acknowledge(&mut self, generation: u64)

Drop the bookkeeping for every batch the native side has acknowledged — the same generation the shell hands PlatformViewState::acknowledge, so the two stay in step.

Source

pub fn clear(&mut self)

Forget every pairing (backgrounding, surface recreation) — see the type’s Lifecycle note. Also drops the idle stretch, so the ticks counted against frames belonging to a surface (or a foreground session) that is gone cannot age the first batch recorded after it.

Source

pub fn is_empty(&self) -> bool

Whether any batch is still waiting to be paired off.

Trait Implementations§

Source§

impl Debug for FramePairing

Source§

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

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

impl Default for FramePairing

Source§

fn default() -> Self

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

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> 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, 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.