viewport_lib/runtime/plugin.rs
1//! Runtime plugin trait and phase ordering.
2
3use crate::interaction::selection::NodeId;
4use super::context::RuntimeStepContext;
5
6/// Named priority band constants for runtime plugins.
7///
8/// Each constant is the base priority for a logical execution phase. Plugins
9/// can use values between bands (e.g. `phase::ANIMATE + 50`) to run at a
10/// specific point within a range.
11pub mod phase {
12 /// First phase each frame. Update time-dependent state before any queries.
13 pub const PREPARE: i32 = 100;
14 /// Ray-cast and object picking.
15 pub const PICK: i32 = 200;
16 /// Selection state updates driven by pick results.
17 pub const SELECT: i32 = 300;
18 /// Transform manipulation from gizmo drag or keyboard input.
19 pub const MANIPULATE: i32 = 400;
20 /// Procedural or keyframe animation.
21 pub const ANIMATE: i32 = 500;
22 /// Physics or simulation. With a fixed timestep this runs once per
23 /// accumulated step.
24 pub const SIMULATE: i32 = 600;
25 /// Runs after all Simulate iterations, before Writeback.
26 pub const POST_SIM: i32 = 700;
27 /// Flush accumulated transform ops to the scene.
28 pub const WRITEBACK: i32 = 800;
29}
30
31/// Named execution phase for a runtime plugin.
32///
33/// This enum is a convenience alias for the numeric priority bands in the
34/// [`phase`] module. Use [`to_priority`](RuntimePhase::to_priority) to convert
35/// to an `i32` priority value.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub enum RuntimePhase {
39 /// Runs first. Update time-dependent state before any queries.
40 Prepare,
41 /// Ray-cast and object picking.
42 Pick,
43 /// Selection state updates driven by pick results.
44 Select,
45 /// Transform manipulation from gizmo drag or keyboard input.
46 Manipulate,
47 /// Procedural or keyframe animation.
48 Animate,
49 /// Physics or simulation. With a fixed timestep this runs once per
50 /// accumulated step, so it may execute multiple times per rendered frame.
51 Simulate,
52 /// Runs after all Simulate iterations, before Writeback.
53 PostSimulate,
54 /// Flush accumulated transform ops to the scene. Runs once per frame after
55 /// all other phases, including after all `Simulate` iterations.
56 Writeback,
57}
58
59impl RuntimePhase {
60 /// Convert this phase to its numeric priority value.
61 pub fn to_priority(&self) -> i32 {
62 match self {
63 RuntimePhase::Prepare => phase::PREPARE,
64 RuntimePhase::Pick => phase::PICK,
65 RuntimePhase::Select => phase::SELECT,
66 RuntimePhase::Manipulate => phase::MANIPULATE,
67 RuntimePhase::Animate => phase::ANIMATE,
68 RuntimePhase::Simulate => phase::SIMULATE,
69 RuntimePhase::PostSimulate => phase::POST_SIM,
70 RuntimePhase::Writeback => phase::WRITEBACK,
71 }
72 }
73}
74
75/// An event emitted by the runtime when the scene node set changes.
76#[derive(Debug, Clone)]
77pub enum RuntimeEvent {
78 /// A node was added to the scene since the last frame.
79 NodeAdded(NodeId),
80 /// A node was removed from the scene since the last frame.
81 NodeRemoved(NodeId),
82}
83
84/// A plugin that executes during the scene step.
85///
86/// Register plugins with [`super::ViewportRuntime::with_plugin`]. Each frame
87/// the runtime calls `submit`, then `step` (once or more for simulate phases),
88/// then `collect`.
89///
90/// # Example
91///
92/// ```rust,ignore
93/// use viewport_lib::runtime::{RuntimePlugin, RuntimeStepContext};
94/// use viewport_lib::runtime::plugin::phase;
95///
96/// struct GravityPlugin {
97/// gravity: glam::Vec3,
98/// }
99///
100/// impl RuntimePlugin for GravityPlugin {
101/// fn priority(&self) -> i32 {
102/// phase::SIMULATE
103/// }
104///
105/// fn step(&mut self, ctx: &mut RuntimeStepContext) {
106/// // Apply gravity to tracked bodies via ctx.writeback.set(id, new_transform).
107/// }
108/// }
109/// ```
110pub trait RuntimePlugin: Send + 'static {
111 /// Numeric execution priority. Lower values run first. Use the constants
112 /// in [`phase`] as base values and offset within a band if needed.
113 fn priority(&self) -> i32;
114
115 /// Called once per frame before the step loop, in priority order.
116 ///
117 /// Use this to kick off background work (e.g. async physics) that will
118 /// be read in `collect` on the next frame.
119 fn submit(&mut self, _ctx: &RuntimeStepContext<'_>) {}
120
121 /// Called once per frame after the step loop, in priority order.
122 ///
123 /// Use this to read results from background work started in `submit`.
124 fn collect(&mut self, _ctx: &mut RuntimeStepContext<'_>) {}
125
126 /// Called for each lifecycle event before the step loop.
127 fn on_event(&mut self, _event: &RuntimeEvent, _ctx: &mut RuntimeStepContext<'_>) {}
128
129 /// Called once per phase execution.
130 ///
131 /// For plugins in the `[SIMULATE, POST_SIM)` range with a fixed timestep,
132 /// `ctx.dt` is the fixed step size and this is called once per step. For
133 /// all other priorities, `ctx.dt` is the wall-clock frame delta and this
134 /// is called once per frame.
135 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>);
136}