use crate::app::{Playback, Screen, VersusPhase};
use crate::bots::game::Feed;
use crate::sim::Replay;
use bevy::prelude::*;
use std::collections::VecDeque;
use std::sync::Mutex;
use std::sync::mpsc::Receiver;
#[derive(Resource, Default)]
pub struct Watch {
file: Option<Replay>,
live: Option<Mutex<Receiver<Feed>>>,
next: VecDeque<Replay>,
}
pub fn file(replay: Replay) -> Watch {
Watch {
file: Some(replay),
..Watch::default()
}
}
pub fn live(feed: Receiver<Feed>) -> Watch {
Watch {
live: Some(Mutex::new(feed)),
..Watch::default()
}
}
pub fn begin(
mut watch: ResMut<Watch>,
mut playback: ResMut<Playback>,
mut next_screen: ResMut<NextState<Screen>>,
) {
if let Some(replay) = watch.file.take() {
playback.0 = Some((replay, 0));
next_screen.set(Screen::Versus);
}
}
pub fn pump(
mut watch: ResMut<Watch>,
mut playback: ResMut<Playback>,
screen: Res<State<Screen>>,
vphase: Res<State<VersusPhase>>,
time: Res<Time>,
mut over_for: Local<f32>,
mut next_screen: ResMut<NextState<Screen>>,
) {
let Watch { live, next, .. } = &mut *watch;
let Some(live) = live else {
return;
};
let rx = live
.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner);
while let Ok(feed) = rx.try_recv() {
match feed {
Feed::Start(replay) => {
if playback.0.is_none() && *screen.get() != Screen::Versus {
next.clear();
playback.0 = Some((*replay, 0));
next_screen.set(Screen::Versus);
} else {
next.push_back(*replay);
}
}
Feed::Tick(actions) => match (next.back_mut(), playback.0.as_mut()) {
(Some(queued), _) => queued.record(actions),
(None, Some((replay, _))) => replay.record(actions),
(None, None) => {}
},
}
}
if *screen.get() == Screen::Versus && *vphase.get() == VersusPhase::Over && !next.is_empty() {
*over_for += time.delta_secs();
if *over_for > 4.0 {
*over_for = 0.0;
next_screen.set(Screen::Versus);
}
} else {
*over_for = 0.0;
}
}
pub fn install(mut watch: ResMut<Watch>, mut playback: ResMut<Playback>) {
if let Some(replay) = watch.next.pop_front() {
playback.0 = Some((replay, 0));
}
}
pub fn dropped_replays(
mut drops: MessageReader<FileDragAndDrop>,
settings: Res<crate::app::settings::GameSettings>,
mut playback: ResMut<Playback>,
mut library: ResMut<crate::app::replays::Library>,
mut notice: ResMut<crate::app::RoundNotice>,
mut next_screen: ResMut<NextState<Screen>>,
) {
for drop in drops.read() {
let FileDragAndDrop::DroppedFile { path_buf, .. } = drop else {
continue;
};
let read = std::fs::read_to_string(path_buf)
.map_err(|e| e.to_string())
.and_then(|text| Replay::parse(&text));
match read {
Ok(replay) => {
playback.0 = Some((replay, 0));
next_screen.set(Screen::Versus);
return;
}
Err(e) => {
let why = crate::app::i18n::fill(settings.tr().replay_drop_bad, &[("e", &e)]);
library.feedback.clone_from(&why);
notice.0 = why;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::{Level, MAX_PLAYERS, PlayerAction, classic_arena};
use std::sync::mpsc::{self, Sender};
fn game(name: &str) -> Box<Replay> {
let mut replay = Replay::new(Level::from_board("Arena", 3, classic_arena(false, 2)));
replay.names[0] = name.into();
Box::new(replay)
}
fn arena_window() -> (App, Sender<Feed>) {
let (tx, rx) = mpsc::channel();
let mut app = App::new();
app.add_plugins(bevy::state::app::StatesPlugin);
app.init_state::<Screen>();
app.init_state::<VersusPhase>();
app.insert_resource(Time::<()>::default());
app.insert_resource(live(rx));
app.insert_resource(Playback::default());
app.add_systems(Update, pump);
app.add_systems(OnEnter(Screen::Versus), install);
app.add_systems(OnExit(Screen::Versus), |mut playback: ResMut<Playback>| {
playback.0 = None;
});
(app, tx)
}
fn on_screen(app: &App) -> Option<(String, usize)> {
app.world()
.resource::<Playback>()
.0
.as_ref()
.map(|(replay, _)| (replay.names[0].clone(), replay.inputs.len()))
}
#[test]
fn a_game_started_after_leaving_is_the_one_watched() {
let (mut app, feed) = arena_window();
let tick = [PlayerAction::None; MAX_PLAYERS];
feed.send(Feed::Start(game("g1"))).expect("feed");
app.update();
app.update();
assert_eq!(
*app.world().resource::<State<Screen>>().get(),
Screen::Versus
);
feed.send(Feed::Start(game("g2"))).expect("feed");
feed.send(Feed::Tick(tick)).expect("feed");
app.update();
assert_eq!(on_screen(&app), Some(("g1".into(), 0)));
app.world_mut()
.resource_mut::<NextState<Screen>>()
.set(Screen::Menu);
app.update();
assert_eq!(on_screen(&app), None);
feed.send(Feed::Start(game("g3"))).expect("feed");
for _ in 0..3 {
feed.send(Feed::Tick(tick)).expect("feed");
}
app.update();
app.update();
assert_eq!(
*app.world().resource::<State<Screen>>().get(),
Screen::Versus
);
assert_eq!(on_screen(&app), Some(("g3".into(), 3)));
feed.send(Feed::Tick(tick)).expect("feed");
app.update();
assert_eq!(on_screen(&app), Some(("g3".into(), 4)));
}
}