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}