alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
root crates server src save maps.rs
use std::fs::File;
use std::io::{BufReader, BufWriter, Write};
use std::path::PathBuf;
use common::map::Map;
use common::nbt::Compound;
use rustc_hash::FxHashMap;
use super::{SaveError, io_error, read_gzip, sync, write_gzip};
pub struct StoredMap {
pub map: Map,
pub revisions: [u64; 128],
pub ticks: i32,
dirty: bool,
extra: Compound,
}
impl StoredMap {
pub fn new(map: Map) -> Self {
Self {
map,
revisions: [0; 128],
ticks: 0,
dirty: true,
extra: Default::default(),
}
}
pub fn changed(&mut self, x: usize) {
self.revisions[x] = self.revisions[x].wrapping_add(1);
self.dirty = true;
}
}
#[derive(Clone)]
pub struct Holder {
pub maps: Vec<i16>,
pub position: glam::DVec3,
pub yaw: f32,
pub dimension: i8,
}
#[derive(Default)]
pub struct Maps {
directory: Option<PathBuf>,
ids: Compound,
ids_dirty: bool,
pub loaded: FxHashMap<i16, StoredMap>,
pub holders: FxHashMap<i32, Holder>,
}
impl Maps {
pub fn open(world: &std::path::Path) -> Result<Self, SaveError> {
let directory = world.join("data");
let path = directory.join("idcounts.dat");
// `MapStorage.loadIdCounts` starts over from unreadable counters
let ids = match File::open(&path) {
Ok(file) => Compound::read(&mut BufReader::new(file)).unwrap_or_else(|error| {
tracing::warn!("cannot read {}: {error}", path.display());
Compound::default()
}),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Compound::default(),
Err(error) => {
tracing::warn!("cannot read {}: {error}", path.display());
Compound::default()
}
};
Ok(Self {
directory: Some(directory),
ids,
..Default::default()
})
}
pub fn load(&mut self, id: i16) -> Result<bool, SaveError> {
if self.loaded.contains_key(&id) {
return Ok(true);
}
let Some(directory) = &self.directory else {
return Ok(false);
};
let path = directory.join(format!("map_{id}.dat"));
if !path.exists() {
return Ok(false);
}
let root = read_gzip(&path)?;
let extra = root.compound("data").cloned().unwrap_or_default();
let mut stored = StoredMap::new(Map::read(&extra));
stored.extra = extra;
stored.dirty = false;
self.loaded.insert(id, stored);
Ok(true)
}
pub fn create(&mut self, center: [i32; 2], dimension: i8) -> Result<i16, SaveError> {
let start = if self.ids.contains("map") {
self.ids.short("map").wrapping_add(1)
} else {
0
};
let mut id = start;
while self.load(id)? {
id = id.wrapping_add(1);
if id == start {
return Err(SaveError::Invalid("all map identifiers are in use".into()));
}
}
self.ids.set_short("map", id);
self.ids_dirty = true;
self.loaded
.insert(id, StoredMap::new(Map::new(center, dimension)));
Ok(id)
}
pub fn save(&mut self) -> Result<(), SaveError> {
if !self.ids_dirty && !self.loaded.values().any(|map| map.dirty) {
return Ok(());
}
let Some(directory) = &self.directory else {
return Ok(());
};
std::fs::create_dir_all(directory).map_err(io_error(directory))?;
// Publish map data before its allocated identifier
for (&id, map) in &mut self.loaded {
if !map.dirty {
continue;
}
let mut data = map.extra.clone();
map.map.write(&mut data);
let mut root = Compound::default();
root.set_compound("data", data);
atomic_nbt(directory.join(format!("map_{id}.dat")), &root, true)?;
map.dirty = false;
}
if self.ids_dirty {
atomic_nbt(directory.join("idcounts.dat"), &self.ids, false)?;
self.ids_dirty = false;
}
sync(directory)
}
}
fn atomic_nbt(path: PathBuf, nbt: &Compound, gzip: bool) -> Result<(), SaveError> {
let temporary = path.with_extension("dat.tmp");
if gzip {
write_gzip(&temporary, nbt)?;
} else {
let mut writer = BufWriter::new(File::create(&temporary).map_err(io_error(&temporary))?);
nbt.write(&mut writer)
.and_then(|()| writer.flush())
.and_then(|()| writer.get_ref().sync_all())
.map_err(io_error(&temporary))?;
}
std::fs::rename(&temporary, &path).map_err(io_error(&path))
}