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 crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction}; use crate::settings::UserSettings; pub enum Instruction<'a>{ Input(SessionInputInstruction), Control(SessionControlInstruction), Playback(SessionPlaybackInstruction), ChangeMap(&'a strafesnet_common::map::CompleteMap), } 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(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId), } pub enum SessionControlInstruction{ SetPaused(bool), // copy the current session simulation recording into a replay and view it CopyRecordingIntoReplayAndSpectate, StopSpectate, } pub enum SessionPlaybackInstruction{ SkipForward, SkipBack, TogglePaused, DecreaseTimescale, IncreaseTimescale, } pub struct FrameState{ pub body:crate::physics::Body, pub camera:crate::physics::PhysicsCamera, pub time:PhysicsTime, } pub struct Simulation{ timer:Timer>, physics:crate::physics::PhysicsContext, } impl Simulation{ pub const fn new( timer:Timer>, physics:crate::physics::PhysicsContext, )->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>, } impl Recording{ fn clear(&mut self){ self.instructions.clear(); } } pub struct Replay{ last_instruction_id:usize, recording:Recording, simulation:Simulation, } impl Replay{ pub const fn new( recording:Recording, simulation:Simulation, )->Self{ Self{ last_instruction_id:0, recording, simulation, } } } #[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{ user_settings:UserSettings, mouse_interpolator:crate::mouse_interpolator::MouseInterpolator, view_state:ViewState, //gui:GuiState simulation:Simulation, // below fields not included in lite session recording:Recording, //players:HashMap, replays:HashMap, } impl Session{ pub fn new( user_settings:UserSettings, simulation:Simulation, )->Self{ Self{ user_settings, mouse_interpolator:MouseInterpolator::new(), 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.generate_models(map); } pub fn get_frame_state(&self,time:SessionTime)->Option{ 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> for Session{ type TimeInner=SessionTimeInner; fn process_instruction(&mut self,ins:TimedInstruction){ // 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(); 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)); }, 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)=>{ // 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 timer=Timer::unpaused(ins.time,recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.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)=>{ _=self.simulation.timer.set_paused(ins.time,false); self.view_state=ViewState::Play; }, Instruction::Playback(_)=>{ println!("[session] todo: Playback instructions"); }, Instruction::ChangeMap(complete_map)=>{ self.clear_recording(); self.change_map(complete_map); // ResetAndSpawn 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(ModeId::MAIN,StageId::FIRST))); }, }; // process all emitted output instructions self.process_exhaustive(ins.time); } } impl InstructionConsumer for Session{ type TimeInner=SessionTimeInner; fn process_instruction(&mut self,ins:TimedInstruction){ 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()); self.simulation.physics.run_input_instruction(instruction); } } } impl InstructionEmitter for Session{ type TimeInner=SessionTimeInner; fn next_instruction(&self,time_limit:SessionTime)->Option>{ self.mouse_interpolator.next_instruction(time_limit) } }