1pub mod dev_console;
2
3use gizmo_core::system::Schedule;
4use gizmo_core::world::World;
5use gizmo_editor::gui::EditorContext;
6use gizmo_renderer::renderer::Renderer;
7use gizmo_renderer::RenderContext;
8use std::sync::atomic::{AtomicPtr, Ordering};
9use std::sync::Arc;
10use winit::{
11 event::{Event, WindowEvent},
12 event_loop::{ControlFlow, EventLoop},
13 window::WindowBuilder,
14};
15
16static WORLD_PTR: AtomicPtr<gizmo_core::world::World> = AtomicPtr::new(std::ptr::null_mut());
17
18pub fn setup_panic_hook() {
19 #[cfg(target_arch = "wasm32")]
20 {
21 console_error_panic_hook::set_once();
22 }
23 #[cfg(not(target_arch = "wasm32"))]
24 {
25 std::panic::set_hook(Box::new(|panic_info| {
26 let payload = panic_info.payload();
27 let message = if let Some(s) = payload.downcast_ref::<&str>() {
28 *s
29 } else if let Some(s) = payload.downcast_ref::<String>() {
30 s.as_str()
31 } else {
32 "Bilinmeyen hata"
33 };
34
35 let location = if let Some(loc) = panic_info.location() {
36 format!("{}:{}", loc.file(), loc.line())
37 } else {
38 "Bilinmeyen konum".to_string()
39 };
40
41 let error_msg = format!("Gizmo Engine Coktu!\n\nKonum: {}\nHata: {}\n\nOlay Yeri Inceleme Raporu 'gizmo_crash_report.json' olarak kaydedildi.", location, message);
42
43 tracing::info!("{}", error_msg);
44
45 let backtrace = backtrace::Backtrace::new();
46 tracing::info!("--- BACKTRACE ---\n{:?}", backtrace);
47
48 unsafe {
49 let ptr = WORLD_PTR.load(Ordering::Acquire);
50 if !ptr.is_null() {
51 let world = &*ptr;
52 let registry = gizmo_scene::registry::SceneRegistry::default();
53 let _ = gizmo_scene::scene::SceneData::save(
54 world,
55 "gizmo_crash_report.json",
56 ®istry,
57 );
58 }
59 }
60
61 rfd::MessageDialog::new()
62 .set_title("Gizmo Engine Fatal Error")
63 .set_description(&error_msg)
64 .set_level(rfd::MessageLevel::Error)
65 .show();
66 }));
67 }
68}
69
70pub trait Plugin<State: 'static = ()> {
71 fn build(&self, app: &mut App<State>);
72}
73
74pub struct AssetPlugin;
75
76impl<State: 'static> Plugin<State> for AssetPlugin {
77 fn build(&self, app: &mut App<State>) {
78 app.world
79 .insert_resource(gizmo_core::asset::Assets::<gizmo_renderer::components::Mesh>::new());
80 app.world.insert_resource(gizmo_core::asset::Assets::<
81 gizmo_renderer::components::Material,
82 >::new());
83 }
84}
85
86pub struct App<State: 'static = ()> {
87 pub world: World,
88 pub schedule: Schedule,
89 window_title: String,
90 window_size: (u32, u32),
91
92 setup_fn: Option<Box<dyn FnOnce(&mut World, &Renderer) -> State + 'static>>,
93 update_fn: Option<Box<dyn FnMut(&mut World, &mut State, f32, &gizmo_core::input::Input)>>, render_fn: Option<
95 Box<
96 dyn FnMut(
97 &mut World,
98 &State,
99 &mut wgpu::CommandEncoder,
100 &wgpu::TextureView,
101 &mut Renderer,
102 f32,
103 ),
104 >,
105 >, simple_render_fn: Option<Box<dyn for<'a> FnMut(&mut World, &State, &mut RenderContext<'a>)>>,
107 input_fn: Option<Box<dyn FnMut(&mut World, &mut State, &winit::event::Event<()>) -> bool>>, ui_fn: Option<Box<dyn FnMut(&mut World, &mut State, &egui::Context)>>, pub input: gizmo_core::input::Input,
110 #[allow(clippy::type_complexity)]
111 event_updaters: Vec<Box<dyn FnMut(&mut World)>>,
112 initial_scene: Option<String>,
113 window_icon: Option<&'static [u8]>,
114 pub record_mode: bool,
115 pub playback_file: Option<String>,
116 record_data: Option<gizmo_core::input::PlaybackData>,
117 playback_data: Option<gizmo_core::input::PlaybackData>,
118 playback_frame_index: usize,
119 runner: Option<Box<dyn FnOnce(App<State>)>>,
120 embedded_assets: std::collections::HashMap<String, std::borrow::Cow<'static, [u8]>>,
121}
122
123impl<State: 'static> App<State> {
124 pub fn new(title: &str, width: u32, height: u32) -> Self {
125 let mut app = Self {
126 world: World::new(),
127 schedule: Schedule::new(),
128 window_title: title.to_string(),
129 window_size: (width, height),
130 setup_fn: None,
131 update_fn: None,
132 render_fn: None,
133 simple_render_fn: None,
134 input_fn: None,
135 ui_fn: None,
136 input: gizmo_core::input::Input::new(),
137 event_updaters: Vec::new(),
138 initial_scene: None,
139 window_icon: None,
140 record_mode: false,
141 playback_file: None,
142 record_data: None,
143 playback_data: None,
144 playback_frame_index: 0,
145 runner: None,
146 embedded_assets: std::collections::HashMap::new(),
147 };
148 app = app.add_plugin(AssetPlugin);
149 app
150 }
151
152 pub fn set_runner<F>(mut self, f: F) -> Self
153 where
154 F: FnOnce(App<State>) + 'static,
155 {
156 self.runner = Some(Box::new(f));
157 self
158 }
159
160 pub fn set_runner_mut<F>(&mut self, f: F)
161 where
162 F: FnOnce(App<State>) + 'static,
163 {
164 self.runner = Some(Box::new(f));
165 }
166
167 pub fn start_recording(mut self) -> Self {
168 self.record_mode = true;
169 self.record_data = Some(gizmo_core::input::PlaybackData { frames: Vec::new() });
170 self
171 }
172
173 pub fn start_playback(mut self, path: &str) -> Self {
174 self.playback_file = Some(path.to_string());
175 self
176 }
177
178 pub fn add_event<T: 'static + Send + Sync>(mut self) -> Self {
181 self.world
182 .insert_resource(gizmo_core::event::Events::<T>::new());
183 self.event_updaters.push(Box::new(|world| {
184 if let Some(mut events) = world.get_resource_mut::<gizmo_core::event::Events<T>>() {
185 events.update();
186 }
187 }));
188 self
189 }
190
191 pub fn with_icon(mut self, icon_bytes: &'static [u8]) -> Self {
192 self.window_icon = Some(icon_bytes);
193 self
194 }
195
196 pub fn add_plugin<P: Plugin<State>>(mut self, plugin: P) -> Self {
197 plugin.build(&mut self);
198 self
199 }
200
201 pub fn add_embedded_asset(mut self, path: &str, data: std::borrow::Cow<'static, [u8]>) -> Self {
202 self.embedded_assets.insert(path.to_string(), data);
203 self
204 }
205
206 pub fn set_setup<F>(mut self, f: F) -> Self
207 where
208 F: FnOnce(&mut World, &Renderer) -> State + 'static,
209 {
210 self.setup_fn = Some(Box::new(f));
211 self
212 }
213
214 pub fn set_update<F>(mut self, f: F) -> Self
215 where
216 F: FnMut(&mut World, &mut State, f32, &gizmo_core::input::Input) + 'static,
217 {
218 self.update_fn = Some(Box::new(f));
219 self
220 }
221
222 pub fn set_render<F>(mut self, f: F) -> Self
223 where
224 F: FnMut(
225 &mut World,
226 &State,
227 &mut wgpu::CommandEncoder,
228 &wgpu::TextureView,
229 &mut Renderer,
230 f32,
231 ) + 'static,
232 {
233 self.render_fn = Some(Box::new(f));
234 self
235 }
236
237 pub fn set_simple_render<F>(mut self, f: F) -> Self
239 where
240 F: for<'a> FnMut(&mut World, &State, &mut RenderContext<'a>) + 'static,
241 {
242 self.simple_render_fn = Some(Box::new(f));
243 self
244 }
245
246 pub fn set_input<F>(mut self, f: F) -> Self
247 where
248 F: FnMut(&mut World, &mut State, &Event<()>) -> bool + 'static,
249 {
250 self.input_fn = Some(Box::new(f));
251 self
252 }
253
254 pub fn set_ui<F>(mut self, f: F) -> Self
255 where
256 F: FnMut(&mut World, &mut State, &egui::Context) + 'static,
257 {
258 self.ui_fn = Some(Box::new(f));
259 self
260 }
261
262 pub fn add_system<Params, S: gizmo_core::system::IntoSystemConfig<Params>>(
263 mut self,
264 system: S,
265 ) -> Self {
266 self.schedule.add_di_system(system);
267 self
268 }
269
270 pub fn load_scene(mut self, path: &str) -> Self {
271 self.initial_scene = Some(path.to_string());
272 self
273 }
274
275 pub fn run(mut self) {
276 if let Some(runner) = self.runner.take() {
277 runner(self);
278 return;
279 }
280 self.run_default();
281 }
282
283 fn run_default(mut self) {
284 setup_panic_hook();
285 WORLD_PTR.store(&mut self.world as *mut _, Ordering::Release);
286
287 if let Some(ref path) = self.playback_file {
288 match gizmo_core::input::PlaybackData::load(path) {
289 Ok(data) => {
290 self.playback_data = Some(data);
291 tracing::info!("Playback loaded from: {}", path);
292 }
293 Err(e) => {
294 tracing::error!("Failed to load playback data: {}", e);
295 }
296 }
297 }
298
299 let event_loop = EventLoop::new().expect("Event Loop başlatılamadı");
300 let mut builder = WindowBuilder::new()
301 .with_title(&self.window_title)
302 .with_inner_size(winit::dpi::LogicalSize::new(
303 self.window_size.0,
304 self.window_size.1,
305 ));
306
307 #[cfg(target_arch = "wasm32")]
308 {
309 use wasm_bindgen::JsCast;
310 use winit::platform::web::WindowBuilderExtWebSys;
311 let canvas = web_sys::window()
313 .and_then(|win| win.document())
314 .and_then(|doc| {
315 let canvas = doc.create_element("canvas").ok()?;
316 let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into().ok()?;
317 canvas.set_width(1280);
318 canvas.set_height(720);
319 canvas.style().set_property("width", "100%").ok()?;
320 canvas.style().set_property("height", "100%").ok()?;
321 doc.body()?.append_child(&canvas).ok()?;
322 Some(canvas)
323 });
324 if let Some(canvas) = canvas {
325 builder = builder.with_canvas(Some(canvas));
326 } else {
327 builder = builder.with_append(true);
328 }
329 }
330
331 if let Some(icon_bytes) = self.window_icon {
332 if let Ok(image) = image::load_from_memory(icon_bytes) {
333 let rgba = image.into_rgba8();
334 let (width, height) = rgba.dimensions();
335 if let Ok(icon) = winit::window::Icon::from_rgba(rgba.into_raw(), width, height) {
336 builder = builder.with_window_icon(Some(icon));
337 }
338 }
339 }
340
341 let window = Arc::new(builder.build(&event_loop).expect("Pencere oluşturulamadı!"));
342
343 #[cfg(not(target_arch = "wasm32"))]
344 {
345 pollster::block_on(self.run_internal(event_loop, window));
346 }
347 #[cfg(target_arch = "wasm32")]
348 {
349 wasm_bindgen_futures::spawn_local(self.run_internal(event_loop, window));
350 }
351 }
352
353 async fn run_internal(mut self, event_loop: EventLoop<()>, window: Arc<winit::window::Window>) {
354 self.world
356 .insert_resource(gizmo_core::cvar::CVarRegistry::new());
357 self.world.insert_resource(gizmo_core::window::WindowInfo {
359 width: self.window_size.0 as f32,
360 height: self.window_size.1 as f32,
361 });
362
363 let renderer = Renderer::new(window.clone()).await;
365 renderer.asset_manager.write().unwrap().embedded_assets =
366 std::mem::take(&mut self.embedded_assets);
367 self.world.insert_resource(renderer);
368
369 let mut state = if let Some(setup) = self.setup_fn.take() {
370 let r = self.world.remove_resource::<Renderer>().unwrap();
371 let state = setup(&mut self.world, &r);
372 self.world.insert_resource(r);
373 state
374 } else {
375 panic!("setup() fonksiyonu atanmadi! Lütfen set_setup çağırın veya State yapılandırmanızı kontrol edin.");
376 };
377
378 if let Some(scene_path) = self.initial_scene.take() {
379 if let Some(mut asset_manager) = self
380 .world
381 .remove_resource::<gizmo_renderer::asset::AssetManager>()
382 {
383 let dummy_rgba = [255, 255, 255, 255];
384 let r = self.world.remove_resource::<Renderer>().unwrap();
385 let dummy_bg = r.create_texture(&dummy_rgba, 1, 1);
386
387 {
388 gizmo_scene::scene::SceneData::load_into(
389 &scene_path,
390 &mut self.world,
391 &r.device,
392 &r.queue,
393 &r.scene.texture_bind_group_layout,
394 &mut asset_manager,
395 Arc::new(dummy_bg),
396 &gizmo_scene::registry::SceneRegistry::default(),
397 );
398 }
399
400 self.world.insert_resource(r);
401 self.world.insert_resource(asset_manager);
402 } else {
403 tracing::error!("[App::run] AssetManager bulunamadı, sahne yüklenemiyor!");
404 }
405 }
406
407 let mut editor = {
408 let r = self.world.get_resource::<Renderer>().unwrap();
409 EditorContext::new(&r.device, r.config.format, &window, 1)
410 };
411
412 #[cfg(not(target_arch = "wasm32"))]
413 let mut last_frame_time = std::time::Instant::now();
414 #[cfg(target_arch = "wasm32")]
415 let mut last_frame_time = web_time::Instant::now();
416 let mut light_time = 0.0;
417
418 event_loop
419 .run(move |event, current_window| {
420 current_window.set_control_flow(ControlFlow::Poll);
421
422 let mut consumes_input = false;
423
424 if let Event::WindowEvent {
426 ref event,
427 window_id,
428 } = event
429 {
430 if window_id == window.id() {
431 consumes_input = editor.handle_event(&window, event);
432 }
433 }
434
435 if !consumes_input {
437 if let Some(input_hk) = self.input_fn.as_mut() {
438 let _ = input_hk(&mut self.world, &mut state, &event);
439 }
440 }
441
442 match event {
443 Event::WindowEvent {
444 ref event,
445 window_id,
446 } if window_id == window.id() => {
447 match event {
448 WindowEvent::CloseRequested => {
449 if let Some(record) = &self.record_data {
450 let _ = record.save("gizmo_record.ron");
451 tracing::info!(
452 "Kayit basariyla 'gizmo_record.ron' dosyasina kaydedildi."
453 );
454 }
455 #[cfg(not(target_arch = "wasm32"))]
457 std::process::exit(0);
458 #[cfg(target_arch = "wasm32")]
459 current_window.exit();
460 }
461 WindowEvent::Resized(physical_size) => {
462 {
463 let mut r = self.world.get_resource_mut::<Renderer>().unwrap();
464 r.resize(*physical_size);
465 }
466 let mut win_info = self
467 .world
468 .get_resource_mut_or_default::<gizmo_core::window::WindowInfo>(
469 );
470 win_info.width = physical_size.width as f32;
471 win_info.height = physical_size.height as f32;
472 }
473 WindowEvent::KeyboardInput {
474 event: kb_event, ..
475 } => {
476 let mut codes_to_press = Vec::new();
477 if let winit::keyboard::PhysicalKey::Code(keycode) =
479 kb_event.physical_key
480 {
481 codes_to_press.push(keycode as u32);
482 }
483 if codes_to_press.is_empty() {
485 if let winit::keyboard::Key::Character(c) =
486 kb_event.logical_key.as_ref()
487 {
488 match c.to_lowercase().as_str() {
489 "w" => codes_to_press
490 .push(winit::keyboard::KeyCode::KeyW as u32),
491 "a" => codes_to_press
492 .push(winit::keyboard::KeyCode::KeyA as u32),
493 "s" => codes_to_press
494 .push(winit::keyboard::KeyCode::KeyS as u32),
495 "d" => codes_to_press
496 .push(winit::keyboard::KeyCode::KeyD as u32),
497 _ => {}
498 }
499 } else if let winit::keyboard::Key::Named(named) =
500 kb_event.logical_key
501 {
502 match named {
503 winit::keyboard::NamedKey::ArrowUp => codes_to_press
504 .push(winit::keyboard::KeyCode::ArrowUp as u32),
505 winit::keyboard::NamedKey::ArrowDown => codes_to_press
506 .push(winit::keyboard::KeyCode::ArrowDown as u32),
507 winit::keyboard::NamedKey::ArrowLeft => codes_to_press
508 .push(winit::keyboard::KeyCode::ArrowLeft as u32),
509 winit::keyboard::NamedKey::ArrowRight => codes_to_press
510 .push(winit::keyboard::KeyCode::ArrowRight as u32),
511 winit::keyboard::NamedKey::Space => codes_to_press
512 .push(winit::keyboard::KeyCode::Space as u32),
513 winit::keyboard::NamedKey::Escape => codes_to_press
514 .push(winit::keyboard::KeyCode::Escape as u32),
515 _ => {}
516 }
517 }
518 } for code in codes_to_press {
521 if kb_event.state == winit::event::ElementState::Pressed {
522 self.input.on_key_pressed(code);
523 } else {
524 self.input.on_key_released(code);
525 }
526 }
527 }
528 WindowEvent::MouseInput {
529 state: m_state,
530 button,
531 ..
532 } => {
533 let btn_code = match button {
534 winit::event::MouseButton::Left => {
535 gizmo_core::input::mouse::LEFT
536 }
537 winit::event::MouseButton::Right => {
538 gizmo_core::input::mouse::RIGHT
539 }
540 winit::event::MouseButton::Middle => {
541 gizmo_core::input::mouse::MIDDLE
542 }
543 _ => u32::MAX,
544 };
545 if btn_code != u32::MAX {
546 if *m_state == winit::event::ElementState::Pressed {
547 self.input.on_mouse_button_pressed(btn_code);
548 } else {
549 self.input.on_mouse_button_released(btn_code);
550 }
551 }
552 }
553 WindowEvent::CursorMoved { position, .. } => {
554 self.input
555 .on_mouse_moved(position.x as f32, position.y as f32);
556 }
557 _ => {}
558 }
559 if let WindowEvent::RedrawRequested = event {
560 #[cfg(not(target_arch = "wasm32"))]
561 let now = std::time::Instant::now();
562 #[cfg(target_arch = "wasm32")]
563 let now = web_time::Instant::now();
564 let mut dt = now.duration_since(last_frame_time).as_secs_f32();
565 dt = dt.min(0.05); last_frame_time = now;
567
568 if let Some(playback) = &self.playback_data {
570 if self.playback_frame_index < playback.frames.len() {
571 let frame = &playback.frames[self.playback_frame_index];
572 dt = frame.dt;
573 self.input = frame.input.clone();
574 self.playback_frame_index += 1;
575 } else {
576 tracing::info!("Playback bitti. Uygulama kapaniyor...");
577 current_window.exit();
578 }
579 } else if self.record_mode {
580 if let Some(record) = &mut self.record_data {
581 record.frames.push(gizmo_core::input::FrameRecord {
582 dt,
583 input: self.input.clone(),
584 });
585 }
586 }
587
588 light_time += dt;
589
590 let full_output = editor.run(&window, |ctx| {
592 if let Some(ui_hk) = self.ui_fn.as_mut() {
593 ui_hk(&mut self.world, &mut state, ctx);
594 }
595
596 dev_console::ui_dev_console(&mut self.world, ctx, &self.input);
598 });
599
600 if self
602 .world
603 .get_resource::<gizmo_editor::EditorState>()
604 .is_some()
605 {
606 let mut ed_state_ref = self
607 .world
608 .get_resource_mut::<gizmo_editor::EditorState>()
609 .unwrap();
610 let (rw, rh) = {
611 let r = self.world.get_resource::<Renderer>().unwrap();
612 (r.size.width, r.size.height)
613 };
614 let scene_w = ed_state_ref
615 .scene_view_size
616 .map(|s| s.x as u32)
617 .unwrap_or(rw);
618 let scene_h = ed_state_ref
619 .scene_view_size
620 .map(|s| s.y as u32)
621 .unwrap_or(rh);
622 let game_w = ed_state_ref
623 .game_view_size
624 .map(|s| s.x as u32)
625 .unwrap_or(rw);
626 let game_h = ed_state_ref
627 .game_view_size
628 .map(|s| s.y as u32)
629 .unwrap_or(rh);
630
631 let mut new_scene_target = None;
632 let mut new_game_target = None;
633
634 let mut needs_recreate_scene = false;
636 if let Some(target) = self
637 .world
638 .get_resource::<gizmo_renderer::components::EditorRenderTarget>(
639 ) {
640 if target.0.width != scene_w || target.0.height != scene_h {
641 needs_recreate_scene = true;
642 }
643 } else {
644 needs_recreate_scene = true;
645 }
646
647 if needs_recreate_scene && scene_w > 0 && scene_h > 0 {
648 if let Some(old_id) = ed_state_ref.scene_texture_id {
649 editor.renderer.free_texture(&old_id);
650 }
651 let tex_id;
652 {
653 let r = self.world.get_resource::<Renderer>().unwrap();
654 let texture =
655 r.device.create_texture(&wgpu::TextureDescriptor {
656 label: Some("Editor RTT"),
657 size: wgpu::Extent3d {
658 width: scene_w,
659 height: scene_h,
660 depth_or_array_layers: 1,
661 },
662 mip_level_count: 1,
663 sample_count: 1,
664 dimension: wgpu::TextureDimension::D2,
665 format: r.config.format,
666 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
667 | wgpu::TextureUsages::TEXTURE_BINDING,
668 view_formats: &[],
669 });
670 let view = texture
671 .create_view(&wgpu::TextureViewDescriptor::default());
672 tex_id = Some(editor.renderer.register_native_texture(
673 &r.device,
674 &view,
675 wgpu::FilterMode::Linear,
676 ));
677 new_scene_target =
678 Some((std::sync::Arc::new(view), scene_w, scene_h));
679 }
680 ed_state_ref.scene_texture_id = tex_id;
681 }
682
683 let mut needs_recreate_game = false;
685 if let Some(target) = self
686 .world
687 .get_resource::<gizmo_renderer::components::GameRenderTarget>(
688 ) {
689 if target.0.width != game_w || target.0.height != game_h {
690 needs_recreate_game = true;
691 }
692 } else {
693 needs_recreate_game = true;
694 }
695
696 if needs_recreate_game && game_w > 0 && game_h > 0 {
697 if let Some(old_id) = ed_state_ref.game_texture_id {
698 editor.renderer.free_texture(&old_id);
699 }
700 let tex_id;
701 {
702 let r = self.world.get_resource::<Renderer>().unwrap();
703 let texture =
704 r.device.create_texture(&wgpu::TextureDescriptor {
705 label: Some("Game RTT"),
706 size: wgpu::Extent3d {
707 width: game_w,
708 height: game_h,
709 depth_or_array_layers: 1,
710 },
711 mip_level_count: 1,
712 sample_count: 1,
713 dimension: wgpu::TextureDimension::D2,
714 format: r.config.format,
715 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
716 | wgpu::TextureUsages::TEXTURE_BINDING,
717 view_formats: &[],
718 });
719 let view = texture
720 .create_view(&wgpu::TextureViewDescriptor::default());
721 tex_id = Some(editor.renderer.register_native_texture(
722 &r.device,
723 &view,
724 wgpu::FilterMode::Linear,
725 ));
726 new_game_target =
727 Some((std::sync::Arc::new(view), game_w, game_h));
728 }
729 ed_state_ref.game_texture_id = tex_id;
730 }
731
732 drop(ed_state_ref);
733
734 if let Some((view, w, h)) = new_scene_target {
735 self.world.insert_resource(
736 gizmo_renderer::components::EditorRenderTarget(
737 gizmo_renderer::components::RenderTarget {
738 view,
739 width: w,
740 height: h,
741 },
742 ),
743 );
744 }
745 if let Some((view, w, h)) = new_game_target {
746 self.world.insert_resource(
747 gizmo_renderer::components::GameRenderTarget(
748 gizmo_renderer::components::RenderTarget {
749 view,
750 width: w,
751 height: h,
752 },
753 ),
754 );
755 }
756 }
757
758 let maybe_dialog_result = {
761 let mut st =
762 self.world.get_resource_mut::<gizmo_editor::EditorState>();
763 if let Some(ref mut ed) = st {
764 if let Some(rx_mutex) = ed.pending_dialog_rx.take() {
765 match rx_mutex.into_inner() {
766 Ok(rx) => match rx.try_recv() {
767 Ok((is_save, Some(path))) => {
768 Some((is_save, Some(path)))
769 }
770 Ok((_, None)) => None, Err(std::sync::mpsc::TryRecvError::Empty) => {
772 ed.pending_dialog_rx =
774 Some(std::sync::Mutex::new(rx));
775 None
776 }
777 Err(_) => None,
778 },
779 Err(_) => None,
780 }
781 } else {
782 None
783 }
784 } else {
785 None
786 }
787 };
788 if let Some((is_save, Some(path))) = maybe_dialog_result {
789 if let Some(mut ed) =
790 self.world.get_resource_mut::<gizmo_editor::EditorState>()
791 {
792 ed.scene_path = path.clone();
793 if is_save {
794 ed.scene.save_request = Some(path);
795 } else {
796 ed.scene.load_request = Some(path);
797 }
798 }
799 }
800
801 let (save_req, load_req, clear_req) = {
803 if let Some(mut ed) =
804 self.world.get_resource_mut::<gizmo_editor::EditorState>()
805 {
806 (
807 ed.scene.save_request.take(),
808 ed.scene.load_request.take(),
809 std::mem::replace(&mut ed.scene.clear_request, false),
810 )
811 } else {
812 (None, None, false)
813 }
814 };
815
816 if let Some(ref path) = save_req {
818 let registry = gizmo_scene::registry::SceneRegistry::default();
819 match gizmo_scene::scene::SceneData::save(
820 &self.world,
821 path,
822 ®istry,
823 ) {
824 Ok(()) => {
825 if let Some(mut ed) = self
826 .world
827 .get_resource_mut::<gizmo_editor::EditorState>()
828 {
829 ed.has_unsaved_changes = false;
830 ed.status_message = format!("Kaydedildi: {}", path);
831 }
832 }
833 Err(e) => tracing::error!("[App] Sahne kayıt hatası: {}", e),
834 }
835 }
836
837 if clear_req || load_req.is_some() {
839 let editor_entities: std::collections::HashSet<u32> = {
840 let names = self.world.borrow::<gizmo_core::EntityName>();
841 names
842 .iter()
843 .filter_map(|(id, _)| {
844 names.get(id).and_then(|n| {
845 if n.0.starts_with("Editor ")
846 || n.0 == "Highlight Box"
847 {
848 Some(id)
849 } else {
850 None
851 }
852 })
853 })
854 .collect()
855 };
856 let to_despawn: Vec<_> = self
857 .world
858 .iter_alive_entities()
859 .into_iter()
860 .filter(|e| !editor_entities.contains(&e.id()))
861 .collect();
862 for e in to_despawn {
863 self.world.despawn(e);
864 }
865 }
866 if let Some(ref path) = load_req {
867 if let Some(mut asset_manager) =
868 self.world
869 .remove_resource::<gizmo_renderer::asset::AssetManager>()
870 {
871 let r = self.world.remove_resource::<Renderer>().unwrap();
872 let dummy_rgba = [255u8, 255, 255, 255];
873 let dummy_bg = r.create_texture(&dummy_rgba, 1, 1);
874 let registry = gizmo_scene::registry::SceneRegistry::default();
875 let ok = gizmo_scene::scene::SceneData::load_into(
876 path,
877 &mut self.world,
878 &r.device,
879 &r.queue,
880 &r.scene.texture_bind_group_layout,
881 &mut asset_manager,
882 Arc::new(dummy_bg),
883 ®istry,
884 );
885 self.world.insert_resource(r);
886 self.world.insert_resource(asset_manager);
887 if let Some(mut ed) =
888 self.world.get_resource_mut::<gizmo_editor::EditorState>()
889 {
890 ed.status_message = if ok {
891 format!("Yüklendi: {}", path)
892 } else {
893 format!("Sahne yüklenemedi: {}", path)
894 };
895 ed.has_unsaved_changes = false;
896 }
897 }
898 }
899
900 self.world.insert_resource(self.input.clone());
902 {
903 let has_time = self
904 .world
905 .get_resource::<gizmo_core::time::Time>()
906 .is_some();
907 if has_time {
908 let mut time = self
909 .world
910 .get_resource_mut::<gizmo_core::time::Time>()
911 .unwrap();
912 time.update(dt);
913 } else {
914 let mut time = gizmo_core::time::Time::new();
915 time.update(dt);
916 self.world.insert_resource(time);
917 }
918 }
919
920 if self
923 .world
924 .get_resource::<gizmo_core::time::PhysicsTime>()
925 .is_none()
926 {
927 self.world
928 .insert_resource(gizmo_core::time::PhysicsTime::default());
929 }
930 {
931 let mut phys_time = self
932 .world
933 .get_resource_mut::<gizmo_core::time::PhysicsTime>()
934 .unwrap();
935 phys_time.accumulate(dt);
936 }
937
938 loop {
940 let should = self
941 .world
942 .get_resource::<gizmo_core::time::PhysicsTime>()
943 .map(|pt| pt.should_step())
944 .unwrap_or(false);
945 if !should {
946 break;
947 }
948
949 let fixed_dt = self
950 .world
951 .get_resource::<gizmo_core::time::PhysicsTime>()
952 .map(|pt| pt.fixed_dt())
953 .unwrap_or(1.0 / 60.0);
954
955 self.schedule.run(&mut self.world, fixed_dt);
957
958 let mut phys_time = self
959 .world
960 .get_resource_mut::<gizmo_core::time::PhysicsTime>()
961 .unwrap();
962 phys_time.consume_step();
963 }
964
965 {
967 let mut phys_time = self
968 .world
969 .get_resource_mut::<gizmo_core::time::PhysicsTime>()
970 .unwrap();
971 phys_time.compute_alpha();
972 }
973
974 if let Some(update_hk) = self.update_fn.as_mut() {
976 update_hk(&mut self.world, &mut state, dt, &self.input);
977 }
978
979 self.world.apply_commands();
981
982 if let Some(physics_world) =
984 self.world
985 .get_resource::<gizmo_physics::world::PhysicsWorld>()
986 {
987 if !physics_world.fracture_events.is_empty() {
988 let renderer = self.world.get_resource::<Renderer>().unwrap();
989 if let Some(gpu_particles) = &renderer.gpu_particles {
990 for event in &physics_world.fracture_events {
991 let center = [
992 event.impact_point.x,
993 event.impact_point.y,
994 event.impact_point.z,
995 ];
996 let dust_color = [0.6, 0.55, 0.5, 0.8]; let force =
998 (event.impact_force * 0.01).clamp(2.0, 15.0);
999 let particle_count = (event.impact_force * 0.1)
1000 .clamp(50.0, 500.0)
1001 as u32;
1002 gpu_particles.spawn_explosion(
1003 &renderer.queue,
1004 center,
1005 particle_count,
1006 dust_color,
1007 force,
1008 );
1009 }
1010 }
1011 }
1012 }
1013
1014 for updater in &mut self.event_updaters {
1016 updater(&mut self.world);
1017 }
1018
1019 let mut renderer = self.world.remove_resource::<Renderer>().unwrap();
1021
1022 let output = match renderer.surface.get_current_texture() {
1023 Ok(texture) => texture,
1024 Err(wgpu::SurfaceError::Outdated) => {
1025 self.world.insert_resource(renderer);
1026 return;
1027 }
1028 Err(e) => {
1029 tracing::error!("Surface hatasi: {:?}", e);
1030 self.world.insert_resource(renderer);
1031 return;
1032 }
1033 };
1034
1035 let view = output
1036 .texture
1037 .create_view(&wgpu::TextureViewDescriptor::default());
1038
1039 let mut encoder = renderer.device.create_command_encoder(
1040 &wgpu::CommandEncoderDescriptor {
1041 label: Some("Render Encoder"),
1042 },
1043 );
1044
1045 if let Some(render_hk) = self.render_fn.as_mut() {
1047 render_hk(
1048 &mut self.world,
1049 &state,
1050 &mut encoder,
1051 &view,
1052 &mut renderer,
1053 light_time,
1054 );
1055 } else if let Some(s_render) = self.simple_render_fn.as_mut() {
1056 let mut ctx = RenderContext::new(
1057 &mut encoder,
1058 &view,
1059 &mut renderer,
1060 light_time,
1061 );
1062 s_render(&mut self.world, &state, &mut ctx);
1063 }
1064
1065 editor.render(
1066 &window,
1067 &renderer.device,
1068 &renderer.queue,
1069 &mut encoder,
1070 &view,
1071 full_output,
1072 );
1073
1074 renderer.queue.submit(std::iter::once(encoder.finish()));
1075 output.present();
1076
1077 self.world.insert_resource(renderer);
1078
1079 self.input.begin_frame();
1081 }
1082 }
1083 Event::AboutToWait => {
1084 window.request_redraw();
1085 }
1086 Event::DeviceEvent {
1087 event: winit::event::DeviceEvent::MouseMotion { delta },
1088 ..
1089 } => {
1090 self.input.on_mouse_delta(delta.0 as f32, delta.1 as f32);
1091 }
1092 _ => {}
1093 }
1094 })
1095 .unwrap();
1096 }
1097}