Skip to main content

khora_sdk/
traits.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Injection traits for the engine framework.
16//!
17//! The engine is a generic framework — the application (e.g. editor, game launcher)
18//! provides concrete implementations of these traits to inject platform-specific
19//! behavior, agents, custom phases, and application logic.
20
21use khora_control::DccService;
22use khora_core::agent::ExecutionPhase;
23use khora_core::platform::KhoraWindow;
24
25use crate::GameWorld;
26use crate::InputEvent;
27use crate::WindowConfig;
28
29// ─────────────────────────────────────────────────────────────────────
30// WindowProvider — abstracts the platform window backend
31// ─────────────────────────────────────────────────────────────────────
32
33/// Provides a platform window for the engine to render into.
34///
35/// The engine doesn't know about winit, SDL, or any specific windowing library.
36/// This trait abstracts window creation, event polling, and surface queries.
37pub trait WindowProvider: 'static {
38    /// Creates the window.
39    ///
40    /// The `native_loop` parameter is an opaque handle to the native event loop.
41    /// The implementation must downcast it to the correct type.
42    /// For winit: `native_loop.downcast_ref::<winit::event_loop::ActiveEventLoop>()`.
43    fn create(native_loop: &dyn std::any::Any, config: &WindowConfig) -> Self
44    where
45        Self: Sized;
46
47    /// Polls for window events and returns them as an iterator of raw event types.
48    fn request_redraw(&self);
49
50    /// Returns the current inner size of the window in physical pixels.
51    fn inner_size(&self) -> (u32, u32);
52
53    /// Returns the current DPI scaling factor for the window.
54    fn scale_factor(&self) -> f64;
55
56    /// Returns a reference to the window as a `dyn KhoraWindow` for use by the engine and agents.
57    fn as_khora_window(&self) -> &dyn KhoraWindow;
58
59    /// Translates a raw window event (e.g., winit::event::Event) into an engine-agnostic `InputEvent` that can be forwarded to game logic.
60    fn translate_event(&self, raw_event: &dyn std::any::Any) -> Option<InputEvent>;
61
62    /// Clones a long-lived handle to the raw native window as an opaque
63    /// `Arc<dyn Any>`. Used by the winit runner to insert the underlying
64    /// `Arc<winit::window::Window>` into the service registry so that
65    /// editor hooks (e.g., overlay `begin_frame`) can retrieve it without
66    /// the SDK leaking winit types into application code.
67    fn clone_raw_window_arc(&self) -> std::sync::Arc<dyn std::any::Any + Send + Sync>;
68}
69
70// ─────────────────────────────────────────────────────────────────────
71// AgentProvider — inject agents into the DCC
72// ─────────────────────────────────────────────────────────────────────
73
74/// Allows the application to register custom agents with the DCC.
75///
76/// This is where mode-specific agents (like the editor's UiAgent) are registered.
77/// The engine calls this method once during initialization, after the DCC is created.
78pub trait AgentProvider {
79    /// Register agents with the DCC service.
80    ///
81    /// Use `dcc.register_agent(agent, priority)` for agents active in all modes.
82    /// Use `dcc.register_agent_for_mode(agent, priority, modes)` for mode-specific agents.
83    ///
84    /// The `runtime` bundle provides access to engine services / backends /
85    /// resources that agents may need.
86    fn register_agents(&self, dcc: &DccService, runtime: &mut khora_core::Runtime);
87}
88
89// ─────────────────────────────────────────────────────────────────────
90// PhaseProvider — inject custom execution phases
91// ─────────────────────────────────────────────────────────────────────
92
93/// Allows the application to add custom execution phases to the scheduler.
94///
95/// By default, the engine uses the standard phase order:
96/// OBSERVE → INPUT → TRANSFORM → SIMULATE → OUTPUT → PRESENT
97///
98/// Applications can insert custom phases (e.g. editor-specific phases).
99pub trait PhaseProvider {
100    /// Returns additional phases to insert into the scheduler's phase order.
101    fn custom_phases(&self) -> Vec<ExecutionPhase> {
102        Vec::new()
103    }
104
105    /// Returns phases to remove from the default order.
106    fn removed_phases(&self) -> Vec<ExecutionPhase> {
107        Vec::new()
108    }
109}
110
111// ─────────────────────────────────────────────────────────────────────
112// EngineApp — composite trait
113// ─────────────────────────────────────────────────────────────────────
114
115/// The single generic bound for the engine's application type.
116///
117/// An application must implement `AgentProvider` and `PhaseProvider`.
118/// `AgentProvider::register_agents` is where the app registers its custom agents
119/// with the DCC — game logic belongs in custom agents using `ExecutionTiming`
120/// and `ExecutionPhase`, not in a free-form `update()` method.
121///
122/// The engine also requires these inherent methods on the app type:
123/// - `window_config() -> WindowConfig`
124/// - `new() -> Self`
125/// - `setup(&mut self, world: &mut GameWorld)`
126/// - `update(&mut self, world: &mut GameWorld, inputs: &[InputEvent])`
127/// - `on_shutdown(&mut self)`
128///
129/// # Examples
130///
131/// A minimal application. `setup` populates the world once; `update` runs every
132/// frame. The optional hooks (`on_shutdown`, `before_frame`, …) keep their
133/// no-op defaults.
134///
135/// ```rust,no_run
136/// use khora_sdk::prelude::*;
137/// use khora_sdk::{
138///     AgentProvider, DccService, EngineApp, GameWorld, PhaseProvider, Runtime,
139///     WindowConfig,
140/// };
141///
142/// struct MyGame {
143///     frame: u64,
144/// }
145///
146/// impl EngineApp for MyGame {
147///     fn window_config() -> WindowConfig {
148///         WindowConfig::default()
149///     }
150///     fn new() -> Self {
151///         MyGame { frame: 0 }
152///     }
153///     fn setup(&mut self, world: &mut GameWorld, _runtime: &Runtime) {
154///         world.spawn_camera(ecs::Camera::new_perspective(
155///             std::f32::consts::FRAC_PI_4,
156///             16.0 / 9.0,
157///             0.1,
158///             1000.0,
159///         ));
160///     }
161///     fn update(&mut self, _world: &mut GameWorld, _inputs: &[InputEvent]) {
162///         self.frame += 1;
163///     }
164/// }
165///
166/// impl AgentProvider for MyGame {
167///     fn register_agents(&self, _dcc: &DccService, _runtime: &mut Runtime) {}
168/// }
169/// impl PhaseProvider for MyGame {}
170/// ```
171pub trait EngineApp: AgentProvider + PhaseProvider + Send + Sync {
172    /// Returns the window configuration for the application.
173    fn window_config() -> WindowConfig
174    where
175        Self: Sized;
176
177    /// Creates a new instance of the application.
178    fn new() -> Self
179    where
180        Self: Sized;
181
182    /// Called once during engine initialization to set up the game world.
183    fn setup(&mut self, world: &mut GameWorld, runtime: &khora_core::Runtime);
184
185    /// Called every frame to update game logic.
186    fn update(&mut self, world: &mut GameWorld, inputs: &[InputEvent]);
187
188    /// Called during shutdown to clean up application resources.
189    fn on_shutdown(&mut self) {}
190
191    /// Optional: intercept a raw window event before the engine translates it into
192    /// an [`InputEvent`]. Return `true` if the event was consumed (e.g., by an
193    /// egui overlay) and should NOT be forwarded to game logic.
194    ///
195    /// Default: do not intercept.
196    fn intercept_window_event(
197        &mut self,
198        _event: &dyn std::any::Any,
199        _window: &dyn KhoraWindow,
200    ) -> bool {
201        false
202    }
203
204    /// Optional: called once per frame BEFORE [`update`](Self::update).
205    /// Use to begin a UI overlay frame (e.g., `egui::Context::begin_frame`) and
206    /// render shell chrome (menus, docks, panels) that produces UI commands.
207    fn before_frame(
208        &mut self,
209        _world: &mut GameWorld,
210        _runtime: &khora_core::Runtime,
211        _window: &dyn KhoraWindow,
212    ) {
213    }
214
215    /// Optional: called after the renderer's `begin_frame` and before the
216    /// scheduler dispatches agents. Use to switch the renderer to an offscreen
217    /// viewport target (e.g., `set_render_to_viewport(true)`).
218    fn before_agents(&mut self, _world: &mut GameWorld, _runtime: &khora_core::Runtime) {}
219
220    /// Optional: called after agent execution and `submit_frame_graph`, but
221    /// BEFORE the renderer's `end_frame`. Use to render gizmos to the offscreen
222    /// viewport, switch back to the swapchain (`set_render_to_viewport(false)`),
223    /// and present a UI overlay (`render_overlay`).
224    fn after_agents(&mut self, _world: &mut GameWorld, _runtime: &khora_core::Runtime) {}
225}