Compare commits

...

31 Commits

Author SHA1 Message Date
96eb23d66b test resimulation 2024-08-05 18:43:27 -07:00
6e206f4207 fix sensitivity 2024-08-05 18:43:11 -07:00
c485c02b05 this doesn't need to be option 2024-08-05 17:25:11 -07:00
af478bce28 the mistake 2024-08-05 17:13:12 -07:00
a3ac815e45 leave tabs alone to make diffs legible 2024-08-05 17:08:43 -07:00
78cf5e155e yeah 2024-08-05 16:53:21 -07:00
26b286af31 wip 2024-08-05 16:23:11 -07:00
ad78fd22be move mouse state 2024-08-05 16:23:02 -07:00
3d0643e7cf level up 2024-08-05 16:15:25 -07:00
78d7af70a3 wip 2024-08-05 14:52:17 -07:00
b27ad3473e wip 2024-08-05 13:35:59 -07:00
0985661e45 move mouse and physics types to common 2024-08-05 13:35:54 -07:00
eb99ea3707 the api 2024-08-05 13:35:27 -07:00
8d4db7e654 move physics types to common 2024-08-02 13:47:02 -07:00
4eccd27237 this is how 2024-08-02 12:57:39 -07:00
a54836a9cb testing test 2024-08-02 12:14:26 -07:00
d3bede8b1c wip bot worker 2024-08-02 11:15:07 -07:00
8a13640c55 time travel warning 2024-08-02 10:42:10 -07:00
85ba12ff92 pause on focus 2024-08-02 10:42:10 -07:00
4f492d73b0 update deps 2024-08-02 10:42:10 -07:00
a8d54fdd7c minor tweak to loading map from arg 2024-08-02 09:20:34 -07:00
34ac541260 ignore ReachWalkTargetVelocity 2024-08-02 08:03:16 -07:00
099788b746 this is wrong, even when velocity is zero 2024-08-02 08:03:16 -07:00
305017c6c8 iso shortcut 2024-08-02 08:03:16 -07:00
e47d152af2 make a distinction between restart and spawning 2024-08-02 08:03:16 -07:00
755adeaefd refactor physics instruction processing
This is an important engine upgrade: idle events do not donate their timestamp to engine objects and pollute the timeline with unnecessary game ticks that can be represented as analytic continuations of previous game ticks.  This means that all "render" tick updates can be dropped from bot timelines.  In other words, progressing the physics simulation is invariant to differing subdivisions of an overall time advancement with no external input.
2024-08-02 08:03:16 -07:00
e04e754abb v0.10.2 run timer 2024-08-01 09:39:16 -07:00
5c1f69628d update deps 2024-08-01 09:36:11 -07:00
44031c8b83 simple run timer 2024-08-01 09:29:13 -07:00
d6470ee81b denormalize zone data on load 2024-08-01 09:29:09 -07:00
a7c7088c1f model is supposed to be guaranteed to exist 2024-07-31 12:08:57 -07:00
12 changed files with 508 additions and 305 deletions

70
Cargo.lock generated

@ -28,7 +28,7 @@ dependencies = [
"getrandom",
"once_cell",
"version_check",
"zerocopy",
"zerocopy 0.7.35",
]
[[package]]
@ -262,9 +262,9 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.16.1"
version = "1.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e"
checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83"
dependencies = [
"bytemuck_derive",
]
@ -294,9 +294,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.6.1"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]]
name = "calloop"
@ -802,9 +802,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.2.6"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
dependencies = [
"equivalent",
"hashbrown",
@ -1040,9 +1040,9 @@ dependencies = [
[[package]]
name = "naga"
version = "22.0.0"
version = "22.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09eeccb9b50f4f7839b214aa3e08be467159506a986c18e0702170ccf720a453"
checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad"
dependencies = [
"arrayvec",
"bit-set",
@ -1460,9 +1460,12 @@ checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f"
dependencies = [
"zerocopy 0.6.6",
]
[[package]]
name = "presser"
@ -1674,9 +1677,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.5"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
@ -1877,7 +1880,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strafe-client"
version = "0.10.1"
version = "0.10.2"
dependencies = [
"bytemuck",
"configparser",
@ -1909,9 +1912,9 @@ dependencies = [
[[package]]
name = "strafesnet_common"
version = "0.2.0"
version = "0.2.2"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "74580c59a09194ce39db49cd814a5c2fc2d61513c88c6b811b5b40c0da6de057"
checksum = "8d5459f1862aa23c514a6331e8b65cdee1d18b340d003ae03ebbfaa34c82e614"
dependencies = [
"bitflags 2.6.0",
"glam",
@ -2052,9 +2055,9 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.7"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
[[package]]
name = "toml_edit"
@ -2396,9 +2399,9 @@ dependencies = [
[[package]]
name = "wgpu"
version = "22.0.0"
version = "22.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87e07e87a179614940ad845397e03201847453a37b43a31a3b54eee2e6e32ce"
checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433"
dependencies = [
"arrayvec",
"cfg_aliases 0.1.1",
@ -2421,9 +2424,9 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "22.0.0"
version = "22.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f191908a21968991463fcf3b42cb6c9648c0fb7fa301b8fc733bc21a9ed9bd"
checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a"
dependencies = [
"arrayvec",
"bit-vec",
@ -2876,13 +2879,34 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193"
[[package]]
name = "zerocopy"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6"
dependencies = [
"byteorder 1.5.0",
"zerocopy-derive 0.6.6",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
"zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy-derive"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
]
[[package]]

@ -1,6 +1,6 @@
[package]
name = "strafe-client"
version = "0.10.1"
version = "0.10.2"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-client"
license = "Custom"
@ -23,7 +23,7 @@ id = { version = "0.1.0", registry = "strafesnet" }
parking_lot = "0.12.1"
pollster = "0.3.0"
strafesnet_bsp_loader = { version = "0.1.3", registry = "strafesnet", optional = true }
strafesnet_common = { version = "0.2.0", registry = "strafesnet" }
strafesnet_common = { version = "0.2.1", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.3.1", features = ["legacy"], registry = "strafesnet", optional = true }
strafesnet_rbx_loader = { version = "0.3.2", registry = "strafesnet", optional = true }
strafesnet_snf = { version = "0.1.0", registry = "strafesnet", optional = true }

31
src/bot_worker.rs Normal file

@ -0,0 +1,31 @@
use strafesnet_snf::bot::BotDebug;
pub enum Instruction{
//TODO: pass map id
Create,
//Delete,
Push{
ins:strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction>,
},
}
//a new worker is spawned on a thread
//create opens a new file
//push pushes an instruction to the file
pub struct Worker{
file:BotDebug,
}
pub fn new<'a>(scope:&'a std::thread::Scope<'a,'_>)->crate::worker::QNWorker<'a,Instruction>{
let mut worker=Worker{
file:BotDebug::new().unwrap(),
};
crate::worker::QNWorker::new(scope,move|instruction|{
match instruction{
Instruction::Create=>worker.file=BotDebug::new().unwrap(),
//Instruction::Delete=>worker.file.delete().unwrap(),
Instruction::Push{ins}=>worker.file.push(ins).unwrap(),
}
})
}

@ -816,7 +816,7 @@ impl GraphicsState{
});
let camera=GraphicsCamera::default();
let camera_uniforms=camera.to_uniform_data(crate::physics::PhysicsOutputState::default().extrapolate(crate::physics::MouseState::default()));
let camera_uniforms=camera.to_uniform_data(crate::physics::PhysicsOutputState::default().extrapolate(strafesnet_common::mouse::MouseState::default()));
let camera_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
label:Some("Camera"),
contents:bytemuck::cast_slice(&camera_uniforms),
@ -893,7 +893,7 @@ impl GraphicsState{
let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None});
// update rotation
let camera_uniforms=self.camera.to_uniform_data(physics_output.extrapolate(crate::physics::MouseState{pos:mouse_pos,time:predicted_time}));
let camera_uniforms=self.camera.to_uniform_data(physics_output.extrapolate(strafesnet_common::mouse::MouseState{pos:mouse_pos,time:predicted_time}));
self.staging_belt
.write_buffer(
&mut encoder,

@ -4,8 +4,7 @@ pub enum Instruction{
Render(crate::physics::PhysicsOutputState,integer::Time,glam::IVec2),
//UpdateModel(crate::graphics::GraphicsModelUpdate),
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
GenerateModels(strafesnet_common::map::CompleteMap),
ClearModels,
ChangeMap(strafesnet_common::map::CompleteMap),
}
//Ideally the graphics thread worker description is:
@ -27,11 +26,9 @@ pub fn new<'a>(
let mut resize=None;
crate::compat_worker::INWorker::new(move |ins:Instruction|{
match ins{
Instruction::GenerateModels(map)=>{
graphics.generate_models(&device,&queue,&map);
},
Instruction::ClearModels=>{
Instruction::ChangeMap(map)=>{
graphics.clear();
graphics.generate_models(&device,&queue,&map);
},
Instruction::Resize(size,user_settings)=>{
resize=Some((size,user_settings));
@ -69,4 +66,4 @@ pub fn new<'a>(
}
}
})
}
}

@ -5,6 +5,7 @@ mod worker;
mod physics;
mod graphics;
mod settings;
mod bot_worker;
mod face_crawler;
mod compat_worker;
mod model_physics;

@ -2,8 +2,10 @@ use std::collections::{HashMap,HashSet};
use crate::model_physics::{self,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId};
use strafesnet_common::bvh;
use strafesnet_common::map;
use strafesnet_common::run;
use strafesnet_common::aabb;
use strafesnet_common::model::{MeshId,ModelId};
use strafesnet_common::mouse::MouseState;
use strafesnet_common::gameplay_attributes::{self,CollisionAttributesId};
use strafesnet_common::gameplay_modes::{self,StageId};
use strafesnet_common::gameplay_style::{self,StyleModifiers};
@ -12,40 +14,26 @@ use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer
use strafesnet_common::integer::{self,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2};
use gameplay::ModeState;
//external influence
//this is how you influence the physics from outside
use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
//internal influence
//when the physics asks itself what happens next, this is how it's represented
#[derive(Debug)]
pub enum PhysicsInstruction {
enum PhysicsInternalInstruction{
CollisionStart(Collision),
CollisionEnd(Collision),
StrafeTick,
ReachWalkTargetVelocity,
// Water,
// Spawn(
// Option<SpawnId>,
// bool,//true = Trigger; false = teleport
// bool,//true = Force
// )
//InputInstructions conditionally activate RefreshWalkTarget (by doing what SetWalkTargetVelocity used to do and then flagging it)
Input(PhysicsInputInstruction),
SetSensitivity(Ratio64Vec2),
}
#[derive(Debug)]
pub enum PhysicsInputInstruction {
ReplaceMouse(MouseState,MouseState),
SetNextMouse(MouseState),
SetMoveRight(bool),
SetMoveUp(bool),
SetMoveBack(bool),
SetMoveLeft(bool),
SetMoveDown(bool),
SetMoveForward(bool),
SetJump(bool),
SetZoom(bool),
Reset,
Idle,
//Idle: there were no input events, but the simulation is safe to advance to this timestep
//for interpolation / networking / playback reasons, most playback heads will always want
//to be 1 instruction ahead to generate the next state for interpolation.
PracticeFly,
enum PhysicsInstruction{
Internal(PhysicsInternalInstruction),
//InputInstructions conditionally activate RefreshWalkTarget
//(by doing what SetWalkTargetVelocity used to do and then flagging it)
Input(PhysicsInputInstruction),
}
#[derive(Clone,Copy,Debug,Default,Hash)]
@ -67,32 +55,6 @@ impl std::ops::Neg for Body{
}
}
//hey dumbass just use a delta
#[derive(Clone,Debug)]
pub struct MouseState {
pub pos: glam::IVec2,
pub time:Time,
}
impl Default for MouseState{
fn default() -> Self {
Self {
time:Time::ZERO,
pos:glam::IVec2::ZERO,
}
}
}
impl MouseState {
pub fn lerp(&self,target:&MouseState,time:Time)->glam::IVec2 {
let m0=self.pos.as_i64vec2();
let m1=target.pos.as_i64vec2();
//these are deltas
let t1t=(target.time-time).nanos();
let tt0=(time-self.time).nanos();
let dt=(target.time-self.time).nanos();
((m0*t1t+m1*tt0)/dt).as_ivec2()
}
}
#[derive(Clone,Debug,Default)]
pub struct InputState{
mouse:MouseState,
@ -249,8 +211,8 @@ impl PhysicsModels{
&model.transform
)
}
fn model(&self,model_id:PhysicsModelId)->Option<&PhysicsModel>{
self.models.get(&model_id)
fn model(&self,model_id:PhysicsModelId)->&PhysicsModel{
&self.models[&model_id]
}
fn attr(&self,model_id:PhysicsModelId)->&PhysicsCollisionAttributes{
&self.attributes[&self.models[&model_id].attr_id]
@ -558,13 +520,13 @@ impl MoveState{
=>None,
}
}
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInstruction>>{
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
//check if you have a valid walk state and create an instruction
match self{
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
&TransientAcceleration::Reachable{acceleration:_,time}=>Some(TimedInstruction{
time,
instruction:PhysicsInstruction::ReachWalkTargetVelocity
instruction:PhysicsInternalInstruction::ReachWalkTargetVelocity
}),
TransientAcceleration::Unreachable{acceleration:_}
|TransientAcceleration::Reached
@ -574,7 +536,7 @@ impl MoveState{
TimedInstruction{
time:strafe.next_tick(time),
//only poll the physics if there is a before and after mouse event
instruction:PhysicsInstruction::StrafeTick
instruction:PhysicsInternalInstruction::StrafeTick
}
}),
MoveState::Water=>None,//TODO
@ -768,7 +730,7 @@ impl TouchingState{
}
}
}
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInstruction>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
let relative_body=VirtualBody::relative(&Body::default(),body).body(time);
for contact in &self.contacts{
//detect face slide off
@ -777,7 +739,7 @@ impl TouchingState{
collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{
TimedInstruction{
time,
instruction:PhysicsInstruction::CollisionEnd(
instruction:PhysicsInternalInstruction::CollisionEnd(
Collision::Contact(ContactCollision{convex_mesh_id:contact.convex_mesh_id,face_id:contact.face_id})
),
}
@ -790,7 +752,7 @@ impl TouchingState{
collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{
TimedInstruction{
time,
instruction:PhysicsInstruction::CollisionEnd(
instruction:PhysicsInternalInstruction::CollisionEnd(
Collision::Intersect(IntersectCollision{convex_mesh_id:intersect.convex_mesh_id})
),
}
@ -906,6 +868,10 @@ pub struct PhysicsState{
//gameplay_state
mode_state:ModeState,
move_state:MoveState,
//run is non optional: when you spawn in a run is created
//the run cannot be finished unless you start it by visiting
//a start zone. If you change mode, a new run is created.
run:run::Run,
}
//random collection of contextual data that doesn't belong in PhysicsState
pub struct PhysicsData{
@ -925,11 +891,12 @@ impl Default for PhysicsState{
time:Time::ZERO,
style:StyleModifiers::default(),
touching:TouchingState::default(),
move_state: MoveState::Air,
move_state:MoveState::Air,
camera:PhysicsCamera::default(),
input_state:InputState::default(),
world:WorldState{},
mode_state:ModeState::default(),
run:run::Run::new(),
}
}
}
@ -944,15 +911,16 @@ impl Default for PhysicsData{
}
}
impl PhysicsState {
impl PhysicsState{
fn clear(&mut self){
self.touching.clear();
}
fn advance_time(&mut self, time: Time){
self.body.advance_time(time);
self.time=time;
fn reset_to_default(&mut self){
let mut new_state=Self::default();
new_state.camera.sensitivity=self.camera.sensitivity;
*self=new_state;
}
fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInstruction>>{
fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInternalInstruction>>{
self.move_state.next_move_instruction(&self.style.strafe,self.time)
}
//lmao idk this is convenient
@ -1025,33 +993,19 @@ pub struct PhysicsContext{
state:PhysicsState,//this captures the entire state of the physics.
data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
}
//the physics consumes the generic PhysicsInstruction, but can only emit the more narrow PhysicsInternalInstruction
impl instruction::InstructionConsumer<PhysicsInstruction> for PhysicsContext{
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInstruction>){
atomic_state_update(&mut self.state,&self.data,ins)
}
}
impl instruction::InstructionEmitter<PhysicsInstruction> for PhysicsContext{
impl instruction::InstructionEmitter<PhysicsInternalInstruction> for PhysicsContext{
//this little next instruction function can cache its return value and invalidate the cached value by watching the State.
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInstruction>>{
literally_next_instruction_but_with_context(&self.state,&self.data,time_limit)
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
next_instruction_internal(&self.state,&self.data,time_limit)
}
}
impl PhysicsContext{
pub fn clear(&mut self){
self.state.clear();
}
pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){
self.process_instruction(TimedInstruction{
time:self.state.time,
instruction:PhysicsInstruction::SetSensitivity(user_settings.calculate_sensitivity()),
});
}
pub fn spawn(&mut self){
self.process_instruction(TimedInstruction{
time:self.state.time,
instruction:PhysicsInstruction::Input(PhysicsInputInstruction::Reset),
});
}
pub const fn output(&self)->PhysicsOutputState{
PhysicsOutputState{
body:self.state.body,
@ -1063,8 +1017,13 @@ impl PhysicsContext{
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
pub fn generate_models(&mut self,map:&map::CompleteMap){
self.state.clear();
self.data.modes=map.modes.clone();
for mode in &mut self.data.modes.modes{
mode.denormalize_data();
}
let mut used_attributes=Vec::new();
let mut physics_attr_id_from_model_attr_id=HashMap::<CollisionAttributesId,PhysicsAttributesId>::new();
let mut used_meshes=Vec::new();
@ -1132,16 +1091,19 @@ impl PhysicsContext{
}
//tickless gaming
fn run(&mut self,time_limit:Time){
fn run_internal_exhaustive(&mut self,time_limit:Time){
//prepare is ommitted - everything is done via instructions.
while let Some(instruction)=self.next_instruction(time_limit){//collect
//process
self.process_instruction(instruction);
self.process_instruction(TimedInstruction{
time:instruction.time,
instruction:PhysicsInstruction::Internal(instruction.instruction),
});
//write hash lol
}
}
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction>){
self.run(instruction.time);
self.run_internal_exhaustive(instruction.time);
self.process_instruction(TimedInstruction{
time:instruction.time,
instruction:PhysicsInstruction::Input(instruction.instruction),
@ -1149,7 +1111,8 @@ impl PhysicsContext{
}
}
fn literally_next_instruction_but_with_context(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<PhysicsInstruction>>{
//this is the one who asks
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
//JUST POLLING!!! NO MUTATION
let mut collector = instruction::InstructionCollector::new(time_limit);
@ -1171,7 +1134,7 @@ impl PhysicsContext{
//temp (?) code to avoid collision loops
.map_or(None,|(face,time)|if time==state.time{None}else{Some((face,time))})
.map(|(face,time)|{
TimedInstruction{time,instruction:PhysicsInstruction::CollisionStart(match data.models.attr(convex_mesh_id.model_id){
TimedInstruction{time,instruction:PhysicsInternalInstruction::CollisionStart(match data.models.attr(convex_mesh_id.model_id){
PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>Collision::Contact(ContactCollision{convex_mesh_id,face_id:face}),
PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{convex_mesh_id}),
})}
@ -1242,7 +1205,7 @@ fn teleport(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,sty
MoveState::Air
}
fn teleport_to_spawn(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,mode:&gameplay_modes::Mode,models:&PhysicsModels,stage_id:gameplay_modes::StageId)->Option<MoveState>{
let model=models.model(mode.get_spawn_model_id(stage_id)?.into()).unwrap();
let model=models.model(mode.get_spawn_model_id(stage_id)?.into());
let point=model.transform.vertex.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16);
Some(teleport(body,touching,models,style,hitbox_mesh,point))
}
@ -1298,8 +1261,8 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
}
match wormhole{
&Some(gameplay_attributes::Wormhole{destination_model})=>{
let origin_model=models.model(convex_mesh_id.model_id).unwrap();
let destination_model=models.model(destination_model.into()).unwrap();
let origin_model=models.model(convex_mesh_id.model_id);
let destination_model=models.model(destination_model.into());
//ignore the transform for now
Some(teleport(body,touching,models,style,hitbox_mesh,body.position-origin_model.transform.vertex.translation+destination_model.transform.vertex.translation))
}
@ -1307,27 +1270,18 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
}
}
fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInstruction>){
match &ins.instruction{
PhysicsInstruction::Input(PhysicsInputInstruction::Idle)
|PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_))
|PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_))
|PhysicsInstruction::StrafeTick=>(),
_=>println!("{}|{:?}",ins.time,ins.instruction),
}
//selectively update body
match &ins.instruction{
PhysicsInstruction::Input(PhysicsInputInstruction::Idle)=>state.time=ins.time,//idle simply updates time
PhysicsInstruction::Input(_)
|PhysicsInstruction::ReachWalkTargetVelocity
|PhysicsInstruction::CollisionStart(_)
|PhysicsInstruction::CollisionEnd(_)
|PhysicsInstruction::StrafeTick
|PhysicsInstruction::SetSensitivity(_)
=>state.advance_time(ins.time),
}
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction>){
let should_advance_body=match ins.instruction{
PhysicsInternalInstruction::CollisionStart(_)
|PhysicsInternalInstruction::CollisionEnd(_)
|PhysicsInternalInstruction::StrafeTick
|PhysicsInternalInstruction::ReachWalkTargetVelocity=>true,
};
if should_advance_body{
state.body.advance_time(state.time);
}
match ins.instruction{
PhysicsInstruction::CollisionStart(collision)=>{
PhysicsInternalInstruction::CollisionStart(collision)=>{
let convex_mesh_id=collision.convex_mesh_id();
match (data.models.attr(convex_mesh_id.model_id),&collision){
(PhysicsCollisionAttributes::Contact{contacting,general},&Collision::Contact(contact))=>{
@ -1411,17 +1365,32 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
//doing input_and_body to refresh the walk state if you hit a wall while accelerating
state.apply_enum_and_input_and_body(data);
},
(PhysicsCollisionAttributes::Intersect{intersecting: _,general},Collision::Intersect(intersect))=>{
(PhysicsCollisionAttributes::Intersect{intersecting:_,general},Collision::Intersect(_intersect))=>{
//I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop
state.touching.insert(collision);
if let Some(mode)=data.modes.get_mode(state.mode_state.get_mode_id()){
let zone=mode.get_zone(convex_mesh_id.model_id.into());
match zone{
Some(gameplay_modes::Zone::Start)=>{
println!("@@@@ Starting new run!");
state.run=run::Run::new();
},
Some(gameplay_modes::Zone::Finish)=>{
match state.run.finish(state.time){
Ok(())=>println!("@@@@ Finished run time={}",state.run.time(state.time)),
Err(e)=>println!("@@@@ Run Finish error:{e:?}"),
}
},
Some(gameplay_modes::Zone::Anticheat)=>state.run.flag(run::FlagReason::Anticheat),
None=>(),
}
run_teleport_behaviour(&general.wormhole,&data.models,mode,&state.style,&data.hitbox_mesh,&mut state.mode_state,&mut state.touching,&mut state.body,convex_mesh_id);
}
},
_=>panic!("invalid pair"),
}
},
PhysicsInstruction::CollisionEnd(collision)=>{
PhysicsInternalInstruction::CollisionEnd(collision)=>{
match (data.models.attr(collision.convex_mesh_id().model_id),&collision){
(PhysicsCollisionAttributes::Contact{contacting:_,general:_},&Collision::Contact(contact))=>{
state.touching.remove(&collision);//remove contact before calling contact_constrain_acceleration
@ -1438,11 +1407,23 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
},
(PhysicsCollisionAttributes::Intersect{intersecting: _,general:_},Collision::Intersect(_))=>{
state.touching.remove(&collision);
if let Some(mode)=data.modes.get_mode(state.mode_state.get_mode_id()){
let zone=mode.get_zone(collision.convex_mesh_id().model_id.into());
match zone{
Some(gameplay_modes::Zone::Start)=>{
match state.run.start(state.time){
Ok(())=>println!("@@@@ Started run"),
Err(e)=>println!("@@@@ Run Start error:{e:?}"),
}
},
_=>(),
}
}
},
_=>panic!("invalid pair"),
}
},
PhysicsInstruction::StrafeTick=>{
PhysicsInternalInstruction::StrafeTick=>{
//TODO make this less huge
if let Some(strafe_settings)=&state.style.strafe{
let controls=state.input_state.controls;
@ -1460,7 +1441,7 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
}
}
}
PhysicsInstruction::ReachWalkTargetVelocity=>{
PhysicsInternalInstruction::ReachWalkTargetVelocity=>{
match &mut state.move_state{
MoveState::Air
|MoveState::Water
@ -1484,10 +1465,47 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
}
}
},
PhysicsInstruction::SetSensitivity(sensitivity)=>state.camera.sensitivity=sensitivity,
PhysicsInstruction::Input(input_instruction)=>{
}
}
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInputInstruction>){
let should_advance_body=match ins.instruction{
//the body may as well be a quantum wave function
//as far as these instruction are concerned (they don't care where it is)
PhysicsInputInstruction::SetSensitivity(..)
|PhysicsInputInstruction::Reset
|PhysicsInputInstruction::Restart
|PhysicsInputInstruction::Spawn(..)
|PhysicsInputInstruction::SetZoom(..)
|PhysicsInputInstruction::Idle=>false,
//these controls only update the body if you are on the ground
PhysicsInputInstruction::SetNextMouse(..)
|PhysicsInputInstruction::ReplaceMouse(..)
|PhysicsInputInstruction::SetMoveForward(..)
|PhysicsInputInstruction::SetMoveLeft(..)
|PhysicsInputInstruction::SetMoveBack(..)
|PhysicsInputInstruction::SetMoveRight(..)
|PhysicsInputInstruction::SetMoveUp(..)
|PhysicsInputInstruction::SetMoveDown(..)
|PhysicsInputInstruction::SetJump(..)=>{
match &state.move_state{
MoveState::Fly
|MoveState::Water
|MoveState::Walk(_)
|MoveState::Ladder(_)=>true,
MoveState::Air=>false,
}
},
//the body must be updated unconditionally
PhysicsInputInstruction::PracticeFly=>true,
};
if should_advance_body{
state.body.advance_time(state.time);
}
//TODO: UNTAB
let mut b_refresh_walk_target=true;
match input_instruction{
match ins.instruction{
PhysicsInputInstruction::SetSensitivity(sensitivity)=>state.camera.sensitivity=sensitivity,
PhysicsInputInstruction::SetNextMouse(m)=>{
state.camera.move_mouse(state.input_state.mouse_delta());
state.input_state.set_next_mouse(m);
@ -1503,7 +1521,6 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
PhysicsInputInstruction::SetMoveUp(s)=>state.input_state.set_control(Controls::MoveUp,s),
PhysicsInputInstruction::SetMoveDown(s)=>state.input_state.set_control(Controls::MoveDown,s),
PhysicsInputInstruction::SetJump(s)=>{
b_refresh_walk_target=false;
state.input_state.set_control(Controls::Jump,s);
if let Some(walk_state)=state.move_state.get_walk_state(){
if let Some(jump_settings)=&state.style.jump{
@ -1512,23 +1529,35 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
state.cull_velocity(&data,jumped_velocity);
}
}
b_refresh_walk_target=false;
},
PhysicsInputInstruction::SetZoom(s)=>{
state.input_state.set_control(Controls::Zoom,s);
b_refresh_walk_target=false;
},
PhysicsInputInstruction::Reset=>{
//it matters which of these runs first, but I have not thought it through yet as it doesn't matter yet
state.mode_state.clear();
state.mode_state.set_stage_id(gameplay_modes::StageId::FIRST);
let spawn_point=data.modes.get_mode(state.mode_state.get_mode_id()).and_then(|mode|
//totally reset physics state
state.reset_to_default();
b_refresh_walk_target=false;
},
PhysicsInputInstruction::Restart=>{
//teleport to start zone
let spawn_point=data.modes.get_mode(state.mode_state.get_mode_id()).map(|mode|
//TODO: spawn at the bottom of the start zone plus the hitbox size
data.models.model(mode.get_start().into()).map(|model|model.transform.vertex.translation)
//TODO: set camera andles to face the same way as the start zone
data.models.model(mode.get_start().into()).transform.vertex.translation
).unwrap_or(Planar64Vec3::ZERO);
set_position(&mut state.body,&mut state.touching,spawn_point);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,Planar64Vec3::ZERO);
state.set_move_state(data,MoveState::Air);
b_refresh_walk_target=false;
}
PhysicsInputInstruction::Spawn(mode_id,stage_id)=>{
//spawn at a particular stage
if let Some(mode)=data.modes.get_mode(mode_id){
teleport_to_spawn(&mut state.body,&mut state.touching,&state.style,&data.hitbox_mesh,mode,&data.models,stage_id);
}
b_refresh_walk_target=false;
},
PhysicsInputInstruction::PracticeFly=>{
match &state.move_state{
@ -1541,13 +1570,33 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
}
b_refresh_walk_target=false;
},
PhysicsInputInstruction::Idle=>{b_refresh_walk_target=false;},//literally idle!
PhysicsInputInstruction::Idle=>{
//literally idle!
b_refresh_walk_target=false;
},
}
if b_refresh_walk_target{
state.apply_input_and_body(data);
state.cull_velocity(data,state.body.velocity);
}
},
}
fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInstruction>){
match &ins.instruction{
PhysicsInstruction::Input(PhysicsInputInstruction::Idle)
|PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_))
|PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_))
|PhysicsInstruction::Internal(PhysicsInternalInstruction::StrafeTick)
|PhysicsInstruction::Internal(PhysicsInternalInstruction::ReachWalkTargetVelocity)=>(),
_=>println!("{}|{:?}",ins.time,ins.instruction),
}
if ins.time<state.time{
println!("@@@@ Time travel warning! {:?}",ins);
}
state.time=ins.time;
match ins.instruction{
PhysicsInstruction::Internal(instruction)=>atomic_internal_instruction(state,data,TimedInstruction{time:ins.time,instruction}),
PhysicsInstruction::Input(instruction)=>atomic_input_instruction(state,data,TimedInstruction{time:ins.time,instruction}),
}
}
@ -1765,4 +1814,38 @@ mod test{
Time::ZERO
),None);
}
fn simulate(map:&str,bot:&str){
//create physics
let mut physics=PhysicsContext::default();
//load map
let map_file=std::fs::File::open(format!("/run/media/quat/Files/Documents/map-files/verify-scripts/maps/bhop_snfm/{map}.snfm")).unwrap();
let map=strafesnet_snf::read_map(map_file).unwrap().into_complete_map().unwrap();
physics.generate_models(&map);
//load bot
let bot_file=std::fs::File::open(format!("/home/quat/strafesnet/strafe-client/tools/debug_bots/{bot}")).unwrap();
let instructions=strafesnet_snf::bot::read_bot_debug(bot_file).unwrap();
//run bot on physics
for ins in instructions{
physics.run_input_instruction(ins);
}
}
#[test]
fn simulate_bot_1(){
simulate("5692113331","ff4940efb50f724e48eb54ce3593d88f")
}
#[test]
fn simulate_bot_2(){
simulate("5692113331","17cf70412eba16d172a67385cab5727e")
}
#[test]
fn simulate_bot_3(){
simulate("5692176057","f0fe0dc2fda5f8b59a658e82d26fc69")
}
#[test]
fn simulate_bot_4(){
simulate("5692176057","c0631c6f524eebddbf75237cac48e78e")
}
}

@ -1,6 +1,10 @@
use crate::physics::{MouseState,PhysicsInputInstruction};
use strafesnet_common::mouse::MouseState;
use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
use strafesnet_common::integer::Time;
use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer};
use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::timer::{Scaled,Timer,TimerState};
use mouse_interpolator::MouseInterpolator;
#[derive(Debug)]
pub enum InputInstruction{
MoveMouse(glam::IVec2),
@ -12,29 +16,57 @@ pub enum InputInstruction{
MoveForward(bool),
Jump(bool),
Zoom(bool),
Reset,
ResetAndRestart,
ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId,strafesnet_common::gameplay_modes::StageId),
PracticeFly,
}
pub enum Instruction{
Input(InputInstruction),
Render,
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
GenerateModels(strafesnet_common::map::CompleteMap),
ClearModels,
Resize(winit::dpi::PhysicalSize<u32>),
ChangeMap(strafesnet_common::map::CompleteMap),
//SetPaused is not an InputInstruction: the physics doesn't know that it's paused.
SetPaused(bool),
//Graphics(crate::graphics_worker::Instruction),
}
pub struct MouseInterpolator{
mod mouse_interpolator{
use super::*;
//TODO: move this or tab
pub struct MouseInterpolator<'a>{
//"PlayerController"
timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction>>,
last_mouse_time:Time,
last_mouse_time:Time,//this value is pre-transformed to simulation time
mouse_blocking:bool,
//????
bot_worker:crate::worker::QNWorker<'a,crate::bot_worker::Instruction>,
user_settings:crate::settings::UserSettings,
//"Simulation"
timer:Timer<Scaled>,
physics:crate::physics::PhysicsContext,
}
impl MouseInterpolator{
fn push_mouse_instruction(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>,m:glam::IVec2){
impl MouseInterpolator<'_>{
pub fn new<'a>(
physics:crate::physics::PhysicsContext,
bot_worker:crate::worker::QNWorker<'a,crate::bot_worker::Instruction>,
user_settings:crate::settings::UserSettings,
)->MouseInterpolator<'a>{
MouseInterpolator{
mouse_blocking:true,
last_mouse_time:physics.get_next_mouse().time,
timeline:std::collections::VecDeque::new(),
timer:Timer::from_state(Scaled::identity(),false),
physics,
bot_worker,
user_settings,
}
}
fn push_mouse_instruction(&mut self,ins:&TimedInstruction<Instruction>,m:glam::IVec2){
if self.mouse_blocking{
//tell the game state which is living in the past about its future
self.timeline.push_front(TimedInstruction{
time:self.last_mouse_time,
instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:ins.time,pos:m}),
instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:self.timer.time(ins.time),pos:m}),
});
}else{
//mouse has just started moving again after being still for longer than 10ms.
@ -42,56 +74,93 @@ impl MouseInterpolator{
self.timeline.push_front(TimedInstruction{
time:self.last_mouse_time,
instruction:PhysicsInputInstruction::ReplaceMouse(
MouseState{time:self.last_mouse_time,pos:physics.get_next_mouse().pos},
MouseState{time:ins.time,pos:m}
MouseState{time:self.last_mouse_time,pos:self.physics.get_next_mouse().pos},
MouseState{time:self.timer.time(ins.time),pos:m}
),
});
//delay physics execution until we have an interpolation target
self.mouse_blocking=true;
}
self.last_mouse_time=ins.time;
self.last_mouse_time=self.timer.time(ins.time);
}
/// returns the mapped physics input instruction
fn push(&mut self,time:Time,phys_input:PhysicsInputInstruction){
//This is always a non-mouse event
self.timeline.push_back(TimedInstruction{
time:self.timer.time(time),
instruction:phys_input,
});
}
/// returns should_empty_queue
/// may or may not mutate internal state XD!
fn map_instruction(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>)->Option<PhysicsInputInstruction>{
fn map_instruction(&mut self,ins:&TimedInstruction<Instruction>)->bool{
let mut update_mouse_blocking=true;
match &ins.instruction{
Instruction::Input(input_instruction)=>match input_instruction{
&InputInstruction::MoveMouse(m)=>{
self.push_mouse_instruction(physics,ins,m);
None
if !self.timer.is_paused(){
self.push_mouse_instruction(ins,m);
}
update_mouse_blocking=false;
},
&InputInstruction::MoveForward(s)=>Some(PhysicsInputInstruction::SetMoveForward(s)),
&InputInstruction::MoveLeft(s)=>Some(PhysicsInputInstruction::SetMoveLeft(s)),
&InputInstruction::MoveBack(s)=>Some(PhysicsInputInstruction::SetMoveBack(s)),
&InputInstruction::MoveRight(s)=>Some(PhysicsInputInstruction::SetMoveRight(s)),
&InputInstruction::MoveUp(s)=>Some(PhysicsInputInstruction::SetMoveUp(s)),
&InputInstruction::MoveDown(s)=>Some(PhysicsInputInstruction::SetMoveDown(s)),
&InputInstruction::Jump(s)=>Some(PhysicsInputInstruction::SetJump(s)),
&InputInstruction::Zoom(s)=>Some(PhysicsInputInstruction::SetZoom(s)),
InputInstruction::Reset=>Some(PhysicsInputInstruction::Reset),
InputInstruction::PracticeFly=>Some(PhysicsInputInstruction::PracticeFly),
&InputInstruction::MoveForward(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveForward(s)),
&InputInstruction::MoveLeft(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveLeft(s)),
&InputInstruction::MoveBack(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveBack(s)),
&InputInstruction::MoveRight(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveRight(s)),
&InputInstruction::MoveUp(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveUp(s)),
&InputInstruction::MoveDown(s)=>self.push(ins.time,PhysicsInputInstruction::SetMoveDown(s)),
&InputInstruction::Jump(s)=>self.push(ins.time,PhysicsInputInstruction::SetJump(s)),
&InputInstruction::Zoom(s)=>self.push(ins.time,PhysicsInputInstruction::SetZoom(s)),
&InputInstruction::ResetAndSpawn(mode_id,stage_id)=>{
self.push(ins.time,PhysicsInputInstruction::Reset);
self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity()));
self.push(ins.time,PhysicsInputInstruction::Spawn(mode_id,stage_id));
},
InputInstruction::ResetAndRestart=>{
self.push(ins.time,PhysicsInputInstruction::Reset);
self.push(ins.time,PhysicsInputInstruction::SetSensitivity(self.user_settings.calculate_sensitivity()));
self.push(ins.time,PhysicsInputInstruction::Restart);
},
InputInstruction::PracticeFly=>self.push(ins.time,PhysicsInputInstruction::PracticeFly),
},
Instruction::GenerateModels(_)=>Some(PhysicsInputInstruction::Idle),
Instruction::ClearModels=>Some(PhysicsInputInstruction::Idle),
Instruction::Resize(_,_)=>Some(PhysicsInputInstruction::Idle),
Instruction::Render=>Some(PhysicsInputInstruction::Idle),
//do these really need to idle the physics?
//sending None dumps the instruction queue
Instruction::ChangeMap(_)=>self.push(ins.time,PhysicsInputInstruction::Idle),
Instruction::Resize(_)=>self.push(ins.time,PhysicsInputInstruction::Idle),
Instruction::Render=>self.push(ins.time,PhysicsInputInstruction::Idle),
&Instruction::SetPaused(paused)=>{
if let Err(e)=self.timer.set_paused(ins.time,paused){
println!("Cannot pause: {e}");
}
self.push(ins.time,PhysicsInputInstruction::Idle);
},
}
if update_mouse_blocking{
//this returns the bool for us
self.update_mouse_blocking(ins.time)
}else{
//do flush that queue
true
}
}
fn update_mouse_blocking(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>)->bool{
/// must check if self.mouse_blocking==true before calling!
fn unblock_mouse(&mut self,time:Time){
//push an event to extrapolate no movement from
self.timeline.push_front(TimedInstruction{
time:self.last_mouse_time,
instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:self.timer.time(time),pos:self.physics.get_next_mouse().pos}),
});
self.last_mouse_time=self.timer.time(time);
//stop blocking. the mouse is not moving so the physics does not need to live in the past and wait for interpolation targets.
self.mouse_blocking=false;
}
fn update_mouse_blocking(&mut self,time:Time)->bool{
if self.mouse_blocking{
//assume the mouse has stopped moving after 10ms.
//shitty mice are 125Hz which is 8ms so this should cover that.
//setting this to 100us still doesn't print even though it's 10x lower than the polling rate,
//so mouse events are probably not handled separately from drawing and fire right before it :(
if Time::from_millis(10)<ins.time-physics.get_next_mouse().time{
//push an event to extrapolate no movement from
self.timeline.push_front(TimedInstruction{
time:self.last_mouse_time,
instruction:PhysicsInputInstruction::SetNextMouse(MouseState{time:ins.time,pos:physics.get_next_mouse().pos}),
});
self.last_mouse_time=ins.time;
//stop blocking. the mouse is not moving so the physics does not need to live in the past and wait for interpolation targets.
self.mouse_blocking=false;
if Time::from_millis(10)<self.timer.time(time)-self.physics.get_next_mouse().time{
self.unblock_mouse(time);
true
}else{
false
@ -99,65 +168,79 @@ impl MouseInterpolator{
}else{
//keep this up to date so that it can be used as a known-timestamp
//that the mouse was not moving when the mouse starts moving again
self.last_mouse_time=ins.time;
self.last_mouse_time=self.timer.time(time);
true
}
}
/// returns whether or not to empty the instruction queue
fn handle_physics_input(&mut self,physics:&crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>,phys_input_option:Option<PhysicsInputInstruction>)->bool{
if let Some(phys_input)=phys_input_option{
//non-mouse event
self.timeline.push_back(TimedInstruction{
time:ins.time,
instruction:phys_input,
});
//this returns the bool for us
self.update_mouse_blocking(physics,ins)
}else{
//mouse event
true
}
}
fn empty_queue(&mut self,physics:&mut crate::physics::PhysicsContext){
fn empty_queue(&mut self){
while let Some(instruction)=self.timeline.pop_front(){
physics.run_input_instruction(instruction);
//makeshift clone because requiring all TimedInstructions to be Clone is troublesome
self.bot_worker.send(crate::bot_worker::Instruction::Push{
ins:TimedInstruction{
time:instruction.time,
instruction:instruction.instruction.clone(),
}
}).unwrap();
self.physics.run_input_instruction(instruction);
}
}
fn handle_instruction(&mut self,physics:&mut crate::physics::PhysicsContext,ins:&TimedInstruction<Instruction>){
let physics_input_option=self.map_instruction(physics,ins);
let should_empty_queue=self.handle_physics_input(physics,ins,physics_input_option);
pub fn handle_instruction(&mut self,ins:&TimedInstruction<Instruction>){
let should_empty_queue=self.map_instruction(ins);
if should_empty_queue{
self.empty_queue(physics);
self.empty_queue();
}
}
pub fn get_render_stuff(&self,time:Time)->(crate::physics::PhysicsOutputState,Time,glam::IVec2){
(self.physics.output(),self.timer.time(time),self.physics.get_next_mouse().pos)
}
pub fn change_map(&mut self,time:Time,map:&strafesnet_common::map::CompleteMap){
//dump any pending interpolation state
if self.mouse_blocking{
self.unblock_mouse(time);
}
self.empty_queue();
//doing it like this to avoid doing PhysicsInstruction::ChangeMap(Rc<CompleteMap>)
self.physics.generate_models(&map);
//use the standard input interface so the instructions are written out to bots
self.handle_instruction(&TimedInstruction{
time:self.timer.time(time),
instruction:Instruction::Input(InputInstruction::ResetAndSpawn(
strafesnet_common::gameplay_modes::ModeId::MAIN,
strafesnet_common::gameplay_modes::StageId::FIRST,
)),
});
}
pub const fn user_settings(&self)->&crate::settings::UserSettings{
&self.user_settings
}
}
}
pub fn new(mut physics:crate::physics::PhysicsContext,mut graphics_worker:crate::compat_worker::INWorker<crate::graphics_worker::Instruction>)->crate::compat_worker::QNWorker<TimedInstruction<Instruction>>{
let mut interpolator=MouseInterpolator{
mouse_blocking:true,
last_mouse_time:physics.get_next_mouse().time,
timeline:std::collections::VecDeque::new(),
};
pub fn new<'a>(
mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>,
bot_worker:crate::worker::QNWorker<'a,crate::bot_worker::Instruction>,
user_settings:crate::settings::UserSettings,
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction>>{
let physics=crate::physics::PhysicsContext::default();
let mut interpolator=MouseInterpolator::new(physics,bot_worker,user_settings);
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction>|{
interpolator.handle_instruction(&mut physics,&ins);
interpolator.handle_instruction(&ins);
match ins.instruction{
Instruction::Render=>{
graphics_worker.send(crate::graphics_worker::Instruction::Render(physics.output(),ins.time,physics.get_next_mouse().pos)).unwrap();
let (physics_output,time,mouse_pos)=interpolator.get_render_stuff(ins.time);
graphics_worker.send(crate::graphics_worker::Instruction::Render(physics_output,time,mouse_pos)).unwrap();
},
Instruction::Resize(size,user_settings)=>{
graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,user_settings)).unwrap();
Instruction::Resize(size)=>{
graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,interpolator.user_settings().clone())).unwrap();
},
Instruction::GenerateModels(map)=>{
physics.generate_models(&map);
physics.spawn();
graphics_worker.send(crate::graphics_worker::Instruction::GenerateModels(map)).unwrap();
Instruction::ChangeMap(map)=>{
interpolator.change_map(ins.time,&map);
graphics_worker.send(crate::graphics_worker::Instruction::ChangeMap(map)).unwrap();
},
Instruction::ClearModels=>{
physics.clear();
graphics_worker.send(crate::graphics_worker::Instruction::ClearModels).unwrap();
},
_=>(),
Instruction::Input(_)=>(),
Instruction::SetPaused(_)=>(),
}
})
}

@ -213,22 +213,22 @@ pub fn setup_and_start(title:String){
//dedicated thread to ping request redraw back and resize the window doesn't seem logical
let window=crate::window::WindowContextSetup::new(&setup_context,&window);
//the thread that spawns the physics thread
let mut window_thread=window.into_worker(setup_context);
std::thread::scope(|scope|{
//the thread that spawns the physics thread
let mut window_thread=crate::window::worker(&window,setup_context,scope);
let args:Vec<String>=std::env::args().collect();
if args.len()==2{
let path=std::path::PathBuf::from(&args[1]);
window_thread.send(TimedInstruction{
time:integer::Time::ZERO,
instruction:WindowInstruction::WindowEvent(winit::event::WindowEvent::DroppedFile(path)),
}).unwrap();
};
if let Some(arg)=std::env::args().nth(1){
let path=std::path::PathBuf::from(arg);
window_thread.send(TimedInstruction{
time:integer::Time::ZERO,
instruction:WindowInstruction::WindowEvent(winit::event::WindowEvent::DroppedFile(path)),
}).unwrap();
};
println!("Entering event loop...");
let root_time=std::time::Instant::now();
run_event_loop(event_loop,window_thread,root_time).unwrap();
println!("Entering event loop...");
let root_time=std::time::Instant::now();
run_event_loop(event_loop,window_thread,root_time).unwrap();
});
}
fn run_event_loop(

@ -13,9 +13,8 @@ pub enum WindowInstruction{
//holds thread handles to dispatch to
struct WindowContext<'a>{
manual_mouse_lock:bool,
mouse:crate::physics::MouseState,//std::sync::Arc<std::sync::Mutex<>>
mouse:strafesnet_common::mouse::MouseState,//std::sync::Arc<std::sync::Mutex<>>
screen_size:glam::UVec2,
user_settings:crate::settings::UserSettings,
window:&'a winit::window::Window,
physics_thread:crate::compat_worker::QNWorker<'a, TimedInstruction<crate::physics_worker::Instruction>>,
}
@ -28,15 +27,16 @@ impl WindowContext<'_>{
match event {
winit::event::WindowEvent::DroppedFile(path)=>{
match crate::file::load(path.as_path()){
Ok(map)=>{
self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::ClearModels}).unwrap();
self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::GenerateModels(map)}).unwrap();
},
Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::ChangeMap(map)}).unwrap(),
Err(e)=>println!("Failed to load map: {e}"),
}
},
winit::event::WindowEvent::Focused(_state)=>{
winit::event::WindowEvent::Focused(state)=>{
//pause unpause
self.physics_thread.send(TimedInstruction{
time,
instruction:crate::physics_worker::Instruction::SetPaused(!state),
}).unwrap();
//recalculate pressed keys on focus
},
winit::event::WindowEvent::KeyboardInput{
@ -107,7 +107,11 @@ impl WindowContext<'_>{
"e"=>Some(InputInstruction::MoveUp(s)),
"q"=>Some(InputInstruction::MoveDown(s)),
"z"=>Some(InputInstruction::Zoom(s)),
"r"=>if s{Some(InputInstruction::Reset)}else{None},
"r"=>if s{
//mouse needs to be reset since the position is absolute
self.mouse=strafesnet_common::mouse::MouseState::default();
Some(InputInstruction::ResetAndRestart)
}else{None},
"f"=>if s{Some(InputInstruction::PracticeFly)}else{None},
_=>None,
},
@ -161,48 +165,28 @@ impl WindowContext<'_>{
}
}
}
pub struct WindowContextSetup<'a>{
user_settings:crate::settings::UserSettings,
window:&'a winit::window::Window,
physics:crate::physics::PhysicsContext,
graphics:crate::graphics::GraphicsState,
}
impl<'a> WindowContextSetup<'a>{
pub fn new(context:&crate::setup::SetupContext,window:&'a winit::window::Window)->Self{
pub fn worker<'a>(window:&'a winit::window::Window,setup_context:crate::setup::SetupContext<'a>,scope:&'a std::thread::Scope<'a,'_>)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction>>{
// WindowContextSetup::new
let user_settings=crate::settings::read_user_settings();
let mut physics=crate::physics::PhysicsContext::default();
physics.load_user_settings(&user_settings);
let mut graphics=crate::graphics::GraphicsState::new(&context.device,&context.queue,&context.config);
let mut graphics=crate::graphics::GraphicsState::new(&setup_context.device,&setup_context.queue,&setup_context.config);
graphics.load_user_settings(&user_settings);
Self{
user_settings,
window,
graphics,
physics,
}
}
fn into_context(self,setup_context:crate::setup::SetupContext<'a>)->WindowContext<'a>{
//WindowContextSetup::into_context
let screen_size=glam::uvec2(setup_context.config.width,setup_context.config.height);
let graphics_thread=crate::graphics_worker::new(self.graphics,setup_context.config,setup_context.surface,setup_context.device,setup_context.queue);
WindowContext{
let graphics_thread=crate::graphics_worker::new(graphics,setup_context.config,setup_context.surface,setup_context.device,setup_context.queue);
//this obviously doesn't belong here, do something about it pls
let bot_thread=crate::bot_worker::new(scope);
let mut window_context=WindowContext{
manual_mouse_lock:false,
mouse:crate::physics::MouseState::default(),
mouse:strafesnet_common::mouse::MouseState::default(),
//make sure to update this!!!!!
screen_size,
user_settings:self.user_settings,
window:self.window,
physics_thread:crate::physics_worker::new(self.physics,graphics_thread),
}
}
window,
physics_thread:crate::physics_worker::new(graphics_thread,bot_thread,user_settings),
};
pub fn into_worker(self,setup_context:crate::setup::SetupContext<'a>)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction>>{
let mut window_context=self.into_context(setup_context);
//WindowContextSetup::into_worker
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction>|{
match ins.instruction{
WindowInstruction::RequestRedraw=>{
@ -218,7 +202,7 @@ impl<'a> WindowContextSetup<'a>{
window_context.physics_thread.send(
TimedInstruction{
time:ins.time,
instruction:crate::physics_worker::Instruction::Resize(size,window_context.user_settings.clone())
instruction:crate::physics_worker::Instruction::Resize(size)
}
).unwrap();
}
@ -232,5 +216,4 @@ impl<'a> WindowContextSetup<'a>{
}
}
})
}
}
}

@ -190,7 +190,7 @@ mod test{
for _ in 0..5 {
let task = instruction::TimedInstruction{
time:integer::Time::ZERO,
instruction:physics::PhysicsInstruction::StrafeTick,
instruction:strafesnet_common::physics::Instruction::Idle,
};
worker.send(task).unwrap();
}
@ -204,7 +204,7 @@ mod test{
// Send a new task
let task = instruction::TimedInstruction{
time:integer::Time::ZERO,
instruction:physics::PhysicsInstruction::StrafeTick,
instruction:strafesnet_common::physics::Instruction::Idle,
};
worker.send(task).unwrap();

1
tools/iso Executable file

@ -0,0 +1 @@
mangohud ../target/release/strafe-client bhop_maps/5692124338.snfm