use crate::world_segment::ids::TileId;
use crate::world_segment::tile::{TileBounds, VoxelTile};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Access {
Random,
Forward,
}
#[derive(thiserror::Error, Debug)]
pub enum TileError {
#[error("io: {0}")]
Io(String),
#[error("source does not support random access; use for_each_tile")]
NotRandomAccess,
#[error("malformed source: {0}")]
Malformed(String),
#[error("iteration stopped by callback")]
Stop,
}
pub const REGION_BLOCKS: i32 = 512;
pub fn region_tile_bounds(
region_x: i32,
region_z: i32,
min_y: i32,
max_y: i32,
) -> (TileId, TileBounds) {
let x0 = region_x * REGION_BLOCKS;
let z0 = region_z * REGION_BLOCKS;
(
TileId {
x: region_x,
z: region_z,
},
TileBounds {
min: (x0, min_y, z0),
max: (x0 + REGION_BLOCKS - 1, max_y, z0 + REGION_BLOCKS - 1),
},
)
}
pub trait TileSource {
fn access(&self) -> Access;
fn tile_ids(&self) -> Result<Vec<TileId>, TileError>;
fn tile(&self, id: TileId) -> Result<Option<VoxelTile>, TileError>;
fn for_each_tile(
&self,
f: &mut dyn FnMut(VoxelTile) -> Result<(), TileError>,
) -> Result<(), TileError> {
for id in self.tile_ids()? {
if let Some(t) = self.tile(id)? {
match f(t) {
Ok(()) => {}
Err(TileError::Stop) => return Ok(()),
Err(e) => return Err(e),
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn region_tile_bounds_maps_region_to_512_block_span() {
let (id, b) = region_tile_bounds(0, 0, -64, 319);
assert_eq!(id, crate::world_segment::ids::TileId { x: 0, z: 0 });
assert_eq!(b.min, (0, -64, 0));
assert_eq!(b.max, (511, 319, 511));
let (id2, b2) = region_tile_bounds(-1, 0, -64, 319);
assert_eq!(id2, crate::world_segment::ids::TileId { x: -1, z: 0 });
assert_eq!(b2.min, (-512, -64, 0));
assert_eq!(b2.max, (-1, 319, 511));
}
#[test]
fn access_is_copy_and_comparable() {
assert_eq!(Access::Random, Access::Random);
assert_ne!(Access::Random, Access::Forward);
let a = Access::Forward;
let _b = a; assert_eq!(a, Access::Forward);
}
struct ThreeTileSource;
impl TileSource for ThreeTileSource {
fn access(&self) -> Access {
Access::Random
}
fn tile_ids(&self) -> Result<Vec<TileId>, TileError> {
Ok(vec![
TileId { x: 0, z: 0 },
TileId { x: 1, z: 0 },
TileId { x: 2, z: 0 },
])
}
fn tile(&self, id: TileId) -> Result<Option<VoxelTile>, TileError> {
Ok(Some(VoxelTile::from_blocks(
id,
TileBounds {
min: (0, 0, 0),
max: (15, 15, 15),
},
std::iter::once((
(0, 0, 0),
crate::block_state::BlockState::new("minecraft:stone"),
)),
)))
}
}
#[test]
fn default_for_each_tile_stops_on_stop_sentinel_and_reports_ok() {
let source = ThreeTileSource;
let mut call_count = 0usize;
let result = source.for_each_tile(&mut |_tile| {
call_count += 1;
Err(TileError::Stop)
});
assert!(
result.is_ok(),
"Stop must not propagate as an error: got {result:?}"
);
assert_eq!(
call_count, 1,
"iteration must stop right after the Stop-returning call"
);
}
}