viewport_lib/runtime/plugin.rs
1//! Runtime plugin trait and phase ordering.
2
3use super::context::RuntimeStepContext;
4use crate::interaction::select::selection::NodeId;
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/// Registration model: runtime plugins are multi-instance. They carry no
91/// external identity, so registering two of the same type runs both, in
92/// priority order; the runtime does not deduplicate. This is intentional (two
93/// emitters, two solvers with different config are valid) and is frozen
94/// behavior. Item-type plugins differ: they are singleton-by-type because
95/// their `type_name` is referenced externally. See
96/// [`ItemTypePlugin`](crate::plugin_api::ItemTypePlugin).
97///
98/// # Example
99///
100/// ```rust,ignore
101/// use viewport_lib::runtime::{RuntimePlugin, RuntimeStepContext};
102/// use viewport_lib::runtime::plugin::phase;
103///
104/// struct GravityPlugin {
105/// gravity: glam::Vec3,
106/// }
107///
108/// impl RuntimePlugin for GravityPlugin {
109/// fn priority(&self) -> i32 {
110/// phase::SIMULATE
111/// }
112///
113/// fn step(&mut self, ctx: &mut RuntimeStepContext) {
114/// // Apply gravity to tracked bodies via ctx.writeback.set(id, new_transform).
115/// }
116/// }
117/// ```
118pub trait RuntimePlugin: Send + 'static {
119 /// Numeric execution priority. Lower values run first. Use the constants
120 /// in [`phase`] as base values and offset within a band if needed.
121 fn priority(&self) -> i32;
122
123 /// A stable name for this plugin, used to key per-plugin timing in
124 /// [`RuntimeStats`](crate::RuntimeStats). Defaults to the concrete type
125 /// name; override to disambiguate when several plugins share a type or to
126 /// give a friendlier label.
127 fn type_name(&self) -> &'static str {
128 std::any::type_name::<Self>()
129 }
130
131 /// Called once per frame before the step loop, in priority order.
132 ///
133 /// Use this to kick off background work (e.g. async physics) that will
134 /// be read in `collect` on the next frame.
135 fn submit(&mut self, _ctx: &RuntimeStepContext<'_>) {}
136
137 /// Called once per frame after the step loop, in priority order.
138 ///
139 /// Use this to read results from background work started in `submit`.
140 fn collect(&mut self, _ctx: &mut RuntimeStepContext<'_>) {}
141
142 /// Called for each lifecycle event before the step loop.
143 fn on_event(&mut self, _event: &RuntimeEvent, _ctx: &mut RuntimeStepContext<'_>) {}
144
145 /// Called once per phase execution.
146 ///
147 /// For plugins in the `[SIMULATE, POST_SIM)` range with a fixed timestep,
148 /// `ctx.dt` is the fixed step size and this is called once per step. For
149 /// all other priorities, `ctx.dt` is the wall-clock frame delta and this
150 /// is called once per frame.
151 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>);
152}