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