use std::collections::HashMap; use strafesnet_common::gameplay_modes::{ModeId,StageId}; use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction}; // session represents the non-hardware state of the client. // Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state. use strafesnet_common::physics::{ ModeInstruction,MiscInstruction, Instruction as PhysicsInputInstruction, TimeInner as PhysicsTimeInner, Time as PhysicsTime }; use strafesnet_common::timer::{Scaled,Timer}; use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime}; use strafesnet_settings::directories::Directories; use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction}; use strafesnet_physics::physics::{self,PhysicsContext,PhysicsData}; use strafesnet_settings::settings::UserSettings; pub enum Instruction<'a>{ Input(SessionInputInstruction), Control(SessionControlInstruction), Playback(SessionPlaybackInstruction), ChangeMap(&'a strafesnet_common::map::CompleteMap), LoadReplay(strafesnet_snf::bot::Segment), Idle, } pub enum SessionInputInstruction{ Mouse(glam::IVec2), SetControl(strafesnet_common::physics::SetControlInstruction), Mode(ImplicitModeInstruction), Misc(strafesnet_common::physics::MiscInstruction), } /// Implicit mode instruction are fed separately to session. /// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction #[derive(Clone,Debug)] pub enum ImplicitModeInstruction{ ResetAndRestart, ResetAndSpawn(ModeId,StageId), } pub enum SessionControlInstruction{ SetPaused(bool), // copy the current session simulation recording into a replay and view it CopyRecordingIntoReplayAndSpectate, StopSpectate, SaveReplay, LoadIntoReplayState, } pub enum SessionPlaybackInstruction{ SkipForward, SkipBack, TogglePaused, DecreaseTimescale, IncreaseTimescale, } pub struct FrameState{ pub body:physics::Body, pub camera:physics::PhysicsCamera, pub time:PhysicsTime, } pub struct Simulation{ timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>, physics:physics::PhysicsState, } impl Simulation{ pub const fn new( timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>, physics:physics::PhysicsState, )->Self{ Self{ timer, physics, } } pub fn get_frame_state(&self,time:SessionTime)->FrameState{ FrameState{ body:self.physics.camera_body(), camera:self.physics.camera(), time:self.timer.time(time), } } } #[derive(Default)] pub struct Recording{ instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>, } impl Recording{ pub fn new( instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>, )->Self{ Self{instructions} } fn clear(&mut self){ self.instructions.clear(); } } pub struct Replay{ next_instruction_id:usize, recording:Recording, simulation:Simulation, } impl Replay{ pub const fn new( recording:Recording, simulation:Simulation, )->Self{ Self{ next_instruction_id:0, recording, simulation, } } pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){ let mut time=self.simulation.timer.time(time_limit); loop{ if let Some(ins)=self.recording.instructions.get(self.next_instruction_id){ if ins.time<time{ PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone()); self.next_instruction_id+=1; }else{ break; } }else{ // loop playback self.next_instruction_id=0; // No need to reset physics because the very first instruction is 'Reset' let new_time=self.recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time); self.simulation.timer.set_time(time_limit,new_time); time=new_time; } } } } #[derive(Clone,Copy,Hash,PartialEq,Eq)] struct BotId(u32); //#[derive(Clone,Copy,Hash,PartialEq,Eq)] //struct PlayerId(u32); enum ViewState{ Play, //Spectate(PlayerId), Replay(BotId), } pub struct Session{ directories:Directories, user_settings:UserSettings, mouse_interpolator:crate::mouse_interpolator::MouseInterpolator, view_state:ViewState, //gui:GuiState geometry_shared:physics::PhysicsData, simulation:Simulation, // below fields not included in lite session recording:Recording, //players:HashMap<PlayerId,Simulation>, replays:HashMap<BotId,Replay>, } impl Session{ pub fn new( user_settings:UserSettings, directories:Directories, simulation:Simulation, )->Self{ Self{ user_settings, directories, mouse_interpolator:MouseInterpolator::new(), geometry_shared:Default::default(), simulation, view_state:ViewState::Play, recording:Default::default(), replays:HashMap::new(), } } fn clear_recording(&mut self){ self.recording.clear(); } fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){ self.simulation.physics.clear(); self.geometry_shared.generate_models(map); } pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{ match &self.view_state{ ViewState::Play=>Some(self.simulation.get_frame_state(time)), ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay| replay.simulation.get_frame_state(time) ), } } pub fn user_settings(&self)->&UserSettings{ &self.user_settings } } // mouseinterpolator consumes RawInputInstruction // mouseinterpolator emits PhysicsInputInstruction // mouseinterpolator consumes DoStep to move on to the next emitted instruction // Session comsumes SessionInstruction -> forwards RawInputInstruction to mouseinterpolator // Session consumes DoStep -> forwards DoStep to mouseinterpolator // Session emits DoStep impl InstructionConsumer<Instruction<'_>> for Session{ type Time=SessionTime; fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::Time>){ // repetitive procedure macro macro_rules! run_mouse_interpolator_instruction{ ($instruction:expr)=>{ self.mouse_interpolator.process_instruction(TimedInstruction{ time:ins.time, instruction:TimedInstruction{ time:self.simulation.timer.time(ins.time), instruction:$instruction, }, }); }; } // process any timeouts that occured since the last instruction self.process_exhaustive(ins.time); match ins.instruction{ // send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime Instruction::Input(SessionInputInstruction::Mouse(pos))=>{ run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos)); }, Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{ run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::SetControl(set_control_instruction)); }, Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{ self.clear_recording(); let mode_id=self.simulation.physics.mode(); run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset)); run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))); run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Restart(mode_id))); }, Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{ self.clear_recording(); run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset)); run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))); run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id))); }, Instruction::Input(SessionInputInstruction::Misc(misc_instruction))=>{ run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(misc_instruction)); }, Instruction::Control(SessionControlInstruction::SetPaused(paused))=>{ // don't flush the buffered instructions in the mouse interpolator // until the mouse is confirmed to be not moving at a later time // what if they pause for 5ms lmao _=self.simulation.timer.set_paused(ins.time,paused); }, Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{ // Bind: B // pause simulation _=self.simulation.timer.set_paused(ins.time,true); // create recording let mut recording=Recording::default(); recording.instructions.extend(self.recording.instructions.iter().cloned()); // create timer starting at first instruction (or zero if the list is empty) let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time); let timer=Timer::unpaused(ins.time,new_time); // create default physics state let simulation=Simulation::new(timer,Default::default()); // invent a new bot id and insert the replay let bot_id=BotId(self.replays.len() as u32); self.replays.insert(bot_id,Replay::new( recording, simulation, )); // begin spectate self.view_state=ViewState::Replay(bot_id); }, Instruction::Control(SessionControlInstruction::StopSpectate)=>{ let view_state=core::mem::replace(&mut self.view_state,ViewState::Play); // delete the bot, otherwise it's inaccessible and wastes CPU match view_state{ ViewState::Play=>(), ViewState::Replay(bot_id)=>{ self.replays.remove(&bot_id); }, } _=self.simulation.timer.set_paused(ins.time,false); }, Instruction::Control(SessionControlInstruction::SaveReplay)=>{ // Bind: N let view_state=core::mem::replace(&mut self.view_state,ViewState::Play); match view_state{ ViewState::Play=>(), ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){ let mut replays_path=self.directories.replays.clone(); let file_name=format!("{}.snfb",ins.time); std::thread::spawn(move ||{ std::fs::create_dir_all(replays_path.as_path()).unwrap(); replays_path.push(file_name); let file=std::fs::File::create(replays_path).unwrap(); strafesnet_snf::bot::write_bot( std::io::BufWriter::new(file), strafesnet_physics::VERSION.get(), replay.recording.instructions ).unwrap(); println!("Finished writing bot file!"); }); }, } _=self.simulation.timer.set_paused(ins.time,false); }, Instruction::Control(SessionControlInstruction::LoadIntoReplayState)=>{ // Bind: J let view_state=core::mem::replace(&mut self.view_state,ViewState::Play); match view_state{ ViewState::Play=>(), ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){ self.recording.instructions=replay.recording.instructions.into_iter().take(replay.next_instruction_id).collect(); self.simulation=replay.simulation; }, } // don't unpause -- use the replay timer state whether it is pasued or unpaused }, Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{ match &self.view_state{ ViewState::Play=>{ // allow simulation timescale for fun let scale=self.simulation.timer.get_scale(); self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap()); }, ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){ let scale=replay.simulation.timer.get_scale(); replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap()); }, } }, Instruction::Playback(SessionPlaybackInstruction::DecreaseTimescale)=>{ match &self.view_state{ ViewState::Play=>{ // allow simulation timescale for fun let scale=self.simulation.timer.get_scale(); self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap()); }, ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){ let scale=replay.simulation.timer.get_scale(); replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap()); }, } }, Instruction::Playback(SessionPlaybackInstruction::SkipForward)=>{ match &self.view_state{ ViewState::Play=>(), ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){ let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5)); replay.simulation.timer.set_time(ins.time,time); }, } }, Instruction::Playback(SessionPlaybackInstruction::SkipBack)=>{ match &self.view_state{ ViewState::Play=>(), ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){ let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5)); replay.simulation.timer.set_time(ins.time,time); // resimulate the entire playback lol replay.next_instruction_id=0; }, } }, Instruction::Playback(SessionPlaybackInstruction::TogglePaused)=>{ match &self.view_state{ ViewState::Play=>(), ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){ _=replay.simulation.timer.set_paused(ins.time,!replay.simulation.timer.is_paused()); }, } } Instruction::ChangeMap(complete_map)=>{ self.clear_recording(); self.change_map(complete_map); }, Instruction::LoadReplay(bot)=>{ // pause simulation _=self.simulation.timer.set_paused(ins.time,true); // create recording let recording=Recording::new(bot.instructions); // create timer starting at first instruction (or zero if the list is empty) let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time); let timer=Timer::unpaused(ins.time,new_time); // create default physics state let simulation=Simulation::new(timer,Default::default()); // invent a new bot id and insert the replay let bot_id=BotId(self.replays.len() as u32); self.replays.insert(bot_id,Replay::new( recording, simulation, )); // begin spectate self.view_state=ViewState::Replay(bot_id); }, Instruction::Idle=>{ run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle); // this just refreshes the replays for replay in self.replays.values_mut(){ // TODO: filter idles from recording, inject new idles in real time replay.advance(&self.geometry_shared,ins.time); } } }; // process all emitted output instructions self.process_exhaustive(ins.time); } } impl InstructionConsumer<StepInstruction> for Session{ type Time=SessionTime; fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::Time>){ let time=self.simulation.timer.time(ins.time); if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){ //record self.recording.instructions.push(instruction.clone()); PhysicsContext::run_input_instruction(&mut self.simulation.physics,&self.geometry_shared,instruction); } } } impl InstructionEmitter<StepInstruction> for Session{ type Time=SessionTime; fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::Time>>{ self.mouse_interpolator.next_instruction(time_limit) } }