Initial server save file integration (NO SAVING YET, ONLY LOADING)

This commit is contained in:
griffi-gh 2024-09-03 15:47:04 +02:00
parent 37e68912eb
commit 884551089c
13 changed files with 100 additions and 33 deletions

View file

@ -4,6 +4,7 @@ max_clients = 32
timeout_ms = 10000 timeout_ms = 10000
[world] [world]
file = "world.kubi"
seed = 0xfeb_face_dead_cafe seed = 0xfeb_face_dead_cafe
preheat_radius = 8 preheat_radius = 8

View file

@ -1,6 +1,6 @@
use shipyard::{AllStoragesView, Unique}; use shipyard::{AllStoragesView, Unique};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use std::{fs, net::SocketAddr}; use std::{fs, net::SocketAddr, path::PathBuf};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ConfigTableServer { pub struct ConfigTableServer {
@ -12,6 +12,7 @@ pub struct ConfigTableServer {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ConfigTableWorld { pub struct ConfigTableWorld {
pub file: Option<PathBuf>,
pub seed: u64, pub seed: u64,
pub preheat_radius: u32, pub preheat_radius: u32,
} }

View file

@ -1,5 +1,6 @@
use shipyard::{IntoWorkload, Workload, WorkloadModificator, World}; use shipyard::{IntoWorkload, Workload, WorkloadModificator, World};
use std::{thread, time::Duration}; use std::{thread, time::Duration};
use kubi_shared::fixed_timestamp::init_fixed_timestamp_storage;
mod util; mod util;
mod config; mod config;
@ -16,6 +17,7 @@ use world::{update_world, init_world};
fn initialize() -> Workload { fn initialize() -> Workload {
( (
init_fixed_timestamp_storage,
read_config, read_config,
bind_server, bind_server,
init_client_maps, init_client_maps,

View file

@ -24,12 +24,13 @@ use crate::{
pub mod chunk; pub mod chunk;
pub mod tasks; pub mod tasks;
pub mod save;
use chunk::Chunk; use chunk::Chunk;
use self::{ use self::{
tasks::{ChunkTaskManager, ChunkTask, ChunkTaskResponse, init_chunk_task_manager}, tasks::{ChunkTaskManager, ChunkTask, ChunkTaskResponse, init_chunk_task_manager},
chunk::ChunkState chunk::ChunkState,
}; };
#[derive(Unique, Default)] #[derive(Unique, Default)]
@ -106,7 +107,7 @@ fn process_chunk_requests(
chunk.state = ChunkState::Loading; chunk.state = ChunkState::Loading;
chunk.subscriptions.insert(message.client_id); chunk.subscriptions.insert(message.client_id);
chunk_manager.chunks.insert(chunk_position, chunk); chunk_manager.chunks.insert(chunk_position, chunk);
task_manager.spawn_task(ChunkTask::LoadChunk { task_manager.run(ChunkTask::LoadChunk {
position: chunk_position, position: chunk_position,
seed: config.world.seed, seed: config.world.seed,
}); });
@ -278,7 +279,7 @@ pub fn preheat_world(
let mut chunk = Chunk::new(); let mut chunk = Chunk::new();
chunk.state = ChunkState::Loading; chunk.state = ChunkState::Loading;
chunk_manager.chunks.insert(chunk_position, chunk); chunk_manager.chunks.insert(chunk_position, chunk);
task_manager.spawn_task(ChunkTask::LoadChunk { task_manager.run(ChunkTask::LoadChunk {
position: chunk_position, position: chunk_position,
seed: config.world.seed, seed: config.world.seed,
}); });
@ -292,7 +293,7 @@ pub fn init_world() -> Workload {
init_chunk_manager_and_block_queue.before_all(preheat_world), init_chunk_manager_and_block_queue.before_all(preheat_world),
init_chunk_task_manager.before_all(preheat_world), init_chunk_task_manager.before_all(preheat_world),
preheat_world, preheat_world,
).into_workload() ).into_sequential_workload()
} }
pub fn update_world() -> Workload { pub fn update_world() -> Workload {

View file

@ -0,0 +1,16 @@
use kubi_shared::data::{io_thread::IOThreadManager, open_local_save_file};
use shipyard::{AllStoragesView, UniqueView};
use crate::config::ConfigTable;
pub fn init_save_file(storages: &AllStoragesView) -> Option<IOThreadManager> {
let config = storages.borrow::<UniqueView<ConfigTable>>().unwrap();
if let Some(file_path) = &config.world.file {
log::info!("Initializing save file from {:?}", file_path);
let save = open_local_save_file(&file_path).unwrap();
Some(IOThreadManager::new(save))
} else {
log::warn!("No save file specified, world will not be saved");
None
}
}

View file

@ -4,10 +4,9 @@ use glam::IVec3;
use rayon::{ThreadPool, ThreadPoolBuilder}; use rayon::{ThreadPool, ThreadPoolBuilder};
use anyhow::Result; use anyhow::Result;
use kubi_shared::{ use kubi_shared::{
chunk::BlockData, chunk::BlockData, data::io_thread::{IOCommand, IOResponse, IOThreadManager}, queue::QueuedBlock, worldgen::generate_world
worldgen::generate_world,
queue::QueuedBlock,
}; };
use super::save::init_save_file;
pub enum ChunkTask { pub enum ChunkTask {
LoadChunk { LoadChunk {
@ -28,15 +27,30 @@ pub enum ChunkTaskResponse {
pub struct ChunkTaskManager { pub struct ChunkTaskManager {
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>), channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
pool: ThreadPool, pool: ThreadPool,
iota: Option<IOThreadManager>,
} }
impl ChunkTaskManager { impl ChunkTaskManager {
pub fn new() -> Result<Self> { pub fn new(iota: Option<IOThreadManager>) -> Result<Self> {
Ok(Self { Ok(Self {
channel: unbounded(), channel: unbounded(),
pool: ThreadPoolBuilder::new().build()? pool: ThreadPoolBuilder::new().build()?,
iota,
}) })
} }
pub fn spawn_task(&self, task: ChunkTask) {
pub fn run(&self, task: ChunkTask) {
// 1. Check if the chunk exists in the save file
#[allow(irrefutable_let_patterns)]
if let ChunkTask::LoadChunk { position, .. } = &task {
if let Some(iota) = &self.iota {
if iota.chunk_exists(*position) {
iota.send(IOCommand::LoadChunk { position: *position });
}
}
}
// 2. Generate the chunk if it doesn't exist
let sender = self.channel.0.clone(); let sender = self.channel.0.clone();
self.pool.spawn(move || { self.pool.spawn(move || {
sender.send(match task { sender.send(match task {
@ -48,13 +62,31 @@ impl ChunkTaskManager {
}).unwrap() }).unwrap()
}) })
} }
pub fn receive(&self) -> Option<ChunkTaskResponse> { pub fn receive(&self) -> Option<ChunkTaskResponse> {
self.channel.1.try_recv().ok() // Try to receive IO results first
// If there are none, try to receive worldgen results
self.iota.as_ref().map(|iota| {
iota.poll_single().map(|response| match response {
IOResponse::ChunkLoaded { position, data } => ChunkTaskResponse::ChunkLoaded {
chunk_position: position,
blocks: data.expect("chunk data exists in the header, but was not loaded"),
queue: Vec::with_capacity(0)
},
_ => panic!("Unexpected response from IO thread"),
})
}).flatten().or_else(|| {
self.channel.1.try_recv().ok()
})
} }
} }
pub fn init_chunk_task_manager( pub fn init_chunk_task_manager(
storages: AllStoragesView storages: AllStoragesView
) { ) {
storages.add_unique(ChunkTaskManager::new().expect("ChunkTaskManager Init failed")); let iota = init_save_file(&storages);
storages.add_unique(
ChunkTaskManager::new(iota)
.expect("ChunkTaskManager Init failed")
);
} }

View file

@ -1,7 +1,8 @@
use std::{ use std::{
fs::File,
mem::size_of, mem::size_of,
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write}, io::{Read, Seek, SeekFrom, Write},
path::Path,
borrow::Cow, borrow::Cow,
sync::{Arc, RwLock} sync::{Arc, RwLock}
}; };
@ -182,3 +183,21 @@ impl WorldSaveFile {
Arc::clone(&self.header) Arc::clone(&self.header)
} }
} }
/// Utility function to open a local save file, creating it if it doesn't exist
pub fn open_local_save_file(path: &Path) -> Result<WorldSaveFile> {
let mut save_file = WorldSaveFile::new({
OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?
});
if save_file.file.metadata().unwrap().len() == 0 {
save_file.initialize()?;
} else {
save_file.load_data()?;
}
Ok(save_file)
}

View file

@ -172,6 +172,11 @@ impl IOSingleThread {
self.tx.send(cmd).unwrap(); self.tx.send(cmd).unwrap();
} }
/// Poll the IO thread for a single response (non-blocking)
pub fn poll_single(&self) -> Option<IOResponse> {
self.rx.try_recv().ok()
}
/// Poll the IO thread for responses (non-blocking) /// Poll the IO thread for responses (non-blocking)
pub fn poll(&self) -> TryIter<IOResponse> { pub fn poll(&self) -> TryIter<IOResponse> {
self.rx.try_iter() self.rx.try_iter()
@ -227,6 +232,10 @@ impl IOThreadManager {
self.thread.send(cmd); self.thread.send(cmd);
} }
pub fn poll_single(&self) -> Option<IOResponse> {
self.thread.poll_single()
}
pub fn poll(&self) -> TryIter<IOResponse> { pub fn poll(&self) -> TryIter<IOResponse> {
self.thread.poll() self.thread.poll()
} }

View file

@ -8,3 +8,4 @@ pub mod entity;
pub mod player; pub mod player;
pub mod queue; pub mod queue;
pub mod data; pub mod data;
pub mod fixed_timestamp;

View file

@ -5,23 +5,7 @@ use crate::{
networking::{GameType, ServerAddress}, networking::{GameType, ServerAddress},
state::{GameState, NextState} state::{GameState, NextState}
}; };
use kubi_shared::data::{io_thread::IOThreadManager, WorldSaveFile}; use kubi_shared::data::{io_thread::IOThreadManager, WorldSaveFile, open_local_save_file};
fn open_local_save_file(path: &Path) -> Result<WorldSaveFile> {
let mut save_file = WorldSaveFile::new({
OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?
});
if save_file.file.metadata().unwrap().len() == 0 {
save_file.initialize()?;
} else {
save_file.load_data()?;
}
Ok(save_file)
}
pub fn initialize_from_args( pub fn initialize_from_args(
all_storages: AllStoragesView, all_storages: AllStoragesView,

View file

@ -23,7 +23,9 @@ use winit::{
use glam::vec3; use glam::vec3;
use std::time::Instant; use std::time::Instant;
//TODO remove these re-exports
pub(crate) use kubi_shared::transform; pub(crate) use kubi_shared::transform;
pub(crate) use kubi_shared::fixed_timestamp;
mod ui; mod ui;
pub(crate) use ui::{ pub(crate) use ui::{
@ -51,7 +53,6 @@ pub(crate) mod hui_integration;
pub(crate) mod networking; pub(crate) mod networking;
pub(crate) mod init; pub(crate) mod init;
pub(crate) mod color; pub(crate) mod color;
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; pub(crate) mod chat;

View file

@ -24,7 +24,7 @@ use super::{
queue::BlockUpdateQueue, queue::BlockUpdateQueue,
}; };
const WORLD_SEED: u64 = 0xbeef_face_dead_cafe; const WORLD_SEED: u64 = 0xfeb_face_dead_cafe;
const MAX_CHUNK_OPS_INGAME: usize = 8; const MAX_CHUNK_OPS_INGAME: usize = 8;
const MAX_CHUNK_OPS: usize = 32; const MAX_CHUNK_OPS: usize = 32;