generated from GarandPLG/rust-flake-template
Refactor App to use unified AppChannels for events
App now receives an AppChannels struct; AppEvent enum removed. Event handling, tick generation, and audio communication are now performed through dedicated channels.
This commit is contained in:
+18
-23
@@ -1,17 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
GameStates, handle_keybindings,
|
GameStates, handle_keybindings,
|
||||||
threads::{AppEvent, AudioCmd},
|
threads::{AppChannels, AudioCmd},
|
||||||
view::View,
|
view::View,
|
||||||
},
|
},
|
||||||
cli::Cli,
|
cli::Cli,
|
||||||
};
|
};
|
||||||
use ratatui::{DefaultTerminal, Frame, crossterm::event::KeyEvent, layout::Rect};
|
use ratatui::{DefaultTerminal, Frame, crossterm::event::KeyEvent, layout::Rect};
|
||||||
use std::{
|
use std::{io::Result, sync::mpsc::Sender, thread::sleep, time::Duration};
|
||||||
io::Result,
|
|
||||||
sync::mpsc::{Receiver, RecvTimeoutError, Sender},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub exit: bool,
|
pub exit: bool,
|
||||||
@@ -42,31 +38,29 @@ impl App {
|
|||||||
self.states.as_mut()
|
self.states.as_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, terminal: &mut DefaultTerminal, rx: Receiver<AppEvent>) -> Result<()> {
|
pub fn run(&mut self, terminal: &mut DefaultTerminal, channels: AppChannels) -> Result<()> {
|
||||||
while !self.exit {
|
while !self.exit {
|
||||||
terminal.draw(|frame: &mut Frame<'_>| self.draw(frame))?;
|
terminal.draw(|frame: &mut Frame<'_>| self.draw(frame))?;
|
||||||
|
|
||||||
let event = match rx.recv_timeout(Duration::from_millis(100)) {
|
if let Ok(key) = channels.input_rx.try_recv() {
|
||||||
Ok(ev) => ev,
|
self.handle_key_event(key)?;
|
||||||
Err(RecvTimeoutError::Timeout) => {
|
}
|
||||||
continue;
|
|
||||||
}
|
if let Ok((_, _)) = channels.resize_rx.try_recv() {
|
||||||
Err(_) => break,
|
let window_area: Rect = self.window_area;
|
||||||
};
|
if let Some(state) = self.states_mut() {
|
||||||
match event {
|
|
||||||
AppEvent::Input(key_event) => self.handle_key_event(key_event)?,
|
|
||||||
AppEvent::Resize(_, _) => {
|
|
||||||
let window_area: Rect = self.window_area;
|
|
||||||
let Some(state) = self.states_mut() else {
|
|
||||||
panic!("State issue")
|
|
||||||
};
|
|
||||||
state
|
state
|
||||||
.skirmish
|
.skirmish
|
||||||
.board
|
.board
|
||||||
.change_resize(&window_area, state.skirmish.side_panel);
|
.change_resize(&window_area, state.skirmish.side_panel);
|
||||||
}
|
}
|
||||||
AppEvent::Tick => self.update()?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(()) = channels.tick_rx.try_recv() {
|
||||||
|
self.update()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -94,8 +88,9 @@ impl App {
|
|||||||
|
|
||||||
fn update(&mut self) -> Result<()> {
|
fn update(&mut self) -> Result<()> {
|
||||||
if let Some(state) = self.states_mut() {
|
if let Some(state) = self.states_mut() {
|
||||||
state.skirmish.update(&self.args);
|
state.skirmish.tick_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{app::states::skirmish_states::BoardState, cli::Cli};
|
use crate::app::states::skirmish_states::BoardState;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SkirmishState {
|
pub struct SkirmishState {
|
||||||
@@ -10,8 +10,8 @@ pub struct SkirmishState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SkirmishState {
|
impl SkirmishState {
|
||||||
pub fn update(&mut self, cli: &Cli) {
|
pub fn tick_update(&mut self) {
|
||||||
self.board.advance_turn();
|
// self.board.advance_turn();
|
||||||
|
|
||||||
// if self.board.is_victory() {}
|
// if self.board.is_victory() {}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use crate::app::threads::AudioCmd;
|
||||||
|
use ratatui::crossterm::event::KeyEvent;
|
||||||
|
use std::sync::mpsc::{Receiver, Sender, channel};
|
||||||
|
|
||||||
|
pub struct AppChannels {
|
||||||
|
pub input_tx: Sender<KeyEvent>,
|
||||||
|
pub input_rx: Receiver<KeyEvent>,
|
||||||
|
|
||||||
|
pub resize_tx: Sender<(u16, u16)>,
|
||||||
|
pub resize_rx: Receiver<(u16, u16)>,
|
||||||
|
|
||||||
|
pub tick_tx: Sender<()>,
|
||||||
|
pub tick_rx: Receiver<()>,
|
||||||
|
|
||||||
|
pub audio_tx: Sender<AudioCmd>,
|
||||||
|
pub audio_rx: Receiver<AudioCmd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppChannels {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (input_tx, input_rx) = channel::<KeyEvent>();
|
||||||
|
let (resize_tx, resize_rx) = channel::<(u16, u16)>();
|
||||||
|
let (tick_tx, tick_rx) = channel::<()>();
|
||||||
|
let (audio_tx, audio_rx) = channel::<AudioCmd>();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
input_tx,
|
||||||
|
input_rx,
|
||||||
|
resize_tx,
|
||||||
|
resize_rx,
|
||||||
|
tick_tx,
|
||||||
|
tick_rx,
|
||||||
|
audio_tx,
|
||||||
|
audio_rx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-21
@@ -1,30 +1,20 @@
|
|||||||
use ratatui::crossterm::event::KeyEvent;
|
use ratatui::crossterm::event::{Event, KeyEvent, read};
|
||||||
use ratatui::crossterm::event::{Event, read};
|
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
pub enum AppEvent {
|
pub fn handle_ct_events(input_tx: Sender<KeyEvent>, resize_tx: Sender<(u16, u16)>) {
|
||||||
Input(KeyEvent),
|
|
||||||
Resize(u16, u16),
|
|
||||||
Tick,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads *all* crossterm events and forwards the ones we care about.
|
|
||||||
pub fn handle_events(tx: Sender<AppEvent>) {
|
|
||||||
loop {
|
loop {
|
||||||
match read() {
|
match read() {
|
||||||
Ok(ev) => match ev {
|
Ok(Event::Key(k)) => {
|
||||||
Event::Key(key) => {
|
if input_tx.send(k).is_err() {
|
||||||
if tx.send(AppEvent::Input(key)).is_err() {
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Event::Resize(cols, rows) => {
|
}
|
||||||
if tx.send(AppEvent::Resize(cols, rows)).is_err() {
|
Ok(Event::Resize(cols, rows)) => {
|
||||||
break;
|
if resize_tx.send((cols, rows)).is_err() {
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
},
|
Ok(_) => {}
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
mod app_channels;
|
||||||
mod audio;
|
mod audio;
|
||||||
mod events;
|
mod events;
|
||||||
mod tick;
|
mod tick;
|
||||||
|
|
||||||
|
pub use app_channels::AppChannels;
|
||||||
pub use audio::{AudioCmd, SoundrackParts, Soundtrack, handle_audio};
|
pub use audio::{AudioCmd, SoundrackParts, Soundtrack, handle_audio};
|
||||||
pub use events::{AppEvent, handle_events};
|
pub use events::handle_ct_events;
|
||||||
pub use tick::spawn_tick_thread;
|
pub use tick::handle_tick_event;
|
||||||
|
|||||||
+11
-14
@@ -1,22 +1,19 @@
|
|||||||
use crate::app::threads::AppEvent;
|
|
||||||
use std::{
|
use std::{
|
||||||
sync::mpsc::Sender,
|
sync::mpsc::Sender,
|
||||||
thread,
|
thread,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn spawn_tick_thread(tx: Sender<AppEvent>, interval_ms: u64) {
|
pub fn handle_tick_event(tx: Sender<()>, interval_ms: u8) {
|
||||||
thread::spawn(move || {
|
let interval: Duration = Duration::from_millis(interval_ms as u64);
|
||||||
let interval: Duration = Duration::from_millis(interval_ms);
|
loop {
|
||||||
loop {
|
if tx.send(()).is_err() {
|
||||||
if tx.send(AppEvent::Tick).is_err() {
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let elapsed: Duration = Instant::now().elapsed();
|
|
||||||
if interval > elapsed {
|
|
||||||
thread::sleep(interval - elapsed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
let elapsed: Duration = Instant::now().elapsed();
|
||||||
|
if interval > elapsed {
|
||||||
|
thread::sleep(interval - elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) {
|
|||||||
"Skills points: {} ({}/{}) | ",
|
"Skills points: {} ({}/{}) | ",
|
||||||
1, 20, states.settings.skill_points_limit
|
1, 20, states.settings.skill_points_limit
|
||||||
),
|
),
|
||||||
format!("Perk Deck: {}/9", 5),
|
format!("Perk Deck: {}/9 | ", 5),
|
||||||
|
format!("Tick: {}", states.skirmish.turn_counter),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
+17
-8
@@ -1,10 +1,13 @@
|
|||||||
use crate::app::{
|
use crate::{
|
||||||
states::{
|
app::{
|
||||||
PerkDecks,
|
states::{
|
||||||
skirmish_states::{GameMode, ZoomLevel},
|
PerkDecks,
|
||||||
|
skirmish_states::{GameMode, ZoomLevel},
|
||||||
|
},
|
||||||
|
threads::Soundtrack,
|
||||||
|
view::View,
|
||||||
},
|
},
|
||||||
threads::Soundtrack,
|
logs::init_logger,
|
||||||
view::View,
|
|
||||||
};
|
};
|
||||||
use clap::{Error, Parser, error::ErrorKind, value_parser};
|
use clap::{Error, Parser, error::ErrorKind, value_parser};
|
||||||
use std::num::ParseFloatError;
|
use std::num::ParseFloatError;
|
||||||
@@ -15,7 +18,7 @@ use std::num::ParseFloatError;
|
|||||||
/// The `clap` attributes describe the flag name, help text, default value,
|
/// The `clap` attributes describe the flag name, help text, default value,
|
||||||
/// and any validation constraints. The struct derives `Parser` so that
|
/// and any validation constraints. The struct derives `Parser` so that
|
||||||
/// `Cli::parse()` can be called directly to obtain a populated instance.
|
/// `Cli::parse()` can be called directly to obtain a populated instance.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
#[command(version, about = "War in Tunnels", long_about = "War in Tunnels")]
|
#[command(version, about = "War in Tunnels", long_about = "War in Tunnels")]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// The initial view/window to display.
|
/// The initial view/window to display.
|
||||||
@@ -169,7 +172,13 @@ pub struct Cli {
|
|||||||
/// handles argument validation and displays helpful error messages if
|
/// handles argument validation and displays helpful error messages if
|
||||||
/// the user supplies invalid input.
|
/// the user supplies invalid input.
|
||||||
pub fn get_args() -> Cli {
|
pub fn get_args() -> Cli {
|
||||||
Cli::parse()
|
let args: Cli = Cli::parse();
|
||||||
|
|
||||||
|
if args.log {
|
||||||
|
init_logger();
|
||||||
|
}
|
||||||
|
|
||||||
|
args
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a string into a floating‑point XP modifier and validates that it
|
/// Parses a string into a floating‑point XP modifier and validates that it
|
||||||
|
|||||||
+20
-33
@@ -1,22 +1,19 @@
|
|||||||
use ratatui::{Terminal, prelude::CrosstermBackend};
|
use ratatui::{Terminal, crossterm::event::KeyEvent, prelude::CrosstermBackend};
|
||||||
use std::{
|
use std::{
|
||||||
io::{Result, Stdout},
|
io::{Result, Stdout},
|
||||||
sync::mpsc::channel,
|
mem::replace,
|
||||||
thread::{
|
sync::mpsc::{Receiver, Sender, channel},
|
||||||
self,
|
thread::{self, JoinHandle},
|
||||||
// JoinHandle
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use war_in_tunnels::{
|
use war_in_tunnels::{
|
||||||
app::{
|
app::{
|
||||||
App,
|
App,
|
||||||
threads::{AppEvent, AudioCmd, handle_audio, handle_events, spawn_tick_thread},
|
threads::{AppChannels, AudioCmd, handle_audio, handle_ct_events, handle_tick_event},
|
||||||
},
|
},
|
||||||
cli::{Cli, get_args},
|
cli::{Cli, get_args},
|
||||||
logs::init_logger,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const TICK_MS: u64 = 33;
|
const TICK_MS: u8 = 33;
|
||||||
|
|
||||||
/// Starts the terminal UI application.
|
/// Starts the terminal UI application.
|
||||||
///
|
///
|
||||||
@@ -25,43 +22,33 @@ const TICK_MS: u64 = 33;
|
|||||||
/// terminal, or while running the `App`.
|
/// terminal, or while running the `App`.
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args: Cli = get_args();
|
let args: Cli = get_args();
|
||||||
if args.log {
|
|
||||||
init_logger();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (app_event_tx, app_event_rx) = channel::<AppEvent>();
|
let mut channels: AppChannels = AppChannels::new();
|
||||||
|
|
||||||
// let app_event_thread: JoinHandle<()> = thread::spawn(move || {
|
let input_tx: Sender<KeyEvent> = channels.input_tx.clone();
|
||||||
// handle_events(app_event_tx);
|
let resize_tx: Sender<(u16, u16)> = channels.resize_tx.clone();
|
||||||
// });
|
let events_thread: JoinHandle<()> =
|
||||||
|
thread::spawn(move || handle_ct_events(input_tx, resize_tx));
|
||||||
|
|
||||||
thread::spawn(move || {
|
let tick_tx: Sender<()> = channels.tick_tx.clone();
|
||||||
handle_events(app_event_tx.clone());
|
let tick_thread: JoinHandle<()> = thread::spawn(move || handle_tick_event(tick_tx, TICK_MS));
|
||||||
});
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
spawn_tick_thread(app_event_tx, TICK_MS);
|
|
||||||
});
|
|
||||||
|
|
||||||
let (audio_tx, audio_rx) = channel::<AudioCmd>();
|
|
||||||
|
|
||||||
// let audio_event_thread: JoinHandle<()> = thread::spawn(move || {
|
|
||||||
// handle_audio(audio_rx);
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
let audio_rx: Receiver<AudioCmd> = replace(&mut channels.audio_rx, channel().1);
|
||||||
|
// let audio_thread: JoinHandle<()> =
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
handle_audio(audio_rx, args.mute, args.sound_track);
|
handle_audio(audio_rx, args.mute, args.sound_track);
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init();
|
let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init();
|
||||||
let mut app: App = App::new(args, audio_tx);
|
let mut app: App = App::new(args, channels.audio_tx.clone());
|
||||||
|
|
||||||
let app_result: Result<()> = app.run(&mut terminal, app_event_rx);
|
let app_result: Result<()> = app.run(&mut terminal, channels);
|
||||||
|
|
||||||
ratatui::restore();
|
ratatui::restore();
|
||||||
|
|
||||||
// let _ = app_event_thread.;
|
let _ = events_thread.join();
|
||||||
// let _ = audio_event_thread.join();
|
let _ = tick_thread.join();
|
||||||
|
// let _ = audio_thread.join(); // TODO: kill playing music
|
||||||
|
|
||||||
app_result
|
app_result
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user