Compare commits

...

12 commits

Author SHA1 Message Date
griffi-gh 772a8ea7db add logging to blk queue 2024-04-25 19:14:56 +02:00
griffi-gh 6f84d9014a implement local server queue 2024-04-25 19:13:05 +02:00
griffi-gh 6ee282e744 fix missing module 2024-04-25 18:39:18 +02:00
griffi-gh 5d8906cfb1 upgrade deps 2024-04-25 18:29:34 +02:00
griffi-gh 8c4ef7f83f downgrade back to rand 0.8 from alpha 2024-04-25 18:28:14 +02:00
griffi-gh b8c7dcc196 revert back to yellow 2024-04-25 17:52:42 +02:00
griffi-gh dc1a641887 Make leave messages red 2024-04-25 17:50:09 +02:00
griffi-gh e373aa758c rename ChatManager to ChatHistory 2024-04-25 17:49:12 +02:00
griffi-gh 907a5845fa randomize usernames 2024-04-25 17:48:39 +02:00
griffi-gh 740da98cbd stuff 2024-04-25 15:16:31 +02:00
griffi-gh af3c938a03 uwu 2024-04-25 13:41:50 +02:00
griffi-gh 50cc36e3d5 add chat 2024-04-25 13:39:23 +02:00
10 changed files with 525 additions and 318 deletions

489
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,14 @@
use shipyard::{Unique, UniqueView, UniqueViewMut, Workload, IntoWorkload, AllStoragesView, View, Get, NonSendSync, IntoIter}; use shipyard::{Unique, UniqueView, UniqueViewMut, Workload, IntoWorkload, AllStoragesView, View, Get, NonSendSync, IntoIter};
use glam::IVec3; use glam::IVec3;
use hashbrown::HashMap; use hashbrown::HashMap;
use kubi_shared::networking::{ use kubi_shared::{
channels::Channel, client::{Client, ClientId}, messages::{ClientToServerMessage, ClientToServerMessageType, ServerToClientMessage} chunk::CHUNK_SIZE,
queue::QueuedBlock,
networking::{
channels::Channel,
client::{Client, ClientId},
messages::{ClientToServerMessage, ClientToServerMessageType, ServerToClientMessage}
},
}; };
use uflow::{server::RemoteClient, SendMode}; use uflow::{server::RemoteClient, SendMode};
use lz4_flex::compress_prepend_size as lz4_compress; use lz4_flex::compress_prepend_size as lz4_compress;
@ -21,7 +27,15 @@ pub mod tasks;
use chunk::Chunk; use chunk::Chunk;
use self::{tasks::{ChunkTaskManager, ChunkTask, ChunkTaskResponse, init_chunk_task_manager}, chunk::ChunkState}; use self::{
tasks::{ChunkTaskManager, ChunkTask, ChunkTaskResponse, init_chunk_task_manager},
chunk::ChunkState
};
#[derive(Unique, Default)]
pub struct LocalBlockQueue {
pub queue: Vec<QueuedBlock>,
}
#[derive(Unique, Default)] #[derive(Unique, Default)]
pub struct ChunkManager { pub struct ChunkManager {
@ -106,6 +120,7 @@ fn process_finished_tasks(
mut chunk_manager: UniqueViewMut<ChunkManager>, mut chunk_manager: UniqueViewMut<ChunkManager>,
id_map: UniqueView<ClientIdMap>, id_map: UniqueView<ClientIdMap>,
client_addr: View<ClientAddress>, client_addr: View<ClientAddress>,
mut local_queue: UniqueViewMut<LocalBlockQueue>,
) { ) {
'outer: while let Some(res) = task_manager.receive() { 'outer: while let Some(res) = task_manager.receive() {
let ChunkTaskResponse::ChunkLoaded { chunk_position, blocks, queue } = res; let ChunkTaskResponse::ChunkLoaded { chunk_position, blocks, queue } = res;
@ -120,12 +135,14 @@ fn process_finished_tasks(
chunk.state = ChunkState::Loaded; chunk.state = ChunkState::Loaded;
chunk.blocks = Some(blocks.clone()); chunk.blocks = Some(blocks.clone());
local_queue.queue.extend_from_slice(&queue);
log::debug!("Chunk {chunk_position} loaded, {} subs", chunk.subscriptions.len()); log::debug!("Chunk {chunk_position} loaded, {} subs", chunk.subscriptions.len());
let chunk_packet = &ServerToClientMessage::ChunkResponse { let chunk_packet = &ServerToClientMessage::ChunkResponse {
chunk: chunk_position, chunk: chunk_position,
data: blocks, data: blocks,
queued: queue queued: queue //should this be here?
}; };
for &subscriber in &chunk.subscriptions { for &subscriber in &chunk.subscriptions {
@ -157,6 +174,7 @@ fn process_block_queue_messages(
addr_map: UniqueView<ClientAddressMap>, addr_map: UniqueView<ClientAddressMap>,
clients: View<Client>, clients: View<Client>,
addrs: View<ClientAddress>, addrs: View<ClientAddress>,
mut queue: UniqueViewMut<LocalBlockQueue>,
) { ) {
for event in &events.0 { for event in &events.0 {
let Some(message) = check_message_auth let Some(message) = check_message_auth
@ -164,7 +182,10 @@ fn process_block_queue_messages(
(&server, event, &clients, &addr_map) else { continue }; (&server, event, &clients, &addr_map) else { continue };
let ClientToServerMessage::QueueBlock { item } = message.message else { unreachable!() }; let ClientToServerMessage::QueueBlock { item } = message.message else { unreachable!() };
//TODO place in our own queue, for now just send to other clients
//place in our local world
queue.queue.push(item);
log::info!("Placed block {:?} at {}", item.block_type, item.position); log::info!("Placed block {:?} at {}", item.block_type, item.position);
for (other_client, other_client_address) in (&clients, &addrs).iter() { for (other_client, other_client_address) in (&clients, &addrs).iter() {
//No need to send the event back //No need to send the event back
@ -188,23 +209,48 @@ fn process_block_queue_messages(
} }
} }
fn init_chunk_manager( fn process_block_queue(
mut chunk_manager: UniqueViewMut<ChunkManager>,
mut queue: UniqueViewMut<LocalBlockQueue>,
) {
let initial_len = queue.queue.len();
queue.queue.retain(|item| {
let chunk_position = item.position.div_euclid(IVec3::splat(CHUNK_SIZE as i32));
let block_position = item.position.rem_euclid(IVec3::splat(CHUNK_SIZE as i32));
let Some(chunk) = chunk_manager.chunks.get_mut(&chunk_position) else {
return true
};
let Some(blocks) = &mut chunk.blocks else {
return true
};
blocks[block_position.x as usize][block_position.y as usize][block_position.z as usize] = item.block_type;
false
});
if initial_len != queue.queue.len() {
log::debug!("queue processed {}/{} items", initial_len - queue.queue.len(), initial_len);
}
}
/// init local block queue and chunk manager
fn init_chunk_manager_and_block_queue(
storages: AllStoragesView storages: AllStoragesView
) { ) {
storages.add_unique(ChunkManager::new()); storages.add_unique(ChunkManager::new());
storages.add_unique(LocalBlockQueue::default());
} }
pub fn init_world() -> Workload { pub fn init_world() -> Workload {
( (
init_chunk_manager, init_chunk_manager_and_block_queue,
init_chunk_task_manager, init_chunk_task_manager,
).into_workload() ).into_workload()
} }
pub fn update_world() -> Workload { pub fn update_world() -> Workload {
( (
process_chunk_requests,
process_finished_tasks, process_finished_tasks,
process_block_queue_messages, process_block_queue_messages,
).into_workload() process_block_queue,
process_chunk_requests,
).into_sequential_workload()
} }

View file

@ -20,7 +20,7 @@ winit = { version = "0.29", features = ["android-native-activity"] }
glutin-winit = "0.4" glutin-winit = "0.4"
raw-window-handle = "=0.5.*" raw-window-handle = "=0.5.*"
glam = { version = "0.27", features = ["debug-glam-assert", "fast-math"] } glam = { version = "0.27", features = ["debug-glam-assert", "fast-math"] }
image = { version = "0.24", default_features = false, features = ["png"] } image = { version = "0.25", default_features = false, features = ["png"] }
strum = { version = "0.26", features = ["derive"] } strum = { version = "0.26", features = ["derive"] }
hashbrown = "0.14" hashbrown = "0.14"
nohash-hasher = "0.2" nohash-hasher = "0.2"
@ -35,13 +35,14 @@ lz4_flex = { version = "0.11", default-features = false, features = ["std"] }
static_assertions = "1.1" static_assertions = "1.1"
tinyset = "0.4" tinyset = "0.4"
serde_json = { version = "1.0", optional = true } #only used for `generate_visualizer_data` serde_json = { version = "1.0", optional = true } #only used for `generate_visualizer_data`
rand = { version = "0.8", features = ["alloc", "small_rng"]}
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android-activity = "^0.5.2" android-activity = "^0.5.2"
ndk = "0.8" ndk = "0.8"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
winapi = "0.3" winapi = { version = "0.3", features = ["wincon"] }
[features] [features]
default = ["raw-evt"] default = ["raw-evt"]
@ -49,7 +50,7 @@ raw-evt = [] #required for mouse input, but breaks keyboard on android
generate_visualizer_data = ["dep:serde_json", "shipyard/serde1"] generate_visualizer_data = ["dep:serde_json", "shipyard/serde1"]
safe_lz4 = ["lz4_flex/safe-encode", "lz4_flex/safe-decode"] safe_lz4 = ["lz4_flex/safe-encode", "lz4_flex/safe-decode"]
parallel = ["shipyard/parallel"] # causes some serious issues! parallel = ["shipyard/parallel"] # causes some serious issues!
nightly = ["hashbrown/nightly", "glam/core-simd", "static_assertions/nightly", "lz4_flex/nightly", "kubi-shared/nightly"] nightly = ["hashbrown/nightly", "glam/core-simd", "static_assertions/nightly", "lz4_flex/nightly", "kubi-shared/nightly", "rand/nightly"]
#part of wip android support #part of wip android support
[package.metadata.android] [package.metadata.android]

View file

@ -43,6 +43,7 @@ fn need_perspective_calc(
pub fn update_matrices() -> Workload { pub fn update_matrices() -> Workload {
( (
update_view_matrix, update_view_matrix,
//update_perspective_matrix,
update_perspective_matrix.run_if(need_perspective_calc), update_perspective_matrix.run_if(need_perspective_calc),
).into_sequential_workload() ).into_sequential_workload()
} }

64
kubi/src/chat.rs Normal file
View file

@ -0,0 +1,64 @@
use kubi_shared::networking::client::ClientId;
use shipyard::{AllStoragesView, Unique, UniqueViewMut};
pub enum ChatMessage {
PlayerMessage {
id: ClientId,
username: String,
message: String,
},
PlayerJoin {
id: ClientId,
username: String,
},
PlayerLeave {
id: ClientId,
username: String,
},
System(String),
}
// impl ChatMessage {
// pub fn render() -> String {
// todo!() //TODO
// }
// }
#[derive(Unique, Default)]
pub struct ChatHistory {
pub messages: Vec<ChatMessage>,
}
impl ChatHistory {
pub fn add_message(&mut self, message: ChatMessage) {
self.messages.push(message);
}
pub fn add_chat_message(&mut self, id: ClientId, username: String, message: String,) {
self.messages.push(ChatMessage::PlayerMessage { id, username, message });
}
pub fn add_player_join(&mut self, id: ClientId, username: String) {
self.messages.push(ChatMessage::PlayerJoin { id, username });
}
pub fn add_player_leave(&mut self, id: ClientId, username: String) {
self.messages.push(ChatMessage::PlayerLeave { id, username });
}
pub fn add_system_message(&mut self, message: String) {
self.messages.push(ChatMessage::System(message));
}
pub fn get_messages(&self) -> &[ChatMessage] {
&self.messages[..]
}
}
pub fn init_chat_manager(
storages: AllStoragesView,
) {
let mut chat_manager = ChatHistory::default();
chat_manager.add_system_message("Welcome to Kubi! Chat messages will appear here".to_string());
storages.add_unique(chat_manager);
}

View file

@ -1,6 +1,10 @@
use shipyard::{AllStoragesView, Unique, NonSendSync, UniqueView, UniqueViewMut}; use shipyard::{AllStoragesView, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
use crate::rendering::Renderer; use crate::{events::InputDeviceEvent, rendering::Renderer};
use winit::window::CursorGrabMode; use winit::{
event::{DeviceEvent, ElementState, RawKeyEvent},
keyboard::{KeyCode, PhysicalKey},
window::CursorGrabMode
};
#[derive(Unique)] #[derive(Unique)]
pub struct CursorLock(pub bool); pub struct CursorLock(pub bool);
@ -34,3 +38,18 @@ pub fn lock_cursor_now(
) { ) {
lock.0 = true lock.0 = true
} }
/// XXX: this is a huge hack
pub fn debug_toggle_lock(
mut lock: UniqueViewMut<CursorLock>,
device_events: View<InputDeviceEvent>,
) {
for evt in device_events.iter() {
if let DeviceEvent::Key(RawKeyEvent {
physical_key: PhysicalKey::Code(KeyCode::F3),
state: ElementState::Pressed,
}) = evt.event {
lock.0 = !lock.0;
}
}
}

View file

@ -18,8 +18,9 @@ pub(crate) use kubi_shared::transform;
mod ui { mod ui {
pub(crate) mod loading_screen; pub(crate) mod loading_screen;
pub(crate) mod connecting_screen; pub(crate) mod connecting_screen;
pub(crate) mod chat_ui;
} }
pub(crate) use ui::{loading_screen, connecting_screen}; pub(crate) use ui::{loading_screen, connecting_screen, chat_ui};
pub(crate) mod rendering; pub(crate) mod rendering;
pub(crate) mod world; pub(crate) mod world;
@ -42,6 +43,7 @@ pub(crate) mod color;
pub(crate) mod fixed_timestamp; pub(crate) mod fixed_timestamp;
pub(crate) mod filesystem; pub(crate) mod filesystem;
pub(crate) mod client_physics; pub(crate) mod client_physics;
pub(crate) mod chat;
use world::{ use world::{
init_game_world, init_game_world,
@ -76,7 +78,7 @@ use rendering::{
}; };
use block_placement::update_block_placement; use block_placement::update_block_placement;
use delta_time::{DeltaTime, init_delta_time}; use delta_time::{DeltaTime, init_delta_time};
use cursor_lock::{insert_lock_state, update_cursor_lock_state, lock_cursor_now}; use cursor_lock::{debug_toggle_lock, insert_lock_state, lock_cursor_now, update_cursor_lock_state};
use control_flow::{exit_on_esc, insert_control_flow_unique, RequestExit}; use control_flow::{exit_on_esc, insert_control_flow_unique, RequestExit};
use state::{is_ingame, is_ingame_or_loading, is_loading, init_state, update_state, is_connecting}; use state::{is_ingame, is_ingame_or_loading, is_loading, init_state, update_state, is_connecting};
use networking::{update_networking, update_networking_late, is_multiplayer, disconnect_on_exit, is_singleplayer}; use networking::{update_networking, update_networking_late, is_multiplayer, disconnect_on_exit, is_singleplayer};
@ -87,6 +89,8 @@ use connecting_screen::update_connecting_screen;
use fixed_timestamp::init_fixed_timestamp_storage; use fixed_timestamp::init_fixed_timestamp_storage;
use filesystem::AssetManager; use filesystem::AssetManager;
use client_physics::{init_client_physics, update_client_physics_late}; use client_physics::{init_client_physics, update_client_physics_late};
use chat_ui::render_chat;
use chat::init_chat_manager;
/// stuff required to init the renderer and other basic systems /// stuff required to init the renderer and other basic systems
fn pre_startup() -> Workload { fn pre_startup() -> Workload {
@ -111,11 +115,13 @@ fn startup() -> Workload {
insert_control_flow_unique, insert_control_flow_unique,
init_delta_time, init_delta_time,
init_client_physics, init_client_physics,
init_chat_manager,
).into_sequential_workload() ).into_sequential_workload()
} }
fn update() -> Workload { fn update() -> Workload {
( (
debug_toggle_lock,
update_window_size, update_window_size,
update_cursor_lock_state, update_cursor_lock_state,
process_inputs, process_inputs,
@ -144,6 +150,7 @@ fn update() -> Workload {
update_raycasts, update_raycasts,
update_block_placement, update_block_placement,
apply_queued_blocks, apply_queued_blocks,
render_chat,
).into_sequential_workload().run_if(is_ingame), ).into_sequential_workload().run_if(is_ingame),
update_networking_late.run_if(is_multiplayer), update_networking_late.run_if(is_multiplayer),
compute_cameras, compute_cameras,

View file

@ -5,9 +5,21 @@ use kubi_shared::networking::{
state::ClientJoinState, state::ClientJoinState,
channels::Channel, channels::Channel,
}; };
use crate::player::{spawn_local_player_multiplayer, spawn_remote_player_multiplayer}; use rand::prelude::*;
use crate::{chat::ChatHistory, player::{spawn_local_player_multiplayer, spawn_remote_player_multiplayer}};
use super::{UdpClient, NetworkEvent}; use super::{UdpClient, NetworkEvent};
const USERNAME_BANK: &[&str] = &[
"XxX-FishFucker-69420",
"Sbeve34",
"ShadowBladeX",
"CyberNinja92",
"sputnik1",
"dumbpotato",
"FortNiteNinja",
"MinecraftMiner",
];
#[derive(Unique)] #[derive(Unique)]
pub struct ConnectionRejectionReason { pub struct ConnectionRejectionReason {
pub reason: String, pub reason: String,
@ -23,7 +35,8 @@ pub fn set_client_join_state_to_connected(
pub fn say_hello( pub fn say_hello(
mut client: UniqueViewMut<UdpClient>, mut client: UniqueViewMut<UdpClient>,
) { ) {
let username = "XxX-FishFucker-69420-XxX".into(); let mut rng = thread_rng();
let username = (*USERNAME_BANK.choose(&mut rng).unwrap()).to_owned();
let password = None; let password = None;
log::info!("Authenticating"); log::info!("Authenticating");
client.0.send( client.0.send(
@ -65,6 +78,9 @@ pub fn check_server_hello_response(
// health: Health, // health: Health,
// } // }
let client_id = init.user.client_id;
let username = init.user.username.clone();
//Add components to main player //Add components to main player
spawn_local_player_multiplayer(&mut storages, init.user); spawn_local_player_multiplayer(&mut storages, init.user);
@ -78,6 +94,10 @@ pub fn check_server_hello_response(
*join_state = ClientJoinState::Joined; *join_state = ClientJoinState::Joined;
log::info!("Joined the server!"); log::info!("Joined the server!");
// Send chat message
let mut chat = storages.borrow::<UniqueViewMut<ChatHistory>>().unwrap();
chat.add_player_join(client_id, username);
} }
pub fn check_server_fuck_off_response( pub fn check_server_fuck_off_response(

View file

@ -4,12 +4,13 @@ use uflow::{SendMode, client::Event as ClientEvent};
use kubi_shared::{ use kubi_shared::{
transform::Transform, transform::Transform,
networking::{ networking::{
messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType},
channels::Channel, channels::Channel,
client::ClientIdMap, client::{ClientIdMap, Username},
messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType},
}, },
}; };
use crate::{ use crate::{
chat::ChatHistory,
events::player_actions::PlayerActionEvent, events::player_actions::PlayerActionEvent,
player::spawn_remote_player_multiplayer, player::spawn_remote_player_multiplayer,
}; };
@ -96,6 +97,9 @@ pub fn receive_player_connect_events(
for message in messages { for message in messages {
let ServerToClientMessage::PlayerConnected { init } = message else { unreachable!() }; let ServerToClientMessage::PlayerConnected { init } = message else { unreachable!() };
log::info!("player connected: {} (id {})", init.username, init.client_id); log::info!("player connected: {} (id {})", init.username, init.client_id);
let mut chat = storages.borrow::<UniqueViewMut<ChatHistory>>().unwrap();
chat.add_player_join(init.client_id, init.username.clone());
drop(chat);
spawn_remote_player_multiplayer(&mut storages, init); spawn_remote_player_multiplayer(&mut storages, init);
} }
} }
@ -120,12 +124,21 @@ pub fn receive_player_disconnect_events(
for message in messages { for message in messages {
let ServerToClientMessage::PlayerDisconnected { id } = message else { unreachable!() }; let ServerToClientMessage::PlayerDisconnected { id } = message else { unreachable!() };
log::info!("player disconnected: {}", id); log::info!("player disconnected: {}", id);
let mut id_map = storages.borrow::<UniqueViewMut<ClientIdMap>>().unwrap(); let mut id_map = storages.borrow::<UniqueViewMut<ClientIdMap>>().unwrap();
let Some(ent_id) = id_map.0.remove(&id) else { let Some(ent_id) = id_map.0.remove(&id) else {
log::warn!("Disconnected player entity not found in client-id map"); log::warn!("Disconnected player entity not found in client-id map");
continue continue
}; };
let username = storages.get::<&Username>(ent_id).unwrap();
let mut chat = storages.borrow::<UniqueViewMut<ChatHistory>>().unwrap();
chat.add_player_leave(id, username.0.to_string());
drop(chat);
drop(id_map); drop(id_map);
drop(username);
if !storages.delete_entity(ent_id) { if !storages.delete_entity(ent_id) {
log::warn!("Disconnected player entity not found in storage"); log::warn!("Disconnected player entity not found in storage");
} }

43
kubi/src/ui/chat_ui.rs Normal file
View file

@ -0,0 +1,43 @@
use hui::{color, element::{container::Container, text::Text, UiElementExt}, layout::Alignment, size};
use shipyard::{NonSendSync, UniqueView, UniqueViewMut};
use crate::{chat::{ChatHistory, ChatMessage}, hui_integration::UiState, rendering::WindowSize};
pub fn render_chat(
mut hui: NonSendSync<UniqueViewMut<UiState>>,
size: UniqueView<WindowSize>,
chat: UniqueView<ChatHistory>,
) {
let messages = chat.get_messages();
if messages.is_empty() { return }
Container::default()
.with_size(size!(100%, 100%))
.with_align((Alignment::Begin, Alignment::End))
.with_children(|ui| {
for message in messages.iter().rev().take(10).rev() {
let (text, color) = match message {
ChatMessage::PlayerMessage { username, id, message } => {
(format!("{username} ({id}): {message}"), color::CYAN)
}
ChatMessage::PlayerJoin { username, id } => {
(format!("{username} ({id}) joined the game"), color::YELLOW)
}
ChatMessage::PlayerLeave { username, id } => {
(format!("{username} ({id}) left the game"), color::YELLOW)
}
ChatMessage::System(message) => {
(message.clone(), color::WHITE)
}
};
Container::default()
.with_background((0., 0., 0., 0.5))
.with_padding((5., 2.))
.with_children(|ui| {
Text::new(text)
.with_color(color)
.add_child(ui)
})
.add_child(ui);
}
})
.add_root(&mut hui.hui, size.0.as_vec2());
}