This commit is contained in:
griffi-gh 2023-03-07 04:50:06 +01:00
parent ac5f39a49e
commit 5754dc7485
12 changed files with 226 additions and 29 deletions

2
Cargo.lock generated
View file

@ -928,6 +928,7 @@ name = "kubi-server"
version = "0.1.0"
dependencies = [
"anyhow",
"flume",
"glam",
"hashbrown 0.13.2",
"kubi-logging",
@ -935,6 +936,7 @@ dependencies = [
"kubi-udp",
"log",
"nohash-hasher",
"rayon",
"serde",
"shipyard",
"toml",

View file

@ -2,3 +2,6 @@
address = "0.0.0.0:12345"
max_clients = 254
timeout_ms = 10000
[world]
seed = 0xbeef_face_dead_cafe

View file

@ -16,6 +16,8 @@ glam = { version = "0.23", features = ["debug-glam-assert", "fast-math"] }
hashbrown = "0.13"
nohash-hasher = "0.2.0"
anyhow = "1.0"
rayon = "1.6"
flume = "0.10"
[features]
default = []

View file

@ -1,19 +0,0 @@
use glam::IVec3;
use hashbrown::HashMap;
use kubi_shared::chunk::BlockData;
use shipyard::Unique;
pub struct Chunk {
pub blocks: BlockData
}
#[derive(Unique)]
pub struct ChunkManager {
pub chunks: HashMap<IVec3, Chunk>
}
pub fn server_chunk_response(
) {
}

View file

@ -10,9 +10,15 @@ pub struct ConfigTableServer {
pub password: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub struct ConfigTableWorld {
pub seed: u64,
}
#[derive(Unique, Serialize, Deserialize)]
pub struct ConfigTable {
pub server: ConfigTableServer
pub server: ConfigTableServer,
pub world: ConfigTableWorld,
}
pub fn read_config(

View file

@ -1,4 +1,3 @@
use shipyard::{World, Workload, IntoWorkload};
use std::{thread, time::Duration};
@ -6,17 +5,19 @@ pub(crate) mod util;
pub(crate) mod config;
pub(crate) mod server;
pub(crate) mod client;
pub(crate) mod chunk;
pub(crate) mod world;
pub(crate) mod auth;
use config::read_config;
use server::{bind_server, update_server, update_server_events};
use auth::authenticate_players;
use world::{update_world, init_world};
fn initialize() -> Workload {
(
read_config,
bind_server,
init_world,
).into_workload()
}
@ -25,6 +26,7 @@ fn update() -> Workload {
update_server,
update_server_events,
authenticate_players,
update_world,
).into_workload()
}

111
kubi-server/src/world.rs Normal file
View file

@ -0,0 +1,111 @@
use shipyard::{Unique, UniqueView, UniqueViewMut, Workload, IntoWorkload, AllStoragesView};
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,
util::log_error,
};
pub mod chunk;
pub mod tasks;
use chunk::Chunk;
use self::{tasks::{ChunkTaskManager, ChunkTask, ChunkTaskResponse}, chunk::ChunkState};
#[derive(Unique, Default)]
pub struct ChunkManager {
pub chunks: HashMap<IVec3, Chunk>
}
impl ChunkManager {
pub fn new() -> Self {
Self::default()
}
}
fn process_chunk_requests(
mut server: UniqueViewMut<UdpServer>,
events: UniqueView<ServerEvents>,
mut chunk_manager: UniqueViewMut<ChunkManager>,
task_manager: UniqueView<ChunkTaskManager>,
config: UniqueView<ConfigTable>
) {
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(),
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_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<UdpServer>,
task_manager: UniqueView<ChunkTaskManager>,
mut chunk_manager: UniqueViewMut<ChunkManager>,
) {
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");
continue
};
if chunk.state != ChunkState::Loading {
log::warn!("Chunk discarded: Not Loading");
continue
}
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.iter().map(|item| (item.position.to_array(), item.block_type)).collect()
}).map_err(log_error).ok();
}
}
}
fn init_chunk_manager(
storages: AllStoragesView
) {
storages.add_unique(ChunkManager::new());
}
pub fn init_world() -> Workload {
(
init_chunk_manager
).into_workload()
}
pub fn update_world() -> Workload {
(
process_chunk_requests,
process_finished_tasks,
).into_workload()
}

View file

@ -0,0 +1,29 @@
use glam::IVec3;
use hashbrown::HashSet;
use nohash_hasher::BuildNoHashHasher;
use kubi_shared::chunk::BlockData;
use kubi_udp::{ClientId, ClientIdRepr};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChunkState {
Nothing,
Loading,
Loaded,
}
pub struct Chunk {
pub position: IVec3,
pub state: ChunkState,
pub blocks: Option<BlockData>,
pub subscriptions: HashSet<ClientId, BuildNoHashHasher<ClientIdRepr>>,
}
impl Chunk {
pub fn new(position: IVec3) -> Self {
Self {
position,
state: ChunkState::Nothing,
blocks: None,
subscriptions: HashSet::with_hasher(BuildNoHashHasher::default()),
}
}
}

View file

@ -0,0 +1,58 @@
use shipyard::{Unique, AllStoragesView};
use flume::{unbounded, Sender, Receiver};
use glam::IVec3;
use rayon::{ThreadPool, ThreadPoolBuilder};
use anyhow::Result;
use kubi_shared::{
chunk::BlockData,
worldgen::{QueuedBlock, generate_world}
};
pub enum ChunkTask {
LoadChunk {
position: IVec3,
seed: u64,
}
}
pub enum ChunkTaskResponse {
ChunkLoaded {
chunk_position: IVec3,
blocks: BlockData,
queue: Vec<QueuedBlock>
}
}
#[derive(Unique)]
pub struct ChunkTaskManager {
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
pool: ThreadPool,
}
impl ChunkTaskManager {
pub fn new() -> Result<Self> {
Ok(Self {
channel: unbounded(),
pool: ThreadPoolBuilder::new().build()?
})
}
pub fn spawn_task(&self, task: ChunkTask) {
let sender = self.channel.0.clone();
self.pool.spawn(move || {
sender.send(match task {
ChunkTask::LoadChunk { position: chunk_position, seed } => {
let (blocks, queue) = generate_world(chunk_position, seed);
ChunkTaskResponse::ChunkLoaded { chunk_position, blocks, queue }
}
}).unwrap()
})
}
pub fn receive(&self) -> Option<ChunkTaskResponse> {
self.channel.1.try_recv().ok()
}
}
pub fn init_chunk_task_manager(
storages: AllStoragesView
) {
storages.add_unique(ChunkTaskManager::new().expect("ChunkTaskManager Init failed"));
}

View file

@ -1,7 +1,6 @@
use std::num::NonZeroUsize;
use bincode::{Encode, Decode};
use crate::chunk::BlockData;
use crate::{chunk::BlockData, block::Block};
type IVec3Arr = [i32; 3];
type Vec3Arr = [f32; 3];
@ -20,7 +19,7 @@ pub enum ClientToServerMessage {
velocity: Vec3Arr,
direction: QuatArr,
},
ChunkRequest {
ChunkSubRequest {
chunk: IVec3Arr,
},
}
@ -54,6 +53,7 @@ pub enum ServerToClientMessage {
},
ChunkResponse {
chunk: IVec3Arr,
data: BlockData
data: BlockData,
queued: Vec<(IVec3Arr, Block)>,
}
}

View file

@ -134,7 +134,7 @@ fn start_required_tasks(
DesiredChunkState::Loaded | DesiredChunkState::Rendered if chunk.current_state == CurrentChunkState::Nothing => {
//start load task
if let Some(client) = &udp_client {
client.0.send_message(ClientToServerMessage::ChunkRequest {
client.0.send_message(ClientToServerMessage::ChunkSubRequest {
chunk: position.to_array()
}).unwrap();
} else {

View file

@ -83,12 +83,15 @@ pub fn inject_network_responses_into_manager_queue(
events: View<NetworkEvent>
) {
for event in events.iter() {
if let ClientEvent::MessageReceived(ServerToClientMessage::ChunkResponse { chunk, data }) = &event.0 {
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: Vec::with_capacity(0)
queued: queued.iter().map(|&(position, block_type)| QueuedBlock {
position: IVec3::from_array(position),
block_type
}).collect()
});
}
}