centralized state

This commit is contained in:
2026-06-13 13:44:02 +02:00
parent 0a8861a5b1
commit 48c499e356
11 changed files with 693 additions and 206 deletions
+78 -14
View File
@@ -1,15 +1,26 @@
//! Fenster-Frontend: winit-Loop um den wgpu-Renderer.
//!
//! Stand: Schritt 1+2 des Renderer-Plans (notes/renderer-plan.md) —
//! Fenster, internes 320×240-Target mit Testmuster, Nearest-Upscale mit
//! 4:3-Letterbox. Szene, PS1-Shader und Flycam folgen als nächste Schritte.
//! Stand: Schritt 3 des Renderer-Plans (notes/renderer-plan.md) —
//! internes 320×240-Target, PS1-Szenen-Pass (Pixel-Snap, affine
//! Interpolation, RGB555+Bayer) am Testwürfel, Letterbox-Blit.
//! Als Nächstes: Flycam (Schritt 4), dann OBJ-Szene (Schritt 5).
//!
//! Gleiche Schicht-Regel wie `cli`: Geschwister von `engine`, konsumiert
//! dessen Schnittstellen (sobald hier eine Szene läuft) — nie umgekehrt.
//! Steuert dieselbe [`Session`] wie die CLI: das Fenster *besitzt* sie,
//! Terminal-Befehle kommen über einen Channel von einem stdin-Thread und
//! werden pro Loop-Durchlauf eingespielt. Im Dialog-Modus pausiert die
//! Welt (die Sim-Uhr läuft nicht weiter) — so sieht das Fenster nie einen
//! anderen Zustand als die Konsole.
//!
//! Schicht-Regel wie `cli`: Geschwister von `engine`/`session`, konsumiert
//! deren Schnittstellen — nie umgekehrt.
mod gpu;
mod math;
mod scene;
use std::sync::mpsc::{self, Receiver};
use std::sync::Arc;
use std::time::Instant;
use winit::application::ApplicationHandler;
use winit::event::{ElementState, KeyEvent, WindowEvent};
@@ -17,20 +28,56 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::{Window, WindowId};
use crate::session::{Mode, Session};
use gpu::Gpu;
pub fn run() {
pub fn run(mut session: Session) {
// Init-Signal feuern, bevor das Fenster steht (kann bereits einen
// Dialog öffnen — dann startet die Welt eben pausiert).
for line in session.start() { println!("{line}"); }
// stdin auf einem eigenen Thread: er darf blockieren, der Main-Thread
// (winit) nicht. Es queren nur Strings die Thread-Grenze, kein Zustand
// — der bleibt allein auf dem Main-Thread.
let (tx, rx) = mpsc::channel::<String>();
std::thread::spawn(move || {
let stdin = std::io::stdin();
let mut buf = String::new();
loop {
buf.clear();
match stdin.read_line(&mut buf) {
Ok(0) | Err(_) => break, // EOF (Ctrl-D) → Thread endet
Ok(_) => {
if tx.send(buf.trim_end().to_string()).is_err() { break; }
}
}
}
});
let event_loop = EventLoop::new().expect("winit: Event-Loop");
// Poll statt Wait: wir rendern kontinuierlich (Spiel, kein Editor).
// Poll statt Wait: wir rendern kontinuierlich (Spiel, kein Editor) und
// pollen nebenbei den Befehls-Channel.
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = App::default();
let mut app = App::new(session, rx);
event_loop.run_app(&mut app).expect("winit: run");
}
#[derive(Default)]
struct App {
window: Option<Arc<Window>>,
gpu: Option<Gpu>,
window: Option<Arc<Window>>,
gpu: Option<Gpu>,
session: Session,
/// Befehlszeilen vom stdin-Thread.
rx: Receiver<String>,
/// Sim-Zeit (Sekunden), läuft nur im `Free`-Modus weiter → Welt-Pause
/// im Dialog. Treibt aktuell die Orbit-Kamera (bis Flycam, Schritt 4).
sim_t: f32,
last: Option<Instant>,
}
impl App {
fn new(session: Session, rx: Receiver<String>) -> Self {
Self { window: None, gpu: None, session, rx, sim_t: 0.0, last: None }
}
}
impl ApplicationHandler for App {
@@ -48,6 +95,17 @@ impl ApplicationHandler for App {
self.window = Some(window);
}
/// Läuft einmal pro Loop-Durchlauf, nachdem die Events abgearbeitet
/// sind: Konsolen-Befehle einspielen und den nächsten Frame anfordern.
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
while let Ok(line) = self.rx.try_recv() {
let r = self.session.exec(&line);
for out in r.output { println!("{out}"); }
if r.quit { event_loop.exit(); }
}
if let Some(w) = &self.window { w.request_redraw(); }
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
@@ -63,9 +121,15 @@ impl ApplicationHandler for App {
}
}
WindowEvent::RedrawRequested => {
if let Some(gpu) = &mut self.gpu { gpu.frame(); }
// Sofort den nächsten Frame anfordern (Vsync drosselt).
if let Some(w) = &self.window { w.request_redraw(); }
// dt messen; im Dialog die Sim-Uhr anhalten → Welt pausiert,
// während die Konsole den Dialog treibt.
let now = Instant::now();
let dt = self.last.replace(now)
.map_or(0.0, |prev| (now - prev).as_secs_f32());
if matches!(self.session.mode, Mode::Free) {
self.sim_t += dt;
}
if let Some(gpu) = &mut self.gpu { gpu.frame(self.sim_t); }
}
_ => {}
}