~starkingdoms/starkingdoms

66ecf5a99f256b08f04b394146eb4a362306273c — core 5 months ago b694983
client start
13 files changed, 367 insertions(+), 354 deletions(-)

M Cargo.lock
D crates/proc/Cargo.toml
D crates/proc/src/lib.rs
M crates/unified/Cargo.toml
A crates/unified/src/client/mod.rs
A crates/unified/src/client_plugins.rs
A crates/unified/src/ecs.rs
M crates/unified/src/main.rs
D crates/unified/src/replication/message.rs
D crates/unified/src/replication/mod.rs
M crates/unified/src/server/mod.rs
M crates/unified/src/server_plugins.rs
M crates/unified/src/shared_plugins.rs
M Cargo.lock => Cargo.lock +224 -110
@@ 29,6 29,10 @@ name = "accesskit"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "becf0eb5215b6ecb0a739c31c21bd83c4f326524c9b46b7e882d77559b60a529"
dependencies = [
 "enumn",
 "serde",
]

[[package]]
name = "accesskit_consumer"


@@ 147,6 151,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"

[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
 "crypto-common",
 "generic-array",
]

[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 613,30 627,6 @@ dependencies = [
]

[[package]]
name = "bevy-trait-query"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0536fb7ec58a26201bd1a30b89267bb100ee0dcb12edc6c472b3bc38f45f1447"
dependencies = [
 "bevy-trait-query-impl",
 "bevy_app 0.16.1",
 "bevy_ecs 0.16.1",
 "tracing",
]

[[package]]
name = "bevy-trait-query-impl"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d62739c868c0dcbf469df5235b738be46510dd6fc8fc7f2240106ba6610d0754"
dependencies = [
 "proc-macro-crate",
 "proc-macro2",
 "quote",
 "syn 2.0.104",
]

[[package]]
name = "bevy_a11y"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 659,6 649,7 @@ dependencies = [
 "bevy_derive 0.16.1",
 "bevy_ecs 0.16.1",
 "bevy_reflect 0.16.1",
 "serde",
]

[[package]]


@@ 1309,6 1300,7 @@ dependencies = [
 "bevy_utils 0.16.1",
 "derive_more",
 "log",
 "serde",
 "smol_str",
 "thiserror 2.0.12",
]


@@ 1882,6 1874,48 @@ dependencies = [
]

[[package]]
name = "bevy_renet2"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d238ee38a3a9aa6dc2360fe27103ef93090d7107793da3ef1d6660d7b28e9e5a"
dependencies = [
 "bevy_app 0.16.1",
 "bevy_ecs 0.16.1",
 "bevy_time 0.16.1",
 "renet2",
 "renet2_netcode",
]

[[package]]
name = "bevy_replicon"
version = "0.34.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d4ce1947bc4f59da376589e1b019def816b49ecb9d23ecf0233211d137ac9fe"
dependencies = [
 "bevy 0.16.1",
 "bitflags 2.9.1",
 "bytes",
 "log",
 "petgraph 0.8.2",
 "postcard",
 "serde",
 "typeid",
 "variadics_please",
]

[[package]]
name = "bevy_replicon_renet2"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cbcacc0928f78601b7ce3f4c1d8b9314689d75fc93c9f7375a35af6bf16ba74"
dependencies = [
 "bevy 0.16.1",
 "bevy_renet2",
 "bevy_replicon",
 "log",
]

[[package]]
name = "bevy_scene"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2205,6 2239,7 @@ dependencies = [
 "bytemuck",
 "derive_more",
 "nonmax",
 "serde",
 "smallvec",
 "taffy 0.7.7",
 "thiserror 2.0.12",


@@ 2368,6 2403,7 @@ dependencies = [
 "cfg-if",
 "crossbeam-channel",
 "raw-window-handle",
 "serde",
 "tracing",
 "wasm-bindgen",
 "web-sys",


@@ 2628,6 2664,9 @@ name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
dependencies = [
 "portable-atomic",
]

[[package]]
name = "bzip2"


@@ 2790,6 2829,30 @@ dependencies = [
]

[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
 "cfg-if",
 "cipher",
 "cpufeatures",
]

[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
 "aead",
 "chacha20",
 "cipher",
 "poly1305",
 "zeroize",
]

[[package]]
name = "chrono"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2844,6 2907,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
 "crypto-common",
 "inout",
 "zeroize",
]

[[package]]


@@ 2898,6 2962,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"

[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
 "thiserror 2.0.12",
]

[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3325,6 3398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
 "generic-array",
 "rand_core 0.6.4",
 "typenum",
]



@@ 3694,6 3768,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"

[[package]]
name = "enumn"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.104",
]

[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4607,6 4692,12 @@ dependencies = [
]

[[package]]
name = "hmac-sha256"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425"

[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4940,15 5031,6 @@ dependencies = [
]

[[package]]
name = "inventory"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83"
dependencies = [
 "rustversion",
]

[[package]]
name = "io-kit-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6168,6 6250,12 @@ dependencies = [
]

[[package]]
name = "octets"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109983a091271ee8916076731ba5fdc9ee22fea871bc7c6ceab9bfd423eb1d99"

[[package]]
name = "offset-allocator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6205,6 6293,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"

[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"

[[package]]
name = "orbclient"
version = "0.3.48"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6351,6 6445,18 @@ dependencies = [
]

[[package]]
name = "petgraph"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca"
dependencies = [
 "fixedbitset 0.5.7",
 "hashbrown 0.15.4",
 "indexmap",
 "serde",
]

[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6493,6 6599,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"

[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
 "cpufeatures",
 "opaque-debug",
 "universal-hash",
]

[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6508,6 6625,28 @@ dependencies = [
]

[[package]]
name = "postcard"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c1de96e20f51df24ca73cafcc4690e044854d803259db27a00a461cb3b9d17a"
dependencies = [
 "cobs",
 "postcard-derive",
 "serde",
]

[[package]]
name = "postcard-derive"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f049d94cb6dda6938cc8a531d2898e7c08d71c6de63d8e67123cca6cdde2cc"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.104",
]

[[package]]
name = "potential_utf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6927,6 7066,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"

[[package]]
name = "renet2"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6024b28b4d37cf4d4d0e0dc31d3a4ebb61880da278d77547bb94055a2a164f5"
dependencies = [
 "bevy_ecs 0.16.1",
 "bytes",
 "log",
 "octets",
]

[[package]]
name = "renet2_netcode"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee5d017932669cb57862c8c24ad5294d4fff877ace85487bc771b36ab958a054"
dependencies = [
 "bevy_ecs 0.16.1",
 "bytes",
 "hmac-sha256",
 "log",
 "octets",
 "renet2",
 "renetcode2",
 "serde",
 "url",
]

[[package]]
name = "renetcode2"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a232e1196d6bef6f57e02fbe83fadd88ceb894bfff369cbbe27044559d06d89a"
dependencies = [
 "chacha20poly1305",
 "log",
]

[[package]]
name = "resvg"
version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 7445,16 7623,6 @@ dependencies = [
]

[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
 "libc",
 "windows-sys 0.52.0",
]

[[package]]
name = "socks"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 7512,17 7680,12 @@ name = "starkingdoms"
version = "0.1.0"
dependencies = [
 "bevy 0.16.1",
 "bevy-trait-query",
 "bevy_replicon",
 "bevy_replicon_renet2",
 "clap",
 "crossbeam-channel",
 "inventory",
 "serde",
 "serde_json",
 "starkingdoms-proc",
 "tokio",
 "tokio-tungstenite",
 "tracing-subscriber",
 "typetag",
]

[[package]]


@@ 7575,15 7738,6 @@ dependencies = [
]

[[package]]
name = "starkingdoms-proc"
version = "0.1.0"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.104",
]

[[package]]
name = "starkingdoms-server"
version = "0.1.0-alpha1"
dependencies = [


@@ 8017,33 8171,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"

[[package]]
name = "tokio"
version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
dependencies = [
 "backtrace",
 "bytes",
 "libc",
 "mio",
 "pin-project-lite",
 "socket2",
 "windows-sys 0.52.0",
]

[[package]]
name = "tokio-tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
dependencies = [
 "futures-util",
 "log",
 "tokio",
 "tungstenite 0.26.2",
]

[[package]]
name = "toml"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 8349,30 8476,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"

[[package]]
name = "typetag"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f22b40dd7bfe8c14230cf9702081366421890435b2d625fa92b4acc4c3de6f"
dependencies = [
 "erased-serde",
 "inventory",
 "once_cell",
 "serde",
 "typetag-impl",
]

[[package]]
name = "typetag-impl"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.104",
]

[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 8457,6 8560,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"

[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
 "crypto-common",
 "subtle",
]

[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 8490,6 8603,7 @@ dependencies = [
 "form_urlencoded",
 "idna",
 "percent-encoding",
 "serde",
]

[[package]]

D crates/proc/Cargo.toml => crates/proc/Cargo.toml +0 -12
@@ 1,12 0,0 @@
[package]
name = "starkingdoms-proc"
version = "0.1.0"
edition = "2024"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["full"] }
\ No newline at end of file

D crates/proc/src/lib.rs => crates/proc/src/lib.rs +0 -23
@@ 1,23 0,0 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_attribute]
pub fn replicable(_metadata: proc_macro::TokenStream, input: proc_macro::TokenStream)
                  -> proc_macro::TokenStream {
    let input: TokenStream = input.into();
    let output = quote! {
        #[derive(::serde::Serialize, ::serde::Deserialize, ::starkingdoms_proc::_Replicable)]
        #input
    };
    output.into()
}
#[proc_macro_derive(_Replicable)]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let DeriveInput { ident, .. } = parse_macro_input!(input);
    let output = quote! {
        #[typetag::serde]
        impl crate::replication::Replicable for #ident {}
    };
    output.into()
}
\ No newline at end of file

M crates/unified/Cargo.toml => crates/unified/Cargo.toml +4 -15
@@ 5,25 5,14 @@ edition = "2024"
version = "0.1.0"

[dependencies]
bevy = "0.16"
bevy-trait-query = "0.16"
bevy = { version = "0.16", features = ["serialize"] }

tokio = { version = "1", optional = true, features = ["rt-multi-thread"] }
tokio-tungstenite = { version = "0.26", optional = true }

serde = { version = "1", features = ["derive"] }
serde_json = "1"
typetag = "0.2"
starkingdoms-proc = { path = "../proc" }
inventory = "0.3"
bevy_replicon = "0.34"
bevy_replicon_renet2 = { version = "0.10", features = ["native_transport"] }

clap = { version = "4", features = ["derive", "cargo"] }

tracing-subscriber = "0.3"

crossbeam-channel = "0.5"

[features]
default = []
server = ["dep:tokio", "dep:tokio-tungstenite"]
client = []
\ No newline at end of file
serde = { version = "1", features = ["derive"] }
\ No newline at end of file

A crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +42 -0
@@ 0,0 1,42 @@
use std::net::{SocketAddr, UdpSocket};
use std::time::SystemTime;
use bevy::prelude::*;
use bevy_replicon::prelude::RepliconChannels;
use bevy_replicon_renet2::netcode::{ClientAuthentication, NetcodeClientTransport, NativeSocket};
use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetClient};
use bevy_replicon_renet2::RenetChannelsExt;



pub struct ClientPlugin {
    pub server: SocketAddr
}
impl Plugin for ClientPlugin {
    fn build(&self, app: &mut App) {
        let server = self.server.clone();
        app
            .add_systems(Startup, move |mut commands: Commands, channels: Res<RepliconChannels>| {
                let client = RenetClient::new(
                    ConnectionConfig::from_channels(channels.server_configs(), channels.client_configs()),
                    false
                );
                let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
                let client_id = current_time.as_millis() as u64;
                let socket = UdpSocket::bind(SocketAddr::new("::1".parse().unwrap(), 0)).unwrap();
                let authentication = ClientAuthentication::Unsecure {
                    client_id,
                    protocol_id: 0,
                    socket_id: 0,
                    server_addr: server,
                    user_data: None
                };
                let transport = NetcodeClientTransport::new(current_time, authentication, NativeSocket::new(socket).unwrap()).unwrap();

                commands.insert_resource(client);
                commands.insert_resource(transport);

                info!(?client_id, "connected!");
            });

    }
}
\ No newline at end of file

A crates/unified/src/client_plugins.rs => crates/unified/src/client_plugins.rs +21 -0
@@ 0,0 1,21 @@
use std::net::SocketAddr;
use bevy::app::{PluginGroup, PluginGroupBuilder};
use bevy::DefaultPlugins;
use bevy_replicon::RepliconPlugins;
use bevy_replicon_renet2::RepliconRenetClientPlugin;
use crate::client::ClientPlugin;

pub struct ClientPluginGroup {
    pub server: SocketAddr
}
impl PluginGroup for ClientPluginGroup {
    fn build(self) -> PluginGroupBuilder {
        PluginGroupBuilder::start::<Self>()
            .add_group(DefaultPlugins)
            .add_group(
                RepliconPlugins
            )
            .add(RepliconRenetClientPlugin)
            .add(ClientPlugin { server: self.server })
    }
}
\ No newline at end of file

A crates/unified/src/ecs.rs => crates/unified/src/ecs.rs +0 -0
M crates/unified/src/main.rs => crates/unified/src/main.rs +21 -9
@@ 1,13 1,16 @@
pub mod replication;
pub mod server_plugins;
mod shared_plugins;
mod server;
pub mod shared_plugins;
pub mod server;
pub mod client;
pub mod ecs;
mod client_plugins;

use std::net::SocketAddr;
use std::process::exit;
use bevy::log::tracing_subscriber;
use clap::Parser;
use bevy::prelude::*;
use crate::client_plugins::ClientPluginGroup;
use crate::server_plugins::ServerPluginGroup;
use crate::shared_plugins::SharedPluginGroup;



@@ 19,10 22,12 @@ enum Cli {
        server: SocketAddr
    },
    Server {
        #[arg(short, long)]
        #[arg(short = 'b', long)]
        bind: SocketAddr,
        #[arg(short, long)]
        tick_rate: f64
        #[arg(short = 'r', long)]
        tick_rate: f64,
        #[arg(short = 'C', long)]
        max_clients: usize
    }
}



@@ 36,9 41,15 @@ fn main() -> AppExit {

    let mut app = App::new();



    match cli {
        Cli::Client { server } => { todo!() },
        Cli::Server { bind, tick_rate } => {
        Cli::Client { server } => {
            app.add_plugins(ClientPluginGroup {
                server
            });
        },
        Cli::Server { bind, tick_rate, max_clients } => {
            if cfg!(target_family = "wasm") {
                eprintln!("the server cannot run on webassembly");
                exit(1);


@@ 46,7 57,8 @@ fn main() -> AppExit {

            app.add_plugins(ServerPluginGroup {
                bind,
                tick_rate
                tick_rate,
                max_clients
            });
        }
    }

D crates/unified/src/replication/message.rs => crates/unified/src/replication/message.rs +0 -10
@@ 1,10 0,0 @@
use serde::{Deserialize, Serialize};
use crate::replication::ReplicatedEntityID;

#[derive(Serialize, Deserialize, Debug)]
pub enum ReplicationMessage {
    AddEntity(ReplicatedEntityID),
    UpdateComponent(ReplicatedEntityID, serde_json::Value),
    RemoveComponent(ReplicatedEntityID, String),
    RemoveEntity(ReplicatedEntityID)
}
\ No newline at end of file

D crates/unified/src/replication/mod.rs => crates/unified/src/replication/mod.rs +0 -132
@@ 1,132 0,0 @@
mod message;

use std::collections::{HashMap, HashSet};
use std::sync::atomic::AtomicU64;
use bevy::ecs::component::HookContext;
use bevy::ecs::world::DeferredWorld;
use bevy::prelude::Component;
use starkingdoms_proc::replicable;
use bevy::prelude::*;
use bevy::tasks::AsyncComputeTaskPool;
use bevy_trait_query::{All, RegisterExt};
use serde::{Deserialize, Serialize};
use crate::replication::message::ReplicationMessage;

pub struct ReplicationServerPlugin;
impl Plugin for ReplicationServerPlugin {
    fn build(&self, app: &mut App) {
        let (tx_to_io_task, rx_from_io_task) = crossbeam_channel::unbounded();
        let res = ReplicationServerResource { message_sender: tx_to_io_task, component_tracking_map: HashMap::new() };

        let task_pool = AsyncComputeTaskPool::get();
        task_pool.spawn(async move {
            while let Ok(message) = rx_from_io_task.recv() {
                debug!(?message, "broadcasting to clients");
            }
        }).detach();

        app
            .insert_resource(res)
            .replicate::<Replicated>()
            .add_systems(Update, replicate);

    }
}

#[derive(Resource)]
struct ReplicationServerResource {
    message_sender: crossbeam_channel::Sender<message::ReplicationMessage>,
    component_tracking_map: HashMap<Entity, HashSet<String>>,
}

/// Implement this trait on a component to enable replication for that component.
#[bevy_trait_query::queryable]
#[typetag::serde(tag = "component")]
pub trait Replicable {
    fn component_id(&self) -> &'static str {
        std::any::type_name::<Self>()
    }
}

/// Add this component to an entity to replicate it, and all of it's `Replicatable` components to clients
#[derive(Component)]
#[replicable]
#[require(ReplicatedEntityID)]
#[component(on_add = replicated_add_hook)]
#[component(on_remove = replicated_remove_hook)]
pub struct Replicated;

fn replicated_add_hook(mut world: DeferredWorld, context: HookContext) {
    world.get_resource_mut::<ReplicationServerResource>().unwrap().component_tracking_map.insert(context.entity, HashSet::new());

    let replicated_id = world.get::<ReplicatedEntityID>(context.entity).unwrap();
    world.get_resource::<ReplicationServerResource>().unwrap().message_sender.send(ReplicationMessage::AddEntity(*replicated_id)).unwrap();
}
fn replicated_remove_hook(mut world: DeferredWorld, context: HookContext) {
    world.get_resource_mut::<ReplicationServerResource>().unwrap().component_tracking_map.remove(&context.entity);

    let replicated_id = world.get::<ReplicatedEntityID>(context.entity).unwrap();
    world.get_resource::<ReplicationServerResource>().unwrap().message_sender.send(ReplicationMessage::RemoveEntity(*replicated_id)).unwrap();
}

static ID_COUNTER: AtomicU64 = AtomicU64::new(0);

#[derive(Component, Debug, Serialize, Deserialize, Copy, Clone)]
struct ReplicatedEntityID(u64);
impl Default for ReplicatedEntityID {
    fn default() -> Self {
        Self(ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
    }
}


/// The main server-side replication system. Tracks all changes and produce replication messages
fn replicate(
    replicated_query: Query<(Entity, &ReplicatedEntityID, All<&dyn Replicable>), With<Replicated>>,
    mut replication_server_resource: ResMut<ReplicationServerResource>
) {
    for (server_entity, replicated_entity, replicated_components) in &replicated_query {
        for added_component in replicated_components.iter_added() {
            let our_entry = replication_server_resource.component_tracking_map.get_mut(&server_entity).unwrap();
            our_entry.insert(added_component.component_id().to_string());
        }

        for updated_component in replicated_components.iter_changed() {
            let serialized_update = serde_json::to_value(updated_component.into_inner()).unwrap();
            replication_server_resource.message_sender.send(ReplicationMessage::UpdateComponent(
                *replicated_entity,
                serialized_update
            )).unwrap();
        }
        
        let mut existing_component_ids = vec![];
        for component in replicated_components.iter() {
            existing_component_ids.push(component.component_id());
        }
        let our_entry = replication_server_resource.component_tracking_map.get_mut(&server_entity).unwrap();
        let mut removed_component_ids = vec![];
        our_entry.retain(|u| {
            if !existing_component_ids.contains(&&**u) {
                removed_component_ids.push(u.clone());
                false
            } else { true }
        });
        
        for removed_component in &removed_component_ids {
            replication_server_resource.message_sender.send(ReplicationMessage::RemoveComponent(
                *replicated_entity,
                removed_component.to_string()
            )).unwrap();
        }
    }
}

pub trait ReplicateExt {
    fn replicate<T: Replicable + Component>(&mut self) -> &mut Self;
}
impl ReplicateExt for App {
    fn replicate<T: Replicable + Component>(&mut self) -> &mut Self {
        self.register_component_as::<dyn Replicable, T>();
        self
    }
}
\ No newline at end of file

M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +35 -36
@@ 1,44 1,43 @@
use std::time::Duration;
use std::net::{SocketAddr, UdpSocket};
use std::time::{SystemTime, UNIX_EPOCH};
use bevy::prelude::*;
use starkingdoms_proc::replicable;
use crate::replication::{ReplicateExt, Replicated, ReplicationServerPlugin};
use bevy_replicon::prelude::RepliconChannels;
use bevy_replicon_renet2::netcode::{NativeSocket, NetcodeServerTransport, ServerAuthentication, ServerSetupConfig};
use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetServer};
use bevy_replicon_renet2::RenetChannelsExt;

pub fn server_plugin(app: &mut App) {
    app
        .replicate::<TestComponent>()
        .add_systems(Startup, spawn_test_thingy)
        .add_systems(FixedUpdate, update_test_thingy)
        .add_systems(Update, remove_test_thingy);
pub struct ServerPlugin {
    pub bind: SocketAddr,
    pub max_clients: usize,
}
impl Plugin for ServerPlugin {
    fn build(&self, app: &mut App) {
        let bind = self.bind.clone();
        let max_clients = self.max_clients.clone();

#[derive(Component)]
struct TestComponent2;
        app
            .add_systems(FixedPreUpdate, bevy_replicon::server::increment_tick) // !!important!! do not remove or move
            .add_systems(Startup, move |mut commands: Commands, channels: Res<RepliconChannels>| {
                let server = RenetServer::new(ConnectionConfig::from_channels(
                    channels.server_configs(),
                    channels.client_configs()
                ));

#[derive(Component)]
#[replicable]
pub struct TestComponent {
    hi: i32
}
                let server_config = ServerSetupConfig {
                    current_time: SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
                    max_clients: max_clients,
                    protocol_id: 0,
                    authentication: ServerAuthentication::Unsecure,
                    socket_addresses: vec![vec![bind]]
                };
                let socket = UdpSocket::bind(bind).unwrap();

fn spawn_test_thingy(mut commands: Commands) {
    commands.spawn((
        Replicated,
        TestComponent2,
        TestComponent { hi: 123 },
    ));
}
fn update_test_thingy(mut query: Query<&mut TestComponent>) {
    for mut thingy in query.iter_mut() {
        thingy.hi += 1;
    }
}
fn remove_test_thingy(mut commands: Commands, query: Query<Entity, With<TestComponent2>>, time: Res<Time>) {
    if time.elapsed() < Duration::from_secs(10) { return; }
    for entity in query.iter() {
        commands.entity(entity).remove::<TestComponent>();
    }
    if time.elapsed() < Duration::from_secs(20) { return; }
    for entity in query.iter() {
        commands.entity(entity).despawn();
                let transport = NetcodeServerTransport::new(server_config, NativeSocket::new(socket).unwrap()).unwrap();

                commands.insert_resource(server);
                commands.insert_resource(transport);

                info!("websocket server listening");
            });
    }
}
\ No newline at end of file

M crates/unified/src/server_plugins.rs => crates/unified/src/server_plugins.rs +14 -6
@@ 2,14 2,14 @@ use std::net::SocketAddr;
use std::time::Duration;
use bevy::app::{PluginGroup, PluginGroupBuilder, ScheduleRunnerPlugin, TaskPoolPlugin};
use bevy::diagnostic::FrameCountPlugin;
use bevy::log::LogPlugin;
use bevy::time::TimePlugin;
use crate::replication::ReplicationServerPlugin;
use crate::server::server_plugin;
use bevy_replicon::RepliconPlugins;
use bevy_replicon_renet2::RepliconRenetServerPlugin;

pub struct ServerPluginGroup {
    pub bind: SocketAddr,
    pub tick_rate: f64
    pub tick_rate: f64,
    pub max_clients: usize
}
impl PluginGroup for ServerPluginGroup {
    fn build(self) -> PluginGroupBuilder {


@@ 20,7 20,15 @@ impl PluginGroup for ServerPluginGroup {
            .add(ScheduleRunnerPlugin::run_loop(
                Duration::from_secs_f64(1.0 / self.tick_rate)
            ))
            .add(ReplicationServerPlugin)
            .add(server_plugin)
            .add_group(
                RepliconPlugins
            )
            .add(
                RepliconRenetServerPlugin
            )
            .add(crate::server::ServerPlugin {
                bind: self.bind,
                max_clients: self.max_clients,
            })
    }
}
\ No newline at end of file

M crates/unified/src/shared_plugins.rs => crates/unified/src/shared_plugins.rs +6 -1
@@ 1,9 1,14 @@
use bevy::app::{PluginGroup, PluginGroupBuilder};
use bevy::app::{App, PluginGroup, PluginGroupBuilder};

pub struct SharedPluginGroup;

impl PluginGroup for SharedPluginGroup {
    fn build(self) -> PluginGroupBuilder {
        PluginGroupBuilder::start::<Self>()
            .add(register_everything)
    }
}

pub fn register_everything(app: &mut App) {
    app;
}
\ No newline at end of file