From 801274b49f9cea9b73ddf21a1ebdca19b2e88702 Mon Sep 17 00:00:00 2001 From: Patrick Owen Date: Sat, 14 Oct 2023 20:59:10 -0400 Subject: [PATCH] Delay Sim creation until server params received This allows Sim to store SimConfig without wrapping it in an Option --- client/src/graphics/draw.rs | 18 ++-- client/src/graphics/window.rs | 104 +++++++++++++-------- client/src/main.rs | 5 +- client/src/sim.rs | 166 ++++++++++++++-------------------- 4 files changed, 146 insertions(+), 147 deletions(-) diff --git a/client/src/graphics/draw.rs b/client/src/graphics/draw.rs index 4a5741d7..873d65e8 100644 --- a/client/src/graphics/draw.rs +++ b/client/src/graphics/draw.rs @@ -7,9 +7,9 @@ use lahar::Staged; use metrics::histogram; use super::{fog, voxels, Base, Fog, Frustum, GltfScene, Meshes, Voxels}; -use crate::{sim, Asset, Config, Loader, Sim}; -use common::math; +use crate::{Asset, Config, Loader, Sim}; use common::proto::{Character, Position}; +use common::{math, SimConfig}; /// Manages rendering, independent of what is being rendered to pub struct Draw { @@ -215,12 +215,12 @@ impl Draw { } /// Called with server-defined world parameters once they're known - pub fn configure(&mut self, params: &sim::Parameters) { + pub fn configure(&mut self, cfg: &SimConfig) { let voxels = Voxels::new( &self.gfx, self.cfg.clone(), &mut self.loader, - u32::from(params.cfg.chunk_size), + u32::from(cfg.chunk_size), PIPELINE_DEPTH, ); for state in &mut self.states { @@ -256,7 +256,7 @@ impl Draw { /// attachment. pub unsafe fn draw( &mut self, - sim: &mut Sim, + mut sim: Option<&mut Sim>, framebuffer: vk::Framebuffer, depth_view: vk::ImageView, extent: vk::Extent2D, @@ -264,7 +264,7 @@ impl Draw { frustum: &Frustum, ) { let draw_started = Instant::now(); - let view = sim.view(); + let view = sim.as_ref().map_or_else(Position::origin, |sim| sim.view()); let projection = frustum.projection(1.0e-4); let view_projection = projection.matrix() * math::mtranspose(&view.local); self.loader.drive(); @@ -356,7 +356,7 @@ impl Draw { .build(), ); - if let Some(ref mut voxels) = self.voxels { + if let (Some(voxels), Some(sim)) = (self.voxels.as_mut(), sim.as_mut()) { voxels.prepare( device, state.voxels.as_mut().unwrap(), @@ -434,7 +434,7 @@ impl Draw { ); } - if let Some(params) = sim.params() { + if let Some(sim) = sim.as_deref() { for (node, transform) in nearby_nodes( &sim.graph, &view, @@ -453,7 +453,7 @@ impl Draw { if let Ok(ch) = sim.world.get::<&Character>(entity) { let transform = transform * pos.local - * na::Matrix4::new_scaling(params.cfg.meters_to_absolute) + * na::Matrix4::new_scaling(sim.cfg().meters_to_absolute) * ch.state.orientation.to_homogeneous(); for mesh in &character_model.0 { self.meshes diff --git a/client/src/graphics/window.rs b/client/src/graphics/window.rs index e3c66360..6c772658 100644 --- a/client/src/graphics/window.rs +++ b/client/src/graphics/window.rs @@ -5,7 +5,7 @@ use std::{f32, os::raw::c_char}; use ash::{extensions::khr, vk}; use lahar::DedicatedImage; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; -use tracing::info; +use tracing::{error, info}; use winit::{ dpi::PhysicalSize, event::{ @@ -16,7 +16,8 @@ use winit::{ }; use super::{Base, Core, Draw, Frustum}; -use crate::{Config, Sim}; +use crate::Net; +use crate::{net, Config, Sim}; /// OS window pub struct EarlyWindow { @@ -53,7 +54,8 @@ pub struct Window { swapchain: Option, swapchain_needs_update: bool, draw: Option, - sim: Sim, + sim: Option, + net: Net, } impl Window { @@ -63,7 +65,7 @@ impl Window { core: Arc, config: Arc, metrics: Arc, - sim: Sim, + net: Net, ) -> Self { let surface = unsafe { ash_window::create_surface( @@ -88,7 +90,8 @@ impl Window { swapchain: None, swapchain_needs_update: false, draw: None, - sim, + sim: None, + net, } } @@ -126,50 +129,49 @@ impl Window { .unwrap() .run(move |event, _, control_flow| match event { Event::MainEventsCleared => { - let this_frame = Instant::now(); - let dt = this_frame - last_frame; - let move_direction: na::Vector3 = na::Vector3::x() - * (right as u8 as f32 - left as u8 as f32) - + na::Vector3::y() * (up as u8 as f32 - down as u8 as f32) - + na::Vector3::z() * (back as u8 as f32 - forward as u8 as f32); - self.sim - .set_movement_input(if move_direction.norm_squared() > 1.0 { + while let Ok(msg) = self.net.incoming.try_recv() { + self.handle_net(msg); + } + + if let Some(sim) = self.sim.as_mut() { + let this_frame = Instant::now(); + let dt = this_frame - last_frame; + let move_direction: na::Vector3 = na::Vector3::x() + * (right as u8 as f32 - left as u8 as f32) + + na::Vector3::y() * (up as u8 as f32 - down as u8 as f32) + + na::Vector3::z() * (back as u8 as f32 - forward as u8 as f32); + sim.set_movement_input(if move_direction.norm_squared() > 1.0 { move_direction.normalize() } else { move_direction }); - self.sim.rotate(&na::UnitQuaternion::from_axis_angle( - &-na::Vector3::z_axis(), - (clockwise as u8 as f32 - anticlockwise as u8 as f32) - * 2.0 - * dt.as_secs_f32(), - )); - - let had_params = self.sim.params().is_some(); - - self.sim.step(dt); - last_frame = this_frame; + sim.rotate(&na::UnitQuaternion::from_axis_angle( + &-na::Vector3::z_axis(), + (clockwise as u8 as f32 - anticlockwise as u8 as f32) + * 2.0 + * dt.as_secs_f32(), + )); - if !had_params { - if let Some(params) = self.sim.params() { - self.draw.as_mut().unwrap().configure(params); - } + sim.step(dt, &mut self.net); + last_frame = this_frame; } self.draw(); } Event::DeviceEvent { event, .. } => match event { DeviceEvent::MouseMotion { delta } if mouse_captured => { - const SENSITIVITY: f32 = 2e-3; - let rot = na::UnitQuaternion::from_axis_angle( - &na::Vector3::y_axis(), - -delta.0 as f32 * SENSITIVITY, - ) * na::UnitQuaternion::from_axis_angle( - &na::Vector3::x_axis(), - -delta.1 as f32 * SENSITIVITY, - ); - self.sim.rotate(&rot); + if let Some(sim) = self.sim.as_mut() { + const SENSITIVITY: f32 = 2e-3; + let rot = na::UnitQuaternion::from_axis_angle( + &na::Vector3::y_axis(), + -delta.0 as f32 * SENSITIVITY, + ) * na::UnitQuaternion::from_axis_angle( + &na::Vector3::x_axis(), + -delta.1 as f32 * SENSITIVITY, + ); + sim.rotate(&rot); + } } _ => {} }, @@ -231,7 +233,9 @@ impl Window { down = state == ElementState::Pressed; } VirtualKeyCode::V if state == ElementState::Pressed => { - self.sim.toggle_no_clip(); + if let Some(sim) = self.sim.as_mut() { + sim.toggle_no_clip(); + } } VirtualKeyCode::Escape => { let _ = self.window.set_cursor_grab(CursorGrabMode::None); @@ -256,6 +260,28 @@ impl Window { }); } + fn handle_net(&mut self, msg: net::Message) { + match msg { + net::Message::ConnectionLost(e) => { + error!("connection lost: {}", e); + } + net::Message::Hello(msg) => { + let sim = Sim::new(msg.sim_config, msg.character); + if let Some(draw) = self.draw.as_mut() { + draw.configure(sim.cfg()); + } + self.sim = Some(sim); + } + msg => { + if let Some(sim) = self.sim.as_mut() { + sim.handle_net(msg); + } else { + error!("Received game data before ServerHello"); + } + } + } + } + /// Draw a new frame fn draw(&mut self) { let swapchain = self.swapchain.as_mut().unwrap(); @@ -292,7 +318,7 @@ impl Window { let frustum = Frustum::from_vfov(f32::consts::FRAC_PI_4 * 1.2, aspect_ratio); // Render the frame draw.draw( - &mut self.sim, + self.sim.as_mut(), frame.buffer, frame.depth_view, swapchain.state.extent, diff --git a/client/src/main.rs b/client/src/main.rs index b02e3416..ff0b81bd 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -3,7 +3,7 @@ use std::{ sync::Arc, }; -use client::{graphics, metrics, net, Config, Sim}; +use client::{graphics, metrics, net, Config}; use save::Save; use ash::extensions::khr; @@ -65,10 +65,9 @@ fn main() { // Kick off networking let net = net::spawn(config.clone()); - let sim = Sim::new(net); // Finish creating the window, including the Vulkan resources used to render to it - let window = graphics::Window::new(window, core.clone(), config, metrics, sim); + let window = graphics::Window::new(window, core.clone(), config, metrics, net); // Initialize widely-shared graphics resources let gfx = Arc::new( diff --git a/client/src/sim.rs b/client/src/sim.rs index 5f4d3e30..df68b3d0 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -7,7 +7,7 @@ use tracing::{debug, error, trace}; use crate::{net, prediction::PredictedMotion, Net}; use common::{ character_controller, - graph::{Graph, NodeId}, + graph::NodeId, node::{populate_fresh_nodes, DualGraph}, proto::{self, Character, CharacterInput, CharacterState, Command, Component, Position}, sanitize_motion_input, EntityId, GraphEntities, SimConfig, Step, @@ -15,14 +15,13 @@ use common::{ /// Game state pub struct Sim { - net: Net, - // World state pub graph: DualGraph, pub graph_entities: GraphEntities, entity_ids: FxHashMap, pub world: hecs::World, - pub params: Option, + pub cfg: SimConfig, + pub local_character_id: EntityId, pub local_character: Option, orientation: na::UnitQuaternion, step: Option, @@ -45,15 +44,16 @@ pub struct Sim { } impl Sim { - pub fn new(net: Net) -> Self { + pub fn new(cfg: SimConfig, local_character_id: EntityId) -> Self { + let mut graph = DualGraph::new(); + populate_fresh_nodes(&mut graph); Self { - net, - - graph: Graph::new(), + graph, graph_entities: GraphEntities::new(), entity_ids: FxHashMap::default(), world: hecs::World::new(), - params: None, + cfg, + local_character_id, local_character: None, orientation: na::one(), step: None, @@ -85,71 +85,58 @@ impl Sim { self.toggle_no_clip = true; } - pub fn params(&self) -> Option<&Parameters> { - self.params.as_ref() + pub fn cfg(&self) -> &SimConfig { + &self.cfg } - pub fn step(&mut self, dt: Duration) { + pub fn step(&mut self, dt: Duration, net: &mut Net) { self.orientation.renormalize_fast(); - while let Ok(msg) = self.net.incoming.try_recv() { - self.handle_net(msg); - } - - if let Some(step_interval) = self.params.as_ref().map(|x| x.cfg.step_interval) { - self.since_input_sent += dt; - if let Some(overflow) = self.since_input_sent.checked_sub(step_interval) { - // At least one step interval has passed since we last sent input, so it's time to - // send again. + let step_interval = self.cfg.step_interval; + self.since_input_sent += dt; + if let Some(overflow) = self.since_input_sent.checked_sub(step_interval) { + // At least one step interval has passed since we last sent input, so it's time to + // send again. - // Update average movement input for the time between the last input sample and the end of - // the previous step. dt > overflow because we check whether a step has elapsed - // after each increment. - self.average_movement_input += self.movement_input * (dt - overflow).as_secs_f32() - / step_interval.as_secs_f32(); + // Update average movement input for the time between the last input sample and the end of + // the previous step. dt > overflow because we check whether a step has elapsed + // after each increment. + self.average_movement_input += + self.movement_input * (dt - overflow).as_secs_f32() / step_interval.as_secs_f32(); - // Send fresh input - self.send_input(); + // Send fresh input + self.send_input(net); - // Toggle no clip at the start of a new step - if self.toggle_no_clip { - self.no_clip = !self.no_clip; - self.toggle_no_clip = false; - } + // Toggle no clip at the start of a new step + if self.toggle_no_clip { + self.no_clip = !self.no_clip; + self.toggle_no_clip = false; + } - // Reset state for the next step - if overflow > step_interval { - // If it's been more than two timesteps since we last sent input, skip ahead - // rather than spamming the server. - self.average_movement_input = na::zero(); - self.since_input_sent = Duration::new(0, 0); - } else { - self.average_movement_input = - self.movement_input * overflow.as_secs_f32() / step_interval.as_secs_f32(); - // Send the next input a little sooner if necessary to stay in sync - self.since_input_sent = overflow; - } + // Reset state for the next step + if overflow > step_interval { + // If it's been more than two timesteps since we last sent input, skip ahead + // rather than spamming the server. + self.average_movement_input = na::zero(); + self.since_input_sent = Duration::new(0, 0); } else { - // Update average movement input for the time within the current step - self.average_movement_input += - self.movement_input * dt.as_secs_f32() / step_interval.as_secs_f32(); + self.average_movement_input = + self.movement_input * overflow.as_secs_f32() / step_interval.as_secs_f32(); + // Send the next input a little sooner if necessary to stay in sync + self.since_input_sent = overflow; } + } else { + // Update average movement input for the time within the current step + self.average_movement_input += + self.movement_input * dt.as_secs_f32() / step_interval.as_secs_f32(); } } - fn handle_net(&mut self, msg: net::Message) { + pub fn handle_net(&mut self, msg: net::Message) { use net::Message::*; match msg { - ConnectionLost(e) => { - error!("connection lost: {}", e); - } - Hello(msg) => { - self.params = Some(Parameters { - character_id: msg.character, - cfg: msg.sim_config, - }); - // Populate the root node - populate_fresh_nodes(&mut self.graph); + ConnectionLost(_) | Hello(_) => { + panic!("Should not get here, case handled elsewhere"); } Spawns(msg) => self.handle_spawns(msg), StateDelta(msg) => { @@ -200,10 +187,7 @@ impl Sim { } fn reconcile_prediction(&mut self, latest_input: u16) { - let Some(params) = self.params.as_ref() else { - return; - }; - let id = params.character_id; + let id = self.local_character_id; let Some(&entity) = self.entity_ids.get(&id) else { debug!(%id, "reconciliation attempted for unknown entity"); return; @@ -223,7 +207,7 @@ impl Sim { } }; self.prediction.reconcile( - ¶ms.cfg, + &self.cfg, &self.graph, latest_input, *pos, @@ -277,7 +261,7 @@ impl Sim { if let Some(node) = node { self.graph_entities.insert(node, entity); } - if id == self.params.as_ref().unwrap().character_id { + if id == self.local_character_id { self.local_character = Some(entity); } if let Some(x) = self.entity_ids.insert(id, entity) { @@ -286,18 +270,17 @@ impl Sim { } } - fn send_input(&mut self) { - let params = self.params.as_ref().unwrap(); + fn send_input(&mut self, net: &mut Net) { let character_input = CharacterInput { movement: sanitize_motion_input(self.orientation * self.average_movement_input), no_clip: self.no_clip, }; let generation = self .prediction - .push(¶ms.cfg, &self.graph, &character_input); + .push(&self.cfg, &self.graph, &character_input); // Any failure here will be better handled in handle_net's ConnectionLost case - let _ = self.net.outgoing.send(Command { + let _ = net.outgoing.send(Command { generation, character_input, orientation: self.orientation, @@ -307,27 +290,24 @@ impl Sim { pub fn view(&self) -> Position { let mut result = *self.prediction.predicted_position(); let mut predicted_velocity = *self.prediction.predicted_velocity(); - if let Some(ref params) = self.params { - // Apply input that hasn't been sent yet - let predicted_input = CharacterInput { - // We divide by how far we are through the timestep because self.average_movement_input - // is always over the entire timestep, filling in zeroes for the future, and we - // want to use the average over what we have so far. Dividing by zero is handled - // by the character_controller sanitizing this input. - movement: self.orientation * self.average_movement_input - / (self.since_input_sent.as_secs_f32() - / params.cfg.step_interval.as_secs_f32()), - no_clip: self.no_clip, - }; - character_controller::run_character_step( - ¶ms.cfg, - &self.graph, - &mut result, - &mut predicted_velocity, - &predicted_input, - self.since_input_sent.as_secs_f32(), - ); - } + // Apply input that hasn't been sent yet + let predicted_input = CharacterInput { + // We divide by how far we are through the timestep because self.average_movement_input + // is always over the entire timestep, filling in zeroes for the future, and we + // want to use the average over what we have so far. Dividing by zero is handled + // by the character_controller sanitizing this input. + movement: self.orientation * self.average_movement_input + / (self.since_input_sent.as_secs_f32() / self.cfg.step_interval.as_secs_f32()), + no_clip: self.no_clip, + }; + character_controller::run_character_step( + &self.cfg, + &self.graph, + &mut result, + &mut predicted_velocity, + &predicted_input, + self.since_input_sent.as_secs_f32(), + ); result.local *= self.orientation.to_homogeneous(); result } @@ -352,9 +332,3 @@ impl Sim { .expect("destroyed nonexistent entity"); } } - -/// Simulation details received on connect -pub struct Parameters { - pub cfg: SimConfig, - pub character_id: EntityId, -}