1#![warn(missing_docs)]
21
22mod game_world;
23mod vessel;
24
25pub use game_world::GameWorld;
26pub use vessel::{spawn_cube_at, spawn_plane, spawn_sphere, Vessel};
27
28use anyhow::Result;
29use khora_control::{DccConfig, DccService};
30use khora_core::platform::KhoraWindow;
31use khora_core::renderer::api::core::RenderSettings;
32use khora_core::renderer::api::scene::RenderObject;
33use khora_core::renderer::traits::RenderSystem;
34use khora_core::telemetry::MonitoredResourceType;
35use khora_core::ServiceRegistry;
36use khora_infra::platform::input::translate_winit_input;
37use khora_infra::platform::window::{WinitWindow, WinitWindowBuilder};
38use khora_infra::telemetry::memory_monitor::MemoryMonitor;
39use khora_infra::{GpuMonitor, WgpuRenderSystem};
40use khora_telemetry::TelemetryService;
41use std::collections::VecDeque;
42use std::sync::atomic::{AtomicBool, Ordering};
43use std::sync::{Arc, Mutex};
44use std::time::Duration;
45use winit::application::ApplicationHandler;
46use winit::event::WindowEvent;
47use winit::event_loop::{ActiveEventLoop, EventLoop};
48use winit::window::WindowId;
49
50pub mod prelude {
51 pub use khora_core::asset::{AssetHandle, AssetMetadata, AssetSource, AssetUUID};
53 pub use khora_core::renderer::api::{
54 core::{ShaderModuleDescriptor, ShaderModuleId, ShaderSourceData},
55 pipeline::state::{DepthBiasState, StencilFaceState},
56 pipeline::{
57 ColorTargetStateDescriptor, ColorWrites, CompareFunction, DepthStencilStateDescriptor,
58 MultisampleStateDescriptor, PipelineLayoutDescriptor, RenderPipelineDescriptor,
59 RenderPipelineId, VertexAttributeDescriptor, VertexBufferLayoutDescriptor,
60 VertexFormat, VertexStepMode,
61 },
62 resource::{BufferDescriptor, BufferId, BufferUsage},
63 scene::RenderObject,
64 util::{IndexFormat, SampleCount, ShaderStageFlags as ShaderStage, TextureFormat},
65 };
66 pub use khora_core::EngineContext;
67 pub use khora_data::allocators::SaaTrackingAllocator;
68 pub use khora_data::ecs::HandleComponent;
69 pub use khora_infra::platform::input::MouseButton;
70
71 pub mod ecs {
72 pub use khora_core::ecs::entity::EntityId;
74 pub use khora_core::renderer::light::{DirectionalLight, LightType, PointLight, SpotLight};
75 pub use khora_data::ecs::{
76 Camera, Component, ComponentBundle, GlobalTransform, Light, MaterialComponent,
77 Transform,
78 };
79 }
80
81 pub mod materials {
82 pub use khora_core::asset::{
84 EmissiveMaterial, StandardMaterial, UnlitMaterial, WireframeMaterial,
85 };
86 }
87
88 pub mod shaders {
89 pub use khora_lanes::render_lane::shaders::*;
91 }
92
93 pub mod math {
94 pub use khora_core::math::*;
96 }
97}
98
99pub use khora_core::EngineContext;
100pub use khora_infra::platform::input::InputEvent;
101
102pub trait Application: Sized + 'static {
107 fn new(context: EngineContext) -> Self;
109
110 fn setup(&mut self, _world: &mut GameWorld) {}
112
113 fn update(&mut self, _world: &mut GameWorld, _inputs: &[InputEvent]) {}
115
116 fn render(&mut self) -> Vec<RenderObject> {
118 Vec::new()
119 }
120}
121
122struct EngineState<A: Application> {
124 app: Option<A>,
125 game_world: Option<GameWorld>,
126 window: Option<WinitWindow>,
127 renderer: Option<Arc<Mutex<Box<dyn RenderSystem>>>>,
128 graphics_device: Option<Arc<dyn khora_core::renderer::GraphicsDevice>>,
130 telemetry: Option<TelemetryService>,
131 dcc: Option<DccService>,
132 render_settings: RenderSettings,
133 simulation_started: bool,
134 running: Arc<AtomicBool>,
135 input_events: VecDeque<InputEvent>,
137}
138
139impl<A: Application> EngineState<A> {
140 fn log_telemetry_summary(&self) {
141 if let Some(telemetry) = &self.telemetry {
142 log::info!("--- Telemetry Summary ---");
143 for monitor in telemetry.monitor_registry().get_all_monitors() {
144 let report = monitor.get_usage_report();
145 match monitor.resource_type() {
146 MonitoredResourceType::SystemRam => {
147 let current_mb = report.current_bytes as f64 / (1024.0 * 1024.0);
148 let peak_mb = report.peak_bytes.unwrap_or(0) as f64 / (1024.0 * 1024.0);
149 log::info!(" RAM: {:.2} MB (Peak: {:.2} MB)", current_mb, peak_mb);
150 }
151 MonitoredResourceType::Vram => {
152 let current_mb = report.current_bytes as f64 / (1024.0 * 1024.0);
153 let peak_mb = report.peak_bytes.unwrap_or(0) as f64 / (1024.0 * 1024.0);
154 log::info!(" VRAM: {:.2} MB (Peak: {:.2} MB)", current_mb, peak_mb);
155 }
156 MonitoredResourceType::Gpu => {
157 if let Some(gpu_monitor) = monitor.as_any().downcast_ref::<GpuMonitor>() {
158 if let Some(gpu_report) = gpu_monitor.get_gpu_report() {
159 log::info!(
160 " GPU: {:.3} ms (Frame: {})",
161 gpu_report.frame_total_duration_us().unwrap_or(0) as f32
162 / 1000.0,
163 gpu_report.frame_number
164 );
165 }
166 }
167 }
168 MonitoredResourceType::Hardware => {
169 log::info!(" Hardware: Active");
170 }
171 }
172 }
173 log::info!("-------------------------");
174 }
175 }
176}
177
178impl<A: Application> Drop for EngineState<A> {
179 fn drop(&mut self) {
180 log::info!("EngineState: Shutting down...");
181 self.running.store(false, Ordering::SeqCst);
182 if let Some(renderer) = self.renderer.take() {
183 if let Ok(mut r) = renderer.lock() {
184 r.shutdown();
185 }
186 }
187 log::info!("Engine shutdown complete.");
188 }
189}
190
191impl<A: Application> ApplicationHandler for EngineState<A> {
192 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
193 if self.window.is_some() {
194 return;
195 }
196
197 log::info!("Engine: Initializing...");
198
199 let window = WinitWindowBuilder::new().build(event_loop).unwrap();
200 let mut renderer: Box<dyn RenderSystem> = Box::new(WgpuRenderSystem::new());
201 let renderer_monitors = renderer.init(&window).unwrap();
202
203 let (mut dcc, dcc_rx) = DccService::new(DccConfig::default());
204 let telemetry =
205 TelemetryService::new(Duration::from_secs(1)).with_dcc_sender(dcc.event_sender());
206
207 dcc.start(dcc_rx);
208
209 for monitor in renderer_monitors {
210 telemetry.monitor_registry().register(monitor);
211 }
212
213 let memory_monitor = Arc::new(MemoryMonitor::new("System_RAM".to_string()));
214 telemetry.monitor_registry().register(memory_monitor);
215
216 let graphics_device = renderer.graphics_device();
217
218 let mut services = ServiceRegistry::new();
220 services.insert(graphics_device.clone());
221 let context = EngineContext {
222 world: None,
223 services,
224 };
225
226 let mut app = A::new(context);
227 let mut game_world = GameWorld::new();
228 app.setup(&mut game_world);
229
230 Self::register_default_agents(&dcc, graphics_device.clone());
233
234 let _ = dcc
235 .event_sender()
236 .send(khora_core::telemetry::TelemetryEvent::PhaseChange(
237 "boot".to_string(),
238 ));
239
240 let renderer = Arc::new(Mutex::new(renderer));
242
243 self.window = Some(window);
244 self.renderer = Some(renderer);
245 self.graphics_device = Some(graphics_device);
246 self.telemetry = Some(telemetry);
247 self.dcc = Some(dcc);
248 self.game_world = Some(game_world);
249 self.render_settings = RenderSettings::default();
250 self.simulation_started = false;
251 self.app = Some(app);
252 }
253
254 fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
255 match event {
256 WindowEvent::CloseRequested => {
257 log::info!("Shutdown requested");
258 event_loop.exit();
259 }
260 WindowEvent::Resized(size) => {
261 if let Some(renderer) = self.renderer.as_ref() {
262 log::info!("Window resized: {}x{}", size.width, size.height);
263 if let Ok(mut r) = renderer.lock() {
264 r.resize(size.width, size.height);
265 }
266 }
267 }
268 WindowEvent::RedrawRequested => {
269 self.handle_frame(event_loop);
270 }
271 _ => {
272 if !matches!(event, WindowEvent::CursorMoved { .. }) {
274 log::info!("Raw event: {:?}", event);
275 }
276 if let Some(input_event) = translate_winit_input(&event) {
277 log::info!("Input: {:?}", input_event);
278 self.input_events.push_back(input_event);
279 }
280 }
281 }
282 }
283
284 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
285 if let Some(window) = &self.window {
286 window.request_redraw();
287 }
288 }
289}
290
291impl<A: Application> EngineState<A> {
292 fn register_default_agents(
293 dcc: &DccService,
294 _graphics_device: Arc<dyn khora_core::renderer::GraphicsDevice>,
295 ) {
296 let render_agent = khora_agents::render_agent::RenderAgent::new()
303 .with_telemetry_sender(dcc.event_sender());
304 dcc.register_agent(Arc::new(Mutex::new(render_agent)), 1.0);
305
306 let gc_agent = khora_agents::ecs_agent::GarbageCollectorAgent::new()
307 .with_dcc_sender(dcc.event_sender());
308 dcc.register_agent(Arc::new(Mutex::new(gc_agent)), 0.8);
309
310 log::info!("Engine: Registered {} default agents", dcc.agent_count());
311 }
312
313 fn handle_frame(&mut self, _event_loop: &ActiveEventLoop) {
314 let Some(renderer) = self.renderer.as_ref() else {
315 return;
316 };
317 let Some(telemetry) = self.telemetry.as_mut() else {
318 return;
319 };
320
321 if !self.simulation_started {
322 if let Some(dcc) = &self.dcc {
323 let _ =
324 dcc.event_sender()
325 .send(khora_core::telemetry::TelemetryEvent::PhaseChange(
326 "simulation".to_string(),
327 ));
328 }
329 self.simulation_started = true;
330 }
331
332 let should_log_summary = telemetry.tick();
333
334 let Some(app) = self.app.as_mut() else { return };
335
336 let inputs: Vec<InputEvent> = self.input_events.drain(..).collect();
338
339 if let Some(gw) = self.game_world.as_mut() {
341 app.update(gw, &inputs);
342 }
343
344 let mut services = ServiceRegistry::new();
346 if let Some(device) = &self.graphics_device {
347 services.insert(device.clone());
348 }
349 services.insert(Arc::clone(renderer));
350
351 if let (Some(dcc), Some(gw)) = (&self.dcc, self.game_world.as_mut()) {
355 let mut context = gw.as_engine_context(services);
356 dcc.update_agents(&mut context);
357 }
358
359 if should_log_summary {
360 self.log_telemetry_summary();
361 }
362 }
363}
364
365pub struct Engine;
367
368impl Engine {
369 pub fn run<A: Application>() -> Result<()> {
374 log::info!("Khora Engine SDK: Starting...");
375 let event_loop = EventLoop::new()?;
376
377 let mut app_state = EngineState::<A> {
378 app: None,
379 game_world: None,
380 window: None,
381 renderer: None,
382 graphics_device: None,
383 telemetry: None,
384 dcc: None,
385 render_settings: RenderSettings::default(),
386 simulation_started: false,
387 running: Arc::new(AtomicBool::new(true)),
388 input_events: VecDeque::new(),
389 };
390
391 event_loop.run_app(&mut app_state)?;
392 Ok(())
393 }
394}