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
[world]
file = "world.kubi"
seed = 0xfeb_face_dead_cafe
preheat_radius = 8

View file

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

View file

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

View file

@ -24,12 +24,13 @@ use crate::{
pub mod chunk;
pub mod tasks;
pub mod save;
use chunk::Chunk;
use self::{
tasks::{ChunkTaskManager, ChunkTask, ChunkTaskResponse, init_chunk_task_manager},
chunk::ChunkState
chunk::ChunkState,
};
#[derive(Unique, Default)]
@ -106,7 +107,7 @@ fn process_chunk_requests(
chunk.state = ChunkState::Loading;
chunk.subscriptions.insert(message.client_id);
chunk_manager.chunks.insert(chunk_position, chunk);
task_manager.spawn_task(ChunkTask::LoadChunk {
task_manager.run(ChunkTask::LoadChunk {
position: chunk_position,
seed: config.world.seed,
});
@ -278,7 +279,7 @@ pub fn preheat_world(
let mut chunk = Chunk::new();
chunk.state = ChunkState::Loading;
chunk_manager.chunks.insert(chunk_position, chunk);
task_manager.spawn_task(ChunkTask::LoadChunk {
task_manager.run(ChunkTask::LoadChunk {
position: chunk_position,
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_task_manager.before_all(preheat_world),
preheat_world,
).into_workload()
).into_sequential_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 anyhow::Result;
use kubi_shared::{
chunk::BlockData,
worldgen::generate_world,
queue::QueuedBlock,
chunk::BlockData, data::io_thread::{IOCommand, IOResponse, IOThreadManager}, queue::QueuedBlock, worldgen::generate_world
};
use super::save::init_save_file;
pub enum ChunkTask {
LoadChunk {
@ -28,15 +27,30 @@ pub enum ChunkTaskResponse {
pub struct ChunkTaskManager {
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
pool: ThreadPool,
iota: Option<IOThreadManager>,
}
impl ChunkTaskManager {
pub fn new() -> Result<Self> {
pub fn new(iota: Option<IOThreadManager>) -> Result<Self> {
Ok(Self {
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();
self.pool.spawn(move || {
sender.send(match task {
@ -48,13 +62,31 @@ impl ChunkTaskManager {
}).unwrap()
})
}
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(
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::{
fs::File,
mem::size_of,
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
path::Path,
borrow::Cow,
sync::{Arc, RwLock}
};
@ -182,3 +183,21 @@ impl WorldSaveFile {
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();
}
/// 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)
pub fn poll(&self) -> TryIter<IOResponse> {
self.rx.try_iter()
@ -227,6 +232,10 @@ impl IOThreadManager {
self.thread.send(cmd);
}
pub fn poll_single(&self) -> Option<IOResponse> {
self.thread.poll_single()
}
pub fn poll(&self) -> TryIter<IOResponse> {
self.thread.poll()
}

View file

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

View file

@ -5,23 +5,7 @@ use crate::{
networking::{GameType, ServerAddress},
state::{GameState, NextState}
};
use kubi_shared::data::{io_thread::IOThreadManager, WorldSaveFile};
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)
}
use kubi_shared::data::{io_thread::IOThreadManager, WorldSaveFile, open_local_save_file};
pub fn initialize_from_args(
all_storages: AllStoragesView,

View file

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

View file

@ -24,7 +24,7 @@ use super::{
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: usize = 32;