Files
wsg/lib/src/app.rs
T
Jérôme Bousquié 7b25564483 doc
2026-07-08 15:46:50 +02:00

154 lines
7.2 KiB
Rust

//! # 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<EventLoop<()>>, // 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<Window>,
}
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<H: AppHandler + 'static>(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<App, WsgError> {
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,
})
}
}