~starkingdoms/starkingdoms

1bcde11a581ededd03b6e60e8e97048f7556ad66 — core 8 days ago 0835565
netcode: part updates (mystery performance issue, probably related to lots of malloc())
M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +24 -8
@@ 22,7 22,7 @@ use crate::client::components::Me;
use crate::client::ship::attachment::client_attachment_plugin;
use crate::shared::ecs::{GameplayState, TimeOffset};
use crate::shared::gravity::update_gravity;
use crate::shared::net::{Hi, ClientMessageRegistry, ServerMessageRegistry};
use crate::shared::net::{Hi, ClientMessageRegistry, ServerMessageRegistry, ServerEntityMap};
use crate::shared::orbit::OrbitPlugin;

pub mod colors;


@@ 66,7 66,8 @@ impl Plugin for ClientPlugin {
            .add_plugins(starguide_input_plugin)
            .add_plugins(starguide_orbit_plugin)
            .add_plugins(crafting_ui_plugin)
            .add_systems(Update, find_me)
            .insert_resource(HeartyEntityId(None))
            .add_systems(Update, (handle_hi, find_me))
            .insert_state(GameplayState::Main)
            .insert_resource(DebugPickingMode::Disabled)
            .insert_resource(TimeOffset::default());


@@ 145,15 146,30 @@ pub fn on_disconnected(trigger: On<Disconnected>, names: Query<&Name>) {
    }
}

pub fn find_me(
#[derive(Resource)]
struct HeartyEntityId(Option<Entity>);

pub fn handle_hi(
    mut msgs: MessageReader<Hi>,
    mut commands: Commands,
    mut res: ResMut<HeartyEntityId>,
    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);
        res.0 = Some(we_are);
        time_offset.0 = msg.time_offset;
    }*/
    }
}

pub fn find_me(
    entity_map: Res<ServerEntityMap>,
    mut res: ResMut<HeartyEntityId>,
    mut commands: Commands,
) {
    let Some(server_entity) = res.0 else { return; };
    let Some(local_entity) = entity_map.server_to_client.get(&server_entity) else { return; };
    let Ok(mut e) = commands.get_entity(*local_entity) else { return; };
    info!(?local_entity, ?server_entity, "joined successfully");
    e.insert(Me);
    res.0 = None;
}
\ No newline at end of file

M crates/unified/src/client/net.rs => crates/unified/src/client/net.rs +3 -1
@@ 1,10 1,12 @@
pub mod incoming_planets;
pub mod incoming_parts;

use crate::{prelude::*, shared::net::{ServerEntityMap}};
use crate::client::net::incoming_parts::handle_incoming_parts;
use crate::client::net::incoming_planets::handle_incoming_planets;

pub fn net_plugin(app: &mut App) {
    app
        .insert_resource(ServerEntityMap::default())
        .add_systems(Update, handle_incoming_planets);
        .add_systems(PreUpdate, (handle_incoming_planets, handle_incoming_parts));
}
\ No newline at end of file

A crates/unified/src/client/net/incoming_parts.rs => crates/unified/src/client/net/incoming_parts.rs +40 -0
@@ 0,0 1,40 @@
use bevy::log::{debug, warn};
use bevy::prelude::{Commands, MessageReader, ResMut, Transform};
use crate::prelude::Query;
use crate::shared::ecs::{Part, Temperature};
use crate::shared::net::part::PartUpdatePacket;
use crate::shared::net::ServerEntityMap;

pub fn handle_incoming_parts(
    mut msgs: MessageReader<PartUpdatePacket>,

    mut q_parts: Query<(&mut Part, &mut Transform, &mut Temperature)>,
    mut entity_map: ResMut<ServerEntityMap>,
    mut commands: Commands
) {
    for msg in msgs.read() {
        'to_next_part: for part in &msg.updated_parts {
            if let Some(local_entity) = entity_map.server_to_client.get(&part.server_entity) {
                let Ok((mut part_data, mut transform, mut temperature)) = q_parts.get_mut(*local_entity) else {
                    warn!("local part entity {:?} for part srv:{:?} doesn't exist? skipping update, this is a bug", local_entity, part.server_entity);
                    continue 'to_next_part
                };
                if part.part_data_changed {
                    *part_data = part.part.clone();
                }
                *transform = part.transform;
                *temperature = part.temperature;
            } else {
                // Spawn new part
                let e = commands.spawn((
                    part.part.clone(),
                    part.transform,
                    part.temperature,
                )).id();
                entity_map.server_to_client.insert(part.server_entity, e.clone());
                entity_map.client_to_server.insert(e.clone(), part.server_entity);
                debug!(?part.part, "spawned new part");
            }
        }
    }
}
\ No newline at end of file

M crates/unified/src/client/starguide/orbit.rs => crates/unified/src/client/starguide/orbit.rs +1 -1
@@ 27,7 27,7 @@ fn update_orbits(
    let mut p_mass = None;
    let mut p_transform = None;
    let mut p_velocity = None;
    let (sun_mass, _, sun_transform, sun_velocity) = planets.iter().find(|planet| planet.1.name == "Sun").unwrap();
    let Some((sun_mass, _, sun_transform, sun_velocity)) = planets.iter().find(|planet| planet.1.name == "Sun") else { return; }; // not loaded yet
    let mut closest = f32::INFINITY;
    for (mass, planet, transform, velocity) in planets {
        if planet.name == "Sun" { continue }

M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +6 -2
@@ 20,7 20,7 @@ use aeronet::io::server::Server;
use aeronet::io::Session;
//use aeronet_replicon::server::AeronetRepliconServer;
use aeronet_transport::lane::LaneKind;
use aeronet_transport::Transport;
use aeronet_transport::{Transport, TransportConfig};
use aeronet_websocket::server::{ServerConfig, WebSocketServer};
//use bevy_replicon::prelude::Replicated;
//use bevy_replicon::server::AuthorizedClient;


@@ 128,7 128,11 @@ fn on_connected(
        ConnectedNetworkEntity {
            game_entity: player,
        },
        transport
        transport,
        TransportConfig {
            max_memory_usage: 536_870_912, // 512 MiB,
            ..Default::default()
        }
    ));
}


M crates/unified/src/server/net.rs => crates/unified/src/server/net.rs +3 -1
@@ 1,8 1,10 @@
pub mod update_planets;
pub mod update_parts;

use crate::{prelude::*, shared::net::{SendTargets, ToClients}};
use crate::server::net::update_parts::send_updated_parts;
use crate::server::net::update_planets::send_updated_planets;

pub fn net_plugin(app: &mut App) {
    app.add_systems(Update, send_updated_planets);
    app.add_systems(PostUpdate, (send_updated_planets, send_updated_parts));
}
\ No newline at end of file

A crates/unified/src/server/net/update_parts.rs => crates/unified/src/server/net/update_parts.rs +75 -0
@@ 0,0 1,75 @@
use std::collections::BTreeSet;
use bevy::prelude::{Added, Changed, Commands, MessageWriter, Query};
use crate::prelude::{debug, Entity, ParamSet, Transform};
use crate::shared::ecs::{Part, Temperature};
use crate::shared::net::part::{PartUpdatePacket, PartDto};
use crate::shared::net::staged_transform::LastStagedTransform;
use crate::shared::net::{SendTargets, ToClients};

pub fn send_updated_parts(
    mut set: ParamSet<(
        // If you need to send more data, don't add it here...
        Query<(Entity, &Transform), Added<Part>>,
        Query<(Entity, &Transform, &LastStagedTransform), Changed<Transform>>,
        Query<Entity, Changed<Part>>,

        // add it here.
        Query<(&Part, &Transform, &Temperature, &mut LastStagedTransform)>,
    )>,

    mut update_packets_out: MessageWriter<ToClients<PartUpdatePacket>>,
    mut commands: Commands
) {
    let start = bevy::platform::time::Instant::now();
    let mut parts_to_send: BTreeSet<Entity> = BTreeSet::new();

    // Updating the part behavior triggers sprite loading on the client. We don't want to send it every time the Transform changes
    let mut parts_with_part_change: BTreeSet<Entity> = BTreeSet::new();

    for (new_part, transform) in set.p0().iter() {
        parts_to_send.insert(new_part); // Always send newly created parts...
        // ...and add LastStagedTransform...
        commands.entity(new_part).insert(LastStagedTransform(*transform));
        // and put a marker, we need to copy part data across
        parts_with_part_change.insert(new_part);
    }
    for (moved_part, current_transform, maybe_staged_transform) in set.p1().iter() {
        // have we had a significant transform change?
        if !maybe_staged_transform.should_update(current_transform) { continue; }
        // we have, resend
        parts_to_send.insert(moved_part);
    }
    for changed_part in set.p2().iter() {
        // if part changed, always resend
        parts_to_send.insert(changed_part);
        // and put a marker, we need to copy part data across
        parts_with_part_change.insert(changed_part);
    }

    // process all parts to send
    let mut packet = PartUpdatePacket {
        updated_parts: vec![]
    };

    for part_to_send in parts_to_send.iter() {
        let mut q = set.p3();
        let Ok(mut part_info) = q.get_mut(*part_to_send) else { continue };

        *part_info.3 = LastStagedTransform(*part_info.1);

        packet.updated_parts.push(PartDto {
            server_entity: *part_to_send,
            part: part_info.0.clone(),
            part_data_changed: parts_with_part_change.contains(part_to_send),
            transform: *part_info.1,
            temperature: *part_info.2
        });

    }

    update_packets_out.write(ToClients {
        message: packet,
        targets: SendTargets::All,
    });

}
\ No newline at end of file

M crates/unified/src/server/net/update_planets.rs => crates/unified/src/server/net/update_planets.rs +18 -11
@@ 1,17 1,21 @@
use std::collections::BTreeSet;
use bevy::prelude::{Added, Changed, Commands, MessageWriter, Query};
use crate::prelude::{Entity, Transform};
use crate::prelude::{Entity, ParamSet, Transform};
use crate::shared::config::planet::Planet;
use crate::shared::net::planet::{PlanetUpdatePacket, UpdatedPlanet};
use crate::shared::net::planet::{PlanetUpdatePacket, PlanetDto};
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>>,
    mut set: ParamSet<(
        // If you need to send more data, don't add it here...
        Query<(Entity, &Transform), Added<Planet>>,
        Query<(Entity, &Transform, &LastStagedTransform), Changed<Transform>>,
        Query<Entity, Changed<Planet>>,

    q_planets: Query<(&Planet, &Transform)>,
        // add it here.
        Query<(&Planet, &Transform, &mut LastStagedTransform)>,
    )>,

    mut update_packets_out: MessageWriter<ToClients<PlanetUpdatePacket>>,
    mut commands: Commands


@@ 21,20 25,20 @@ pub fn send_updated_planets(
    // 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() {
    for (new_planet, transform) in set.p0().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() {
    for (moved_planet, current_transform, maybe_staged_transform) in set.p1().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() {
    for changed_planet in set.p2().iter() {
        // if Planet changed, always resend
        planets_to_send.insert(changed_planet);
        // and put a marker, we need to copy planet data across


@@ 47,9 51,12 @@ pub fn send_updated_planets(
    };

    for planet_to_send in planets_to_send.iter() {
        let Ok(planet_info) = q_planets.get(*planet_to_send) else { continue };
        let mut p3 = set.p3();
        let Ok(mut planet_info) = p3.get_mut(*planet_to_send) else { continue };

        packet.updated_planets.push(UpdatedPlanet {
        *planet_info.2 = LastStagedTransform(*planet_info.1);

        packet.updated_planets.push(PlanetDto {
            server_entity: *planet_to_send,
            planet: planet_info.0.clone(),
            planet_data_changed: planets_with_planet_change.contains(planet_to_send),

M crates/unified/src/shared/ecs.rs => crates/unified/src/shared/ecs.rs +2 -2
@@ 22,7 22,7 @@ pub const ORBIT_LAYER: RenderLayers = RenderLayers::layer(2);

// corresponding planet name

#[derive(Component, Serialize, Deserialize, Debug)]
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
#[require(
    RigidBody::Dynamic,
    LinearVelocity,


@@ 73,7 73,7 @@ pub struct CraftPartRequest {
    pub inputs: HashMap<String, u32>,
}

#[derive(Component, Serialize, Deserialize, Debug)]
#[derive(Component, Serialize, Deserialize, Debug, Copy, Clone)]
pub struct Temperature(pub f64);
#[derive(Component, Serialize, Deserialize, Debug)]
pub struct Cooler {

M crates/unified/src/shared/net.rs => crates/unified/src/shared/net.rs +4 -1
@@ 1,5 1,6 @@
pub mod planet;
pub mod staged_transform;
pub mod part;

use std::any::TypeId;
use std::collections::HashMap;


@@ 23,13 24,15 @@ use crate::shared::attachment::{Joint, JointOf, PartInShip, Peer, Ship, SnapOf, 
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};
use crate::shared::net::part::PartUpdatePacket;
use crate::shared::net::planet::PlanetUpdatePacket;

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

        .add_server_message::<PlanetUpdatePacket>()
        .add_server_message::<PartUpdatePacket>()

        .add_client_message::<DragRequestEvent>()
        .add_client_message::<ToggleDrillEvent>()

A crates/unified/src/shared/net/part.rs => crates/unified/src/shared/net/part.rs +20 -0
@@ 0,0 1,20 @@
use bevy::prelude::{Entity, Message, Transform, TypePath};
use serde::{Deserialize, Serialize};
use crate::shared::config::planet::Planet;
use crate::shared::ecs::{Part, Temperature};

#[derive(Serialize, Deserialize, Message, TypePath, Debug, Clone)]
pub struct PartUpdatePacket {
    pub updated_parts: Vec<PartDto>
}

#[derive(Serialize, Deserialize, TypePath, Debug, Clone)]
pub struct PartDto {
    pub server_entity: Entity,

    pub part: Part,
    pub part_data_changed: bool,

    pub transform: Transform,
    pub temperature: Temperature,
}
\ No newline at end of file

M crates/unified/src/shared/net/planet.rs => crates/unified/src/shared/net/planet.rs +2 -2
@@ 4,11 4,11 @@ use crate::shared::config::planet::Planet;

#[derive(Serialize, Deserialize, Message, TypePath, Debug, Clone)]
pub struct PlanetUpdatePacket {
    pub updated_planets: Vec<UpdatedPlanet>
    pub updated_planets: Vec<PlanetDto>
}

#[derive(Serialize, Deserialize, TypePath, Debug, Clone)]
pub struct UpdatedPlanet {
pub struct PlanetDto {
    pub server_entity: Entity,

    pub planet: Planet,