diff --git a/.vscode/settings.json b/.vscode/settings.json index 4fb2774..dad53cc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,9 @@ { "editor.tabSize": 2, "rust-analyzer.diagnostics.disabled": [ - "unresolved-method" //rust-analyzer issue #14269 + //rust-analyzer issue #14269, + "unresolved-method", + "unresolved-import", + "unresolved-field" ] } diff --git a/Cargo.lock b/Cargo.lock index 8245711..8a4cca4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "atomic-polyfill" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -87,25 +96,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "bincode" -version = "2.0.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb50c5a2ef4b9b1e7ae73e3a73b52ea24b20312d629f9c4df28260b7ad2c3c4" -dependencies = [ - "bincode_derive", - "serde", -] - -[[package]] -name = "bincode_derive" -version = "2.0.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a45a23389446d2dd25dc8e73a7a3b3c43522b630cac068927f0649d43d719d2" -dependencies = [ - "virtue", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -203,6 +193,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "cocoa" version = "0.24.1" @@ -302,6 +298,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" + [[package]] name = "crossbeam-channel" version = "0.5.7" @@ -753,6 +755,15 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -771,6 +782,20 @@ dependencies = [ "ahash 0.8.3", ] +[[package]] +name = "heapless" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -906,12 +931,13 @@ dependencies = [ "image", "kubi-logging", "kubi-shared", - "kubi-udp", "log", "nohash-hasher", + "postcard", "rayon", "shipyard", "strum", + "uflow", "winapi", ] @@ -933,13 +959,15 @@ dependencies = [ "hashbrown 0.13.2", "kubi-logging", "kubi-shared", - "kubi-udp", "log", "nohash-hasher", + "postcard", + "rand", "rayon", "serde", "shipyard", "toml", + "uflow", ] [[package]] @@ -947,27 +975,16 @@ name = "kubi-shared" version = "0.1.0" dependencies = [ "anyhow", - "bincode", "bracket-noise", "glam", + "postcard", "rand", "rand_xoshiro", + "serde", "shipyard", "strum", ] -[[package]] -name = "kubi-udp" -version = "0.1.0" -dependencies = [ - "anyhow", - "bincode", - "hashbrown 0.13.2", - "kubi-logging", - "log", - "nohash-hasher", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -1388,6 +1405,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "postcard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa512cd0d087cc9f99ad30a1bf64795b67871edbead083ffc3a4dfafa59aa00" +dependencies = [ + "cobs", + "heapless", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1543,6 +1571,15 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.36.9" @@ -1607,6 +1644,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + [[package]] name = "serde" version = "1.0.152" @@ -1731,6 +1774,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.10.0" @@ -1864,6 +1913,15 @@ dependencies = [ "winnow", ] +[[package]] +name = "uflow" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be4d71c1c106a57b0333ac2c28bd4521e0b16a2b98fe84405cdf7f544be46b6" +dependencies = [ + "rand", +] + [[package]] name = "unicode-ident" version = "1.0.8" @@ -1888,12 +1946,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "virtue" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b60dcd6a64dd45abf9bd426970c9843726da7fc08f44cd6fcebf68c21220a63" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index ae1d7de..cfbd2e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["kubi", "kubi-server", "kubi-shared", "kubi-udp", "kubi-logging"] +members = ["kubi", "kubi-server", "kubi-shared", "kubi-logging"] resolver = "2" [profile.release-with-debug] diff --git a/kubi/README.md b/README.md similarity index 100% rename from kubi/README.md rename to README.md diff --git a/kubi-server/Cargo.toml b/kubi-server/Cargo.toml index f3b9d0b..6fb5e3d 100644 --- a/kubi-server/Cargo.toml +++ b/kubi-server/Cargo.toml @@ -6,11 +6,10 @@ publish = false [dependencies] kubi-shared = { path = "../kubi-shared" } -kubi-udp = { path = "../kubi-udp" } kubi-logging = { path = "../kubi-logging" } log = "*" -shipyard = { git = "https://github.com/leudz/shipyard", rev = "eb189f66" } -serde = "1.0" +shipyard = { git = "https://github.com/leudz/shipyard", rev = "eb189f66", features = ["thread_local"] } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } toml = "0.7" glam = { version = "0.23", features = ["debug-glam-assert", "fast-math"] } hashbrown = "0.13" @@ -18,7 +17,10 @@ nohash-hasher = "0.2.0" anyhow = "1.0" rayon = "1.6" flume = "0.10" +rand = "0.8" +uflow = "0.7" +postcard = { version = "1.0", features = ["alloc"] } [features] default = [] -unstable = ["glam/core-simd"] +nightly = ["rand/nightly", "rand/simd_support", "serde/unstable", "glam/core-simd", "kubi-shared/nightly"] diff --git a/kubi-server/src/auth.rs b/kubi-server/src/auth.rs index 7ecaba0..7d6dfae 100644 --- a/kubi-server/src/auth.rs +++ b/kubi-server/src/auth.rs @@ -1,49 +1,86 @@ -use shipyard::{UniqueView, UniqueViewMut}; -use kubi_shared::networking::messages::{ClientToServerMessage, ServerToClientMessage, InitData}; -use kubi_udp::server::ServerEvent; -use crate::{server::{ServerEvents, UdpServer}, config::ConfigTable, util::log_error}; +use shipyard::{UniqueView, NonSendSync}; +use uflow::{server::Event as ServerEvent, SendMode}; +use kubi_shared::networking::messages::{ + ClientToServerMessage, + ServerToClientMessage, + InitData, + C_CLIENT_HELLO +}; +use crate::{ + server::{ServerEvents, UdpServer, IsMessageOfType}, + config::ConfigTable +}; pub fn authenticate_players( - mut server: UniqueViewMut, + server: NonSendSync>, events: UniqueView, config: UniqueView ) { for event in &events.0 { - if let ServerEvent::MessageReceived { - from, - message: ClientToServerMessage::ClientHello { - username, - password - } - } = event { - log::info!("ClientHello from {} with username {} and password {:?}", from, username, password); + // if let ServerEvent::MessageReceived { + // from, + // message: ClientToServerMessage::ClientHello { + // username, + // password + // } + // } = event { + + let ServerEvent::Receive(client_addr, data) = event else{ + continue + }; + let Some(client) = server.0.client(client_addr) else { + log::error!("Client doesn't exist"); + continue + }; + if !event.is_message_of_type::() { + continue + } + let Ok(parsed_message) = postcard::from_bytes(data) else { + log::error!("Malformed message"); + continue + }; + let ClientToServerMessage::ClientHello { username, password } = parsed_message else { + unreachable!() + }; - // Handle password auth - if let Some(server_password) = &config.server.password { - if let Some(user_password) = &password { - if server_password != user_password { - server.0.send_message(*from, ServerToClientMessage::ServerFuckOff { - reason: "Passwords don't match".into() - }).map_err(log_error).ok(); - continue - } - } else { - server.0.send_message(*from, ServerToClientMessage::ServerFuckOff { - reason: "This server is password-protected".into() - }).map_err(log_error).ok(); + log::info!("ClientHello; username={} password={:?}", username, password); + + // Handle password auth + if let Some(server_password) = &config.server.password { + if let Some(user_password) = &password { + if server_password != user_password { + let res = postcard::to_allocvec(&ServerToClientMessage::ServerFuckOff { + reason: "Passwords don't match".into() + }).unwrap().into_boxed_slice(); + client.borrow_mut().send( + res, 0, SendMode::Reliable + ); continue } + } else { + let res = postcard::to_allocvec(&ServerToClientMessage::ServerFuckOff { + reason: "This server is password protected".into() + }).unwrap().into_boxed_slice(); + client.borrow_mut().send( + res, 0, SendMode::Reliable + ); + continue } - - //Spawn the user - //TODO Spawn the user on server side - - //Approve the user - server.0.send_message(*from, ServerToClientMessage::ServerHello { - init: InitData { - users: vec![] //TODO create init data - } - }).map_err(log_error).ok(); } + + //Spawn the user + //TODO Spawn the user on server side + + //Approve the user + let res = postcard::to_allocvec(&ServerToClientMessage::ServerHello { + init: InitData { + users: vec![] //TODO create init data + } + }).unwrap().into_boxed_slice(); + client.borrow_mut().send( + res, 0, SendMode::Reliable + ); + + log::info!("{username} joined the game!") } } diff --git a/kubi-server/src/client.rs b/kubi-server/src/client.rs index 23587b7..086e398 100644 --- a/kubi-server/src/client.rs +++ b/kubi-server/src/client.rs @@ -1,12 +1,12 @@ use shipyard::{Component, EntityId}; use hashbrown::HashMap; use nohash_hasher::BuildNoHashHasher; -use kubi_udp::{ClientId, ClientIdRepr}; +use kubi_shared::networking::client::ClientId; #[derive(Component)] pub struct Client(ClientId); -pub struct ClientMap(HashMap>); +pub struct ClientMap(HashMap>); impl ClientMap { pub fn new() -> Self { Self(HashMap::with_hasher(BuildNoHashHasher::default())) diff --git a/kubi-server/src/main.rs b/kubi-server/src/main.rs index fc24b57..ad24e18 100644 --- a/kubi-server/src/main.rs +++ b/kubi-server/src/main.rs @@ -5,29 +5,31 @@ pub(crate) mod util; pub(crate) mod config; pub(crate) mod server; pub(crate) mod client; -pub(crate) mod world; +//pub(crate) mod world; pub(crate) mod auth; use config::read_config; -use server::{bind_server, update_server, update_server_events}; +use server::{bind_server, update_server, log_server_errors}; use auth::authenticate_players; -use world::{update_world, init_world}; +//use world::{update_world, init_world}; fn initialize() -> Workload { ( read_config, bind_server, - init_world, + //init_world, ).into_workload() } fn update() -> Workload { ( update_server, - update_server_events, - authenticate_players, - update_world, - ).into_workload() + ( + log_server_errors, + authenticate_players, + //update_world, + ).into_workload() + ).into_sequential_workload() } fn main() { diff --git a/kubi-server/src/server.rs b/kubi-server/src/server.rs index c9bde00..8b84264 100644 --- a/kubi-server/src/server.rs +++ b/kubi-server/src/server.rs @@ -1,47 +1,62 @@ -use shipyard::{AllStoragesView, Unique, UniqueView, UniqueViewMut}; -use kubi_udp::server::{Server, ServerConfig, ServerEvent}; -use kubi_shared::networking::messages::{ClientToServerMessage, ServerToClientMessage}; -use std::time::Duration; +use shipyard::{AllStoragesView, Unique, UniqueView, UniqueViewMut, NonSendSync}; +use uflow::{server::{Server, Event as ServerEvent, Config as ServerConfig}, EndpointConfig}; use crate::config::ConfigTable; #[derive(Unique)] #[repr(transparent)] -pub struct UdpServer(pub Server); +pub struct UdpServer(pub Server); #[derive(Unique, Default)] -pub struct ServerEvents(pub Vec>); +pub struct ServerEvents(pub Vec); + +pub trait IsMessageOfType { + ///Checks if postcard-encoded message has a type + fn is_message_of_type(&self) -> bool; +} +impl IsMessageOfType for ServerEvent { + fn is_message_of_type(&self) -> bool { + let ServerEvent::Receive(_, data) = &self else { return false }; + if data.len() == 0 { return false } + data[0] == T + } +} pub fn bind_server( storages: AllStoragesView, ) { log::info!("Creating server..."); let config = storages.borrow::>().unwrap(); - let server: Server = Server::bind( + let server = Server::bind( config.server.address, - ServerConfig { - max_clients: config.server.max_clients, - client_timeout: Duration::from_millis(config.server.timeout_ms), + ServerConfig { + max_total_connections: config.server.max_clients, + max_active_connections: config.server.max_clients, + enable_handshake_errors: true, + endpoint_config: EndpointConfig { + active_timeout_ms: config.server.timeout_ms, + ..Default::default() + }, ..Default::default() } - ).unwrap(); - storages.add_unique(UdpServer(server)); + ).expect("Failed to create the server"); + storages.add_unique_non_send_sync(UdpServer(server)); storages.add_unique(ServerEvents::default()); } pub fn update_server( - mut server: UniqueViewMut -) { - if let Err(error) = server.0.update() { - log::error!("Server error: {error:?}") - } -} - -pub fn update_server_events( - mut server: UniqueViewMut, + mut server: NonSendSync>, mut events: UniqueViewMut, ) { - //drop current events events.0.clear(); - //fetch new ones - events.0.extend(server.0.process_events().rev()); + events.0.extend(server.0.step()); +} + +pub fn log_server_errors( + events: UniqueView, +) { + for event in &events.0 { + if let ServerEvent::Error(addr, error) = event { + log::error!("Server error addr: {addr} error: {error:?}"); + } + } } diff --git a/kubi-server/src/world.rs b/kubi-server/src/world.rs index 36f7070..4124e7b 100644 --- a/kubi-server/src/world.rs +++ b/kubi-server/src/world.rs @@ -2,7 +2,6 @@ use shipyard::{Unique, UniqueView, UniqueViewMut, Workload, IntoWorkload, AllSto use glam::IVec3; use hashbrown::HashMap; use kubi_shared::networking::messages::{ClientToServerMessage, ServerToClientMessage}; -use kubi_udp::server::ServerEvent; use crate::{ server::{UdpServer, ServerEvents}, config::ConfigTable, @@ -87,7 +86,7 @@ fn process_finished_tasks( server.0.send_message(subscriber, ServerToClientMessage::ChunkResponse { chunk: chunk_position.to_array(), data: blocks.clone(), - queued: queue.iter().map(|item| (item.position.to_array(), item.block_type)).collect() + queued: queue }).map_err(log_error).ok(); } log::debug!("Chunk {chunk_position} loaded, {} subs", chunk.subscriptions.len()); diff --git a/kubi-server/src/world/chunk.rs b/kubi-server/src/world/chunk.rs index a83b0ee..bfecbcd 100644 --- a/kubi-server/src/world/chunk.rs +++ b/kubi-server/src/world/chunk.rs @@ -1,8 +1,10 @@ use glam::IVec3; use hashbrown::HashSet; use nohash_hasher::BuildNoHashHasher; -use kubi_shared::chunk::BlockData; -use kubi_udp::{ClientId, ClientIdRepr}; +use kubi_shared::{ + chunk::BlockData, + networking::client::ClientId +}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ChunkState { @@ -15,7 +17,7 @@ pub struct Chunk { pub position: IVec3, pub state: ChunkState, pub blocks: Option, - pub subscriptions: HashSet>, + pub subscriptions: HashSet>, } impl Chunk { pub fn new(position: IVec3) -> Self { diff --git a/kubi-server/src/world/tasks.rs b/kubi-server/src/world/tasks.rs index fd2d120..8094632 100644 --- a/kubi-server/src/world/tasks.rs +++ b/kubi-server/src/world/tasks.rs @@ -5,7 +5,8 @@ use rayon::{ThreadPool, ThreadPoolBuilder}; use anyhow::Result; use kubi_shared::{ chunk::BlockData, - worldgen::{QueuedBlock, generate_world} + worldgen::generate_world, + queue::QueuedBlock, }; pub enum ChunkTask { diff --git a/kubi-shared/Cargo.toml b/kubi-shared/Cargo.toml index 7a4f1cc..a63255d 100644 --- a/kubi-shared/Cargo.toml +++ b/kubi-shared/Cargo.toml @@ -8,9 +8,10 @@ publish = false [dependencies] glam = { version = "0.23", features = ["debug-glam-assert", "fast-math", "serde"] } -shipyard = { git = "https://github.com/leudz/shipyard", rev = "eb189f66" } +shipyard = { git = "https://github.com/leudz/shipyard", rev = "eb189f66", default-features = false, features = ["std"] } strum = { version = "0.24", features = ["derive"] } -bincode = "2.0.0-rc" +postcard = { version = "1.0", features = ["alloc"] } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } anyhow = "1.0" bracket-noise = "0.8" rand = { version = "0.8", default_features = false, features = ["std", "min_const_gen"] } @@ -18,4 +19,4 @@ rand_xoshiro = "0.6" [features] default = [] -nightly = ["rand/nightly", "rand/simd_support"] +nightly = ["rand/nightly", "rand/simd_support", "serde/unstable", "glam/core-simd"] diff --git a/kubi-shared/src/block.rs b/kubi-shared/src/block.rs index 5afc8db..b2f0bee 100644 --- a/kubi-shared/src/block.rs +++ b/kubi-shared/src/block.rs @@ -1,7 +1,7 @@ -use bincode::{Encode, Decode}; +use serde::{Serialize, Deserialize}; use strum::EnumIter; -#[derive(Clone, Copy, Debug, EnumIter)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, EnumIter)] #[repr(u8)] pub enum BlockTexture { Stone, @@ -22,7 +22,7 @@ pub enum BlockTexture { WaterSolid, } -#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, EnumIter)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, EnumIter)] #[repr(u8)] pub enum Block { Air, diff --git a/kubi-shared/src/lib.rs b/kubi-shared/src/lib.rs index 2f1da39..10ea49c 100644 --- a/kubi-shared/src/lib.rs +++ b/kubi-shared/src/lib.rs @@ -4,3 +4,5 @@ pub mod worldgen; pub mod chunk; pub mod transform; pub mod entity; +pub mod player; +pub mod queue; diff --git a/kubi-shared/src/networking.rs b/kubi-shared/src/networking.rs index 4945d0c..05bff68 100644 --- a/kubi-shared/src/networking.rs +++ b/kubi-shared/src/networking.rs @@ -1,2 +1,3 @@ pub mod messages; pub mod state; +pub mod client; diff --git a/kubi-shared/src/networking/client.rs b/kubi-shared/src/networking/client.rs new file mode 100644 index 0000000..35f3744 --- /dev/null +++ b/kubi-shared/src/networking/client.rs @@ -0,0 +1,3 @@ +pub type ClientId = u16; +pub type ClientKey = u16; + diff --git a/kubi-shared/src/networking/messages.rs b/kubi-shared/src/networking/messages.rs index c8b17a9..9213547 100644 --- a/kubi-shared/src/networking/messages.rs +++ b/kubi-shared/src/networking/messages.rs @@ -1,30 +1,63 @@ use std::num::NonZeroUsize; -use bincode::{Encode, Decode}; -use crate::{chunk::BlockData, block::Block}; +use serde::{Serialize, Deserialize}; +use crate::{chunk::BlockData, queue::QueuedBlock}; -type IVec3Arr = [i32; 3]; -type Vec3Arr = [f32; 3]; -type QuatArr = [f32; 3]; +pub type IVec3Arr = [i32; 3]; +pub type Vec3Arr = [f32; 3]; +pub type QuatArr = [f32; 3]; -pub const PROTOCOL_ID: u16 = 1; +pub const PROTOCOL_ID: u16 = 2; -#[derive(Encode, Decode, Clone)] +pub const C_CLIENT_HELLO: u8 = 0; +pub const C_POSITION_CHANGED: u8 = 1; +pub const C_CHUNK_SUB_REQUEST: u8 = 2; + +#[derive(Serialize, Deserialize, Clone)] +#[repr(u8)] pub enum ClientToServerMessage { ClientHello { username: String, password: Option, - }, + } = C_CLIENT_HELLO, PositionChanged { position: Vec3Arr, velocity: Vec3Arr, direction: QuatArr, - }, + } = C_POSITION_CHANGED, ChunkSubRequest { chunk: IVec3Arr, - }, + } = C_CHUNK_SUB_REQUEST, } -#[derive(Encode, Decode, Clone)] +pub const S_SERVER_HELLO: u8 = 0; +pub const S_SERVER_FUCK_OFF: u8 = 1; +pub const S_PLAYER_POSITION_CHANGED: u8 = 2; +pub const S_CHUNK_RESPONSE: u8 = 3; + +#[derive(Serialize, Deserialize, Clone)] +#[repr(u8)] +pub enum ServerToClientMessage { + ServerHello { + init: InitData + } = S_SERVER_HELLO, + ServerFuckOff { + reason: String, + } = S_SERVER_FUCK_OFF, + PlayerPositionChanged { + client_id: u8, + position: Vec3Arr, + direction: QuatArr, + } = S_PLAYER_POSITION_CHANGED, + ChunkResponse { + chunk: IVec3Arr, + data: BlockData, + queued: Vec, + } = S_CHUNK_RESPONSE, +} + +//--- + +#[derive(Serialize, Deserialize, Clone)] pub struct UserInitData { pub client_id: NonZeroUsize, //maybe use the proper type instead pub username: String, @@ -33,27 +66,7 @@ pub struct UserInitData { pub direction: QuatArr, } -#[derive(Encode, Decode, Clone)] +#[derive(Serialize, Deserialize, Clone)] pub struct InitData { pub users: Vec } - -#[derive(Encode, Decode, Clone)] -pub enum ServerToClientMessage { - ServerHello { - init: InitData - }, - ServerFuckOff { - reason: String, - }, - PlayerPositionChanged { - client_id: u8, - position: Vec3Arr, - direction: QuatArr, - }, - ChunkResponse { - chunk: IVec3Arr, - data: BlockData, - queued: Vec<(IVec3Arr, Block)>, - } -} diff --git a/kubi-shared/src/player.rs b/kubi-shared/src/player.rs new file mode 100644 index 0000000..d2df58c --- /dev/null +++ b/kubi-shared/src/player.rs @@ -0,0 +1,4 @@ +use shipyard::Component; + +#[derive(Component)] +pub struct Player; diff --git a/kubi-shared/src/queue.rs b/kubi-shared/src/queue.rs new file mode 100644 index 0000000..7216756 --- /dev/null +++ b/kubi-shared/src/queue.rs @@ -0,0 +1,11 @@ +use glam::IVec3; +use serde::{Serialize, Deserialize}; +use crate::block::Block; + +#[derive(Serialize, Deserialize, Clone, Copy, Debug)] +pub struct QueuedBlock { + pub position: IVec3, + pub block_type: Block, + /// Only replace air blocks + pub soft: bool, +} diff --git a/kubi-shared/src/worldgen.rs b/kubi-shared/src/worldgen.rs index 09cb993..56fd807 100644 --- a/kubi-shared/src/worldgen.rs +++ b/kubi-shared/src/worldgen.rs @@ -4,7 +4,8 @@ use glam::{IVec3, ivec3, Vec3Swizzles, IVec2}; use rand_xoshiro::Xoshiro256StarStar; use crate::{ chunk::{BlockData, CHUNK_SIZE}, - block::Block + block::Block, + queue::QueuedBlock, }; fn mountain_ramp(mut x: f32) -> f32 { @@ -29,10 +30,6 @@ fn local_y_position(height: i32, chunk_position: IVec3) -> Option { (0..CHUNK_SIZE as i32).contains(&position).then_some(position as usize) } -pub struct QueuedBlock { - pub position: IVec3, - pub block_type: Block, -} pub fn generate_world(chunk_position: IVec3, seed: u64) -> (BlockData, Vec) { let offset = chunk_position * CHUNK_SIZE as i32; @@ -47,7 +44,8 @@ pub fn generate_world(chunk_position: IVec3, seed: u64) -> (BlockData, Vec), - Timeout, - ConnectionReset, - InvalidProtocolId, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ClientStatus { - Disconnected, - Connecting, - Connected, -} - -#[derive(Clone, Copy, Debug)] -pub struct ClientConfig { - pub protocol_id: u16, - pub timeout: Duration, - pub heartbeat_interval: Duration, -} -impl Default for ClientConfig { - fn default() -> Self { - Self { - protocol_id: DEFAULT_USER_PROTOCOL_ID, - timeout: Duration::from_secs(5), - heartbeat_interval: Duration::from_secs(3), - } - } -} - -pub enum ClientEvent where T: Message { - Connected(ClientId), - Disconnected(DisconnectReason), - MessageReceived(T) -} - -pub struct Client where S: Message, R: Message { - config: ClientConfig, - addr: SocketAddr, - socket: UdpSocket, - status: ClientStatus, - timeout: Instant, - last_heartbeat: Instant, - client_id: Option, - disconnect_reason: DisconnectReason, - event_queue: VecDeque>, - _s: PhantomData, -} -impl Client where S: Message, R: Message { - #[inline] - pub fn new(addr: SocketAddr, config: ClientConfig) -> Result { - if config.protocol_id == 0 { - log::warn!("Warning: using 0 as protocol_id is not recommended"); - } - if config.protocol_id == DEFAULT_USER_PROTOCOL_ID { - log::warn!("Warning: using default protocol_id is not recommended"); - } - let bind_addr: SocketAddr = "0.0.0.0:0".parse().unwrap(); - let socket = UdpSocket::bind(bind_addr)?; - socket.set_nonblocking(true)?; - Ok(Self { - addr, - config, - socket, - status: ClientStatus::Disconnected, - timeout: Instant::now(), - last_heartbeat: Instant::now(), - client_id: None, - disconnect_reason: DisconnectReason::default(), - event_queue: VecDeque::new(), - _s: PhantomData, - }) - } - - fn send_raw_packet(&self, packet: ClientPacket) -> Result<()> { - let id_packet = IdClientPacket(self.client_id, packet); - let bytes = bincode::encode_to_vec(id_packet, BINCODE_CONFIG)?; - self.socket.send(&bytes)?; - Ok(()) - } - - fn disconnect_inner(&mut self, reason: DisconnectReason, silent: bool) -> Result<()> { - log::info!("client disconnected because {reason:?}"); - if !silent { - self.send_raw_packet(ClientPacket::Disconnect)?; - } - self.client_id = None; - self.status = ClientStatus::Disconnected; - self.disconnect_reason = reason; - self.event_queue.push_back(ClientEvent::Disconnected(self.disconnect_reason.clone())); - Ok(()) - } - - fn reset_timeout(&mut self) { - self.timeout = Instant::now(); - } - - #[inline] - pub fn connect(&mut self) -> Result<()> { - log::info!("Client connecting.."); - if self.status != ClientStatus::Disconnected { - bail!("Not Disconnected"); - } - self.status = ClientStatus::Connecting; - self.last_heartbeat = Instant::now(); - self.reset_timeout(); - self.socket.connect(self.addr)?; - self.send_raw_packet(ClientPacket::Connect{ - user_protocol: self.config.protocol_id, - inner_protocol: PROTOCOL_ID, - })?; - Ok(()) - } - - #[inline] - pub fn disconnect(&mut self) -> Result<()> { - if self.status != ClientStatus::Connected { - bail!("Not Connected"); - } - self.disconnect_inner(DisconnectReason::ClientDisconnected, false)?; - Ok(()) - } - - #[inline] - pub fn set_nonblocking(&mut self, is_nonblocking: bool) -> Result<()> { - self.socket.set_nonblocking(is_nonblocking)?; - Ok(()) - } - - #[inline] - pub fn get_status(&self) -> ClientStatus { - self.status - } - - #[inline] - pub fn is_connected(&self) -> bool { - self.status == ClientStatus::Connected - } - - #[inline] - pub fn is_connecting(&self) -> bool { - self.status == ClientStatus::Connecting - } - - #[inline] - pub fn is_disconnected(&self) -> bool { - self.status == ClientStatus::Disconnected - } - - //Return true if the client has not made any connection attempts yet - #[inline] - pub fn has_not_made_connection_attempts(&self) -> bool { - matches!(self.status, ClientStatus::Disconnected) && - matches!(self.disconnect_reason, DisconnectReason::NotConnected) - } - - #[inline] - pub fn send_message(&self, message: S) -> Result<()> { - if self.status != ClientStatus::Connected { - bail!("Not Connected"); - } - self.send_raw_packet(ClientPacket::Data(message))?; - Ok(()) - } - - #[inline] - pub fn update(&mut self) -> Result<()> { // , callback: fn(ClientEvent) -> Result<()> - if self.status == ClientStatus::Disconnected { - return Ok(()) - } - if self.timeout.elapsed() > self.config.timeout { - log::warn!("Client timed out"); - //We don't care if this packet actually gets sent because the server is likely dead - let _ = self.disconnect_inner(DisconnectReason::Timeout, false).map_err(|_| { - log::warn!("Failed to send disconnect packet"); - }); - return Ok(()) - } - if self.last_heartbeat.elapsed() > self.config.heartbeat_interval { - log::trace!("Sending heartbeat packet"); - self.send_raw_packet(ClientPacket::Heartbeat)?; - self.last_heartbeat = Instant::now(); - } - //receive - let mut buf = [0; PACKET_SIZE]; - loop { - match self.socket.recv(&mut buf) { - Ok(length) => { - //TODO check the first byte of the raw data instead of decoding? - let (packet, _): (IdServerPacket, _) = bincode::decode_from_slice(&buf[..length], BINCODE_CONFIG)?; - let IdServerPacket(user_id, packet) = packet; - if self.client_id.map(|x| Some(x) != user_id).unwrap_or_default() { - return Ok(()) - } - self.reset_timeout(); - match packet { - ServerPacket::Connected(client_id) => { - log::info!("client connected with id {client_id}"); - self.client_id = Some(client_id); - self.status = ClientStatus::Connected; - self.event_queue.push_back(ClientEvent::Connected(client_id)); - return Ok(()) - }, - ServerPacket::Disconnected(reason) => { - log::info!("client kicked: {reason}"); - let reason = DisconnectReason::KickedByServer(Some(reason)); - self.disconnect_inner(reason, true)?; //this should never fail but we're handling the error anyway - return Ok(()) - }, - ServerPacket::Data(message) => { - self.event_queue.push_back(ClientEvent::MessageReceived(message)); - self.timeout = Instant::now(); - }, - ServerPacket::Heartbeat => { - self.timeout = Instant::now(); - }, - ServerPacket::ProtoDisconnect => { - let reason = DisconnectReason::InvalidProtocolId; - self.disconnect_inner(reason, true)?; //this should never fail but we're handling the error anyway - return Ok(()); - } - } - }, - Err(error) if error.kind() != ErrorKind::WouldBlock => { - match error.kind() { - ErrorKind::ConnectionReset => { - log::error!("Connection interrupted"); - self.disconnect_inner(DisconnectReason::ConnectionReset, true)?; - }, - _ => { - log::error!("IO error {error}"); - return Err(error.into()); - }, - } - }, - _ => break, - } - } - Ok(()) - } - - #[inline] - pub fn pop_event(&mut self) -> Option> { - self.event_queue.pop_front() - } - - #[inline] - pub fn process_events(&mut self) -> DrainDeque> { - self.event_queue.drain(..) - } -} diff --git a/kubi-udp/src/common.rs b/kubi-udp/src/common.rs deleted file mode 100644 index be598a7..0000000 --- a/kubi-udp/src/common.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::num::NonZeroU8; - -pub type ClientId = NonZeroU8; -pub type ClientIdRepr = u8; - -pub const MAX_CLIENTS: usize = u8::MAX as _; - -pub const PROTOCOL_ID: u16 = 1; -pub const DEFAULT_USER_PROTOCOL_ID: u16 = 0xffff; - -pub const PACKET_SIZE: usize = u16::MAX as usize; diff --git a/kubi-udp/src/lib.rs b/kubi-udp/src/lib.rs deleted file mode 100644 index 82588e3..0000000 --- a/kubi-udp/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub mod client; -pub mod server; -pub(crate) mod packet; -pub(crate) mod common; -pub use common::ClientId; -pub use common::ClientIdRepr; -pub use common::MAX_CLIENTS; - -//pub(crate) trait Serializable: bincode::Encode + bincode::Decode {} -pub(crate) const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard() - .with_little_endian() - .with_variable_int_encoding() - .skip_fixed_array_length(); diff --git a/kubi-udp/src/packet.rs b/kubi-udp/src/packet.rs deleted file mode 100644 index b5eff11..0000000 --- a/kubi-udp/src/packet.rs +++ /dev/null @@ -1,33 +0,0 @@ -use bincode::{Encode, Decode}; -use crate::common::ClientId; - -pub trait Message: Encode + Decode + Clone {} -impl Message for T {} - -#[repr(u8)] -#[derive(Encode, Decode)] -pub enum ClientPacket where T: Message { - Connect { - inner_protocol: u16, - user_protocol: u16, - }, //should always stay 0! - Data(T), - Disconnect, - Heartbeat, -} - -#[derive(Encode, Decode)] -pub struct IdClientPacket(pub Option, pub ClientPacket); - -#[repr(u8)] -#[derive(Encode, Decode)] -pub enum ServerPacket where T: Message { - ProtoDisconnect = 0, - Data(T), - Disconnected(String), - Connected(ClientId), - Heartbeat, -} - -#[derive(Encode, Decode)] -pub struct IdServerPacket(pub Option, pub ServerPacket); diff --git a/kubi-udp/src/server.rs b/kubi-udp/src/server.rs deleted file mode 100644 index 337f292..0000000 --- a/kubi-udp/src/server.rs +++ /dev/null @@ -1,266 +0,0 @@ -use std::{ - net::{UdpSocket, SocketAddr}, - time::{Instant, Duration}, - marker::PhantomData, - collections::{VecDeque, vec_deque::Drain as DrainDeque}, - io::ErrorKind -}; -use anyhow::{Result, Error, bail}; -use hashbrown::HashMap; -use nohash_hasher::BuildNoHashHasher; -use crate::{ - BINCODE_CONFIG, - common::{ClientId, ClientIdRepr, MAX_CLIENTS, PROTOCOL_ID, DEFAULT_USER_PROTOCOL_ID, PACKET_SIZE}, - packet::{IdClientPacket, ClientPacket, ServerPacket, IdServerPacket, Message} -}; - -//i was feeling a bit sick while writing most of this please excuse me for my terrible code :3 - -pub struct ConnectedClient { - id: ClientId, - addr: SocketAddr, - timeout: Instant, -} - -#[derive(Clone, Copy, Debug)] -pub struct ServerConfig { - pub max_clients: usize, - pub client_timeout: Duration, - pub protocol_id: u16, -} -impl Default for ServerConfig { - fn default() -> Self { - Self { - max_clients: MAX_CLIENTS, - client_timeout: Duration::from_secs(5), - protocol_id: DEFAULT_USER_PROTOCOL_ID, - } - } -} - -pub enum ServerEvent where T: Message { - Connected(ClientId), - Disconnected(ClientId), - MessageReceived { - from: ClientId, - message: T - } -} - -pub struct Server where S: Message, R: Message { - socket: UdpSocket, - clients: HashMap>, - config: ServerConfig, - event_queue: VecDeque>, - _s: PhantomData, -} -impl Server where S: Message, R: Message { - pub fn bind(addr: SocketAddr, config: ServerConfig) -> anyhow::Result { - assert!(config.max_clients <= MAX_CLIENTS, "max_clients value exceeds the maximum allowed amount of clients"); - if config.protocol_id == 0 { - log::warn!("Warning: using 0 as protocol_id is not recommended"); - } - if config.protocol_id == DEFAULT_USER_PROTOCOL_ID { - log::warn!("Warning: using default protocol_id is not recommended"); - } - let socket = UdpSocket::bind(addr)?; - socket.set_nonblocking(true)?; - Ok(Self { - config, - socket, - clients: HashMap::with_capacity_and_hasher(MAX_CLIENTS, BuildNoHashHasher::default()), - event_queue: VecDeque::new(), - _s: PhantomData, - }) - } - - - fn send_to_addr_inner(socket: &UdpSocket, addr: SocketAddr, packet: IdServerPacket) -> Result<()> { - let bytes = bincode::encode_to_vec(packet, BINCODE_CONFIG)?; - socket.send_to(&bytes, addr)?; - Ok(()) - } - - fn send_to_addr(&self, addr: SocketAddr, packet: IdServerPacket) -> Result<()> { - Self::send_to_addr_inner(&self.socket, addr, packet) - } - - fn send_packet(&self, packet: IdServerPacket) -> Result<()> { - let Some(id) = packet.0 else { - bail!("send_to_client call without id") - }; - let Some(client) = self.clients.get(&id) else { - bail!("client with id {id} doesn't exist") - }; - self.send_to_addr(client.addr, packet)?; - Ok(()) - } - - fn add_client(&mut self, addr: SocketAddr) -> Result { - let Some(id) = (1..=self.config.max_clients) - .map(|x| ClientId::new(x as _).unwrap()) - .find(|i| !self.clients.contains_key(i)) else { - bail!("Server full"); - }; - if self.clients.iter().any(|x| x.1.addr == addr) { - bail!("Already connected from the same address"); - } - self.clients.insert(id, ConnectedClient { - id, - addr, - timeout: Instant::now(), - }); - Ok(id) - } - - fn disconnect_client_inner(&mut self, id: ClientId, reason: String) -> Result<()> { - let result = self.send_packet(IdServerPacket( - Some(id), ServerPacket::Disconnected(reason) - )); - self.clients.remove(&id); - result - } - - pub fn kick_client(&mut self, id: ClientId, reason: String) -> Result<()> { - if !self.clients.contains_key(&id) { - bail!("Already disconnected") - } - self.disconnect_client_inner(id, reason)?; - Ok(()) - } - - pub fn shutdown(mut self) -> Result<()> { - let clients = self.clients.keys().copied().collect::>(); - for id in clients { - self.kick_client(id, "Server is shutting down".into())?; - } - Ok(()) - } - - pub fn send_message(&mut self, id: ClientId, message: S) -> Result<()> { - self.send_packet(IdServerPacket(Some(id), ServerPacket::Data(message)))?; - Ok(()) - } - pub fn multicast_message(&mut self, clients: impl IntoIterator, message: S) -> Vec { - //TODO use actual udp multicast - let mut errors = Vec::with_capacity(0); - for client in clients { - if let Err(error) = self.send_message(client, message.clone()) { - log::error!("Message broadcast failed for id {client}"); - errors.push(error); - } - } - errors - } - pub fn broadcast_message(&mut self, message: S) -> Vec { - let ids = self.clients.keys().copied().collect::>(); - self.multicast_message(ids, message) - } - pub fn broadcast_message_except(&mut self, except: ClientId, message: S) -> Vec { - let ids = self.clients.keys().copied().filter(|&k| k != except).collect::>(); - self.multicast_message(ids, message) - } - - pub fn update(&mut self) -> Result<()> { - //kick inactive clients - self.clients.retain(|&id, client| { - if client.timeout.elapsed() > self.config.client_timeout { - if let Err(_) = Self::send_to_addr_inner(&self.socket, client.addr, IdServerPacket( - Some(id), ServerPacket::Disconnected("Timed out".into()) - )) { - log::warn!("Client {id} timed out and we failed to send the kick packet. This shouldn't reaally matter") - } else { - log::info!("Client {id} timed out"); - } - return false - } - true - }); - - let mut buf = [0; PACKET_SIZE]; - loop { - match self.socket.recv_from(&mut buf) { - Ok((len, addr)) => { - if let Ok(packet) = bincode::decode_from_slice(&buf[..len], BINCODE_CONFIG) { - let (packet, _): (IdClientPacket, _) = packet; - let IdClientPacket(id, packet) = packet; - match id { - Some(id) => { - if !self.clients.contains_key(&id) { - bail!("Client with id {id} doesn't exist"); - } - if self.clients.get(&id).unwrap().addr != addr { - bail!("Client addr doesn't match"); - } - match packet { - ClientPacket::Data(data) => { - self.event_queue.push_back(ServerEvent::MessageReceived { - from: id, - message: data, - }); - } - ClientPacket::Disconnect => { - log::info!("Client {id} disconnected"); - self.event_queue.push_back(ServerEvent::Disconnected(id)); - self.disconnect_client_inner(id, "Disconnected".into())?; - }, - ClientPacket::Heartbeat => { - self.clients.get_mut(&id).unwrap().timeout = Instant::now(); - self.send_packet(IdServerPacket(Some(id), ServerPacket::Heartbeat))?; - }, - ClientPacket::Connect{..} => bail!("Client already connected"), - } - }, - None => { - match packet { - ClientPacket::Connect { user_protocol, inner_protocol } => { - if (inner_protocol != PROTOCOL_ID) || (user_protocol != self.config.protocol_id ) { - log::error!("Client conenction refused: Invalid protocol id"); - self.send_to_addr(addr, - IdServerPacket(None, ServerPacket::ProtoDisconnect) - )?; - continue; - } - - match self.add_client(addr) { - Ok(id) => { - log::info!("Client with id {id} connected"); - self.event_queue.push_back(ServerEvent::Connected(id)); - self.send_to_addr(addr, - IdServerPacket(None, ServerPacket::Connected(id)) - )?; - }, - Err(error) => { - let reason = error.to_string(); - log::error!("Client connection failed: {reason}"); - self.send_to_addr(addr, IdServerPacket( - None, ServerPacket::Disconnected(reason) - ))?; - } - } - }, - _ => bail!("Invalid packet type for non-id packet") - } - } - } - } else { - bail!("Corrupted packet received"); - } - }, - Err(error) if error.kind() != ErrorKind::WouldBlock => { - log::error!("IO error {}", error); - // return Err(error.into()); - }, - _ => break, - } - } - Ok(()) - } - - pub fn pop_event(&mut self) -> Option> { - self.event_queue.pop_front() - } - pub fn process_events(&mut self) -> DrainDeque> { - self.event_queue.drain(..) - } -} diff --git a/kubi-udp/tests/test.rs b/kubi-udp/tests/test.rs deleted file mode 100644 index 7deb52c..0000000 --- a/kubi-udp/tests/test.rs +++ /dev/null @@ -1,93 +0,0 @@ -use kubi_udp::{ - server::{Server, ServerConfig, ServerEvent}, - client::{Client, ClientConfig, ClientEvent}, -}; -use std::{thread, time::Duration}; - -const TEST_ADDR: &str = "127.0.0.1:22342"; - -type CtsMessage = u32; -type StcMessage = u64; - -const CTS_MSG: CtsMessage = 0xbeef_face; -const STC_MSG: StcMessage = 0xdead_beef_cafe_face; - -#[test] -fn test_connection() { - //Init logging - kubi_logging::init(); - - //Create server and client - let mut server: Server = Server::bind( - TEST_ADDR.parse().expect("Invalid TEST_ADDR"), - ServerConfig::default() - ).expect("Failed to create server"); - let mut client: Client = Client::new( - TEST_ADDR.parse().unwrap(), - ClientConfig::default() - ).expect("Failed to create client"); - - //Start server update thread - let server_handle = thread::spawn(move || { - let mut message_received = false; - loop { - server.update().unwrap(); - let events: Vec<_> = server.process_events().collect(); - for event in events { - match event { - ServerEvent::Connected(id) => { - assert_eq!(id.get(), 1, "Unexpected client id"); - server.send_message(id, STC_MSG).unwrap(); - }, - ServerEvent::Disconnected(id) => { - assert!(message_received, "Client {id} disconnected from the server before sending the message"); - return; - }, - ServerEvent::MessageReceived { from, message } => { - log::info!("server received message"); - assert_eq!(message, CTS_MSG, "Received message not equal"); - message_received = true; - }, - _ => () - } - } - } - }); - - //Wait a bit - thread::sleep(Duration::from_secs(1)); - - //Connect client - client.connect().expect("Client connect failed"); - - //Start updating the client - let client_handle = thread::spawn(move || { - let mut message_received = false; - loop { - client.update().unwrap(); - let events: Vec<_> = client.process_events().collect(); - for event in events { - match event { - ClientEvent::Connected(id) => { - assert_eq!(id.get(), 1, "Unexpected client id"); - client.send_message(CTS_MSG).unwrap(); - }, - ClientEvent::Disconnected(reason) => { - assert!(message_received, "Client lost connection to the server before sending the message with reason: {reason:?}"); - return; - }, - ClientEvent::MessageReceived(data) => { - log::info!("client received message"); - assert_eq!(data, STC_MSG, "Received message not equal"); - message_received = true; - client.disconnect().unwrap(); - }, - _ => () - } - } - } - }); - - server_handle.join().unwrap(); - client_handle.join().unwrap(); -} diff --git a/kubi/Cargo.toml b/kubi/Cargo.toml index 6b16766..f2ca9d0 100644 --- a/kubi/Cargo.toml +++ b/kubi/Cargo.toml @@ -6,7 +6,6 @@ publish = false [dependencies] kubi-shared = { path = "../kubi-shared" } -kubi-udp = { path = "../kubi-udp" } kubi-logging = { path = "../kubi-logging" } log = "*" glium = "0.32" @@ -15,15 +14,18 @@ image = { version = "0.24", default_features = false, features = ["png"] } strum = { version = "0.24", features = ["derive"] } hashbrown = "0.13" rayon = "1.6" -shipyard = { git = "https://github.com/leudz/shipyard", rev = "eb189f66", features = ["thread_local"] } +shipyard = { git = "https://github.com/leudz/shipyard", rev = "eb189f66", default-features = false, features = ["std", "proc", "thread_local"] } nohash-hasher = "0.2.0" anyhow = "1.0" flume = "0.10" gilrs = { version = "0.10", default_features = false, features = ["xinput"] } +uflow = "0.7" +postcard = { version = "1.0", features = ["alloc"] } [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3" } [features] default = [] +parallel = ["shipyard/parallel"] nightly = ["glam/core-simd", "kubi-shared/nightly"] diff --git a/kubi/src/block_placement.rs b/kubi/src/block_placement.rs index 19cfa8f..888311c 100644 --- a/kubi/src/block_placement.rs +++ b/kubi/src/block_placement.rs @@ -1,9 +1,12 @@ use shipyard::{UniqueViewMut, UniqueView, View, IntoIter, ViewMut, EntitiesViewMut, Component, Workload, IntoWorkload}; use glium::glutin::event::VirtualKeyCode; -use kubi_shared::block::Block; +use kubi_shared::{ + block::Block, + queue::QueuedBlock, +}; use crate::{ player::MainPlayer, - world::{raycast::{LookingAtBlock, RAYCAST_STEP}, queue::{BlockUpdateQueue, BlockUpdateEvent}}, + world::{raycast::{LookingAtBlock, RAYCAST_STEP}, queue::BlockUpdateQueue}, input::{Inputs, PrevInputs, RawKbmInputState}, events::{EventComponent, player_actions::PlayerActionEvent}, }; @@ -67,9 +70,9 @@ fn block_placement_system( (ray.block_position, Block::Air) }; //queue place - block_event_queue.push(BlockUpdateEvent { + block_event_queue.push(QueuedBlock { position: place_position, - value: place_block, + block_type: place_block, soft: place_block != Block::Air, }); //send event diff --git a/kubi/src/camera/matrices.rs b/kubi/src/camera/matrices.rs index 109c3de..b6ded27 100644 --- a/kubi/src/camera/matrices.rs +++ b/kubi/src/camera/matrices.rs @@ -1,6 +1,6 @@ use glam::{Vec3, Mat4}; -use shipyard::{ViewMut, View, IntoIter, Workload, IntoWorkload, track}; -use crate::{transform::Transform, events::WindowResizedEvent}; +use shipyard::{ViewMut, View, IntoIter, Workload, IntoWorkload, track, UniqueView, SystemModificator}; +use crate::{transform::Transform, rendering::WindowSize, events::WindowResizedEvent}; use super::Camera; //maybe parallelize these two? @@ -18,11 +18,8 @@ fn update_view_matrix( fn update_perspective_matrix( mut vm_camera: ViewMut, - resize: View, + size: UniqueView, ) { - let Some(&size) = resize.iter().next() else { - return - }; for mut camera in (&mut vm_camera).iter() { camera.perspective_matrix = Mat4::perspective_rh_gl( camera.fov, @@ -33,9 +30,19 @@ fn update_perspective_matrix( } } +fn need_perspective_calc( + v_camera: View, + resize_event: View, +) -> bool { + (resize_event.len() > 0) || + (v_camera.iter().any(|camera| { + camera.perspective_matrix == Mat4::default() + })) +} + pub fn update_matrices() -> Workload { ( update_view_matrix, - update_perspective_matrix, + update_perspective_matrix.run_if(need_perspective_calc), ).into_workload() } diff --git a/kubi/src/main.rs b/kubi/src/main.rs index 76481e5..75503a0 100644 --- a/kubi/src/main.rs +++ b/kubi/src/main.rs @@ -7,7 +7,8 @@ use shipyard::{ World, Workload, IntoWorkload, UniqueView, UniqueViewMut, - NonSendSync, WorkloadModificator, SystemModificator + NonSendSync, WorkloadModificator, + SystemModificator }; use glium::{ glutin::{ @@ -46,9 +47,9 @@ use world::{ loading::update_loaded_world_around_player, raycast::update_raycasts, queue::apply_queued_blocks, - tasks::inject_network_responses_into_manager_queue + tasks::{inject_network_responses_into_manager_queue, ChunkTaskManager}, ChunkStorage }; -use player::spawn_player; +use player::{spawn_player, MainPlayer}; use prefabs::load_prefabs; use settings::load_settings; use camera::compute_cameras; @@ -96,8 +97,6 @@ fn startup() -> Workload { lock_cursor_now, init_input, init_gui, - init_game_world, - spawn_player, insert_control_flow_unique, init_delta_time, ).into_workload() @@ -107,19 +106,23 @@ fn update() -> Workload { update_window_size, update_cursor_lock_state, process_inputs, + ( + init_game_world.run_if_missing_unique::(), + spawn_player.run_if_storage_empty::(), + ).into_sequential_workload().run_if(is_ingame_or_loading).tag("game_init"), ( update_networking, - inject_network_responses_into_manager_queue, - ).into_sequential_workload().run_if(is_multiplayer), + inject_network_responses_into_manager_queue.run_if(is_ingame_or_loading).skip_if_missing_unique::(), + ).into_sequential_workload().run_if(is_multiplayer).tag("networking").after_all("game_init"), ( switch_to_loading_if_connected - ).into_workload().run_if(is_connecting), + ).into_workload().run_if(is_connecting).after_all("networking"), ( update_loading_screen, - ).into_workload().run_if(is_loading), + ).into_workload().run_if(is_loading).after_all("game_init"), ( update_loaded_world_around_player, - ).into_workload().run_if(is_ingame_or_loading), + ).into_workload().run_if(is_ingame_or_loading).after_all("game_init"), ( update_controllers, generate_move_events, diff --git a/kubi/src/networking.rs b/kubi/src/networking.rs index 6630c1c..31450ac 100644 --- a/kubi/src/networking.rs +++ b/kubi/src/networking.rs @@ -1,9 +1,9 @@ use shipyard::{Unique, AllStoragesView, UniqueView, UniqueViewMut, Workload, IntoWorkload, EntitiesViewMut, Component, ViewMut, SystemModificator, View, IntoIter, WorkloadModificator}; use glium::glutin::event_loop::ControlFlow; use std::net::SocketAddr; -use kubi_udp::client::{Client, ClientConfig, ClientEvent}; +use uflow::client::{Client, Config as ClientConfig, Event as ClientEvent}; use kubi_shared::networking::{ - messages::{ClientToServerMessage, ServerToClientMessage}, + messages::{ClientToServerMessage, ServerToClientMessage, S_SERVER_HELLO}, state::ClientJoinState }; use crate::{events::EventComponent, control_flow::SetControlFlow}; @@ -18,43 +18,34 @@ pub enum GameType { pub struct ServerAddress(pub SocketAddr); #[derive(Unique)] -pub struct UdpClient(pub Client); +pub struct UdpClient(pub Client); #[derive(Component)] -pub struct NetworkEvent(pub ClientEvent); +pub struct NetworkEvent(pub ClientEvent); -fn create_client( +impl NetworkEvent { + ///Checks if postcard-encoded message has a type + pub fn is_message_of_type(&self) -> bool { + let ClientEvent::Receive(data) = &self.0 else { return false }; + if data.len() == 0 { return false } + data[0] == T + } +} + +#[derive(Component)] +pub struct NetworkMessageEvent(pub ServerToClientMessage); + +fn connect_client( storages: AllStoragesView ) { log::info!("Creating client"); let address = storages.borrow::>().unwrap(); - storages.add_unique(UdpClient(Client::new( - address.0, - ClientConfig::default() - ).unwrap())); + let client = Client::connect(address.0, ClientConfig::default()).expect("Client connection failed"); + storages.add_unique(UdpClient(client)); storages.add_unique(ClientJoinState::Disconnected); } -fn connect_client( - mut client: UniqueViewMut -) { - log::info!("Connect called"); - client.0.connect().unwrap(); -} - -fn should_connect( - client: UniqueView -) -> bool { - client.0.has_not_made_connection_attempts() -} - -fn update_client( - mut client: UniqueViewMut, -) { - client.0.update().unwrap(); -} - -fn insert_client_events( +fn poll_client( mut client: UniqueViewMut, mut entities: EntitiesViewMut, mut events: ViewMut, @@ -63,7 +54,7 @@ fn insert_client_events( entities.bulk_add_entity(( &mut events, &mut network_events, - ), client.0.process_events().map(|event| { + ), client.0.step().map(|event| { (EventComponent, NetworkEvent(event)) })); } @@ -76,13 +67,19 @@ fn set_client_join_state_to_connected( } fn say_hello( - client: UniqueViewMut, + mut client: UniqueViewMut, ) { log::info!("Authenticating"); - client.0.send_message(ClientToServerMessage::ClientHello { - username: "Sbeve".into(), - password: None - }).unwrap(); + client.0.send( + postcard::to_allocvec( + &ClientToServerMessage::ClientHello { + username: "Sbeve".into(), + password: None + } + ).unwrap().into_boxed_slice(), + 0, + uflow::SendMode::Reliable + ); } fn check_server_hello_response( @@ -90,20 +87,30 @@ fn check_server_hello_response( mut join_state: UniqueViewMut ) { for event in network_events.iter() { - if let ClientEvent::MessageReceived(ServerToClientMessage::ServerHello { init }) = &event.0 { - log::info!("Joined the server!"); - //TODO handle init data - *join_state = ClientJoinState::Joined; + let ClientEvent::Receive(data) = &event.0 else { + continue + }; + if !event.is_message_of_type::() { + continue } + let Ok(parsed_message) = postcard::from_bytes(data) else { + log::error!("Malformed message"); + continue + }; + let ServerToClientMessage::ServerHello { init } = parsed_message else { + unreachable!() + }; + //TODO handle init data + *join_state = ClientJoinState::Joined; + log::info!("Joined the server!"); + return; } } pub fn update_networking() -> Workload { ( - create_client.run_if_missing_unique::(), - connect_client.run_if(should_connect), - update_client, - insert_client_events, + connect_client.run_if_missing_unique::(), + poll_client, ( set_client_join_state_to_connected, say_hello, @@ -119,12 +126,19 @@ pub fn disconnect_on_exit( mut client: UniqueViewMut, ) { if let Some(ControlFlow::ExitWithCode(_)) = control_flow.0 { - client.0.set_nonblocking(false).expect("Failed to switch socket to blocking mode"); - if let Err(error) = client.0.disconnect() { - log::error!("failed to disconnect: {}", error); - } else { + if client.0.is_active() { + client.0.flush(); + client.0.disconnect(); + while client.0.is_active() { client.0.step().for_each(|_|()); } log::info!("Client disconnected"); + } else { + log::info!("Client inactive") } + // if let Err(error) = client.0. { + // log::error!("failed to disconnect: {}", error); + // } else { + // log::info!("Client disconnected"); + // } } } @@ -133,7 +147,7 @@ pub fn disconnect_on_exit( fn if_just_connected( network_events: View, ) -> bool { - network_events.iter().any(|event| matches!(&event.0, ClientEvent::Connected(_))) + network_events.iter().any(|event| matches!(&event.0, ClientEvent::Connect)) } fn is_join_state( diff --git a/kubi/src/player.rs b/kubi/src/player.rs index cc1e705..0ae0a9a 100644 --- a/kubi/src/player.rs +++ b/kubi/src/player.rs @@ -1,4 +1,4 @@ -use shipyard::{Component, AllStoragesViewMut}; +use shipyard::{Component, AllStoragesViewMut, View, IntoIter}; use crate::{ transform::Transform, camera::Camera, @@ -6,9 +6,7 @@ use crate::{ world::raycast::LookingAtBlock, block_placement::PlayerHolding, }; - -#[derive(Component)] -pub struct Player; +pub use kubi_shared::player::Player; #[derive(Component)] pub struct MainPlayer; @@ -17,7 +15,7 @@ pub fn spawn_player ( mut storages: AllStoragesViewMut ) { log::info!("spawning player"); - storages.add_entity(( + let entity_id = storages.add_entity(( Player, MainPlayer, Transform::default(), diff --git a/kubi/src/world/loading.rs b/kubi/src/world/loading.rs index 76d67f3..154782d 100644 --- a/kubi/src/world/loading.rs +++ b/kubi/src/world/loading.rs @@ -2,6 +2,8 @@ use glam::{IVec3, ivec3}; use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType}; use kubi_shared::networking::messages::ClientToServerMessage; use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync, track}; +use kubi_shared::queue::QueuedBlock; +use uflow::SendMode; use crate::{ player::MainPlayer, transform::Transform, @@ -14,7 +16,7 @@ use super::{ ChunkStorage, ChunkMeshStorage, chunk::{Chunk, DesiredChunkState, CHUNK_SIZE, ChunkMesh, CurrentChunkState, ChunkData}, tasks::{ChunkTaskManager, ChunkTaskResponse, ChunkTask}, - queue::{BlockUpdateQueue, BlockUpdateEvent}, + queue::BlockUpdateQueue }; const MAX_CHUNK_OPS_INGAME: usize = 6; @@ -120,7 +122,7 @@ fn unload_downgrade_chunks( fn start_required_tasks( task_manager: UniqueView, - udp_client: Option>, + mut udp_client: Option>, mut world: UniqueViewMut, ) { if !world.is_modified() { @@ -133,10 +135,14 @@ fn start_required_tasks( match chunk.desired_state { DesiredChunkState::Loaded | DesiredChunkState::Rendered if chunk.current_state == CurrentChunkState::Nothing => { //start load task - if let Some(client) = &udp_client { - client.0.send_message(ClientToServerMessage::ChunkSubRequest { - chunk: position.to_array() - }).unwrap(); + if let Some(client) = &mut udp_client { + client.0.send( + postcard::to_allocvec(&ClientToServerMessage::ChunkSubRequest { + chunk: position.to_array() + }).unwrap().into_boxed_slice(), + 0, + SendMode::Reliable + ); } else { task_manager.spawn_task(ChunkTask::LoadChunk { seed: 0xbeef_face_dead_cafe, @@ -208,12 +214,9 @@ fn process_completed_tasks( chunk.current_state = CurrentChunkState::Loaded; //push queued blocks - for event in queued { - queue.push(BlockUpdateEvent { - position: event.position, - value: event.block_type, - soft: true, - }); + //TODO use extend + for item in queued { + queue.push(item); } //increase ops counter diff --git a/kubi/src/world/mesh/builder.rs b/kubi/src/world/mesh/builder.rs index ed1ffd7..fb5949e 100644 --- a/kubi/src/world/mesh/builder.rs +++ b/kubi/src/world/mesh/builder.rs @@ -126,7 +126,7 @@ impl MeshBuilder { let face_type = face_type as usize; let vertices = CROSS_FACES[face_type]; let normal_front = CROSS_FACE_NORMALS[face_type].to_array(); - let normal_back = CROSS_FACE_NORMALS[face_type].to_array(); + let normal_back = CROSS_FACE_NORMALS_BACK[face_type].to_array(); self.vertex_buffer.reserve(8); for i in 0..4 { //push front vertices self.vertex_buffer.push(ChunkVertex { diff --git a/kubi/src/world/queue.rs b/kubi/src/world/queue.rs index 670e909..eb9f346 100644 --- a/kubi/src/world/queue.rs +++ b/kubi/src/world/queue.rs @@ -1,26 +1,17 @@ use glam::{IVec3, ivec3}; -use kubi_shared::{block::Block, chunk::CHUNK_SIZE}; +use kubi_shared::{block::Block, chunk::CHUNK_SIZE, queue::QueuedBlock}; use shipyard::{UniqueViewMut, Unique}; - use super::ChunkStorage; -#[derive(Clone, Copy, Debug)] -pub struct BlockUpdateEvent { - pub position: IVec3, - pub value: Block, - //Only replace air blocks - pub soft: bool, -} - #[derive(Unique, Default, Clone)] pub struct BlockUpdateQueue { - queue: Vec + queue: Vec } impl BlockUpdateQueue { pub fn new() -> Self { Self::default() } - pub fn push(&mut self, event: BlockUpdateEvent) { + pub fn push(&mut self, event: QueuedBlock) { self.queue.push(event) } } @@ -35,7 +26,7 @@ pub fn apply_queued_blocks( if event.soft && *block != Block::Air { return false } - *block = event.value; + *block = event.block_type; //mark chunk as dirty let (chunk_pos, block_pos) = ChunkStorage::to_chunk_coords(event.position); let chunk = world.chunks.get_mut(&chunk_pos).expect("This error should never happen, if it does then something is super fucked up and the whole project needs to be burnt down."); diff --git a/kubi/src/world/tasks.rs b/kubi/src/world/tasks.rs index 0bc05ad..3a50ea4 100644 --- a/kubi/src/world/tasks.rs +++ b/kubi/src/world/tasks.rs @@ -1,11 +1,12 @@ use flume::{Sender, Receiver}; use glam::IVec3; use kubi_shared::{ - networking::messages::ServerToClientMessage, - worldgen::QueuedBlock + networking::messages::{S_CHUNK_RESPONSE, ServerToClientMessage}, + queue::QueuedBlock }; use shipyard::{Unique, UniqueView, View, IntoIter}; use rayon::{ThreadPool, ThreadPoolBuilder}; +use uflow::client::Event as ClientEvent; use super::{ chunk::BlockData, mesh::{generate_mesh, data::MeshGenData}, @@ -15,7 +16,6 @@ use crate::{ rendering::world::ChunkVertex, networking::NetworkEvent, }; -use kubi_udp::client::ClientEvent; pub enum ChunkTask { LoadChunk { @@ -83,16 +83,24 @@ pub fn inject_network_responses_into_manager_queue( events: View ) { for event in events.iter() { - if let ClientEvent::MessageReceived(ServerToClientMessage::ChunkResponse { chunk, data, queued }) = &event.0 { - let position = IVec3::from_array(*chunk); + if event.is_message_of_type::() { + let NetworkEvent(ClientEvent::Receive(data)) = &event else { unreachable!() }; + let ServerToClientMessage::ChunkResponse { + chunk, data, queued + } = postcard::from_bytes(data).expect("Chunk decode failed") else { unreachable!() }; manager.add_sussy_response(ChunkTaskResponse::LoadedChunk { - position, - chunk_data: data.clone(), - queued: queued.iter().map(|&(position, block_type)| QueuedBlock { - position: IVec3::from_array(position), - block_type - }).collect() + position: IVec3::from_array(chunk), + chunk_data: data, + queued }); } + // if let ClientEvent::MessageReceived(ServerToClientMessage::ChunkResponse { &chunk, data, queued }) = &event.0 { + // let position = IVec3::from_array(chunk); + // manager.add_sussy_response(ChunkTaskResponse::LoadedChunk { + // position, + // chunk_data: data.clone(), + // queued + // }); + // } } }