Compare commits

...

14 Commits
master ... web2

Author SHA1 Message Date
8ab8776916 outrageous resize hack 2025-03-10 18:08:04 -07:00
5cff2229be ai 2025-03-10 18:04:30 -07:00
b731ff2ad3 begone root 2025-03-10 17:47:36 -07:00
da8489fad5 begone flex 2025-03-10 17:47:36 -07:00
199c96abca use existing canvas 2025-03-10 17:47:36 -07:00
30b2a92c2a my size 2025-03-10 17:47:21 -07:00
375bbd4aa5 cap size to work around loop resize bug 2025-03-10 17:47:21 -07:00
9800992b67 use chrono instead of std::time 2025-03-10 17:47:21 -07:00
61a86a78f4 pick up wasm dep 2025-03-10 17:40:11 -07:00
da2c246bec color functions 2025-03-10 17:39:57 -07:00
2bcf3cf75d attach canvas 2025-03-10 17:39:57 -07:00
4a980d44cc replace enumerate_adapters with request_adapter 2025-03-10 17:39:44 -07:00
280e2ab58b drop Send requirement 2025-03-10 17:39:44 -07:00
16cd673829 Initial trunk 2025-03-10 17:39:44 -07:00
10 changed files with 286 additions and 244 deletions

400
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -16,6 +16,7 @@ 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]
chrono = "0.4.39"
glam = "0.30.0" glam = "0.30.0"
parking_lot = "0.12.1" parking_lot = "0.12.1"
pollster = "0.4.0" pollster = "0.4.0"
@ -28,5 +29,8 @@ strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", o
strafesnet_session = { path = "../engine/session", registry = "strafesnet" } strafesnet_session = { path = "../engine/session", registry = "strafesnet" }
strafesnet_settings = { path = "../engine/settings", 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 }
wasm-bindgen = "0.2.99"
wasm-bindgen-futures = "0.4.49"
web-sys = { version = "0.3.76", features = ["console"] }
wgpu = "24.0.0" wgpu = "24.0.0"
winit = "0.30.7" winit = "0.30.7"

@ -4,12 +4,12 @@ use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::session::Time as SessionTime; use strafesnet_common::session::Time as SessionTime;
pub struct App<'a>{ pub struct App<'a>{
root_time:std::time::Instant, root_time:chrono::DateTime<chrono::Utc>,
window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>, window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>,
} }
impl<'a> App<'a>{ impl<'a> App<'a>{
pub fn new( pub fn new(
root_time:std::time::Instant, root_time:chrono::DateTime<chrono::Utc>,
window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>, window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>,
)->App<'a>{ )->App<'a>{
Self{ Self{
@ -18,7 +18,7 @@ impl<'a> App<'a>{
} }
} }
fn send_timed_instruction(&mut self,instruction:Instruction){ fn send_timed_instruction(&mut self,instruction:Instruction){
let time=integer::Time::from_nanos(self.root_time.elapsed().as_nanos() as i64); let time=integer::Time::from_nanos((chrono::Utc::now()-self.root_time).num_nanoseconds().unwrap());
self.window_thread.send(TimedInstruction{time,instruction}).unwrap(); self.window_thread.send(TimedInstruction{time,instruction}).unwrap();
} }
} }

@ -3,11 +3,11 @@ pub type INWorker<'a,Task>=CompatNWorker<'a,Task>;
pub struct CompatNWorker<'a,Task>{ pub struct CompatNWorker<'a,Task>{
data:std::marker::PhantomData<Task>, data:std::marker::PhantomData<Task>,
f:Box<dyn FnMut(Task)+Send+'a>, f:Box<dyn FnMut(Task)+'a>,
} }
impl<'a,Task> CompatNWorker<'a,Task>{ impl<'a,Task> CompatNWorker<'a,Task>{
pub fn new(f:impl FnMut(Task)+Send+'a)->CompatNWorker<'a,Task>{ pub fn new(f:impl FnMut(Task)+'a)->CompatNWorker<'a,Task>{
Self{ Self{
data:std::marker::PhantomData, data:std::marker::PhantomData,
f:Box::new(f), f:Box::new(f),

@ -18,6 +18,9 @@ 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
fn print(message:&str){
web_sys::console::log_1(&message.into());
}
pub fn new( pub fn new(
mut graphics:graphics::GraphicsState, mut graphics:graphics::GraphicsState,
mut config:wgpu::SurfaceConfiguration, mut config:wgpu::SurfaceConfiguration,
@ -33,12 +36,20 @@ pub fn new(
}, },
Instruction::Resize(size,user_settings)=>{ Instruction::Resize(size,user_settings)=>{
println!("Resizing to {:?}",size); println!("Resizing to {:?}",size);
let t0=std::time::Instant::now(); //let t0=std::time::Instant::now();
config.width=size.width.max(1); match size{
config.height=size.height.max(1); winit::dpi::PhysicalSize{width:2560,height:1440}=>{
config.width=size.width.clamp(1,2560);
config.height=size.height.clamp(1,1440);
},
_=>{
config.width=size.width.clamp(1,1280);
config.height=size.height.clamp(1,720);
}
}
surface.configure(&device,&config); surface.configure(&device,&config);
graphics.resize(&device,&config,&user_settings); graphics.resize(&device,&config,&user_settings);
println!("Resize took {:?}",t0.elapsed()); //println!("Resize took {:?}",t0.elapsed());
} }
Instruction::Render(frame_state)=>{ Instruction::Render(frame_state)=>{
//this has to go deeper somehow //this has to go deeper somehow

@ -9,5 +9,8 @@ mod graphics_worker;
const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION")); const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));
fn main(){ fn main(){
setup::setup_and_start(TITLE); #[cfg(target_arch="wasm32")]
wasm_bindgen_futures::spawn_local(setup::setup_and_start(TITLE));
#[cfg(not(target_arch="wasm32"))]
pollster::block_on(setup::setup_and_start(TITLE));
} }

@ -20,6 +20,16 @@ struct SetupContextPartial1{
fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Result<winit::window::Window,winit::error::OsError>{ fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Result<winit::window::Window,winit::error::OsError>{
let mut attr=winit::window::WindowAttributes::default(); let mut attr=winit::window::WindowAttributes::default();
attr=attr.with_title(title); attr=attr.with_title(title);
#[cfg(target_arch="wasm32")]
{
use wasm_bindgen::JsCast;
use winit::platform::web::WindowAttributesExtWebSys;
let canvas=web_sys::window().unwrap()
.document().unwrap()
.get_element_by_id("canvas").unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
attr=attr.with_canvas(Some(canvas));
}
event_loop.create_window(attr) event_loop.create_window(attr)
} }
fn create_instance()->SetupContextPartial1{ fn create_instance()->SetupContextPartial1{
@ -44,36 +54,18 @@ struct SetupContextPartial2<'a>{
surface:wgpu::Surface<'a>, surface:wgpu::Surface<'a>,
} }
impl<'a> SetupContextPartial2<'a>{ impl<'a> SetupContextPartial2<'a>{
fn pick_adapter(self)->SetupContextPartial3<'a>{ async fn pick_adapter(self)->SetupContextPartial3<'a>{
let adapter; let adapter;
//TODO: prefer adapter that implements optional features //TODO: prefer adapter that implements optional features
//let optional_features=optional_features(); //let optional_features=optional_features();
let required_features=required_features(); let required_features=required_features();
//no helper function smh gotta write it myself let chosen_adapter=self.instance.request_adapter(&wgpu::RequestAdapterOptions{
let adapters=self.instance.enumerate_adapters(self.backends); power_preference:wgpu::PowerPreference::HighPerformance,
force_fallback_adapter:false,
let mut chosen_adapter=None; compatible_surface:Some(&self.surface),
let mut chosen_adapter_score=0; }).await;
for adapter in adapters {
if !adapter.is_surface_supported(&self.surface) {
continue;
}
let score=match adapter.get_info().device_type{
wgpu::DeviceType::IntegratedGpu=>3,
wgpu::DeviceType::DiscreteGpu=>4,
wgpu::DeviceType::VirtualGpu=>2,
wgpu::DeviceType::Other|wgpu::DeviceType::Cpu=>1,
};
let adapter_features=adapter.features();
if chosen_adapter_score<score&&adapter_features.contains(required_features) {
chosen_adapter_score=score;
chosen_adapter=Some(adapter);
}
}
if let Some(maybe_chosen_adapter)=chosen_adapter{ if let Some(maybe_chosen_adapter)=chosen_adapter{
adapter=maybe_chosen_adapter; adapter=maybe_chosen_adapter;
@ -110,7 +102,7 @@ struct SetupContextPartial3<'a>{
adapter:wgpu::Adapter, adapter:wgpu::Adapter,
} }
impl<'a> SetupContextPartial3<'a>{ impl<'a> SetupContextPartial3<'a>{
fn request_device(self)->SetupContextPartial4<'a>{ async fn request_device(self)->SetupContextPartial4<'a>{
let optional_features=optional_features(); let optional_features=optional_features();
let required_features=required_features(); let required_features=required_features();
@ -118,7 +110,7 @@ impl<'a> SetupContextPartial3<'a>{
let needed_limits=strafesnet_graphics::graphics::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)=self.adapter
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
@ -127,7 +119,7 @@ impl<'a> SetupContextPartial3<'a>{
memory_hints:wgpu::MemoryHints::Performance, memory_hints:wgpu::MemoryHints::Performance,
}, },
trace_dir.ok().as_ref().map(std::path::Path::new), trace_dir.ok().as_ref().map(std::path::Path::new),
)) ).await
.expect("Unable to find a suitable GPU adapter!"); .expect("Unable to find a suitable GPU adapter!");
SetupContextPartial4{ SetupContextPartial4{
@ -169,7 +161,7 @@ pub struct SetupContext<'a>{
pub config:wgpu::SurfaceConfiguration, pub config:wgpu::SurfaceConfiguration,
} }
pub fn setup_and_start(title:&str){ pub async fn setup_and_start(title:&str){
let event_loop=winit::event_loop::EventLoop::new().unwrap(); let event_loop=winit::event_loop::EventLoop::new().unwrap();
println!("Initializing the surface..."); println!("Initializing the surface...");
@ -180,9 +172,9 @@ pub fn setup_and_start(title:&str){
let partial_2=partial_1.create_surface(&window).unwrap(); let partial_2=partial_1.create_surface(&window).unwrap();
let partial_3=partial_2.pick_adapter(); let partial_3=partial_2.pick_adapter().await;
let partial_4=partial_3.request_device(); let partial_4=partial_3.request_device().await;
let size=window.inner_size(); let size=window.inner_size();
@ -205,7 +197,7 @@ pub fn setup_and_start(title:&str){
println!("Entering event loop..."); println!("Entering event loop...");
let mut app=crate::app::App::new( let mut app=crate::app::App::new(
std::time::Instant::now(), chrono::Utc::now(),
window_thread window_thread
); );
event_loop.run_app(&mut app).unwrap(); event_loop.run_app(&mut app).unwrap();

1
web-client/.gitignore vendored Normal file

@ -0,0 +1 @@
/dist

2
web-client/Trunk.toml Normal file

@ -0,0 +1,2 @@
[build]
target = "index.html"

35
web-client/index.html Normal file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Strafe Client</title>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#wasm-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="wasm-container">
<canvas class="main-canvas" id="canvas"></canvas>
</div>
<link data-trunk rel="rust" href="../strafe-client/Cargo.toml"/>
</body>
</html>