From 15a9136fc44cfc8528718d356c85cb4b4318539a Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Wed, 15 Jan 2025 22:59:59 -0800
Subject: [PATCH] session instruction changes for control and playback

---
 strafe-client/src/physics_worker.rs | 19 ++++---
 strafe-client/src/session.rs        | 31 ++++++++++--
 strafe-client/src/window.rs         | 77 +++++++++++++++++++++++------
 3 files changed, 101 insertions(+), 26 deletions(-)

diff --git a/strafe-client/src/physics_worker.rs b/strafe-client/src/physics_worker.rs
index 092d5187..084d1646 100644
--- a/strafe-client/src/physics_worker.rs
+++ b/strafe-client/src/physics_worker.rs
@@ -1,13 +1,17 @@
 use crate::graphics_worker::Instruction as GraphicsInstruction;
-use crate::session::{SessionInputInstruction,Instruction as SessionInstruction,Session,Simulation};
+use crate::session::{
+	Session,Simulation,SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction,
+	Instruction as SessionInstruction,
+};
 use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer};
 use strafesnet_common::physics::Time as PhysicsTime;
 use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
 use strafesnet_common::timer::Timer;
 
 pub enum Instruction{
-	Input(SessionInputInstruction),
-	SetPaused(bool),
+	SessionInput(SessionInputInstruction),
+	SessionControl(SessionControlInstruction),
+	SessionPlayback(SessionPlaybackInstruction),
 	Render,
 	Resize(winit::dpi::PhysicalSize<u32>),
 	ChangeMap(strafesnet_common::map::CompleteMap),
@@ -42,11 +46,14 @@ pub fn new<'a>(
 			};
 		}
 		match ins.instruction{
-			Instruction::Input(unbuffered_instruction)=>{
+			Instruction::SessionInput(unbuffered_instruction)=>{
 				run_session_instruction!(ins.time,SessionInstruction::Input(unbuffered_instruction));
 			},
-			Instruction::SetPaused(paused)=>{
-				run_session_instruction!(ins.time,SessionInstruction::SetPaused(paused));
+			Instruction::SessionControl(unbuffered_instruction)=>{
+				run_session_instruction!(ins.time,SessionInstruction::Control(unbuffered_instruction));
+			},
+			Instruction::SessionPlayback(unbuffered_instruction)=>{
+				run_session_instruction!(ins.time,SessionInstruction::Playback(unbuffered_instruction));
 			},
 			Instruction::Render=>{
 				run_session_instruction!(ins.time,SESSION_INSTRUCTION_IDLE);
diff --git a/strafe-client/src/session.rs b/strafe-client/src/session.rs
index 1a913d5a..dc0b17aa 100644
--- a/strafe-client/src/session.rs
+++ b/strafe-client/src/session.rs
@@ -16,9 +16,9 @@ use crate::settings::UserSettings;
 
 pub enum Instruction<'a>{
 	Input(SessionInputInstruction),
-	SetPaused(bool),
+	Control(SessionControlInstruction),
+	Playback(SessionPlaybackInstruction),
 	ChangeMap(&'a strafesnet_common::map::CompleteMap),
-	//Graphics(crate::graphics_worker::Instruction),
 }
 
 pub enum SessionInputInstruction{
@@ -35,6 +35,20 @@ pub enum ImplicitModeInstruction{
 	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,
@@ -176,12 +190,21 @@ impl InstructionConsumer<Instruction<'_>> for Session{
 			Instruction::Input(SessionInputInstruction::Misc(other_other_instruction))=>{
 				run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(other_other_instruction));
 			},
-			Instruction::SetPaused(paused)=>{
+			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)=>{
+				todo!();
+			},
+			Instruction::Control(SessionControlInstruction::StopSpectate)=>{
+				todo!();
+			},
+			Instruction::Playback(_)=>{
+				todo!();
+			},
 			Instruction::ChangeMap(complete_map)=>{
 				self.clear_recording();
 				self.change_map(complete_map);
diff --git a/strafe-client/src/window.rs b/strafe-client/src/window.rs
index 441d3d3d..00f52a2f 100644
--- a/strafe-client/src/window.rs
+++ b/strafe-client/src/window.rs
@@ -2,7 +2,7 @@ use strafesnet_common::instruction::TimedInstruction;
 use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
 use strafesnet_common::physics::{MiscInstruction,SetControlInstruction};
 use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
-use crate::session::SessionInputInstruction;
+use crate::session::{SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction};
 
 pub enum Instruction{
 	Resize(winit::dpi::PhysicalSize<u32>),
@@ -37,7 +37,7 @@ impl WindowContext<'_>{
 				//pause unpause
 				self.physics_thread.send(TimedInstruction{
 					time,
-					instruction:PhysicsWorkerInstruction::SetPaused(!state),
+					instruction:PhysicsWorkerInstruction::SessionControl(SessionControlInstruction::SetPaused(!state)),
 				}).unwrap();
 				//recalculate pressed keys on focus
 			},
@@ -92,29 +92,74 @@ impl WindowContext<'_>{
 					},
 					(keycode,state)=>{
 						let s=state.is_pressed();
-						if let Some(session_input_instruction)=match keycode{
-							winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(s))),
+
+						// internal variants for this scope
+						enum SessionInstructionSubset{
+							Input(SessionInputInstruction),
+							Control(SessionControlInstruction),
+							Playback(SessionPlaybackInstruction),
+						}
+						macro_rules! input_ctrl{
+							($variant:ident,$state:expr)=>{
+								Some(SessionInstructionSubset::Input(SessionInputInstruction::SetControl(SetControlInstruction::$variant($state))))
+							};
+						}
+						macro_rules! input_misc{
+							($variant:ident,$state:expr)=>{
+								s.then_some(SessionInstructionSubset::Input(SessionInputInstruction::Misc(MiscInstruction::$variant)))
+							};
+						}
+						macro_rules! session_ctrl{
+							($variant:ident,$state:expr)=>{
+								s.then_some(SessionInstructionSubset::Control(SessionControlInstruction::$variant))
+							};
+						}
+						macro_rules! session_playback{
+							($variant:ident,$state:expr)=>{
+								s.then_some(SessionInstructionSubset::Playback(SessionPlaybackInstruction::$variant))
+							};
+						}
+						impl From<SessionInstructionSubset> for PhysicsWorkerInstruction{
+							fn from(value:SessionInstructionSubset)->Self{
+								match value{
+									SessionInstructionSubset::Input(session_input_instruction)=>PhysicsWorkerInstruction::SessionInput(session_input_instruction),
+									SessionInstructionSubset::Control(session_control_instruction)=>PhysicsWorkerInstruction::SessionControl(session_control_instruction),
+									SessionInstructionSubset::Playback(session_playback_instruction)=>PhysicsWorkerInstruction::SessionPlayback(session_playback_instruction),
+								}
+							}
+						}
+
+						if let Some(session_instruction)=match keycode{
+							winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>input_ctrl!(SetJump,s),
+							// TODO: bind system so playback pausing can use spacebar
+							winit::keyboard::Key::Named(winit::keyboard::NamedKey::Enter)=>session_playback!(TogglePaused,s),
+							winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowUp)=>session_playback!(IncreaseTimescale,s),
+							winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowDown)=>session_playback!(DecreaseTimescale,s),
+							winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowLeft)=>session_playback!(SkipBack,s),
+							winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowRight)=>session_playback!(SkipForward,s),
 							winit::keyboard::Key::Character(key)=>match key.as_str(){
-								"W"|"w"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveForward(s))),
-								"A"|"a"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveLeft(s))),
-								"S"|"s"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveBack(s))),
-								"D"|"d"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveRight(s))),
-								"E"|"e"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveUp(s))),
-								"Q"|"q"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveDown(s))),
-								"Z"|"z"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetZoom(s))),
+								"W"|"w"=>input_ctrl!(SetMoveForward,s),
+								"A"|"a"=>input_ctrl!(SetMoveLeft,s),
+								"S"|"s"=>input_ctrl!(SetMoveBack,s),
+								"D"|"d"=>input_ctrl!(SetMoveRight,s),
+								"E"|"e"=>input_ctrl!(SetMoveUp,s),
+								"Q"|"q"=>input_ctrl!(SetMoveDown,s),
+								"Z"|"z"=>input_ctrl!(SetZoom,s),
 								"R"|"r"=>s.then(||{
 									//mouse needs to be reset since the position is absolute
 									self.mouse_pos=glam::DVec2::ZERO;
-									SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndRestart)
+									SessionInstructionSubset::Input(SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndRestart))
 								}),
-								"F"|"f"=>s.then_some(SessionInputInstruction::Misc(MiscInstruction::PracticeFly)),
+								"F"|"f"=>input_misc!(PracticeFly,s),
+								"B"|"b"=>session_ctrl!(CopyRecordingIntoReplayAndSpectate,s),
+								"X"|"x"=>session_ctrl!(StopSpectate,s),
 								_=>None,
 							},
 							_=>None,
 						}{
 							self.physics_thread.send(TimedInstruction{
 								time,
-								instruction:PhysicsWorkerInstruction::Input(session_input_instruction),
+								instruction:session_instruction.into(),
 							}).unwrap();
 						}
 					},
@@ -138,7 +183,7 @@ impl WindowContext<'_>{
 				self.mouse_pos+=glam::dvec2(delta.0,delta.1);
 				self.physics_thread.send(TimedInstruction{
 					time,
-					instruction:PhysicsWorkerInstruction::Input(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())),
+					instruction:PhysicsWorkerInstruction::SessionInput(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())),
 				}).unwrap();
 			},
 			winit::event::DeviceEvent::MouseWheel {
@@ -148,7 +193,7 @@ impl WindowContext<'_>{
 				if false{//self.physics.style.use_scroll{
 					self.physics_thread.send(TimedInstruction{
 						time,
-						instruction:PhysicsWorkerInstruction::Input(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(true))),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump
+						instruction:PhysicsWorkerInstruction::SessionInput(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(true))),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump
 					}).unwrap();
 				}
 			},