Skip to main content

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}