diff --git a/Server.toml b/Server.toml index 1cfc2a3..608af3f 100644 --- a/Server.toml +++ b/Server.toml @@ -1,7 +1,10 @@ [server] address = "0.0.0.0:12345" -max_clients = 254 +max_clients = 32 timeout_ms = 10000 [world] seed = 0xfeb_face_dead_cafe + +[query] +name = "Kubi Server" diff --git a/kubi-server/src/auth.rs b/kubi-server/src/auth.rs index 7d6dfae..09b9d67 100644 --- a/kubi-server/src/auth.rs +++ b/kubi-server/src/auth.rs @@ -1,40 +1,47 @@ -use shipyard::{UniqueView, NonSendSync}; +use shipyard::{UniqueView, NonSendSync, EntitiesViewMut, ViewMut, UniqueViewMut}; use uflow::{server::Event as ServerEvent, SendMode}; -use kubi_shared::networking::messages::{ - ClientToServerMessage, - ServerToClientMessage, - InitData, - C_CLIENT_HELLO +use kubi_shared::{ + networking::{ + messages::{ + ClientToServerMessage, + ServerToClientMessage, + InitData, + C_CLIENT_HELLO + }, + client::{Client, ClientId}, channels::CHANNEL_AUTH + }, + player::Player, + transform::Transform }; use crate::{ server::{ServerEvents, UdpServer, IsMessageOfType}, - config::ConfigTable + config::ConfigTable, + client::{ClientAddress, ClientIdMap, ClientAddressMap} }; pub fn authenticate_players( + mut entities: EntitiesViewMut, + mut players: ViewMut, + mut clients: ViewMut, + mut client_addrs: ViewMut, + mut transforms: ViewMut, + mut client_entity_map: UniqueViewMut, + mut client_addr_map: UniqueViewMut, server: NonSendSync>, events: UniqueView, config: UniqueView ) { for event in &events.0 { - // 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 Some(client) = server.0.client(client_addr) else { + log::error!("Client doesn't exist"); + continue + }; let Ok(parsed_message) = postcard::from_bytes(data) else { log::error!("Malformed message"); continue @@ -49,38 +56,70 @@ pub fn authenticate_players( 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 + postcard::to_allocvec(&ServerToClientMessage::ServerFuckOff { + reason: "Incorrect password".into() + }).unwrap().into_boxed_slice(), + CHANNEL_AUTH, + 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 + postcard::to_allocvec(&ServerToClientMessage::ServerFuckOff { + reason: "This server is password protected".into() + }).unwrap().into_boxed_slice(), + CHANNEL_AUTH, + SendMode::Reliable ); continue } } + //Find the player ID + let max_clients = config.server.max_clients as ClientId; + let Some(client_id) = (0..max_clients).into_iter().find(|id| { + !client_entity_map.0.contains_key(id) + }) else { + client.borrow_mut().send( + postcard::to_allocvec(&ServerToClientMessage::ServerFuckOff { + reason: "Can't find a free spot for you!".into() + }).unwrap().into_boxed_slice(), + CHANNEL_AUTH, + SendMode::Reliable + ); + continue + }; + //Spawn the user - //TODO Spawn the user on server side + let entity_id = entities.add_entity(( + &mut players, + &mut clients, + &mut client_addrs, + &mut transforms, + ), ( + Player, + Client(client_id), + ClientAddress(*client_addr), + Transform::default(), + )); + + //Add the user to the ClientIdMap and ClientAddressMap + client_entity_map.0.insert(client_id, entity_id); + client_addr_map.0.insert(*client_addr, entity_id); //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 + postcard::to_allocvec(&ServerToClientMessage::ServerHello { + init: InitData { + users: vec![] //TODO create init data + } + }).unwrap().into_boxed_slice(), + CHANNEL_AUTH, + SendMode::Reliable ); - log::info!("{username} joined the game!") + log::info!("{username}({client_id}) joined the game!") } } diff --git a/kubi-server/src/client.rs b/kubi-server/src/client.rs index 086e398..cbba8fc 100644 --- a/kubi-server/src/client.rs +++ b/kubi-server/src/client.rs @@ -1,19 +1,34 @@ -use shipyard::{Component, EntityId}; +use shipyard::{Component, EntityId, Unique, Workload, AllStoragesView}; use hashbrown::HashMap; use nohash_hasher::BuildNoHashHasher; +use std::net::SocketAddr; use kubi_shared::networking::client::ClientId; -#[derive(Component)] -pub struct Client(ClientId); +#[derive(Component, Clone, Copy)] +pub struct ClientAddress(pub SocketAddr); -pub struct ClientMap(HashMap>); -impl ClientMap { +#[derive(Unique)] +pub struct ClientIdMap(pub HashMap>); +impl ClientIdMap { pub fn new() -> Self { Self(HashMap::with_hasher(BuildNoHashHasher::default())) } } -impl Default for ClientMap { +impl Default for ClientIdMap { fn default() -> Self { Self::new() } } + +#[derive(Unique, Default)] +pub struct ClientAddressMap(pub HashMap); +impl ClientAddressMap { + pub fn new() -> Self { Self::default() } +} + +pub fn init_client_maps( + storages: AllStoragesView +) { + storages.add_unique(ClientIdMap::new()); + storages.add_unique(ClientAddressMap::new()); +} diff --git a/kubi-server/src/config.rs b/kubi-server/src/config.rs index 3a6abf1..a183674 100644 --- a/kubi-server/src/config.rs +++ b/kubi-server/src/config.rs @@ -15,10 +15,16 @@ pub struct ConfigTableWorld { pub seed: u64, } +#[derive(Serialize, Deserialize)] +pub struct ConfigTableQuery { + pub name: Option +} + #[derive(Unique, Serialize, Deserialize)] pub struct ConfigTable { pub server: ConfigTableServer, pub world: ConfigTableWorld, + pub query: ConfigTableQuery, } pub fn read_config( diff --git a/kubi-server/src/main.rs b/kubi-server/src/main.rs index ad24e18..3878e8e 100644 --- a/kubi-server/src/main.rs +++ b/kubi-server/src/main.rs @@ -1,23 +1,25 @@ use shipyard::{World, Workload, IntoWorkload}; use std::{thread, time::Duration}; -pub(crate) mod util; -pub(crate) mod config; -pub(crate) mod server; -pub(crate) mod client; -//pub(crate) mod world; -pub(crate) mod auth; +mod util; +mod config; +mod server; +mod client; +mod world; +mod auth; use config::read_config; use server::{bind_server, update_server, log_server_errors}; +use client::init_client_maps; 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_client_maps, + init_world, ).into_workload() } @@ -27,7 +29,7 @@ fn update() -> Workload { ( log_server_errors, authenticate_players, - //update_world, + update_world, ).into_workload() ).into_sequential_workload() } diff --git a/kubi-server/src/world.rs b/kubi-server/src/world.rs index 4124e7b..d261b4e 100644 --- a/kubi-server/src/world.rs +++ b/kubi-server/src/world.rs @@ -1,11 +1,19 @@ -use shipyard::{Unique, UniqueView, UniqueViewMut, Workload, IntoWorkload, AllStoragesView}; +use shipyard::{Unique, UniqueView, UniqueViewMut, Workload, IntoWorkload, AllStoragesView, View, Get, NonSendSync}; use glam::IVec3; use hashbrown::HashMap; -use kubi_shared::networking::messages::{ClientToServerMessage, ServerToClientMessage}; +use kubi_shared::networking::{ + messages::{ClientToServerMessage, ServerToClientMessage, C_CHUNK_SUB_REQUEST}, + channels::CHANNEL_WORLD, + client::Client, +}; +use uflow::{ + server::Event as ServerEvent, + SendMode +}; use crate::{ - server::{UdpServer, ServerEvents}, + server::{UdpServer, ServerEvents, IsMessageOfType}, config::ConfigTable, - util::log_error, + client::{ClientAddress, ClientIdMap, ClientAddressMap}, }; pub mod chunk; @@ -26,51 +34,77 @@ impl ChunkManager { } fn process_chunk_requests( - mut server: UniqueViewMut, + mut server: NonSendSync>, events: UniqueView, mut chunk_manager: UniqueViewMut, task_manager: UniqueView, - config: UniqueView + config: UniqueView, + addr_map: UniqueView, + clients: View ) { for event in &events.0 { - if let ServerEvent::MessageReceived { - from: client_id, - message: ClientToServerMessage::ChunkSubRequest { - chunk: chunk_position - } - } = event { - let chunk_position = IVec3::from_array(*chunk_position); - if let Some(chunk) = chunk_manager.chunks.get_mut(&chunk_position) { - chunk.subscriptions.insert(*client_id); - //TODO Start task here if status is "Nothing" - if let Some(blocks) = &chunk.blocks { - server.0.send_message(*client_id, kubi_shared::networking::messages::ServerToClientMessage::ChunkResponse { - chunk: chunk_position.to_array(), + let ServerEvent::Receive(client_addr, data) = event else{ + continue + }; + if !event.is_message_of_type::() { + continue + } + let Some(client) = server.0.client(client_addr) else { + log::error!("Client doesn't exist"); + continue + }; + let Some(&entity_id) = addr_map.0.get(client_addr) else { + log::error!("Client not authenticated"); + continue + }; + let Ok(&Client(client_id)) = (&clients).get(entity_id) else { + log::error!("Entity ID is invalid"); + continue + }; + let Ok(parsed_message) = postcard::from_bytes(data) else { + log::error!("Malformed message"); + continue + }; + let ClientToServerMessage::ChunkSubRequest { chunk: chunk_position } = parsed_message else { + unreachable!() + }; + + if let Some(chunk) = chunk_manager.chunks.get_mut(&chunk_position) { + chunk.subscriptions.insert(client_id); + //TODO Start task here if status is "Nothing" + if let Some(blocks) = &chunk.blocks { + client.borrow_mut().send( + postcard::to_allocvec(&ServerToClientMessage::ChunkResponse { + chunk: chunk_position, data: blocks.clone(), queued: Vec::with_capacity(0) - }).map_err(log_error).ok(); - } - } else { - let mut chunk = Chunk::new(chunk_position); - chunk.state = ChunkState::Loading; - chunk.subscriptions.insert(*client_id); - chunk_manager.chunks.insert(chunk_position, chunk); - task_manager.spawn_task(ChunkTask::LoadChunk { - position: chunk_position, - seed: config.world.seed, - }); + }).unwrap().into_boxed_slice(), + CHANNEL_WORLD, + SendMode::Reliable, + ); } + } else { + let mut chunk = Chunk::new(chunk_position); + chunk.state = ChunkState::Loading; + chunk.subscriptions.insert(client_id); + chunk_manager.chunks.insert(chunk_position, chunk); + task_manager.spawn_task(ChunkTask::LoadChunk { + position: chunk_position, + seed: config.world.seed, + }); } } } fn process_finished_tasks( - mut server: UniqueViewMut, + mut server: NonSendSync>, task_manager: UniqueView, mut chunk_manager: UniqueViewMut, + id_map: UniqueView, + client_addr: View, ) { let mut limit: usize = 8; - while let Some(res) = task_manager.receive() { + 'outer: while let Some(res) = task_manager.receive() { let ChunkTaskResponse::ChunkLoaded { chunk_position, blocks, queue } = res; let Some(chunk) = chunk_manager.chunks.get_mut(&chunk_position) else { log::warn!("Chunk discarded: Doesn't exist"); @@ -82,18 +116,29 @@ fn process_finished_tasks( } chunk.state = ChunkState::Loaded; chunk.blocks = Some(blocks.clone()); - for &subscriber in &chunk.subscriptions { - server.0.send_message(subscriber, ServerToClientMessage::ChunkResponse { - chunk: chunk_position.to_array(), - data: blocks.clone(), - queued: queue - }).map_err(log_error).ok(); - } log::debug!("Chunk {chunk_position} loaded, {} subs", chunk.subscriptions.len()); - //HACK: Implement proper flow control/reliable transport in kubi-udp - limit -= 1; - if limit == 0 { - break; + for &subscriber in &chunk.subscriptions { + let Some(&entity_id) = id_map.0.get(&subscriber) else { + log::error!("Invalid subscriber client id"); + continue 'outer; + }; + let Ok(&ClientAddress(client_addr)) = (&client_addr).get(entity_id) else { + log::error!("Invalid subscriber entity id"); + continue 'outer; + }; + let Some(client) = server.0.client(&client_addr) else { + log::error!("Client not connected"); + continue 'outer; + }; + client.borrow_mut().send( + postcard::to_allocvec(&ServerToClientMessage::ChunkResponse { + chunk: chunk_position, + data: blocks.clone(), + queued: queue.clone() + }).unwrap().into_boxed_slice(), + CHANNEL_WORLD, + SendMode::Reliable, + ); } } } diff --git a/kubi-shared/src/networking.rs b/kubi-shared/src/networking.rs index 05bff68..8b2a776 100644 --- a/kubi-shared/src/networking.rs +++ b/kubi-shared/src/networking.rs @@ -1,3 +1,4 @@ pub mod messages; pub mod state; pub mod client; +pub mod channels; diff --git a/kubi-shared/src/networking/channels.rs b/kubi-shared/src/networking/channels.rs new file mode 100644 index 0000000..62182af --- /dev/null +++ b/kubi-shared/src/networking/channels.rs @@ -0,0 +1,3 @@ +pub const CHANNEL_GENERIC: usize = 0; +pub const CHANNEL_AUTH: usize = 1; +pub const CHANNEL_WORLD: usize = 2; diff --git a/kubi-shared/src/networking/client.rs b/kubi-shared/src/networking/client.rs index 35f3744..39e42c7 100644 --- a/kubi-shared/src/networking/client.rs +++ b/kubi-shared/src/networking/client.rs @@ -1,3 +1,7 @@ +use shipyard::Component; + pub type ClientId = u16; pub type ClientKey = u16; +#[derive(Component, Clone, Copy, Debug)] +pub struct Client(pub ClientId); diff --git a/kubi-shared/src/networking/messages.rs b/kubi-shared/src/networking/messages.rs index 9213547..35dbbea 100644 --- a/kubi-shared/src/networking/messages.rs +++ b/kubi-shared/src/networking/messages.rs @@ -1,10 +1,7 @@ -use std::num::NonZeroUsize; +use glam::{Vec3, IVec3, Quat}; use serde::{Serialize, Deserialize}; use crate::{chunk::BlockData, queue::QueuedBlock}; - -pub type IVec3Arr = [i32; 3]; -pub type Vec3Arr = [f32; 3]; -pub type QuatArr = [f32; 3]; +use super::client::ClientId; pub const PROTOCOL_ID: u16 = 2; @@ -20,12 +17,12 @@ pub enum ClientToServerMessage { password: Option, } = C_CLIENT_HELLO, PositionChanged { - position: Vec3Arr, - velocity: Vec3Arr, - direction: QuatArr, + position: Vec3, + velocity: Vec3, + direction: Quat, } = C_POSITION_CHANGED, ChunkSubRequest { - chunk: IVec3Arr, + chunk: IVec3, } = C_CHUNK_SUB_REQUEST, } @@ -45,11 +42,11 @@ pub enum ServerToClientMessage { } = S_SERVER_FUCK_OFF, PlayerPositionChanged { client_id: u8, - position: Vec3Arr, - direction: QuatArr, + position: Vec3, + direction: Quat, } = S_PLAYER_POSITION_CHANGED, ChunkResponse { - chunk: IVec3Arr, + chunk: IVec3, data: BlockData, queued: Vec, } = S_CHUNK_RESPONSE, @@ -59,11 +56,11 @@ pub enum ServerToClientMessage { #[derive(Serialize, Deserialize, Clone)] pub struct UserInitData { - pub client_id: NonZeroUsize, //maybe use the proper type instead + pub client_id: ClientId, pub username: String, - pub position: Vec3Arr, - pub velocity: Vec3Arr, - pub direction: QuatArr, + pub position: Vec3, + pub velocity: Vec3, + pub direction: Quat, } #[derive(Serialize, Deserialize, Clone)] diff --git a/kubi/src/networking.rs b/kubi/src/networking.rs index 965e34f..9784e09 100644 --- a/kubi/src/networking.rs +++ b/kubi/src/networking.rs @@ -4,7 +4,8 @@ use std::net::SocketAddr; use uflow::client::{Client, Config as ClientConfig, Event as ClientEvent}; use kubi_shared::networking::{ messages::{ClientToServerMessage, ServerToClientMessage, S_SERVER_HELLO}, - state::ClientJoinState + state::ClientJoinState, + channels::CHANNEL_AUTH, }; use crate::{events::EventComponent, control_flow::SetControlFlow}; @@ -77,7 +78,7 @@ fn say_hello( password: None } ).unwrap().into_boxed_slice(), - 0, + CHANNEL_AUTH, uflow::SendMode::Reliable ); } diff --git a/kubi/src/world/loading.rs b/kubi/src/world/loading.rs index 05332a8..0441649 100644 --- a/kubi/src/world/loading.rs +++ b/kubi/src/world/loading.rs @@ -138,7 +138,7 @@ fn start_required_tasks( if let Some(client) = &mut udp_client { client.0.send( postcard::to_allocvec(&ClientToServerMessage::ChunkSubRequest { - chunk: position.to_array() + chunk: position, }).unwrap().into_boxed_slice(), 0, SendMode::Reliable diff --git a/kubi/src/world/tasks.rs b/kubi/src/world/tasks.rs index 3a50ea4..eeae5c2 100644 --- a/kubi/src/world/tasks.rs +++ b/kubi/src/world/tasks.rs @@ -89,7 +89,7 @@ pub fn inject_network_responses_into_manager_queue( chunk, data, queued } = postcard::from_bytes(data).expect("Chunk decode failed") else { unreachable!() }; manager.add_sussy_response(ChunkTaskResponse::LoadedChunk { - position: IVec3::from_array(chunk), + position: chunk, chunk_data: data, queued });