mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
Split Chunk
into loaded and unloaded types (#37)
These changes will pave the way for nonblocking terrain generation in `terrain.rs`.
This commit is contained in:
parent
a4c8b282a5
commit
20546e2fb8
|
@ -3,6 +3,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use valence::block::{BlockPos, BlockState};
|
use valence::block::{BlockPos, BlockState};
|
||||||
|
use valence::chunk::{Chunk, UnloadedChunk};
|
||||||
use valence::client::{
|
use valence::client::{
|
||||||
default_client_event, ClientEvent, ClientId, GameMode, InteractWithEntityKind,
|
default_client_event, ClientEvent, ClientId, GameMode, InteractWithEntityKind,
|
||||||
};
|
};
|
||||||
|
@ -86,13 +87,16 @@ impl Config for Game {
|
||||||
let (_, world) = server.worlds.insert(DimensionId::default(), ());
|
let (_, world) = server.worlds.insert(DimensionId::default(), ());
|
||||||
server.state = Some(server.player_lists.insert(()).0);
|
server.state = Some(server.player_lists.insert(()).0);
|
||||||
|
|
||||||
let min_y = server.shared.dimension(DimensionId::default()).min_y;
|
let dim = server.shared.dimension(DimensionId::default());
|
||||||
|
let min_y = dim.min_y;
|
||||||
|
let height = dim.height as usize;
|
||||||
|
|
||||||
// Create circular arena.
|
// Create circular arena.
|
||||||
let size = 2;
|
let size = 2;
|
||||||
for chunk_z in -size - 2..size + 2 {
|
for chunk_z in -size - 2..size + 2 {
|
||||||
for chunk_x in -size - 2..size + 2 {
|
for chunk_x in -size - 2..size + 2 {
|
||||||
let chunk = world.chunks.insert([chunk_x, chunk_z], ());
|
let mut chunk = UnloadedChunk::new(height);
|
||||||
|
|
||||||
let r = -size..size;
|
let r = -size..size;
|
||||||
if r.contains(&chunk_x) && r.contains(&chunk_z) {
|
if r.contains(&chunk_x) && r.contains(&chunk_z) {
|
||||||
for z in 0..16 {
|
for z in 0..16 {
|
||||||
|
@ -107,6 +111,8 @@ impl Config for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
world.chunks.insert([chunk_x, chunk_z], chunk, ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ use num::Integer;
|
||||||
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||||
use valence::biome::Biome;
|
use valence::biome::Biome;
|
||||||
use valence::block::BlockState;
|
use valence::block::BlockState;
|
||||||
|
use valence::chunk::{Chunk, UnloadedChunk};
|
||||||
use valence::client::{default_client_event, ClientEvent, Hand};
|
use valence::client::{default_client_event, ClientEvent, Hand};
|
||||||
use valence::config::{Config, ServerListPing};
|
use valence::config::{Config, ServerListPing};
|
||||||
use valence::dimension::{Dimension, DimensionId};
|
use valence::dimension::{Dimension, DimensionId};
|
||||||
|
@ -109,7 +110,11 @@ impl Config for Game {
|
||||||
|
|
||||||
for chunk_z in -2..Integer::div_ceil(&(SIZE_Z as i32), &16) + 2 {
|
for chunk_z in -2..Integer::div_ceil(&(SIZE_Z as i32), &16) + 2 {
|
||||||
for chunk_x in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 {
|
for chunk_x in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 {
|
||||||
world.chunks.insert((chunk_x as i32, chunk_z as i32), ());
|
world.chunks.insert(
|
||||||
|
[chunk_x as i32, chunk_z as i32],
|
||||||
|
UnloadedChunk::default(),
|
||||||
|
(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use valence::async_trait;
|
use valence::async_trait;
|
||||||
use valence::block::{BlockPos, BlockState};
|
use valence::block::{BlockPos, BlockState};
|
||||||
|
use valence::chunk::UnloadedChunk;
|
||||||
use valence::client::{default_client_event, GameMode};
|
use valence::client::{default_client_event, GameMode};
|
||||||
use valence::config::{Config, ServerListPing};
|
use valence::config::{Config, ServerListPing};
|
||||||
use valence::dimension::DimensionId;
|
use valence::dimension::DimensionId;
|
||||||
|
@ -80,7 +81,7 @@ impl Config for Game {
|
||||||
let size = 5;
|
let size = 5;
|
||||||
for z in -size..size {
|
for z in -size..size {
|
||||||
for x in -size..size {
|
for x in -size..size {
|
||||||
world.chunks.insert([x, z], ());
|
world.chunks.insert([x, z], UnloadedChunk::default(), ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use valence::async_trait;
|
use valence::async_trait;
|
||||||
use valence::block::{BlockPos, BlockState};
|
use valence::block::{BlockPos, BlockState};
|
||||||
|
use valence::chunk::UnloadedChunk;
|
||||||
use valence::client::{default_client_event, GameMode};
|
use valence::client::{default_client_event, GameMode};
|
||||||
use valence::config::{Config, ServerListPing};
|
use valence::config::{Config, ServerListPing};
|
||||||
use valence::dimension::DimensionId;
|
use valence::dimension::DimensionId;
|
||||||
|
@ -78,7 +79,7 @@ impl Config for Game {
|
||||||
let size = 5;
|
let size = 5;
|
||||||
for z in -size..size {
|
for z in -size..size {
|
||||||
for x in -size..size {
|
for x in -size..size {
|
||||||
world.chunks.insert([x, z], ());
|
world.chunks.insert([x, z], UnloadedChunk::default(), ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use noise::{NoiseFn, Seedable, SuperSimplex};
|
||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::ParallelIterator;
|
||||||
use valence::async_trait;
|
use valence::async_trait;
|
||||||
use valence::block::{BlockState, PropName, PropValue};
|
use valence::block::{BlockState, PropName, PropValue};
|
||||||
use valence::chunk::ChunkPos;
|
use valence::chunk::{Chunk, ChunkPos, UnloadedChunk};
|
||||||
use valence::client::{default_client_event, GameMode};
|
use valence::client::{default_client_event, GameMode};
|
||||||
use valence::config::{Config, ServerListPing};
|
use valence::config::{Config, ServerListPing};
|
||||||
use valence::dimension::DimensionId;
|
use valence::dimension::DimensionId;
|
||||||
|
@ -151,7 +151,7 @@ impl Config for Game {
|
||||||
if let Some(chunk) = world.chunks.get_mut(pos) {
|
if let Some(chunk) = world.chunks.get_mut(pos) {
|
||||||
chunk.state = true;
|
chunk.state = true;
|
||||||
} else {
|
} else {
|
||||||
world.chunks.insert(pos, true);
|
world.chunks.insert(pos, UnloadedChunk::default(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
417
src/chunk.rs
417
src/chunk.rs
|
@ -1,4 +1,10 @@
|
||||||
//! Chunks and related types.
|
//! Chunks and related types.
|
||||||
|
//!
|
||||||
|
//! A chunk is a 16x16-block segment of a world with a height determined by the
|
||||||
|
//! [`Dimension`](crate::dimension::Dimension) of the world.
|
||||||
|
//!
|
||||||
|
//! In addition to blocks, chunks also contain [biomes](crate::biome::Biome).
|
||||||
|
//! Every 4x4x4 segment of blocks in a chunk corresponds to a biome.
|
||||||
|
|
||||||
// TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil
|
// TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil
|
||||||
|
|
||||||
|
@ -23,9 +29,9 @@ use crate::protocol::packets::s2c::play::{
|
||||||
use crate::protocol::{Encode, NbtBridge, VarInt, VarLong};
|
use crate::protocol::{Encode, NbtBridge, VarInt, VarLong};
|
||||||
use crate::server::SharedServer;
|
use crate::server::SharedServer;
|
||||||
|
|
||||||
/// A container for all [`Chunk`]s in a [`World`](crate::world::World).
|
/// A container for all [`LoadedChunk`]s in a [`World`](crate::world::World).
|
||||||
pub struct Chunks<C: Config> {
|
pub struct Chunks<C: Config> {
|
||||||
chunks: HashMap<ChunkPos, Chunk<C>>,
|
chunks: HashMap<ChunkPos, LoadedChunk<C>>,
|
||||||
shared: SharedServer<C>,
|
shared: SharedServer<C>,
|
||||||
dimension: DimensionId,
|
dimension: DimensionId,
|
||||||
}
|
}
|
||||||
|
@ -39,36 +45,49 @@ impl<C: Config> Chunks<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an empty chunk at the provided position and returns a mutable
|
/// Consumes an [`UnloadedChunk`] and creates a [`LoadedChunk`] at a given
|
||||||
/// reference to it.
|
/// position. An exclusive reference to the new chunk is returned.
|
||||||
///
|
///
|
||||||
/// If a chunk at the position already exists, then the old chunk
|
/// If a chunk at the position already exists, then the old chunk
|
||||||
/// is overwritten.
|
/// is overwritten and its contents are dropped.
|
||||||
|
///
|
||||||
|
/// The given chunk is resized to match the height of the world as if by
|
||||||
|
/// calling [`UnloadedChunk::resize`].
|
||||||
///
|
///
|
||||||
/// **Note**: For the vanilla Minecraft client to see a chunk, all chunks
|
/// **Note**: For the vanilla Minecraft client to see a chunk, all chunks
|
||||||
/// adjacent to it must also be loaded. It is also important that clients
|
/// adjacent to it must also be loaded. Clients should not be spawned within
|
||||||
/// are not spawned within unloaded chunks via
|
/// unloaded chunks via [`spawn`](crate::client::Client::spawn).
|
||||||
/// [`spawn`](crate::client::Client::spawn).
|
pub fn insert(
|
||||||
pub fn insert(&mut self, pos: impl Into<ChunkPos>, state: C::ChunkState) -> &mut Chunk<C> {
|
&mut self,
|
||||||
let section_count = (self.shared.dimension(self.dimension).height / 16) as u32;
|
pos: impl Into<ChunkPos>,
|
||||||
|
chunk: UnloadedChunk,
|
||||||
|
state: C::ChunkState,
|
||||||
|
) -> &mut LoadedChunk<C> {
|
||||||
|
let dimension_section_count = (self.shared.dimension(self.dimension).height / 16) as usize;
|
||||||
let biome_registry_len = self.shared.biomes().len();
|
let biome_registry_len = self.shared.biomes().len();
|
||||||
let chunk = Chunk::new(section_count, biome_registry_len, state);
|
let loaded = LoadedChunk::new(chunk, dimension_section_count, biome_registry_len, state);
|
||||||
|
|
||||||
match self.chunks.entry(pos.into()) {
|
match self.chunks.entry(pos.into()) {
|
||||||
Entry::Occupied(mut oe) => {
|
Entry::Occupied(mut oe) => {
|
||||||
oe.insert(chunk);
|
oe.insert(loaded);
|
||||||
oe.into_mut()
|
oe.into_mut()
|
||||||
}
|
}
|
||||||
Entry::Vacant(ve) => ve.insert(chunk),
|
Entry::Vacant(ve) => ve.insert(loaded),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a chunk at the provided position.
|
/// Removes a chunk at the provided position.
|
||||||
///
|
///
|
||||||
/// If a chunk exists at the position, then it is deleted and its
|
/// If a chunk exists at the position, then it is removed from the world and
|
||||||
/// `ChunkState` is returned. Otherwise, `None` is returned.
|
/// its content is returned. Otherwise, `None` is returned.
|
||||||
pub fn remove(&mut self, pos: impl Into<ChunkPos>) -> Option<C::ChunkState> {
|
pub fn remove(&mut self, pos: impl Into<ChunkPos>) -> Option<(UnloadedChunk, C::ChunkState)> {
|
||||||
self.chunks.remove(&pos.into()).map(|c| c.state)
|
let loaded = self.chunks.remove(&pos.into())?;
|
||||||
|
|
||||||
|
let unloaded = UnloadedChunk {
|
||||||
|
sections: loaded.sections.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((unloaded, loaded.state))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of loaded chunks.
|
/// Returns the number of loaded chunks.
|
||||||
|
@ -84,21 +103,21 @@ impl<C: Config> Chunks<C> {
|
||||||
/// Gets a shared reference to the chunk at the provided position.
|
/// Gets a shared reference to the chunk at the provided position.
|
||||||
///
|
///
|
||||||
/// If there is no chunk at the position, then `None` is returned.
|
/// If there is no chunk at the position, then `None` is returned.
|
||||||
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk<C>> {
|
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&LoadedChunk<C>> {
|
||||||
self.chunks.get(&pos.into())
|
self.chunks.get(&pos.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets an exclusive reference to the chunk at the provided position.
|
/// Gets an exclusive reference to the chunk at the provided position.
|
||||||
///
|
///
|
||||||
/// If there is no chunk at the position, then `None` is returned.
|
/// If there is no chunk at the position, then `None` is returned.
|
||||||
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk<C>> {
|
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut LoadedChunk<C>> {
|
||||||
self.chunks.get_mut(&pos.into())
|
self.chunks.get_mut(&pos.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes all chunks for which `f` returns `true`.
|
/// Removes all chunks for which `f` returns `true`.
|
||||||
///
|
///
|
||||||
/// All chunks are visited in an unspecified order.
|
/// All chunks are visited in an unspecified order.
|
||||||
pub fn retain(&mut self, mut f: impl FnMut(ChunkPos, &mut Chunk<C>) -> bool) {
|
pub fn retain(&mut self, mut f: impl FnMut(ChunkPos, &mut LoadedChunk<C>) -> bool) {
|
||||||
self.chunks.retain(|&pos, chunk| f(pos, chunk))
|
self.chunks.retain(|&pos, chunk| f(pos, chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +130,8 @@ impl<C: Config> Chunks<C> {
|
||||||
/// order.
|
/// order.
|
||||||
pub fn iter(
|
pub fn iter(
|
||||||
&self,
|
&self,
|
||||||
) -> impl ExactSizeIterator<Item = (ChunkPos, &Chunk<C>)> + FusedIterator + Clone + '_ {
|
) -> impl ExactSizeIterator<Item = (ChunkPos, &LoadedChunk<C>)> + FusedIterator + Clone + '_
|
||||||
|
{
|
||||||
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
|
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,23 +139,27 @@ impl<C: Config> Chunks<C> {
|
||||||
/// unspecified order.
|
/// unspecified order.
|
||||||
pub fn iter_mut(
|
pub fn iter_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> impl ExactSizeIterator<Item = (ChunkPos, &mut Chunk<C>)> + FusedIterator + '_ {
|
) -> impl ExactSizeIterator<Item = (ChunkPos, &mut LoadedChunk<C>)> + FusedIterator + '_ {
|
||||||
self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a parallel iterator over all chunks in the world in an
|
/// Returns a parallel iterator over all chunks in the world in an
|
||||||
/// unspecified order.
|
/// unspecified order.
|
||||||
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk<C>)> + Clone + '_ {
|
pub fn par_iter(
|
||||||
|
&self,
|
||||||
|
) -> impl ParallelIterator<Item = (ChunkPos, &LoadedChunk<C>)> + Clone + '_ {
|
||||||
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
|
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a parallel mutable iterator over all chunks in the world in an
|
/// Returns a parallel mutable iterator over all chunks in the world in an
|
||||||
/// unspecified order.
|
/// unspecified order.
|
||||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk<C>)> + '_ {
|
pub fn par_iter_mut(
|
||||||
|
&mut self,
|
||||||
|
) -> impl ParallelIterator<Item = (ChunkPos, &mut LoadedChunk<C>)> + '_ {
|
||||||
self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the block state at a position.
|
/// Gets the block state at a global position in the world.
|
||||||
///
|
///
|
||||||
/// If the position is not inside of a chunk, then `None` is returned.
|
/// If the position is not inside of a chunk, then `None` is returned.
|
||||||
///
|
///
|
||||||
|
@ -162,10 +186,11 @@ impl<C: Config> Chunks<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the block state at a position.
|
/// Sets the block state at a global position in the world.
|
||||||
///
|
///
|
||||||
/// If the position is inside of a chunk, then `true` is returned.
|
/// If the position is inside of a chunk, then `true` is returned.
|
||||||
/// Otherwise, `false` is returned.
|
/// Otherwise, `false` is returned. The function has no effect if `false` is
|
||||||
|
/// returned.
|
||||||
///
|
///
|
||||||
/// Note: if you need to set a large number of blocks, it may be more
|
/// Note: if you need to set a large number of blocks, it may be more
|
||||||
/// efficient write to the chunks directly with
|
/// efficient write to the chunks directly with
|
||||||
|
@ -215,57 +240,119 @@ impl<C: Config> Chunks<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A chunk is a 16x16-block segment of a world with a height determined by the
|
/// Operations that can be performed on a chunk. [`LoadedChunk`] and
|
||||||
/// [`Dimension`](crate::dimension::Dimension) of the world.
|
/// [`UnloadedChunk`] implement this trait.
|
||||||
///
|
pub trait Chunk {
|
||||||
/// In addition to blocks, chunks also contain [biomes](crate::biome::Biome).
|
/// Returns the height of this chunk in blocks. The result is always a
|
||||||
/// Every 4x4x4 segment of blocks in a chunk corresponds to a biome.
|
/// multiple of 16.
|
||||||
pub struct Chunk<C: Config> {
|
fn height(&self) -> usize;
|
||||||
/// Custom state.
|
|
||||||
pub state: C::ChunkState,
|
|
||||||
sections: Box<[ChunkSection]>,
|
|
||||||
// TODO block_entities: HashMap<u32, BlockEntity>,
|
|
||||||
/// The MOTION_BLOCKING heightmap
|
|
||||||
heightmap: Vec<i64>,
|
|
||||||
created_this_tick: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Config> Chunk<C> {
|
|
||||||
fn new(section_count: u32, biome_registry_len: usize, data: C::ChunkState) -> Self {
|
|
||||||
let sect = ChunkSection {
|
|
||||||
blocks: [BlockState::AIR.to_raw(); 4096],
|
|
||||||
modified_count: 1, // Must be >0 so the chunk is initialized.
|
|
||||||
biomes: [BiomeId::default(); 64],
|
|
||||||
compact_data: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut chunk = Self {
|
|
||||||
state: data,
|
|
||||||
sections: vec![sect; section_count as usize].into(),
|
|
||||||
heightmap: Vec::new(),
|
|
||||||
created_this_tick: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
chunk.apply_modifications(biome_registry_len);
|
|
||||||
chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this chunk was created during the current tick.
|
|
||||||
pub fn created_this_tick(&self) -> bool {
|
|
||||||
self.created_this_tick
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the height of this chunk in blocks.
|
|
||||||
pub fn height(&self) -> usize {
|
|
||||||
self.sections.len() * 16
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the block state at the provided offsets in the chunk.
|
/// Gets the block state at the provided offsets in the chunk.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the offsets are outside the bounds of the chunk.
|
/// Panics if the offsets are outside the bounds of the chunk.
|
||||||
pub fn get_block_state(&self, x: usize, y: usize, z: usize) -> BlockState {
|
fn get_block_state(&self, x: usize, y: usize, z: usize) -> BlockState;
|
||||||
|
|
||||||
|
/// Sets the block state at the provided offsets in the chunk.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the offsets are outside the bounds of the chunk.
|
||||||
|
fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: BlockState);
|
||||||
|
|
||||||
|
/// Gets the biome at the provided biome offsets in the chunk.
|
||||||
|
///
|
||||||
|
/// Note: the arguments are **not** block positions. Biomes are 4x4x4
|
||||||
|
/// segments of a chunk, so `x` and `z` are in `0..=4`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the offsets are outside the bounds of the chunk.
|
||||||
|
fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId;
|
||||||
|
|
||||||
|
/// Sets the biome at the provided biome offsets in the chunk.
|
||||||
|
///
|
||||||
|
/// Note: the arguments are **not** block positions. Biomes are 4x4x4
|
||||||
|
/// segments of a chunk, so `x` and `z` are in `0..=4`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the offsets are outside the bounds of the chunk.
|
||||||
|
fn set_biome(&mut self, x: usize, y: usize, z: usize, biome: BiomeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A chunk that is not loaded in any world.
|
||||||
|
pub struct UnloadedChunk {
|
||||||
|
sections: Vec<ChunkSection>,
|
||||||
|
// TODO: block_entities: HashMap<u32, BlockEntity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnloadedChunk {
|
||||||
|
/// Constructs a new unloaded chunk containing only [`BlockState::AIR`] with
|
||||||
|
/// the given height in blocks.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the value of `height` does not meet the following criteria:
|
||||||
|
/// `height % 16 == 0 && height <= 4064`.
|
||||||
|
pub fn new(height: usize) -> Self {
|
||||||
|
let mut chunk = Self {
|
||||||
|
sections: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
chunk.resize(height);
|
||||||
|
chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the height of the chunk to `new_height`. This is a potentially
|
||||||
|
/// expensive operation that may involve copying.
|
||||||
|
///
|
||||||
|
/// The chunk is extended and truncated from the top. New blocks are always
|
||||||
|
/// [`BlockState::AIR`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// The constraints on `new_height` are the same as [`Self::new`].
|
||||||
|
pub fn resize(&mut self, new_height: usize) {
|
||||||
|
assert!(
|
||||||
|
new_height % 16 == 0 && new_height <= 4064,
|
||||||
|
"invalid chunk height of {new_height}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let old_height = self.sections.len() * 16;
|
||||||
|
|
||||||
|
if new_height > old_height {
|
||||||
|
let sect = ChunkSection {
|
||||||
|
blocks: [BlockState::AIR.to_raw(); 4096],
|
||||||
|
modified_count: 0,
|
||||||
|
biomes: [BiomeId::default(); 64],
|
||||||
|
compact_data: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let additional = (new_height - old_height) / 16;
|
||||||
|
self.sections.reserve_exact(additional);
|
||||||
|
self.sections.resize_with(new_height / 16, || sect.clone());
|
||||||
|
debug_assert_eq!(self.sections.capacity(), self.sections.len());
|
||||||
|
} else if new_height < old_height {
|
||||||
|
self.sections.truncate(new_height / 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new chunk with height `0`.
|
||||||
|
impl Default for UnloadedChunk {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chunk for UnloadedChunk {
|
||||||
|
fn height(&self) -> usize {
|
||||||
|
self.sections.len() * 16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_block_state(&self, x: usize, y: usize, z: usize) -> BlockState {
|
||||||
assert!(
|
assert!(
|
||||||
x < 16 && y < self.height() && z < 16,
|
x < 16 && y < self.height() && z < 16,
|
||||||
"chunk block offsets must be within bounds"
|
"chunk block offsets must be within bounds"
|
||||||
|
@ -276,41 +363,17 @@ impl<C: Config> Chunk<C> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the block state at the provided offsets in the chunk.
|
fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: BlockState) {
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the offsets are outside the bounds of the chunk.
|
|
||||||
pub fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: BlockState) {
|
|
||||||
assert!(
|
assert!(
|
||||||
x < 16 && y < self.height() && z < 16,
|
x < 16 && y < self.height() && z < 16,
|
||||||
"chunk block offsets must be within bounds"
|
"chunk block offsets must be within bounds"
|
||||||
);
|
);
|
||||||
|
|
||||||
let sect = &mut self.sections[y / 16];
|
self.sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16] = block.to_raw();
|
||||||
let idx = x + z * 16 + y % 16 * 16 * 16;
|
// TODO: handle block entity here?
|
||||||
|
|
||||||
if block.to_raw() != sect.blocks[idx] & BLOCK_STATE_MASK {
|
|
||||||
if sect.blocks[idx] & !BLOCK_STATE_MASK == 0 {
|
|
||||||
sect.modified_count += 1;
|
|
||||||
}
|
|
||||||
sect.blocks[idx] = block.to_raw() | !BLOCK_STATE_MASK;
|
|
||||||
|
|
||||||
// TODO: if the block type was modified and the old block type
|
|
||||||
// could be a block entity, then the block entity at this
|
|
||||||
// position must be cleared.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the biome at the provided biome offsets in the chunk.
|
fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId {
|
||||||
///
|
|
||||||
/// Note: the arguments are **not** block positions. Biomes are 4x4x4
|
|
||||||
/// segments of a chunk, so `x` and `z` are in `0..=4`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the offsets are outside the bounds of the chunk.
|
|
||||||
pub fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId {
|
|
||||||
assert!(
|
assert!(
|
||||||
x < 4 && y < self.height() / 4 && z < 4,
|
x < 4 && y < self.height() / 4 && z < 4,
|
||||||
"chunk biome offsets must be within bounds"
|
"chunk biome offsets must be within bounds"
|
||||||
|
@ -319,21 +382,79 @@ impl<C: Config> Chunk<C> {
|
||||||
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4]
|
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the biome at the provided biome offsets in the chunk.
|
fn set_biome(&mut self, x: usize, y: usize, z: usize, biome: BiomeId) {
|
||||||
///
|
|
||||||
/// Note: the arguments are **not** block positions. Biomes are 4x4x4
|
|
||||||
/// segments of a chunk, so `x` and `z` are in `0..=4`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the offsets are outside the bounds of the chunk.
|
|
||||||
pub fn set_biome(&mut self, x: usize, y: usize, z: usize, b: BiomeId) {
|
|
||||||
assert!(
|
assert!(
|
||||||
x < 4 && y < self.height() / 4 && z < 4,
|
x < 4 && y < self.height() / 4 && z < 4,
|
||||||
"chunk biome offsets must be within bounds"
|
"chunk biome offsets must be within bounds"
|
||||||
);
|
);
|
||||||
|
|
||||||
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = b;
|
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = biome;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A chunk which is currently loaded in a world.
|
||||||
|
pub struct LoadedChunk<C: Config> {
|
||||||
|
/// Custom state.
|
||||||
|
pub state: C::ChunkState,
|
||||||
|
sections: Box<[ChunkSection]>,
|
||||||
|
// TODO block_entities: HashMap<u32, BlockEntity>,
|
||||||
|
/// The MOTION_BLOCKING heightmap
|
||||||
|
heightmap: Vec<i64>,
|
||||||
|
created_this_tick: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A 16x16x16 section of blocks, biomes, and light in a chunk.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ChunkSection {
|
||||||
|
/// The block states in this section stored in x, z, y order.
|
||||||
|
/// The most significant bit is used to indicate if this block has been
|
||||||
|
/// modified.
|
||||||
|
blocks: [u16; 4096],
|
||||||
|
/// The number of modified blocks
|
||||||
|
modified_count: u16,
|
||||||
|
biomes: [BiomeId; 64],
|
||||||
|
compact_data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLOCK_STATE_MASK: u16 = 0x7fff;
|
||||||
|
|
||||||
|
const _: () = assert!(
|
||||||
|
BlockState::max_raw() <= BLOCK_STATE_MASK,
|
||||||
|
"There is not enough space in the block state type to store the modified bit. A bit array \
|
||||||
|
separate from the block state array should be created to keep track of modified blocks in \
|
||||||
|
the chunk section."
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<C: Config> LoadedChunk<C> {
|
||||||
|
fn new(
|
||||||
|
mut chunk: UnloadedChunk,
|
||||||
|
dimension_section_count: usize,
|
||||||
|
biome_registry_len: usize,
|
||||||
|
state: C::ChunkState,
|
||||||
|
) -> Self {
|
||||||
|
chunk.resize(dimension_section_count * 16);
|
||||||
|
|
||||||
|
let mut sections = chunk.sections.into_boxed_slice();
|
||||||
|
|
||||||
|
// Mark all sections as modified so the chunk is properly initialized.
|
||||||
|
for sect in sections.iter_mut() {
|
||||||
|
sect.modified_count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut loaded = Self {
|
||||||
|
state,
|
||||||
|
sections,
|
||||||
|
heightmap: Vec::new(),
|
||||||
|
created_this_tick: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
loaded.apply_modifications(biome_registry_len);
|
||||||
|
loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this chunk was created during the current tick.
|
||||||
|
pub fn created_this_tick(&self) -> bool {
|
||||||
|
self.created_this_tick
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the chunk data packet for this chunk with the given position. This
|
/// Gets the chunk data packet for this chunk with the given position. This
|
||||||
|
@ -468,6 +589,60 @@ impl<C: Config> Chunk<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<C: Config> Chunk for LoadedChunk<C> {
|
||||||
|
fn height(&self) -> usize {
|
||||||
|
self.sections.len() * 16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_block_state(&self, x: usize, y: usize, z: usize) -> BlockState {
|
||||||
|
assert!(
|
||||||
|
x < 16 && y < self.height() && z < 16,
|
||||||
|
"chunk block offsets must be within bounds"
|
||||||
|
);
|
||||||
|
|
||||||
|
BlockState::from_raw_unchecked(
|
||||||
|
self.sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16] & BLOCK_STATE_MASK,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: BlockState) {
|
||||||
|
assert!(
|
||||||
|
x < 16 && y < self.height() && z < 16,
|
||||||
|
"chunk block offsets must be within bounds"
|
||||||
|
);
|
||||||
|
|
||||||
|
let sect = &mut self.sections[y / 16];
|
||||||
|
let idx = x + z * 16 + y % 16 * 16 * 16;
|
||||||
|
|
||||||
|
if block.to_raw() != sect.blocks[idx] & BLOCK_STATE_MASK {
|
||||||
|
if sect.blocks[idx] & !BLOCK_STATE_MASK == 0 {
|
||||||
|
sect.modified_count += 1;
|
||||||
|
}
|
||||||
|
sect.blocks[idx] = block.to_raw() | !BLOCK_STATE_MASK;
|
||||||
|
|
||||||
|
// TODO: handle block entity here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId {
|
||||||
|
assert!(
|
||||||
|
x < 4 && y < self.height() / 4 && z < 4,
|
||||||
|
"chunk biome offsets must be within bounds"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_biome(&mut self, x: usize, y: usize, z: usize, biome: BiomeId) {
|
||||||
|
assert!(
|
||||||
|
x < 4 && y < self.height() / 4 && z < 4,
|
||||||
|
"chunk biome offsets must be within bounds"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = biome;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum BlockChangePacket {
|
pub(crate) enum BlockChangePacket {
|
||||||
Single(BlockUpdate),
|
Single(BlockUpdate),
|
||||||
|
@ -483,28 +658,6 @@ impl From<BlockChangePacket> for S2cPlayPacket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A 16x16x16 section of blocks, biomes, and light in a chunk.
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ChunkSection {
|
|
||||||
/// The block states in this section, stored in x, z, y order.
|
|
||||||
/// The most significant bit is used to indicate if this block has been
|
|
||||||
/// modified.
|
|
||||||
blocks: [u16; 4096],
|
|
||||||
/// The number of modified blocks
|
|
||||||
modified_count: u16,
|
|
||||||
biomes: [BiomeId; 64],
|
|
||||||
compact_data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const BLOCK_STATE_MASK: u16 = 0x7fff;
|
|
||||||
|
|
||||||
const _: () = assert!(
|
|
||||||
BlockState::max_raw() <= BLOCK_STATE_MASK,
|
|
||||||
"There is not enough space in the block state type to store the modified bit. A bit array \
|
|
||||||
separate from the block state array should be created to keep track of modified blocks in \
|
|
||||||
the chunk section."
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Builds the MOTION_BLOCKING heightmap.
|
/// Builds the MOTION_BLOCKING heightmap.
|
||||||
fn build_heightmap(sections: &[ChunkSection], heightmap: &mut Vec<i64>) {
|
fn build_heightmap(sections: &[ChunkSection], heightmap: &mut Vec<i64>) {
|
||||||
let height = sections.len() * 16;
|
let height = sections.len() * 16;
|
||||||
|
|
|
@ -30,7 +30,8 @@ pub trait Config: Sized + Send + Sync + UnwindSafe + RefUnwindSafe + 'static {
|
||||||
type EntityState: Send + Sync;
|
type EntityState: Send + Sync;
|
||||||
/// Custom state to store with every [`World`](crate::world::World).
|
/// Custom state to store with every [`World`](crate::world::World).
|
||||||
type WorldState: Send + Sync;
|
type WorldState: Send + Sync;
|
||||||
/// Custom state to store with every [`Chunk`](crate::chunk::Chunk).
|
/// Custom state to store with every
|
||||||
|
/// [`LoadedChunk`](crate::chunk::LoadedChunk).
|
||||||
type ChunkState: Send + Sync;
|
type ChunkState: Send + Sync;
|
||||||
/// Custom state to store with every
|
/// Custom state to store with every
|
||||||
/// [`PlayerList`](crate::player_list::PlayerList).
|
/// [`PlayerList`](crate::player_list::PlayerList).
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
//!
|
//!
|
||||||
//! In Valence, many types are owned by the library but given out as mutable
|
//! In Valence, many types are owned by the library but given out as mutable
|
||||||
//! references for the user to modify. Examples of such types include [`World`],
|
//! references for the user to modify. Examples of such types include [`World`],
|
||||||
//! [`Chunk`], [`Entity`], and [`Client`].
|
//! [`LoadedChunk`], [`Entity`], and [`Client`].
|
||||||
//!
|
//!
|
||||||
//! **You must not call [`mem::swap`] on these references (or any other
|
//! **You must not call [`mem::swap`] on these references (or any other
|
||||||
//! function that would move their location in memory).** Doing so breaks
|
//! function that would move their location in memory).** Doing so breaks
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
//! [`Worlds`]: crate::world::Worlds
|
//! [`Worlds`]: crate::world::Worlds
|
||||||
//! [`World`]: crate::world::World
|
//! [`World`]: crate::world::World
|
||||||
//! [`Chunks`]: crate::chunk::Chunks
|
//! [`Chunks`]: crate::chunk::Chunks
|
||||||
//! [`Chunk`]: crate::chunk::Chunk
|
//! [`LoadedChunk`]: crate::chunk::LoadedChunk
|
||||||
//! [`Entity`]: crate::entity::Entity
|
//! [`Entity`]: crate::entity::Entity
|
||||||
//! [`Client`]: crate::client::Client
|
//! [`Client`]: crate::client::Client
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue