Phosphor
Qt6 / Wayland library suite for window-management tools
 
Loading...
Searching...
No Matches
PhosphorAnimation::QtQuickClock Class Referencefinal

Qt Quick adapter implementing IMotionClock. More...

#include <phosphor-animation/include/PhosphorAnimation/QtQuickClock.h>

Inheritance diagram for PhosphorAnimation::QtQuickClock:
[legend]

Public Member Functions

 QtQuickClock (QQuickWindow *window)
 Construct a clock bound to window.
 
 ~QtQuickClock () override
 
std::chrono::nanoseconds now () const override
 Monotonically non-decreasing steady-clock reading (nanoseconds).
 
qreal refreshRate () const override
 Nominal refresh rate in Hz, or zero if unknown.
 
void requestFrame () override
 Schedule another paint tick. Idempotent within a single frame.
 
const void * epochIdentity () const override
 Opaque epoch identity for rebindClock compatibility.
 
QQuickWindow * window () const
 The QQuickWindow this clock is bound to. May be null.
 
- Public Member Functions inherited from PhosphorAnimation::IMotionClock
virtual ~IMotionClock ()=default
 
 IMotionClock (const IMotionClock &)=delete
 
IMotionClockoperator= (const IMotionClock &)=delete
 
 IMotionClock (IMotionClock &&)=delete
 
IMotionClockoperator= (IMotionClock &&)=delete
 

Additional Inherited Members

- Static Public Member Functions inherited from PhosphorAnimation::IMotionClock
static const void * steadyClockEpoch ()
 Shared sentinel for std::chrono::steady_clock-backed clocks.
 
static bool epochCompatible (const IMotionClock *a, const IMotionClock *b)
 True iff both clocks are non-null and share the same non-null epochIdentity.
 
- Protected Member Functions inherited from PhosphorAnimation::IMotionClock
 IMotionClock ()=default
 

Detailed Description

Qt Quick adapter implementing IMotionClock.

Ships only when the library is built with PHOSPHOR_ANIMATION_QUICK=ON — the flag gates both the header install and the Qt6::Quick link dependency so core consumers that don't need QML motion (the KWin compositor adapter, headless tools, daemon) don't pull in Qt Quick transitively.

Bound to one QQuickWindow per instance. Multi-window QML shells construct one QtQuickClock per top-level window and route their AnimatedValue<T> instances through the matching clock — same per-output phase-locking rationale as CompositorClock.

One clock per window

Two QtQuickClock instances bound to the same QQuickWindow will each connect to its beforeRendering signal and each write into its own independent m_nowCache. That works mechanically (no shared state between them, no UAF) but defeats the entire point of caching: the two clocks report identical timestamps with double the signal-dispatch cost, and a rebind from one to the other would waste an epoch-compatibility check on clocks that are already equivalent. Callers must therefore maintain one QtQuickClock per QQuickWindow (cache it on the window via dynamic property or a lookup map). The class does not enforce this because the identity lookup belongs on the caller's side — two unrelated shells may legitimately each want their own clock on the same embedded window — and a Q_ASSERT here would force that policy rather than document it.

Driver model

The clock subscribes to QQuickWindow::beforeRendering — fires once per scene-graph render pass on the render thread. The connected lambda latches the current std::chrono::steady_clock::now() into the clock's cached timestamp, so now() returns a vsync-aligned reading for the frame currently being rendered.

requestFrame() forwards to QQuickWindow::update(), which is the Qt Quick equivalent of effects->addRepaint() — schedules another render pass from whichever thread the call lands on (Qt's update() is thread-safe by design).

Refresh rate

refreshRate() reads from window->screen()->refreshRate() when available. Returns 0.0 (unknown) if the window is not yet shown or has no screen attached — the usual state during construction.

Monotonicity

std::chrono::steady_clock is monotonic by contract, so the clamp CompositorClock applies for KWin's (rarely) regressing presentTime is unnecessary here.

Cross-thread read safety

The beforeRendering slot fires on the Qt Quick render thread; any consumer calling now() from a different thread (e.g., a QML scene animation driven from the GUI thread) would otherwise race on a plain 64-bit field. The cached timestamp is held as std::atomic<int64_t> with memory_order_relaxed load/store so the cross-thread read is well-defined even though the common case (both reader and writer on the render thread) pays only the cost of a plain aligned load/store on x86_64.

Teardown race

Qt::DirectConnection slots execute synchronously on the signal's emitting thread; Qt's implicit ~QObject disconnect does not join already-running slot bodies. Destroying the clock on the GUI thread while the render thread was mid-slot would otherwise write to a freed cache. The adapter mitigates this internally via a three-stage teardown: set detached, disconnect the signal, then spin-wait until every in-progress slot body has exited. The join makes destruction strictly synchronous with respect to the render-thread slot — consumers do not need to externally synchronise destruction and are not exposed to the narrow atomic-ordering UAF the flag-only pattern permitted.

The join imposes one constraint: the destructor MUST NOT run on the render thread that fires the slot. A self-join would deadlock. QtQuickClock's ownership model makes this trivially satisfied — clocks are owned by GUI-thread objects (compositor effects, QtQuick items driving custom animations) and destroyed on that thread. The spin is typically zero-iterations and at worst waits for one slot body's microsecond-scale duration.

Tearing down a clock while the render thread is still active is legal but wasteful — the destructor blocks the owning thread on the render thread's next scheduling window. Prefer destroying clocks after the scene graph has been torn down.

Constructor & Destructor Documentation

◆ QtQuickClock()

PhosphorAnimation::QtQuickClock::QtQuickClock ( QQuickWindow *  window)
explicit

Construct a clock bound to window.

The clock subscribes to the window's beforeRendering signal immediately — construction does not require the window to be shown. window may be null for testing; in that mode now() stays at zero and requestFrame() is a no-op.

◆ ~QtQuickClock()

PhosphorAnimation::QtQuickClock::~QtQuickClock ( )
override

Member Function Documentation

◆ epochIdentity()

const void * PhosphorAnimation::QtQuickClock::epochIdentity ( ) const
overridevirtual

Opaque epoch identity for rebindClock compatibility.

nullptr (default) = incompatible with rebind onto any other clock. Clocks sharing the same non-null identity are rebase-safe.

Reimplemented from PhosphorAnimation::IMotionClock.

◆ now()

std::chrono::nanoseconds PhosphorAnimation::QtQuickClock::now ( ) const
overridevirtual

Monotonically non-decreasing steady-clock reading (nanoseconds).

Implements PhosphorAnimation::IMotionClock.

◆ refreshRate()

qreal PhosphorAnimation::QtQuickClock::refreshRate ( ) const
overridevirtual

Nominal refresh rate in Hz, or zero if unknown.

Implements PhosphorAnimation::IMotionClock.

◆ requestFrame()

void PhosphorAnimation::QtQuickClock::requestFrame ( )
overridevirtual

Schedule another paint tick. Idempotent within a single frame.

Implements PhosphorAnimation::IMotionClock.

◆ window()

QQuickWindow * PhosphorAnimation::QtQuickClock::window ( ) const

The QQuickWindow this clock is bound to. May be null.


The documentation for this class was generated from the following file: