treewide: update to bincode 2

Consistently use bincode's Encode/Decode to avoid issues with
incompatible serde features. Support for storing some temporary files as
JSON is removed.

The size of the "processed" directory is reduced by ~8% with the new
default encoding of bincode 2. Performance is more or less unaffected.
This commit is contained in:
Matthias Schiffer 2025-03-12 20:34:26 +01:00
parent 404ad74235
commit 53a0f24600
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
16 changed files with 133 additions and 81 deletions

View file

@ -3,13 +3,15 @@
use std::{
collections::{BTreeMap, BTreeSet},
fmt::Debug,
hash::Hash,
path::{Path, PathBuf},
};
use anyhow::{Context, Result};
use bincode::{Decode, Encode};
use clap::ValueEnum;
use regex::{Regex, RegexSet};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use crate::{
io::fs::FileMetaVersion,
@ -24,7 +26,7 @@ use crate::{
///
/// Increase when the generation of processed regions from region data changes
/// (usually because of updated resource data)
pub const REGION_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(5);
pub const REGION_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(6);
/// MinedMap map tile data version number
///
@ -46,7 +48,7 @@ pub const MIPMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0);
/// MinedMap processed entity data version number
///
/// Increase when entity collection changes bacause of code changes.
pub const ENTITIES_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(1);
pub const ENTITIES_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(2);
/// Coordinate pair of a generated tile
///
@ -85,7 +87,7 @@ impl TileCoordMap {
}
/// Data structure for storing chunk data between processing and rendering steps
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Encode, Decode)]
pub struct ProcessedChunk {
/// Block type data
pub blocks: Box<layer::BlockArray>,
@ -96,7 +98,7 @@ pub struct ProcessedChunk {
}
/// Data structure for storing region data between processing and rendering steps
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Debug, Default, Encode, Decode)]
pub struct ProcessedRegion {
/// List of biomes used in the region
///
@ -107,7 +109,7 @@ pub struct ProcessedRegion {
}
/// Data structure for storing entity data between processing and collection steps
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Debug, Default, Encode, Decode)]
pub struct ProcessedEntities {
/// List of block entities
pub block_entities: Vec<BlockEntity>,

View file

@ -78,23 +78,22 @@ impl<'a> EntityCollector<'a> {
let mut output = ProcessedEntities::default();
for source_path in sources {
let mut source: ProcessedEntities =
match storage::read_file(source_path.as_ref(), storage::Format::Json) {
Ok(source) => source,
Err(err) => {
warn!(
"Failed to read entity data file {}: {:?}",
source_path.as_ref().display(),
err,
);
continue;
}
};
let mut source: ProcessedEntities = match storage::read_file(source_path.as_ref()) {
Ok(source) => source,
Err(err) => {
warn!(
"Failed to read entity data file {}: {:?}",
source_path.as_ref().display(),
err,
);
continue;
}
};
output.block_entities.append(&mut source.block_entities);
}
storage::write(file, &output, storage::Format::Json).context("Failed to write entity data")
storage::write(file, &output).context("Failed to write entity data")
}
/// Runs the mipmap generation

View file

@ -179,9 +179,8 @@ impl<'a> MetadataWriter<'a> {
/// Generates [Entities] data from collected entity lists
fn entities(&self) -> Result<Entities> {
let data: ProcessedEntities =
storage::read_file(&self.config.entities_path_final, storage::Format::Json)
.context("Failed to read entity data file")?;
let data: ProcessedEntities = storage::read_file(&self.config.entities_path_final)
.context("Failed to read entity data file")?;
let ret = Entities {
signs: data

View file

@ -168,7 +168,6 @@ impl<'a> SingleRegionProcessor<'a> {
storage::write_file(
&self.output_path,
&self.processed_region,
storage::Format::Bincode,
REGION_FILE_META_VERSION,
self.input_timestamp,
)
@ -207,7 +206,6 @@ impl<'a> SingleRegionProcessor<'a> {
storage::write_file(
&self.entities_path,
&self.entities,
storage::Format::Json,
ENTITIES_FILE_META_VERSION,
self.input_timestamp,
)

View file

@ -105,8 +105,7 @@ impl<'a> TileRenderer<'a> {
region_loader
.get_or_try_init(|| async {
storage::read_file(&processed_path, storage::Format::Bincode)
.context("Failed to load processed region data")
storage::read_file(&processed_path).context("Failed to load processed region data")
})
.await
.cloned()

View file

@ -10,28 +10,16 @@ use std::{
};
use anyhow::{Context, Result};
use serde::{de::DeserializeOwned, Serialize};
use bincode::{Decode, Encode};
use super::fs;
/// Storage format
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Format {
/// Encode as Bincode
///
/// Bincode is more efficient than JSON, but cannot handle many of
/// serde's features like flatten, conditional skipping, ...
Bincode,
/// Encode as JSON
Json,
}
/// Bincode configuration
const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard();
/// Serializes data and writes it to a writer
pub fn write<W: Write, T: Serialize>(writer: &mut W, value: &T, format: Format) -> Result<()> {
let data = match format {
Format::Bincode => bincode::serialize(value)?,
Format::Json => serde_json::to_vec(value)?,
};
pub fn write<W: Write, T: Encode>(writer: &mut W, value: &T) -> Result<()> {
let data = bincode::encode_to_vec(value, BINCODE_CONFIG)?;
let len = u32::try_from(data.len())?;
let compressed = zstd::bulk::compress(&data, 1)?;
drop(data);
@ -45,18 +33,21 @@ pub fn write<W: Write, T: Serialize>(writer: &mut W, value: &T, format: Format)
/// Serializes data and stores it in a file
///
/// A timestamp is stored in an assiciated metadata file.
pub fn write_file<T: Serialize>(
pub fn write_file<T: Encode>(
path: &Path,
value: &T,
format: Format,
version: fs::FileMetaVersion,
timestamp: SystemTime,
) -> Result<()> {
fs::create_with_timestamp(path, version, timestamp, |file| write(file, value, format))
fs::create_with_timestamp(path, version, timestamp, |file| write(file, value))
}
/// Reads data from a reader and deserializes it
pub fn read<R: Read, T: DeserializeOwned>(reader: &mut R, format: Format) -> Result<T> {
pub fn read<R, T>(reader: &mut R) -> Result<T>
where
R: Read,
T: Decode<()>,
{
let mut len_buf = [0u8; 4];
reader.read_exact(&mut len_buf)?;
let len = usize::try_from(u32::from_be_bytes(len_buf))?;
@ -66,18 +57,17 @@ pub fn read<R: Read, T: DeserializeOwned>(reader: &mut R, format: Format) -> Res
let data = zstd::bulk::decompress(&compressed, len)?;
drop(compressed);
let value = match format {
Format::Bincode => bincode::deserialize(&data)?,
Format::Json => serde_json::from_slice(&data)?,
};
Ok(value)
Ok(bincode::decode_from_slice(&data, BINCODE_CONFIG)?.0)
}
/// Reads data from a file and deserializes it
pub fn read_file<T: DeserializeOwned>(path: &Path, format: Format) -> Result<T> {
pub fn read_file<T>(path: &Path) -> Result<T>
where
T: Decode<()>,
{
(|| -> Result<T> {
let mut file = File::open(path)?;
read(&mut file, format)
read(&mut file)
})()
.with_context(|| format!("Failed to read file {}", path.display()))
}

View file

@ -1,7 +1,8 @@
//! Processing of block entity data
use bincode::{Decode, Encode};
use minedmap_resource::{BlockFlag, BlockType};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use super::{
de,
@ -9,7 +10,7 @@ use super::{
};
/// Kind of sign block
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SignKind {
/// Standing sign
@ -23,7 +24,7 @@ pub enum SignKind {
}
/// Processed sign data
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, Serialize)]
pub struct Sign {
/// The kind of the sign
pub kind: SignKind,
@ -54,7 +55,7 @@ impl Sign {
}
/// Data for different kinds of [BlockEntity]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum BlockEntityData {
/// A sign block
@ -62,7 +63,7 @@ pub enum BlockEntityData {
}
/// A processed block entity
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, Serialize)]
pub struct BlockEntity {
/// Global X coordinate
pub x: i32,

View file

@ -2,6 +2,7 @@
use std::{collections::VecDeque, fmt::Display};
use bincode::{Decode, Encode};
use minedmap_resource::Color;
use serde::{Deserialize, Serialize};
@ -12,7 +13,9 @@ use serde::{Deserialize, Serialize};
/// is handled by [DeserializedText].
///
/// Formatting that is not set in a node is inherited from the parent.
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
#[derive(
Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Encode, Decode,
)]
pub struct FormattedText {
#[serde(default)]
/// Text content
@ -84,7 +87,7 @@ impl From<String> for FormattedTextTree {
}
/// List of [FormattedText]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Encode, Decode)]
pub struct FormattedTextList(pub Vec<FormattedText>);
impl FormattedTextList {

View file

@ -3,8 +3,8 @@
use std::num::NonZeroU16;
use anyhow::{Context, Result};
use bincode::{Decode, Encode};
use indexmap::IndexSet;
use serde::{Deserialize, Serialize};
use super::chunk::{Chunk, SectionIterItem};
use crate::{
@ -13,7 +13,7 @@ use crate::{
};
/// Height (Y coordinate) of a block
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
pub struct BlockHeight(pub i32);
impl BlockHeight {

View file

@ -2,8 +2,9 @@
use std::fmt::Display;
use bincode::{Decode, Encode};
use minedmap_resource::Color;
use serde::{Deserialize, Serialize};
use serde::Serialize;
use super::{
de,
@ -104,7 +105,7 @@ impl BlockEntitySignExt for de::BlockEntitySign {
}
}
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
/// Deserialized and linearized sign text
pub struct SignText(pub Vec<FormattedTextList>);