From 08355656f50d8e095c280962b250178acbd1299f Mon Sep 17 00:00:00 2001 From: core Date: Sat, 6 Jun 2026 22:35:44 -0400 Subject: [PATCH] netcode: planet updates --- crates/unified/src/client/mod.rs | 4 +- crates/unified/src/client/net.rs | 22 ++--- .../src/client/net/incoming_planets.rs | 38 ++++++++ crates/unified/src/server/net.rs | 22 ++--- .../unified/src/server/net/update_planets.rs | 64 +++++++++++++ crates/unified/src/server/player/join.rs | 3 +- crates/unified/src/server/plugins.rs | 21 +++++ crates/unified/src/shared/ecs.rs | 2 - crates/unified/src/shared/net.rs | 92 +++++-------------- crates/unified/src/shared/net/planet.rs | 18 ++++ .../src/shared/net/staged_transform.rs | 14 +++ crates/unified/src/shared/plugins.rs | 18 +--- 12 files changed, 196 insertions(+), 122 deletions(-) create mode 100644 crates/unified/src/client/net/incoming_planets.rs create mode 100644 crates/unified/src/server/net/update_planets.rs create mode 100644 crates/unified/src/shared/net/planet.rs create mode 100644 crates/unified/src/shared/net/staged_transform.rs diff --git a/crates/unified/src/client/mod.rs b/crates/unified/src/client/mod.rs index 337c0b250daa62ff6026600c69a11300ef93c0d6..e26f312938ca7bf13c17532ef5c268c58c2f2788 100644 --- a/crates/unified/src/client/mod.rs +++ b/crates/unified/src/client/mod.rs @@ -149,11 +149,11 @@ pub fn find_me( mut msgs: MessageReader, mut commands: Commands, mut time_offset: ResMut, -) { +) {/* for msg in msgs.read() { let we_are = msg.you_are; info!(?we_are, "joined successfully"); commands.entity(we_are).insert(Me); time_offset.0 = msg.time_offset; - } + }*/ } diff --git a/crates/unified/src/client/net.rs b/crates/unified/src/client/net.rs index 7094a80012e1e607c7016c4a3e9f1cdaaba925c0..11d9629ae5ffeccb924e92d15b490ae26b501083 100644 --- a/crates/unified/src/client/net.rs +++ b/crates/unified/src/client/net.rs @@ -1,20 +1,10 @@ -use crate::{prelude::*, shared::net::{ServerEntityMap, SpawnEntity}}; +pub mod incoming_planets; + +use crate::{prelude::*, shared::net::{ServerEntityMap}}; +use crate::client::net::incoming_planets::handle_incoming_planets; pub fn net_plugin(app: &mut App) { app .insert_resource(ServerEntityMap::default()) - .add_systems(PostUpdate, spawn_server_entities); -} - -fn spawn_server_entities( - mut spawn_entity_messages: MessageReader, - mut entity_mapper: ResMut, - mut commands: Commands, -) { - for spawn_entity in spawn_entity_messages.read() { - let entity = commands.spawn_empty(); - debug!("client: {:?}, server: {:?}", entity.id(), spawn_entity.server); - entity_mapper.server_to_client.set_mapped(spawn_entity.server, entity.id()); - entity_mapper.client_to_server.set_mapped(entity.id(), spawn_entity.server); - } -} + .add_systems(Update, handle_incoming_planets); +} \ No newline at end of file diff --git a/crates/unified/src/client/net/incoming_planets.rs b/crates/unified/src/client/net/incoming_planets.rs new file mode 100644 index 0000000000000000000000000000000000000000..93332a1ba7dd8155495f57c53dfdfc8ecbf57dd5 --- /dev/null +++ b/crates/unified/src/client/net/incoming_planets.rs @@ -0,0 +1,38 @@ +use bevy::log::{debug, warn}; +use bevy::prelude::{Commands, MessageReader, ResMut, Transform}; +use crate::prelude::Query; +use crate::shared::config::planet::Planet; +use crate::shared::net::planet::PlanetUpdatePacket; +use crate::shared::net::ServerEntityMap; + +pub fn handle_incoming_planets( + mut msgs: MessageReader, + + mut q_planets: Query<(&mut Planet, &mut Transform)>, + mut entity_map: ResMut, + mut commands: Commands +) { + for msg in msgs.read() { + 'to_next_planet: for planet in &msg.updated_planets { + if let Some(local_entity) = entity_map.server_to_client.get(&planet.server_entity) { + let Ok((mut planet_data, mut transform)) = q_planets.get_mut(*local_entity) else { + warn!("local planet entity {:?} for planet srv:{:?} doesn't exist? skipping update, this is a bug", local_entity, planet.server_entity); + continue 'to_next_planet + }; + if planet.planet_data_changed { + *planet_data = planet.planet.clone(); + } + *transform = planet.transform; + } else { + // Spawn new planet + let e = commands.spawn(( + planet.planet.clone(), + planet.transform + )).id(); + entity_map.server_to_client.insert(planet.server_entity, e.clone()); + entity_map.client_to_server.insert(e.clone(), planet.server_entity); + debug!(?planet.planet, "spawned new planet"); + } + } + } +} \ No newline at end of file diff --git a/crates/unified/src/server/net.rs b/crates/unified/src/server/net.rs index 3664b6c72cceefb2cf8a88ce12c0eab1ffcbafd3..d4214611b81952a90177fde59a64dd2dc9af375f 100644 --- a/crates/unified/src/server/net.rs +++ b/crates/unified/src/server/net.rs @@ -1,18 +1,8 @@ -use crate::{prelude::*, shared::net::{Mapped, SendTargets, SpawnEntity, ToClients}}; +pub mod update_planets; -pub fn net_plugin(app: &mut App) { - app.add_systems(PreUpdate, detect_entity_spawn); -} +use crate::{prelude::*, shared::net::{SendTargets, ToClients}}; +use crate::server::net::update_planets::send_updated_planets; -fn detect_entity_spawn( - mapped_entities: Query>, - mut spawn_entity: MessageWriter>, -) { - for entity in mapped_entities { - debug!("entity was spawned"); - spawn_entity.write(ToClients { - message: SpawnEntity { server: entity }, - targets: SendTargets::All, - }); - } -} +pub fn net_plugin(app: &mut App) { + app.add_systems(Update, send_updated_planets); +} \ No newline at end of file diff --git a/crates/unified/src/server/net/update_planets.rs b/crates/unified/src/server/net/update_planets.rs new file mode 100644 index 0000000000000000000000000000000000000000..bfa374f223b0358c47a0f069361888773e3b5107 --- /dev/null +++ b/crates/unified/src/server/net/update_planets.rs @@ -0,0 +1,64 @@ +use std::collections::BTreeSet; +use bevy::prelude::{Added, Changed, Commands, MessageWriter, Query}; +use crate::prelude::{Entity, Transform}; +use crate::shared::config::planet::Planet; +use crate::shared::net::planet::{PlanetUpdatePacket, UpdatedPlanet}; +use crate::shared::net::staged_transform::LastStagedTransform; +use crate::shared::net::{SendTargets, ToClients}; + +pub fn send_updated_planets( + q_new_planets: Query<(Entity, &Transform), Added>, + q_moved_planets: Query<(Entity, &Transform, &LastStagedTransform), Changed>, + q_changed_planets: Query>, + + q_planets: Query<(&Planet, &Transform)>, + + mut update_packets_out: MessageWriter>, + mut commands: Commands +) { + let mut planets_to_send: BTreeSet = BTreeSet::new(); + + // Updating the Planet behavior triggers sprite loading on the client. We don't want to send it every time the Transform changes + let mut planets_with_planet_change: BTreeSet = BTreeSet::new(); + + for (new_planet, transform) in q_new_planets.iter() { + planets_to_send.insert(new_planet); // Always send newly created planets... + // ...and add LastStagedTransform... + commands.entity(new_planet).insert(LastStagedTransform(*transform)); + // and put a marker, we need to copy planet data across + planets_with_planet_change.insert(new_planet); + } + for (moved_planet, current_transform, maybe_staged_transform) in q_moved_planets.iter() { + // have we had a significant transform change? + if !maybe_staged_transform.should_update(current_transform) { continue; } + // we have, resend + planets_to_send.insert(moved_planet); + } + for changed_planet in q_changed_planets.iter() { + // if Planet changed, always resend + planets_to_send.insert(changed_planet); + // and put a marker, we need to copy planet data across + planets_with_planet_change.insert(changed_planet); + } + + // process all planets to send + let mut packet = PlanetUpdatePacket { + updated_planets: vec![] + }; + + for planet_to_send in planets_to_send.iter() { + let Ok(planet_info) = q_planets.get(*planet_to_send) else { continue }; + + packet.updated_planets.push(UpdatedPlanet { + server_entity: *planet_to_send, + planet: planet_info.0.clone(), + planet_data_changed: planets_with_planet_change.contains(planet_to_send), + transform: *planet_info.1, + }); + } + + update_packets_out.write(ToClients { + message: packet, + targets: SendTargets::All, + }); +} \ No newline at end of file diff --git a/crates/unified/src/server/player/join.rs b/crates/unified/src/server/player/join.rs index df6aeda1ab34b866b33e65f0bb20df2b99d438de..3d43e5051cbfebf540dea261f9d12bbeddd6d22e 100644 --- a/crates/unified/src/server/player/join.rs +++ b/crates/unified/src/server/player/join.rs @@ -5,7 +5,7 @@ use crate::shared::ecs::{Player, PlayerStorage}; use crate::prelude::*; use crate::server::ConnectedGameEntity; use crate::server::part::SpawnPartRequest; -use crate::shared::net::{ClientId, Hi, SendTargets, ToClients, Mapped}; +use crate::shared::net::{ClientId, Hi, SendTargets, ToClients}; use crate::shared::world_config::WorldConfigResource; const SPAWN_ORBIT_OFFSET: f64 = 150.0; @@ -60,7 +60,6 @@ fn join_player(joined_player: Entity, mut commands: Commands, wc: &GlobalWorldCo } #[derive(Component)] -#[require(Mapped)] pub struct PendingPlayer; pub fn handle_new_players( diff --git a/crates/unified/src/server/plugins.rs b/crates/unified/src/server/plugins.rs index 438125043cbd2ed7a9b7f9b0d0f66a3033eaaa49..e28d2470775971e357c99819e95ef9e8956057b1 100644 --- a/crates/unified/src/server/plugins.rs +++ b/crates/unified/src/server/plugins.rs @@ -1,15 +1,36 @@ use std::time::Duration; use aeronet_transport::AeronetTransportPlugin; use aeronet_websocket::server::WebSocketServerPlugin; +use avian2d::interpolation::PhysicsInterpolationPlugin; +use avian2d::PhysicsPlugins; +use avian2d::prelude::{Gravity, IslandPlugin}; use bevy::app::{PluginGroup, PluginGroupBuilder, ScheduleRunnerPlugin}; +use crate::prelude::{App, Startup}; use crate::shared::plugins::TICK_RATE; pub struct ServerPluginGroup; +const PHYSICS_LENGTH_UNIT: f64 = 100.0; + impl PluginGroup for ServerPluginGroup { fn build(self) -> PluginGroupBuilder { PluginGroupBuilder::start::() .add(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(1.0 / TICK_RATE))) .add(WebSocketServerPlugin) + .add_group( + PhysicsPlugins::default() + .with_length_unit(PHYSICS_LENGTH_UNIT) + .set(PhysicsInterpolationPlugin::interpolate_all()) + .build() + .disable::() + ) + .add(physics_setup_plugin) } } + +fn physics_setup_plugin(app: &mut App) { + app.insert_resource(Gravity::ZERO); + app.add_systems(Startup, setup_physics); +} + +fn setup_physics() {} diff --git a/crates/unified/src/shared/ecs.rs b/crates/unified/src/shared/ecs.rs index 37ba0686121d97f9d4173a840a368ec416e36cc4..fcda652bedc9fa7ecceb9b0a344a2f3cbdf7c8a9 100644 --- a/crates/unified/src/shared/ecs.rs +++ b/crates/unified/src/shared/ecs.rs @@ -1,6 +1,5 @@ pub mod thruster; -use crate::shared::net::Mapped; use crate::shared::config::part::PartConfig; use bevy::math::{Quat, Vec2}; use bevy::camera::visibility::RenderLayers; @@ -41,7 +40,6 @@ pub struct PartHandle(pub Handle); pub struct TimeOffset(pub f64); #[derive(Component, Serialize, Deserialize, Debug)] -#[require(Mapped)] pub struct Player { pub client: Entity, } diff --git a/crates/unified/src/shared/net.rs b/crates/unified/src/shared/net.rs index a927be617970a9763253a816e9e074c65ffc2513..750623ad08c00e69963c143bcb3656ad0bbcd775 100644 --- a/crates/unified/src/shared/net.rs +++ b/crates/unified/src/shared/net.rs @@ -1,3 +1,7 @@ +pub mod planet; +pub mod staged_transform; + +use std::any::TypeId; use std::collections::HashMap; use std::sync::OnceLock; use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; @@ -14,61 +18,29 @@ use bevy::reflect::DynamicTypePath; use postcard::{from_bytes, to_allocvec, to_slice, to_vec}; use crate::prelude::{App, Message}; use crate::shared::thrust::ThrustSolution; -//use bevy_replicon::prelude::*; use serde::{Deserialize, Serialize}; use crate::shared::attachment::{Joint, JointOf, PartInShip, Peer, Ship, SnapOf, SnapOfJoint}; use crate::shared::config::planet::{Planet, PlanetSpring, PlanetSpringJoint}; use crate::shared::ecs::{CanCraft, CraftPartRequest, DragRequestEvent, Drill, Part, Player, PlayerStorage, SingleStorage, Temperature, ToggleDrillEvent}; use crate::shared::ecs::thruster::{Thruster, ThrusterOfPart}; - - -/*pub fn register_replication(app: &mut App) { - app - .add_mapped_server_message::(Channel::Ordered) - - .replicate::() - .replicate::() - - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - - .replicate::() - .replicate::() - .replicate::() - - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::() - .replicate::(); -}*/ +use crate::shared::net::planet::PlanetUpdatePacket; pub fn register_net(app: &mut App) { app .add_mapped_server_message::() - .add_server_message::() + + .add_server_message::() .add_client_message::() .add_client_message::() .add_client_message::() - .add_client_message::(); + .add_client_message::() + + .add_systems(PostStartup, |srv_msg_reg: Res, client_msg_reg: Res| { + debug!("network registration complete"); + debug!("client registry: {:#?}", client_msg_reg); + debug!("server registry: {:#?}", srv_msg_reg); + }); } #[derive(Message, Clone, Deserialize, Serialize, TypePath, MapEntities)] @@ -78,10 +50,6 @@ pub struct Hi { pub time_offset: f64, } -#[derive(Message, Deserialize, Serialize, TypePath)] -pub struct SpawnEntity { - pub server: Entity, -} pub fn setup_net(app: &mut App) { app.insert_resource(ServerMessageRegistry::default()); app.insert_resource(ClientMessageRegistry::default()); @@ -112,17 +80,15 @@ pub enum ClientId { Server, Client(Entity), } -#[derive(Component, Default)] -pub struct Mapped; #[derive(Resource, Default)] pub struct ServerEntityMap { pub server_to_client: EntityHashMap, pub client_to_server: EntityHashMap, } -#[derive(Resource, Default)] +#[derive(Resource, Default, Debug)] pub struct ServerMessageRegistry { - forward: HashMap, + forward: HashMap, reverse: HashMap, &mut World)>, } impl ServerMessageRegistry { @@ -130,9 +96,9 @@ impl ServerMessageRegistry { self.forward.len() } } -#[derive(Resource, Default)] +#[derive(Resource, Default, Debug)] pub struct ClientMessageRegistry { - forward: HashMap, + forward: HashMap, reverse: HashMap, ClientId, &mut World)>, } impl ClientMessageRegistry { @@ -188,18 +154,17 @@ impl NetAppExt for App { } fn register_mapped_server_message Deserialize<'a>>(registry: &mut ServerMessageRegistry) { let lane_index = get_lane_index(); - registry.forward.insert(T::type_path().to_string(), lane_index); + registry.forward.insert(TypeId::of::(), lane_index); registry.reverse.insert(lane_index, |payload: Vec, world: &mut World| { let mut entity_map = world.resource_mut::(); let mut message = from_bytes::(&payload).expect(&format!("Failed to deserialize message of type {}", T::type_path())); message.map_entities(&mut entity_map.server_to_client); - debug!("entity map: {:?}", entity_map.server_to_client); world.write_message(message).expect("Could not send message to game"); }); } fn register_server_message Deserialize<'a>>(registry: &mut ServerMessageRegistry) { let lane_index = get_lane_index(); - registry.forward.insert(T::type_path().to_string(), lane_index); + registry.forward.insert(TypeId::of::(), lane_index); registry.reverse.insert(lane_index, |payload: Vec, world: &mut World| { let message = from_bytes::(&payload).expect(&format!("Failed to deserialize message of type {}", T::type_path())); world.write_message(message).expect("Could not send message to game"); @@ -207,7 +172,7 @@ fn register_server_message Deserialize<'a>>(regi } fn register_client_message Deserialize<'a>>(registry: &mut ClientMessageRegistry) { let lane_index = get_lane_index(); - registry.forward.insert(T::type_path().to_string(), lane_index); + registry.forward.insert(TypeId::of::(), lane_index); registry.reverse.insert(lane_index, |payload: Vec, client_id: ClientId, world: &mut World| { let message = from_bytes::(&payload).expect(&format!("Failed to deserialize message of type {}", T::type_path())); world.write_message(FromClients { @@ -226,10 +191,8 @@ fn send_to_client( match &message.targets { SendTargets::All => { for (client_entity, mut transport) in &mut clients { - debug!("lane: {:?}", message_registry.forward.get(M::type_path()).unwrap()); - debug!("registry: {:?}", message_registry.forward); transport.send.push( - *message_registry.forward.get(M::type_path()) + *message_registry.forward.get(&TypeId::of::()) .expect("Failed to get message lane; the message likely isn't serialized yet"), to_allocvec(&message).expect("Failed to serialize message").into(), bevy::platform::time::Instant::now(), @@ -239,11 +202,8 @@ fn send_to_client( SendTargets::Single(client) => { for (client_entity, mut transport) in &mut clients { if let ClientId::Client(client) = client && client_entity == *client { - debug!("writing message"); - debug!("lane: {:?}", message_registry.forward.get(M::type_path()).unwrap()); - debug!("registry: {:?}", message_registry.forward); transport.send.push( - *message_registry.forward.get(M::type_path()) + *message_registry.forward.get(&TypeId::of::()) .expect("Failed to get message lane; the message likely isn't serialized yet"), to_allocvec(&message).expect("Failed to serialize message").into(), bevy::platform::time::Instant::now(), @@ -262,7 +222,7 @@ fn send_to_server( for message in messages.read() { for mut transport in &mut sessions { transport.send.push( - *message_registry.forward.get(M::type_path()) + *message_registry.forward.get(&TypeId::of::()) .expect("Failed to get message lane; the message likely isn't serialized yet"), to_allocvec(&message).expect("Failed to serialize message").into(), bevy::platform::time::Instant::now(), @@ -281,7 +241,7 @@ fn send_mapped_to_server()) .expect("Failed to get message lane; the message likely isn't serialized yet"), to_allocvec(&message).expect("Failed to serialize message").into(), bevy::platform::time::Instant::now(), @@ -300,8 +260,6 @@ fn recv_from_server( let mut messages = Vec::new(); for mut transport in sessions.iter_mut() { for message in transport.recv.msgs.drain() { - debug!("forward: {:?}", message_registry.forward); - debug!("reverse: {:?}", message_registry.reverse); let payload = message.payload; let message_fn = message_registry.reverse.get(&message.lane).expect("Packet was sent across a lane that didn't have a message assigned to it yet"); messages.push((*message_fn, payload)); diff --git a/crates/unified/src/shared/net/planet.rs b/crates/unified/src/shared/net/planet.rs new file mode 100644 index 0000000000000000000000000000000000000000..2e290d45f8c70f2bf5d5639e2c01104b1353cbd3 --- /dev/null +++ b/crates/unified/src/shared/net/planet.rs @@ -0,0 +1,18 @@ +use bevy::prelude::{Entity, Message, Transform, TypePath}; +use serde::{Deserialize, Serialize}; +use crate::shared::config::planet::Planet; + +#[derive(Serialize, Deserialize, Message, TypePath, Debug, Clone)] +pub struct PlanetUpdatePacket { + pub updated_planets: Vec +} + +#[derive(Serialize, Deserialize, TypePath, Debug, Clone)] +pub struct UpdatedPlanet { + pub server_entity: Entity, + + pub planet: Planet, + pub planet_data_changed: bool, + + pub transform: Transform +} \ No newline at end of file diff --git a/crates/unified/src/shared/net/staged_transform.rs b/crates/unified/src/shared/net/staged_transform.rs new file mode 100644 index 0000000000000000000000000000000000000000..f4f490678cc446a3f0c329ddf276d1cbba0981ed --- /dev/null +++ b/crates/unified/src/shared/net/staged_transform.rs @@ -0,0 +1,14 @@ +use bevy::prelude::{Component, Transform}; + +#[derive(Component)] +pub struct LastStagedTransform(pub Transform); + +impl LastStagedTransform { + pub fn should_update(&self, given_current: &Transform) -> bool { + const VECTOR_THRESHOLD: f32 = 0.1; // how much a value needs to change to resend this transform + + if !given_current.translation.abs_diff_eq(self.0.translation, VECTOR_THRESHOLD) { return true }; + if !given_current.scale.abs_diff_eq(self.0.scale, VECTOR_THRESHOLD) { return true }; + !given_current.rotation.abs_diff_eq(self.0.rotation, VECTOR_THRESHOLD) + } +} \ No newline at end of file diff --git a/crates/unified/src/shared/plugins.rs b/crates/unified/src/shared/plugins.rs index 4390058cffe244c1ad551b16f69d66c4ca3bafe0..94951fc24ef37e0ad5c44562e0d4243d4a2d134e 100644 --- a/crates/unified/src/shared/plugins.rs +++ b/crates/unified/src/shared/plugins.rs @@ -15,7 +15,6 @@ use crate::shared::config::world::GlobalWorldConfig; use crate::shared::world_config::world_config_plugin; pub const TICK_RATE: f64 = 20.0; -const PHYSICS_LENGTH_UNIT: f64 = 100.0; pub struct SharedPluginGroup; @@ -32,14 +31,6 @@ impl PluginGroup for SharedPluginGroup { app.insert_resource(Time::::default().with_relative_speed(1.0)); app.insert_resource(TimeOffset::default()); }) - .add_group( - PhysicsPlugins::default() - .with_length_unit(PHYSICS_LENGTH_UNIT) - .set(PhysicsInterpolationPlugin::interpolate_all()) - .build() - .disable::() - ) - .add(physics_setup_plugin) //.add(register_replication) .add(register_everything) .add(setup_net) @@ -55,11 +46,4 @@ impl PluginGroup for SharedPluginGroup { } pub fn register_everything(app: &mut App) { -} - -fn physics_setup_plugin(app: &mut App) { - app.insert_resource(Gravity::ZERO); - app.add_systems(Startup, setup_physics); -} - -fn setup_physics() {} +} \ No newline at end of file