Compare commits

..

51 Commits

Author SHA1 Message Date
f98ffe6e0b fix spin bug edge case 2025-01-15 00:48:03 -08:00
f9d4ca8370 remove single use function 2025-01-15 00:36:15 -08:00
fea5bf4398 low polling rate edge case 2025-01-15 00:29:45 -08:00
d393f9f187 change some function names 2025-01-15 00:26:40 -08:00
ada34237c9 fix timeout timestamp 2025-01-15 00:02:26 -08:00
292df72709 transpose next buffer state calculation 2025-01-14 23:49:31 -08:00
7476d7cdc7 the mouse spin bug 2025-01-14 23:49:31 -08:00
6138d70a6f unify timeout 😍 2025-01-14 23:49:31 -08:00
cac4d698c3 fix overall correctness 2025-01-14 23:23:50 -08:00
e0ea0d6119 remove holdover case 2025-01-14 23:00:46 -08:00
80a4431ee8 test mouse_interpolator 2025-01-14 23:00:46 -08:00
80424cf24c spawn on map change 2025-01-14 21:41:55 -08:00
c338826513 finish 2025-01-14 21:16:36 -08:00
a6a242175b rewrite enums again 2025-01-14 20:44:17 -08:00
08bd57ffe1 remove incorrect comment 2025-01-14 19:02:34 -08:00
0d9c6648e2 accumulate mouse_pos as float 2025-01-14 19:00:17 -08:00
405cba3549 discover ternary method on bool 2025-01-14 18:39:38 -08:00
38d8dc1302 InstructionCache 2025-01-14 18:26:59 -08:00
33ccefc411 pop_buffered_instruction can be accomplished with mem::replace 2025-01-14 03:44:23 -08:00
93277c042b make pain code smaller 2025-01-14 01:45:09 -08:00
90f6437817 wrong instruction 2025-01-14 01:36:50 -08:00
29f9d5298f work 2025-01-14 01:34:26 -08:00
b0489a3746 work work work 2025-01-13 23:59:16 -08:00
a8847d3632 ruin physics code 2025-01-13 23:55:42 -08:00
fb8c2a619a rename fields in MouseInstruction::ReplaceMouse 2025-01-13 23:51:13 -08:00
6898302fa5 move code to more relevant location 2025-01-13 23:23:58 -08:00
52bbaaddc7 don't mutate physics_timeline on the fly 2025-01-13 23:23:58 -08:00
a8581a2a4f don't reconstruct MouseState struct with noop 2025-01-13 23:23:58 -08:00
c6ff11dd3e use replace_with to replace the enum variant in-place without cloning 2025-01-13 23:11:43 -08:00
844c7a08e1 add replace_with dep 2025-01-13 22:46:16 -08:00
bd61d03c91 work 2025-01-13 22:32:26 -08:00
b58ebb2775 todos 2025-01-11 01:50:06 -08:00
9095215cad write pop_buffered_instruction 2025-01-11 01:38:45 -08:00
92c30c3b87 cool changes 2025-01-11 01:07:06 -08:00
1b35c96f6e cook a bit 2025-01-10 23:18:53 -08:00
47bf9f1af3 pain 2025-01-10 22:08:54 -08:00
719c702b95 actually need ReplaceMouse because of OS level issue
The operating system does not report the timestamp at which it checks that the mouse was not moving, so the mouse interpolation will necessarily be incorrect for up to 1 polling period.  The alternative is to guess / make up a timestamp, but I don't want to do this.
2025-01-10 22:01:02 -08:00
ceb2499ad2 delete ReplaceMouse instruction 2025-01-10 20:59:25 -08:00
fe43ce9df6 progress 2025-01-10 20:03:53 -08:00
1fcd18bc45 how does it work 2025-01-09 21:20:25 -08:00
e371f95a4b a 2025-01-09 21:14:17 -08:00
b02c1bc7b4 idk if dropinstruction is gonan work 2025-01-09 21:14:15 -08:00
89446a933a a 2025-01-09 20:48:11 -08:00
0a3d965bb6 work 2025-01-09 20:48:11 -08:00
b6206d52c8 work 2025-01-09 20:48:11 -08:00
498c628280 asd 2025-01-09 20:48:11 -08:00
273e915f67 no 2025-01-09 20:48:11 -08:00
5072e5d7a8 yeah 2025-01-09 20:48:11 -08:00
3f0e3e0d3c update mouse interpolator code 2025-01-09 20:48:11 -08:00
2e88ae0612 wip 2025-01-09 20:48:11 -08:00
4c216a5b28 wip 2025-01-09 20:48:11 -08:00
69 changed files with 1106 additions and 2453 deletions

View File

@ -1,2 +1,6 @@
[registries.strafesnet] [registries.strafesnet]
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]

499
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,5 @@
[workspace] [workspace]
members = [ members = [
"engine/graphics",
"engine/physics",
"engine/session",
"engine/settings",
"integration-testing",
"lib/bsp_loader", "lib/bsp_loader",
"lib/common", "lib/common",
"lib/deferred_loader", "lib/deferred_loader",

View File

@ -3,9 +3,6 @@
# Strafe Project # Strafe Project
Monorepo for working on projects related to strafe client. Monorepo for working on projects related to strafe client.
## Try it out
See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for downloads.
## How to build and run ## How to build and run
1. Have rust and git installed 1. Have rust and git installed
2. `git clone https://git.itzana.me/StrafesNET/strafe-project` 2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
@ -13,4 +10,4 @@ See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for dow
4. `cargo run --release --bin strafe-client` 4. `cargo run --release --bin strafe-client`
## Licenses ## Licenses
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client and engine crates have a sole proprietor license. Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client has a sole proprietor license.

View File

@ -1,14 +0,0 @@
[package]
name = "strafesnet_graphics"
version = "0.1.0"
edition = "2021"
[dependencies]
bytemuck = { version = "1.13.1", features = ["derive"] }
ddsfile = "0.5.1"
glam = "0.29.0"
id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_session = { path = "../session", registry = "strafesnet" }
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
wgpu = "24.0.0"

View File

@ -1,8 +0,0 @@
/*******************************************************
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
*
* This file is part of the StrafesNET bhop/surf client.
*
* StrafesNET can not be copied and/or distributed
* without the express permission of Rhys Lloyd
*******************************************************/

View File

@ -1,2 +0,0 @@
pub mod model;
pub mod graphics;

View File

@ -1,10 +0,0 @@
[package]
name = "strafesnet_physics"
version = "0.1.0"
edition = "2021"
[dependencies]
arrayvec = "0.7.6"
glam = "0.29.0"
id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }

View File

@ -1,8 +0,0 @@
/*******************************************************
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
*
* This file is part of the StrafesNET bhop/surf client.
*
* StrafesNET can not be copied and/or distributed
* without the express permission of Rhys Lloyd
*******************************************************/

View File

@ -1,45 +0,0 @@
mod body;
mod push_solve;
mod face_crawler;
mod model;
pub mod physics;
// Physics bug fixes can easily desync all bots.
//
// When replaying a bot, use the exact physics version which it was recorded with.
//
// When validating a new bot, ignore the version and use the latest version,
// and overwrite the version in the file.
//
// Compatible physics versions should be determined
// empirically at development time via leaderboard resimulation.
//
// Compatible physics versions should result in an identical leaderboard state,
// or the only bots which fail are ones exploiting a surgically patched bug.
#[derive(Clone,Copy,Hash,Debug,id::Id,Eq,PartialEq,Ord,PartialOrd)]
pub struct PhysicsVersion(u32);
pub const VERSION:PhysicsVersion=PhysicsVersion(0);
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
let compat=[0];
let mut input_version=0;
while input_version<compat.len(){
// compatible version must be greater than or equal to the input version
assert!(input_version as u32<=compat[input_version]);
// compatible version must be a version that exists
assert!(compat[input_version]<=VERSION.0);
input_version+=1;
}
compat
};
pub enum PhysicsVersionError{
UnknownPhysicsVersion,
}
pub const fn get_latest_compatible_version(PhysicsVersion(version):PhysicsVersion)->Result<PhysicsVersion,PhysicsVersionError>{
if (version as usize)<LATEST_COMPATIBLE_VERSION.len(){
Ok(PhysicsVersion(LATEST_COMPATIBLE_VERSION[version as usize]))
}else{
Err(PhysicsVersionError::UnknownPhysicsVersion)
}
}

View File

@ -1,12 +0,0 @@
[package]
name = "strafesnet_session"
version = "0.1.0"
edition = "2021"
[dependencies]
glam = "0.29.0"
replace_with = "0.1.7"
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_physics = { path = "../physics", registry = "strafesnet" }
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
strafesnet_snf = { path = "../../lib/snf", registry = "strafesnet" }

View File

@ -1,8 +0,0 @@
/*******************************************************
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
*
* This file is part of the StrafesNET bhop/surf client.
*
* StrafesNET can not be copied and/or distributed
* without the express permission of Rhys Lloyd
*******************************************************/

View File

@ -1,2 +0,0 @@
mod mouse_interpolator;
pub mod session;

View File

@ -1,443 +0,0 @@
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,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{
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 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 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 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)
}
}

View File

@ -1,10 +0,0 @@
[package]
name = "strafesnet_settings"
version = "0.1.0"
edition = "2021"
[dependencies]
configparser = "3.0.2"
directories = "6.0.0"
glam = "0.29.0"
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }

View File

@ -1,8 +0,0 @@
/*******************************************************
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
*
* This file is part of the StrafesNET bhop/surf client.
*
* StrafesNET can not be copied and/or distributed
* without the express permission of Rhys Lloyd
*******************************************************/

View File

@ -1,32 +0,0 @@
use std::path::PathBuf;
use crate::settings::{UserSettings,load_user_settings};
pub struct Directories{
pub settings:PathBuf,
pub maps:PathBuf,
pub replays:PathBuf,
}
impl Directories{
pub fn settings(&self)->UserSettings{
load_user_settings(&self.settings)
}
pub fn user()->Option<Self>{
let dirs=directories::ProjectDirs::from("net.strafes","StrafesNET","Strafe Client")?;
Some(Self{
settings:dirs.config_dir().join("settings.conf"),
maps:dirs.cache_dir().join("maps"),
// separate directory for remote downloaded replays (cache)
// bots:dirs.cache_dir().join("bots"),
replays:dirs.data_local_dir().join("replays"),
})
}
pub fn portable()->Result<Self,std::io::Error>{
let current_dir=std::env::current_dir()?;
Ok(Self{
settings:current_dir.join("settings.conf"),
maps:current_dir.join("maps"),
replays:current_dir.join("replays"),
})
}
}

View File

@ -1,2 +0,0 @@
pub mod settings;
pub mod directories;

View File

@ -1,9 +0,0 @@
[package]
name = "integration-testing"
version = "0.1.0"
edition = "2021"
[dependencies]
strafesnet_common = { version = "0.5.2", path = "../lib/common", registry = "strafesnet" }
strafesnet_physics = { version = "0.1.0", path = "../engine/physics", registry = "strafesnet" }
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }

View File

@ -1,221 +0,0 @@
use std::{io::{Cursor,Read},path::Path};
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
fn main(){
test_determinism().unwrap();
}
#[allow(unused)]
#[derive(Debug)]
enum ReplayError{
IO(std::io::Error),
SNF(strafesnet_snf::Error),
SNFM(strafesnet_snf::map::Error),
SNFB(strafesnet_snf::bot::Error),
}
impl From<std::io::Error> for ReplayError{
fn from(value:std::io::Error)->Self{
Self::IO(value)
}
}
impl From<strafesnet_snf::Error> for ReplayError{
fn from(value:strafesnet_snf::Error)->Self{
Self::SNF(value)
}
}
impl From<strafesnet_snf::map::Error> for ReplayError{
fn from(value:strafesnet_snf::map::Error)->Self{
Self::SNFM(value)
}
}
impl From<strafesnet_snf::bot::Error> for ReplayError{
fn from(value:strafesnet_snf::bot::Error)->Self{
Self::SNFB(value)
}
}
fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
let mut file=std::fs::File::open(path)?;
let mut data=Vec::new();
file.read_to_end(&mut data)?;
Ok(Cursor::new(data))
}
fn run_replay()->Result<(),ReplayError>{
println!("loading map file..");
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
println!("loading bot file..");
let data=read_entire_file("../tools/replays/535s+159764769ns.snfb")?;
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
// create recording
let mut physics_data=PhysicsData::default();
println!("generating models..");
physics_data.generate_models(&map);
println!("simulating...");
let mut physics=PhysicsState::default();
for ins in bot.instructions{
PhysicsContext::run_input_instruction(&mut physics,&physics_data,ins);
}
match physics.get_finish_time(){
Some(time)=>println!("finish time:{}",time),
None=>println!("simulation did not end in finished state"),
}
Ok(())
}
enum DeterminismResult{
Deterministic,
NonDeterministic,
}
fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->DeterminismResult{
// create default physics state
let mut physics_deterministic=PhysicsState::default();
// create a second physics state
let mut physics_filtered=PhysicsState::default();
// invent a new bot id and insert the replay
println!("simulating...");
let mut non_idle_count=0;
for (i,ins) in bot.instructions.into_iter().enumerate(){
let state_deterministic=physics_deterministic.clone();
let state_filtered=physics_filtered.clone();
PhysicsContext::run_input_instruction(&mut physics_deterministic,&physics_data,ins.clone());
match ins{
strafesnet_common::instruction::TimedInstruction{instruction:strafesnet_common::physics::Instruction::Idle,..}=>(),
other=>{
non_idle_count+=1;
// run
PhysicsContext::run_input_instruction(&mut physics_filtered,&physics_data,other.clone());
// check if position matches
let b0=physics_deterministic.camera_body();
let b1=physics_filtered.camera_body();
if b0.position!=b1.position{
println!("desync at instruction #{}",i);
println!("non idle instructions completed={non_idle_count}");
println!("instruction #{i}={:?}",other);
println!("deterministic state0:\n{state_deterministic:?}");
println!("filtered state0:\n{state_filtered:?}");
println!("deterministic state1:\n{:?}",physics_deterministic);
println!("filtered state1:\n{:?}",physics_filtered);
return DeterminismResult::NonDeterministic;
}
},
}
}
match physics_deterministic.get_finish_time(){
Some(time)=>println!("[with idle] finish time:{}",time),
None=>println!("[with idle] simulation did not end in finished state"),
}
match physics_filtered.get_finish_time(){
Some(time)=>println!("[filtered] finish time:{}",time),
None=>println!("[filtered] simulation did not end in finished state"),
}
DeterminismResult::Deterministic
}
type ThreadResult=Result<Option<DeterminismResult>,ReplayError>;
fn read_and_run(file_path:std::path::PathBuf,physics_data:&PhysicsData)->ThreadResult{
let data=read_entire_file(file_path.as_path())?;
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
println!("Running {:?}",file_path.file_stem());
Ok(Some(segment_determinism(bot,physics_data)))
}
fn do_thread<'a>(s:&'a std::thread::Scope<'a,'_>,file_path:std::path::PathBuf,send:std::sync::mpsc::Sender<ThreadResult>,physics_data:&'a PhysicsData){
s.spawn(move ||{
let result=read_and_run(file_path,physics_data);
// send when thread is complete
send.send(result).unwrap();
});
}
fn get_file_path(dir_entry:std::fs::DirEntry)->Result<Option<std::path::PathBuf>,std::io::Error>{
Ok(dir_entry.file_type()?.is_file().then_some(
dir_entry.path()
))
}
fn test_determinism()->Result<(),ReplayError>{
let thread_limit=std::thread::available_parallelism()?.get();
println!("loading map file..");
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
let mut physics_data=PhysicsData::default();
println!("generating models..");
physics_data.generate_models(&map);
let (send,recv)=std::sync::mpsc::channel();
let mut read_dir=std::fs::read_dir("../tools/replays")?;
// promise that &physics_data will outlive the spawned threads
let thread_results=std::thread::scope(|s|{
let mut thread_results=Vec::new();
// spawn threads
println!("spawning up to {thread_limit} threads...");
let mut active_thread_count=0;
while active_thread_count<thread_limit{
if let Some(dir_entry_result)=read_dir.next(){
if let Some(file_path)=get_file_path(dir_entry_result?)?{
active_thread_count+=1;
do_thread(s,file_path,send.clone(),&physics_data);
}
}else{
break;
}
}
// spawn another thread every time a message is received from the channel
println!("riding parallelism wave...");
while let Some(dir_entry_result)=read_dir.next(){
if let Some(file_path)=get_file_path(dir_entry_result?)?{
// wait for a thread to complete
thread_results.push(recv.recv().unwrap());
do_thread(s,file_path,send.clone(),&physics_data);
}
}
// wait for remaining threads to complete
println!("waiting for all threads to complete...");
for _ in 0..active_thread_count{
thread_results.push(recv.recv().unwrap());
}
println!("done.");
Ok::<_,ReplayError>(thread_results)
})?;
// tally results
#[derive(Default)]
struct Totals{
deterministic:u32,
nondeterministic:u32,
invalid:u32,
error:u32,
}
let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
match result{
Ok(Some(DeterminismResult::Deterministic))=>totals.deterministic+=1,
Ok(Some(DeterminismResult::NonDeterministic))=>totals.nondeterministic+=1,
Ok(None)=>totals.invalid+=1,
Err(_)=>totals.error+=1,
}
totals
});
println!("deterministic={deterministic}");
println!("nondeterministic={nondeterministic}");
println!("invalid={invalid}");
println!("error={error}");
assert!(nondeterministic==0);
assert!(invalid==0);
assert!(error==0);
Ok(())
}

View File

@ -240,7 +240,7 @@ impl PartialMap2{
.enumerate().map(|(new_texture_id,(old_texture_id,texture))|{ .enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32))) (texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
}).unzip(); }).unzip();
let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{ let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{
//this may generate duplicate no-texture render configs but idc //this may generate duplicate no-texture render configs but idc
render_config.texture=render_config.texture.and_then(|texture_id| render_config.texture=render_config.texture.and_then(|texture_id|
texture_id_map.get(&texture_id).copied() texture_id_map.get(&texture_id).copied()

View File

@ -1,19 +1,10 @@
use crate::integer::Time; use crate::integer::Time;
#[derive(Clone,Debug)] #[derive(Debug)]
pub struct TimedInstruction<I,T>{ pub struct TimedInstruction<I,T>{
pub time:Time<T>, pub time:Time<T>,
pub instruction:I, pub instruction:I,
} }
impl<I,T> TimedInstruction<I,T>{
#[inline]
pub fn set_time<TimeInner>(self,new_time:Time<TimeInner>)->TimedInstruction<I,TimeInner>{
TimedInstruction{
time:new_time,
instruction:self.instruction,
}
}
}
/// Ensure all emitted instructions are processed before consuming external instructions /// Ensure all emitted instructions are processed before consuming external instructions
pub trait InstructionEmitter<I>{ pub trait InstructionEmitter<I>{
@ -30,7 +21,6 @@ pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+Instruction
where where
Time<T>:Copy, Time<T>:Copy,
{ {
#[inline]
fn process_exhaustive(&mut self,time_limit:Time<T>){ fn process_exhaustive(&mut self,time_limit:Time<T>){
while let Some(instruction)=self.next_instruction(time_limit){ while let Some(instruction)=self.next_instruction(time_limit){
self.process_instruction(instruction); self.process_instruction(instruction);
@ -43,6 +33,42 @@ impl<I,T,X> InstructionFeedback<I,T> for X
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>, X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
{} {}
pub struct InstructionCache<S,I,T>{
instruction_machine:S,
cached_instruction:Option<TimedInstruction<I,T>>,
time_limit:Time<T>,
}
impl<S,I,T> InstructionCache<S,I,T>
where
Time<T>:Copy+Ord,
Option<TimedInstruction<I,T>>:Clone,
S:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
{
pub fn new(
instruction_machine:S,
)->Self{
Self{
instruction_machine,
cached_instruction:None,
time_limit:Time::MIN,
}
}
pub fn next_instruction_cached(&mut self,time_limit:Time<T>)->Option<TimedInstruction<I,T>>{
if time_limit<self.time_limit{
return self.cached_instruction.clone();
}
let next_instruction=self.instruction_machine.next_instruction(time_limit);
self.cached_instruction=next_instruction.clone();
self.time_limit=time_limit;
next_instruction
}
pub fn process_instruction(&mut self,instruction:TimedInstruction<I,T>){
// invalidate cache
self.time_limit=Time::MIN;
self.instruction_machine.process_instruction(instruction);
}
}
//PROPER PRIVATE FIELDS!!! //PROPER PRIVATE FIELDS!!!
pub struct InstructionCollector<I,T>{ pub struct InstructionCollector<I,T>{
time:Time<T>, time:Time<T>,
@ -51,7 +77,6 @@ pub struct InstructionCollector<I,T>{
impl<I,T> InstructionCollector<I,T> impl<I,T> InstructionCollector<I,T>
where Time<T>:Copy+PartialOrd, where Time<T>:Copy+PartialOrd,
{ {
#[inline]
pub const fn new(time:Time<T>)->Self{ pub const fn new(time:Time<T>)->Self{
Self{ Self{
time, time,
@ -62,21 +87,25 @@ impl<I,T> InstructionCollector<I,T>
pub const fn time(&self)->Time<T>{ pub const fn time(&self)->Time<T>{
self.time self.time
} }
#[inline]
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){ pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
if let Some(ins)=instruction{ match instruction{
if ins.time<self.time{ Some(unwrap_instruction)=>{
self.time=ins.time; if unwrap_instruction.time<self.time {
self.instruction=Some(ins.instruction); self.time=unwrap_instruction.time;
self.instruction=Some(unwrap_instruction.instruction);
}
},
None=>(),
} }
} }
} pub fn instruction(self)->Option<TimedInstruction<I,T>>{
#[inline]
pub fn take(self)->Option<TimedInstruction<I,T>>{
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR //STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
self.instruction.map(|instruction|TimedInstruction{ match self.instruction{
Some(instruction)=>Some(TimedInstruction{
time:self.time, time:self.time,
instruction instruction
}) }),
None=>None,
}
} }
} }

View File

@ -14,7 +14,6 @@ impl<T> Time<T>{
pub const MIN:Self=Self::raw(i64::MIN); pub const MIN:Self=Self::raw(i64::MIN);
pub const MAX:Self=Self::raw(i64::MAX); pub const MAX:Self=Self::raw(i64::MAX);
pub const ZERO:Self=Self::raw(0); pub const ZERO:Self=Self::raw(0);
pub const EPSILON:Self=Self::raw(1);
pub const ONE_SECOND:Self=Self::raw(1_000_000_000); pub const ONE_SECOND:Self=Self::raw(1_000_000_000);
pub const ONE_MILLISECOND:Self=Self::raw(1_000_000); pub const ONE_MILLISECOND:Self=Self::raw(1_000_000);
pub const ONE_MICROSECOND:Self=Self::raw(1_000); pub const ONE_MICROSECOND:Self=Self::raw(1_000);

View File

@ -1,5 +1,4 @@
use crate::mouse::MouseState; use crate::mouse::MouseState;
use crate::gameplay_modes::{ModeId,StageId};
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum TimeInner{} pub enum TimeInner{}
@ -8,14 +7,16 @@ pub type Time=crate::integer::Time<TimeInner>;
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub enum Instruction{ pub enum Instruction{
Mouse(MouseInstruction), Mouse(MouseInstruction),
SetControl(SetControlInstruction), Other(OtherInstruction),
Mode(ModeInstruction),
Misc(MiscInstruction),
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
Idle,
} }
impl Instruction{ impl Instruction{
pub const IDLE:Self=Self::Idle; pub const IDLE:Self=Self::Other(OtherInstruction::Other(OtherOtherInstruction::Idle));
}
#[derive(Clone,Debug)]
pub enum OtherInstruction{
SetControl(SetControlInstruction),
Mode(ModeInstruction),
Other(OtherOtherInstruction),
} }
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub enum MouseInstruction{ pub enum MouseInstruction{
@ -43,14 +44,15 @@ pub enum ModeInstruction{
/// This forgets all inputs and settings which need to be reapplied. /// This forgets all inputs and settings which need to be reapplied.
Reset, Reset,
/// Restart: Teleport to the start zone. /// Restart: Teleport to the start zone.
/// This runs when you press R or teleport to a bonus Restart,
Restart(ModeId),
/// Spawn: Teleport to a specific mode's spawn /// Spawn: Teleport to a specific mode's spawn
/// This runs when the map loads to put you at the map lobby /// Sets current mode & spawn
Spawn(ModeId,StageId), Spawn(crate::gameplay_modes::ModeId,crate::gameplay_modes::StageId),
} }
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub enum MiscInstruction{ pub enum OtherOtherInstruction{
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
Idle,
PracticeFly, PracticeFly,
SetSensitivity(crate::integer::Ratio64Vec2), SetSensitivity(crate::integer::Ratio64Vec2),
} }

View File

@ -76,7 +76,7 @@ impl Run{
match &self.state{ match &self.state{
RunState::Created=>Time::ZERO, RunState::Created=>Time::ZERO,
RunState::Started{timer}=>timer.time(time), RunState::Started{timer}=>timer.time(time),
RunState::Finished{timer}=>timer.time(), RunState::Finished{timer}=>timer.time(time),
} }
} }
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{ pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
@ -110,10 +110,4 @@ impl Run{
self.flagged=Some(flag_reason); self.flagged=Some(flag_reason);
} }
} }
pub fn get_finish_time(&self)->Option<Time>{
match &self.state{
RunState::Finished{timer}=>Some(timer.time()),
_=>None,
}
}
} }

View File

@ -23,7 +23,7 @@ impl PauseState for Unpaused{
} }
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum Inner{} enum Inner{}
type InnerTime=Time<Inner>; type InnerTime=Time<Inner>;
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug)]
@ -157,7 +157,7 @@ impl<T:TimerState> TimerFixed<T,Paused>
where Time<T::In>:Copy, where Time<T::In>:Copy,
{ {
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{ pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
let new_time=self.time(); let new_time=self.time(time);
let mut timer=TimerFixed{ let mut timer=TimerFixed{
state:self.state, state:self.state,
_paused:Unpaused, _paused:Unpaused,
@ -165,9 +165,6 @@ impl<T:TimerState> TimerFixed<T,Paused>
timer.set_time(time,new_time); timer.set_time(time,new_time);
timer timer
} }
pub fn time(&self)->Time<T::Out>{
self.state.get_offset().coerce()
}
} }
impl<T:TimerState> TimerFixed<T,Unpaused> impl<T:TimerState> TimerFixed<T,Unpaused>
where Time<T::In>:Copy, where Time<T::In>:Copy,
@ -181,9 +178,6 @@ impl<T:TimerState> TimerFixed<T,Unpaused>
timer.set_time(time,new_time); timer.set_time(time,new_time);
timer timer
} }
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
self.state.get_time(time)
}
} }
//the new constructor and time queries are generic across both //the new constructor and time queries are generic across both
@ -205,6 +199,12 @@ impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
pub fn into_state(self)->T{ pub fn into_state(self)->T{
self.state self.state
} }
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
match P::IS_PAUSED{
true=>self.state.get_offset().coerce(),
false=>self.state.get_time(time),
}
}
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){ pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
match P::IS_PAUSED{ match P::IS_PAUSED{
true=>self.state.set_offset(new_time.coerce()), true=>self.state.set_offset(new_time.coerce()),
@ -256,7 +256,7 @@ impl<T:TimerState> Timer<T>
} }
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{ pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
match self{ match self{
Self::Paused(timer)=>timer.time(), Self::Paused(timer)=>timer.time(time),
Self::Unpaused(timer)=>timer.time(time), Self::Unpaused(timer)=>timer.time(time),
} }
} }
@ -329,7 +329,7 @@ mod test{
//create a paused timer that reads 0s //create a paused timer that reads 0s
let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0))); let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0)));
//the paused timer at 1 second should read 0s //the paused timer at 1 second should read 0s
assert_eq!(timer.time(),sec!(0)); assert_eq!(timer.time(sec!(1)),sec!(0));
//unpause it after one second //unpause it after one second
let timer=timer.into_unpaused(sec!(1)); let timer=timer.into_unpaused(sec!(1));
@ -339,7 +339,7 @@ mod test{
//pause the timer after 11 seconds //pause the timer after 11 seconds
let timer=timer.into_paused(sec!(11)); let timer=timer.into_paused(sec!(11));
//the paused timer at 20 seconds should read 5s //the paused timer at 20 seconds should read 5s
assert_eq!(timer.time(),sec!(5)); assert_eq!(timer.time(sec!(20)),sec!(5));
} }
#[test] #[test]
fn test_timer()->Result<(),Error>{ fn test_timer()->Result<(),Error>{

View File

@ -1,5 +1,3 @@
// This whole thing should be a drive macro
pub trait Updatable<Updater>{ pub trait Updatable<Updater>{
fn update(&mut self,update:Updater); fn update(&mut self,update:Updater);
} }
@ -55,3 +53,4 @@ impl Updatable<OuterUpdate> for Outer{
} }
} }
} }
//*/

View File

@ -12,8 +12,8 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[features] [features]
default = ["legacy"] default = ["legacy"]
legacy = ["dep:url","dep:vbsp"] legacy = ["dep:url","dep:vbsp"]
roblox = [] #roblox = ["dep:lazy-regex"]
source = ["dep:vbsp"] #source = ["dep:vbsp"]
[dependencies] [dependencies]
strafesnet_common = { path = "../common", registry = "strafesnet" } strafesnet_common = { path = "../common", registry = "strafesnet" }

View File

@ -1,6 +1,6 @@
use bnum::{BInt,cast::As}; use bnum::{BInt,cast::As};
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,Eq,PartialOrd,Ord)] #[derive(Clone,Copy,Debug,Default,Hash,PartialEq,PartialOrd,Ord)]
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled) /// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
/// N is the number of u64s to use /// N is the number of u64s to use
/// F is the number of fractional bits (always N*32 lol) /// F is the number of fractional bits (always N*32 lol)
@ -97,6 +97,7 @@ where
self.bits.eq(&other.into()) self.bits.eq(&other.into())
} }
} }
impl<const N:usize,const F:usize> Eq for Fixed<N,F>{}
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F> impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
where where

View File

@ -57,7 +57,7 @@ fn from_f32(){
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
//I32F32::MIN hits a special case since it's not representable as a positive signed integer //I32F32::MIN hits a special case since it's not representable as a positive signed integer
//TODO: don't return an overflow because this is technically possible //TODO: don't return an overflow because this is technically possible
let _a=I32F32::MIN; let a=I32F32::MIN;
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into(); let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow)); assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
//16 is within the 24 bits of float precision //16 is within the 24 bits of float precision

View File

@ -42,7 +42,7 @@ pub struct Place{
services:roblox_emulator::context::Services, services:roblox_emulator::context::Services,
} }
impl Place{ impl Place{
pub fn new(dom:WeakDom)->Option<Self>{ fn new(dom:WeakDom)->Option<Self>{
let context=roblox_emulator::context::Context::from_ref(&dom); let context=roblox_emulator::context::Context::from_ref(&dom);
Some(Self{ Some(Self{
services:context.find_services()?, services:context.find_services()?,

View File

@ -3,7 +3,6 @@ use std::collections::HashMap;
use rbx_mesh::mesh::{Vertex2, Vertex2Truncated}; use rbx_mesh::mesh::{Vertex2, Vertex2Truncated};
use strafesnet_common::{integer::vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}}; use strafesnet_common::{integer::vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}};
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError), Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),

View File

@ -1,4 +1,4 @@
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId}; use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,IndexedVertexList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
use strafesnet_common::integer::{vec3,Planar64Vec3}; use strafesnet_common::integer::{vec3,Planar64Vec3};
#[derive(Debug)] #[derive(Debug)]
@ -126,6 +126,9 @@ const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
vec3::int( 0,-1, 0),//CornerWedge::Bottom vec3::int( 0,-1, 0),//CornerWedge::Bottom
vec3::int( 0, 0,-1),//CornerWedge::Front vec3::int( 0, 0,-1),//CornerWedge::Front
]; ];
pub fn unit_sphere(render:RenderConfigId)->Mesh{
unit_cube(render)
}
#[derive(Default)] #[derive(Default)]
pub struct CubeFaceDescription([Option<FaceDescription>;6]); pub struct CubeFaceDescription([Option<FaceDescription>;6]);
impl CubeFaceDescription{ impl CubeFaceDescription{
@ -146,6 +149,10 @@ pub fn unit_cube(render:RenderConfigId)->Mesh{
t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render)); t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
generate_partial_unit_cube(t) generate_partial_unit_cube(t)
} }
pub fn unit_cylinder(render:RenderConfigId)->Mesh{
//lmao
unit_cube(render)
}
#[derive(Default)] #[derive(Default)]
pub struct WedgeFaceDescription([Option<FaceDescription>;5]); pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
impl WedgeFaceDescription{ impl WedgeFaceDescription{
@ -156,15 +163,15 @@ impl WedgeFaceDescription{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
} }
} }
// pub fn unit_wedge(render:RenderConfigId)->Mesh{ pub fn unit_wedge(render:RenderConfigId)->Mesh{
// let mut t=WedgeFaceDescription::default(); let mut t=WedgeFaceDescription::default();
// t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render)); t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render)); t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render)); t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render)); t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render)); t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
// generate_partial_unit_wedge(t) generate_partial_unit_wedge(t)
// } }
#[derive(Default)] #[derive(Default)]
pub struct CornerWedgeFaceDescription([Option<FaceDescription>;5]); pub struct CornerWedgeFaceDescription([Option<FaceDescription>;5]);
impl CornerWedgeFaceDescription{ impl CornerWedgeFaceDescription{
@ -175,15 +182,15 @@ impl CornerWedgeFaceDescription{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
} }
} }
// pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{ pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
// let mut t=CornerWedgeFaceDescription::default(); let mut t=CornerWedgeFaceDescription::default();
// t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render)); t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render)); t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render)); t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render)); t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render)); t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
// generate_partial_unit_cornerwedge(t) generate_partial_unit_cornerwedge(t)
// } }
#[derive(Clone)] #[derive(Clone)]
pub struct FaceDescription{ pub struct FaceDescription{

View File

@ -130,9 +130,9 @@ impl ModesBuilder{
fn push_mode_update(&mut self,mode_id:gameplay_modes::ModeId,mode_update:gameplay_modes::ModeUpdate){ fn push_mode_update(&mut self,mode_id:gameplay_modes::ModeId,mode_update:gameplay_modes::ModeUpdate){
self.mode_updates.push((mode_id,mode_update)); self.mode_updates.push((mode_id,mode_update));
} }
// fn push_stage_update(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage_update:gameplay_modes::StageUpdate){ fn push_stage_update(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage_update:gameplay_modes::StageUpdate){
// self.stage_updates.push((mode_id,stage_id,stage_update)); self.stage_updates.push((mode_id,stage_id,stage_update));
// } }
} }
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
let mut general=attr::GeneralAttributes::default(); let mut general=attr::GeneralAttributes::default();
@ -485,6 +485,9 @@ where
continue; continue;
} }
//at this point a new model is going to be generated for sure.
let model_id=model::ModelId::new(primitive_models_deferred_attributes.len() as u32);
//TODO: also detect "CylinderMesh" etc here //TODO: also detect "CylinderMesh" etc here
let shape=match object.class.as_str(){ let shape=match object.class.as_str(){
"Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){ "Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){
@ -886,14 +889,8 @@ impl PartialMap2{
=textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,texture))|{ =textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32))) (texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
}).unzip(); }).unzip();
let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{ let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{
// This may generate duplicate no-texture render configs but idc //this may generate duplicate no-texture render configs but idc
//
// This is because some textures may not exist, so the render config
// that it points to is unique but is texture.
//
// I don't think this needs to be fixed because missing textures
// should be a conversion error anyways.
render_config.texture=render_config.texture.and_then(|texture_id| render_config.texture=render_config.texture.and_then(|texture_id|
texture_id_map.get(&texture_id).copied() texture_id_map.get(&texture_id).copied()
); );

View File

@ -1,22 +1,22 @@
macro_rules! type_from_lua_userdata{ macro_rules! type_from_lua_userdata{
($ty:ident)=>{ ($asd:ident)=>{
impl mlua::FromLua for $ty{ impl mlua::FromLua for $asd{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{ fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{ match value{
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?), mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))), other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
} }
} }
} }
}; };
} }
macro_rules! type_from_lua_userdata_lua_lifetime{ macro_rules! type_from_lua_userdata_lua_lifetime{
($ty:ident)=>{ ($asd:ident)=>{
impl mlua::FromLua for $ty<'static>{ impl mlua::FromLua for $asd<'static>{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{ fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{ match value{
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?), mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))), other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
} }
} }
} }

View File

@ -59,8 +59,8 @@ fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
match delay.classify(){ match delay.classify(){
std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?, std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?,
// cases where the number is too large to schedule // cases where the number is too large to schedule
std::num::FpCategory::Infinite std::num::FpCategory::Infinite=>return Ok(()),
|std::num::FpCategory::Normal if (u64::MAX as f64)<delay=>{ std::num::FpCategory::Normal=>if (u64::MAX as f64)<delay{
return Ok(()); return Ok(());
}, },
_=>(), _=>(),

View File

@ -1,347 +1,98 @@
use binrw::{binrw,BinReaderExt,BinWrite,BinWriterExt}; use binrw::{BinReaderExt, binrw};
use crate::newtypes;
use crate::file::BlockId;
use strafesnet_common::physics::Time;
const VERSION:u32=0;
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::TimeInner>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
InvalidHeader(binrw::Error), InvalidHeader,
InvalidSegment(binrw::Error), InvalidSegment(binrw::Error),
SegmentConvert(newtypes::integer::RatioError),
InstructionConvert(newtypes::physics::InstructionConvert),
InstructionWrite(binrw::Error),
InvalidSegmentId(SegmentId), InvalidSegmentId(SegmentId),
InvalidData(binrw::Error),
IO(std::io::Error),
File(crate::file::Error), File(crate::file::Error),
} }
// Bot files are simply the sequence of instructions that the physics received during the run.
// The instructions are partitioned into timestamped blocks for ease of streaming.
//
// Keyframe information required for efficient seeking
// is part of a different file, and is generated from this file.
/* block types /* block types
BLOCK_BOT_HEADER: BLOCK_BOT_HEADER:
// Segments are laid out in chronological order, u128 map_resource_uuid //which map is this bot running
// but block_id is not necessarily in ascending order. //don't include style info in bot header because it's in the simulation state
// //blocks are laid out in chronological order, but indices may jump around.
// This is to place the final segment close to the start of the file, u64 num_segments
// which allows the duration of the bot to be conveniently calculated
// from the first and last instruction timestamps.
//
// Use exact physics version for replay playback
// Use highest compatible physics version for verification
u32 physics_version
u32 num_segments
for _ in 0..num_segments{ for _ in 0..num_segments{
i64 time i64 time //simulation_state timestamp
u32 instruction_count u64 block_id
u32 block_id
} }
BLOCK_BOT_SEGMENT: BLOCK_BOT_SEGMENT:
// segments can potentially be losslessly compressed! //format version indicates what version of these structures to use
for _ in 0..instruction_count{ SimulationState simulation_state //SimulationState is just under ClientState which includes Play/Pause events that the simulation doesn't know about.
// TODO: delta encode as much as possible (time,mousepos) //to read, greedily decode instructions until eof
i64 time loop{
physics::Instruction instruction //delta encode as much as possible (time,mousepos)
//strafe ticks are implied
//physics can be implied in an input-only bot file
TimedInstruction<SimulationInstruction> instruction
} }
*/ */
#[binrw] //error hiding mock code
mod simulation{
#[super::binrw]
#[brw(little)] #[brw(little)]
struct SegmentHeader{ pub struct State{}
time:i64, #[super::binrw]
instruction_count:u32,
block_id:BlockId,
}
#[binrw]
#[brw(little)] #[brw(little)]
struct Header{ pub struct Instruction{}
physics_version:u32,
num_segments:u32,
#[br(count=num_segments)]
segments:Vec<SegmentHeader>,
} }
// mod instruction{
// #[super::binrw]
// #[brw(little)]
// pub struct TimedInstruction<Instruction:binrw::BinRead+binrw::BinWrite>{
// time:u64,
// instruction:Instruction
// }
// }
// mod timeline{
// #[super::binrw]
// #[brw(little)]
// pub struct Timeline<Instruction:binrw::BinRead+binrw::BinWrite>{
// #[bw(try_calc(u32::try_from(instructions.len())))]
// instruction_count:u32,
// #[br(count=instruction_count)]
// instructions:Vec<super::instruction::TimedInstruction<Instruction>>
// }
// }
//serious code
#[binrw] #[binrw]
#[brw(little)] #[brw(little)]
#[derive(Clone,Copy,Debug,id::Id)] #[derive(Clone,Copy,Debug,id::Id)]
pub struct SegmentId(u32); pub struct SegmentId(u32);
#[binrw]
#[brw(little)]
pub struct Segment{ pub struct Segment{
pub instructions:Vec<TimedPhysicsInstruction> state:simulation::State,
} //#[bw(try_calc(u32::try_from(instructions.len())))]
//instruction_count:u32,
//#[br(count=instruction_count)]
//instructions:Vec<instruction::TimedInstruction<simulation::Instruction>>
#[derive(Clone,Copy,Debug)] //please remember that strafe ticks are implicit! 33% smaller bot files
pub struct SegmentInfo{
/// time of the first instruction in this segment.
time:Time,
instruction_count:u32,
/// How many total instructions in segments up to and including this segment
/// Alternatively, the id of the first instruction be in the _next_ segment
instructions_subtotal:u64,
block_id:BlockId,
} }
pub struct StreamableBot<R:BinReaderExt>{ pub struct StreamableBot<R:BinReaderExt>{
file:crate::file::File<R>, file:crate::file::File<R>,
segment_map:Vec<SegmentInfo>, //timeline:timeline::Timeline<SegmentId>,
segment_id_to_block_id:Vec<crate::file::BlockId>,
} }
impl<R:BinReaderExt> StreamableBot<R>{ impl<R:BinReaderExt> StreamableBot<R>{
pub(crate) fn new(mut file:crate::file::File<R>)->Result<Self,Error>{ pub(crate) fn new(file:crate::file::File<R>)->Result<Self,Error>{
//assume the file seek is in the right place to start reading header Err(Error::InvalidHeader)
let header:Header=file.data_mut().read_le().map_err(Error::InvalidHeader)?;
let mut instructions_subtotal=0;
let segment_map=header.segments.into_iter().map(|SegmentHeader{time,instruction_count,block_id}|{
instructions_subtotal+=instruction_count as u64;
SegmentInfo{
time:Time::raw(time),
instruction_count,
instructions_subtotal,
block_id,
}
}).collect();
Ok(Self{
file,
segment_map,
})
}
fn get_segment_info(&self,segment_id:SegmentId)->Result<SegmentInfo,Error>{
Ok(*self.segment_map.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?)
}
pub fn find_segments_instruction_range(&self,start_instruction:u64,end_instruction:u64)->&[SegmentInfo]{
let start=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<start_instruction);
let end=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<end_instruction);
&self.segment_map[start..=end]
}
// pub fn find_segments_time_range(&self,start_time:Time,end_time:Time)->&[SegmentInfo]{
// // TODO: This is off by one, both should be one less
// let start=self.segment_map.partition_point(|segment_info|segment_info.time<start_time);
// let end=self.segment_map.partition_point(|segment_info|segment_info.time<end_time);
// &self.segment_map[start..=end]
// }
fn append_to_segment(&mut self,segment_info:SegmentInfo,segment:&mut Segment)->Result<(),Error>{
let mut block=self.file.block_reader(segment_info.block_id).map_err(Error::File)?;
for _ in 0..segment_info.instruction_count{
let instruction:newtypes::physics::TimedInstruction=block.read_le().map_err(Error::InvalidSegment)?;
segment.instructions.push(instruction.try_into().map_err(Error::SegmentConvert)?);
}
Ok(())
}
pub fn load_segment(&mut self,segment_info:SegmentInfo)->Result<Segment,Error>{
let mut segment=Segment{
instructions:Vec::with_capacity(segment_info.instruction_count as usize),
};
self.append_to_segment(segment_info,&mut segment)?;
Ok(segment)
}
pub fn read_all(&mut self)->Result<Segment,Error>{
let mut segment=Segment{
instructions:Vec::new(),
};
for i in 0..self.segment_map.len(){
let segment_info=self.segment_map[i];
self.append_to_segment(segment_info,&mut segment)?;
} }
pub fn load_segment(&mut self,segment_id:SegmentId)->Result<Segment,Error>{
let block_id=*self.segment_id_to_block_id.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?;
let mut block=self.file.block_reader(block_id).map_err(Error::File)?;
let segment=block.read_le().map_err(Error::InvalidSegment)?;
Ok(segment) Ok(segment)
} }
} }
const MAX_BLOCK_SIZE:usize=64*1024;//64 kB
pub fn write_bot<W:BinWriterExt>(mut writer:W,physics_version:u32,instructions:impl IntoIterator<Item=TimedPhysicsInstruction>)->Result<(),Error>{
// decide which instructions to put in which segment
// write segment 1 to block 1
// write segment N to block 2
// write rest of segments
// 1 2 3 4 5
// becomes
// [1 5] 2 3 4
struct SegmentHeaderInfo{
time:Time,
instruction_count:u32,
range:core::ops::Range<usize>
}
let mut segment_header_infos=Vec::new();
let mut raw_segments=std::io::Cursor::new(Vec::new());
// block info
let mut start_time=Time::ZERO;
let mut start_position=raw_segments.position() as usize;
let mut instruction_count=0;
let mut last_position=start_position;
let mut iter=instructions.into_iter();
macro_rules! collect_instruction{
($instruction:expr)=>{
let time=$instruction.time;
let instruction_writable:newtypes::physics::TimedInstruction=$instruction.try_into().map_err(Error::InstructionConvert)?;
instruction_writable.write_le(&mut raw_segments).map_err(Error::InstructionWrite)?;
instruction_count+=1;
let position=raw_segments.position() as usize;
// exceeds max block size
if MAX_BLOCK_SIZE<position-last_position{
segment_header_infos.push(SegmentHeaderInfo{
time:start_time,
instruction_count,
range:start_position..last_position,
});
start_position=last_position;
instruction_count=0;
start_time=time;
}
last_position=position;
}
}
// unroll one loop iteration to grab the starting time
if let Some(instruction)=iter.next(){
start_time=instruction.time;
collect_instruction!(instruction);
}
for instruction in iter{
collect_instruction!(instruction);
}
//last block, whatever size it happens to be
{
let final_position=raw_segments.position() as usize;
segment_header_infos.push(SegmentHeaderInfo{
time:start_time,
instruction_count,
range:start_position..final_position,
});
}
// drop cursor
let raw_segments=raw_segments.into_inner();
let num_segments=segment_header_infos.len();
// segments list is in chronological order
let make_segment_header=|block_id,&SegmentHeaderInfo{time,instruction_count,range:ref _range}|SegmentHeader{
time:time.get(),
instruction_count,
block_id,
};
let segments=if 2<num_segments{
let mut segments=Vec::with_capacity(num_segments);
// segment 1 is second block
if let Some(seg)=segment_header_infos.first(){
segments.push(make_segment_header(BlockId::new(1),seg));
}
// rest of segments start at fourth block
for (i,seg) in segment_header_infos[1..num_segments-1].iter().enumerate(){
make_segment_header(BlockId::new(3+i as u32),seg);
}
// last segment is third block
if let Some(seg)=segment_header_infos.last(){
segments.push(make_segment_header(BlockId::new(2),seg));
}
segments
}else{
// all segments in order
segment_header_infos.iter().enumerate().map(|(i,seg)|
make_segment_header(BlockId::new(1+i as u32),seg)
).collect()
};
let header=Header{
physics_version,
num_segments:num_segments as u32,
segments,
};
// map header is +1
let block_count=1+num_segments as u32;
let mut offset=crate::file::Header::calculate_size(block_count) as u64;
// block_location is one longer than block_count
let mut block_location=Vec::with_capacity(1+block_count as usize);
//probe header length
let mut bot_header_data=Vec::new();
binrw::BinWrite::write_le(&header,&mut std::io::Cursor::new(&mut bot_header_data)).map_err(Error::InvalidData)?;
// the first block location is the map header
block_location.push(offset);
offset+=bot_header_data.len() as u64;
block_location.push(offset);
// priming includes file header + first 3 blocks [bot header, first segment, last segment]
let priming=if 2<num_segments{
// segment 1 is block 2
if let Some(seg)=segment_header_infos.first(){
offset+=seg.range.len() as u64;
block_location.push(offset);
}
// last segment is block 3
if let Some(seg)=segment_header_infos.last(){
offset+=seg.range.len() as u64;
block_location.push(offset);
}
let priming=offset;
// rest of segments
for seg in &segment_header_infos[1..num_segments-1]{
offset+=seg.range.len() as u64;
block_location.push(offset);
}
priming
}else{
// all segments in order
for seg in &segment_header_infos{
offset+=seg.range.len() as u64;
block_location.push(offset);
}
offset
};
let file_header=crate::file::Header{
fourcc:crate::file::FourCC::Bot,
version:VERSION,
priming,
resource:0,
block_count,
block_location,
};
// write file header
writer.write_le(&file_header).map_err(Error::InvalidData)?;
// write bot header
writer.write(&bot_header_data).map_err(Error::IO)?;
// write blocks
if 2<num_segments{
// segment 1 is block 2
if let Some(seg)=segment_header_infos.first(){
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
}
// last segment is block 3
if let Some(seg)=segment_header_infos.last(){
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
}
// rest of segments
for seg in &segment_header_infos[1..num_segments-1]{
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
}
}else{
// all segments in order
for seg in segment_header_infos{
writer.write(&raw_segments[seg.range]).map_err(Error::IO)?;
}
}
Ok(())
}

View File

@ -73,16 +73,6 @@ pub struct Header{
#[br(count=block_count+1)] #[br(count=block_count+1)]
pub block_location:Vec<u64>, pub block_location:Vec<u64>,
} }
impl Header{
pub const fn calculate_size(block_count:u32)->usize{
4 // fourcc
+4 // version
+8 // priming
+16 // resource
+4 // block_count
+(block_count as usize+1)*8 // block_location
}
}
#[binrw] #[binrw]
#[brw(little)] #[brw(little)]

View File

@ -86,7 +86,6 @@ for model_id in 0..num_models{
//if you hash the resource itself and set the first 8 bits to this, that's the resource uuid //if you hash the resource itself and set the first 8 bits to this, that's the resource uuid
#[binrw] #[binrw]
#[brw(little,repr=u8)] #[brw(little,repr=u8)]
#[repr(u8)]
enum ResourceType{ enum ResourceType{
Mesh, Mesh,
Texture, Texture,
@ -95,6 +94,21 @@ enum ResourceType{
//Video, //Video,
//Animation, //Animation,
} }
const RESOURCE_TYPE_VARIANT_COUNT:u8=2;
#[binrw]
#[brw(little)]
struct ResourceId(u128);
impl ResourceId{
fn resource_type(&self)->Option<ResourceType>{
let discriminant=self.0 as u8;
//TODO: use this when it is stabilized https://github.com/rust-lang/rust/issues/73662
//if (discriminant as usize)<std::mem::variant_count::<ResourceType>(){
match discriminant<RESOURCE_TYPE_VARIANT_COUNT{
true=>Some(unsafe{std::mem::transmute::<u8,ResourceType>(discriminant)}),
false=>None,
}
}
}
struct ResourceMap<T>{ struct ResourceMap<T>{
meshes:HashMap<strafesnet_common::model::MeshId,T>, meshes:HashMap<strafesnet_common::model::MeshId,T>,
@ -121,6 +135,11 @@ struct ResourceBlockHeader{
resource:ResourceType, resource:ResourceType,
id:BlockId, id:BlockId,
} }
#[binrw]
#[brw(little)]
struct ResourceExternalHeader{
resource_uuid:ResourceId,
}
#[binrw] #[binrw]
#[brw(little)] #[brw(little)]
@ -392,26 +411,28 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
attributes:map.attributes.into_iter().map(Into::into).collect(), attributes:map.attributes.into_iter().map(Into::into).collect(),
render_configs:map.render_configs.into_iter().map(Into::into).collect(), render_configs:map.render_configs.into_iter().map(Into::into).collect(),
}; };
//probe header length let mut file_header=crate::file::Header{
let mut map_header_data=Vec::new();
binrw::BinWrite::write_le(&map_header,&mut std::io::Cursor::new(&mut map_header_data)).map_err(Error::InvalidData)?;
// calculate final file header
let mut offset=crate::file::Header::calculate_size(block_count) as u64;
offset+=map_header_data.len() as u64;
// priming includes map header
let priming=offset;
for position in &mut block_location{
*position+=offset;
}
let file_header=crate::file::Header{
fourcc:crate::file::FourCC::Map, fourcc:crate::file::FourCC::Map,
version:0, version:0,
priming, priming:0,
resource:0, resource:0,
block_count, block_count,
block_location, block_location,
}; };
//probe header length
let mut file_header_data=Vec::new();
binrw::BinWrite::write_le(&file_header,&mut std::io::Cursor::new(&mut file_header_data)).map_err(Error::InvalidData)?;
let mut map_header_data=Vec::new();
binrw::BinWrite::write_le(&map_header,&mut std::io::Cursor::new(&mut map_header_data)).map_err(Error::InvalidData)?;
//update file header according to probe data
let mut offset=file_header_data.len() as u64;
file_header.priming=offset;
file_header.block_location[0]=offset;
offset+=map_header_data.len() as u64;
for position in &mut file_header.block_location[1..]{
*position+=offset;
}
//write (updated) file header //write (updated) file header
writer.write_le(&file_header).map_err(Error::InvalidData)?; writer.write_le(&file_header).map_err(Error::InvalidData)?;

View File

@ -1,9 +1,7 @@
mod common; mod common;
pub mod aabb; pub mod aabb;
pub mod model; pub mod model;
pub mod mouse;
pub mod integer; pub mod integer;
pub mod physics;
pub mod gameplay_modes; pub mod gameplay_modes;
pub mod gameplay_style; pub mod gameplay_style;
pub mod gameplay_attributes; pub mod gameplay_attributes;

View File

@ -1,9 +1,3 @@
pub const fn flag(b:bool,mask:u8)->u8{ pub const fn flag(b:bool,mask:u8)->u8{
(-(b as i8) as u8)&mask (-(b as i8) as u8)&mask
} }
pub fn bool_from_u8(value:u8)->bool{
value!=0
}
pub fn bool_into_u8(value:&bool)->u8{
*value as u8
}

View File

@ -38,23 +38,6 @@ pub struct Ratio64Vec2{
pub x:Ratio64, pub x:Ratio64,
pub y:Ratio64, pub y:Ratio64,
} }
impl TryInto<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
type Error=RatioError;
fn try_into(self)->Result<strafesnet_common::integer::Ratio64Vec2,Self::Error>{
Ok(strafesnet_common::integer::Ratio64Vec2{
x:self.x.try_into()?,
y:self.y.try_into()?,
})
}
}
impl From<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
fn from(value:strafesnet_common::integer::Ratio64Vec2)->Self{
Self{
x:value.x.into(),
y:value.y.into(),
}
}
}
pub type Angle32=i32; pub type Angle32=i32;
pub type Planar64=i64; pub type Planar64=i64;

View File

@ -1,25 +0,0 @@
use super::integer::Time;
#[binrw::binrw]
#[brw(little)]
pub struct MouseState{
pub pos:[i32;2],
pub time:Time,
}
impl<T> Into<strafesnet_common::mouse::MouseState<T>> for MouseState{
fn into(self)->strafesnet_common::mouse::MouseState<T>{
strafesnet_common::mouse::MouseState{
pos:self.pos.into(),
time:strafesnet_common::integer::Time::raw(self.time),
}
}
}
impl<T> From<strafesnet_common::mouse::MouseState<T>> for MouseState{
fn from(value:strafesnet_common::mouse::MouseState<T>)->Self{
Self{
pos:value.pos.to_array(),
time:value.time.get(),
}
}
}

View File

@ -1,156 +0,0 @@
use super::integer::Time;
use super::common::{bool_from_u8,bool_into_u8};
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::TimeInner>;
#[binrw::binrw]
#[brw(little)]
pub struct TimedInstruction{
pub time:Time,
pub instruction:Instruction,
}
impl TryInto<TimedPhysicsInstruction> for TimedInstruction{
type Error=super::integer::RatioError;
fn try_into(self)->Result<TimedPhysicsInstruction,Self::Error>{
Ok(strafesnet_common::instruction::TimedInstruction{
time:strafesnet_common::integer::Time::raw(self.time),
instruction:self.instruction.try_into()?,
})
}
}
impl TryFrom<TimedPhysicsInstruction> for TimedInstruction{
type Error=super::physics::InstructionConvert;
fn try_from(value:TimedPhysicsInstruction)->Result<Self,Self::Error>{
Ok(Self{
time:value.time.get(),
instruction:value.instruction.try_into()?,
})
}
}
#[binrw::binrw]
#[brw(little)]
pub enum Instruction{
#[brw(magic=0u8)]
ReplaceMouse{
m0:super::mouse::MouseState,
m1:super::mouse::MouseState
},
#[brw(magic=1u8)]
SetNextMouse(super::mouse::MouseState),
#[brw(magic=2u8)]
SetMoveRight(
#[br(map=bool_from_u8)]
#[bw(map=bool_into_u8)]
bool),
#[brw(magic=3u8)]
SetMoveUp(
#[br(map=bool_from_u8)]
#[bw(map=bool_into_u8)]
bool),
#[brw(magic=4u8)]
SetMoveBack(
#[br(map=bool_from_u8)]
#[bw(map=bool_into_u8)]
bool),
#[brw(magic=5u8)]
SetMoveLeft(
#[br(map=bool_from_u8)]
#[bw(map=bool_into_u8)]
bool),
#[brw(magic=6u8)]
SetMoveDown(
#[br(map=bool_from_u8)]
#[bw(map=bool_into_u8)]
bool),
#[brw(magic=7u8)]
SetMoveForward(
#[br(map=bool_from_u8)]
#[bw(map=bool_into_u8)]
bool),
#[brw(magic=8u8)]
SetJump(
#[br(map=bool_from_u8)]
#[bw(map=bool_into_u8)]
bool),
#[brw(magic=9u8)]
SetZoom(
#[br(map=bool_from_u8)]
#[bw(map=bool_into_u8)]
bool),
#[brw(magic=10u8)]
Reset,
#[brw(magic=11u8)]
Restart(super::gameplay_modes::ModeId),
#[brw(magic=12u8)]
Spawn(super::gameplay_modes::ModeId,super::gameplay_modes::StageId),
#[brw(magic=13u8)]
PracticeFly,
#[brw(magic=14u8)]
SetSensitivity(super::integer::Ratio64Vec2),
#[brw(magic=255u8)]
Idle,
}
#[derive(Debug)]
pub enum InstructionConvert{
/// This is an instruction that can be dropped when serializing
DropInstruction,
}
impl std::fmt::Display for InstructionConvert{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for InstructionConvert{}
impl TryInto<strafesnet_common::physics::Instruction> for Instruction{
type Error=super::integer::RatioError;
fn try_into(self)->Result<strafesnet_common::physics::Instruction,Self::Error>{
Ok(match self{
Instruction::ReplaceMouse{m0,m1}=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
Instruction::SetNextMouse(m)=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m.into())),
Instruction::SetMoveRight(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state.into())),
Instruction::SetMoveUp(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state.into())),
Instruction::SetMoveBack(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state.into())),
Instruction::SetMoveLeft(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state.into())),
Instruction::SetMoveDown(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state.into())),
Instruction::SetMoveForward(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state.into())),
Instruction::SetJump(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state.into())),
Instruction::SetZoom(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state.into())),
Instruction::Reset=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset),
Instruction::Restart(mode_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(strafesnet_common::gameplay_modes::ModeId::new(mode_id))),
Instruction::Spawn(mode_id,stage_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(
strafesnet_common::gameplay_modes::ModeId::new(mode_id),
strafesnet_common::gameplay_modes::StageId::new(stage_id),
)),
Instruction::PracticeFly=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly),
Instruction::SetSensitivity(sensitivity)=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity.try_into()?)),
Instruction::Idle=>strafesnet_common::physics::Instruction::Idle,
})
}
}
impl TryFrom<strafesnet_common::physics::Instruction> for Instruction{
type Error=InstructionConvert;
fn try_from(value:strafesnet_common::physics::Instruction)->Result<Self,Self::Error>{
match value{
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0,m1})=>Ok(Instruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m))=>Ok(Instruction::SetNextMouse(m.into())),
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state))=>Ok(Instruction::SetMoveRight(state.into())),
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state))=>Ok(Instruction::SetMoveUp(state.into())),
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state))=>Ok(Instruction::SetMoveBack(state.into())),
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state))=>Ok(Instruction::SetMoveLeft(state.into())),
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state))=>Ok(Instruction::SetMoveDown(state.into())),
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state))=>Ok(Instruction::SetMoveForward(state.into())),
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state))=>Ok(Instruction::SetJump(state.into())),
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state))=>Ok(Instruction::SetZoom(state.into())),
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset)=>Ok(Instruction::Reset),
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(mode_id))=>Ok(Instruction::Restart(mode_id.get())),
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(mode_id,stage_id))=>Ok(Instruction::Spawn(
mode_id.get(),
stage_id.get(),
)),
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly)=>Ok(Instruction::PracticeFly),
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity))=>Ok(Instruction::SetSensitivity(sensitivity.into())),
strafesnet_common::physics::Instruction::Idle=>Ok(Instruction::Idle),
}
}
}

View File

@ -1,6 +1,6 @@
[package] [package]
name = "strafe-client" name = "strafe-client"
version = "0.11.0" version = "0.10.5"
edition = "2021" edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "Custom" license = "Custom"
@ -9,24 +9,25 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
user-install=[] # as opposed to portable install
default = ["snf"] default = ["snf"]
snf = ["dep:strafesnet_snf"] snf = ["dep:strafesnet_snf"]
source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"] source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"] roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
[dependencies] [dependencies]
arrayvec = "0.7.6"
bytemuck = { version = "1.13.1", features = ["derive"] }
configparser = "3.0.2"
ddsfile = "0.5.1"
glam = "0.29.0" glam = "0.29.0"
id = { version = "0.1.0", registry = "strafesnet" }
parking_lot = "0.12.1" parking_lot = "0.12.1"
pollster = "0.4.0" pollster = "0.4.0"
replace_with = "0.1.7"
strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true } strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true }
strafesnet_common = { path = "../lib/common", registry = "strafesnet" } strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true } strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true }
strafesnet_graphics = { path = "../engine/graphics", registry = "strafesnet" }
strafesnet_physics = { path = "../engine/physics", registry = "strafesnet" }
strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", optional = true } strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", optional = true }
strafesnet_session = { path = "../engine/session", registry = "strafesnet" }
strafesnet_settings = { path = "../engine/settings", registry = "strafesnet" }
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true } strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
wgpu = "24.0.0" wgpu = "23.0.1"
winit = "0.30.7" winit = "0.30.7"

View File

@ -1,68 +0,0 @@
use crate::window::Instruction;
use strafesnet_common::integer;
use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::session::TimeInner as SessionTimeInner;
pub struct App<'a>{
root_time:std::time::Instant,
window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>,
}
impl<'a> App<'a>{
pub fn new(
root_time:std::time::Instant,
window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>,
)->App<'a>{
Self{
root_time,
window_thread,
}
}
fn send_timed_instruction(&mut self,instruction:Instruction){
let time=integer::Time::from_nanos(self.root_time.elapsed().as_nanos() as i64);
self.window_thread.send(TimedInstruction{time,instruction}).unwrap();
}
}
impl winit::application::ApplicationHandler for App<'_>{
fn resumed(&mut self,_event_loop:&winit::event_loop::ActiveEventLoop){
//
}
fn window_event(
&mut self,
event_loop:&winit::event_loop::ActiveEventLoop,
_window_id:winit::window::WindowId,
event:winit::event::WindowEvent,
){
match event{
winit::event::WindowEvent::KeyboardInput{
event:winit::event::KeyEvent{
logical_key:winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
state:winit::event::ElementState::Pressed,
..
},
..
}
|winit::event::WindowEvent::CloseRequested=>{
event_loop.exit();
},
_=>(),
}
self.send_timed_instruction(Instruction::WindowEvent(event));
}
fn device_event(
&mut self,
_event_loop:&winit::event_loop::ActiveEventLoop,
_device_id:winit::event::DeviceId,
event:winit::event::DeviceEvent,
){
self.send_timed_instruction(Instruction::DeviceEvent(event));
}
fn about_to_wait(
&mut self,
_event_loop:&winit::event_loop::ActiveEventLoop
){
self.send_timed_instruction(Instruction::WindowEvent(winit::event::WindowEvent::RedrawRequested));
}
}

View File

@ -31,14 +31,6 @@ impl<T> Body<T>
time, time,
} }
} }
pub const fn relative_to<'a>(&'a self,body0:&'a Body<T>)->VirtualBody<'a,T>{
//(p0,v0,a0,t0)
//(p1,v1,a1,t1)
VirtualBody{
body0,
body1:self,
}
}
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{ pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time; let dt=time-self.time;
self.position self.position
@ -90,7 +82,7 @@ impl<T> Body<T>
// a*dt + v // a*dt + v
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
} }
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){ pub fn advance_time_ratio_dt(&mut self,dt:crate::model_physics::GigaTime){
self.position=self.extrapolated_position_ratio_dt(dt); self.position=self.extrapolated_position_ratio_dt(dt);
self.velocity=self.extrapolated_velocity_ratio_dt(dt); self.velocity=self.extrapolated_velocity_ratio_dt(dt);
self.time+=dt.into(); self.time+=dt.into();
@ -145,6 +137,14 @@ pub struct VirtualBody<'a,T>{
impl<T> VirtualBody<'_,T> impl<T> VirtualBody<'_,T>
where Time<T>:Copy, where Time<T>:Copy,
{ {
pub const fn relative<'a>(body0:&'a Body<T>,body1:&'a Body<T>)->VirtualBody<'a,T>{
//(p0,v0,a0,t0)
//(p1,v1,a1,t1)
VirtualBody{
body0,
body1,
}
}
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{ pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time) self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
} }

View File

@ -1,4 +1,4 @@
use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge}; use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge};
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3}; use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
use crate::physics::{Time,Body}; use crate::physics::{Time,Body};
@ -12,20 +12,6 @@ pub enum CrawlResult<M:MeshQuery>{
Miss(FEV<M>), Miss(FEV<M>),
Hit(M::Face,GigaTime), Hit(M::Face,GigaTime),
} }
impl<M:MeshQuery> CrawlResult<M>{
pub fn hit(self)->Option<(M::Face,GigaTime)>{
match self{
CrawlResult::Miss(_)=>None,
CrawlResult::Hit(face,time)=>Some((face,time)),
}
}
pub fn miss(self)->Option<FEV<M>>{
match self{
CrawlResult::Miss(fev)=>Some(fev),
CrawlResult::Hit(_,_)=>None,
}
}
}
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M> impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
where where

View File

@ -1,6 +1,5 @@
use std::io::Read; use std::io::Read;
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum ReadError{ pub enum ReadError{
#[cfg(feature="roblox")] #[cfg(feature="roblox")]
@ -11,8 +10,6 @@ pub enum ReadError{
StrafesNET(strafesnet_snf::Error), StrafesNET(strafesnet_snf::Error),
#[cfg(feature="snf")] #[cfg(feature="snf")]
StrafesNETMap(strafesnet_snf::map::Error), StrafesNETMap(strafesnet_snf::map::Error),
#[cfg(feature="snf")]
StrafesNETBot(strafesnet_snf::bot::Error),
Io(std::io::Error), Io(std::io::Error),
UnknownFileFormat, UnknownFileFormat,
} }
@ -23,44 +20,32 @@ impl std::fmt::Display for ReadError{
} }
impl std::error::Error for ReadError{} impl std::error::Error for ReadError{}
pub enum ReadFormat{ pub enum DataStructure{
#[cfg(feature="roblox")] #[cfg(feature="roblox")]
Roblox(strafesnet_rbx_loader::Model), Roblox(strafesnet_rbx_loader::Model),
#[cfg(feature="source")] #[cfg(feature="source")]
Source(strafesnet_bsp_loader::Bsp), Source(strafesnet_bsp_loader::Bsp),
#[cfg(feature="snf")] #[cfg(feature="snf")]
SNFM(strafesnet_common::map::CompleteMap), StrafesNET(strafesnet_common::map::CompleteMap),
#[cfg(feature="snf")]
SNFB(strafesnet_snf::bot::Segment),
} }
pub fn read<R:Read+std::io::Seek>(input:R)->Result<ReadFormat,ReadError>{ pub fn read<R:Read+std::io::Seek>(input:R)->Result<DataStructure,ReadError>{
let mut buf=std::io::BufReader::new(input); let mut buf=std::io::BufReader::new(input);
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?[0..4].to_owned(); let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
// reading the entire file is way faster than round tripping the disk constantly match &peek[0..4]{
let mut entire_file=Vec::new();
buf.read_to_end(&mut entire_file).map_err(ReadError::Io)?;
let cursor=std::io::Cursor::new(entire_file);
match peek.as_slice(){
#[cfg(feature="roblox")] #[cfg(feature="roblox")]
b"<rob"=>Ok(ReadFormat::Roblox(strafesnet_rbx_loader::read(cursor).map_err(ReadError::Roblox)?)), b"<rob"=>Ok(DataStructure::Roblox(strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?)),
#[cfg(feature="source")] #[cfg(feature="source")]
b"VBSP"=>Ok(ReadFormat::Source(strafesnet_bsp_loader::read(cursor).map_err(ReadError::Source)?)), b"VBSP"=>Ok(DataStructure::Source(strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?)),
#[cfg(feature="snf")] #[cfg(feature="snf")]
b"SNFM"=>Ok(ReadFormat::SNFM( b"SNFM"=>Ok(DataStructure::StrafesNET(
strafesnet_snf::read_map(cursor).map_err(ReadError::StrafesNET)? strafesnet_snf::read_map(buf).map_err(ReadError::StrafesNET)?
.into_complete_map().map_err(ReadError::StrafesNETMap)? .into_complete_map().map_err(ReadError::StrafesNETMap)?
)), )),
#[cfg(feature="snf")]
b"SNFB"=>Ok(ReadFormat::SNFB(
strafesnet_snf::read_bot(cursor).map_err(ReadError::StrafesNET)?
.read_all().map_err(ReadError::StrafesNETBot)?
)),
_=>Err(ReadError::UnknownFileFormat), _=>Err(ReadError::UnknownFileFormat),
} }
} }
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum LoadError{ pub enum LoadError{
ReadError(ReadError), ReadError(ReadError),
@ -74,23 +59,14 @@ impl std::fmt::Display for LoadError{
} }
impl std::error::Error for LoadError{} impl std::error::Error for LoadError{}
pub enum LoadFormat{ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::CompleteMap,LoadError>{
#[cfg(feature="snf")]
Map(strafesnet_common::map::CompleteMap),
#[cfg(feature="snf")]
Bot(strafesnet_snf::bot::Segment),
}
pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<LoadFormat,LoadError>{
//blocking because it's simpler... //blocking because it's simpler...
let file=std::fs::File::open(path).map_err(LoadError::File)?; let file=std::fs::File::open(path).map_err(LoadError::File)?;
match read(file).map_err(LoadError::ReadError)?{ match read(file).map_err(LoadError::ReadError)?{
#[cfg(feature="snf")] #[cfg(feature="snf")]
ReadFormat::SNFB(bot)=>Ok(LoadFormat::Bot(bot)), DataStructure::StrafesNET(map)=>Ok(map),
#[cfg(feature="snf")]
ReadFormat::SNFM(map)=>Ok(LoadFormat::Map(map)),
#[cfg(feature="roblox")] #[cfg(feature="roblox")]
ReadFormat::Roblox(model)=>{ DataStructure::Roblox(model)=>{
let mut place=model.into_place(); let mut place=model.into_place();
place.run_scripts(); place.run_scripts();
@ -123,10 +99,10 @@ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<LoadFormat,LoadError>{
) )
); );
Ok(LoadFormat::Map(map)) Ok(map)
}, },
#[cfg(feature="source")] #[cfg(feature="source")]
ReadFormat::Source(bsp)=>{ DataStructure::Source(bsp)=>{
let mut loader=strafesnet_deferred_loader::source_legacy(); let mut loader=strafesnet_deferred_loader::source_legacy();
let (texture_loader,mesh_loader)=loader.get_inner_mut(); let (texture_loader,mesh_loader)=loader.get_inner_mut();
@ -162,7 +138,7 @@ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<LoadFormat,LoadError>{
), ),
); );
Ok(LoadFormat::Map(map)) Ok(map)
}, },
} }
} }

View File

@ -1,15 +1,9 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{HashSet,HashMap}; use std::collections::{HashSet,HashMap};
use strafesnet_common::map; use strafesnet_common::map;
use strafesnet_settings::settings;
use strafesnet_session::session;
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId}; use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel}; use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex}; use crate::model_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
pub fn required_limits()->wgpu::Limits{
wgpu::Limits::default()
}
struct Indices{ struct Indices{
count:u32, count:u32,
@ -142,7 +136,7 @@ impl GraphicsState{
pub fn clear(&mut self){ pub fn clear(&mut self){
self.models.clear(); self.models.clear();
} }
pub fn load_user_settings(&mut self,user_settings:&settings::UserSettings){ pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){
self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2(); self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2();
} }
pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){ pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
@ -454,7 +448,7 @@ impl GraphicsState{
//.into_iter() the modeldata vec so entities can be /moved/ to models.entities //.into_iter() the modeldata vec so entities can be /moved/ to models.entities
let mut model_count=0; let mut model_count=0;
let mut instance_count=0; let mut instance_count=0;
let uniform_buffer_binding_size=required_limits().max_uniform_buffer_binding_size as usize; let uniform_buffer_binding_size=crate::setup::required_limits().max_uniform_buffer_binding_size as usize;
let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES; let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES;
self.models.reserve(models.len()); self.models.reserve(models.len());
for model in models.into_iter(){ for model in models.into_iter(){
@ -614,7 +608,7 @@ impl GraphicsState{
// Create the render pipeline // Create the render pipeline
let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{ let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
label:None, label:None,
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../../../strafe-client/src/shader.wgsl"))), source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
}); });
//load textures //load textures
@ -642,10 +636,10 @@ impl GraphicsState{
wgpu::TextureFormat::Astc{ wgpu::TextureFormat::Astc{
block:AstcBlock::B4x4, block:AstcBlock::B4x4,
channel:AstcChannel::UnormSrgb, channel:AstcChannel::UnormSrgb,
}=>&include_bytes!("../../../strafe-client/images/astc.dds")[..], }=>&include_bytes!("../images/astc.dds")[..],
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../../../strafe-client/images/etc2.dds")[..], wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../images/etc2.dds")[..],
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../../../strafe-client/images/bc1.dds")[..], wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../images/bc1.dds")[..],
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../../../strafe-client/images/bgra.dds")[..], wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../images/bgra.dds")[..],
_=>unreachable!(), _=>unreachable!(),
}; };
@ -688,7 +682,7 @@ impl GraphicsState{
//squid //squid
let squid_texture_view={ let squid_texture_view={
let bytes=include_bytes!("../../../strafe-client/images/squid.dds"); let bytes=include_bytes!("../images/squid.dds");
let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();
@ -870,7 +864,7 @@ impl GraphicsState{
&mut self, &mut self,
device:&wgpu::Device, device:&wgpu::Device,
config:&wgpu::SurfaceConfiguration, config:&wgpu::SurfaceConfiguration,
user_settings:&settings::UserSettings, user_settings:&crate::settings::UserSettings,
){ ){
self.depth_view=Self::create_depth_texture(config,device); self.depth_view=Self::create_depth_texture(config,device);
self.camera.screen_size=glam::uvec2(config.width,config.height); self.camera.screen_size=glam::uvec2(config.width,config.height);
@ -881,7 +875,7 @@ impl GraphicsState{
view:&wgpu::TextureView, view:&wgpu::TextureView,
device:&wgpu::Device, device:&wgpu::Device,
queue:&wgpu::Queue, queue:&wgpu::Queue,
frame_state:session::FrameState, frame_state:crate::session::FrameState,
){ ){
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input //TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input

View File

@ -1,11 +1,7 @@
use strafesnet_graphics::graphics;
use strafesnet_session::session;
use strafesnet_settings::settings;
pub enum Instruction{ pub enum Instruction{
Render(session::FrameState), Render(crate::session::FrameState),
//UpdateModel(graphics::GraphicsModelUpdate), //UpdateModel(crate::graphics::GraphicsModelUpdate),
Resize(winit::dpi::PhysicalSize<u32>,settings::UserSettings), Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
ChangeMap(strafesnet_common::map::CompleteMap), ChangeMap(strafesnet_common::map::CompleteMap),
} }
@ -19,7 +15,7 @@ WorkerDescription{
//up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order //up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order
pub fn new( pub fn new(
mut graphics:graphics::GraphicsState, mut graphics:crate::graphics::GraphicsState,
mut config:wgpu::SurfaceConfiguration, mut config:wgpu::SurfaceConfiguration,
surface:wgpu::Surface, surface:wgpu::Surface,
device:wgpu::Device, device:wgpu::Device,

View File

@ -1,11 +1,20 @@
mod app; mod body;
mod file; mod file;
mod setup; mod setup;
mod window; mod window;
mod worker; mod worker;
mod physics;
mod session;
mod graphics;
mod settings;
mod push_solve;
mod face_crawler;
mod compat_worker; mod compat_worker;
mod model_physics;
mod model_graphics;
mod physics_worker; mod physics_worker;
mod graphics_worker; mod graphics_worker;
mod mouse_interpolator;
const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION")); const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));

View File

@ -1,6 +1,5 @@
use std::borrow::{Borrow,Cow}; use std::borrow::{Borrow,Cow};
use std::collections::{HashSet,HashMap}; use std::collections::{HashSet,HashMap};
use core::ops::Range;
use strafesnet_common::integer::vec3::Vector3; use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::model::{self,MeshId,PolygonIter}; use strafesnet_common::model::{self,MeshId,PolygonIter};
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio}; use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
@ -85,11 +84,11 @@ pub trait MeshQuery{
} }
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3; fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset); fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
fn face_edges(&self,face_id:Self::Face)->Cow<[Self::Edge]>; fn face_edges(&self,face_id:Self::Face)->Cow<Vec<Self::Edge>>;
fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>; fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>;
fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>; fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>;
fn vert_edges(&self,vert_id:Self::Vert)->Cow<[Self::Edge]>; fn vert_edges(&self,vert_id:Self::Vert)->Cow<Vec<Self::Edge>>;
fn vert_faces(&self,vert_id:Self::Vert)->Cow<[Self::Face]>; fn vert_faces(&self,vert_id:Self::Vert)->Cow<Vec<Self::Face>>;
} }
struct FaceRefs{ struct FaceRefs{
edges:Vec<SubmeshDirectedEdgeId>, edges:Vec<SubmeshDirectedEdgeId>,
@ -280,12 +279,15 @@ struct EdgePool{
} }
impl EdgePool{ impl EdgePool{
fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){ fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){
let edge_id=*self.edge_id_from_guy.entry(edge_ref_verts.clone()).or_insert_with(||{ let edge_id=if let Some(&edge_id)=self.edge_id_from_guy.get(&edge_ref_verts){
let edge_id=SubmeshEdgeId::new(self.edge_guys.len() as u32);
self.edge_guys.push((edge_ref_verts,EdgeRefFaces::new()));
edge_id edge_id
}); }else{
(&mut self.edge_guys[edge_id.get() as usize].1,edge_id) let edge_id=SubmeshEdgeId::new(self.edge_guys.len() as u32);
self.edge_guys.push((edge_ref_verts.clone(),EdgeRefFaces::new()));
self.edge_id_from_guy.insert(edge_ref_verts,edge_id);
edge_id
};
(&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id.get() as usize)}.1,edge_id)
} }
} }
@ -320,11 +322,16 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
let mut submesh_verts=Vec::new(); let mut submesh_verts=Vec::new();
let mut submesh_vert_id_from_mesh_vert_id=HashMap::<MeshVertId,SubmeshVertId>::new(); let mut submesh_vert_id_from_mesh_vert_id=HashMap::<MeshVertId,SubmeshVertId>::new();
//lazy closure //lazy closure
let mut get_submesh_vert_id=|vert_id:MeshVertId|*submesh_vert_id_from_mesh_vert_id.entry(vert_id).or_insert_with(||{ let mut get_submesh_vert_id=|vert_id:MeshVertId|{
if let Some(&submesh_vert_id)=submesh_vert_id_from_mesh_vert_id.get(&vert_id){
submesh_vert_id
}else{
let submesh_vert_id=SubmeshVertId::new(submesh_verts.len() as u32); let submesh_vert_id=SubmeshVertId::new(submesh_verts.len() as u32);
submesh_verts.push(vert_id); submesh_verts.push(vert_id);
submesh_vert_id_from_mesh_vert_id.insert(vert_id,submesh_vert_id);
submesh_vert_id submesh_vert_id
}); }
};
let mut edge_pool=EdgePool::default(); let mut edge_pool=EdgePool::default();
let mut vert_ref_guys=vec![VertRefGuy::default();mesh.unique_pos.len()]; let mut vert_ref_guys=vec![VertRefGuy::default();mesh.unique_pos.len()];
let mut face_ref_guys=Vec::new(); let mut face_ref_guys=Vec::new();
@ -357,10 +364,10 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
edge_ref_faces.push(!is_sorted as usize,submesh_face_id); edge_ref_faces.push(!is_sorted as usize,submesh_face_id);
//index edges & face into vertices //index edges & face into vertices
{ {
let vert_ref_guy=&mut vert_ref_guys[submesh_vert0_id.get() as usize]; let vert_ref_guy=unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert0_id.get() as usize)};
vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted)); vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted));
vert_ref_guy.faces.insert(submesh_face_id); vert_ref_guy.faces.insert(submesh_face_id);
vert_ref_guys[submesh_vert1_id.get() as usize].edges.insert(edge_id.as_directed(!is_sorted)); unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert1_id.get() as usize)}.edges.insert(edge_id.as_directed(!is_sorted));
} }
//return directed_edge_id //return directed_edge_id
edge_id.as_directed(is_sorted) edge_id.as_directed(is_sorted)
@ -435,7 +442,7 @@ impl MeshQuery for PhysicsMeshView<'_>{
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize; let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
self.data.verts[vert_idx].0 self.data.verts[vert_idx].0
} }
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{ fn face_edges(&self,face_id:SubmeshFaceId)->Cow<Vec<SubmeshDirectedEdgeId>>{
Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges) Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges)
} }
fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{
@ -444,10 +451,10 @@ impl MeshQuery for PhysicsMeshView<'_>{
fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{
Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts) Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts)
} }
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{ fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshDirectedEdgeId>>{
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges) Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges)
} }
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{ fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshFaceId>>{
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces) Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces)
} }
} }
@ -513,7 +520,7 @@ impl MeshQuery for TransformedMesh<'_>{
self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1() self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1()
} }
#[inline] #[inline]
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{ fn face_edges(&self,face_id:SubmeshFaceId)->Cow<Vec<SubmeshDirectedEdgeId>>{
self.view.face_edges(face_id) self.view.face_edges(face_id)
} }
#[inline] #[inline]
@ -525,11 +532,11 @@ impl MeshQuery for TransformedMesh<'_>{
self.view.edge_verts(edge_id) self.view.edge_verts(edge_id)
} }
#[inline] #[inline]
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{ fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshDirectedEdgeId>>{
self.view.vert_edges(vert_id) self.view.vert_edges(vert_id)
} }
#[inline] #[inline]
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{ fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshFaceId>>{
self.view.vert_faces(vert_id) self.view.vert_faces(vert_id)
} }
} }
@ -705,54 +712,48 @@ impl MinkowskiMesh<'_>{
}, },
} }
} }
// TODO: fundamentally improve this algorithm. fn closest_fev_not_inside(&self,mut infinity_body:Body)->Option<FEV<MinkowskiMesh>>{
// All it needs to do is find the closest point on the mesh infinity_body.infinity_dir().map_or(None,|dir|{
// and return the FEV which the point resides on.
//
// What it actually does is use the above functions to trace a ray in from infinity,
// crawling the closest point along the mesh surface until the ray reaches
// the starting point to discover the final FEV.
//
// The actual collision prediction probably does a single test
// and then immediately returns with 0 FEV transitions on average,
// because of the strict time_limit constraint.
//
// Most of the calculation time is just calculating the starting point
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Time)->Option<FEV<MinkowskiMesh>>{
infinity_body.infinity_dir().and_then(|dir|{
let infinity_fev=self.infinity_fev(-dir,infinity_body.position); let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
//a line is simpler to solve than a parabola //a line is simpler to solve than a parabola
infinity_body.velocity=dir; infinity_body.velocity=dir;
infinity_body.acceleration=vec3::ZERO; infinity_body.acceleration=vec3::ZERO;
//crawl in from negative infinity along a tangent line to get the closest fev //crawl in from negative infinity along a tangent line to get the closest fev
// TODO: change crawl_fev args to delta time? Optional values? // TODO: change crawl_fev args to delta time? Optional values?
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,start_time).miss() match infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){
crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev),
crate::face_crawler::CrawlResult::Hit(_,_)=>None,
}
}) })
} }
pub fn predict_collision_in(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{ pub fn predict_collision_in(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
self.closest_fev_not_inside(relative_body.clone(),start_time).and_then(|fev|{ self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{
//continue forwards along the body parabola //continue forwards along the body parabola
fev.crawl(self,relative_body,start_time,time_limit).hit() match fev.crawl(self,relative_body,relative_body.time,time_limit){
crate::face_crawler::CrawlResult::Miss(_)=>None,
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
}
}) })
} }
pub fn predict_collision_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{ pub fn predict_collision_out(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
//create an extrapolated body at time_limit //create an extrapolated body at time_limit
let infinity_body=-relative_body.clone(); let infinity_body=Body::new(
self.closest_fev_not_inside(infinity_body,-time_limit).and_then(|fev|{ relative_body.extrapolated_position(time_limit),
-relative_body.extrapolated_velocity(time_limit),
relative_body.acceleration,
-time_limit,
);
self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{
//continue backwards along the body parabola //continue backwards along the body parabola
fev.crawl(self,&infinity_body,-time_limit,-start_time).hit() match fev.crawl(self,&-relative_body.clone(),-time_limit,-relative_body.time){
//no need to test -time<time_limit because of the first step crate::face_crawler::CrawlResult::Miss(_)=>None,
.map(|(face,time)|(face,-time)) crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,-time)),//no need to test -time<time_limit because of the first step
}
}) })
} }
pub fn predict_collision_face_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{ pub fn predict_collision_face_out(&self,relative_body:&Body,time_limit:Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
//no algorithm needed, there is only one state and two cases (Edge,None) //no algorithm needed, there is only one state and two cases (Edge,None)
//determine when it passes an edge ("sliding off" case) //determine when it passes an edge ("sliding off" case)
let start_time={
let r=(start_time-relative_body.time).to_ratio();
Ratio::new(r.num,r.den)
};
let mut best_time={ let mut best_time={
let r=(time_limit-relative_body.time).to_ratio(); let r=(time_limit-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4()) Ratio::new(r.num.fix_4(),r.den.fix_4())
@ -768,7 +769,7 @@ impl MinkowskiMesh<'_>{
//WARNING! d outside of *2 //WARNING! d outside of *2
//WARNING: truncated precision //WARNING: truncated precision
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){ for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){
if start_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if Ratio::new(Planar64::ZERO,Planar64::EPSILON).le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt; best_time=dt;
best_edge=Some(directed_edge_id); best_edge=Some(directed_edge_id);
break; break;
@ -779,7 +780,10 @@ impl MinkowskiMesh<'_>{
} }
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{ fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position); let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time).hit() match infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){
crate::face_crawler::CrawlResult::Miss(_)=>None,
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
}
} }
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{ pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO); let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
@ -828,7 +832,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
}, },
} }
} }
fn face_edges(&self,face_id:MinkowskiFace)->Cow<[MinkowskiDirectedEdge]>{ fn face_edges(&self,face_id:MinkowskiFace)->Cow<Vec<MinkowskiDirectedEdge>>{
match face_id{ match face_id{
MinkowskiFace::VertFace(v0,f1)=>{ MinkowskiFace::VertFace(v0,f1)=>{
Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{ Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{
@ -929,7 +933,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
}, },
} }
} }
fn vert_edges(&self,vert_id:MinkowskiVert)->Cow<[MinkowskiDirectedEdge]>{ fn vert_edges(&self,vert_id:MinkowskiVert)->Cow<Vec<MinkowskiDirectedEdge>>{
match vert_id{ match vert_id{
MinkowskiVert::VertVert(v0,v1)=>{ MinkowskiVert::VertVert(v0,v1)=>{
let mut edges=Vec::new(); let mut edges=Vec::new();
@ -971,7 +975,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
}, },
} }
} }
fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow<[MinkowskiFace]>{ fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow<Vec<MinkowskiFace>>{
unimplemented!() unimplemented!()
} }
} }
@ -985,7 +989,7 @@ fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
for k in 0..len{ for k in 0..len{
if k!=i&&k!=j{ if k!=i&&k!=j{
let d=n.dot(normals[k]).is_negative(); let d=n.dot(normals[k]).is_negative();
if let &Some(comp)=&d_comp{ if let Some(comp)=&d_comp{
// This is testing if d_comp*d < 0 // This is testing if d_comp*d < 0
if comp^d{ if comp^d{
return true; return true;
@ -1005,3 +1009,9 @@ fn test_is_empty_volume(){
assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec())); assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec()));
assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec())); assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec()));
} }
#[test]
fn build_me_a_cube(){
let mesh=PhysicsMesh::unit_cube();
//println!("mesh={:?}",mesh);
}

View File

@ -1,74 +1,28 @@
use strafesnet_common::mouse::MouseState; use strafesnet_common::mouse::MouseState;
use strafesnet_common::physics::{ use strafesnet_common::physics::{
MouseInstruction,SetControlInstruction,ModeInstruction,MiscInstruction, Instruction as PhysicsInputInstruction,
Instruction as PhysicsInstruction,
TimeInner as PhysicsTimeInner, TimeInner as PhysicsTimeInner,
Time as PhysicsTime, Time as PhysicsTime,
MouseInstruction,
OtherInstruction,
}; };
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction}; use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTimeInner>; type TimedPhysicsInstruction=TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>;
type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTimeInner>; type TimedUnbufferedInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
type DoubleTimedUnbufferedInstruction=TimedInstruction<TimedUnbufferedInstruction,SessionTimeInner>;
type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTimeInner>;
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10); const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
/// To be fed into MouseInterpolator /// To be fed into MouseInterpolator
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub(crate) enum Instruction{ pub enum Instruction{
MoveMouse(glam::IVec2), MoveMouse(glam::IVec2),
SetControl(SetControlInstruction), Other(OtherInstruction),
Mode(ModeInstruction),
Misc(MiscInstruction),
Idle,
}
#[derive(Clone,Debug)]
enum UnbufferedInstruction{
MoveMouse(glam::IVec2),
NonMouse(NonMouseInstruction),
}
#[derive(Clone,Debug)]
enum BufferedInstruction{
Mouse(MouseInstruction),
NonMouse(NonMouseInstruction),
}
#[derive(Clone,Debug)]
pub(crate) enum NonMouseInstruction{
SetControl(SetControlInstruction),
Mode(ModeInstruction),
Misc(MiscInstruction),
Idle,
}
impl From<Instruction> for UnbufferedInstruction{
#[inline]
fn from(value:Instruction)->Self{
match value{
Instruction::MoveMouse(mouse_instruction)=>UnbufferedInstruction::MoveMouse(mouse_instruction),
Instruction::SetControl(set_control_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::SetControl(set_control_instruction)),
Instruction::Mode(mode_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Mode(mode_instruction)),
Instruction::Misc(misc_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Misc(misc_instruction)),
Instruction::Idle=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Idle),
}
}
}
impl From<BufferedInstruction> for PhysicsInstruction{
#[inline]
fn from(value:BufferedInstruction)->Self{
match value{
BufferedInstruction::Mouse(mouse_instruction)=>PhysicsInstruction::Mouse(mouse_instruction),
BufferedInstruction::NonMouse(non_mouse_instruction)=>match non_mouse_instruction{
NonMouseInstruction::SetControl(set_control_instruction)=>PhysicsInstruction::SetControl(set_control_instruction),
NonMouseInstruction::Mode(mode_instruction)=>PhysicsInstruction::Mode(mode_instruction),
NonMouseInstruction::Misc(misc_instruction)=>PhysicsInstruction::Misc(misc_instruction),
NonMouseInstruction::Idle=>PhysicsInstruction::Idle,
},
}
}
} }
pub(crate) enum StepInstruction{ pub enum StepInstruction{
Pop, Pop,
Timeout, Timeout,
} }
@ -88,10 +42,10 @@ pub struct MouseInterpolator{
} }
// Maybe MouseInterpolator manipulation is better expressed using impls // Maybe MouseInterpolator manipulation is better expressed using impls
// and called from Instruction trait impls in session // and called from Instruction trait impls in session
impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{ impl InstructionConsumer<TimedUnbufferedInstruction> for MouseInterpolator{
type TimeInner=SessionTimeInner; type TimeInner=SessionTimeInner;
fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){ fn process_instruction(&mut self,ins:DoubleTimedUnbufferedInstruction){
self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into()) self.push_unbuffered_input(ins)
} }
} }
impl InstructionEmitter<StepInstruction> for MouseInterpolator{ impl InstructionEmitter<StepInstruction> for MouseInterpolator{
@ -111,7 +65,7 @@ impl MouseInterpolator{
fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){ fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){
self.buffer.push_front(TimedInstruction{ self.buffer.push_front(TimedInstruction{
time:ins.time, time:ins.time,
instruction:BufferedInstruction::Mouse(ins.instruction).into(), instruction:PhysicsInputInstruction::Mouse(ins.instruction),
}); });
// flush buffer to output // flush buffer to output
if self.output.len()==0{ if self.output.len()==0{
@ -132,8 +86,7 @@ impl MouseInterpolator{
} }
} }
} }
fn timeout_mouse(&mut self,timeout_time:PhysicsTime){ fn timeout_mouse(&mut self,time:PhysicsTime){
// the state always changes to unbuffered
let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered); let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered);
match buffer_state{ match buffer_state{
BufferState::Unbuffered=>(), BufferState::Unbuffered=>(),
@ -142,21 +95,28 @@ impl MouseInterpolator{
self.push_mouse_and_flush_buffer(TimedInstruction{ self.push_mouse_and_flush_buffer(TimedInstruction{
time:mouse_state.time, time:mouse_state.time,
instruction:MouseInstruction::ReplaceMouse{ instruction:MouseInstruction::ReplaceMouse{
m1:MouseState{pos:mouse_state.pos,time:timeout_time}, m1:MouseState{pos:mouse_state.pos,time},
m0:mouse_state, m0:mouse_state,
}, },
}); });
} }
BufferState::Buffered(_time,mouse_state)=>{ BufferState::Buffered(_time,mouse_state)=>{
// duplicate the currently buffered mouse state but at a later (future, from the physics perspective) time // convert to BufferState::Unbuffered
// use the first instruction which should be a mouse instruction
// to push a ReplaceMouse instruction
// duplicate the current mouse
self.push_mouse_and_flush_buffer(TimedInstruction{ self.push_mouse_and_flush_buffer(TimedInstruction{
// This should be simulation_timer.time(timeout)
// but the timer is not accessible from this scope
// and it's just here to say that the mouse isn't moving anyways.
// I think this is a divide by zero bug, two identical mouse_states will occupy the interpolation state
time:mouse_state.time, time:mouse_state.time,
instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time:timeout_time}), instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time}),
}); });
}, },
} }
} }
fn push_unbuffered_input(&mut self,session_time:SessionTime,physics_time:PhysicsTime,ins:UnbufferedInstruction){ pub fn push_unbuffered_input(&mut self,ins:DoubleTimedUnbufferedInstruction){
// new input // new input
// if there is zero instruction buffered, it means the mouse is not moving // if there is zero instruction buffered, it means the mouse is not moving
// case 1: unbuffered // case 1: unbuffered
@ -168,16 +128,18 @@ impl MouseInterpolator{
// case 3: stop // case 3: stop
// a mouse event is buffered, but no mouse events have transpired within 10ms // a mouse event is buffered, but no mouse events have transpired within 10ms
// replace_with allows the enum variant to safely be replaced // push buffered mouse instruction and flush buffer to output
// from behind a mutable reference, but a panic in the closure means that if self.get_mouse_timedout_at(ins.time).is_some(){
// the entire program terminates rather than completing an unwind. self.timeout_mouse(ins.instruction.time);
}
// replace_with allows the enum variant to safely be replaced from behind a mutable reference
let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{ let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{
match ins{ match ins.instruction.instruction{
UnbufferedInstruction::MoveMouse(pos)=>{ Instruction::MoveMouse(pos)=>{
let next_mouse_state=MouseState{pos,time:physics_time}; let next_mouse_state=MouseState{pos,time:ins.instruction.time};
match buffer_state{ match buffer_state{
BufferState::Unbuffered=>{ BufferState::Unbuffered=>{
((None,None),BufferState::Initializing(session_time,next_mouse_state)) ((None,None),BufferState::Initializing(ins.time,next_mouse_state))
}, },
BufferState::Initializing(_time,mouse_state)=>{ BufferState::Initializing(_time,mouse_state)=>{
let ins_mouse=TimedInstruction{ let ins_mouse=TimedInstruction{
@ -187,19 +149,19 @@ impl MouseInterpolator{
m1:next_mouse_state.clone(), m1:next_mouse_state.clone(),
}, },
}; };
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state)) ((Some(ins_mouse),None),BufferState::Buffered(ins.time,next_mouse_state))
}, },
BufferState::Buffered(_time,mouse_state)=>{ BufferState::Buffered(_time,mouse_state)=>{
let ins_mouse=TimedInstruction{ let ins_mouse=TimedInstruction{
time:mouse_state.time, time:mouse_state.time,
instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()), instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()),
}; };
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state)) ((Some(ins_mouse),None),BufferState::Buffered(ins.time,next_mouse_state))
}, },
} }
}, },
UnbufferedInstruction::NonMouse(other_instruction)=>((None,Some(TimedInstruction{ Instruction::Other(other_instruction)=>((None,Some(TimedInstruction{
time:physics_time, time:ins.instruction.time,
instruction:other_instruction, instruction:other_instruction,
})),buffer_state), })),buffer_state),
} }
@ -210,7 +172,7 @@ impl MouseInterpolator{
if let Some(ins)=ins_other{ if let Some(ins)=ins_other{
let instruction=TimedInstruction{ let instruction=TimedInstruction{
time:ins.time, time:ins.time,
instruction:BufferedInstruction::NonMouse(ins.instruction).into(), instruction:PhysicsInputInstruction::Other(ins.instruction),
}; };
if matches!(self.buffer_state,BufferState::Unbuffered){ if matches!(self.buffer_state,BufferState::Unbuffered){
self.output.push_back(instruction); self.output.push_back(instruction);
@ -219,7 +181,7 @@ impl MouseInterpolator{
} }
} }
} }
fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{ pub fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{
match self.get_mouse_timedout_at(time_limit){ match self.get_mouse_timedout_at(time_limit){
Some(timeout)=>Some(TimedInstruction{ Some(timeout)=>Some(TimedInstruction{
time:timeout, time:timeout,
@ -232,7 +194,7 @@ impl MouseInterpolator{
}), }),
} }
} }
pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedPhysicsInstruction>{ pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>{
match ins.instruction{ match ins.instruction{
StepInstruction::Pop=>(), StepInstruction::Pop=>(),
StepInstruction::Timeout=>self.timeout_mouse(ins.time), StepInstruction::Timeout=>self.timeout_mouse(ins.time),
@ -253,11 +215,13 @@ mod test{
macro_rules! push{ macro_rules! push{
($time:expr,$ins:expr)=>{ ($time:expr,$ins:expr)=>{
println!("in={:?}",$ins); println!("in={:?}",$ins);
interpolator.push_unbuffered_input( interpolator.push_unbuffered_input(TimedInstruction{
$time, time:$time,
timer.time($time), instruction:TimedInstruction{
$ins, time:timer.time($time),
); instruction:$ins,
}
});
while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){ while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){
let ins_retimed=TimedInstruction{ let ins_retimed=TimedInstruction{
time:timer.time(ins.time), time:timer.time(ins.time),
@ -271,11 +235,11 @@ mod test{
// test each buffer_state transition // test each buffer_state transition
let mut t=SessionTime::ZERO; let mut t=SessionTime::ZERO;
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0))); push!(t,Instruction::MoveMouse(glam::ivec2(0,0)));
t+=SessionTime::from_millis(5); t+=SessionTime::from_millis(5);
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0))); push!(t,Instruction::MoveMouse(glam::ivec2(0,0)));
t+=SessionTime::from_millis(5); t+=SessionTime::from_millis(5);
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0))); push!(t,Instruction::MoveMouse(glam::ivec2(0,0)));
t+=SessionTime::from_millis(1); t+=SessionTime::from_millis(1);
} }
} }

View File

@ -1,5 +1,5 @@
use std::collections::{HashMap,HashSet}; use std::collections::{HashMap,HashSet};
use crate::model::{self as model_physics,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId}; use crate::model_physics::{self,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId};
use strafesnet_common::bvh; use strafesnet_common::bvh;
use strafesnet_common::map; use strafesnet_common::map;
use strafesnet_common::run; use strafesnet_common::run;
@ -19,7 +19,7 @@ type MouseState=strafesnet_common::mouse::MouseState<TimeInner>;
//external influence //external influence
//this is how you influence the physics from outside //this is how you influence the physics from outside
use strafesnet_common::physics::{Instruction,MouseInstruction,ModeInstruction,MiscInstruction,SetControlInstruction}; use strafesnet_common::physics::{Instruction,OtherInstruction,MouseInstruction,ModeInstruction,OtherOtherInstruction,SetControlInstruction};
//internal influence //internal influence
//when the physics asks itself what happens next, this is how it's represented //when the physics asks itself what happens next, this is how it's represented
@ -32,23 +32,21 @@ pub enum InternalInstruction{
// Water, // Water,
} }
#[derive(Clone,Debug)] #[derive(Clone,Debug,Default)]
pub struct InputState{ pub struct InputState{
mouse:MouseState, mouse:MouseState,
next_mouse:MouseState, next_mouse:MouseState,
controls:strafesnet_common::controls_bitflag::Controls, controls:strafesnet_common::controls_bitflag::Controls,
} }
impl InputState{ impl InputState{
pub const fn get_next_mouse(&self)->&MouseState{
&self.next_mouse
}
fn set_next_mouse(&mut self,next_mouse:MouseState){ fn set_next_mouse(&mut self,next_mouse:MouseState){
// would this be correct?
// if self.next_mouse.time==next_mouse.time{
// self.next_mouse=next_mouse;
// }else{
//I like your functions magic language //I like your functions magic language
self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse); self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse);
//equivalently: //equivalently:
//(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone()); //(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone());
// }
} }
fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){ fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){
(self.next_mouse,self.mouse)=(next_mouse,mouse); (self.next_mouse,self.mouse)=(next_mouse,mouse);
@ -70,15 +68,6 @@ impl InputState{
((dm*t)/dt).as_ivec2() ((dm*t)/dt).as_ivec2()
} }
} }
impl Default for InputState{
fn default()->Self{
Self{
mouse:MouseState{pos:Default::default(),time:Time::ZERO-Time::EPSILON*2},
next_mouse:MouseState{pos:Default::default(),time:Time::ZERO-Time::EPSILON},
controls:Default::default(),
}
}
}
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
enum JumpDirection{ enum JumpDirection{
Exactly(Planar64Vec3), Exactly(Planar64Vec3),
@ -161,17 +150,17 @@ fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCol
let normal=contact_normal(models,hitbox_mesh,contact); let normal=contact_normal(models,hitbox_mesh,contact);
let gravity=touching.base_acceleration(models,style,camera,input_state); let gravity=touching.base_acceleration(models,style,camera,input_state);
let control_dir=style.get_y_control_dir(camera,input_state.controls); let control_dir=style.get_y_control_dir(camera,input_state.controls);
let target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal); let mut target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal);
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_velocity); touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity);
(gravity,target_velocity_clipped) (gravity,target_velocity)
} }
fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){ fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){
let normal=contact_normal(models,hitbox_mesh,contact); let normal=contact_normal(models,hitbox_mesh,contact);
let gravity=touching.base_acceleration(models,style,camera,input_state); let gravity=touching.base_acceleration(models,style,camera,input_state);
let control_dir=style.get_y_control_dir(camera,input_state.controls); let control_dir=style.get_y_control_dir(camera,input_state.controls);
let target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal); let mut target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal);
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_velocity); touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity);
(gravity,target_velocity_clipped) (gravity,target_velocity)
} }
#[derive(Default)] #[derive(Default)]
@ -229,6 +218,12 @@ impl PhysicsModels{
.map(|model|&model.transform), .map(|model|&model.transform),
} }
} }
fn contact_model(&self,model_id:ContactModelId)->&ContactModel{
&self.contact_models[&model_id]
}
fn intersect_model(&self,model_id:IntersectModelId)->&IntersectModel{
&self.intersect_models[&model_id]
}
fn contact_attr(&self,model_id:ContactModelId)->&gameplay_attributes::ContactAttributes{ fn contact_attr(&self,model_id:ContactModelId)->&gameplay_attributes::ContactAttributes{
&self.contact_attributes[&self.contact_models[&model_id].attr_id] &self.contact_attributes[&self.contact_models[&model_id].attr_id]
} }
@ -698,18 +693,18 @@ struct IntersectModel{
} }
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] #[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
pub struct ContactCollision{ struct ContactCollision{
face_id:model_physics::MinkowskiFace, face_id:model_physics::MinkowskiFace,
model_id:ContactModelId, model_id:ContactModelId,
submesh_id:PhysicsSubmeshId, submesh_id:PhysicsSubmeshId,
} }
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] #[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
pub struct IntersectCollision{ struct IntersectCollision{
model_id:IntersectModelId, model_id:IntersectModelId,
submesh_id:PhysicsSubmeshId, submesh_id:PhysicsSubmeshId,
} }
#[derive(Debug,Clone,Eq,Hash,PartialEq)] #[derive(Debug,Clone,Eq,Hash,PartialEq)]
pub enum Collision{ enum Collision{
Contact(ContactCollision), Contact(ContactCollision),
Intersect(IntersectCollision), Intersect(IntersectCollision),
} }
@ -762,8 +757,8 @@ impl TouchingState{
//TODO: add water //TODO: add water
a a
} }
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:Planar64Vec3)->Planar64Vec3{ fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){
let contacts:Vec<_>=self.contacts.iter().map(|contact|{ let contacts=self.contacts.iter().map(|contact|{
let n=contact_normal(models,hitbox_mesh,contact); let n=contact_normal(models,hitbox_mesh,contact);
crate::push_solve::Contact{ crate::push_solve::Contact{
position:vec3::ZERO, position:vec3::ZERO,
@ -771,10 +766,10 @@ impl TouchingState{
normal:n, normal:n,
} }
}).collect(); }).collect();
crate::push_solve::push_solve(&contacts,velocity) *velocity=crate::push_solve::push_solve(&contacts,*velocity);
} }
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:Planar64Vec3)->Planar64Vec3{ fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){
let contacts:Vec<_>=self.contacts.iter().map(|contact|{ let contacts=self.contacts.iter().map(|contact|{
let n=contact_normal(models,hitbox_mesh,contact); let n=contact_normal(models,hitbox_mesh,contact);
crate::push_solve::Contact{ crate::push_solve::Contact{
position:vec3::ZERO, position:vec3::ZERO,
@ -782,16 +777,15 @@ impl TouchingState{
normal:n, normal:n,
} }
}).collect(); }).collect();
crate::push_solve::push_solve(&contacts,acceleration) *acceleration=crate::push_solve::push_solve(&contacts,*acceleration);
} }
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,start_time:Time){ fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
// let relative_body=body.relative_to(&Body::ZERO); let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time);
let relative_body=body;
for contact in &self.contacts{ for contact in &self.contacts{
//detect face slide off //detect face slide off
let model_mesh=models.contact_mesh(contact); let model_mesh=models.contact_mesh(contact);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_face_out(&relative_body,start_time..collector.time(),contact.face_id).map(|(_face,time)|{ collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{
TimedInstruction{ TimedInstruction{
time:relative_body.time+time.into(), time:relative_body.time+time.into(),
instruction:InternalInstruction::CollisionEnd( instruction:InternalInstruction::CollisionEnd(
@ -805,7 +799,7 @@ impl TouchingState{
//detect model collision in reverse //detect model collision in reverse
let model_mesh=models.intersect_mesh(intersect); let model_mesh=models.intersect_mesh(intersect);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_out(&relative_body,start_time..collector.time()).map(|(_face,time)|{ collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{
TimedInstruction{ TimedInstruction{
time:relative_body.time+time.into(), time:relative_body.time+time.into(),
instruction:InternalInstruction::CollisionEnd( instruction:InternalInstruction::CollisionEnd(
@ -838,7 +832,17 @@ pub struct PhysicsState{
//a start zone. If you change mode, a new run is created. //a start zone. If you change mode, a new run is created.
run:run::Run, run:run::Run,
} }
//random collection of contextual data that doesn't belong in PhysicsState
pub struct PhysicsData{
//permanent map data
bvh:bvh::BvhNode<ConvexMeshId>,
//transient map/environment data (open world loads/unloads parts of this data)
models:PhysicsModels,
//semi-transient data
modes:gameplay_modes::Modes,
//cached calculations
hitbox_mesh:HitboxMesh,
}
impl Default for PhysicsState{ impl Default for PhysicsState{
fn default()->Self{ fn default()->Self{
Self{ Self{
@ -855,24 +859,19 @@ impl Default for PhysicsState{
} }
} }
} }
impl Default for PhysicsData{
fn default()->Self{
Self{
bvh:bvh::BvhNode::default(),
models:Default::default(),
modes:Default::default(),
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
}
}
}
impl PhysicsState{ impl PhysicsState{
pub fn camera_body(&self)->Body{ fn clear(&mut self){
Body{
position:self.body.position+self.style.camera_offset,
..self.body
}
}
pub const fn camera(&self)->PhysicsCamera{
self.camera
}
pub const fn mode(&self)->gameplay_modes::ModeId{
self.mode_state.get_mode_id()
}
pub fn get_finish_time(&self)->Option<run::Time>{
self.run.get_finish_time()
}
pub fn clear(&mut self){
self.touching.clear(); self.touching.clear();
} }
fn reset_to_default(&mut self){ fn reset_to_default(&mut self){
@ -921,67 +920,49 @@ impl PhysicsState{
// }); // });
// } // }
} }
// shared geometry for simulations
pub struct PhysicsData{ #[derive(Default)]
//permanent map data pub struct PhysicsContext{
bvh:bvh::BvhNode<ConvexMeshId>, state:PhysicsState,//this captures the entire state of the physics.
//transient map/environment data (open world loads/unloads parts of this data) data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
models:PhysicsModels,
//semi-transient data
modes:gameplay_modes::Modes,
//cached calculations
hitbox_mesh:HitboxMesh,
}
impl Default for PhysicsData{
fn default()->Self{
Self{
bvh:bvh::BvhNode::default(),
models:Default::default(),
modes:Default::default(),
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
}
}
}
// the collection of information required to run physics
pub struct PhysicsContext<'a>{
state:&'a mut PhysicsState,//this captures the entire state of the physics.
data:&'a PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
} }
// the physics consumes both Instruction and PhysicsInternalInstruction, // the physics consumes both Instruction and PhysicsInternalInstruction,
// but can only emit PhysicsInternalInstruction // but can only emit PhysicsInternalInstruction
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{ impl InstructionConsumer<InternalInstruction> for PhysicsContext{
type TimeInner=TimeInner; type TimeInner=TimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,TimeInner>){ fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,TimeInner>){
atomic_internal_instruction(&mut self.state,&self.data,ins) atomic_internal_instruction(&mut self.state,&self.data,ins)
} }
} }
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{ impl InstructionConsumer<Instruction> for PhysicsContext{
type TimeInner=TimeInner; type TimeInner=TimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,TimeInner>){ fn process_instruction(&mut self,ins:TimedInstruction<Instruction,TimeInner>){
atomic_input_instruction(&mut self.state,&self.data,ins) atomic_input_instruction(&mut self.state,&self.data,ins)
} }
} }
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{ impl InstructionEmitter<InternalInstruction> for PhysicsContext{
type TimeInner=TimeInner; type TimeInner=TimeInner;
//this little next instruction function could cache its return value and invalidate the cached value by watching the State. //this little next instruction function could cache its return value and invalidate the cached value by watching the State.
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{ fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
next_instruction_internal(&self.state,&self.data,time_limit) next_instruction_internal(&self.state,&self.data,time_limit)
} }
} }
impl PhysicsContext<'_>{ impl PhysicsContext{
pub fn run_input_instruction( pub fn camera_body(&self)->Body{
state:&mut PhysicsState, Body{
data:&PhysicsData, position:self.state.body.position+self.state.style.camera_offset,
instruction:TimedInstruction<Instruction,TimeInner> ..self.state.body
){
let mut context=PhysicsContext{state,data};
context.process_exhaustive(instruction.time);
context.process_instruction(instruction);
} }
} }
impl PhysicsData{ pub const fn camera(&self)->PhysicsCamera{
self.state.camera
}
pub const fn get_next_mouse(&self)->&MouseState{
self.state.input_state.get_next_mouse()
}
/// use with caution, this is the only non-instruction way to mess with physics /// use with caution, this is the only non-instruction way to mess with physics
pub fn generate_models(&mut self,map:&map::CompleteMap){ pub fn generate_models(&mut self,map:&map::CompleteMap){
self.state.clear();
let mut modes=map.modes.clone(); let mut modes=map.modes.clone();
for mode in &mut modes.modes{ for mode in &mut modes.modes{
mode.denormalize_data(); mode.denormalize_data();
@ -1112,12 +1093,17 @@ impl PhysicsData{
(IntersectAttributesId::new(attr_id as u32),attr) (IntersectAttributesId::new(attr_id as u32),attr)
).collect(), ).collect(),
}; };
self.bvh=bvh; self.data.bvh=bvh;
self.models=models; self.data.models=models;
self.modes=modes; self.data.modes=modes;
//hitbox_mesh is unchanged //hitbox_mesh is unchanged
println!("Physics Objects: {}",model_count); println!("Physics Objects: {}",model_count);
} }
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<Instruction,TimeInner>){
self.process_exhaustive(instruction.time);
self.process_instruction(instruction);
}
} }
//this is the one who asks //this is the one who asks
@ -1134,15 +1120,15 @@ impl PhysicsData{
state.body.grow_aabb(&mut aabb,state.time,collector.time()); state.body.grow_aabb(&mut aabb,state.time,collector.time());
aabb.inflate(data.hitbox_mesh.halfsize); aabb.inflate(data.hitbox_mesh.halfsize);
//relative to moving platforms //relative to moving platforms
//let relative_body=state.body.relative_to(&Body::ZERO); //let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time);
let relative_body=&state.body; let relative_body=&state.body;
data.bvh.the_tester(&aabb,&mut |&convex_mesh_id|{ data.bvh.the_tester(&aabb,&mut |&convex_mesh_id|{
//no checks are needed because of the time limits. //no checks are needed because of the time limits.
let model_mesh=data.models.mesh(convex_mesh_id); let model_mesh=data.models.mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh()); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_in(relative_body,state.time..collector.time()) collector.collect(minkowski.predict_collision_in(relative_body,collector.time())
//temp (?) code to avoid collision loops //temp (?) code to avoid collision loops
.and_then(|(face,dt)|{ .map_or(None,|(face,dt)|{
// this must be rounded to avoid the infinite loop when hitting the start zone // this must be rounded to avoid the infinite loop when hitting the start zone
let time=relative_body.time+dt.into(); let time=relative_body.time+dt.into();
(state.time<time).then_some((time,face,dt)) (state.time<time).then_some((time,face,dt))
@ -1157,7 +1143,7 @@ impl PhysicsData{
) )
); );
}); });
collector.take() collector.instruction()
} }
@ -1197,7 +1183,7 @@ fn recalculate_touching(
aabb.grow(body.position); aabb.grow(body.position);
aabb.inflate(hitbox_mesh.halfsize); aabb.inflate(hitbox_mesh.halfsize);
//relative to moving platforms //relative to moving platforms
//let relative_body=state.body.relative_to(&Body::ZERO); //let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time);
bvh.the_tester(&aabb,&mut |&convex_mesh_id|{ bvh.the_tester(&aabb,&mut |&convex_mesh_id|{
//no checks are needed because of the time limits. //no checks are needed because of the time limits.
let model_mesh=models.mesh(convex_mesh_id); let model_mesh=models.mesh(convex_mesh_id);
@ -1252,14 +1238,16 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM
let r=n.dot(v).is_positive(); let r=n.dot(v).is_positive();
if r{ if r{
culled=true; culled=true;
println!("set_velocity_cull contact={:?}",contact);
} }
!r !r
}); });
set_velocity(body,touching,models,hitbox_mesh,v); set_velocity(body,touching,models,hitbox_mesh,v);
culled culled
} }
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){ fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut v:Planar64Vec3){
body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);; touching.constrain_velocity(models,hitbox_mesh,&mut v);
body.velocity=v;
} }
fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{ fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{
//This is not correct but is better than what I have //This is not correct but is better than what I have
@ -1269,14 +1257,16 @@ fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&Phys
let r=n.dot(a).is_positive(); let r=n.dot(a).is_positive();
if r{ if r{
culled=true; culled=true;
println!("set_acceleration_cull contact={:?}",contact);
} }
!r !r
}); });
set_acceleration(body,touching,models,hitbox_mesh,a); set_acceleration(body,touching,models,hitbox_mesh,a);
culled culled
} }
fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3){ fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut a:Planar64Vec3){
body.acceleration=touching.constrain_acceleration(models,hitbox_mesh,a); touching.constrain_acceleration(models,hitbox_mesh,&mut a);
body.acceleration=a;
} }
fn teleport( fn teleport(
@ -1485,7 +1475,7 @@ fn collision_start_contact(
let model_id=contact.model_id.into(); let model_id=contact.model_id.into();
let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id); let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id);
match &attr.contacting.contact_behaviour{ match &attr.contacting.contact_behaviour{
Some(gameplay_attributes::ContactingBehaviour::Surf)=>(), Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"),
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"), Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
&Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{ &Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{
let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1(); let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1();
@ -1515,21 +1505,6 @@ fn collision_start_contact(
} }
}, },
} }
match &attr.general.trajectory{
Some(trajectory)=>{
match trajectory{
gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(),
gameplay_attributes::SetTrajectory::Height(_)=>todo!(),
gameplay_attributes::SetTrajectory::TargetPointTime{..}=>todo!(),
gameplay_attributes::SetTrajectory::TargetPointSpeed{..}=>todo!(),
&gameplay_attributes::SetTrajectory::Velocity(velocity)=>{
move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
},
gameplay_attributes::SetTrajectory::DotVelocity{..}=>todo!(),
}
},
None=>(),
}
//I love making functions with 10 arguments to dodge the borrow checker //I love making functions with 10 arguments to dodge the borrow checker
if allow_run_teleport_behaviour{ if allow_run_teleport_behaviour{
run_teleport_behaviour(model_id,attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time); run_teleport_behaviour(model_id,attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
@ -1557,6 +1532,21 @@ fn collision_start_contact(
} }
} }
} }
match &attr.general.trajectory{
Some(trajectory)=>{
match trajectory{
gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(),
gameplay_attributes::SetTrajectory::Height(_)=>todo!(),
gameplay_attributes::SetTrajectory::TargetPointTime { target_point: _, time: _ }=>todo!(),
gameplay_attributes::SetTrajectory::TargetPointSpeed { target_point: _, speed: _, trajectory_choice: _ }=>todo!(),
&gameplay_attributes::SetTrajectory::Velocity(velocity)=>{
move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
},
gameplay_attributes::SetTrajectory::DotVelocity { direction: _, dot: _ }=>todo!(),
}
},
None=>(),
}
//doing enum to set the acceleration when surfing //doing enum to set the acceleration when surfing
//doing input_and_body to refresh the walk state if you hit a wall while accelerating //doing input_and_body to refresh the walk state if you hit a wall while accelerating
move_state.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state); move_state.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
@ -1727,19 +1717,19 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|MoveState::Fly |MoveState::Fly
=>println!("ReachWalkTargetVelocity fired for non-walking MoveState"), =>println!("ReachWalkTargetVelocity fired for non-walking MoveState"),
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{ MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{
match &walk_state.target{
//you are not supposed to reach a walk target which is already reached!
TransientAcceleration::Reached=>unreachable!(),
TransientAcceleration::Reachable{acceleration:_,time:_}=>{
//velocity is already handled by advance_time //velocity is already handled by advance_time
//we know that the acceleration is precisely zero because the walk target is known to be reachable //we know that the acceleration is precisely zero because the walk target is known to be reachable
//which means that gravity can be fully cancelled //which means that gravity can be fully cancelled
//ignore moving platforms for now //ignore moving platforms for now
let target=core::mem::replace(&mut walk_state.target,TransientAcceleration::Reached);
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
// check what the target was to see if it was invalid walk_state.target=TransientAcceleration::Reached;
match target{ },
//you are not supposed to reach a walk target which is already reached!
TransientAcceleration::Reached=>println!("Invalid walk target: Reached"),
TransientAcceleration::Reachable{..}=>(),
//you are not supposed to reach an unreachable walk target! //you are not supposed to reach an unreachable walk target!
TransientAcceleration::Unreachable{..}=>println!("Invalid walk target: Unreachable"), TransientAcceleration::Unreachable{acceleration:_}=>unreachable!(),
} }
} }
} }
@ -1752,13 +1742,13 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
let should_advance_body=match ins.instruction{ let should_advance_body=match ins.instruction{
//the body may as well be a quantum wave function //the body may as well be a quantum wave function
//as far as these instruction are concerned (they don't care where it is) //as far as these instruction are concerned (they don't care where it is)
Instruction::Misc(MiscInstruction::SetSensitivity(..)) Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(..)))
|Instruction::Mode(_) |Instruction::Other(OtherInstruction::Mode(_))
|Instruction::SetControl(SetControlInstruction::SetZoom(..)) |Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetZoom(..)))
|Instruction::Idle=>false, |Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::Idle))=>false,
//these controls only update the body if you are on the ground //these controls only update the body if you are on the ground
Instruction::Mouse(_) Instruction::Mouse(_)
|Instruction::SetControl(_)=>{ |Instruction::Other(OtherInstruction::SetControl(_))=>{
match &state.move_state{ match &state.move_state{
MoveState::Fly MoveState::Fly
|MoveState::Water |MoveState::Water
@ -1768,7 +1758,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
} }
}, },
//the body must be updated unconditionally //the body must be updated unconditionally
Instruction::Misc(MiscInstruction::PracticeFly)=>true, Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::PracticeFly))=>true,
}; };
if should_advance_body{ if should_advance_body{
state.body.advance_time(state.time); state.body.advance_time(state.time);
@ -1784,37 +1774,37 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
state.camera.move_mouse(m0.pos-state.input_state.mouse.pos); state.camera.move_mouse(m0.pos-state.input_state.mouse.pos);
state.input_state.replace_mouse(m0,m1); state.input_state.replace_mouse(m0,m1);
}, },
Instruction::Misc(MiscInstruction::SetSensitivity(sensitivity))=>state.camera.sensitivity=sensitivity, Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(sensitivity)))=>state.camera.sensitivity=sensitivity,
Instruction::SetControl(SetControlInstruction::SetMoveForward(s))=>state.input_state.set_control(Controls::MoveForward,s), Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveForward(s)))=>state.input_state.set_control(Controls::MoveForward,s),
Instruction::SetControl(SetControlInstruction::SetMoveLeft(s))=>state.input_state.set_control(Controls::MoveLeft,s), Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveLeft(s)))=>state.input_state.set_control(Controls::MoveLeft,s),
Instruction::SetControl(SetControlInstruction::SetMoveBack(s))=>state.input_state.set_control(Controls::MoveBackward,s), Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveBack(s)))=>state.input_state.set_control(Controls::MoveBackward,s),
Instruction::SetControl(SetControlInstruction::SetMoveRight(s))=>state.input_state.set_control(Controls::MoveRight,s), Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveRight(s)))=>state.input_state.set_control(Controls::MoveRight,s),
Instruction::SetControl(SetControlInstruction::SetMoveUp(s))=>state.input_state.set_control(Controls::MoveUp,s), Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveUp(s)))=>state.input_state.set_control(Controls::MoveUp,s),
Instruction::SetControl(SetControlInstruction::SetMoveDown(s))=>state.input_state.set_control(Controls::MoveDown,s), Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetMoveDown(s)))=>state.input_state.set_control(Controls::MoveDown,s),
Instruction::SetControl(SetControlInstruction::SetJump(s))=>{ Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetJump(s)))=>{
state.input_state.set_control(Controls::Jump,s); state.input_state.set_control(Controls::Jump,s);
if let Some(walk_state)=state.move_state.get_walk_state(){ if let Some(walk_state)=state.move_state.get_walk_state(){
if let Some(jump_settings)=&state.style.jump{ if let Some(jump_settings)=&state.style.jump{
let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact); let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact);
let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref(); let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref();
let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option); let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option);
state.cull_velocity(data,jumped_velocity); state.cull_velocity(&data,jumped_velocity);
} }
} }
b_refresh_walk_target=false; b_refresh_walk_target=false;
}, },
Instruction::SetControl(SetControlInstruction::SetZoom(s))=>{ Instruction::Other(OtherInstruction::SetControl(SetControlInstruction::SetZoom(s)))=>{
state.input_state.set_control(Controls::Zoom,s); state.input_state.set_control(Controls::Zoom,s);
b_refresh_walk_target=false; b_refresh_walk_target=false;
}, },
Instruction::Mode(ModeInstruction::Reset)=>{ Instruction::Other(OtherInstruction::Mode(ModeInstruction::Reset))=>{
//totally reset physics state //totally reset physics state
state.reset_to_default(); state.reset_to_default();
b_refresh_walk_target=false; b_refresh_walk_target=false;
}, },
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{ Instruction::Other(OtherInstruction::Mode(ModeInstruction::Restart))=>{
//teleport to mode start zone //teleport to start zone
let mode=data.modes.get_mode(mode_id); let mode=data.modes.get_mode(state.mode_state.get_mode_id());
let spawn_point=mode.and_then(|mode| let spawn_point=mode.and_then(|mode|
//TODO: spawn at the bottom of the start zone plus the hitbox size //TODO: spawn at the bottom of the start zone plus the hitbox size
//TODO: set camera andles to face the same way as the start zone //TODO: set camera andles to face the same way as the start zone
@ -1828,7 +1818,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
b_refresh_walk_target=false; b_refresh_walk_target=false;
} }
// Spawn does not necessarily imply reset // Spawn does not necessarily imply reset
Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{ Instruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(mode_id,stage_id)))=>{
//spawn at a particular stage //spawn at a particular stage
if let Some(mode)=data.modes.get_mode(mode_id){ if let Some(mode)=data.modes.get_mode(mode_id){
if let Some(stage)=mode.get_stage(stage_id){ if let Some(stage)=mode.get_stage(stage_id){
@ -1842,7 +1832,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
} }
b_refresh_walk_target=false; b_refresh_walk_target=false;
}, },
Instruction::Misc(MiscInstruction::PracticeFly)=>{ Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::PracticeFly))=>{
match &state.move_state{ match &state.move_state{
MoveState::Fly=>{ MoveState::Fly=>{
state.set_move_state(data,MoveState::Air); state.set_move_state(data,MoveState::Air);
@ -1853,7 +1843,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
} }
b_refresh_walk_target=false; b_refresh_walk_target=false;
}, },
Instruction::Idle=>{ Instruction::Other(OtherInstruction::Other(OtherOtherInstruction::Idle))=>{
//literally idle! //literally idle!
b_refresh_walk_target=false; b_refresh_walk_target=false;
}, },
@ -1868,6 +1858,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
#[cfg(test)] #[cfg(test)]
mod test{ mod test{
use strafesnet_common::integer::{vec3::{self,int as int3},mat3}; use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
use crate::body::VirtualBody;
use super::*; use super::*;
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){ fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO)); let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO));
@ -1875,7 +1866,7 @@ mod test{
let hitbox_mesh=h1.transformed_mesh(); let hitbox_mesh=h1.transformed_mesh();
let platform_mesh=h0.transformed_mesh(); let platform_mesh=h0.transformed_mesh();
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
let collision=minkowski.predict_collision_in(&relative_body,Time::ZERO..Time::from_secs(10)); let collision=minkowski.predict_collision_in(&relative_body,Time::from_secs(10));
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision"); assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
} }
fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){ fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){
@ -1893,7 +1884,7 @@ mod test{
let hitbox_mesh=h1.transformed_mesh(); let hitbox_mesh=h1.transformed_mesh();
let platform_mesh=h0.transformed_mesh(); let platform_mesh=h0.transformed_mesh();
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
let collision=minkowski.predict_collision_in(&relative_body,Time::ZERO..Time::from_secs(10)); let collision=minkowski.predict_collision_in(&relative_body,Time::from_secs(10));
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision"); assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
} }
fn test_collision(relative_body:Body,expected_collision_time:Option<Time>){ fn test_collision(relative_body:Body,expected_collision_time:Option<Time>){
@ -1947,111 +1938,111 @@ mod test{
} }
#[test] #[test]
fn test_collision_parabola_edge_east_from_west(){ fn test_collision_parabola_edge_east_from_west(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(3,3,0), int3(3,3,0),
int3(100,-1,0), int3(100,-1,0),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_south_from_north(){ fn test_collision_parabola_edge_south_from_north(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,3,3), int3(0,3,3),
int3(0,-1,100), int3(0,-1,100),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_west_from_east(){ fn test_collision_parabola_edge_west_from_east(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(-3,3,0), int3(-3,3,0),
int3(-100,-1,0), int3(-100,-1,0),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_north_from_south(){ fn test_collision_parabola_edge_north_from_south(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,3,-3), int3(0,3,-3),
int3(0,-1,-100), int3(0,-1,-100),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_north_from_ne(){ fn test_collision_parabola_edge_north_from_ne(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,6,-7)>>1, int3(0,6,-7)>>1,
int3(-10,-1,1), int3(-10,-1,1),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_north_from_nw(){ fn test_collision_parabola_edge_north_from_nw(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,6,-7)>>1, int3(0,6,-7)>>1,
int3(10,-1,1), int3(10,-1,1),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_east_from_se(){ fn test_collision_parabola_edge_east_from_se(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(7,6,0)>>1, int3(7,6,0)>>1,
int3(-1,-1,-10), int3(-1,-1,-10),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_east_from_ne(){ fn test_collision_parabola_edge_east_from_ne(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(7,6,0)>>1, int3(7,6,0)>>1,
int3(-1,-1,10), int3(-1,-1,10),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_south_from_se(){ fn test_collision_parabola_edge_south_from_se(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,6,7)>>1, int3(0,6,7)>>1,
int3(-10,-1,-1), int3(-10,-1,-1),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_south_from_sw(){ fn test_collision_parabola_edge_south_from_sw(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,6,7)>>1, int3(0,6,7)>>1,
int3(10,-1,-1), int3(10,-1,-1),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_west_from_se(){ fn test_collision_parabola_edge_west_from_se(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(-7,6,0)>>1, int3(-7,6,0)>>1,
int3(1,-1,-10), int3(1,-1,-10),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_parabola_edge_west_from_ne(){ fn test_collision_parabola_edge_west_from_ne(){
test_collision(Body::new( test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(-7,6,0)>>1, int3(-7,6,0)>>1,
int3(1,-1,10), int3(1,-1,10),
int3(0,-1,0), int3(0,-1,0),
Time::ZERO Time::ZERO
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0))); )).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
} }
#[test] #[test]
fn test_collision_oblique(){ fn test_collision_oblique(){

View File

@ -1,35 +1,29 @@
use crate::graphics_worker::Instruction as GraphicsInstruction; use crate::graphics_worker::Instruction as GraphicsInstruction;
use strafesnet_settings::{directories::Directories,settings}; use crate::session::{SessionInputInstruction,Instruction as SessionInstruction,Session,Simulation};
use strafesnet_session::session::{
Session,Simulation,SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction,ImplicitModeInstruction,
Instruction as SessionInstruction,
};
use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer}; use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer};
use strafesnet_common::physics::Time as PhysicsTime; use strafesnet_common::physics::Time as PhysicsTime;
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
use strafesnet_common::timer::Timer; use strafesnet_common::timer::Timer;
pub enum Instruction{ pub enum Instruction{
SessionInput(SessionInputInstruction), Input(SessionInputInstruction),
SessionControl(SessionControlInstruction), SetPaused(bool),
SessionPlayback(SessionPlaybackInstruction),
Render, Render,
Resize(winit::dpi::PhysicalSize<u32>), Resize(winit::dpi::PhysicalSize<u32>),
ChangeMap(strafesnet_common::map::CompleteMap), ChangeMap(strafesnet_common::map::CompleteMap),
LoadReplay(strafesnet_snf::bot::Segment),
} }
const SESSION_INSTRUCTION_IDLE:SessionInstruction=SessionInstruction::Input(SessionInputInstruction::Other(strafesnet_common::physics::OtherOtherInstruction::Idle));
pub fn new<'a>( pub fn new<'a>(
mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>, mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>,
directories:Directories, user_settings:crate::settings::UserSettings,
user_settings:settings::UserSettings,
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{ )->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{
let physics=strafesnet_physics::physics::PhysicsState::default(); let physics=crate::physics::PhysicsContext::default();
let timer=Timer::unpaused(SessionTime::ZERO,PhysicsTime::ZERO); let timer=Timer::unpaused(SessionTime::ZERO,PhysicsTime::ZERO);
let simulation=Simulation::new(timer,physics); let simulation=Simulation::new(timer,physics);
let mut session=Session::new( let mut session=Session::new(
user_settings, user_settings,
directories,
simulation, simulation,
); );
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{ crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{
@ -48,34 +42,26 @@ pub fn new<'a>(
}; };
} }
match ins.instruction{ match ins.instruction{
Instruction::SessionInput(unbuffered_instruction)=>{ Instruction::Input(unbuffered_instruction)=>{
run_session_instruction!(ins.time,SessionInstruction::Input(unbuffered_instruction)); run_session_instruction!(ins.time,SessionInstruction::Input(unbuffered_instruction));
}, },
Instruction::SessionControl(unbuffered_instruction)=>{ Instruction::SetPaused(paused)=>{
run_session_instruction!(ins.time,SessionInstruction::Control(unbuffered_instruction)); run_session_instruction!(ins.time,SessionInstruction::SetPaused(paused));
},
Instruction::SessionPlayback(unbuffered_instruction)=>{
run_session_instruction!(ins.time,SessionInstruction::Playback(unbuffered_instruction));
}, },
Instruction::Render=>{ Instruction::Render=>{
run_session_instruction!(ins.time,SessionInstruction::Idle); run_session_instruction!(ins.time,SESSION_INSTRUCTION_IDLE);
if let Some(frame_state)=session.get_frame_state(ins.time){ let frame_state=session.get_frame_state(ins.time);
run_graphics_worker_instruction!(GraphicsInstruction::Render(frame_state)); run_graphics_worker_instruction!(GraphicsInstruction::Render(frame_state));
}
}, },
Instruction::Resize(physical_size)=>{ Instruction::Resize(physical_size)=>{
run_session_instruction!(ins.time,SessionInstruction::Idle); run_session_instruction!(ins.time,SESSION_INSTRUCTION_IDLE);
let user_settings=session.user_settings().clone(); let user_settings=session.user_settings().clone();
run_graphics_worker_instruction!(GraphicsInstruction::Resize(physical_size,user_settings)); run_graphics_worker_instruction!(GraphicsInstruction::Resize(physical_size,user_settings));
}, },
Instruction::ChangeMap(complete_map)=>{ Instruction::ChangeMap(complete_map)=>{
run_session_instruction!(ins.time,SessionInstruction::ChangeMap(&complete_map)); run_session_instruction!(ins.time,SessionInstruction::ChangeMap(&complete_map));
run_session_instruction!(ins.time,SessionInstruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId::MAIN,strafesnet_common::gameplay_modes::StageId::FIRST))));
run_graphics_worker_instruction!(GraphicsInstruction::ChangeMap(complete_map)); run_graphics_worker_instruction!(GraphicsInstruction::ChangeMap(complete_map));
}, },
Instruction::LoadReplay(bot)=>{
run_session_instruction!(ins.time,SessionInstruction::LoadReplay(bot));
}
} }
}) })
} }

View File

@ -289,7 +289,7 @@ fn get_best_push_ray_and_conts<'a>(
} }
} }
fn get_first_touch<'a>(contacts:&'a [Contact],ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{ fn get_first_touch<'a>(contacts:&'a Vec<Contact>,ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
contacts.iter() contacts.iter()
.filter(|&contact| .filter(|&contact|
!conts.iter().any(|&c|std::ptr::eq(c,contact)) !conts.iter().any(|&c|std::ptr::eq(c,contact))
@ -299,7 +299,7 @@ fn get_first_touch<'a>(contacts:&'a [Contact],ray:&Ray,conts:&Conts)->Option<(Ra
.min_by_key(|&(t,_)|t) .min_by_key(|&(t,_)|t)
} }
pub fn push_solve(contacts:&[Contact],point:Planar64Vec3)->Planar64Vec3{ pub fn push_solve(contacts:&Vec<Contact>,point:Planar64Vec3)->Planar64Vec3{
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point); let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
loop{ loop{
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){ let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){

View File

@ -0,0 +1,193 @@
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,OtherInstruction,OtherOtherInstruction,
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),
SetPaused(bool),
ChangeMap(&'a strafesnet_common::map::CompleteMap),
//Graphics(crate::graphics_worker::Instruction),
}
pub enum SessionInputInstruction{
Mouse(glam::IVec2),
SetControl(strafesnet_common::physics::SetControlInstruction),
Mode(ImplicitModeInstruction),
Other(strafesnet_common::physics::OtherOtherInstruction),
}
/// 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 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::PhysicsContext,
}
impl Simulation{
pub const fn new(
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
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),
}
}
}
pub struct Replay{
last_instruction_id:usize,
instructions:Vec<PhysicsInputInstruction>,
simulation:Simulation,
}
impl Replay{
pub const fn new(
instructions:Vec<PhysicsInputInstruction>,
simulation:Simulation,
)->Self{
Self{
last_instruction_id:0,
instructions,
simulation,
}
}
}
pub struct Session{
user_settings:UserSettings,
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
//gui:GuiState
simulation:Simulation,
replays:Vec<Replay>,
}
impl Session{
pub fn new(
user_settings:UserSettings,
simulation:Simulation,
)->Self{
Self{
user_settings,
mouse_interpolator:MouseInterpolator::new(),
simulation,
replays:Vec::new(),
}
}
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
self.simulation.physics.generate_models(map);
}
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
self.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>){
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,
},
});
};
}
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::Other(OtherInstruction::SetControl(set_control_instruction)));
},
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset)));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Restart)));
},
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset)));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id))));
},
Instruction::Input(SessionInputInstruction::Other(other_other_instruction))=>{
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(other_other_instruction)));
},
Instruction::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::ChangeMap(complete_map)=>{
self.change_map(complete_map);
// ResetAndSpawn
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Reset)));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Other(OtherOtherInstruction::SetSensitivity(self.user_settings().calculate_sensitivity()))));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Other(OtherInstruction::Mode(ModeInstruction::Spawn(ModeId::MAIN,StageId::FIRST))));
},
};
// run all buffered instruction produced
self.process_exhaustive(ins.time);
}
}
impl InstructionConsumer<StepInstruction> for Session{
type TimeInner=SessionTimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
// ins.time ignored???
let ins_retimed=TimedInstruction{
time:self.simulation.timer.time(ins.time),
instruction:ins.instruction,
};
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins_retimed){
self.simulation.physics.run_input_instruction(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)
}
}

View File

@ -74,9 +74,9 @@ sensitivity_y_from_x_ratio=1
Sensitivity::DeriveY{x:0.0.001,y:DerivedSensitivity{ratio:1.0}} Sensitivity::DeriveY{x:0.0.001,y:DerivedSensitivity{ratio:1.0}}
*/ */
pub fn load_user_settings(path:&std::path::Path)->UserSettings{ pub fn read_user_settings()->UserSettings{
let mut cfg=configparser::ini::Ini::new(); let mut cfg=configparser::ini::Ini::new();
if let Ok(_)=cfg.load(path){ if let Ok(_)=cfg.load("settings.conf"){
let (cfg_fov_x,cfg_fov_y)=(cfg.getfloat("camera","fov_x"),cfg.getfloat("camera","fov_y")); let (cfg_fov_x,cfg_fov_y)=(cfg.getfloat("camera","fov_x"),cfg.getfloat("camera","fov_y"));
let fov=match(cfg_fov_x,cfg_fov_y){ let fov=match(cfg_fov_x,cfg_fov_y){
(Ok(Some(fov_x)),Ok(Some(fov_y)))=>Fov::Exactly { (Ok(Some(fov_x)),Ok(Some(fov_y)))=>Fov::Exactly {

View File

@ -1,3 +1,8 @@
use crate::window::Instruction;
use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::integer;
use strafesnet_common::session::TimeInner as SessionTimeInner;
fn optional_features()->wgpu::Features{ fn optional_features()->wgpu::Features{
wgpu::Features::TEXTURE_COMPRESSION_ASTC wgpu::Features::TEXTURE_COMPRESSION_ASTC
|wgpu::Features::TEXTURE_COMPRESSION_ETC2 |wgpu::Features::TEXTURE_COMPRESSION_ETC2
@ -12,6 +17,9 @@ fn required_downlevel_capabilities()->wgpu::DownlevelCapabilities{
..wgpu::DownlevelCapabilities::default() ..wgpu::DownlevelCapabilities::default()
} }
} }
pub fn required_limits()->wgpu::Limits{
wgpu::Limits::default()
}
struct SetupContextPartial1{ struct SetupContextPartial1{
backends:wgpu::Backends, backends:wgpu::Backends,
@ -20,13 +28,23 @@ struct SetupContextPartial1{
fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Result<winit::window::Window,winit::error::OsError>{ fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Result<winit::window::Window,winit::error::OsError>{
let mut attr=winit::window::WindowAttributes::default(); let mut attr=winit::window::WindowAttributes::default();
attr=attr.with_title(title); attr=attr.with_title(title);
#[cfg(windows_OFF)] // TODO
{
use winit::platform::windows::WindowBuilderExtWindows;
builder=builder.with_no_redirection_bitmap(true);
}
event_loop.create_window(attr) event_loop.create_window(attr)
} }
fn create_instance()->SetupContextPartial1{ fn create_instance()->SetupContextPartial1{
let backends=wgpu::Backends::from_env().unwrap_or_default(); let backends=wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all);
let dx12_shader_compiler=wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default();
SetupContextPartial1{ SetupContextPartial1{
backends, backends,
instance:Default::default(), instance:wgpu::Instance::new(wgpu::InstanceDescriptor{
backends,
dx12_shader_compiler,
..Default::default()
}),
} }
} }
impl SetupContextPartial1{ impl SetupContextPartial1{
@ -100,12 +118,14 @@ impl<'a> SetupContextPartial2<'a>{
required_downlevel_capabilities.flags - downlevel_capabilities.flags required_downlevel_capabilities.flags - downlevel_capabilities.flags
); );
SetupContextPartial3{ SetupContextPartial3{
instance:self.instance,
surface:self.surface, surface:self.surface,
adapter, adapter,
} }
} }
} }
struct SetupContextPartial3<'a>{ struct SetupContextPartial3<'a>{
instance:wgpu::Instance,
surface:wgpu::Surface<'a>, surface:wgpu::Surface<'a>,
adapter:wgpu::Adapter, adapter:wgpu::Adapter,
} }
@ -115,7 +135,7 @@ impl<'a> SetupContextPartial3<'a>{
let required_features=required_features(); let required_features=required_features();
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface. // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface.
let needed_limits=strafesnet_graphics::graphics::required_limits().using_resolution(self.adapter.limits()); let needed_limits=required_limits().using_resolution(self.adapter.limits());
let trace_dir=std::env::var("WGPU_TRACE"); let trace_dir=std::env::var("WGPU_TRACE");
let (device, queue)=pollster::block_on(self.adapter let (device, queue)=pollster::block_on(self.adapter
@ -131,6 +151,7 @@ impl<'a> SetupContextPartial3<'a>{
.expect("Unable to find a suitable GPU adapter!"); .expect("Unable to find a suitable GPU adapter!");
SetupContextPartial4{ SetupContextPartial4{
instance:self.instance,
surface:self.surface, surface:self.surface,
adapter:self.adapter, adapter:self.adapter,
device, device,
@ -139,6 +160,7 @@ impl<'a> SetupContextPartial3<'a>{
} }
} }
struct SetupContextPartial4<'a>{ struct SetupContextPartial4<'a>{
instance:wgpu::Instance,
surface:wgpu::Surface<'a>, surface:wgpu::Surface<'a>,
adapter:wgpu::Adapter, adapter:wgpu::Adapter,
device:wgpu::Device, device:wgpu::Device,
@ -155,6 +177,7 @@ impl<'a> SetupContextPartial4<'a>{
self.surface.configure(&self.device, &config); self.surface.configure(&self.device, &config);
SetupContext{ SetupContext{
instance:self.instance,
surface:self.surface, surface:self.surface,
device:self.device, device:self.device,
queue:self.queue, queue:self.queue,
@ -163,6 +186,7 @@ impl<'a> SetupContextPartial4<'a>{
} }
} }
pub struct SetupContext<'a>{ pub struct SetupContext<'a>{
pub instance:wgpu::Instance,
pub surface:wgpu::Surface<'a>, pub surface:wgpu::Surface<'a>,
pub device:wgpu::Device, pub device:wgpu::Device,
pub queue:wgpu::Queue, pub queue:wgpu::Queue,
@ -196,17 +220,74 @@ pub fn setup_and_start(title:&str){
setup_context, setup_context,
); );
for arg in std::env::args().skip(1){ if let Some(arg)=std::env::args().nth(1){
window_thread.send(strafesnet_common::instruction::TimedInstruction{ let path=std::path::PathBuf::from(arg);
time:strafesnet_common::integer::Time::ZERO, window_thread.send(TimedInstruction{
instruction:crate::window::Instruction::WindowEvent(winit::event::WindowEvent::DroppedFile(arg.into())), time:integer::Time::ZERO,
instruction:Instruction::WindowEvent(winit::event::WindowEvent::DroppedFile(path)),
}).unwrap(); }).unwrap();
}; };
println!("Entering event loop..."); println!("Entering event loop...");
let mut app=crate::app::App::new( let root_time=std::time::Instant::now();
std::time::Instant::now(), run_event_loop(event_loop,window_thread,root_time).unwrap();
window_thread }
);
event_loop.run_app(&mut app).unwrap(); fn run_event_loop(
event_loop:winit::event_loop::EventLoop<()>,
mut window_thread:crate::compat_worker::QNWorker<TimedInstruction<Instruction,SessionTimeInner>>,
root_time:std::time::Instant
)->Result<(),winit::error::EventLoopError>{
event_loop.run(move |event,elwt|{
let time=integer::Time::from_nanos(root_time.elapsed().as_nanos() as i64);
// *control_flow=if cfg!(feature="metal-auto-capture"){
// winit::event_loop::ControlFlow::Exit
// }else{
// winit::event_loop::ControlFlow::Poll
// };
match event{
winit::event::Event::AboutToWait=>{
window_thread.send(TimedInstruction{time,instruction:Instruction::RequestRedraw}).unwrap();
}
winit::event::Event::WindowEvent {
event:
// WindowEvent::Resized(size)
// | WindowEvent::ScaleFactorChanged {
// new_inner_size: &mut size,
// ..
// },
winit::event::WindowEvent::Resized(size),//ignoring scale factor changed for now because mutex bruh
window_id:_,
} => {
window_thread.send(TimedInstruction{time,instruction:Instruction::Resize(size)}).unwrap();
}
winit::event::Event::WindowEvent{event,..}=>match event{
winit::event::WindowEvent::KeyboardInput{
event:
winit::event::KeyEvent {
logical_key: winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
state: winit::event::ElementState::Pressed,
..
},
..
}
|winit::event::WindowEvent::CloseRequested=>{
elwt.exit();
}
winit::event::WindowEvent::RedrawRequested=>{
window_thread.send(TimedInstruction{time,instruction:Instruction::Render}).unwrap();
}
_=>{
window_thread.send(TimedInstruction{time,instruction:Instruction::WindowEvent(event)}).unwrap();
}
},
winit::event::Event::DeviceEvent{
event,
..
} => {
window_thread.send(TimedInstruction{time,instruction:Instruction::DeviceEvent(event)}).unwrap();
},
_=>{}
}
})
} }

View File

@ -1,14 +1,15 @@
use strafesnet_common::instruction::TimedInstruction; use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
use strafesnet_common::physics::{MiscInstruction,SetControlInstruction}; use strafesnet_common::physics::{OtherInstruction,OtherOtherInstruction,SetControlInstruction};
use crate::file::LoadFormat;
use crate::physics_worker::Instruction as PhysicsWorkerInstruction; use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
use strafesnet_session::session::{self,SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction}; use crate::session::SessionInputInstruction;
use strafesnet_settings::directories::Directories;
pub enum Instruction{ pub enum Instruction{
Resize(winit::dpi::PhysicalSize<u32>),
WindowEvent(winit::event::WindowEvent), WindowEvent(winit::event::WindowEvent),
DeviceEvent(winit::event::DeviceEvent), DeviceEvent(winit::event::DeviceEvent),
RequestRedraw,
Render,
} }
//holds thread handles to dispatch to //holds thread handles to dispatch to
@ -28,16 +29,15 @@ impl WindowContext<'_>{
match event{ match event{
winit::event::WindowEvent::DroppedFile(path)=>{ winit::event::WindowEvent::DroppedFile(path)=>{
match crate::file::load(path.as_path()){ match crate::file::load(path.as_path()){
Ok(LoadFormat::Map(map))=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::ChangeMap(map)}).unwrap(), Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::ChangeMap(map)}).unwrap(),
Ok(LoadFormat::Bot(bot))=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::LoadReplay(bot)}).unwrap(), Err(e)=>println!("Failed to load map: {e}"),
Err(e)=>println!("Failed to load file: {e}"),
} }
}, },
winit::event::WindowEvent::Focused(state)=>{ winit::event::WindowEvent::Focused(state)=>{
//pause unpause //pause unpause
self.physics_thread.send(TimedInstruction{ self.physics_thread.send(TimedInstruction{
time, time,
instruction:PhysicsWorkerInstruction::SessionControl(SessionControlInstruction::SetPaused(!state)), instruction:PhysicsWorkerInstruction::SetPaused(!state),
}).unwrap(); }).unwrap();
//recalculate pressed keys on focus //recalculate pressed keys on focus
}, },
@ -92,98 +92,34 @@ impl WindowContext<'_>{
}, },
(keycode,state)=>{ (keycode,state)=>{
let s=state.is_pressed(); let s=state.is_pressed();
if let Some(session_input_instruction)=match keycode{
// internal variants for this scope winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(s))),
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(){ winit::keyboard::Key::Character(key)=>match key.as_str(){
"W"|"w"=>input_ctrl!(SetMoveForward,s), "W"|"w"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveForward(s))),
"A"|"a"=>input_ctrl!(SetMoveLeft,s), "A"|"a"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveLeft(s))),
"S"|"s"=>input_ctrl!(SetMoveBack,s), "S"|"s"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveBack(s))),
"D"|"d"=>input_ctrl!(SetMoveRight,s), "D"|"d"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveRight(s))),
"E"|"e"=>input_ctrl!(SetMoveUp,s), "E"|"e"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveUp(s))),
"Q"|"q"=>input_ctrl!(SetMoveDown,s), "Q"|"q"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetMoveDown(s))),
"Z"|"z"=>input_ctrl!(SetZoom,s), "Z"|"z"=>Some(SessionInputInstruction::SetControl(SetControlInstruction::SetZoom(s))),
"R"|"r"=>s.then(||{ "R"|"r"=>s.then(||{
//mouse needs to be reset since the position is absolute //mouse needs to be reset since the position is absolute
self.mouse_pos=glam::DVec2::ZERO; self.mouse_pos=glam::DVec2::ZERO;
SessionInstructionSubset::Input(SessionInputInstruction::Mode(session::ImplicitModeInstruction::ResetAndRestart)) SessionInputInstruction::Mode(crate::session::ImplicitModeInstruction::ResetAndRestart)
}), }),
"F"|"f"=>input_misc!(PracticeFly,s), "F"|"f"=>s.then_some(SessionInputInstruction::Other(OtherOtherInstruction::PracticeFly)),
"B"|"b"=>session_ctrl!(CopyRecordingIntoReplayAndSpectate,s),
"X"|"x"=>session_ctrl!(StopSpectate,s),
"N"|"n"=>session_ctrl!(SaveReplay,s),
"J"|"j"=>session_ctrl!(LoadIntoReplayState,s),
_=>None, _=>None,
}, },
_=>None, _=>None,
}{ }{
self.physics_thread.send(TimedInstruction{ self.physics_thread.send(TimedInstruction{
time, time,
instruction:session_instruction.into(), instruction:PhysicsWorkerInstruction::Input(session_input_instruction),
}).unwrap(); }).unwrap();
} }
}, },
} }
}, },
winit::event::WindowEvent::Resized(size)=>{
self.physics_thread.send(
TimedInstruction{
time,
instruction:PhysicsWorkerInstruction::Resize(size)
}
).unwrap();
},
winit::event::WindowEvent::RedrawRequested=>{
self.window.request_redraw();
self.physics_thread.send(
TimedInstruction{
time,
instruction:PhysicsWorkerInstruction::Render
}
).unwrap();
},
_=>(), _=>(),
} }
} }
@ -202,7 +138,7 @@ impl WindowContext<'_>{
self.mouse_pos+=glam::dvec2(delta.0,delta.1); self.mouse_pos+=glam::dvec2(delta.0,delta.1);
self.physics_thread.send(TimedInstruction{ self.physics_thread.send(TimedInstruction{
time, time,
instruction:PhysicsWorkerInstruction::SessionInput(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())), instruction:PhysicsWorkerInstruction::Input(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())),
}).unwrap(); }).unwrap();
}, },
winit::event::DeviceEvent::MouseWheel { winit::event::DeviceEvent::MouseWheel {
@ -212,7 +148,7 @@ impl WindowContext<'_>{
if false{//self.physics.style.use_scroll{ if false{//self.physics.style.use_scroll{
self.physics_thread.send(TimedInstruction{ self.physics_thread.send(TimedInstruction{
time, time,
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 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
}).unwrap(); }).unwrap();
} }
}, },
@ -225,14 +161,9 @@ pub fn worker<'a>(
setup_context:crate::setup::SetupContext<'a>, setup_context:crate::setup::SetupContext<'a>,
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{ )->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{
// WindowContextSetup::new // WindowContextSetup::new
#[cfg(feature="user-install")] let user_settings=crate::settings::read_user_settings();
let directories=Directories::user().unwrap();
#[cfg(not(feature="user-install"))]
let directories=Directories::portable().unwrap();
let user_settings=directories.settings(); let mut graphics=crate::graphics::GraphicsState::new(&setup_context.device,&setup_context.queue,&setup_context.config);
let mut graphics=strafesnet_graphics::graphics::GraphicsState::new(&setup_context.device,&setup_context.queue,&setup_context.config);
graphics.load_user_settings(&user_settings); graphics.load_user_settings(&user_settings);
//WindowContextSetup::into_context //WindowContextSetup::into_context
@ -246,7 +177,6 @@ pub fn worker<'a>(
window, window,
physics_thread:crate::physics_worker::new( physics_thread:crate::physics_worker::new(
graphics_thread, graphics_thread,
directories,
user_settings, user_settings,
), ),
}; };
@ -254,12 +184,31 @@ pub fn worker<'a>(
//WindowContextSetup::into_worker //WindowContextSetup::into_worker
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{ crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{
match ins.instruction{ match ins.instruction{
Instruction::RequestRedraw=>{
window_context.window.request_redraw();
}
Instruction::WindowEvent(window_event)=>{ Instruction::WindowEvent(window_event)=>{
window_context.window_event(ins.time,window_event); window_context.window_event(ins.time,window_event);
}, },
Instruction::DeviceEvent(device_event)=>{ Instruction::DeviceEvent(device_event)=>{
window_context.device_event(ins.time,device_event); window_context.device_event(ins.time,device_event);
}, },
Instruction::Resize(size)=>{
window_context.physics_thread.send(
TimedInstruction{
time:ins.time,
instruction:PhysicsWorkerInstruction::Resize(size)
}
).unwrap();
}
Instruction::Render=>{
window_context.physics_thread.send(
TimedInstruction{
time:ins.time,
instruction:PhysicsWorkerInstruction::Render
}
).unwrap();
}
} }
}) })
} }

View File

@ -176,7 +176,7 @@ impl<'a,Task:Send+'a> INWorker<'a,Task>{
#[cfg(test)] #[cfg(test)]
mod test{ mod test{
use super::{thread,QRWorker}; use super::{thread,QRWorker};
type Body=strafesnet_physics::physics::Body; type Body=crate::physics::Body;
use strafesnet_common::{integer,instruction}; use strafesnet_common::{integer,instruction};
#[test]//How to run this test with printing: cargo test --release -- --nocapture #[test]//How to run this test with printing: cargo test --release -- --nocapture
fn test_worker() { fn test_worker() {

View File

@ -1 +1 @@
mangohud ../target/release/strafe-client bhop_maps/5692113331.snfm "$@" mangohud ../target/release/strafe-client bhop_maps/5692113331.snfm

View File

@ -1 +1 @@
mangohud ../target/release/strafe-client bhop_maps/5692124338.snfm "$@" mangohud ../target/release/strafe-client bhop_maps/5692124338.snfm

View File

@ -1 +0,0 @@
/run/media/quat/Files/Documents/map-files/verify-scripts/replays

View File

@ -1 +1 @@
mangohud ../target/release/strafe-client "$@" mangohud ../target/release/strafe-client "$1"

View File

@ -1 +1 @@
mangohud ../target/release/strafe-client bhop_maps/5692152916.snfm "$@" mangohud ../target/release/strafe-client bhop_maps/5692152916.snfm

View File

@ -1 +1 @@
mangohud ../target/release/strafe-client surf_maps/5692145408.snfm "$@" mangohud ../target/release/strafe-client surf_maps/5692145408.snfm