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}
48
49pub struct EngineContext {
51 pub graphics_device: Arc<dyn khora_core::renderer::GraphicsDevice>,
53}
54
55pub trait Application: Sized + 'static {
57 fn new(context: EngineContext) -> Self;
59
60 fn update(&mut self);
62
63 fn render(&mut self) -> Vec<RenderObject>;
65}
66
67struct EngineState<A: Application> {
70 app: Option<A>, window: Option<WinitWindow>,
72 renderer: Option<Box<dyn RenderSystem>>,
73 telemetry: Option<TelemetryService>,
74 render_settings: RenderSettings,
75}
76
77impl<A: Application> EngineState<A> {
78 fn log_telemetry_summary(&self) {
80 if let Some(telemetry) = &self.telemetry {
81 log::info!("--- Telemetry Summary ---");
82 let monitors = telemetry.monitor_registry().get_all_monitors();
83
84 if monitors.is_empty() {
85 log::info!(" No monitors registered.");
86 }
87
88 for monitor in monitors {
89 let report = monitor.get_usage_report();
90 match monitor.resource_type() {
91 MonitoredResourceType::SystemRam => {
92 let current_mb = report.current_bytes as f64 / (1024.0 * 1024.0);
93 let peak_mb = report.peak_bytes.unwrap_or(0) as f64 / (1024.0 * 1024.0);
94 log::info!(
95 " RAM Usage: {:.2} MB (Peak: {:.2} MB)",
96 current_mb,
97 peak_mb
98 );
99 }
100 MonitoredResourceType::Vram => {
101 let current_mb = report.current_bytes as f64 / (1024.0 * 1024.0);
102 let peak_mb = report.peak_bytes.unwrap_or(0) as f64 / (1024.0 * 1024.0);
103 log::info!(
104 " VRAM Usage: {:.2} MB (Peak: {:.2} MB)",
105 current_mb,
106 peak_mb
107 );
108 }
109 MonitoredResourceType::Gpu => {
110 if let Some(gpu_monitor) = monitor.as_any().downcast_ref::<GpuMonitor>() {
112 if let Some(gpu_report) = gpu_monitor.get_gpu_report() {
113 log::info!(
114 " GPU Time: {:.3} ms (Main Pass: {:.3} ms)",
115 gpu_report.frame_total_duration_us().unwrap_or(0) as f32
116 / 1000.0,
117 gpu_report.main_pass_duration_us().unwrap_or(0) as f32 / 1000.0
118 );
119 }
120 }
121 }
122 }
123 }
124 log::info!("-------------------------");
125 }
126 }
127}
128
129impl<A: Application> Drop for EngineState<A> {
133 fn drop(&mut self) {
134 log::info!("EngineState is being dropped. Performing controlled shutdown...");
135
136 if let Some(mut renderer) = self.renderer.take() {
137 renderer.shutdown();
138 }
139
140 log::info!("Engine systems shutdown complete.");
141 }
142}
143
144impl<A: Application> ApplicationHandler for EngineState<A> {
145 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
148 if self.window.is_some() {
149 return; }
151
152 log::info!("Application resumed. Initializing window and engine systems...");
153
154 let window = WinitWindowBuilder::new().build(event_loop).unwrap();
156
157 let mut renderer: Box<dyn RenderSystem> = Box::new(WgpuRenderSystem::new());
159 let renderer_monitors = renderer.init(&window).unwrap();
160
161 let telemetry = TelemetryService::new(Duration::from_secs(1));
163
164 log::info!("Registering default resource monitors...");
166
167 for monitor in renderer_monitors {
169 telemetry.monitor_registry().register(monitor);
170 }
171
172 let memory_monitor = Arc::new(MemoryMonitor::new("System_RAM".to_string()));
174 telemetry.monitor_registry().register(memory_monitor);
175
176 let context = EngineContext {
178 graphics_device: renderer.graphics_device(),
179 };
180 self.app = Some(A::new(context));
181
182 self.window = Some(window);
184 self.renderer = Some(renderer);
185 self.telemetry = Some(telemetry);
186 self.render_settings = RenderSettings::default();
187 }
188
189 fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
190 if let Some(app_window) = self.window.as_ref() {
191 use std::collections::hash_map::DefaultHasher;
192 use std::hash::{Hash, Hasher};
193
194 let mut hasher = DefaultHasher::new();
195 id.hash(&mut hasher);
196 let event_window_hash = hasher.finish();
197
198 if app_window.id() == event_window_hash {
199 match event {
200 WindowEvent::CloseRequested => {
201 log::info!("Shutdown requested, exiting event loop...");
202 event_loop.exit();
203 }
204 WindowEvent::Resized(size) => {
205 if let Some(renderer) = self.renderer.as_mut() {
206 log::info!("Window resized to: {}x{}", size.width, size.height);
207 renderer.resize(size.width, size.height);
208 }
209 }
210 WindowEvent::RedrawRequested => {
211 if let (Some(renderer), Some(telemetry)) =
212 (self.renderer.as_mut(), self.telemetry.as_mut())
213 {
214 let should_log_summary = telemetry.tick();
216
217 let app = self.app.as_mut().unwrap();
219
220 app.update();
221
222 let render_objects = app.render();
223
224 match renderer.render(
226 &render_objects,
227 &Default::default(),
228 &self.render_settings,
229 ) {
230 Ok(stats) => {
231 log::trace!("Frame {} rendered.", stats.frame_number);
232 }
233 Err(e) => log::error!("Rendering error: {}", e),
234 }
235
236 if should_log_summary {
237 self.log_telemetry_summary();
238 }
239 }
240 }
241 _ => {
242 if let Some(input_event) = translate_winit_input(&event) {
244 log::debug!("Input event: {:?}", input_event);
245 }
246 }
247 }
248 }
249 }
250 }
251
252 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
255 if let Some(window) = &self.window {
256 window.request_redraw();
257 }
258 }
259}
260
261pub struct Engine;
263
264impl Engine {
265 pub fn run<A: Application>() -> Result<()> {
271 log::info!("Khora Engine SDK: Starting...");
272 let event_loop = EventLoop::new()?;
273
274 let mut app_state = EngineState::<A> {
276 app: None,
277 window: None,
278 renderer: None,
279 telemetry: None,
280 render_settings: RenderSettings::default(),
281 };
282
283 event_loop.run_app(&mut app_state)?;
284
285 Ok(())
286 }
287}