Compare commits
12 Commits
map-read-s
...
directorie
Author | SHA1 | Date | |
---|---|---|---|
58f892c414 | |||
324367a24a | |||
3e814cb41a | |||
ea854a548f | |||
5f2cf8f32e | |||
b6b090de78 | |||
affbada62e | |||
8d2ba28700 | |||
3eb4e76ab2 | |||
5d31419370 | |||
44a58044c7 | |||
83ac776b78 |
94
Cargo.lock
generated
94
Cargo.lock
generated
@ -493,6 +493,27 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "directories"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dispatch"
|
name = "dispatch"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -1588,6 +1609,12 @@ version = "1.20.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orbclient"
|
name = "orbclient"
|
||||||
version = "0.3.48"
|
version = "0.3.48"
|
||||||
@ -1997,6 +2024,17 @@ dependencies = [
|
|||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"libredox",
|
||||||
|
"thiserror 2.0.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@ -2253,19 +2291,17 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|||||||
name = "strafe-client"
|
name = "strafe-client"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
|
||||||
"bytemuck",
|
|
||||||
"configparser",
|
|
||||||
"ddsfile",
|
|
||||||
"glam",
|
"glam",
|
||||||
"id",
|
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pollster",
|
"pollster",
|
||||||
"replace_with",
|
|
||||||
"strafesnet_bsp_loader",
|
"strafesnet_bsp_loader",
|
||||||
"strafesnet_common",
|
"strafesnet_common",
|
||||||
"strafesnet_deferred_loader",
|
"strafesnet_deferred_loader",
|
||||||
|
"strafesnet_graphics",
|
||||||
|
"strafesnet_physics",
|
||||||
"strafesnet_rbx_loader",
|
"strafesnet_rbx_loader",
|
||||||
|
"strafesnet_session",
|
||||||
|
"strafesnet_settings",
|
||||||
"strafesnet_snf",
|
"strafesnet_snf",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
@ -2303,6 +2339,30 @@ dependencies = [
|
|||||||
"vbsp",
|
"vbsp",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strafesnet_graphics"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"ddsfile",
|
||||||
|
"glam",
|
||||||
|
"id",
|
||||||
|
"strafesnet_common",
|
||||||
|
"strafesnet_session",
|
||||||
|
"strafesnet_settings",
|
||||||
|
"wgpu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strafesnet_physics"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"glam",
|
||||||
|
"id",
|
||||||
|
"strafesnet_common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strafesnet_rbx_loader"
|
name = "strafesnet_rbx_loader"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@ -2319,6 +2379,28 @@ dependencies = [
|
|||||||
"strafesnet_common",
|
"strafesnet_common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strafesnet_session"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"glam",
|
||||||
|
"replace_with",
|
||||||
|
"strafesnet_common",
|
||||||
|
"strafesnet_physics",
|
||||||
|
"strafesnet_settings",
|
||||||
|
"strafesnet_snf",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strafesnet_settings"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"configparser",
|
||||||
|
"directories",
|
||||||
|
"glam",
|
||||||
|
"strafesnet_common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strafesnet_snf"
|
name = "strafesnet_snf"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"engine/graphics",
|
||||||
|
"engine/physics",
|
||||||
|
"engine/session",
|
||||||
|
"engine/settings",
|
||||||
"lib/bsp_loader",
|
"lib/bsp_loader",
|
||||||
"lib/common",
|
"lib/common",
|
||||||
"lib/deferred_loader",
|
"lib/deferred_loader",
|
||||||
|
@ -13,4 +13,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 has a sole proprietor license.
|
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client and engine crates have a sole proprietor license.
|
||||||
|
14
engine/graphics/Cargo.toml
Normal file
14
engine/graphics/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[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"
|
8
engine/graphics/LICENSE
Normal file
8
engine/graphics/LICENSE
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* 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
|
||||||
|
*******************************************************/
|
@ -1,9 +1,15 @@
|
|||||||
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_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
|
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
|
||||||
|
|
||||||
|
pub fn required_limits()->wgpu::Limits{
|
||||||
|
wgpu::Limits::default()
|
||||||
|
}
|
||||||
|
|
||||||
struct Indices{
|
struct Indices{
|
||||||
count:u32,
|
count:u32,
|
||||||
@ -136,7 +142,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:&crate::settings::UserSettings){
|
pub fn load_user_settings(&mut self,user_settings:&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){
|
||||||
@ -448,7 +454,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=crate::setup::required_limits().max_uniform_buffer_binding_size as usize;
|
let uniform_buffer_binding_size=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(){
|
||||||
@ -608,7 +614,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!("shader.wgsl"))),
|
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../../../strafe-client/src/shader.wgsl"))),
|
||||||
});
|
});
|
||||||
|
|
||||||
//load textures
|
//load textures
|
||||||
@ -636,10 +642,10 @@ impl GraphicsState{
|
|||||||
wgpu::TextureFormat::Astc{
|
wgpu::TextureFormat::Astc{
|
||||||
block:AstcBlock::B4x4,
|
block:AstcBlock::B4x4,
|
||||||
channel:AstcChannel::UnormSrgb,
|
channel:AstcChannel::UnormSrgb,
|
||||||
}=>&include_bytes!("../images/astc.dds")[..],
|
}=>&include_bytes!("../../../strafe-client/images/astc.dds")[..],
|
||||||
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../images/etc2.dds")[..],
|
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../../../strafe-client/images/etc2.dds")[..],
|
||||||
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../images/bc1.dds")[..],
|
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../../../strafe-client/images/bc1.dds")[..],
|
||||||
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../images/bgra.dds")[..],
|
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../../../strafe-client/images/bgra.dds")[..],
|
||||||
_=>unreachable!(),
|
_=>unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -682,7 +688,7 @@ impl GraphicsState{
|
|||||||
|
|
||||||
//squid
|
//squid
|
||||||
let squid_texture_view={
|
let squid_texture_view={
|
||||||
let bytes=include_bytes!("../images/squid.dds");
|
let bytes=include_bytes!("../../../strafe-client/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();
|
||||||
|
|
||||||
@ -864,7 +870,7 @@ impl GraphicsState{
|
|||||||
&mut self,
|
&mut self,
|
||||||
device:&wgpu::Device,
|
device:&wgpu::Device,
|
||||||
config:&wgpu::SurfaceConfiguration,
|
config:&wgpu::SurfaceConfiguration,
|
||||||
user_settings:&crate::settings::UserSettings,
|
user_settings:&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);
|
||||||
@ -875,7 +881,7 @@ impl GraphicsState{
|
|||||||
view:&wgpu::TextureView,
|
view:&wgpu::TextureView,
|
||||||
device:&wgpu::Device,
|
device:&wgpu::Device,
|
||||||
queue:&wgpu::Queue,
|
queue:&wgpu::Queue,
|
||||||
frame_state:crate::session::FrameState,
|
frame_state: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
|
||||||
|
|
2
engine/graphics/src/lib.rs
Normal file
2
engine/graphics/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod model;
|
||||||
|
pub mod graphics;
|
10
engine/physics/Cargo.toml
Normal file
10
engine/physics/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[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" }
|
8
engine/physics/LICENSE
Normal file
8
engine/physics/LICENSE
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* 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
|
||||||
|
*******************************************************/
|
@ -31,6 +31,14 @@ 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
|
||||||
@ -82,7 +90,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_physics::GigaTime){
|
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::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();
|
||||||
@ -137,14 +145,6 @@ 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)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge};
|
use crate::model::{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};
|
||||||
|
|
45
engine/physics/src/lib.rs
Normal file
45
engine/physics/src/lib.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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 that 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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
use std::collections::{HashMap,HashSet};
|
use std::collections::{HashMap,HashSet};
|
||||||
use crate::model_physics::{self,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId};
|
use crate::model::{self as model_physics,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;
|
||||||
@ -14,45 +14,6 @@ use strafesnet_common::integer::{self,vec3,mat3,Planar64,Planar64Vec3,Planar64Ma
|
|||||||
pub use strafesnet_common::physics::{Time,TimeInner};
|
pub use strafesnet_common::physics::{Time,TimeInner};
|
||||||
use gameplay::ModeState;
|
use gameplay::ModeState;
|
||||||
|
|
||||||
// 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(2);
|
|
||||||
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
|
|
||||||
let compat=[0,1,2];
|
|
||||||
|
|
||||||
let mut input_version=0;
|
|
||||||
while input_version<compat.len(){
|
|
||||||
// compatible version must be greater that 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Body=crate::body::Body<TimeInner>;
|
pub type Body=crate::body::Body<TimeInner>;
|
||||||
type MouseState=strafesnet_common::mouse::MouseState<TimeInner>;
|
type MouseState=strafesnet_common::mouse::MouseState<TimeInner>;
|
||||||
|
|
||||||
@ -830,7 +791,7 @@ impl TouchingState{
|
|||||||
crate::push_solve::push_solve(&contacts,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,start_time:Time){
|
||||||
// let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time);
|
// let relative_body=body.relative_to(&Body::ZERO);
|
||||||
let relative_body=body;
|
let relative_body=body;
|
||||||
for contact in &self.contacts{
|
for contact in &self.contacts{
|
||||||
//detect face slide off
|
//detect face slide off
|
||||||
@ -1179,7 +1140,7 @@ 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=&VirtualBody::relative(&Body::default(),&state.body).body(state.time);
|
//let relative_body=state.body.relative_to(&Body::ZERO);
|
||||||
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.
|
||||||
@ -1242,7 +1203,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=&VirtualBody::relative(&Body::default(),&state.body).body(state.time);
|
//let relative_body=state.body.relative_to(&Body::ZERO);
|
||||||
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);
|
||||||
@ -1912,7 +1873,6 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test{
|
mod test{
|
||||||
use crate::file;
|
|
||||||
use crate::body::VirtualBody;
|
use crate::body::VirtualBody;
|
||||||
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
|
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -1994,111 +1954,111 @@ mod test{
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_east_from_west(){
|
fn test_collision_parabola_edge_east_from_west(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).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(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(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
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_oblique(){
|
fn test_collision_oblique(){
|
||||||
@ -2127,202 +2087,4 @@ mod test{
|
|||||||
Time::ZERO
|
Time::ZERO
|
||||||
),None);
|
),None);
|
||||||
}
|
}
|
||||||
#[test]
|
|
||||||
fn run_replay(){
|
|
||||||
println!("loading map file..");
|
|
||||||
let map=file::load("../tools/bhop_maps/5692113331.snfm");
|
|
||||||
println!("loading bot file..");
|
|
||||||
let bot=file::load("../tools/replays/534s+997497968ns.snfb");
|
|
||||||
if let (Ok(file::LoadFormat::Map(map)),Ok(file::LoadFormat::Bot(bot)))=(map,bot){
|
|
||||||
// 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"),
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
panic!("missing files");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enum DeterminismResult{
|
|
||||||
Deterministic,
|
|
||||||
NonDeterministic,
|
|
||||||
}
|
|
||||||
#[allow(unused)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum ReplayError{
|
|
||||||
Load(file::LoadError),
|
|
||||||
IO(std::io::Error),
|
|
||||||
}
|
|
||||||
impl From<file::LoadError> for ReplayError{
|
|
||||||
fn from(value:file::LoadError)->Self{
|
|
||||||
Self::Load(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::io::Error> for ReplayError{
|
|
||||||
fn from(value:std::io::Error)->Self{
|
|
||||||
Self::IO(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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>,file::LoadError>;
|
|
||||||
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=match file::load(file_path.as_path()){
|
|
||||||
Ok(file::LoadFormat::Bot(bot))=>{
|
|
||||||
println!("Running {:?}",file_path.file_stem());
|
|
||||||
Ok(Some(segment_determinism(bot,physics_data)))
|
|
||||||
},
|
|
||||||
Ok(_)=>{
|
|
||||||
println!("Provided bot file is not a bot file!");
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(e)=>{
|
|
||||||
println!("Load error");
|
|
||||||
Err(e)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// 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()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn test_determinism()->Result<(),ReplayError>{
|
|
||||||
let thread_limit=std::thread::available_parallelism()?.get();
|
|
||||||
println!("loading map file..");
|
|
||||||
let file::LoadFormat::Map(map)=file::load("../tools/bhop_maps/5692113331.snfm")? else{
|
|
||||||
panic!("Provided map file is not a map file!");
|
|
||||||
};
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
12
engine/session/Cargo.toml
Normal file
12
engine/session/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[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" }
|
8
engine/session/LICENSE
Normal file
8
engine/session/LICENSE
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* 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
|
||||||
|
*******************************************************/
|
2
engine/session/src/lib.rs
Normal file
2
engine/session/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mod mouse_interpolator;
|
||||||
|
pub mod session;
|
@ -14,8 +14,8 @@ use strafesnet_common::timer::{Scaled,Timer};
|
|||||||
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
||||||
|
|
||||||
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
||||||
use crate::physics::{PhysicsContext,PhysicsData};
|
use strafesnet_physics::physics::{self,PhysicsContext,PhysicsData};
|
||||||
use crate::settings::UserSettings;
|
use strafesnet_settings::settings::UserSettings;
|
||||||
|
|
||||||
pub enum Instruction<'a>{
|
pub enum Instruction<'a>{
|
||||||
Input(SessionInputInstruction),
|
Input(SessionInputInstruction),
|
||||||
@ -57,19 +57,19 @@ pub enum SessionPlaybackInstruction{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct FrameState{
|
pub struct FrameState{
|
||||||
pub body:crate::physics::Body,
|
pub body:physics::Body,
|
||||||
pub camera:crate::physics::PhysicsCamera,
|
pub camera:physics::PhysicsCamera,
|
||||||
pub time:PhysicsTime,
|
pub time:PhysicsTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Simulation{
|
pub struct Simulation{
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||||
physics:crate::physics::PhysicsState,
|
physics:physics::PhysicsState,
|
||||||
}
|
}
|
||||||
impl Simulation{
|
impl Simulation{
|
||||||
pub const fn new(
|
pub const fn new(
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||||
physics:crate::physics::PhysicsState,
|
physics:physics::PhysicsState,
|
||||||
)->Self{
|
)->Self{
|
||||||
Self{
|
Self{
|
||||||
timer,
|
timer,
|
||||||
@ -153,7 +153,7 @@ pub struct Session{
|
|||||||
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
||||||
view_state:ViewState,
|
view_state:ViewState,
|
||||||
//gui:GuiState
|
//gui:GuiState
|
||||||
geometry_shared:crate::physics::PhysicsData,
|
geometry_shared:physics::PhysicsData,
|
||||||
simulation:Simulation,
|
simulation:Simulation,
|
||||||
// below fields not included in lite session
|
// below fields not included in lite session
|
||||||
recording:Recording,
|
recording:Recording,
|
||||||
@ -299,7 +299,7 @@ impl InstructionConsumer<Instruction<'_>> for Session{
|
|||||||
std::thread::spawn(move ||{
|
std::thread::spawn(move ||{
|
||||||
std::fs::create_dir_all("replays").unwrap();
|
std::fs::create_dir_all("replays").unwrap();
|
||||||
let file=std::fs::File::create(file_name).unwrap();
|
let file=std::fs::File::create(file_name).unwrap();
|
||||||
strafesnet_snf::bot::write_bot(std::io::BufWriter::new(file),crate::physics::VERSION.get(),replay.recording.instructions).unwrap();
|
strafesnet_snf::bot::write_bot(std::io::BufWriter::new(file),strafesnet_physics::VERSION.get(),replay.recording.instructions).unwrap();
|
||||||
println!("Finished writing bot file!");
|
println!("Finished writing bot file!");
|
||||||
});
|
});
|
||||||
},
|
},
|
10
engine/settings/Cargo.toml
Normal file
10
engine/settings/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[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" }
|
8
engine/settings/LICENSE
Normal file
8
engine/settings/LICENSE
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* 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
|
||||||
|
*******************************************************/
|
32
engine/settings/src/directories.rs
Normal file
32
engine/settings/src/directories.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::settings::UserSettings;
|
||||||
|
|
||||||
|
pub struct Directories{
|
||||||
|
pub settings:PathBuf,
|
||||||
|
pub maps:PathBuf,
|
||||||
|
pub replays:PathBuf,
|
||||||
|
}
|
||||||
|
impl Directories{
|
||||||
|
pub fn user_settings(&self)->UserSettings{
|
||||||
|
crate::settings::read_user_settings(&self.settings)
|
||||||
|
}
|
||||||
|
pub fn installed()->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"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
2
engine/settings/src/lib.rs
Normal file
2
engine/settings/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod settings;
|
||||||
|
pub mod directories;
|
@ -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 read_user_settings()->UserSettings{
|
pub fn read_user_settings(path:&std::path::Path)->UserSettings{
|
||||||
let mut cfg=configparser::ini::Ini::new();
|
let mut cfg=configparser::ini::Ini::new();
|
||||||
if let Ok(_)=cfg.load("settings.conf"){
|
if let Ok(_)=cfg.load(path){
|
||||||
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 {
|
||||||
@ -136,4 +136,4 @@ pub fn read_user_settings()->UserSettings{
|
|||||||
}else{
|
}else{
|
||||||
UserSettings::default()
|
UserSettings::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,19 +15,17 @@ 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 = "24.0.0"
|
||||||
winit = "0.30.7"
|
winit = "0.30.7"
|
||||||
|
@ -34,20 +34,11 @@ pub enum ReadFormat{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R:Read+std::io::Seek>(input:R)->Result<ReadFormat,ReadError>{
|
pub fn read<R:Read+std::io::Seek>(input:R)->Result<ReadFormat,ReadError>{
|
||||||
let t=std::time::Instant::now();
|
|
||||||
println!("reading fourcc...");
|
|
||||||
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)?[0..4].to_owned();
|
||||||
let dt=t.elapsed();
|
|
||||||
println!("{:?} elapsed={:?}",core::str::from_utf8(&peek),dt);
|
|
||||||
|
|
||||||
let t=std::time::Instant::now();
|
|
||||||
println!("reading entire file...");
|
|
||||||
// reading the entire file is way faster than round tripping the disk constantly
|
// reading the entire file is way faster than round tripping the disk constantly
|
||||||
let mut entire_file=Vec::new();
|
let mut entire_file=Vec::new();
|
||||||
buf.read_to_end(&mut entire_file).map_err(ReadError::Io)?;
|
buf.read_to_end(&mut entire_file).map_err(ReadError::Io)?;
|
||||||
println!("elapsed={:?}",t.elapsed());
|
|
||||||
|
|
||||||
let cursor=std::io::Cursor::new(entire_file);
|
let cursor=std::io::Cursor::new(entire_file);
|
||||||
match peek.as_slice(){
|
match peek.as_slice(){
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
@ -55,15 +46,10 @@ pub fn read<R:Read+std::io::Seek>(input:R)->Result<ReadFormat,ReadError>{
|
|||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
b"VBSP"=>Ok(ReadFormat::Source(strafesnet_bsp_loader::read(cursor).map_err(ReadError::Source)?)),
|
b"VBSP"=>Ok(ReadFormat::Source(strafesnet_bsp_loader::read(cursor).map_err(ReadError::Source)?)),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
b"SNFM"=>{
|
b"SNFM"=>Ok(ReadFormat::SNFM(
|
||||||
let t=std::time::Instant::now();
|
strafesnet_snf::read_map(cursor).map_err(ReadError::StrafesNET)?
|
||||||
println!("decoding map...");
|
.into_complete_map().map_err(ReadError::StrafesNETMap)?
|
||||||
let map=
|
)),
|
||||||
strafesnet_snf::read_map(cursor).map_err(ReadError::StrafesNET)?
|
|
||||||
.into_complete_map().map_err(ReadError::StrafesNETMap)?;
|
|
||||||
println!("elapsed={:?}",t.elapsed());
|
|
||||||
Ok(ReadFormat::SNFM(map))
|
|
||||||
},
|
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
b"SNFB"=>Ok(ReadFormat::SNFB(
|
b"SNFB"=>Ok(ReadFormat::SNFB(
|
||||||
strafesnet_snf::read_bot(cursor).map_err(ReadError::StrafesNET)?
|
strafesnet_snf::read_bot(cursor).map_err(ReadError::StrafesNET)?
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
use strafesnet_graphics::graphics;
|
||||||
|
use strafesnet_session::session;
|
||||||
|
use strafesnet_settings::settings;
|
||||||
|
|
||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
Render(crate::session::FrameState),
|
Render(session::FrameState),
|
||||||
//UpdateModel(crate::graphics::GraphicsModelUpdate),
|
//UpdateModel(graphics::GraphicsModelUpdate),
|
||||||
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
|
Resize(winit::dpi::PhysicalSize<u32>,settings::UserSettings),
|
||||||
ChangeMap(strafesnet_common::map::CompleteMap),
|
ChangeMap(strafesnet_common::map::CompleteMap),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,7 +19,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:crate::graphics::GraphicsState,
|
mut graphics:graphics::GraphicsState,
|
||||||
mut config:wgpu::SurfaceConfiguration,
|
mut config:wgpu::SurfaceConfiguration,
|
||||||
surface:wgpu::Surface,
|
surface:wgpu::Surface,
|
||||||
device:wgpu::Device,
|
device:wgpu::Device,
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
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;
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));
|
const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::graphics_worker::Instruction as GraphicsInstruction;
|
use crate::graphics_worker::Instruction as GraphicsInstruction;
|
||||||
use crate::session::{
|
use strafesnet_settings::settings;
|
||||||
Session,Simulation,SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction,
|
use strafesnet_session::session::{
|
||||||
|
Session,Simulation,SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction,ImplicitModeInstruction,
|
||||||
Instruction as SessionInstruction,
|
Instruction as SessionInstruction,
|
||||||
};
|
};
|
||||||
use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer};
|
use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer};
|
||||||
@ -20,9 +21,9 @@ pub enum Instruction{
|
|||||||
|
|
||||||
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>,
|
||||||
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=crate::physics::PhysicsState::default();
|
let physics=strafesnet_physics::physics::PhysicsState::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(
|
||||||
@ -67,7 +68,7 @@ pub fn new<'a>(
|
|||||||
},
|
},
|
||||||
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(crate::session::ImplicitModeInstruction::ResetAndSpawn(strafesnet_common::gameplay_modes::ModeId::MAIN,strafesnet_common::gameplay_modes::StageId::FIRST))));
|
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)=>{
|
Instruction::LoadReplay(bot)=>{
|
||||||
|
@ -17,9 +17,6 @@ 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,
|
||||||
@ -130,7 +127,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=required_limits().using_resolution(self.adapter.limits());
|
let needed_limits=strafesnet_graphics::graphics::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
|
||||||
|
1
strafe-client/src/tests/mod.rs
Normal file
1
strafe-client/src/tests/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
mod replay;
|
203
strafe-client/src/tests/replay.rs
Normal file
203
strafe-client/src/tests/replay.rs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
|
||||||
|
use crate::file;
|
||||||
|
|
||||||
|
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_replay(){
|
||||||
|
println!("loading map file..");
|
||||||
|
let map=file::load("../tools/bhop_maps/5692113331.snfm");
|
||||||
|
println!("loading bot file..");
|
||||||
|
let bot=file::load("../tools/replays/534s+997497968ns.snfb");
|
||||||
|
if let (Ok(file::LoadFormat::Map(map)),Ok(file::LoadFormat::Bot(bot)))=(map,bot){
|
||||||
|
// 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"),
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
panic!("missing files");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum DeterminismResult{
|
||||||
|
Deterministic,
|
||||||
|
NonDeterministic,
|
||||||
|
}
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ReplayError{
|
||||||
|
Load(file::LoadError),
|
||||||
|
IO(std::io::Error),
|
||||||
|
}
|
||||||
|
impl From<file::LoadError> for ReplayError{
|
||||||
|
fn from(value:file::LoadError)->Self{
|
||||||
|
Self::Load(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<std::io::Error> for ReplayError{
|
||||||
|
fn from(value:std::io::Error)->Self{
|
||||||
|
Self::IO(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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>,file::LoadError>;
|
||||||
|
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=match file::load(file_path.as_path()){
|
||||||
|
Ok(file::LoadFormat::Bot(bot))=>{
|
||||||
|
println!("Running {:?}",file_path.file_stem());
|
||||||
|
Ok(Some(segment_determinism(bot,physics_data)))
|
||||||
|
},
|
||||||
|
Ok(_)=>{
|
||||||
|
println!("Provided bot file is not a bot file!");
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
Err(e)=>{
|
||||||
|
println!("Load error");
|
||||||
|
Err(e)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// 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()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_determinism()->Result<(),ReplayError>{
|
||||||
|
let thread_limit=std::thread::available_parallelism()?.get();
|
||||||
|
println!("loading map file..");
|
||||||
|
let file::LoadFormat::Map(map)=file::load("../tools/bhop_maps/5692113331.snfm")? else{
|
||||||
|
panic!("Provided map file is not a map file!");
|
||||||
|
};
|
||||||
|
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(())
|
||||||
|
}
|
@ -3,7 +3,8 @@ use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInn
|
|||||||
use strafesnet_common::physics::{MiscInstruction,SetControlInstruction};
|
use strafesnet_common::physics::{MiscInstruction,SetControlInstruction};
|
||||||
use crate::file::LoadFormat;
|
use crate::file::LoadFormat;
|
||||||
use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
|
use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
|
||||||
use crate::session::{SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction};
|
use strafesnet_session::session::{self,SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction};
|
||||||
|
use strafesnet_settings::directories;
|
||||||
|
|
||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
Resize(winit::dpi::PhysicalSize<u32>),
|
Resize(winit::dpi::PhysicalSize<u32>),
|
||||||
@ -150,7 +151,7 @@ impl WindowContext<'_>{
|
|||||||
"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(crate::session::ImplicitModeInstruction::ResetAndRestart))
|
SessionInstructionSubset::Input(SessionInputInstruction::Mode(session::ImplicitModeInstruction::ResetAndRestart))
|
||||||
}),
|
}),
|
||||||
"F"|"f"=>input_misc!(PracticeFly,s),
|
"F"|"f"=>input_misc!(PracticeFly,s),
|
||||||
"B"|"b"=>session_ctrl!(CopyRecordingIntoReplayAndSpectate,s),
|
"B"|"b"=>session_ctrl!(CopyRecordingIntoReplayAndSpectate,s),
|
||||||
@ -210,9 +211,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
|
||||||
let user_settings=crate::settings::read_user_settings();
|
let user_settings=directories::Directories::portable().unwrap().user_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
|
||||||
|
@ -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=crate::physics::Body;
|
type Body=strafesnet_physics::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() {
|
||||||
|
Reference in New Issue
Block a user