Skip to main content

gizmo_app/
windowed.rs

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