1#![warn(missing_docs)]
20
21use anyhow::Result;
22use khora_core::platform::window::KhoraWindow;
23use khora_core::renderer::{RenderObject, RenderSettings, RenderSystem};
24use khora_core::telemetry::MonitoredResourceType;
25use khora_infra::platform::input::translate_winit_input;
26use khora_infra::platform::window::{WinitWindow, WinitWindowBuilder};
27use khora_infra::telemetry::memory_monitor::MemoryMonitor;
28use khora_infra::{GpuMonitor, WgpuRenderSystem};
29use khora_telemetry::TelemetryService;
30use std::sync::Arc;
31use std::time::Duration;
32use winit::application::ApplicationHandler;
33use winit::event::WindowEvent;
34use winit::event_loop::{ActiveEventLoop, EventLoop};
35use winit::window::WindowId;
36
37pub mod prelude {
39 pub use khora_core::asset::{AssetMetadata, AssetSource, AssetUUID};
40 pub use khora_core::renderer::{
41 BufferDescriptor, BufferId, BufferUsage, ColorTargetStateDescriptor, ColorWrites,
42 IndexFormat, MultisampleStateDescriptor, PipelineLayoutDescriptor, RenderObject,
43 RenderPipelineDescriptor, RenderPipelineId, SampleCount, ShaderModuleDescriptor,
44 ShaderModuleId, ShaderSourceData, ShaderStage, VertexAttributeDescriptor,
45 VertexBufferLayoutDescriptor, VertexFormat, VertexStepMode,
46 };
47 pub use khora_data::allocators::SaaTrackingAllocator;
48
49 pub mod shaders {
51 pub use khora_lanes::render_lane::shaders::*;
52 }
53}
54
55pub struct EngineContext {
57 pub graphics_device: Arc<dyn khora_core::renderer::GraphicsDevice>,
59}
60
61pub trait Application: Sized + 'static {
63 fn new(context: EngineContext) -> Self;
65
66 fn update(&mut self);
68
69 fn render(&mut self) -> Vec<RenderObject>;
71}
72
73struct EngineState<A: Application> {
76 app: Option<A>, window: Option<WinitWindow>,
78 renderer: Option<Box<dyn RenderSystem>>,
79 telemetry: Option<TelemetryService>,
80 render_settings: RenderSettings,
81}
82
83impl<A: Application> EngineState<A> {
84 fn log_telemetry_summary(&self) {
86 if let Some(telemetry) = &self.telemetry {
87 log::info!("--- Telemetry Summary ---");
88 let monitors = telemetry.monitor_registry().get_all_monitors();
89
90 if monitors.is_empty() {
91 log::info!(" No monitors registered.");
92 }
93
94 for monitor in monitors {
95 let report = monitor.get_usage_report();
96 match monitor.resource_type() {
97 MonitoredResourceType::SystemRam => {
98 let current_mb = report.current_bytes as f64 / (1024.0 * 1024.0);
99 let peak_mb = report.peak_bytes.unwrap_or(0) as f64 / (1024.0 * 1024.0);
100 log::info!(
101 " RAM Usage: {:.2} MB (Peak: {:.2} MB)",
102 current_mb,
103 peak_mb
104 );
105 }
106 MonitoredResourceType::Vram => {
107 let current_mb = report.current_bytes as f64 / (1024.0 * 1024.0);
108 let peak_mb = report.peak_bytes.unwrap_or(0) as f64 / (1024.0 * 1024.0);
109 log::info!(
110 " VRAM Usage: {:.2} MB (Peak: {:.2} MB)",
111 current_mb,
112 peak_mb
113 );
114 }
115 MonitoredResourceType::Gpu => {
116 if let Some(gpu_monitor) = monitor.as_any().downcast_ref::<GpuMonitor>() {
118 if let Some(gpu_report) = gpu_monitor.get_gpu_report() {
119 log::info!(
120 " GPU Time: {:.3} ms (Main Pass: {:.3} ms)",
121 gpu_report.frame_total_duration_us().unwrap_or(0) as f32
122 / 1000.0,
123 gpu_report.main_pass_duration_us().unwrap_or(0) as f32 / 1000.0
124 );
125 }
126 }
127 }
128 }
129 }
130 log::info!("-------------------------");
131 }
132 }
133}
134
135impl<A: Application> Drop for EngineState<A> {
139 fn drop(&mut self) {
140 log::info!("EngineState is being dropped. Performing controlled shutdown...");
141
142 if let Some(mut renderer) = self.renderer.take() {
143 renderer.shutdown();
144 }
145
146 log::info!("Engine systems shutdown complete.");
147 }
148}
149
150impl<A: Application> ApplicationHandler for EngineState<A> {
151 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
154 if self.window.is_some() {
155 return; }
157
158 log::info!("Application resumed. Initializing window and engine systems...");
159
160 let window = WinitWindowBuilder::new().build(event_loop).unwrap();
162
163 let mut renderer: Box<dyn RenderSystem> = Box::new(WgpuRenderSystem::new());
165 let renderer_monitors = renderer.init(&window).unwrap();
166
167 let telemetry = TelemetryService::new(Duration::from_secs(1));
169
170 log::info!("Registering default resource monitors...");
172
173 for monitor in renderer_monitors {
175 telemetry.monitor_registry().register(monitor);
176 }
177
178 let memory_monitor = Arc::new(MemoryMonitor::new("System_RAM".to_string()));
180 telemetry.monitor_registry().register(memory_monitor);
181
182 let context = EngineContext {
184 graphics_device: renderer.graphics_device(),
185 };
186 self.app = Some(A::new(context));
187
188 self.window = Some(window);
190 self.renderer = Some(renderer);
191 self.telemetry = Some(telemetry);
192 self.render_settings = RenderSettings::default();
193 }
194
195 fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
196 if let Some(app_window) = self.window.as_ref() {
197 use std::collections::hash_map::DefaultHasher;
198 use std::hash::{Hash, Hasher};
199
200 let mut hasher = DefaultHasher::new();
201 id.hash(&mut hasher);
202 let event_window_hash = hasher.finish();
203
204 if app_window.id() == event_window_hash {
205 match event {
206 WindowEvent::CloseRequested => {
207 log::info!("Shutdown requested, exiting event loop...");
208 event_loop.exit();
209 }
210 WindowEvent::Resized(size) => {
211 if let Some(renderer) = self.renderer.as_mut() {
212 log::info!("Window resized to: {}x{}", size.width, size.height);
213 renderer.resize(size.width, size.height);
214 }
215 }
216 WindowEvent::RedrawRequested => {
217 if let (Some(renderer), Some(telemetry)) =
218 (self.renderer.as_mut(), self.telemetry.as_mut())
219 {
220 let should_log_summary = telemetry.tick();
222
223 let app = self.app.as_mut().unwrap();
225
226 app.update();
227
228 let render_objects = app.render();
229
230 match renderer.render(
232 &render_objects,
233 &Default::default(),
234 &self.render_settings,
235 ) {
236 Ok(stats) => {
237 log::trace!("Frame {} rendered.", stats.frame_number);
238 }
239 Err(e) => log::error!("Rendering error: {}", e),
240 }
241
242 if should_log_summary {
243 self.log_telemetry_summary();
244 }
245 }
246 }
247 _ => {
248 if let Some(input_event) = translate_winit_input(&event) {
250 log::debug!("Input event: {:?}", input_event);
251 }
252 }
253 }
254 }
255 }
256 }
257
258 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
261 if let Some(window) = &self.window {
262 window.request_redraw();
263 }
264 }
265}
266
267pub struct Engine;
269
270impl Engine {
271 pub fn run<A: Application>() -> Result<()> {
277 log::info!("Khora Engine SDK: Starting...");
278 let event_loop = EventLoop::new()?;
279
280 let mut app_state = EngineState::<A> {
282 app: None,
283 window: None,
284 renderer: None,
285 telemetry: None,
286 render_settings: RenderSettings::default(),
287 };
288
289 event_loop.run_app(&mut app_state)?;
290
291 Ok(())
292 }
293}