434 lines
15 KiB
Rust
434 lines
15 KiB
Rust
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::physics::{PhysicsContext,PhysicsData};
|
|
use crate::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:crate::physics::Body,
|
|
pub camera:crate::physics::PhysicsCamera,
|
|
pub time:PhysicsTime,
|
|
}
|
|
|
|
pub struct Simulation{
|
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
|
physics:crate::physics::PhysicsState,
|
|
}
|
|
impl Simulation{
|
|
pub const fn new(
|
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
|
physics:crate::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,PhysicsTimeInner>>,
|
|
}
|
|
impl Recording{
|
|
pub fn new(
|
|
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
|
)->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{
|
|
user_settings:UserSettings,
|
|
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
|
view_state:ViewState,
|
|
//gui:GuiState
|
|
geometry_shared:crate::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,
|
|
simulation:Simulation,
|
|
)->Self{
|
|
Self{
|
|
user_settings,
|
|
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 TimeInner=SessionTimeInner;
|
|
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
|
|
// 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 file_name=format!("replays/{}.snfb",ins.time);
|
|
std::thread::spawn(move ||{
|
|
std::fs::create_dir_all("replays").unwrap();
|
|
let file=std::fs::File::create(file_name).unwrap();
|
|
strafesnet_snf::bot::write_bot(std::io::BufWriter::new(file),crate::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 TimeInner=SessionTimeInner;
|
|
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
|
|
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 TimeInner=SessionTimeInner;
|
|
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
|
self.mouse_interpolator.next_instruction(time_limit)
|
|
}
|
|
}
|