diff --git a/src/game.rs b/src/game.rs index 79556b9..45bc6da 100644 --- a/src/game.rs +++ b/src/game.rs @@ -6,7 +6,7 @@ //! konstruiert und durch die Dispatch-Pipeline gereicht. //! //! Verben, deren Ziel-Subsystem (Szene, Audio, Renderer) noch nicht -//! existiert oder den Kern nichts angeht, produzieren [`Effect`]-Werte +//! existiert oder den Kern nichts angeht, produzieren [`Action`]-Werte //! statt direkt zu wirken. Frontends konsumieren die Queue: die CLI druckt //! sie, die Engine führt sie später aus. So bleibt der Kern headless //! testbar und kennt keines der Subsysteme. @@ -15,30 +15,29 @@ use crate::ink; use crate::kv::Store; use crate::signals::Signals; -/// Von Actions angeforderte Wirkung außerhalb des Kerns. Die Queue in -/// `Game::effects` gehört nach jedem Dispatch geleert (konsumiert). +/// Action, die der Kern nicht selbst ausführt, sondern ans Frontend +/// weiterreicht. Die Queue in `Game::actions` gehört nach jedem Dispatch +/// geleert (konsumiert). #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Effect { +pub enum Action { /// Instance unsichtbar + kollisionslos + nicht mehr interagierbar machen. HideObject(String), /// WAV unter `assets/audio/` als SFX abspielen. PlaySound(String), - /// Palette/Farbschema unter `assets/palettes/` laden. - LoadPalette(String), } pub struct Game { pub kv: Store, pub signals: Signals, pub story: Option, - pub effects: Vec, + pub actions: Vec, } pub struct ActionCtx<'a> { pub kv: &'a mut Store, pub signals: &'a Signals, pub story: &'a mut Option, - pub effects: &'a mut Vec, + pub actions: &'a mut Vec, /// Auslöser-Identifier: bei Objekt-Interaktion der volle Instance-Name /// (mit Blender-Suffix, z.B. `Mushroom.005`). `$self` in Action-Args /// wird damit substituiert. `None` bei Signalen ohne Quell-Instance @@ -48,7 +47,7 @@ pub struct ActionCtx<'a> { impl Game { pub fn new(signals: Signals) -> Self { - Self { kv: Store::new(), signals, story: None, effects: Vec::new() } + Self { kv: Store::new(), signals, story: None, actions: Vec::new() } } pub fn action_ctx(&mut self, instance_name: Option) -> ActionCtx<'_> { @@ -56,7 +55,7 @@ impl Game { kv: &mut self.kv, signals: &self.signals, story: &mut self.story, - effects: &mut self.effects, + actions: &mut self.actions, instance_name, } } diff --git a/src/main.rs b/src/main.rs index 0d109fc..708de17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ //! Ink-Stories) laufen headless; diese REPL ist ihr erstes Frontend. //! Der Renderer kommt später dazu (siehe notes/renderer-plan.md) und //! konsumiert dieselben Schnittstellen — insbesondere `StoryState` und -//! die `Effect`-Queue. +//! die `Action`-Queue. use std::io::{self, Write}; @@ -15,15 +15,15 @@ mod kv; mod signals; mod story_ctrl; -use game::{Effect, Game}; +use game::{Action, Game}; use ink::StoryState; fn main() { let signals_path = assets::path("assets/signals.toml"); - let table = signals::load_signals(&signals_path); + let signal_table = signals::load_signals(&signals_path); println!("wds-Konsole — {} Signale aus {}. `help` für Befehle.", - table.len(), signals_path); - let mut game = Game::new(table); + signal_table.len(), signals_path); + let mut game = Game::new(signal_table); // Reserviertes `[init]`-Signal: KV-Defaults setzen, bevor Stories laufen. fire(&mut game, "init", None); @@ -66,14 +66,14 @@ fn print_help() { println!(" quit beenden"); } -/// Signal dispatchen, Effects ausgeben und — falls eine Action eine Story -/// gestartet hat — den Dialog-Loop fahren. +/// Signal dispatchen, deferred Actions ausgeben und — falls eine Action +/// eine Story gestartet hat — den Dialog-Loop fahren. fn fire(game: &mut Game, signal: &str, instance: Option) { { let mut ctx = game.action_ctx(instance); signals::dispatch(signal, &mut ctx); } - drain_effects(game); + drain_actions(game); if game.story.is_some() { run_story(game); } @@ -90,7 +90,7 @@ fn run_story(game: &mut Game) { for t in &tags { signals::dispatch(t, &mut ctx); } state }; - drain_effects(game); + drain_actions(game); match state { None | Some(StoryState::End) => break, Some(StoryState::Text(text)) => { @@ -115,12 +115,11 @@ fn run_story(game: &mut Game) { } } -fn drain_effects(game: &mut Game) { - for e in game.effects.drain(..) { - match e { - Effect::HideObject(n) => println!("[effect] hide_object {n}"), - Effect::PlaySound(n) => println!("[effect] play_sound {n}"), - Effect::LoadPalette(n) => println!("[effect] load_palette {n}"), +fn drain_actions(game: &mut Game) { + for a in game.actions.drain(..) { + match a { + Action::HideObject(n) => println!("[action] hide_object {n}"), + Action::PlaySound(n) => println!("[action] play_sound {n}"), } } } diff --git a/src/signals.rs b/src/signals.rs index ce1b23b..f3d7256 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -14,9 +14,8 @@ //! `set ` KV setzen (true/false/i32/f32/string) //! `inc []` KV-Integer inkrementieren (Default +1) //! `clear ` KV-Eintrag entfernen -//! `hide_object ` → Effect::HideObject -//! `play_sound ` → Effect::PlaySound -//! `load_palette ` → Effect::LoadPalette +//! `hide_object ` → Action::HideObject (deferred) +//! `play_sound ` → Action::PlaySound (deferred) //! //! Parameter-Substitution: vor dem Parsen ersetzt `execute` `$self` in den //! Action-Args durch `ctx.instance_name`. Damit kann eine generische Action @@ -31,12 +30,11 @@ use std::collections::HashMap; use std::fs::read_to_string; -use crate::game::{ActionCtx, Effect}; +use crate::game::{Action, ActionCtx}; use crate::kv; use crate::story_ctrl; -pub type Actions = Vec; -pub type Signals = HashMap; +pub type Signals = HashMap>; /// Strippt Blenders Duplikat-Suffix (`.NNN` mit reinen Ziffern am Ende), /// damit `Mushroom.001`, `Mushroom.042` etc. alle auf `[Mushroom]` in @@ -67,7 +65,8 @@ fn execute(cmd: &str, ctx: &mut ActionCtx) { // $self → ctx.instance_name. Substituieren bevor wir Verb/Args splitten, // damit Tokens wie `hide_object $self` einheitlich funktionieren. Ohne // instance_name (z.B. `init`-Signal) bleibt `$self` stehen — die Action - // zielt dann ins Leere, was für Effects ein No-Op beim Konsumenten ist. + // zielt dann ins Leere, was für deferred Actions ein No-Op beim + // Konsumenten ist. let cmd_owned: String; let cmd_ref: &str = if cmd.contains("$self") { if let Some(name) = ctx.instance_name.as_deref() { @@ -83,9 +82,8 @@ fn execute(cmd: &str, ctx: &mut ActionCtx) { "set" => kv::apply_set(args, ctx.kv), "inc" => kv::apply_inc(args, ctx.kv), "clear" => kv::apply_clear(args, ctx.kv), - "hide_object" => ctx.effects.push(Effect::HideObject(args.to_string())), - "play_sound" => ctx.effects.push(Effect::PlaySound(args.to_string())), - "load_palette" => ctx.effects.push(Effect::LoadPalette(args.to_string())), + "hide_object" => ctx.actions.push(Action::HideObject(args.to_string())), + "play_sound" => ctx.actions.push(Action::PlaySound(args.to_string())), _ => { /* Unbekannter Verb/Tag → still ignorieren */ } } } @@ -161,7 +159,7 @@ mod tests { } #[test] - fn dispatch_runs_actions_and_queues_effects() { + fn dispatch_runs_actions_and_queues_deferred() { let mut signals = Signals::new(); signals.insert("pickup".into(), vec![ "inc items".into(), @@ -174,9 +172,9 @@ mod tests { dispatch("pickup", &mut ctx); assert_eq!(game.kv["items"].coerce_to_int().unwrap(), 1); - assert_eq!(game.effects, vec![ - Effect::PlaySound("pickup.wav".into()), - Effect::HideObject("Mushroom.005".into()), + assert_eq!(game.actions, vec![ + Action::PlaySound("pickup.wav".into()), + Action::HideObject("Mushroom.005".into()), ]); } @@ -191,6 +189,6 @@ mod tests { // Unbekanntes Verb ist ein stilles No-Op. let mut ctx = game.action_ctx(None); dispatch("color story_a", &mut ctx); - assert!(game.effects.is_empty()); + assert!(game.actions.is_empty()); } }