//! # App Facade Module — High-Level Application Orchestration //! //! Defines the `App` facade type and `AppHandler` trait that provide the high-level user-facing API. //! `App` encapsulates window lifecycle, event loop, frame acquisition, and rendering automation. //! Users implement `AppHandler` to inject their game logic into the render loop without touching wgpu directly. //! The `AppBuilder` provides a builder-style constructor for creating configured `App` instances. //! //! ## Interaction with Other Modules //! - **core::context**: Consumes GPU hardware lifecycle via Context; acquires frames for rendering. //! - **core::renderer**: Delegates draw call execution to Renderer per frame. //! - **pipeline::pipeline_cache**: Holds PipelineCache instance for shader/pipeline management. //! - **scene::scene**: Exposes Scene as mutable field so users can register resources and entities. //! - **utils::conf**: Provides default window title, dimensions, and embedded WGSL source. //! - **handler**: Defines the AppHandler trait that users implement for custom logic. use crate::AppHandler; use crate::core::{Context, Renderer}; use crate::pipeline::PipelineCache; use crate::scene::Scene; use crate::utils::WsgError; use crate::utils::conf::{APP_DEFAULT_HEIGHT, APP_DEFAULT_TITLE, APP_DEFAULT_WIDTH}; use std::sync::Arc; use winit::event_loop::EventLoop; use winit::window::Window; /// High-level application facade that orchestrates window lifecycle, event loop, and rendering automation. /// Encapsulates all five WGPU objects (Instance, Surface, Adapter, Device, Queue) plus the render loop. /// Users create an App via AppBuilder, then run it with their implementation of AppHandler. pub struct App { /// GPU hardware context — owns Instance, Surface, Adapter, Device, Queue lifecycle. pub context: Context, /// Executor layer — binds Materials and Meshes into RenderPasses during draw calls. pub renderer: Renderer, /// Winit event loop for window management. Set to None after run() consumes it. pub event_loop: Option>, // On met en Option pour pouvoir faire .take() facilement /// Shader compilation cache — manages RenderPipelines keyed by shader_id. pub cache: PipelineCache, /// Resource depot and entity graph — users register Meshes/Materials here before the render loop begins. pub scene: Scene, /// The OS-level window backing this application. Shared via Arc for multi-owner access. pub window: Arc, } impl App { /// Runs the application's main loop: processes events, updates logic per frame, renders, and presents. /// Inputs: handler — user-provided AppHandler implementation containing game logic. /// Returns Ok(()) on success or Err(WsgError::WindowSystem) if the event loop exits abnormally. /// Called once at application entry point; runs until the window is closed or an error occurs. /// Internal steps: 1) take EventLoop from Option → 2) enter winit event loop → /// 3a) on AboutToWait: call handler.update() + request_redraw → /// 3b) on RedrawRequested: acquire frame → call handler.render() → present frame → /// 3c) on CloseRequested: exit event loop. pub fn run(mut self, mut handler: H) -> Result<(), WsgError> { // On extrait l'event_loop de manière sûre grâce au Option let event_loop = self.event_loop.take().ok_or(WsgError::WindowSystem)?; // Erreur si déjà pris event_loop .run(move |event, elwt| { match event { winit::event::Event::AboutToWait => { // update logic handler.update(&mut self); self.window.request_redraw(); } winit::event::Event::WindowEvent { event: winit::event::WindowEvent::RedrawRequested, .. } => { // Rendering logic let frame = self.context.get_next_frame(); // On appelle le render() de l'utilisateur handler.render(&mut self); // On présente automatiquement self.renderer.present(frame); } winit::event::Event::WindowEvent { event: winit::event::WindowEvent::CloseRequested, .. } => { elwt.exit(); } _ => {} } }) .map_err(|_| WsgError::WindowSystem) } } /// Builder for constructing a configured `App` instance with custom title and dimensions. /// Provides a fluent API for setting window properties before building the full application context. pub struct AppBuilder { /// Window title displayed in the OS taskbar/window decorations. title: String, /// Window width in pixels. width: u32, /// Window height in pixels. height: u32, } impl AppBuilder { /// Creates an AppBuilder with default values: "WSG App" title, 800x600 resolution. /// Called as the entry point of the builder pattern — always start here. pub fn new() -> Self { Self { title: APP_DEFAULT_TITLE.to_string(), width: APP_DEFAULT_WIDTH, height: APP_DEFAULT_HEIGHT, } } /// Sets the window title to display in the OS taskbar/window decorations. /// Inputs: title (string reference). Returns Self for method chaining. pub fn title(mut self, title: &str) -> Self { self.title = title.to_string(); self } /// Sets the window dimensions in pixels. /// Inputs: width (pixel count), height (pixel count). Returns Self for method chaining. pub fn size(mut self, width: u32, height: u32) -> Self { self.width = width; self.height = height; self } /// Builds the configured `App` instance by creating all required components in order: /// EventLoop → Window → Context → Renderer → PipelineCache → Scene. /// Returns Ok(App) on success or Err(WsgError) if any component fails during creation. /// Called after setting desired properties via the builder pattern; triggers async GPU initialization. pub async fn build(self) -> Result { let event_loop = EventLoop::new().unwrap(); let window = Arc::new( winit::window::WindowBuilder::new() .with_title(&self.title) .build(&event_loop) .map_err(|_| WsgError::WindowSystem)?, ); let context = Context::new(window.clone()).await?; let device = Arc::new(context.device.clone()); let format = context .configure(&context.adapter, self.width, self.height) .map_err(|_| WsgError::SurfaceIncompatible)?; let renderer = Renderer::new(&context, format); let cache = PipelineCache::new(device); let scene = Scene::new(); Ok(App { context, renderer, cache, scene, event_loop: Some(event_loop), window, }) } }