Skip to main content

gizmo_app/
lib.rs

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                        &registry,
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)>>, // dt, input
94    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    >, // light_time
106    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>>, // Input handler
108    ui_fn: Option<Box<dyn FnMut(&mut World, &mut State, &egui::Context)>>, // Editor UI handler
109    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    /// Sisteme yeni bir Olay (Event) türü kaydeder.
179    /// Bu işlem sayesinde her kare bitişinde çift-buffer `update()` otomatik çalışır.
180    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    /// Yeni, basit Render fonksiyonunu (RenderContext) ekler
238    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            // Canvas'ı body'ye ekle ve boyutu ayarla
312            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        // Initialize Core Dev Console Systems BEFORE setup so setup can register cvars
355        self.world
356            .insert_resource(gizmo_core::cvar::CVarRegistry::new());
357        // Window Resource oluştur ve World'e ekle
358        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        // Renderer Resource oluştur ve World'e ekle
364        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                // UI Entegrasyonu: Winit Olaylarını EGUI'ye Gönder
425                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                // Eğer UI girdiyi yakalamadıysa Kullanıcı Input Hook'a Yolla
436                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                                // TLS'teki GPU kaynaklarını temizlemekle uğraşmak yerine direkt çık
456                                #[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                                // Fiziksel Tuş (PhysicalKey)
478                                if let winit::keyboard::PhysicalKey::Code(keycode) =
479                                    kb_event.physical_key
480                                {
481                                    codes_to_press.push(keycode as u32);
482                                }
483                                // Mantıksal Tuş (LogicalKey Fallback)
484                                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                                } // Ends the 'if codes_to_press.is_empty()' block
519
520                                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); // Güvenlik çemberi: Frame takılırsa 50ms'den fazla zıplamayacak, yerçekiminden düşme engellenecek.
566                            last_frame_time = now;
567
568                            // Playback / Record mantigi
569                            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                            // Update
591                            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                                // Render Global Dev Console on top of everything
597                                dev_console::ui_dev_console(&mut self.world, ctx, &self.input);
598                            });
599
600                            // --- Scene View RTT (Render To Texture) YÖNETİMİ ---
601                            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                                // Scene View RTT
635                                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                                // Game View RTT
684                                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                            // --- EDITOR SCENE REQUESTS ---
759                            // 1. Poll the file-dialog channel and promote result to save/load request.
760                            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, // dialog dismissed
771                                                Err(std::sync::mpsc::TryRecvError::Empty) => {
772                                                    // still waiting — put it back
773                                                    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                            // 2. Extract requests before borrowing world mutably.
802                            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                            // 3. Save
817                            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                                    &registry,
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                            // 4. Clear + Load
838                            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                                        &registry,
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                            // ECS Sistemlerini Çalıştırmadan önce DI için Core Resource'ları Güncelle
901                            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                            // ═══ Fixed Timestep Fizik Döngüsü ═══
921                            // PhysicsTime resource'u yoksa oluştur
922                            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                            // Sabit dt'de fizik adımları — frame rate'ten bağımsız
939                            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                                // ECS fizik sistemlerini sabit dt ile çalıştır
956                                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                            // İnterpolasyon alpha'sını hesapla (render için)
966                            {
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                            // Kullanıcı update hook'u (render dt ile — kamera, UI, vb.)
975                            if let Some(update_hk) = self.update_fn.as_mut() {
976                                update_hk(&mut self.world, &mut state, dt, &self.input);
977                            }
978
979                            // Update sonrası olası ertelenmiş komutları (CommandQueue) hemen işle
980                            self.world.apply_commands();
981
982                            // --- DYNAMIC FRACTURE & PARTICLE INTEGRATION ---
983                            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]; // Dust color
997                                            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                            // Olayları Güncelle (Çift-buffer temizliği)
1015                            for updater in &mut self.event_updaters {
1016                                updater(&mut self.world);
1017                            }
1018
1019                            // --- DRAW KISMI ---
1020                            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                            // Kullaniciya CommandEncoder verip cizdiriyoruz!
1046                            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                            // İşlemlerin bitiminde frame-özel input girdilerini (fare delta vs.) temizle
1080                            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}