From 86ea5444abb47dc7581ffdacfa87b6f10abff8c6 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 18 Jun 2021 18:46:46 +0100 Subject: [PATCH] Added simple, dumb chunk persistence --- server/Cargo.toml | 1 + server/src/lib.rs | 5 ++ server/src/sys/msg/in_game.rs | 13 ++++- server/src/sys/terrain.rs | 12 ++++- server/src/terrain_persistence.rs | 86 +++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 server/src/terrain_persistence.rs diff --git a/server/Cargo.toml b/server/Cargo.toml index 4236c3d887..77a7ba0263 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -26,6 +26,7 @@ network = { package = "veloren-network", path = "../network", features = ["metri specs = { git = "https://github.com/amethyst/specs.git", features = ["shred-derive"], rev = "f985bec5d456f7b0dd8aae99848f9473c2cd9d46" } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "8be2abcddf8f524cb5876e8dd20a7e47cfaf7573" } +bincode = "1.3.2" num_cpus = "1.0" tracing = "0.1" vek = { version = "0.14.1", features = ["serde"] } diff --git a/server/src/lib.rs b/server/src/lib.rs index 66184909fa..b7f52c980c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -32,6 +32,7 @@ pub mod state_ext; pub mod sys; #[cfg(not(feature = "worldgen"))] mod test_world; pub mod wiring; +pub mod terrain_persistence; // Reexports pub use crate::{ @@ -54,6 +55,7 @@ use crate::{ rtsim::RtSim, state_ext::StateExt, sys::sentinel::{DeletedEntities, TrackedComps}, + terrain_persistence::TerrainPersistence, }; #[cfg(not(feature = "worldgen"))] use common::grid::Grid; @@ -213,6 +215,7 @@ impl Server { state.ecs_mut().insert(ecs_system_metrics); state.ecs_mut().insert(tick_metrics); state.ecs_mut().insert(physics_metrics); + state.ecs_mut().insert(TerrainPersistence::default()); state .ecs_mut() .write_resource::() @@ -823,6 +826,8 @@ impl Server { pub fn cleanup(&mut self) { // Cleanup the local state self.state.cleanup(); + + self.state.ecs().write_resource::().unload_all(); } fn initialize_client( diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index 9aa4e3ee40..bfc46ca45b 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -1,4 +1,4 @@ -use crate::{client::Client, presence::Presence, Settings}; +use crate::{client::Client, presence::Presence, Settings, TerrainPersistence}; use common::{ comp::{ Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player, Pos, SkillSet, @@ -36,6 +36,7 @@ impl Sys { settings: &Read<'_, Settings>, build_areas: &Read<'_, BuildAreas>, player_physics_settings: &mut Write<'_, PlayerPhysicsSettings>, + terrain_persistence: &mut Write<'_, TerrainPersistence>, maybe_player: &Option<&Player>, maybe_admin: &Option<&Admin>, msg: ClientGeneral, @@ -198,7 +199,10 @@ impl Sys { .filter(|aabb| aabb.contains_point(pos)) .and_then(|_| terrain.get(pos).ok()) { - block_changes.set(pos, block.into_vacant()); + let block = block.into_vacant(); + block_changes.set(pos, block); + // TODO: Only modify if succeeded + terrain_persistence.set_block(pos, block); } } } @@ -217,6 +221,8 @@ impl Sys { .is_some() { block_changes.try_set(pos, block); + // TODO: Only modify if succeeded + terrain_persistence.set_block(pos, block); } } } @@ -287,6 +293,7 @@ impl<'a> System<'a> for Sys { Read<'a, Settings>, Read<'a, BuildAreas>, Write<'a, PlayerPhysicsSettings>, + Write<'a, TerrainPersistence>, ReadStorage<'a, Player>, ReadStorage<'a, Admin>, ); @@ -315,6 +322,7 @@ impl<'a> System<'a> for Sys { settings, build_areas, mut player_physics_settings, + mut terrain_persistence, players, admins, ): Self::SystemData, @@ -349,6 +357,7 @@ impl<'a> System<'a> for Sys { &settings, &build_areas, &mut player_physics_settings, + &mut terrain_persistence, &player, &maybe_admin, msg, diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 8514cfbb31..0a64f0269b 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -7,6 +7,7 @@ use crate::{ settings::Settings, SpawnPoint, Tick, }; +use crate::TerrainPersistence; use common::{ comp::{self, agent, bird_medium, Alignment, BehaviorCapability, ForceUpdate, Pos}, event::{EventBus, ServerEvent}, @@ -14,6 +15,7 @@ use common::{ npc::NPC_NAMES, terrain::TerrainGrid, LoadoutBuilder, SkillSetBuilder, + vol::WriteVol, }; use common_ecs::{Job, Origin, Phase, System}; use common_net::msg::{SerializedTerrainChunk, ServerGeneral}; @@ -98,6 +100,7 @@ impl<'a> System<'a> for Sys { WriteExpect<'a, TerrainGrid>, Write<'a, TerrainChanges>, WriteExpect<'a, RtSim>, + WriteExpect<'a, TerrainPersistence>, WriteStorage<'a, Pos>, ReadStorage<'a, Presence>, ReadStorage<'a, Client>, @@ -122,6 +125,7 @@ impl<'a> System<'a> for Sys { mut terrain, mut terrain_changes, mut rtsim, + mut terrain_persistence, mut positions, presences, clients, @@ -136,7 +140,7 @@ impl<'a> System<'a> for Sys { // Also, send the chunk data to anybody that is close by. let mut new_chunks = Vec::new(); 'insert_terrain_chunks: while let Some((key, res)) = chunk_generator.recv_new_chunk() { - let (chunk, supplement) = match res { + let (mut chunk, supplement) = match res { Ok((chunk, supplement)) => (chunk, supplement), Err(Some(entity)) => { if let Some(client) = clients.get(entity) { @@ -152,6 +156,10 @@ impl<'a> System<'a> for Sys { }, }; + for (key, block) in terrain_persistence.load_chunk(key).blocks() { + chunk.set(key, block); + } + // Arcify the chunk let chunk = Arc::new(chunk); @@ -391,6 +399,8 @@ impl<'a> System<'a> for Sys { }); for key in chunks_to_remove { + terrain_persistence.unload_chunk(key); + // TODO: code duplication for chunk insertion between here and state.rs if terrain.remove(key).is_some() { terrain_changes.removed_chunks.insert(key); diff --git a/server/src/terrain_persistence.rs b/server/src/terrain_persistence.rs new file mode 100644 index 0000000000..32371cb23f --- /dev/null +++ b/server/src/terrain_persistence.rs @@ -0,0 +1,86 @@ +use hashbrown::HashMap; +use common::{ + terrain::{Block, TerrainChunk}, + vol::RectRasterableVol, +}; +use vek::*; +use tracing::{info, error}; +use serde::{Serialize, Deserialize}; +use std::{ + fs::File, + path::PathBuf, +}; + +pub struct TerrainPersistence { + path: PathBuf, + chunks: HashMap, Chunk>, +} + +impl Default for TerrainPersistence { + fn default() -> Self { + let mut path = PathBuf::from(std::env::var("VELOREN_TERRAIN_DATA").unwrap_or_else(|_| String::new())); + path.push("chunks"); + + std::fs::create_dir_all(&path).unwrap(); + + info!("Using {:?} as the terrain persistence path", path); + + Self { + path, + chunks: HashMap::default(), + } + } +} + +impl TerrainPersistence { + fn path_for(&self, key: Vec2) -> PathBuf { + let mut path = self.path.clone(); + path.push(format!("chunk_{}_{}.dat", key.x, key.y)); + path + } + + pub fn load_chunk(&mut self, key: Vec2) -> &mut Chunk { + let path = self.path_for(key); + self.chunks + .entry(key) + .or_insert_with(|| { + File::open(path) + .ok() + .and_then(|f| bincode::deserialize_from(&f).ok()) + .unwrap_or_else(|| Chunk::default()) + }) + } + + pub fn unload_chunk(&mut self, key: Vec2) { + if let Some(chunk) = self.chunks.remove(&key) { + if chunk.blocks.len() > 0 { + match File::create(self.path_for(key)) { + Ok(file) => { bincode::serialize_into(file, &chunk); }, + Err(err) => error!("Failed to create file: {:?}", err), + } + } + } + } + + pub fn unload_all(&mut self) { + for key in self.chunks.keys().copied().collect::>() { + self.unload_chunk(key); + } + } + + pub fn set_block(&mut self, pos: Vec3, block: Block) { + let key = pos.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| e.div_euclid(sz as i32)); + self.load_chunk(key).blocks.insert(pos - key * TerrainChunk::RECT_SIZE.map(|e| e as i32), block); + } +} + +#[derive(Default, Serialize, Deserialize)] +pub struct Chunk { + blocks: HashMap, Block>, +} + +impl Chunk { + pub fn blocks(&self) -> impl Iterator, Block)> + '_ { + self.blocks.iter().map(|(k, b)| (*k, *b)) + } +}