mirror of
https://github.com/griffi-gh/kubi.git
synced 2024-11-23 07:18:42 -06:00
Initial server save file integration (NO SAVING YET, ONLY LOADING)
This commit is contained in:
parent
37e68912eb
commit
884551089c
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
16
kubi-server/src/world/save.rs
Normal file
16
kubi-server/src/world/save.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
// 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()
|
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")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue