Add documentation comments for all items

This commit is contained in:
Matthias Schiffer 2023-08-18 19:13:43 +02:00
parent ba86dc8c06
commit 05a8056cbf
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
26 changed files with 576 additions and 42 deletions

View file

@ -1,3 +1,8 @@
//! Higher-level interfaces to chunk data
//!
//! The data types in this module attempt to provide interfaces abstracting
//! over different data versions as much as possible.
use std::{
collections::{btree_map, BTreeMap},
iter::{self, FusedIterator},
@ -17,6 +22,7 @@ use crate::{
pub enum Chunk<'a> {
/// Minecraft v1.18+ chunk with biome data moved into sections
V1_18 {
/// Section data
section_map: BTreeMap<SectionY, (SectionV1_13<'a>, BiomesV1_18<'a>, BlockLight<'a>)>,
},
/// Minecraft v1.13+ chunk
@ -26,14 +32,18 @@ pub enum Chunk<'a> {
/// section), and a palette mapping these indices to namespaced
/// block IDs
V1_13 {
/// Section data
section_map: BTreeMap<SectionY, (SectionV1_13<'a>, BlockLight<'a>)>,
/// Biome data
biomes: BiomesV0<'a>,
},
/// Original pre-1.13 chunk
///
/// The original chunk format with fixed 8-bit numeric block IDs
V0 {
/// Section data
section_map: BTreeMap<SectionY, (SectionV0<'a>, BlockLight<'a>)>,
/// Biome data
biomes: BiomesV0<'a>,
},
/// Unpopulated chunk without any block data
@ -45,16 +55,21 @@ pub enum Chunk<'a> {
enum SectionIterInner<'a> {
/// Iterator over sections of [Chunk::V1_18]
V1_18 {
/// Inner iterator into section map
iter: btree_map::Iter<'a, SectionY, (SectionV1_13<'a>, BiomesV1_18<'a>, BlockLight<'a>)>,
},
/// Iterator over sections of [Chunk::V1_13]
V1_13 {
/// Inner iterator into section map
iter: btree_map::Iter<'a, SectionY, (SectionV1_13<'a>, BlockLight<'a>)>,
/// Chunk biome data
biomes: &'a BiomesV0<'a>,
},
/// Iterator over sections of [Chunk::V0]
V0 {
/// Inner iterator into section map
iter: btree_map::Iter<'a, SectionY, (SectionV0<'a>, BlockLight<'a>)>,
/// Chunk biome data
biomes: &'a BiomesV0<'a>,
},
/// Empty iterator over an unpopulated chunk ([Chunk::Empty])
@ -64,6 +79,7 @@ enum SectionIterInner<'a> {
/// Iterator over the sections of a [Chunk]
#[derive(Debug, Clone)]
pub struct SectionIter<'a> {
/// Inner iterator enum
inner: SectionIterInner<'a>,
}
@ -193,6 +209,7 @@ impl<'a> Chunk<'a> {
)
}
/// Returns true if the chunk does not contain any sections
pub fn is_empty(&self) -> bool {
match self {
Chunk::V1_18 { section_map } => section_map.is_empty(),
@ -230,14 +247,20 @@ impl<'a> Chunk<'a> {
}
}
/// Reference to block, biome and block light data of a section
#[derive(Debug, Clone, Copy)]
pub struct SectionIterItem<'a> {
/// The Y coordinate of the section
pub y: SectionY,
/// Section block data
pub section: &'a dyn Section,
/// Section biome data
pub biomes: &'a dyn Biomes,
/// Section block light data
pub block_light: BlockLight<'a>,
}
/// Helper trait to specify section iterator trait bounds
trait SectionIterTrait<'a>:
Iterator<Item = SectionIterItem<'a>> + DoubleEndedIterator + ExactSizeIterator + FusedIterator
{
@ -252,6 +275,7 @@ impl<'a, T> SectionIterTrait<'a> for T where
}
impl<'a> SectionIter<'a> {
/// Helper to run a closure on the inner section iterator
fn with_iter<F, T>(&mut self, f: F) -> T
where
F: FnOnce(&mut dyn SectionIterTrait<'a>) -> T,

View file

@ -6,30 +6,39 @@ use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct BlockStatePaletteEntry {
/// Block type ID
pub name: String,
}
/// 1.18+ `block_states` element found in a [section](SectionV1_18)
#[derive(Debug, Deserialize)]
pub struct BlockStatesV1_18 {
/// Palette of block types, indexed by block data
pub palette: Vec<BlockStatePaletteEntry>,
/// Block data
pub data: Option<fastnbt::LongArray>,
}
/// 1.18+ `biomes` element found in a [section](SectionV1_18)
#[derive(Debug, Deserialize)]
pub struct BiomesV1_18 {
/// Palette of biome types, indexed by biome data
pub palette: Vec<String>,
/// Biome data
pub data: Option<fastnbt::LongArray>,
}
/// Element of the 1.18+ `sections` list found in a [Chunk]
#[derive(Debug, Deserialize)]
pub struct SectionV1_18 {
/// Y coordinate
#[serde(rename = "Y")]
pub y: i32,
/// Block type data
pub block_states: BlockStatesV1_18,
/// Biome data
pub biomes: BiomesV1_18,
/// Block light data
#[serde(rename = "BlockLight")]
pub block_light: Option<fastnbt::ByteArray>,
}
@ -38,16 +47,23 @@ pub struct SectionV1_18 {
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum SectionV0Variants {
/// v1.13+ data
#[serde(rename_all = "PascalCase")]
V1_13 {
/// Block data
block_states: fastnbt::LongArray,
/// Block type palette, indexed by block data
palette: Vec<BlockStatePaletteEntry>,
},
/// Pre-1.13 data
#[serde(rename_all = "PascalCase")]
V0 {
/// Block type data
blocks: fastnbt::ByteArray,
/// Block damage / subtype data
data: fastnbt::ByteArray,
},
/// Empty section
Empty {},
}
@ -55,8 +71,11 @@ pub enum SectionV0Variants {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SectionV0 {
/// Y coordinate
pub y: i8,
/// Block light data
pub block_light: Option<fastnbt::ByteArray>,
/// Version-specific data
#[serde(flatten)]
pub section: SectionV0Variants,
}
@ -65,7 +84,9 @@ pub struct SectionV0 {
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum BiomesV0 {
/// Data for Minecraft versions storing biome data as an IntArray
IntArray(fastnbt::IntArray),
/// Data for Minecraft versions storing biome data as an ByteArray
ByteArray(fastnbt::ByteArray),
}
@ -73,8 +94,10 @@ pub enum BiomesV0 {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct LevelV0 {
/// Section data
#[serde(default)]
pub sections: Vec<SectionV0>,
/// Biome data
pub biomes: Option<BiomesV0>,
}
@ -82,11 +105,15 @@ pub struct LevelV0 {
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum ChunkVariants {
/// 1.18+ chunk data
V1_18 {
/// List of chunk sections
sections: Vec<SectionV1_18>,
},
/// Pre-1.18 chunk data
#[serde(rename_all = "PascalCase")]
V0 {
/// `Level` field of the chunk
level: LevelV0,
},
}
@ -95,16 +122,20 @@ pub enum ChunkVariants {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Chunk {
/// The data version of the chunk
pub data_version: Option<u32>,
/// Version-specific chunk data
#[serde(flatten)]
pub chunk: ChunkVariants,
}
/// "Data" compound element of level.dat
/// `Data` compound element of level.dat
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct LevelDatData {
/// X coordinate of spawn point for new players
pub spawn_x: i32,
/// Z coordinate of spawn point for new players
pub spawn_z: i32,
}
@ -112,5 +143,6 @@ pub struct LevelDatData {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct LevelDat {
/// The `Data` field
pub data: LevelDatData,
}

View file

@ -1,3 +1,5 @@
//! Functions to search the "top" layer of a chunk
use std::num::NonZeroU16;
use anyhow::{Context, Result};
@ -10,6 +12,7 @@ use crate::{
types::*,
};
/// Height (Y coordinate) of a block
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockHeight(pub i32);
@ -28,27 +31,52 @@ impl BlockHeight {
}
}
/// Array optionally storing a [BlockType] for each coordinate of a chunk
pub type BlockArray = LayerBlockArray<Option<BlockType>>;
/// Array optionally storing a biome index for each coordinate of a chunk
///
/// The entries refer to a biome list generated with the top layer data.
/// Indices are stored incremented by 1 to allow using a [NonZeroU16].
pub type BiomeArray = LayerBlockArray<Option<NonZeroU16>>;
/// Array storing a block light value for each coordinate for a chunk
pub type BlockLightArray = LayerBlockArray<u8>;
/// Array optionally storing a depth value for each coordinate for a chunk
pub type DepthArray = LayerBlockArray<Option<BlockHeight>>;
/// References to LayerData entries for a single coordinate pair
struct LayerEntry<'a> {
/// The block type of the referenced entry
block: &'a mut Option<BlockType>,
/// The biome type of the referenced entry
biome: &'a mut Option<NonZeroU16>,
/// The block light of the referenced entry
block_light: &'a mut u8,
/// The depth value of the referenced entry
depth: &'a mut Option<BlockHeight>,
}
impl<'a> LayerEntry<'a> {
/// Returns true if the entry has not been filled yet (no opaque block has been encountered)
///
/// The depth value is filled separately when a non-water block is encountered after the block type
/// has already been filled.
fn is_empty(&self) -> bool {
self.block.is_none()
}
/// Returns true if the entry has been filled including its depth (an opaque non-water block has been
/// encountered)
fn done(&self) -> bool {
self.depth.is_some()
}
/// Fills in the LayerEntry
///
/// Checks whether the passed coordinates point at an opaque or non-water block and
/// fills in the entry accordingly. Returns true when the block has been filled including its depth.
fn fill(
&mut self,
biome_list: &mut IndexSet<Biome>,
@ -90,15 +118,24 @@ impl<'a> LayerEntry<'a> {
}
}
/// Top layer data
///
/// A LayerData stores block type, biome, block light and depth data for
/// each coordinate of a chunk.
#[derive(Debug, Default)]
pub struct LayerData {
/// Block type data
pub blocks: Box<BlockArray>,
/// Biome data
pub biomes: Box<BiomeArray>,
/// Block light data
pub block_light: Box<BlockLightArray>,
/// Depth data
pub depths: Box<DepthArray>,
}
impl LayerData {
/// Builds a [LayerEntry] referencing the LayerData at a given coordinate pair
fn entry(&mut self, coords: LayerBlockCoords) -> LayerEntry {
LayerEntry {
block: &mut self.blocks[coords],
@ -109,13 +146,14 @@ impl LayerData {
}
}
/// Fills in a [BlockInfoArray] with the information of the chunk's top
/// Fills in a [LayerData] with the information of the chunk's top
/// block layer
///
/// For each (X, Z) coordinate pair, the topmost opaque block is
/// determined as the block that should be visible on the rendered
/// map. For water blocks, the height of the first non-water block
/// is additionally filled in as the water depth.
/// is additionally filled in as the water depth (the block height is
/// used as depth otherwise).
pub fn top_layer(biome_list: &mut IndexSet<Biome>, chunk: &Chunk) -> Result<Option<LayerData>> {
use BLOCKS_PER_CHUNK as N;

View file

@ -1,3 +1,5 @@
//! Data structures describing Minecraft save data
pub mod chunk;
pub mod de;
pub mod layer;

View file

@ -1,3 +1,8 @@
//! Higher-level interfaces to section data
//!
//! The data types in this module attempt to provide interfaces abstracting
//! over different data versions as much as possible.
use std::fmt::Debug;
use anyhow::{bail, Context, Result};
@ -9,6 +14,14 @@ use crate::{
types::*,
};
use BLOCKS_PER_CHUNK as N;
/// Maximum height of pre-1.18 levels
const HEIGHT: usize = 256;
/// Number of biome entries per chunk in each direction
const BN: usize = N >> 2;
/// Pre-1.18 height of level measured in 4-block spans (resolution of 1.15+ biome data)
const BHEIGHT: usize = HEIGHT >> 2;
/// Determine the number of bits required for indexing into a palette of a given length
///
/// This is basically a base-2 logarithm, with clamping to a minimum value and
@ -29,20 +42,31 @@ fn palette_bits(len: usize, min: u8, max: u8) -> Option<u8> {
/// Trait for common functions of [SectionV1_13] and [SectionV0]
pub trait Section: Debug {
/// Returns the [BlockType] at a coordinate tuple inside the section
fn block_at(&self, coords: SectionBlockCoords) -> Result<Option<BlockType>>;
}
/// Minecraft v1.13+ section block data
#[derive(Debug)]
pub struct SectionV1_13<'a> {
/// Packed block type data
block_states: Option<&'a [i64]>,
/// List of block types indexed by entries encoded in *block_states*
palette: Vec<Option<BlockType>>,
/// Number of bits per block in *block_states*
bits: u8,
/// Set to true if packed block entries in *block_states* are aligned to i64
///
/// In older data formats, entries are unaligned and a single block can span
/// two i64 entries.
aligned_blocks: bool,
}
impl<'a> SectionV1_13<'a> {
/// Constructs a new [SectionV1_13] from deserialized data structures
///
/// The block IDs in the section's palette are resolved to their [BlockType]s
/// to allow for faster lookup later.
pub fn new(
data_version: u32,
block_states: Option<&'a [i64]>,
@ -127,16 +151,21 @@ impl<'a> Section for SectionV1_13<'a> {
/// Pre-1.13 section block data
#[derive(Debug)]
pub struct SectionV0<'a> {
/// Block type data
///
/// Each i8 entry corresponds to a block in the 16x16x16 section
blocks: &'a [i8],
/// Block damage/subtype data
///
/// Uses 4 bits for each block in the 16x16x16 section
data: &'a [i8],
/// Used to look up block type IDs
block_types: &'a BlockTypes,
}
impl<'a> SectionV0<'a> {
/// Constructs a new [SectionV0] from deserialized data structures
pub fn new(blocks: &'a [i8], data: &'a [i8], block_types: &'a BlockTypes) -> Result<Self> {
use BLOCKS_PER_CHUNK as N;
if blocks.len() != N * N * N {
bail!("Invalid section block data");
}
@ -171,6 +200,7 @@ impl<'a> Section for SectionV0<'a> {
/// Trait for common functions of [BiomesV1_18] and [BiomesV0]
pub trait Biomes: Debug {
/// Returns the [Biome] at a coordinate tuple inside the chunk
fn biome_at(&self, section: SectionY, coords: SectionBlockCoords) -> Result<Option<&Biome>>;
}
@ -181,13 +211,21 @@ pub trait Biomes: Debug {
/// v1.13+ block data.
#[derive(Debug)]
pub struct BiomesV1_18<'a> {
/// Packed biome data
///
/// Each entry specifies the biome of a 4x4x4 block area.
///
/// Unlike block type data in [SectionV1_13], biome data is always aligned
/// to whole i64 values.
biomes: Option<&'a [i64]>,
/// Biome palette indexed by entries encoded in *biomes*
palette: Vec<Option<&'a Biome>>,
/// Number of bits used for each entry in *biomes*
bits: u8,
}
impl<'a> BiomesV1_18<'a> {
/// Constructs a new [BiomesV18] from deserialized data structures
/// Constructs a new [BiomesV1_18] from deserialized data structures
pub fn new(
biomes: Option<&'a [i64]>,
palette: &'a [String],
@ -223,9 +261,6 @@ impl<'a> BiomesV1_18<'a> {
/// Looks up the block type palette index at the given coordinates
fn palette_index_at(&self, coords: SectionBlockCoords) -> usize {
const N: usize = BLOCKS_PER_CHUNK;
const BN: usize = N >> 2;
let Some(biomes) = self.biomes else {
return 0;
};
@ -262,28 +297,31 @@ impl<'a> Biomes for BiomesV1_18<'a> {
/// different pre-v1.18 Minecraft versions
#[derive(Debug)]
enum BiomesV0Data<'a> {
/// Biome data stored as IntArray in 1.15+ format
///
/// Minecraft 1.15 switched to 3-dimensional biome information, but reduced
/// the resolution to only use one entry for every 4x4x4 block area.
IntArrayV15(&'a fastnbt::IntArray),
/// Biome data stored as IntArray in some pre-1.15 versions
IntArrayV0(&'a fastnbt::IntArray),
/// Biome data stored as ByteArray in some pre-1.15 versions
ByteArray(&'a fastnbt::ByteArray),
}
/// Pre-v1.18 section biome data
#[derive(Debug)]
pub struct BiomesV0<'a> {
/// Biome data from save data
data: BiomesV0Data<'a>,
/// Used to look up biome IDs
biome_types: &'a BiomeTypes,
}
impl<'a> BiomesV0<'a> {
/// Constructs a new [BiomesV0] from deserialized data structures
pub fn new(biomes: Option<&'a de::BiomesV0>, biome_types: &'a BiomeTypes) -> Result<Self> {
const N: usize = BLOCKS_PER_CHUNK;
const MAXY: usize = 256;
const BN: usize = N >> 2;
const BMAXY: usize = MAXY >> 2;
let data = match biomes {
Some(de::BiomesV0::IntArray(data)) if data.len() == BN * BN * BMAXY => {
Some(de::BiomesV0::IntArray(data)) if data.len() == BN * BN * BHEIGHT => {
BiomesV0Data::IntArrayV15(data)
}
Some(de::BiomesV0::IntArray(data)) if data.len() == N * N => {
@ -302,16 +340,12 @@ impl<'a> Biomes for BiomesV0<'a> {
fn biome_at(&self, section: SectionY, coords: SectionBlockCoords) -> Result<Option<&Biome>> {
let id = match self.data {
BiomesV0Data::IntArrayV15(data) => {
const N: usize = BLOCKS_PER_CHUNK;
const MAXY: usize = 256;
const BN: usize = N >> 2;
let LayerBlockCoords { x, z } = coords.xz;
let y = section
.0
.checked_mul(BLOCKS_PER_CHUNK as i32)
.and_then(|y| y.checked_add_unsigned(coords.y.0.into()))
.filter(|&height| height >= 0 && (height as usize) < MAXY)
.filter(|&height| height >= 0 && (height as usize) < HEIGHT)
.context("Y coordinate out of range")? as usize;
let offset = (y >> 2) * BN * BN + (z.0 >> 2) as usize * BN + (x.0 >> 2) as usize;
let id = data[offset] as u32;
@ -327,12 +361,13 @@ impl<'a> Biomes for BiomesV0<'a> {
}
}
/// Wrapper around chunk block light data array
#[derive(Debug, Clone, Copy)]
pub struct BlockLight<'a>(Option<&'a [i8]>);
impl<'a> BlockLight<'a> {
/// Creates a new [BlockLight], checking validity
pub fn new(block_light: Option<&'a [i8]>) -> Result<Self> {
use BLOCKS_PER_CHUNK as N;
if let Some(block_light) = block_light {
if block_light.len() != N * N * N / 2 {
bail!("Invalid section block light data");
@ -341,6 +376,7 @@ impl<'a> BlockLight<'a> {
Ok(BlockLight(block_light))
}
/// Returns the block light value at the given coordinates
pub fn block_light_at(&self, coords: SectionBlockCoords) -> u8 {
let Some(block_light) = self.0 else {
return 0;