~starkingdoms/starkingdoms

08355656f50d8e095c280962b250178acbd1299f — core 10 days ago aa66f9e
netcode: planet updates
M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +2 -2
@@ 149,11 149,11 @@ pub fn find_me(
    mut msgs: MessageReader<Hi>,
    mut commands: Commands,
    mut time_offset: ResMut<TimeOffset>,
) {
) {/*
    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;
    }
    }*/
}

M crates/unified/src/client/net.rs => crates/unified/src/client/net.rs +6 -16
@@ 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<SpawnEntity>,
    mut entity_mapper: ResMut<ServerEntityMap>,
    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

A crates/unified/src/client/net/incoming_planets.rs => crates/unified/src/client/net/incoming_planets.rs +38 -0
@@ 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<PlanetUpdatePacket>,

    mut q_planets: Query<(&mut Planet, &mut Transform)>,
    mut entity_map: ResMut<ServerEntityMap>,
    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

M crates/unified/src/server/net.rs => crates/unified/src/server/net.rs +6 -16
@@ 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<Entity, Added<Mapped>>,
    mut spawn_entity: MessageWriter<ToClients<SpawnEntity>>,
) {
    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

A crates/unified/src/server/net/update_planets.rs => crates/unified/src/server/net/update_planets.rs +64 -0
@@ 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<Planet>>,
    q_moved_planets: Query<(Entity, &Transform, &LastStagedTransform), Changed<Transform>>,
    q_changed_planets: Query<Entity, Changed<Planet>>,

    q_planets: Query<(&Planet, &Transform)>,

    mut update_packets_out: MessageWriter<ToClients<PlanetUpdatePacket>>,
    mut commands: Commands
) {
    let mut planets_to_send: BTreeSet<Entity> = 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<Entity> = 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

M crates/unified/src/server/player/join.rs => crates/unified/src/server/player/join.rs +1 -2
@@ 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(

M crates/unified/src/server/plugins.rs => crates/unified/src/server/plugins.rs +21 -0
@@ 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::<Self>()
            .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::<IslandPlugin>()
            )
            .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() {}

M crates/unified/src/shared/ecs.rs => crates/unified/src/shared/ecs.rs +0 -2
@@ 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<PartConfig>);
pub struct TimeOffset(pub f64);

#[derive(Component, Serialize, Deserialize, Debug)]
#[require(Mapped)]
pub struct Player {
    pub client: Entity,
}

M crates/unified/src/shared/net.rs => crates/unified/src/shared/net.rs +25 -67
@@ 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::<Hi>(Channel::Ordered)

        .replicate::<Transform>()
        .replicate::<GlobalTransform>()

        .replicate::<Position>()
        .replicate::<Rotation>()
        .replicate::<LinearVelocity>()
        .replicate::<AngularVelocity>()
        .replicate::<AngularInertia>()
        .replicate::<Mass>()
        .replicate::<Collider>()

        .replicate::<Part>()
        .replicate::<Planet>()
        .replicate::<Player>()

        .replicate::<ChildOf>()
        .replicate::<Ship>()
        .replicate::<PartInShip>()
        .replicate::<Joint>()
        .replicate::<Peer>()
        .replicate::<JointOf>()
        .replicate::<SnapOfJoint>()
        .replicate::<SnapOf>()
        .replicate::<PlayerStorage>()
        .replicate::<Thruster>()
        .replicate::<ThrusterOfPart>()
        .replicate::<CanCraft>()
        .replicate::<Temperature>()
        .replicate::<Drill>()
        .replicate::<SingleStorage>()
        .replicate::<PlanetSpring>()
        .replicate::<PlanetSpringJoint>();
}*/
use crate::shared::net::planet::PlanetUpdatePacket;

pub fn register_net(app: &mut App) {
    app
        .add_mapped_server_message::<Hi>()
        .add_server_message::<SpawnEntity>()

        .add_server_message::<PlanetUpdatePacket>()

        .add_client_message::<DragRequestEvent>()
        .add_client_message::<ToggleDrillEvent>()
        .add_client_message::<CraftPartRequest>()
        .add_client_message::<ThrustSolution>();
        .add_client_message::<ThrustSolution>()

        .add_systems(PostStartup, |srv_msg_reg: Res<ServerMessageRegistry>, client_msg_reg: Res<ClientMessageRegistry>| {
            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<Entity>,
    pub client_to_server: EntityHashMap<Entity>,
}
#[derive(Resource, Default)]
#[derive(Resource, Default, Debug)]
pub struct ServerMessageRegistry {
    forward: HashMap<String, LaneIndex>,
    forward: HashMap<TypeId, LaneIndex>,
    reverse: HashMap<LaneIndex, fn(Vec<u8>, &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<String, LaneIndex>,
    forward: HashMap<TypeId, LaneIndex>,
    reverse: HashMap<LaneIndex, fn(Vec<u8>, ClientId, &mut World)>,
}
impl ClientMessageRegistry {


@@ 188,18 154,17 @@ impl NetAppExt for App {
}
fn register_mapped_server_message<T: Message + MapEntities + TypePath + for<'a> 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::<T>(), lane_index);
    registry.reverse.insert(lane_index, |payload: Vec<u8>, world: &mut World| {
        let mut entity_map = world.resource_mut::<ServerEntityMap>();
        let mut message = from_bytes::<T>(&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<T: Message + TypePath + for<'a> 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::<T>(), lane_index);
    registry.reverse.insert(lane_index, |payload: Vec<u8>, world: &mut World| {
        let message = from_bytes::<T>(&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<T: Message + TypePath + for<'a> Deserialize<'a>>(regi
}
fn register_client_message<T: Message + TypePath + for<'a> 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::<T>(), lane_index);
    registry.reverse.insert(lane_index, |payload: Vec<u8>, client_id: ClientId, world: &mut World| {
        let message = from_bytes::<T>(&payload).expect(&format!("Failed to deserialize message of type {}", T::type_path()));
        world.write_message(FromClients {


@@ 226,10 191,8 @@ fn send_to_client<M: Message + TypePath + Serialize>(
        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::<M>())
                            .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<M: Message + TypePath + Serialize>(
            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::<M>())
                                .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<M: Message + TypePath + Serialize>(
    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::<M>())
                    .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<M: Message + Clone + MapEntities + TypePath + Serialize
            let mut message = message.clone();
            message.map_entities(&mut entity_map.client_to_server);
            transport.send.push(
                *message_registry.forward.get(M::type_path())
                *message_registry.forward.get(&TypeId::of::<M>())
                    .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));

A crates/unified/src/shared/net/planet.rs => crates/unified/src/shared/net/planet.rs +18 -0
@@ 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<UpdatedPlanet>
}

#[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

A crates/unified/src/shared/net/staged_transform.rs => crates/unified/src/shared/net/staged_transform.rs +14 -0
@@ 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

M crates/unified/src/shared/plugins.rs => crates/unified/src/shared/plugins.rs +1 -17
@@ 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::<Physics>::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::<IslandPlugin>()
            )
            .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