simvx.core.scene_step¶
The single canonical per-frame logic-phase sequence, shared by every embedder.
Consumers that drive a :class:~simvx.core.scene_tree.SceneTree one frame at a time
(the windowed/headless/streaming :class:FrameLoop, the editor’s PlayMode and the
headless :class:~simvx.core.testing.SceneRunner today; the AI rendered session and
future embedders next) re-spell the same load-bearing order: pump polled input,
step physics, dispatch queued UI events, run logic. The order is pure SceneTree
logic (physics_tick /
interpolate_physics / tick are core methods with zero Vulkan), so it lives in
core where every embedder, including the ones that cannot import graphics, can share
one definition. (The web runtime owns a deliberately different policy and is not routed
through here yet: see its notes.)
- func:
step_scene_logicruns exactly that sequence and stops BEFORE the GPU half (Draw2D reset +tree.render+ submit/present) and BEFORE the windowed Input frame boundary (Input._end_frame/Input._new_frame): those are the caller’s job, because they diverge per embedder (swapchain present vs offscreen render-to-target vs nothing). This module imports nothing from graphics.
Per-phase variation is expressed through :class:ScenePhases: a frozen set of
optional hooks. phases=None runs the engine-default behaviour (one fixed
physics step + interpolate to the step boundary, then one logic tick). A caller
that owns a different physics-stepping policy (the windowed accumulator clock) or
a different per-node walk (the editor’s error-tolerant / profiled walkers) supplies
node_walk; a caller that polls a device the platform does not deliver by callback
supplies input_pump; a caller with a queued UI-event drain or a
between-physics-and-logic window-sync supplies ui_events; a profiler supplies
on_phase.
Module Contents¶
Classes¶
Optional per-phase hooks for :func: |
Functions¶
Run one frame of the canonical logic-phase sequence on |
Data¶
API¶
- simvx.core.scene_step.__all__¶
[‘ScenePhases’, ‘step_scene_logic’]
- class simvx.core.scene_step.ScenePhases[source]¶
Optional per-phase hooks for :func:
step_scene_logic.Every field defaults to
None, which selects the engine-default behaviour for that phase. Supply a hook only to override one phase; the canonical order (input pump, then physics, then UI events, then logic) is fixed and not overridable. The fields are declared in that order and are keyword-only: four interchangeable callables are not something to pass by position.Attributes: input_pump: called once at the very top of the frame, before physics, to publish input the platform does not deliver by callback. The frame’s coalesced pointer motion is delivered just before it, by the engine, for every caller. Controllers are polled, so this is where the poll and the flush of its edges belong: a pad edge published after the physics phase reaches a fixed tick a whole frame later than the same action bound to a key, which the window callback delivers before the frame starts. node_walk:
(root, dt, phase)wherephaseis"physics"or"process". When supplied it replaces the default physics step and/or the default logic tick: the caller owns how the tree is walked for that phase (its own physics-stepping policy, an error-tolerant or profiled per-node walk, etc.).rootis the scene root, orNonewhen the tree has no root. ui_events: called once, after physics and before logic, to drain queued UI / input events into the tree. This is the same slot the windowed loop uses for window-size and theme sync and the editor uses to flush its viewport UI-event queue. on_phase:(name, edge)withedgein{"begin", "end"}, fired around each of"input","physics","ui","process"so a profiler can bracket per-phase timings.- input_pump: collections.abc.Callable[[], None] | None¶
None
- node_walk: collections.abc.Callable[[simvx.core.node.Node | None, float, str], None] | None¶
None
- ui_events: collections.abc.Callable[[], None] | None¶
None
- on_phase: collections.abc.Callable[[str, str], None] | None¶
None
- simvx.core.scene_step.step_scene_logic(tree: simvx.core.scene_tree.SceneTree, dt: float, *, time_scale: float = 1.0, phases: simvx.core.scene_step.ScenePhases | None = None) None[source]¶
Run one frame of the canonical logic-phase sequence on
tree.Order (load-bearing):
input –
tree.flush_pointer_motion(), thenphases.input_pump()if supplied, so the frame’s pointer motion and a polled device’s edges are both published before any tick can read them.physics –
tree.physics_tick(dt * time_scale)thentree.interpolate_physics(1.0), ORphases.node_walk(root, dt, "physics").ui –
phases.ui_events()if supplied (no engine default).process –
tree.tick(dt * time_scale), ORphases.node_walk(root, dt, "process").
Stops before Draw2D / submit / present and before the Input frame boundary: those stay with the caller. Imports nothing from graphics.
With
phases=Nonethis is byte-identical to a hand-rolledtree.physics_tick(dt * time_scale); tree.interpolate_physics(1.0); tree.tick(dt * time_scale).Args: tree: the scene tree to advance. dt: frame delta in seconds (unscaled). time_scale: multiplier applied to
dtfor the default physics and logic steps (slow-mo / fast-forward). Whennode_walkis supplied the caller owns scaling, sodtis passed through unscaled. phases: optional per-phase overrides;Noneselects engine defaults.