Big refactor + Early wip position sync /connect events in multiplayer

This commit is contained in:
griffi-gh 2023-05-19 07:02:20 +02:00
parent c0ce1c71d9
commit 163298fe38
14 changed files with 309 additions and 69 deletions

View file

@ -1,4 +1,5 @@
use shipyard::{UniqueView, NonSendSync, EntitiesViewMut, ViewMut, UniqueViewMut, AllStoragesView};
use glam::{Vec3, Mat4, vec3};
use shipyard::{UniqueView, NonSendSync, EntitiesViewMut, ViewMut, UniqueViewMut, AllStoragesView, IntoIter};
use uflow::{server::Event as ServerEvent, SendMode};
use kubi_shared::{
networking::{
@ -6,9 +7,11 @@ use kubi_shared::{
ClientToServerMessage,
ServerToClientMessage,
InitData,
C_CLIENT_HELLO
ClientInitData,
C_CLIENT_HELLO,
},
client::{Client, ClientId}, channels::CHANNEL_AUTH
client::{Client, ClientId, Username},
channels::{CHANNEL_AUTH, CHANNEL_SYS_EVT},
},
player::{Player, PLAYER_HEALTH},
transform::Transform, entity::{Entity, Health}
@ -99,13 +102,15 @@ pub fn authenticate_players(
&mut storages.borrow::<ViewMut<Client>>().unwrap(),
&mut storages.borrow::<ViewMut<ClientAddress>>().unwrap(),
&mut storages.borrow::<ViewMut<Transform>>().unwrap(),
&mut storages.borrow::<ViewMut<Username>>().unwrap(),
), (
Entity,
Player,
Health::new(PLAYER_HEALTH),
Client(client_id),
ClientAddress(*client_addr),
Transform::default(),
Transform(Mat4::from_translation(vec3(0., 60., 0.))),
Username(username.clone()),
))
};
@ -113,12 +118,60 @@ pub fn authenticate_players(
client_entity_map.0.insert(client_id, entity_id);
client_addr_map.0.insert(*client_addr, entity_id);
//Approve the user
//Create init data
let init_data = {
let mut user = None;
let mut users = Vec::with_capacity(client_entity_map.0.len() - 1);
for (client, username, transform, &health) in (
&storages.borrow::<ViewMut<Client>>().unwrap(),
&storages.borrow::<ViewMut<Username>>().unwrap(),
&storages.borrow::<ViewMut<Transform>>().unwrap(),
&storages.borrow::<ViewMut<Health>>().unwrap(),
).iter() {
let (_, direction, position) = transform.0.to_scale_rotation_translation();
let idata = ClientInitData {
client_id: client.0,
username: username.0.clone(),
position,
velocity: Vec3::ZERO,
direction,
health,
};
if client_id == client.0 {
user = Some(idata);
} else {
users.push(idata);
}
}
InitData {
user: user.unwrap(),
users
}
};
//Announce new player to other clients
{
let message = &ServerToClientMessage::PlayerConnected {
init: init_data.user.clone()
};
for (other_client_addr, _) in client_addr_map.0.iter() {
//TODO: ONLY JOINED CLIENTS HERE!
let Some(other_client) = server.0.client(other_client_addr) else {
log::error!("Other client doesn't exist");
continue
};
other_client.borrow_mut().send(
postcard::to_allocvec(&message).unwrap().into_boxed_slice(),
CHANNEL_SYS_EVT,
SendMode::Reliable
);
}
}
//Approve the user and send init data
client.borrow_mut().send(
postcard::to_allocvec(&ServerToClientMessage::ServerHello {
init: InitData {
users: vec![] //TODO create init data
}
init: init_data
}).unwrap().into_boxed_slice(),
CHANNEL_AUTH,
SendMode::Reliable

View file

@ -1,7 +1,12 @@
use shipyard::{Component, EntityId, Unique, AllStoragesView};
use shipyard::{Component, EntityId, Unique, AllStoragesView, UniqueView, NonSendSync, View, Get};
use hashbrown::HashMap;
use std::net::SocketAddr;
pub use kubi_shared::networking::client::ClientIdMap;
use uflow::server::Event as ServerEvent;
use kubi_shared::networking::{
client::{ClientIdMap, Client},
messages::{ClientToServerMessage, C_POSITION_CHANGED}
};
use crate::server::{ServerEvents, IsMessageOfType, UdpServer};
#[derive(Component, Clone, Copy)]
pub struct ClientAddress(pub SocketAddr);
@ -18,3 +23,40 @@ pub fn init_client_maps(
storages.add_unique(ClientIdMap::new());
storages.add_unique(ClientAddressMap::new());
}
pub fn sync_client_positions(
server: NonSendSync<UniqueView<UdpServer>>,
events: UniqueView<ServerEvents>,
addr_map: UniqueView<ClientAddressMap>,
clients: View<Client>,
) {
for event in &events.0 {
let ServerEvent::Receive(client_addr, data) = event else{
continue
};
if !event.is_message_of_type::<C_POSITION_CHANGED>() {
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::PositionChanged { position, velocity, direction } = parsed_message else {
unreachable!()
};
}
}

View file

@ -13,10 +13,11 @@ use uflow::{
use lz4_flex::compress_prepend_size as lz4_compress;
use anyhow::Result;
use std::{rc::Rc, cell::RefCell};
use kubi_shared::networking::client::ClientIdMap;
use crate::{
server::{UdpServer, ServerEvents, IsMessageOfType},
config::ConfigTable,
client::{ClientAddress, ClientIdMap, ClientAddressMap},
client::{ClientAddress, ClientAddressMap},
};
pub mod chunk;

View file

@ -1,9 +1,10 @@
use shipyard::Component;
use serde::{Serialize, Deserialize};
#[derive(Component)]
pub struct Entity;
#[derive(Component)]
#[derive(Component, Serialize, Deserialize, Clone, Copy, Debug)]
pub struct Health {
pub current: u8,
pub max: u8,
@ -16,3 +17,19 @@ impl Health {
}
}
}
impl PartialEq for Health {
fn eq(&self, other: &Self) -> bool {
self.current == other.current
}
}
impl Eq for Health {}
impl PartialOrd for Health {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.current.partial_cmp(&other.current)
}
}
impl Ord for Health {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.current.cmp(&other.current)
}
}

View file

@ -3,3 +3,4 @@ pub const CHANNEL_AUTH: usize = 1;
pub const CHANNEL_WORLD: usize = 2;
pub const CHANNEL_BLOCK: usize = 3;
pub const CHANNEL_MOVE: usize = 4;
pub const CHANNEL_SYS_EVT: usize = 5;

View file

@ -4,16 +4,24 @@ use nohash_hasher::BuildNoHashHasher;
pub type ClientId = u16;
#[derive(Component, Clone, Debug)]
#[repr(transparent)]
pub struct Username(pub String);
#[derive(Component, Clone, Copy, Debug)]
#[repr(transparent)]
pub struct Client(pub ClientId);
#[derive(Unique)]
#[repr(transparent)]
pub struct ClientIdMap(pub HashMap<ClientId, EntityId, BuildNoHashHasher<ClientId>>);
impl ClientIdMap {
pub fn new() -> Self {
Self(HashMap::with_capacity_and_hasher(16, BuildNoHashHasher::default()))
}
}
impl Default for ClientIdMap {
fn default() -> Self {
Self::new()

View file

@ -1,6 +1,6 @@
use glam::{Vec3, IVec3, Quat};
use serde::{Serialize, Deserialize};
use crate::{chunk::BlockData, queue::QueuedBlock};
use crate::{chunk::BlockData, queue::QueuedBlock, entity::Health};
use super::client::ClientId;
//protocol id not used yet
@ -41,6 +41,7 @@ pub const S_PLAYER_POSITION_CHANGED: u8 = 2;
pub const S_CHUNK_RESPONSE: u8 = 3;
pub const S_QUEUE_BLOCK: u8 = 4;
pub const S_PLAYER_CONNECTED: u8 = 5;
pub const S_PLAYER_DISCONNECTED: u8 = 6;
#[derive(Serialize, Deserialize, Clone)]
#[repr(u8)]
@ -52,7 +53,7 @@ pub enum ServerToClientMessage {
reason: String,
} = S_SERVER_FUCK_OFF,
PlayerPositionChanged {
client_id: u8,
client_id: ClientId,
position: Vec3,
direction: Quat,
} = S_PLAYER_POSITION_CHANGED,
@ -69,22 +70,27 @@ pub enum ServerToClientMessage {
item: QueuedBlock
} = S_QUEUE_BLOCK,
PlayerConnected {
init: UserInitData
init: ClientInitData
} = S_PLAYER_CONNECTED,
PlayerDisconnected {
id: ClientId
} = S_PLAYER_DISCONNECTED,
}
//---
#[derive(Serialize, Deserialize, Clone)]
pub struct UserInitData {
pub struct ClientInitData {
pub client_id: ClientId,
pub username: String,
pub position: Vec3,
pub velocity: Vec3,
pub direction: Quat,
pub health: Health,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct InitData {
pub users: Vec<UserInitData>
pub user: ClientInitData,
pub users: Vec<ClientInitData>
}

View file

@ -1,6 +1,11 @@
use shipyard::Component;
use crate::block::Block;
pub const PLAYER_HEALTH: u8 = 20;
#[derive(Component)]
pub struct Player;
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(transparent)]
pub struct PlayerHolding(pub Option<Block>);

View file

@ -2,7 +2,9 @@ use shipyard::Component;
use glam::{Mat4, Mat3};
#[derive(Component, Clone, Copy, Debug, Default)]
#[repr(transparent)]
pub struct Transform(pub Mat4);
#[derive(Component, Clone, Copy, Debug, Default)]
#[repr(transparent)]
pub struct Transform2d(pub Mat3);

View file

@ -2,7 +2,8 @@ use shipyard::{UniqueViewMut, UniqueView, View, IntoIter, ViewMut, EntitiesViewM
use glium::glutin::event::VirtualKeyCode;
use kubi_shared::{
block::Block,
queue::QueuedBlock,
queue::QueuedBlock,
player::PlayerHolding,
};
use crate::{
player::MainPlayer,
@ -11,14 +12,6 @@ use crate::{
events::{EventComponent, player_actions::PlayerActionEvent},
};
#[derive(Component)]
pub struct PlayerHolding(pub Block);
impl Default for PlayerHolding {
fn default() -> Self {
Self(Block::Cobblestone)
}
}
const BLOCK_KEY_MAP: &[(VirtualKeyCode, Block)] = &[
(VirtualKeyCode::Key1, Block::Cobblestone),
(VirtualKeyCode::Key2, Block::Planks),
@ -38,7 +31,7 @@ fn pick_block_with_number_keys(
let Some((_, mut holding)) = (&main_player, &mut holding).iter().next() else { return };
for &(key, block) in BLOCK_KEY_MAP {
if input.keyboard_state.contains(&key) {
holding.0 = block;
holding.0 = Some(block);
return
}
}
@ -63,9 +56,9 @@ fn block_placement_system(
let Some(ray) = ray.0 else { return };
//get coord and block type
let (place_position, place_block) = if action_place {
if block.0 == Block::Air { return }
if block.0.is_none() { return }
let position = (ray.position - ray.direction * (RAYCAST_STEP + 0.001)).floor().as_ivec3();
(position, block.0)
(position, block.0.unwrap())
} else {
(ray.block_position, Block::Air)
};

View file

@ -29,6 +29,8 @@ use world::{
};
use player::send_player_movement_events;
use self::player::{receive_player_movement_events, receive_player_connect_events};
#[derive(Unique, Clone, Copy, PartialEq, Eq)]
pub enum GameType {
Singleplayer,
@ -120,8 +122,14 @@ pub fn update_networking() -> Workload {
handle_disconnect,
).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Connected as u8}>),
(
recv_block_place_events
).run_if(is_join_state::<{ClientJoinState::Joined as u8}>).run_if(is_ingame_or_loading),
(
receive_player_connect_events
),
(
recv_block_place_events,
receive_player_movement_events,
).into_workload()
).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Joined as u8}>).run_if(is_ingame_or_loading),
inject_network_responses_into_manager_queue.run_if(is_ingame_or_loading).skip_if_missing_unique::<ChunkTaskManager>(),
).into_sequential_workload()
}
@ -131,7 +139,7 @@ pub fn update_networking_late() -> Workload {
(
send_block_place_events,
send_player_movement_events,
).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Joined as u8}>),
).into_workload().run_if(is_join_state::<{ClientJoinState::Joined as u8}>),
flush_client,
).into_sequential_workload()
}

View file

@ -1,11 +1,17 @@
use shipyard::{UniqueViewMut, View, IntoIter};
use glam::Mat4;
use shipyard::{UniqueViewMut, View, IntoIter, AllStoragesViewMut, ViewMut, IntoWithId};
use uflow::{client::Event as ClientEvent, SendMode};
use kubi_shared::networking::{
messages::{ClientToServerMessage, ServerToClientMessage, S_SERVER_HELLO},
state::ClientJoinState,
channels::CHANNEL_AUTH,
use kubi_shared::{
networking::{
messages::{ClientToServerMessage, ServerToClientMessage, S_SERVER_HELLO},
state::ClientJoinState,
channels::CHANNEL_AUTH,
client::{Username, Client},
},
transform::Transform, entity::Health
};
use super::{UdpClient, NetworkEvent};
use crate::player::MainPlayer;
use super::{UdpClient, NetworkEvent, player::add_net_player};
pub fn set_client_join_state_to_connected(
mut join_state: UniqueViewMut<ClientJoinState>
@ -16,14 +22,15 @@ pub fn set_client_join_state_to_connected(
pub fn say_hello(
mut client: UniqueViewMut<UdpClient>,
main_player: View<MainPlayer>,
username: View<Username>
) {
let username = (&main_player, &username).iter().next().unwrap().1.0.clone();
let password = None;
log::info!("Authenticating");
client.0.send(
postcard::to_allocvec(
&ClientToServerMessage::ClientHello {
username: "Sbeve".into(),
password: None
}
&ClientToServerMessage::ClientHello { username, password }
).unwrap().into_boxed_slice(),
CHANNEL_AUTH,
SendMode::Reliable
@ -31,26 +38,63 @@ pub fn say_hello(
}
pub fn check_server_hello_response(
network_events: View<NetworkEvent>,
mut join_state: UniqueViewMut<ClientJoinState>
mut storages: AllStoragesViewMut,
) {
for event in network_events.iter() {
//Check if we got the message and extract the init data from it
let Some(init) = storages.borrow::<View<NetworkEvent>>().unwrap().iter().find_map(|event| {
let ClientEvent::Receive(data) = &event.0 else {
continue
return None
};
if !event.is_message_of_type::<S_SERVER_HELLO>() {
continue
return None
}
let Ok(parsed_message) = postcard::from_bytes(data) else {
log::error!("Malformed message");
continue
return None
};
let ServerToClientMessage::ServerHello { init: _ } = parsed_message else {
let ServerToClientMessage::ServerHello { init } = parsed_message else {
unreachable!()
};
//TODO handle init data
*join_state = ClientJoinState::Joined;
log::info!("Joined the server!");
return;
Some(init)
}) else { return };
// struct ClientInitData {
// client_id: ClientId,
// username: String,
// position: Vec3,
// velocity: Vec3,
// direction: Quat,
// health: Health,
// }
//Add components to main player
{
let entity = (&storages.borrow::<View<MainPlayer>>().unwrap()).iter().with_id().next().unwrap().0;
storages.add_component(entity, Client(init.user.client_id));
}
//Modify main player
{
for (entity, (_, mut username, mut transform, mut health)) in (
&storages.borrow::<View<MainPlayer>>().unwrap(),
&mut storages.borrow::<ViewMut<Username>>().unwrap(),
&mut storages.borrow::<ViewMut<Transform>>().unwrap(),
&mut storages.borrow::<ViewMut<Health>>().unwrap(),
).iter().with_id() {
username.0 = init.user.username.clone();
transform.0 = Mat4::from_rotation_translation(init.user.direction, init.user.position);
*health = init.user.health;
}
}
//Init players
for init_data in init.users {
add_net_player(&mut storages, init_data);
}
// Set state to connected
let mut join_state = storages.borrow::<UniqueViewMut<ClientJoinState>>().unwrap();
*join_state = ClientJoinState::Joined;
log::info!("Joined the server!");
}

View file

@ -1,12 +1,15 @@
use glam::Vec3;
use hashbrown::HashMap;
use nohash_hasher::BuildNoHashHasher;
use shipyard::{UniqueViewMut, View, IntoIter, Unique, EntityId, AllStoragesView};
use glam::{Vec3, Mat4};
use shipyard::{UniqueViewMut, View, IntoIter, AllStoragesView, AllStoragesViewMut, UniqueView, ViewMut, Get};
use uflow::{SendMode, client::Event as ClientEvent};
use kubi_shared::networking::{
messages::{ClientToServerMessage, ServerToClientMessage, S_PLAYER_POSITION_CHANGED},
channels::CHANNEL_MOVE,
client::{ClientId, ClientIdMap},
use kubi_shared::{
player::{Player, PlayerHolding},
entity::Entity,
transform::Transform,
networking::{
messages::{ClientToServerMessage, ServerToClientMessage, S_PLAYER_POSITION_CHANGED, ClientInitData},
channels::CHANNEL_MOVE,
client::{ClientIdMap, Username, Client},
},
};
use crate::events::player_actions::PlayerActionEvent;
use super::{UdpClient, NetworkEvent};
@ -17,8 +20,33 @@ pub fn init_client_map(
storages.add_unique(ClientIdMap::new());
}
pub fn add_net_player() {
//TODO
pub fn add_net_player(
storages: &mut AllStoragesViewMut,
init: ClientInitData
) {
// struct ClientInitData {
// client_id: ClientId,
// username: String,
// position: Vec3,
// velocity: Vec3,
// direction: Quat,
// health: Health,
// }
//Spawn player locally
let entity_id = storages.add_entity((
Username(init.username),
Client(init.client_id),
Player,
Entity,
init.health,
Transform(Mat4::from_rotation_translation(init.direction, init.position)),
PlayerHolding::default(),
));
//Add it to the client id map
let mut client_id_map = storages.borrow::<UniqueViewMut<ClientIdMap>>().unwrap();
client_id_map.0.insert(init.client_id, entity_id);
}
pub fn send_player_movement_events(
@ -42,29 +70,60 @@ pub fn send_player_movement_events(
}
pub fn receive_player_movement_events(
mut transforms: ViewMut<Transform>,
network_events: View<NetworkEvent>,
id_map: UniqueView<ClientIdMap>
) {
for event in network_events.iter() {
let ClientEvent::Receive(data) = &event.0 else {
continue
};
if !event.is_message_of_type::<S_PLAYER_POSITION_CHANGED>() {
continue
}
let Ok(parsed_message) = postcard::from_bytes(data) else {
log::error!("Malformed message");
continue
};
let ServerToClientMessage::PlayerPositionChanged {
client_id, position, direction
} = parsed_message else { unreachable!() };
//TODO apply position to local player
let Some(&ent_id) = id_map.0.get(&client_id) else {
log::error!("Not in client-id map");
continue
};
let mut transform = (&mut transforms).get(ent_id)
.expect("invalid player entity id");
transform.0 = Mat4::from_rotation_translation(direction, position);
}
}
pub fn receive_connected_players(
network_events: View<NetworkEvent>,
pub fn receive_player_connect_events(
mut storages: AllStoragesViewMut,
) {
let messages: Vec<ServerToClientMessage> = storages.borrow::<View<NetworkEvent>>().unwrap().iter().filter_map(|event| {
let ClientEvent::Receive(data) = &event.0 else {
return None
};
if !event.is_message_of_type::<S_PLAYER_POSITION_CHANGED>() {
return None
};
let Ok(parsed_message) = postcard::from_bytes(data) else {
log::error!("Malformed message");
return None
};
Some(parsed_message)
}).collect();
for message in messages {
let ServerToClientMessage::PlayerConnected { init } = message else { unreachable!() };
log::info!("player connected: {} (id {})", init.username, init.client_id);
add_net_player(&mut storages, init);
}
}

View file

@ -1,14 +1,14 @@
use shipyard::{Component, AllStoragesViewMut};
use kubi_shared::{
entity::{Entity, Health},
player::PLAYER_HEALTH
player::{PLAYER_HEALTH, PlayerHolding},
networking::client::Username
};
use crate::{
transform::Transform,
camera::Camera,
fly_controller::FlyController,
world::raycast::LookingAtBlock,
block_placement::PlayerHolding,
};
pub use kubi_shared::player::Player;
@ -29,5 +29,6 @@ pub fn spawn_player (
FlyController,
LookingAtBlock::default(),
PlayerHolding::default(),
Username("Sbeve".into())
));
}