centralized state
This commit is contained in:
+78
-14
@@ -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); }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user