diff --git a/server/src/terrain_persistence.rs b/server/src/terrain_persistence.rs index 32371cb23f..d848a42a33 100644 --- a/server/src/terrain_persistence.rs +++ b/server/src/terrain_persistence.rs @@ -4,9 +4,10 @@ use common::{ vol::RectRasterableVol, }; use vek::*; -use tracing::{info, error}; +use tracing::{info, error, warn}; use serde::{Serialize, Deserialize}; use std::{ + io::{self, Read as _, Write as _}, fs::File, path::PathBuf, }; @@ -33,29 +34,57 @@ impl Default for TerrainPersistence { } impl TerrainPersistence { - fn path_for(&self, key: Vec2) -> PathBuf { + fn path_for(&self, key: Vec2, ext: &str) -> PathBuf { let mut path = self.path.clone(); - path.push(format!("chunk_{}_{}.dat", key.x, key.y)); + path.push(format!("chunk_{}_{}.{}", key.x, key.y, ext)); path } pub fn load_chunk(&mut self, key: Vec2) -> &mut Chunk { - let path = self.path_for(key); + let path = self.path_for(key, "dat"); + let backup_path = self.path_for(key, "dat.backup"); self.chunks .entry(key) .or_insert_with(|| { - File::open(path) + File::open(&path) .ok() - .and_then(|f| bincode::deserialize_from(&f).ok()) + .map(|f| { + let bytes = match std::io::BufReader::new(f).bytes().collect::, _>>() { + Ok(bytes) => bytes, + Err(err) => { + error!("Failed to load chunk {:?} from disk: {:?}", key, err); + return Chunk::default(); + }, + }; + match Chunk::deserialize_from(std::io::Cursor::new(bytes)) { + Some(chunk) => chunk, + None => { + error!("Failed to load chunk {:?}, moving to {:?} instead", key, backup_path); + if let Err(err) = std::fs::copy(&path, backup_path) + .and_then(|_| std::fs::remove_file(&path)) + { + error!("{:?}", err); + } + Chunk::default() + }, + } + }) .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); }, + if chunk.blocks.len() > 0 { // No need to write if no blocks have ever been written + match File::create(self.path_for(key, "dat")) { + Ok(file) => { + let mut writer = std::io::BufWriter::new(file); + if let Err(err) = bincode::serialize_into::<_, version::Current>(&mut writer, &chunk.prepare()) + .and_then(|_| Ok(writer.flush()?)) + { + error!("Failed to write chunk to disk: {:?}", err); + } + }, Err(err) => error!("Failed to create file: {:?}", err), } } @@ -80,7 +109,80 @@ pub struct Chunk { } impl Chunk { + pub fn deserialize_from(reader: R) -> Option { + // Attempt deserialization through various versions + if let Ok(data) = bincode::deserialize_from::<_, version::V2>(reader.clone()) + .map_err(|err| { warn!("Error when loading V2: {:?}", err); err }) + { + Some(Chunk::from(data)) + } else if let Ok(data) = bincode::deserialize_from::<_, Chunk>(reader.clone()) + .map_err(|err| { warn!("Error when loading V1: {:?}", err); err }) + { + Some(Chunk::from(data)) + } else { + None + } + } + + fn prepare(self) -> version::Current { self.into() } + pub fn blocks(&self) -> impl Iterator, Block)> + '_ { self.blocks.iter().map(|(k, b)| (*k, *b)) } } + +mod version { + pub type Current = V2; + + fn version_magic(n: u16) -> u64 { + (n as u64) | (0x3352ACEEA789 << 16) + } + + use super::*; + + // Convert back to current + + impl From for Current { + fn from(chunk: Chunk) -> Self { + Self { version: version_magic(2), blocks: chunk.blocks + .into_iter() + .map(|(pos, b)| (pos.x as u8, pos.y as u8, pos.z as i16, b)) + .collect() } + } + } + + // V1 + + #[derive(Serialize, Deserialize)] + pub struct V1 { + pub blocks: HashMap, Block>, + } + + impl From for Chunk { + fn from(v1: V1) -> Self { Self { blocks: v1.blocks } } + } + + // V2 + + #[derive(Serialize, Deserialize)] + pub struct V2 { + #[serde(deserialize_with = "version::<_, 2>")] + pub version: u64, + pub blocks: Vec<(u8, u8, i16, Block)>, + } + + fn version<'de, D: serde::Deserializer<'de>, const V: u16>(de: D) -> Result { + u64::deserialize(de).and_then(|x| if x == version_magic(V) { + Ok(x) + } else { + Err(serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(x), &"correct version")) + }) + } + + impl From for Chunk { + fn from(v2: V2) -> Self { Self { blocks: v2.blocks + .into_iter() + .map(|(x, y, z, b)| (Vec3::new(x as i32, y as i32, z as i32), b)) + .collect() } } + } +}