From 20b050a87adce7138da5a0233e3ab6bd4384f503 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 28 Aug 2023 06:27:00 -0700 Subject: [PATCH 01/16] Block "batches" first pass --- benches/idle.rs | 2 +- benches/many_players.rs | 4 +- crates/valence_anvil/src/lib.rs | 4 +- crates/valence_anvil/src/parse_chunk.rs | 8 +- crates/valence_boss_bar/src/lib.rs | 31 +- crates/valence_entity/src/lib.rs | 18 +- crates/valence_protocol/src/block_pos.rs | 137 +++--- crates/valence_protocol/src/chunk_pos.rs | 35 +- .../valence_protocol/src/chunk_section_pos.rs | 94 ++++ crates/valence_protocol/src/lib.rs | 5 +- .../packets/play/chunk_delta_update_s2c.rs | 35 +- crates/valence_server/src/chunk_view.rs | 4 +- crates/valence_server/src/client.rs | 12 +- crates/valence_server/src/layer/chunk.rs | 267 +++++----- .../valence_server/src/layer/chunk/batch.rs | 196 ++++++++ .../src/layer/chunk/batch/basic.rs | 245 ++++++++++ .../valence_server/src/layer/chunk/biome.rs | 135 ++++++ .../valence_server/src/layer/chunk/chunk.rs | 159 ++---- .../valence_server/src/layer/chunk/loaded.rs | 454 ++---------------- .../src/layer/chunk/unloaded.rs | 88 +++- crates/valence_server/src/layer/entity.rs | 10 +- examples/advancement.rs | 2 +- examples/anvil_loading.rs | 4 +- examples/bench_players.rs | 2 +- examples/biomes.rs | 4 +- examples/block_entities.rs | 2 +- examples/boss_bar.rs | 2 +- examples/building.rs | 2 +- examples/chest.rs | 2 +- examples/combat.rs | 2 +- examples/cow_sphere.rs | 2 +- examples/ctf.rs | 6 +- examples/death.rs | 2 +- examples/entity_hitbox.rs | 2 +- examples/game_of_life.rs | 2 +- examples/parkour.rs | 4 +- examples/particles.rs | 2 +- examples/player_list.rs | 2 +- examples/resource_pack.rs | 2 +- examples/terrain.rs | 6 +- examples/text.rs | 2 +- examples/weather.rs | 2 +- examples/world_border.rs | 2 +- src/lib.rs | 2 +- src/tests/client.rs | 4 +- src/tests/layer.rs | 18 +- src/tests/player_list.rs | 4 +- 47 files changed, 1166 insertions(+), 863 deletions(-) create mode 100644 crates/valence_protocol/src/chunk_section_pos.rs create mode 100644 crates/valence_server/src/layer/chunk/batch.rs create mode 100644 crates/valence_server/src/layer/chunk/batch/basic.rs create mode 100644 crates/valence_server/src/layer/chunk/biome.rs diff --git a/benches/idle.rs b/benches/idle.rs index d773cc2c7..f879da39e 100644 --- a/benches/idle.rs +++ b/benches/idle.rs @@ -29,7 +29,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/benches/many_players.rs b/benches/many_players.rs index 89d369210..f5acf7ad3 100644 --- a/benches/many_players.rs +++ b/benches/many_players.rs @@ -5,7 +5,7 @@ use criterion::Criterion; use rand::Rng; use valence::entity::Position; use valence::keepalive::KeepaliveSettings; -use valence::layer::chunk::UnloadedChunk; +use valence::layer::chunk::Chunk; use valence::layer::LayerBundle; use valence::math::DVec3; use valence::network::NetworkPlugin; @@ -53,7 +53,7 @@ fn run_many_players( for x in -world_size..world_size { layer .chunk - .insert_chunk(ChunkPos::new(x, z), UnloadedChunk::new()); + .insert_chunk(ChunkPos::new(x, z), Chunk::new()); } } diff --git a/crates/valence_anvil/src/lib.rs b/crates/valence_anvil/src/lib.rs index cd6c3d4a6..6f34b0999 100644 --- a/crates/valence_anvil/src/lib.rs +++ b/crates/valence_anvil/src/lib.rs @@ -34,7 +34,7 @@ use lru::LruCache; use tracing::warn; use valence_server::client::{Client, OldView, View}; use valence_server::entity::{EntityLayerId, OldEntityLayerId}; -use valence_server::layer::chunk::UnloadedChunk; +use valence_server::layer::chunk::Chunk; use valence_server::layer::UpdateLayersPreClientSet; use valence_server::nbt::Compound; use valence_server::protocol::anyhow::{bail, ensure}; @@ -63,7 +63,7 @@ pub struct AnvilLevel { receiver: Receiver<(ChunkPos, WorkerResult)>, } -type WorkerResult = anyhow::Result>; +type WorkerResult = anyhow::Result>; impl AnvilLevel { pub fn new(world_root: impl Into, biomes: &BiomeRegistry) -> Self { diff --git a/crates/valence_anvil/src/parse_chunk.rs b/crates/valence_anvil/src/parse_chunk.rs index 65e7543f5..fa40d774d 100644 --- a/crates/valence_anvil/src/parse_chunk.rs +++ b/crates/valence_anvil/src/parse_chunk.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use num_integer::div_ceil; use thiserror::Error; use valence_server::block::{PropName, PropValue}; -use valence_server::layer::chunk::{Chunk, UnloadedChunk}; +use valence_server::layer::chunk::{ChunkOps, Chunk}; use valence_server::nbt::{Compound, List, Value}; use valence_server::protocol::BlockKind; use valence_server::registry::biome::BiomeId; @@ -68,17 +68,17 @@ pub(crate) enum ParseChunkError { pub(crate) fn parse_chunk( mut nbt: Compound, biome_map: &BTreeMap, BiomeId>, // TODO: replace with biome registry arg. -) -> Result { +) -> Result { let Some(Value::List(List::Compound(sections))) = nbt.remove("sections") else { return Err(ParseChunkError::MissingSections); }; if sections.is_empty() { - return Ok(UnloadedChunk::new()); + return Ok(Chunk::new()); } let mut chunk = - UnloadedChunk::with_height((sections.len() * 16).try_into().unwrap_or(u32::MAX)); + Chunk::with_height((sections.len() * 16).try_into().unwrap_or(u32::MAX)); let min_sect_y = sections .iter() diff --git a/crates/valence_boss_bar/src/lib.rs b/crates/valence_boss_bar/src/lib.rs index f6711872f..0e7910ad3 100644 --- a/crates/valence_boss_bar/src/lib.rs +++ b/crates/valence_boss_bar/src/lib.rs @@ -31,7 +31,7 @@ pub use valence_server::protocol::packets::play::boss_bar_s2c::{ }; use valence_server::protocol::packets::play::BossBarS2c; use valence_server::protocol::WritePacket; -use valence_server::{ChunkPos, ChunkView, Despawned, EntityLayer, Layer, UniqueId}; +use valence_server::{ChunkView, Despawned, EntityLayer, Layer, UniqueId}; mod components; pub use components::*; @@ -68,9 +68,7 @@ fn update_boss_bar( action: part.to_packet_action(), }; if let Some(pos) = pos { - entity_layer - .view_writer(pos.to_chunk_pos()) - .write_packet(&packet); + entity_layer.view_writer(pos.0).write_packet(&packet); } else { entity_layer.write_packet(&packet); } @@ -111,7 +109,7 @@ fn update_boss_bar_layer_view( _old_view_distance, ) in clients_query.iter_mut() { - let view = ChunkView::new(ChunkPos::from_pos(position.0), view_distance.get()); + let view = ChunkView::new(position.0.into(), view_distance.get()); let old_layers = old_visible_entity_layers.get(); let current_layers = &visible_entity_layers.0; @@ -122,7 +120,7 @@ fn update_boss_bar_layer_view( .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == added_layer) { if let Some(position) = boss_bar_position { - if view.contains(position.to_chunk_pos()) { + if view.contains(position.0) { client.write_packet(&BossBarS2c { id: id.0, action: BossBarAction::Add { @@ -155,7 +153,7 @@ fn update_boss_bar_layer_view( .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == removed_layer) { if let Some(position) = boss_bar_position { - if view.contains(position.to_chunk_pos()) { + if view.contains(position.0) { client.write_packet(&BossBarS2c { id: id.0, action: BossBarAction::Remove, @@ -205,20 +203,15 @@ fn update_boss_bar_chunk_view( old_view_distance, ) in clients_query.iter_mut() { - let view = ChunkView::new(ChunkPos::from_pos(position.0), view_distance.get()); - let old_view = ChunkView::new( - ChunkPos::from_pos(old_position.get()), - old_view_distance.get(), - ); + let view = ChunkView::new(position.0.into(), view_distance.get()); + let old_view = ChunkView::new(old_position.get().into(), old_view_distance.get()); for layer in visible_entity_layers.0.iter() { for (id, title, health, style, flags, _, boss_bar_position) in boss_bars_query .iter() .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == *layer) { - if view.contains(boss_bar_position.to_chunk_pos()) - && !old_view.contains(boss_bar_position.to_chunk_pos()) - { + if view.contains(boss_bar_position.0) && !old_view.contains(boss_bar_position.0) { client.write_packet(&BossBarS2c { id: id.0, action: BossBarAction::Add { @@ -229,8 +222,8 @@ fn update_boss_bar_chunk_view( flags: *flags, }, }); - } else if !view.contains(boss_bar_position.to_chunk_pos()) - && old_view.contains(boss_bar_position.to_chunk_pos()) + } else if !view.contains(boss_bar_position.0) + && old_view.contains(boss_bar_position.0) { client.write_packet(&BossBarS2c { id: id.0, @@ -253,9 +246,7 @@ fn boss_bar_despawn( action: BossBarAction::Remove, }; if let Some(pos) = position { - entity_layer - .view_writer(pos.to_chunk_pos()) - .write_packet(&packet); + entity_layer.view_writer(pos.0).write_packet(&packet); } else { entity_layer.write_packet(&packet); } diff --git a/crates/valence_entity/src/lib.rs b/crates/valence_entity/src/lib.rs index 20c4b87ee..f66ffa79f 100644 --- a/crates/valence_entity/src/lib.rs +++ b/crates/valence_entity/src/lib.rs @@ -32,7 +32,7 @@ use paste::paste; use tracing::warn; use tracked_data::TrackedData; use valence_math::{DVec3, Vec3}; -use valence_protocol::{BlockPos, ChunkPos, Decode, Encode, VarInt}; +use valence_protocol::{Decode, Encode, VarInt}; use valence_server_common::{Despawned, UniqueId}; include!(concat!(env!("OUT_DIR"), "/entity.rs")); @@ -209,14 +209,6 @@ impl Position { Self(pos.into()) } - pub fn to_chunk_pos(self) -> ChunkPos { - ChunkPos::from_pos(self.0) - } - - pub fn to_block_pos(self) -> BlockPos { - BlockPos::from_pos(self.0) - } - pub fn get(self) -> DVec3 { self.0 } @@ -246,14 +238,6 @@ impl OldPosition { pub fn get(&self) -> DVec3 { self.0 } - - pub fn chunk_pos(&self) -> ChunkPos { - ChunkPos::from_pos(self.0) - } - - pub fn to_block_pos(&self) -> BlockPos { - BlockPos::from_pos(self.0) - } } impl PartialEq for OldPosition { diff --git a/crates/valence_protocol/src/block_pos.rs b/crates/valence_protocol/src/block_pos.rs index c78d05f65..438091e24 100644 --- a/crates/valence_protocol/src/block_pos.rs +++ b/crates/valence_protocol/src/block_pos.rs @@ -1,16 +1,17 @@ use std::fmt; use std::io::Write; -use std::ops::{Add, Sub}; use anyhow::bail; +use bitfield_struct::bitfield; +use derive_more::From; +use thiserror::Error; use valence_math::DVec3; -use crate::chunk_pos::ChunkPos; use crate::direction::Direction; use crate::{Decode, Encode}; /// Represents an absolute block position in world space. -#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct BlockPos { pub x: i32, pub y: i32, @@ -23,19 +24,6 @@ impl BlockPos { Self { x, y, z } } - /// Returns the block position a point in world space is contained within. - pub fn from_pos(pos: DVec3) -> Self { - Self { - x: pos.x.floor() as i32, - y: pos.y.floor() as i32, - z: pos.z.floor() as i32, - } - } - - pub const fn to_chunk_pos(self) -> ChunkPos { - ChunkPos::from_block_pos(self) - } - /// Get a new [`BlockPos`] that is adjacent to this position in `dir` /// direction. /// @@ -56,32 +44,75 @@ impl BlockPos { Direction::East => BlockPos::new(self.x + 1, self.y, self.z), } } -} -impl Encode for BlockPos { - fn encode(&self, w: impl Write) -> anyhow::Result<()> { + pub const fn packed(self) -> Result { match (self.x, self.y, self.z) { (-0x2000000..=0x1ffffff, -0x800..=0x7ff, -0x2000000..=0x1ffffff) => { - let (x, y, z) = (self.x as u64, self.y as u64, self.z as u64); - (x << 38 | z << 38 >> 26 | y & 0xfff).encode(w) + Ok(PackedBlockPos::new() + .with_x(self.x) + .with_y(self.y) + .with_z(self.z)) } - _ => bail!("out of range: {self:?}"), + _ => Err(Error(self)), + } + } +} + +#[bitfield(u64)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +pub struct PackedBlockPos { + #[bits(12)] + pub y: i32, + #[bits(26)] + pub z: i32, + #[bits(26)] + pub x: i32, +} + +impl Encode for BlockPos { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { + match self.packed() { + Ok(p) => p.encode(w), + Err(e) => bail!("{e}: {self}"), } } } impl Decode<'_> for BlockPos { fn decode(r: &mut &[u8]) -> anyhow::Result { - // Use arithmetic right shift to determine sign. - let val = i64::decode(r)?; - let x = val >> 38; - let z = val << 26 >> 38; - let y = val << 52 >> 52; - Ok(Self { - x: x as i32, - y: y as i32, - z: z as i32, - }) + PackedBlockPos::decode(r).map(Into::into) + } +} + +impl From for BlockPos { + fn from(p: PackedBlockPos) -> Self { + Self { + x: p.x(), + y: p.y(), + z: p.z(), + } + } +} + +impl TryFrom for PackedBlockPos { + type Error = Error; + + fn try_from(pos: BlockPos) -> Result { + pos.packed() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Error, From)] +#[error("block position of {0} is out of range")] +pub struct Error(pub BlockPos); + +impl From for BlockPos { + fn from(pos: DVec3) -> Self { + Self { + x: pos.x.floor() as i32, + y: pos.y.floor() as i32, + z: pos.z.floor() as i32, + } } } @@ -109,39 +140,9 @@ impl From for [i32; 3] { } } -impl fmt::Debug for BlockPos { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - impl fmt::Display for BlockPos { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({}, {}, {})", self.x, self.y, self.z) - } -} - -impl Add for BlockPos { - type Output = BlockPos; - - fn add(self, rhs: Self) -> Self::Output { - Self { - x: self.x + rhs.x, - y: self.y + rhs.y, - z: self.z + rhs.z, - } - } -} - -impl Sub for BlockPos { - type Output = BlockPos; - - fn sub(self, rhs: Self) -> Self::Output { - Self { - x: self.x - rhs.x, - y: self.y - rhs.y, - z: self.z - rhs.z, - } + fmt::Debug::fmt(&(self.x, self.y, self.z), f) } } @@ -150,7 +151,7 @@ mod tests { use super::*; #[test] - fn position() { + fn block_position() { let xzs = [ (-33554432, true), (-33554433, false), @@ -170,17 +171,15 @@ mod tests { (-1, true), ]; - let mut buf = [0; 8]; - for (x, x_valid) in xzs { for (y, y_valid) in ys { for (z, z_valid) in xzs { let pos = BlockPos::new(x, y, z); if x_valid && y_valid && z_valid { - pos.encode(&mut &mut buf[..]).unwrap(); - assert_eq!(BlockPos::decode(&mut &buf[..]).unwrap(), pos); + let c = pos.packed().unwrap(); + assert_eq!((c.x(), c.y(), c.z()), (pos.x, pos.y, pos.z)); } else { - assert!(pos.encode(&mut &mut buf[..]).is_err()); + assert_eq!(pos.packed(), Err(Error(pos))); } } } diff --git a/crates/valence_protocol/src/chunk_pos.rs b/crates/valence_protocol/src/chunk_pos.rs index 99ab8cf7b..939c28253 100644 --- a/crates/valence_protocol/src/chunk_pos.rs +++ b/crates/valence_protocol/src/chunk_pos.rs @@ -1,6 +1,7 @@ use valence_math::DVec3; use crate::block_pos::BlockPos; +use crate::chunk_section_pos::ChunkSectionPos; use crate::{Decode, Encode}; /// The X and Z position of a chunk. @@ -18,16 +19,6 @@ impl ChunkPos { Self { x, z } } - /// Constructs a chunk position from a position in world space. Only the `x` - /// and `z` components are used. - pub fn from_pos(pos: DVec3) -> Self { - Self::new((pos.x / 16.0).floor() as i32, (pos.z / 16.0).floor() as i32) - } - - pub const fn from_block_pos(pos: BlockPos) -> Self { - Self::new(pos.x.div_euclid(16), pos.z.div_euclid(16)) - } - pub const fn distance_squared(self, other: Self) -> u64 { let diff_x = other.x as i64 - self.x as i64; let diff_z = other.z as i64 - self.z as i64; @@ -36,6 +27,30 @@ impl ChunkPos { } } +impl From for ChunkPos { + fn from(pos: BlockPos) -> Self { + Self { + x: pos.x.div_euclid(16), + z: pos.z.div_euclid(16), + } + } +} + +impl From for ChunkPos { + fn from(pos: ChunkSectionPos) -> Self { + Self { x: pos.x, z: pos.z } + } +} + +impl From for ChunkPos { + fn from(pos: DVec3) -> Self { + Self { + x: (pos.x / 16.0).floor() as i32, + z: (pos.z / 16.0).floor() as i32, + } + } +} + impl From<(i32, i32)> for ChunkPos { fn from((x, z): (i32, i32)) -> Self { Self { x, z } diff --git a/crates/valence_protocol/src/chunk_section_pos.rs b/crates/valence_protocol/src/chunk_section_pos.rs new file mode 100644 index 000000000..8a68e0158 --- /dev/null +++ b/crates/valence_protocol/src/chunk_section_pos.rs @@ -0,0 +1,94 @@ +use std::fmt; +use std::io::Write; + +use bitfield_struct::bitfield; +use derive_more::From; +use thiserror::Error; + +use crate::{BlockPos, Decode, Encode}; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct ChunkSectionPos { + pub x: i32, + pub y: i32, + pub z: i32, +} + +impl ChunkSectionPos { + pub const fn new(x: i32, y: i32, z: i32) -> Self { + Self { x, y, z } + } + + pub const fn packed(self) -> Result { + match (self.x, self.y, self.z) { + (-2097152..=2097151, -524288..=524287, -2097152..=2097151) => { + Ok(PackedChunkSectionPos::new() + .with_x(self.x) + .with_y(self.y) + .with_z(self.z)) + } + _ => Err(Error(self)), + } + } +} + +impl fmt::Display for ChunkSectionPos { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&(self.x, self.y, self.z), f) + } +} + +impl Encode for ChunkSectionPos { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { + self.packed()?.encode(w) + } +} + +impl Decode<'_> for ChunkSectionPos { + fn decode(r: &mut &[u8]) -> anyhow::Result { + PackedChunkSectionPos::decode(r).map(Into::into) + } +} + +impl From for ChunkSectionPos { + fn from(pos: BlockPos) -> Self { + Self { + x: pos.x.div_euclid(16), + y: pos.y.div_euclid(16), + z: pos.z.div_euclid(16), + } + } +} + +#[bitfield(u64)] +#[derive(PartialEq, Eq, Ord, PartialOrd, Encode, Decode)] +pub struct PackedChunkSectionPos { + #[bits(20)] + pub y: i32, + #[bits(22)] + pub z: i32, + #[bits(22)] + pub x: i32, +} + +impl From for ChunkSectionPos { + fn from(pos: PackedChunkSectionPos) -> Self { + Self { + x: pos.x(), + y: pos.y(), + z: pos.z(), + } + } +} + +impl TryFrom for PackedChunkSectionPos { + type Error = Error; + + fn try_from(pos: ChunkSectionPos) -> Result { + pos.packed() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Error, From)] +#[error("chunk section position of {0} is out of range")] +pub struct Error(pub ChunkSectionPos); diff --git a/crates/valence_protocol/src/lib.rs b/crates/valence_protocol/src/lib.rs index 49e98788a..7f410a6a5 100644 --- a/crates/valence_protocol/src/lib.rs +++ b/crates/valence_protocol/src/lib.rs @@ -34,7 +34,8 @@ mod bit_set; mod block_pos; mod bounded; mod byte_angle; -mod chunk_pos; +pub mod chunk_pos; +pub mod chunk_section_pos; pub mod decode; mod difficulty; mod direction; @@ -62,6 +63,7 @@ pub use block_pos::BlockPos; pub use bounded::Bounded; pub use byte_angle::ByteAngle; pub use chunk_pos::ChunkPos; +pub use chunk_section_pos::ChunkSectionPos; pub use decode::PacketDecoder; use derive_more::{From, Into}; pub use difficulty::Difficulty; @@ -110,6 +112,7 @@ impl CompressionThreshold { pub const DEFAULT: Self = Self(-1); } +/// No compression. impl Default for CompressionThreshold { fn default() -> Self { Self::DEFAULT diff --git a/crates/valence_protocol/src/packets/play/chunk_delta_update_s2c.rs b/crates/valence_protocol/src/packets/play/chunk_delta_update_s2c.rs index 1908677a9..9615dd68d 100644 --- a/crates/valence_protocol/src/packets/play/chunk_delta_update_s2c.rs +++ b/crates/valence_protocol/src/packets/play/chunk_delta_update_s2c.rs @@ -1,9 +1,38 @@ use std::borrow::Cow; +use std::io::Write; -use crate::{Decode, Encode, Packet, VarLong}; +use bitfield_struct::bitfield; + +use crate::{Decode, Encode, Packet, VarLong, chunk_section_pos::ChunkSectionPos}; #[derive(Clone, Debug, Encode, Decode, Packet)] pub struct ChunkDeltaUpdateS2c<'a> { - pub chunk_section_position: i64, - pub blocks: Cow<'a, [VarLong]>, + pub chunk_section_pos: ChunkSectionPos, + pub blocks: Cow<'a, [ChunkDeltaUpdateEntry]>, +} + +#[bitfield(u64)] +#[derive(PartialEq, Eq)] +pub struct ChunkDeltaUpdateEntry { + #[bits(4)] + pub off_y: u8, + #[bits(4)] + pub off_z: u8, + #[bits(4)] + pub off_x: u8, + pub block_state: u32, + #[bits(20)] + _pad: u32, +} + +impl Encode for ChunkDeltaUpdateEntry { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { + VarLong(self.0 as _).encode(w) + } +} + +impl Decode<'_> for ChunkDeltaUpdateEntry { + fn decode(r: &mut &[u8]) -> anyhow::Result { + Ok(ChunkDeltaUpdateEntry(VarLong::decode(r)?.0 as _)) + } } diff --git a/crates/valence_server/src/chunk_view.rs b/crates/valence_server/src/chunk_view.rs index 134949faf..b5e81e2ae 100644 --- a/crates/valence_server/src/chunk_view.rs +++ b/crates/valence_server/src/chunk_view.rs @@ -32,9 +32,9 @@ impl ChunkView { self.dist } - pub const fn contains(self, pos: ChunkPos) -> bool { + pub fn contains(self, pos: impl Into) -> bool { let true_dist = self.dist as u64 + EXTRA_VIEW_RADIUS as u64; - self.pos.distance_squared(pos) <= true_dist * true_dist + self.pos.distance_squared(pos.into()) <= true_dist * true_dist } /// Returns an iterator over all the chunk positions in this view. Positions diff --git a/crates/valence_server/src/client.rs b/crates/valence_server/src/client.rs index d7ea8e95f..4eb0ccfee 100644 --- a/crates/valence_server/src/client.rs +++ b/crates/valence_server/src/client.rs @@ -33,7 +33,7 @@ use valence_protocol::profile::Property; use valence_protocol::sound::{Sound, SoundCategory, SoundId}; use valence_protocol::text::{IntoText, Text}; use valence_protocol::var_int::VarInt; -use valence_protocol::{BlockPos, ChunkPos, Encode, GameMode, Packet}; +use valence_protocol::{BlockPos, Encode, GameMode, Packet}; use valence_registry::RegistrySet; use valence_server_common::{Despawned, UniqueId}; @@ -500,7 +500,7 @@ pub struct View { impl ViewItem<'_> { pub fn get(&self) -> ChunkView { - ChunkView::new(self.pos.to_chunk_pos(), self.view_dist.0) + ChunkView::new(self.pos.0.into(), self.view_dist.0) } } @@ -512,7 +512,7 @@ pub struct OldView { impl OldViewItem<'_> { pub fn get(&self) -> ChunkView { - ChunkView::new(self.old_pos.chunk_pos(), self.old_view_dist.0) + ChunkView::new(self.old_pos.get().into(), self.old_view_dist.0) } } @@ -617,7 +617,7 @@ fn handle_layer_messages( mut visible_entity_layers, old_visible_entity_layers, )| { - let block_pos = BlockPos::from_pos(old_view.old_pos.get()); + let block_pos = BlockPos::from(old_view.old_pos.get()); let old_view = old_view.get(); fn in_radius(p0: BlockPos, p1: BlockPos, radius_squared: u32) -> bool { @@ -883,8 +883,8 @@ pub(crate) fn update_view_and_layers( view_dist, old_view_dist, )| { - let view = ChunkView::new(ChunkPos::from_pos(pos.0), view_dist.0); - let old_view = ChunkView::new(ChunkPos::from_pos(old_pos.get()), old_view_dist.0); + let view = ChunkView::new(pos.0.into(), view_dist.0); + let old_view = ChunkView::new(old_pos.get().into(), old_view_dist.0); // Make sure the center chunk is set before loading chunks! Otherwise the client // may ignore the chunk. diff --git a/crates/valence_server/src/layer/chunk.rs b/crates/valence_server/src/layer/chunk.rs index baf36c1e2..a784075b6 100644 --- a/crates/valence_server/src/layer/chunk.rs +++ b/crates/valence_server/src/layer/chunk.rs @@ -1,27 +1,32 @@ +pub mod batch; +mod biome; #[allow(clippy::module_inception)] mod chunk; -pub mod loaded; +mod loaded; mod paletted_container; -pub mod unloaded; +mod unloaded; use std::borrow::Cow; use std::collections::hash_map::{Entry, OccupiedEntry, VacantEntry}; -use std::fmt; use bevy_app::prelude::*; use bevy_ecs::prelude::*; -pub use chunk::{MAX_HEIGHT, *}; +pub use biome::*; +pub use chunk::*; pub use loaded::LoadedChunk; use rustc_hash::FxHashMap; -pub use unloaded::UnloadedChunk; +pub use unloaded::Chunk; use valence_math::{DVec3, Vec3}; use valence_nbt::Compound; use valence_protocol::encode::{PacketWriter, WritePacket}; +use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; use valence_protocol::packets::play::particle_s2c::Particle; use valence_protocol::packets::play::{ParticleS2c, PlaySoundS2c}; use valence_protocol::sound::{Sound, SoundCategory, SoundId}; -use valence_protocol::{BlockPos, ChunkPos, CompressionThreshold, Encode, Ident, Packet}; -use valence_registry::biome::{BiomeId, BiomeRegistry}; +use valence_protocol::{ + BlockPos, BlockState, ChunkPos, CompressionThreshold, Encode, Ident, Packet, +}; +use valence_registry::biome::BiomeRegistry; use valence_registry::DimensionTypeRegistry; use valence_server_common::Server; @@ -37,9 +42,11 @@ pub struct ChunkLayer { messages: ChunkLayerMessages, chunks: FxHashMap, info: ChunkLayerInfo, + block_update_buf: Vec, } /// Chunk layer information. +#[derive(Debug)] pub(crate) struct ChunkLayerInfo { dimension_type_name: Ident, height: u32, @@ -48,19 +55,6 @@ pub(crate) struct ChunkLayerInfo { threshold: CompressionThreshold, } -impl fmt::Debug for ChunkLayerInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ChunkLayerInfo") - .field("dimension_type_name", &self.dimension_type_name) - .field("height", &self.height) - .field("min_y", &self.min_y) - .field("biome_registry_len", &self.biome_registry_len) - .field("threshold", &self.threshold) - // Ignore sky light mask and array. - .finish() - } -} - type ChunkLayerMessages = Messages; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] @@ -111,8 +105,8 @@ impl GetChunkPos for LocalMsg { match *self { LocalMsg::PacketAt { pos } => pos, LocalMsg::PacketAtExcept { pos, .. } => pos, - LocalMsg::RadiusAt { center, .. } => center.to_chunk_pos(), - LocalMsg::RadiusAtExcept { center, .. } => center.to_chunk_pos(), + LocalMsg::RadiusAt { center, .. } => center.into(), + LocalMsg::RadiusAtExcept { center, .. } => center.into(), LocalMsg::ChangeBiome { pos } => pos, LocalMsg::ChangeChunkState { pos } => pos, } @@ -152,6 +146,7 @@ impl ChunkLayer { biome_registry_len: biomes.iter().len(), threshold: server.compression_threshold(), }, + block_update_buf: vec![], } } @@ -183,11 +178,7 @@ impl ChunkLayer { /// Insert a chunk into the instance at the given position. The previous /// chunk data is returned. - pub fn insert_chunk( - &mut self, - pos: impl Into, - chunk: UnloadedChunk, - ) -> Option { + pub fn insert_chunk(&mut self, pos: impl Into, chunk: Chunk) -> Option { match self.chunk_entry(pos) { ChunkEntry::Occupied(mut oe) => Some(oe.insert(chunk)), ChunkEntry::Vacant(ve) => { @@ -199,7 +190,7 @@ impl ChunkLayer { /// Unload the chunk at the given position, if it is loaded. Returns the /// chunk if it was loaded. - pub fn remove_chunk(&mut self, pos: impl Into) -> Option { + pub fn remove_chunk(&mut self, pos: impl Into) -> Option { match self.chunk_entry(pos) { ChunkEntry::Occupied(oe) => Some(oe.remove()), ChunkEntry::Vacant(_) => None, @@ -265,82 +256,7 @@ impl ChunkLayer { self.chunks.shrink_to_fit(); self.messages.shrink_to_fit(); - } - - pub fn block(&self, pos: impl Into) -> Option { - let (chunk, x, y, z) = self.chunk_and_offsets(pos.into())?; - Some(chunk.block(x, y, z)) - } - - pub fn set_block(&mut self, pos: impl Into, block: impl IntoBlock) -> Option { - let (chunk, x, y, z) = self.chunk_and_offsets_mut(pos.into())?; - Some(chunk.set_block(x, y, z, block)) - } - - pub fn block_entity_mut(&mut self, pos: impl Into) -> Option<&mut Compound> { - let (chunk, x, y, z) = self.chunk_and_offsets_mut(pos.into())?; - chunk.block_entity_mut(x, y, z) - } - - pub fn biome(&self, pos: impl Into) -> Option { - let (chunk, x, y, z) = self.chunk_and_offsets(pos.into())?; - Some(chunk.biome(x / 4, y / 4, z / 4)) - } - - pub fn set_biome(&mut self, pos: impl Into, biome: BiomeId) -> Option { - let (chunk, x, y, z) = self.chunk_and_offsets_mut(pos.into())?; - Some(chunk.set_biome(x / 4, y / 4, z / 4, biome)) - } - - #[inline] - fn chunk_and_offsets(&self, pos: BlockPos) -> Option<(&LoadedChunk, u32, u32, u32)> { - let Some(y) = pos - .y - .checked_sub(self.info.min_y) - .and_then(|y| y.try_into().ok()) - else { - return None; - }; - - if y >= self.info.height { - return None; - } - - let Some(chunk) = self.chunk(ChunkPos::from_block_pos(pos)) else { - return None; - }; - - let x = pos.x.rem_euclid(16) as u32; - let z = pos.z.rem_euclid(16) as u32; - - Some((chunk, x, y, z)) - } - - #[inline] - fn chunk_and_offsets_mut( - &mut self, - pos: BlockPos, - ) -> Option<(&mut LoadedChunk, u32, u32, u32)> { - let Some(y) = pos - .y - .checked_sub(self.info.min_y) - .and_then(|y| y.try_into().ok()) - else { - return None; - }; - - if y >= self.info.height { - return None; - } - - let Some(chunk) = self.chunk_mut(ChunkPos::from_block_pos(pos)) else { - return None; - }; - - let x = pos.x.rem_euclid(16) as u32; - let z = pos.z.rem_euclid(16) as u32; - - Some((chunk, x, y, z)) + self.block_update_buf.shrink_to_fit(); } pub(crate) fn info(&self) -> &ChunkLayerInfo { @@ -366,15 +282,14 @@ impl ChunkLayer { ) { let position = position.into(); - self.view_writer(ChunkPos::from_pos(position)) - .write_packet(&ParticleS2c { - particle: Cow::Borrowed(particle), - long_distance, - position, - offset: offset.into(), - max_speed, - count, - }); + self.view_writer(position).write_packet(&ParticleS2c { + particle: Cow::Borrowed(particle), + long_distance, + position, + offset: offset.into(), + max_speed, + count, + }); } // TODO: move to `valence_sound`. @@ -391,18 +306,17 @@ impl ChunkLayer { ) { let position = position.into(); - self.view_writer(ChunkPos::from_pos(position)) - .write_packet(&PlaySoundS2c { - id: SoundId::Direct { - id: sound.to_ident().into(), - range: None, - }, - category, - position: (position * 8.0).as_ivec3(), - volume, - pitch, - seed: rand::random(), - }); + self.view_writer(position).write_packet(&PlaySoundS2c { + id: SoundId::Direct { + id: sound.to_ident().into(), + range: None, + }, + category, + position: (position * 8.0).as_ivec3(), + volume, + pitch, + seed: rand::random(), + }); } } @@ -646,7 +560,7 @@ impl<'a> ChunkEntry<'a> { pub fn or_default(self) -> &'a mut LoadedChunk { match self { ChunkEntry::Occupied(oe) => oe.into_mut(), - ChunkEntry::Vacant(ve) => ve.insert(UnloadedChunk::new()), + ChunkEntry::Vacant(ve) => ve.insert(Chunk::new()), } } } @@ -666,7 +580,7 @@ impl<'a> OccupiedChunkEntry<'a> { self.entry.get_mut() } - pub fn insert(&mut self, chunk: UnloadedChunk) -> UnloadedChunk { + pub fn insert(&mut self, chunk: Chunk) -> Chunk { self.messages.send_local_infallible( LocalMsg::ChangeChunkState { pos: *self.entry.key(), @@ -674,7 +588,7 @@ impl<'a> OccupiedChunkEntry<'a> { |b| b.push(ChunkLayer::OVERWRITE), ); - self.entry.get_mut().insert(chunk) + self.entry.get_mut().replace(chunk) } pub fn into_mut(self) -> &'a mut LoadedChunk { @@ -685,29 +599,19 @@ impl<'a> OccupiedChunkEntry<'a> { self.entry.key() } - pub fn remove(self) -> UnloadedChunk { - self.messages.send_local_infallible( - LocalMsg::ChangeChunkState { - pos: *self.entry.key(), - }, - |b| b.push(ChunkLayer::UNLOAD), - ); - - self.entry.remove().remove() + pub fn remove(self) -> Chunk { + self.remove_entry().1 } - pub fn remove_entry(mut self) -> (ChunkPos, UnloadedChunk) { - let pos = *self.entry.key(); - let chunk = self.entry.get_mut().remove(); + pub fn remove_entry(self) -> (ChunkPos, Chunk) { + let (pos, chunk) = self.entry.remove_entry(); - self.messages.send_local_infallible( - LocalMsg::ChangeChunkState { - pos: *self.entry.key(), - }, - |b| b.push(ChunkLayer::UNLOAD), - ); + self.messages + .send_local_infallible(LocalMsg::ChangeChunkState { pos }, |b| { + b.push(ChunkLayer::UNLOAD) + }); - (pos, chunk) + (pos, chunk.into_chunk()) } } @@ -719,9 +623,9 @@ pub struct VacantChunkEntry<'a> { } impl<'a> VacantChunkEntry<'a> { - pub fn insert(self, chunk: UnloadedChunk) -> &'a mut LoadedChunk { + pub fn insert(self, chunk: Chunk) -> &'a mut LoadedChunk { let mut loaded = LoadedChunk::new(self.height); - loaded.insert(chunk); + loaded.replace(chunk); self.messages.send_local_infallible( LocalMsg::ChangeChunkState { @@ -742,6 +646,63 @@ impl<'a> VacantChunkEntry<'a> { } } +/// Represents a complete block, which is a pair of block state and optional NBT +/// data for the block entity. +#[derive(Clone, PartialEq, Default, Debug)] +pub struct Block { + pub state: BlockState, + pub nbt: Option, +} + +impl Block { + pub const fn new(state: BlockState, nbt: Option) -> Self { + Self { state, nbt } + } +} + +impl From for Block { + fn from(state: BlockState) -> Self { + Self { state, nbt: None } + } +} + +/// Like [`Block`] but immutably referenced. +#[derive(Copy, Clone, PartialEq, Default, Debug)] +pub struct BlockRef<'a> { + pub state: BlockState, + pub nbt: Option<&'a Compound>, +} + +impl<'a> BlockRef<'a> { + pub const fn new(state: BlockState, nbt: Option<&'a Compound>) -> Self { + Self { state, nbt } + } +} + +impl From> for Block { + fn from(value: BlockRef<'_>) -> Self { + Self { + state: value.state, + nbt: value.nbt.cloned(), + } + } +} + +impl From for BlockRef<'_> { + fn from(state: BlockState) -> Self { + Self { state, nbt: None } + } +} + +impl<'a> From<&'a Block> for BlockRef<'a> { + fn from(value: &'a Block) -> Self { + Self { + state: value.state, + nbt: value.nbt.as_ref(), + } + } +} + pub(super) fn build(app: &mut App) { app.add_systems( PostUpdate, @@ -753,13 +714,7 @@ pub(super) fn build(app: &mut App) { } fn update_chunk_layers_pre_client(mut layers: Query<&mut ChunkLayer>) { - for layer in &mut layers { - let layer = layer.into_inner(); - - for (&pos, chunk) in &mut layer.chunks { - chunk.update_pre_client(pos, &layer.info, &mut layer.messages); - } - + for mut layer in &mut layers { layer.messages.ready(); } } diff --git a/crates/valence_server/src/layer/chunk/batch.rs b/crates/valence_server/src/layer/chunk/batch.rs new file mode 100644 index 000000000..93c83d3d6 --- /dev/null +++ b/crates/valence_server/src/layer/chunk/batch.rs @@ -0,0 +1,196 @@ +//! Handles getting and setting blocks in chunk layers. + +mod basic; + +use std::borrow::Cow; + +pub use basic::BasicBatch; +use valence_protocol::encode::PacketWriter; +use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; +use valence_protocol::packets::play::{BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDeltaUpdateS2c}; +use valence_protocol::{BlockPos, ChunkPos, ChunkSectionPos, WritePacket}; + +use super::{block_offsets, Block, BlockRef, ChunkOps, LoadedChunk}; +use crate::layer::chunk::LocalMsg; +use crate::{BlockState, ChunkLayer, Layer}; + +impl ChunkLayer { + pub fn block(&self, pos: impl Into) -> Option { + let pos = pos.into(); + let chunk_pos = ChunkPos::from(pos); + + let chunk = self.chunk(chunk_pos)?; + let [x, y, z] = block_offsets(pos, self.info.min_y, self.info.height as i32)?; + + Some(chunk.block(x, y, z)) + } + + pub fn set_block( + &mut self, + pos: impl Into, + block: impl Into, + ) -> Option { + let pos = pos.into(); + let chunk_pos = ChunkPos::from(pos); + let block: Block = block.into(); + + let [x, y, z] = block_offsets(pos, self.info.min_y, self.info.height as i32)?; + + let mut writer = self.view_writer(chunk_pos); + + writer.write_packet(&BlockUpdateS2c { + position: pos, + block_id: block.state, + }); + + if let (Some(nbt), Some(kind)) = (&block.nbt, block.state.block_entity_kind()) { + writer.write_packet(&BlockEntityUpdateS2c { + position: pos, + kind, + data: Cow::Borrowed(nbt), + }); + } + + let chunk = self.chunk_mut(chunk_pos)?; + + Some(chunk.set_block(x, y, z, block)) + } + + pub fn apply_batch(&mut self, batch: impl Batch) { + let block_iter = batch.into_batch_iters(); + + let mut chunk: Option<&mut LoadedChunk> = None; + let mut sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); + + for (pos, block) in block_iter { + let new_sect_pos = ChunkSectionPos::from(pos); + + // Is this block in a new section? If it is, then flush the changes we've + // accumulated for the old section. + if sect_pos != new_sect_pos { + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + match self.block_update_buf.as_slice() { + // Zero updates. Do nothing. + &[] => {} + // One update. Send singular block update packet. + &[update] => self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { + position: BlockPos { + x: sect_pos.x * 16 + update.off_x() as i32, + y: sect_pos.y * 16 + update.off_y() as i32, + z: sect_pos.z * 16 + update.off_z() as i32, + }, + block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), + }); + }), + // >1 updates. Send special section update packet. + updates => { + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet( + &ChunkDeltaUpdateS2c { + chunk_section_pos: sect_pos, + blocks: Cow::Borrowed(updates), + }, + ) + }); + } + } + + // Send block entity update. + if let (Some(nbt), Some(kind)) = (&block.nbt, block.state.block_entity_kind()) { + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet( + &BlockEntityUpdateS2c { + position: pos, + kind, + data: Cow::Borrowed(nbt), + }, + ) + }); + } + + self.block_update_buf.clear(); + + // Update the chunk ref if the chunk pos changed. + if sect_pos.x != new_sect_pos.x || sect_pos.z != new_sect_pos.z { + chunk = self.chunks.get_mut(&ChunkPos::from(new_sect_pos)); + } + + // Update section pos. + sect_pos = new_sect_pos; + } + + if let Some(chunk) = &mut chunk { + let chunk_y = pos.y.wrapping_sub(self.info.min_y) as u32; + + // Is the block pos in bounds of the chunk? + if chunk_y < self.info.height { + let chunk_x = pos.x.rem_euclid(16); + let chunk_z = pos.z.rem_euclid(16); + + // Make change to the chunk and push section update. + + if chunk.viewer_count_mut() > 0 { + self.block_update_buf.push( + ChunkDeltaUpdateEntry::new() + .with_off_x(chunk_x as u8) + .with_off_y((chunk_y % 16) as u8) + .with_off_z(chunk_z as u8) + .with_block_state(block.state.to_raw() as u32), + ); + } + + chunk.set_block(chunk_x as u32, chunk_y, chunk_z as u32, block); + } + } + } + + // Flush any remaining block changes. + + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + match self.block_update_buf.as_slice() { + // Zero updates. Do nothing. + &[] => {} + // One update. Send singular block update packet. + &[update] => self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { + position: BlockPos { + x: sect_pos.x * 16 + update.off_x() as i32, + y: sect_pos.y * 16 + update.off_y() as i32, + z: sect_pos.z * 16 + update.off_z() as i32, + }, + block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), + }); + }), + // >1 updates. Send special section update packet. + updates => { + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&ChunkDeltaUpdateS2c { + chunk_section_pos: sect_pos, + blocks: Cow::Borrowed(updates), + }) + }); + } + } + + self.block_update_buf.clear(); + } +} + +pub trait Batch { + /// An iterator over the blocks modified by this batch. + /// + /// **NOTE:** For optimal performance, the blocks iterated over should be + /// sorted by chunk position and then chunk section position within those + /// groups. + type BlockStateIter: Iterator; + type BlockEntityIter: Iterator + + fn into_batch_iters(self) -> Self::BlockIter; +} diff --git a/crates/valence_server/src/layer/chunk/batch/basic.rs b/crates/valence_server/src/layer/chunk/batch/basic.rs new file mode 100644 index 000000000..fd586fcd7 --- /dev/null +++ b/crates/valence_server/src/layer/chunk/batch/basic.rs @@ -0,0 +1,245 @@ +use bevy_ecs::prelude::Component; +use bitfield_struct::bitfield; +use valence_protocol::{BlockPos, BlockState, ChunkPos}; + +use super::{Batch, Block}; + +#[derive(Clone, PartialEq, Default, Debug, Component)] +pub struct BasicBatch { + updates: Vec, + full: Vec, +} + +impl BasicBatch { + pub fn new() -> Self { + Self::default() + } + + pub fn set_block(&mut self, pos: impl Into, block: impl Into) { + let pos = pos.into(); + let block = block.into(); + + if block.nbt.is_none() { + self.updates + .push(BlockUpdate::from_block_state(pos, block.state)) + } else { + let idx = self.full.len() as u32; + self.full.push(block); + self.updates.push(BlockUpdate::from_index(pos, idx)); + } + } + + pub fn clear(&mut self) { + self.updates.clear(); + self.full.clear(); + } + + pub fn reserve(&mut self, additional: usize) { + self.updates.reserve(additional); + } + + pub fn shrink_to_fit(&mut self) { + self.updates.shrink_to_fit(); + self.full.shrink_to_fit(); + } +} + +impl FromIterator<(P, B)> for BasicBatch +where + P: Into, + B: Into, +{ + fn from_iter>(iter: T) -> Self { + let mut res = Self::new(); + + res.extend(iter); + + res + } +} + +impl Extend<(P, B)> for BasicBatch +where + P: Into, + B: Into, +{ + fn extend>(&mut self, iter: T) { + self.updates.extend(iter.into_iter().map(|(p, b)| { + let pos = p.into(); + let block = b.into(); + + if block.nbt.is_none() { + BlockUpdate::from_block_state(pos, block.state) + } else { + let idx = self.full.len() as u32; + self.full.push(block); + BlockUpdate::from_index(pos, idx) + } + })); + } +} + +/// The basic batch is cleared after application so you can reuse the buffer if +/// desired. +impl<'a> Batch for &'a mut BasicBatch { + type BlockIter = BlockIter<'a>; + + fn into_batch_iters(mut self) -> Self::BlockIter { + // Sort in reverse so the dedup keeps the last of consecutive elements. + self.updates.sort_by(|a, b| b.cmp(a)); + + // Eliminate redundant block assignments. + self.updates + .dedup_by_key(|u| u.0 & BlockUpdate::BLOCK_POS_MASK); + + BlockIter { + updates: self.updates.drain(..), + full: &mut self.full, + } + } +} + +pub struct BlockIter<'a> { + updates: std::vec::Drain<'a, BlockUpdate>, + full: &'a mut Vec, +} + +impl<'a> Iterator for BlockIter<'a> { + type Item = (BlockPos, Block); + + fn next(&mut self) -> Option { + let u = self.updates.next()?; + let pos = u.block_pos(); + + let block = if u.is_index() { + let full = &mut self.full[u.state() as usize]; + + Block { + state: full.state, + nbt: full.nbt.take(), + } + } else { + Block { + state: BlockState::from_raw(u.state() as u16).unwrap(), + nbt: None, + } + }; + + Some((pos, block)) + } + + fn size_hint(&self) -> (usize, Option) { + self.updates.size_hint() + } +} + +impl<'a> ExactSizeIterator for BlockIter<'a> { + fn len(&self) -> usize { + self.updates.len() + } +} + +impl<'a> Drop for BlockIter<'a> { + fn drop(&mut self) { + self.full.clear(); + } +} + +#[bitfield(u128, order = Msb)] +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct BlockUpdate { + // Section coordinate. + #[bits(28)] + section_x: i32, + #[bits(28)] + section_z: i32, + #[bits(28)] + section_y: i32, + // Coordinate within the section. + #[bits(4)] + off_x: u32, + #[bits(4)] + off_z: u32, + #[bits(4)] + off_y: u32, + /// `false` if `state` is a block state, `true` if it's an index into the + /// `full` array. + is_index: bool, + /// Bits of the [`BlockState`] or an index into the `full` array. + #[bits(31)] + state: u32, +} + +impl BlockUpdate { + const CHUNK_POS_MASK: u128 = u128::MAX << 72; + const SECTION_POS_MASK: u128 = u128::MAX << 44; + const BLOCK_POS_MASK: u128 = u128::MAX << 32; + + fn from_block_state(pos: BlockPos, state: BlockState) -> Self { + Self::new() + .with_section_x(pos.x.div_euclid(16)) + .with_section_y(pos.y.div_euclid(16)) + .with_section_z(pos.z.div_euclid(16)) + .with_off_x(pos.x.rem_euclid(16) as u32) + .with_off_y(pos.y.rem_euclid(16) as u32) + .with_off_z(pos.z.rem_euclid(16) as u32) + .with_state(state.to_raw() as u32) + } + + fn from_index(pos: BlockPos, idx: u32) -> Self { + Self::new() + .with_section_x(pos.x.div_euclid(16)) + .with_section_y(pos.y.div_euclid(16)) + .with_section_z(pos.z.div_euclid(16)) + .with_off_x(pos.x.rem_euclid(16) as u32) + .with_off_y(pos.y.rem_euclid(16) as u32) + .with_off_z(pos.z.rem_euclid(16) as u32) + .with_is_index(true) + .with_state(idx) + } + + // fn to_parts(self) -> (BlockPos, BlockState) { + // (self.block_pos(), self.block()) + // } + + fn block_pos(self) -> BlockPos { + BlockPos { + x: self.section_x() * 16 + self.off_x() as i32, + y: self.section_y() * 16 + self.off_y() as i32, + z: self.section_z() * 16 + self.off_z() as i32, + } + } + + fn chunk_pos(self) -> ChunkPos { + ChunkPos { + x: self.section_x(), + z: self.section_z(), + } + } + + fn block(self) -> BlockState { + BlockState::from_raw(self.state() as u16).unwrap() + } +} + +/* +impl PartialEq for BlockUpdate { + fn eq(&self, other: &Self) -> bool { + self.cmp(other).is_eq() + } +} + +impl Eq for BlockUpdate {} + +impl PartialOrd for BlockUpdate { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.ord(other)) + } +} + +impl Ord for BlockUpdate { + fn cmp(&self, other: &Self) -> Ordering { + (self.0 & Self::BLOCK_POS_MASK).cmp(&(other.0 & Self::BLOCK_POS_MASK)) + } +} +*/ diff --git a/crates/valence_server/src/layer/chunk/biome.rs b/crates/valence_server/src/layer/chunk/biome.rs new file mode 100644 index 000000000..d677a8281 --- /dev/null +++ b/crates/valence_server/src/layer/chunk/biome.rs @@ -0,0 +1,135 @@ +//! Handles getting and setting biomes in chunk layers. + +use valence_math::DVec3; +use valence_protocol::{BlockPos, ChunkPos}; +use valence_registry::biome::BiomeId; + +use super::{ChunkOps, LoadedChunk}; +use crate::ChunkLayer; + +/// Identifies the position of a biome in a world. +/// +/// Every biome occupies a 4m³ area, so conversion from [`BlockPos`] is done by +/// dividing all components by 4 (rounding towards negative infinity). +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug)] +pub struct BiomePos { + pub x: i32, + pub y: i32, + pub z: i32, +} + +impl From for BiomePos { + fn from(pos: BlockPos) -> Self { + Self { + x: pos.x.div_euclid(4), + y: pos.y.div_euclid(4), + z: pos.z.div_euclid(4), + } + } +} + +impl From for ChunkPos { + fn from(pos: BiomePos) -> Self { + Self { + x: pos.x.div_euclid(4), + z: pos.z.div_euclid(4), + } + } +} + +impl From for BiomePos { + fn from(pos: DVec3) -> Self { + Self { + x: (pos.x / 4.0).floor() as i32, + y: (pos.y / 4.0).floor() as i32, + z: (pos.z / 4.0).floor() as i32, + } + } +} + +impl ChunkLayer { + pub fn biome(&self, pos: impl Into) -> Option { + let pos = pos.into(); + + let (chunk, x, y, z) = self.chunk_and_biome_offsets(pos)?; + Some(chunk.biome(x, y, z)) + + // Biomes + // if self.changed_biomes { + // self.changed_biomes = false; + + // messages.send_local_infallible(LocalMsg::ChangeBiome { pos }, + // |buf| { for sect in self.sections.iter() { + // sect.biomes + // .encode_mc_format( + // &mut *buf, + // |b| b.to_index() as _, + // 0, + // 3, + // bit_width(info.biome_registry_len - 1), + // ) + // .expect("paletted container encode should always + // succeed"); } + // }); + // } + } + + pub fn set_biome(&mut self, pos: impl Into, biome: BiomeId) -> Option { + let pos = pos.into(); + + let (chunk, x, y, z) = self.chunk_and_biome_offsets_mut(pos)?; + + todo!() + } + + #[inline] + fn chunk_and_biome_offsets(&self, pos: BiomePos) -> Option<(&LoadedChunk, u32, u32, u32)> { + let Some(y) = pos + .y + .checked_sub(self.info.min_y.div_euclid(4)) + .and_then(|y| y.try_into().ok()) + else { + return None; + }; + + if y >= self.info.height / 4 { + return None; + } + + let Some(chunk) = self.chunk(pos) else { + return None; + }; + + let x = pos.x.rem_euclid(4) as u32; + let z = pos.z.rem_euclid(4) as u32; + + Some((chunk, x, y, z)) + } + + #[inline] + fn chunk_and_biome_offsets_mut( + &mut self, + pos: BiomePos, + ) -> Option<(&mut LoadedChunk, u32, u32, u32)> { + let Some(y) = pos + .y + .checked_sub(self.info.min_y.div_euclid(4)) + .and_then(|y| y.try_into().ok()) + else { + return None; + }; + + if y >= self.info.height / 4 { + return None; + } + + let Some(chunk) = self.chunk_mut(pos) else { + return None; + }; + + let x = pos.x.rem_euclid(4) as u32; + let z = pos.z.rem_euclid(4) as u32; + + Some((chunk, x, y, z)) + } +} diff --git a/crates/valence_server/src/layer/chunk/chunk.rs b/crates/valence_server/src/layer/chunk/chunk.rs index e5b53eefc..2e97b5282 100644 --- a/crates/valence_server/src/layer/chunk/chunk.rs +++ b/crates/valence_server/src/layer/chunk/chunk.rs @@ -1,13 +1,13 @@ use valence_nbt::Compound; -use valence_protocol::BlockState; +use valence_protocol::{BlockPos, BlockState, ChunkPos}; use valence_registry::biome::BiomeId; -use super::paletted_container::PalettedContainer; +use super::{BiomePos, BlockRef, Block}; /// Common operations on chunks. Notable implementors are /// [`LoadedChunk`](super::loaded::LoadedChunk) and /// [`UnloadedChunk`](super::unloaded::UnloadedChunk). -pub trait Chunk { +pub trait ChunkOps { /// Gets the height of this chunk in meters or blocks. fn height(&self) -> u32; @@ -33,16 +33,27 @@ pub trait Chunk { /// /// May panic if the position is out of bounds. #[track_caller] - fn set_block(&mut self, x: u32, y: u32, z: u32, block: impl IntoBlock) -> Block { - let block = block.into_block(); - let state = self.set_block_state(x, y, z, block.state); - let nbt = self.set_block_entity(x, y, z, block.nbt); + fn set_block(&mut self, x: u32, y: u32, z: u32, block: impl Into) -> Block { + let block = block.into(); + let old_state = self.set_block_state(x, y, z, block.state); + + let old_nbt = if block.nbt.is_none() && block.state.block_entity_kind().is_some() { + // Make sure there's always NBT data for the block entity. Otherwise, it will + // appear invisible to clients when loading the chunk. + self.set_block_entity(x, y, z, Some(Compound::default())) + } else { + self.set_block_entity(x, y, z, block.nbt) + }; - Block { state, nbt } + Block { + state: old_state, + nbt: old_nbt, + } } + /* /// Sets all the blocks in the entire chunk to the provided block. - fn fill_blocks(&mut self, block: impl IntoBlock) { + fn fill_blocks(&mut self, block: impl Into) { let block = block.into_block(); self.fill_block_states(block.state); @@ -59,6 +70,7 @@ pub trait Chunk { self.clear_block_entities(); } } + */ /// Gets the block state at the provided position in this chunk. `x` and `z` /// are in the range `0..16` while `y` is in the range `0..height`. @@ -211,116 +223,49 @@ pub trait Chunk { fn shrink_to_fit(&mut self); } -/// Represents a complete block, which is a pair of block state and optional NBT -/// data for the block entity. -#[derive(Clone, PartialEq, Default, Debug)] -pub struct Block { - pub state: BlockState, - pub nbt: Option, -} - -impl Block { - pub const fn new(state: BlockState, nbt: Option) -> Self { - Self { state, nbt } - } -} +/// The maximum height of a chunk. +pub const MAX_HEIGHT: u32 = 4096; -/// Like [`Block`], but immutably referenced. -#[derive(Copy, Clone, PartialEq, Default, Debug)] -pub struct BlockRef<'a> { - pub state: BlockState, - pub nbt: Option<&'a Compound>, -} +pub(super) const SECTION_BLOCK_COUNT: usize = 16 * 16 * 16; +pub(super) const SECTION_BIOME_COUNT: usize = 4 * 4 * 4; -impl<'a> BlockRef<'a> { - pub const fn new(state: BlockState, nbt: Option<&'a Compound>) -> Self { - Self { state, nbt } - } +/// Returns the minimum number of bits needed to represent the integer `n`. +pub(super) const fn bit_width(n: usize) -> usize { + (usize::BITS - n.leading_zeros()) as _ } -pub trait IntoBlock { - // TODO: parameterize this with block registry ref? - fn into_block(self) -> Block; -} +pub(super) fn block_offsets(block_pos: BlockPos, min_y: i32, height: i32) -> Option<[u32; 3]> { + let off_x = block_pos.x.rem_euclid(16); + let off_z = block_pos.z.rem_euclid(16); + let off_y = block_pos.y.wrapping_sub(min_y); -impl IntoBlock for Block { - fn into_block(self) -> Block { - self + if off_y < height { + Some([off_x as u32, off_y as u32, off_z as u32]) + } else { + None } } -impl<'a> IntoBlock for BlockRef<'a> { - fn into_block(self) -> Block { - Block { - state: self.state, - nbt: self.nbt.cloned(), - } - } -} +pub(super) fn biome_offsets(biome_pos: BiomePos, min_y: i32, height: i32) -> Option<[u32; 3]> { + let off_x = biome_pos.x.rem_euclid(4); + let off_z = biome_pos.z.rem_euclid(4); + let off_y = biome_pos.y.wrapping_sub(min_y / 4); -/// This will initialize the block with a new empty compound if the block state -/// is associated with a block entity. -impl IntoBlock for BlockState { - fn into_block(self) -> Block { - Block { - state: self, - nbt: self.block_entity_kind().map(|_| Compound::new()), - } + if off_y < height / 4 { + Some([off_x as u32, off_y as u32, off_z as u32]) + } else { + None } } -pub(super) const SECTION_BLOCK_COUNT: usize = 16 * 16 * 16; -pub(super) const SECTION_BIOME_COUNT: usize = 4 * 4 * 4; - -/// The maximum height of a chunk. -pub const MAX_HEIGHT: u32 = 4096; - -pub(super) type BlockStateContainer = - PalettedContainer; - -pub(super) type BiomeContainer = - PalettedContainer; - -#[inline] -#[track_caller] -pub(super) fn check_block_oob(chunk: &impl Chunk, x: u32, y: u32, z: u32) { - assert!( - x < 16 && y < chunk.height() && z < 16, - "chunk block offsets of ({x}, {y}, {z}) are out of bounds" - ); -} - -#[inline] -#[track_caller] -pub(super) fn check_biome_oob(chunk: &impl Chunk, x: u32, y: u32, z: u32) { - assert!( - x < 4 && y < chunk.height() / 4 && z < 4, - "chunk biome offsets of ({x}, {y}, {z}) are out of bounds" - ); -} - -#[inline] -#[track_caller] -pub(super) fn check_section_oob(chunk: &impl Chunk, sect_y: u32) { - assert!( - sect_y < chunk.height() / 16, - "chunk section offset of {sect_y} is out of bounds" - ); -} - -/// Returns the minimum number of bits needed to represent the integer `n`. -pub(super) const fn bit_width(n: usize) -> usize { - (usize::BITS - n.leading_zeros()) as _ -} - #[cfg(test)] mod tests { use super::*; - use crate::layer::chunk::{LoadedChunk, UnloadedChunk}; + use crate::layer::chunk::{Chunk, LoadedChunk}; #[test] fn chunk_get_set() { - fn check(mut chunk: impl Chunk) { + fn check(mut chunk: impl ChunkOps) { assert_eq!( chunk.set_block_state(1, 2, 3, BlockState::CHAIN), BlockState::AIR @@ -334,7 +279,7 @@ mod tests { assert_eq!(chunk.set_block_entity(1, 2, 3, None), Some(Compound::new())); } - let unloaded = UnloadedChunk::with_height(512); + let unloaded = Chunk::with_height(512); let loaded = LoadedChunk::new(512); check(unloaded); @@ -345,7 +290,7 @@ mod tests { #[test] #[should_panic] fn chunk_debug_oob_0() { - let mut chunk = UnloadedChunk::with_height(512); + let mut chunk = Chunk::with_height(512); chunk.set_block_state(0, 0, 16, BlockState::AIR); } @@ -361,7 +306,7 @@ mod tests { #[test] #[should_panic] fn chunk_debug_oob_2() { - let mut chunk = UnloadedChunk::with_height(512); + let mut chunk = Chunk::with_height(512); chunk.set_block_entity(0, 0, 16, None); } @@ -377,7 +322,7 @@ mod tests { #[test] #[should_panic] fn chunk_debug_oob_4() { - let mut chunk = UnloadedChunk::with_height(512); + let mut chunk = Chunk::with_height(512); chunk.set_biome(0, 0, 4, BiomeId::DEFAULT); } @@ -393,7 +338,7 @@ mod tests { #[test] #[should_panic] fn chunk_debug_oob_6() { - let mut chunk = UnloadedChunk::with_height(512); + let mut chunk = Chunk::with_height(512); chunk.fill_block_state_section(chunk.height() / 16, BlockState::AIR); } @@ -409,7 +354,7 @@ mod tests { #[test] #[should_panic] fn chunk_debug_oob_8() { - let mut chunk = UnloadedChunk::with_height(512); + let mut chunk = Chunk::with_height(512); chunk.fill_biome_section(chunk.height() / 16, BiomeId::DEFAULT); } diff --git a/crates/valence_server/src/layer/chunk/loaded.rs b/crates/valence_server/src/layer/chunk/loaded.rs index fa4ff2f90..705b5e742 100644 --- a/crates/valence_server/src/layer/chunk/loaded.rs +++ b/crates/valence_server/src/layer/chunk/loaded.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::collections::{BTreeMap, BTreeSet}; use std::mem; use std::sync::atomic::{AtomicU32, Ordering}; @@ -7,88 +6,45 @@ use parking_lot::Mutex; // Using nonstandard mutex to avoid poisoning API. use valence_nbt::{compound, Compound}; use valence_protocol::encode::{PacketWriter, WritePacket}; use valence_protocol::packets::play::chunk_data_s2c::ChunkDataBlockEntity; -use valence_protocol::packets::play::{ - BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDataS2c, ChunkDeltaUpdateS2c, -}; -use valence_protocol::{BlockPos, BlockState, ChunkPos, Encode, VarLong}; +use valence_protocol::packets::play::ChunkDataS2c; +use valence_protocol::{BlockState, ChunkPos, Encode}; use valence_registry::biome::BiomeId; use valence_registry::RegistryIdx; -use super::chunk::{ - bit_width, check_biome_oob, check_block_oob, check_section_oob, BiomeContainer, - BlockStateContainer, Chunk, SECTION_BLOCK_COUNT, -}; -use super::paletted_container::PalettedContainer; -use super::unloaded::{self, UnloadedChunk}; -use super::{ChunkLayerInfo, ChunkLayerMessages, LocalMsg}; - +use super::chunk::{bit_width, ChunkOps}; +use super::unloaded::Chunk; +use super::{ChunkLayerInfo, SECTION_BLOCK_COUNT}; + +/// A chunk that is actively loaded in a [`ChunkLayer`]. This is only accessible +/// behind a reference. +/// +/// Like [`Chunk`], loaded chunks implement the [`ChunkOps`] trait so you can +/// use many of the same methods. +/// +/// **NOTE:** Loaded chunks are a low-level API. Mutations directly to loaded +/// chunks are intentionally not synchronized with clients. Consider using the +/// relevant methods on [`ChunkLayer`] instead. +/// +/// [`ChunkLayer`]: super::ChunkLayer #[derive(Debug)] pub struct LoadedChunk { + /// Chunk data for this loaded chunk. + chunk: Chunk, /// A count of the clients viewing this chunk. Useful for knowing if it's /// necessary to record changes, since no client would be in view to receive /// the changes if this were zero. viewer_count: AtomicU32, - /// Block and biome data for the chunk. - sections: Box<[Section]>, - /// The block entities in this chunk. - block_entities: BTreeMap, - /// The set of block entities that have been modified this tick. - changed_block_entities: BTreeSet, - /// If any biomes in this chunk have been modified this tick. - changed_biomes: bool, /// Cached bytes of the chunk initialization packet. The cache is considered /// invalidated if empty. This should be cleared whenever the chunk is /// modified in an observable way, even if the chunk is not viewed. cached_init_packets: Mutex>, } -#[derive(Clone, Default, Debug)] -struct Section { - block_states: BlockStateContainer, - biomes: BiomeContainer, - /// Contains modifications for the update section packet. (Or the regular - /// block update packet if len == 1). - section_updates: Vec, -} - -impl Section { - fn count_non_air_blocks(&self) -> u16 { - let mut count = 0; - - match &self.block_states { - PalettedContainer::Single(s) => { - if !s.is_air() { - count += SECTION_BLOCK_COUNT as u16; - } - } - PalettedContainer::Indirect(ind) => { - for i in 0..SECTION_BLOCK_COUNT { - if !ind.get(i).is_air() { - count += 1; - } - } - } - PalettedContainer::Direct(dir) => { - for s in dir.as_ref() { - if !s.is_air() { - count += 1; - } - } - } - } - - count - } -} - impl LoadedChunk { pub(crate) fn new(height: u32) -> Self { Self { viewer_count: AtomicU32::new(0), - sections: vec![Section::default(); height as usize / 16].into(), - block_entities: BTreeMap::new(), - changed_block_entities: BTreeSet::new(), - changed_biomes: false, + chunk: Chunk::with_height(height), cached_init_packets: Mutex::new(vec![]), } } @@ -100,59 +56,21 @@ impl LoadedChunk { /// The previous chunk data is returned. /// /// [resized]: UnloadedChunk::set_height - pub(crate) fn insert(&mut self, mut chunk: UnloadedChunk) -> UnloadedChunk { + pub fn replace(&mut self, mut chunk: Chunk) -> Chunk { chunk.set_height(self.height()); - let old_sections = self - .sections - .iter_mut() - .zip(chunk.sections) - .map(|(sect, other_sect)| { - sect.section_updates.clear(); - - unloaded::Section { - block_states: mem::replace(&mut sect.block_states, other_sect.block_states), - biomes: mem::replace(&mut sect.biomes, other_sect.biomes), - } - }) - .collect(); - let old_block_entities = mem::replace(&mut self.block_entities, chunk.block_entities); - self.changed_block_entities.clear(); - self.changed_biomes = false; self.cached_init_packets.get_mut().clear(); - self.assert_no_changes(); - - UnloadedChunk { - sections: old_sections, - block_entities: old_block_entities, - } + mem::replace(&mut self.chunk, chunk) } - pub(crate) fn remove(&mut self) -> UnloadedChunk { - let old_sections = self - .sections - .iter_mut() - .map(|sect| { - sect.section_updates.clear(); - - unloaded::Section { - block_states: mem::take(&mut sect.block_states), - biomes: mem::take(&mut sect.biomes), - } - }) - .collect(); - let old_block_entities = mem::take(&mut self.block_entities); - self.changed_block_entities.clear(); - self.changed_biomes = false; - self.cached_init_packets.get_mut().clear(); - - self.assert_no_changes(); + pub(super) fn into_chunk(self) -> Chunk { + self.chunk + } - UnloadedChunk { - sections: old_sections, - block_entities: old_block_entities, - } + /// Clones this chunk's data into the returned [`Chunk`]. + pub fn to_chunk(&self) -> Chunk { + self.chunk.clone() } /// Returns the number of clients in view of this chunk. @@ -177,124 +95,6 @@ impl LoadedChunk { debug_assert_ne!(old, 0, "viewer count underflow!"); } - /// Performs the changes necessary to prepare this chunk for client updates. - /// - Chunk change messages are written to the layer. - /// - Recorded changes are cleared. - pub(crate) fn update_pre_client( - &mut self, - pos: ChunkPos, - info: &ChunkLayerInfo, - messages: &mut ChunkLayerMessages, - ) { - if *self.viewer_count.get_mut() == 0 { - // Nobody is viewing the chunk, so no need to send any update packets. There - // also shouldn't be any changes that need to be cleared. - self.assert_no_changes(); - - return; - } - - // Block states - for (sect_y, sect) in self.sections.iter_mut().enumerate() { - match sect.section_updates.len() { - 0 => {} - 1 => { - let packed = sect.section_updates[0].0 as u64; - let offset_y = packed & 0b1111; - let offset_z = (packed >> 4) & 0b1111; - let offset_x = (packed >> 8) & 0b1111; - let block = packed >> 12; - - let global_x = pos.x * 16 + offset_x as i32; - let global_y = info.min_y + sect_y as i32 * 16 + offset_y as i32; - let global_z = pos.z * 16 + offset_z as i32; - - messages.send_local_infallible(LocalMsg::PacketAt { pos }, |buf| { - let mut writer = PacketWriter::new(buf, info.threshold); - - writer.write_packet(&BlockUpdateS2c { - position: BlockPos::new(global_x, global_y, global_z), - block_id: BlockState::from_raw(block as u16).unwrap(), - }); - }); - } - _ => { - let chunk_section_position = (pos.x as i64) << 42 - | (pos.z as i64 & 0x3fffff) << 20 - | (sect_y as i64 + info.min_y.div_euclid(16) as i64) & 0xfffff; - - messages.send_local_infallible(LocalMsg::PacketAt { pos }, |buf| { - let mut writer = PacketWriter::new(buf, info.threshold); - - writer.write_packet(&ChunkDeltaUpdateS2c { - chunk_section_position, - blocks: Cow::Borrowed(§.section_updates), - }); - }); - } - } - - sect.section_updates.clear(); - } - - // Block entities - for &idx in &self.changed_block_entities { - let Some(nbt) = self.block_entities.get(&idx) else { - continue; - }; - - let x = idx % 16; - let z = (idx / 16) % 16; - let y = idx / 16 / 16; - - let state = self.sections[y as usize / 16] - .block_states - .get(idx as usize % SECTION_BLOCK_COUNT); - - let Some(kind) = state.block_entity_kind() else { - continue; - }; - - let global_x = pos.x * 16 + x as i32; - let global_y = info.min_y + y as i32; - let global_z = pos.z * 16 + z as i32; - - messages.send_local_infallible(LocalMsg::PacketAt { pos }, |buf| { - let mut writer = PacketWriter::new(buf, info.threshold); - - writer.write_packet(&BlockEntityUpdateS2c { - position: BlockPos::new(global_x, global_y, global_z), - kind, - data: Cow::Borrowed(nbt), - }); - }); - } - - self.changed_block_entities.clear(); - - // Biomes - if self.changed_biomes { - self.changed_biomes = false; - - messages.send_local_infallible(LocalMsg::ChangeBiome { pos }, |buf| { - for sect in self.sections.iter() { - sect.biomes - .encode_mc_format( - &mut *buf, - |b| b.to_index() as _, - 0, - 3, - bit_width(info.biome_registry_len - 1), - ) - .expect("paletted container encode should always succeed"); - } - }); - } - - // All changes should be cleared. - self.assert_no_changes(); - } - /// Writes the packet data needed to initialize this chunk. pub(crate) fn write_init_packets( &self, @@ -311,7 +111,7 @@ impl LoadedChunk { let mut blocks_and_biomes: Vec = vec![]; - for sect in self.sections.iter() { + for sect in &self.chunk.sections { sect.count_non_air_blocks() .encode(&mut blocks_and_biomes) .unwrap(); @@ -338,6 +138,7 @@ impl LoadedChunk { } let block_entities: Vec<_> = self + .chunk .block_entities .iter() .filter_map(|(&idx, nbt)| { @@ -345,7 +146,7 @@ impl LoadedChunk { let z = idx / 16 % 16; let y = idx / 16 / 16; - let kind = self.sections[y as usize / 16] + let kind = self.chunk.sections[y as usize / 16] .block_states .get(idx as usize % SECTION_BLOCK_COUNT) .block_entity_kind(); @@ -375,124 +176,46 @@ impl LoadedChunk { writer.write_packet_bytes(&init_packets); } - - /// Asserts that no changes to this chunk are currently recorded. - #[track_caller] - fn assert_no_changes(&self) { - #[cfg(debug_assertions)] - { - assert!(!self.changed_biomes); - assert!(self.changed_block_entities.is_empty()); - - for sect in self.sections.iter() { - assert!(sect.section_updates.is_empty()); - } - } - } } -impl Chunk for LoadedChunk { +impl ChunkOps for LoadedChunk { fn height(&self) -> u32 { - self.sections.len() as u32 * 16 + self.chunk.height() } fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState { - check_block_oob(self, x, y, z); - - let idx = x + z * 16 + y % 16 * 16 * 16; - self.sections[y as usize / 16] - .block_states - .get(idx as usize) + self.chunk.block_state(x, y, z) } fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState { - check_block_oob(self, x, y, z); - - let sect_y = y / 16; - let sect = &mut self.sections[sect_y as usize]; - let idx = x + z * 16 + y % 16 * 16 * 16; - - let old_block = sect.block_states.set(idx as usize, block); + let old_block = self.chunk.set_block_state(x, y, z, block); if block != old_block { self.cached_init_packets.get_mut().clear(); - - if *self.viewer_count.get_mut() > 0 { - let compact = (block.to_raw() as i64) << 12 | (x << 8 | z << 4 | (y % 16)) as i64; - sect.section_updates.push(VarLong(compact)); - } } old_block } fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) { - check_section_oob(self, sect_y); - - let sect = &mut self.sections[sect_y as usize]; - - if let PalettedContainer::Single(b) = §.block_states { - if *b != block { - self.cached_init_packets.get_mut().clear(); - - if *self.viewer_count.get_mut() > 0 { - // The whole section is being modified, so any previous modifications would - // be overwritten. - sect.section_updates.clear(); - - // Push section updates for all the blocks in the section. - sect.section_updates.reserve_exact(SECTION_BLOCK_COUNT); - let block_bits = (block.to_raw() as i64) << 12; - for z in 0..16 { - for x in 0..16 { - let packed = block_bits | (x << 8 | z << 4 | sect_y as i64); - sect.section_updates.push(VarLong(packed)); - } - } - } - } - } else { - let block_bits = (block.to_raw() as i64) << 12; - for z in 0..16 { - for x in 0..16 { - let idx = x + z * 16 + sect_y * (16 * 16); - if block != sect.block_states.get(idx as usize) { - self.cached_init_packets.get_mut().clear(); - - if *self.viewer_count.get_mut() > 0 { - let packed = block_bits | (x << 8 | z << 4 | sect_y) as i64; - sect.section_updates.push(VarLong(packed)); - } - } - } - } - } + self.chunk.fill_block_state_section(sect_y, block); - sect.block_states.fill(block); + // TODO: do some checks to avoid calling this sometimes. + self.cached_init_packets.get_mut().clear(); } fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> { - check_block_oob(self, x, y, z); - - let idx = x + z * 16 + y * 16 * 16; - self.block_entities.get(&idx) + self.chunk.block_entity(x, y, z) } fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> { - check_block_oob(self, x, y, z); - - let idx = x + z * 16 + y * 16 * 16; + let res = self.chunk.block_entity_mut(x, y, z); - if let Some(be) = self.block_entities.get_mut(&idx) { - if *self.viewer_count.get_mut() > 0 { - self.changed_block_entities.insert(idx); - } + if res.is_some() { self.cached_init_packets.get_mut().clear(); - - Some(be) - } else { - None } + + res } fn set_block_entity( @@ -502,98 +225,44 @@ impl Chunk for LoadedChunk { z: u32, block_entity: Option, ) -> Option { - check_block_oob(self, x, y, z); - - let idx = x + z * 16 + y * 16 * 16; - - match block_entity { - Some(nbt) => { - if *self.viewer_count.get_mut() > 0 { - self.changed_block_entities.insert(idx); - } - self.cached_init_packets.get_mut().clear(); - - self.block_entities.insert(idx, nbt) - } - None => { - let res = self.block_entities.remove(&idx); - - if res.is_some() { - self.cached_init_packets.get_mut().clear(); - } + self.cached_init_packets.get_mut().clear(); - res - } - } + self.chunk.set_block_entity(x, y, z, block_entity) } fn clear_block_entities(&mut self) { - if self.block_entities.is_empty() { + if self.chunk.block_entities.is_empty() { return; } - self.cached_init_packets.get_mut().clear(); + self.chunk.clear_block_entities(); - if *self.viewer_count.get_mut() > 0 { - self.changed_block_entities - .extend(mem::take(&mut self.block_entities).into_keys()); - } else { - self.block_entities.clear(); - } + self.cached_init_packets.get_mut().clear(); } fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId { - check_biome_oob(self, x, y, z); - - let idx = x + z * 4 + y % 4 * 4 * 4; - self.sections[y as usize / 4].biomes.get(idx as usize) + self.chunk.biome(x, y, z) } fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId { - check_biome_oob(self, x, y, z); - - let idx = x + z * 4 + y % 4 * 4 * 4; - let old_biome = self.sections[y as usize / 4] - .biomes - .set(idx as usize, biome); + let old_biome = self.chunk.set_biome(x, y, z, biome); if biome != old_biome { self.cached_init_packets.get_mut().clear(); - - if *self.viewer_count.get_mut() > 0 { - self.changed_biomes = true; - } } old_biome } fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) { - check_section_oob(self, sect_y); - - let sect = &mut self.sections[sect_y as usize]; - - if let PalettedContainer::Single(b) = §.biomes { - if *b != biome { - self.cached_init_packets.get_mut().clear(); - self.changed_biomes = *self.viewer_count.get_mut() > 0; - } - } else { - self.cached_init_packets.get_mut().clear(); - self.changed_biomes = *self.viewer_count.get_mut() > 0; - } + self.chunk.fill_biome_section(sect_y, biome); - sect.biomes.fill(biome); + self.cached_init_packets.get_mut().clear(); } fn shrink_to_fit(&mut self) { self.cached_init_packets.get_mut().shrink_to_fit(); - - for sect in self.sections.iter_mut() { - sect.block_states.shrink_to_fit(); - sect.biomes.shrink_to_fit(); - sect.section_updates.shrink_to_fit(); - } + self.chunk.shrink_to_fit(); } } @@ -603,23 +272,6 @@ mod tests { use super::*; - #[test] - fn loaded_chunk_unviewed_no_changes() { - let mut chunk = LoadedChunk::new(512); - - chunk.set_block(0, 10, 0, BlockState::MAGMA_BLOCK); - chunk.assert_no_changes(); - - chunk.set_biome(0, 0, 0, BiomeId::from_index(5)); - chunk.assert_no_changes(); - - chunk.fill_block_states(BlockState::ACACIA_BUTTON); - chunk.assert_no_changes(); - - chunk.fill_biomes(BiomeId::from_index(42)); - chunk.assert_no_changes(); - } - #[test] fn loaded_chunk_changes_clear_packet_cache() { #[track_caller] diff --git a/crates/valence_server/src/layer/chunk/unloaded.rs b/crates/valence_server/src/layer/chunk/unloaded.rs index cd19114e1..a7b6ad824 100644 --- a/crates/valence_server/src/layer/chunk/unloaded.rs +++ b/crates/valence_server/src/layer/chunk/unloaded.rs @@ -5,26 +5,59 @@ use valence_nbt::Compound; use valence_protocol::BlockState; use valence_registry::biome::BiomeId; -use super::chunk::{ - check_biome_oob, check_block_oob, check_section_oob, BiomeContainer, BlockStateContainer, - Chunk, MAX_HEIGHT, SECTION_BLOCK_COUNT, -}; +use super::chunk::{ChunkOps, MAX_HEIGHT}; +use super::paletted_container::PalettedContainer; +use super::{SECTION_BIOME_COUNT, SECTION_BLOCK_COUNT}; #[derive(Clone, Default, Debug)] -pub struct UnloadedChunk { +pub struct Chunk { pub(super) sections: Vec
, pub(super) block_entities: BTreeMap, } #[derive(Clone, Default, Debug)] pub(super) struct Section { - pub(super) block_states: BlockStateContainer, - pub(super) biomes: BiomeContainer, + pub(super) block_states: + PalettedContainer, + pub(super) biomes: PalettedContainer, } -impl UnloadedChunk { - pub fn new() -> Self { - Self::default() +impl Section { + pub(super) fn count_non_air_blocks(&self) -> u16 { + let mut count = 0; + + match &self.block_states { + PalettedContainer::Single(s) => { + if !s.is_air() { + count += SECTION_BLOCK_COUNT as u16; + } + } + PalettedContainer::Indirect(ind) => { + for i in 0..SECTION_BLOCK_COUNT { + if !ind.get(i).is_air() { + count += 1; + } + } + } + PalettedContainer::Direct(dir) => { + for s in dir.as_ref() { + if !s.is_air() { + count += 1; + } + } + } + } + + count + } +} + +impl Chunk { + pub const fn new() -> Self { + Self { + sections: vec![], + block_entities: BTreeMap::new(), + } } pub fn with_height(height: u32) -> Self { @@ -34,7 +67,7 @@ impl UnloadedChunk { } } - /// Sets the height of this chunk in meters. The chunk is truncated or + /// Sets the height of this chunk in blocks. The chunk is truncated or /// extended with [`BlockState::AIR`] and [`BiomeId::default()`] from the /// top. /// @@ -63,7 +96,7 @@ impl UnloadedChunk { } } -impl Chunk for UnloadedChunk { +impl ChunkOps for Chunk { fn height(&self) -> u32 { self.sections.len() as u32 * 16 } @@ -157,13 +190,40 @@ impl Chunk for UnloadedChunk { } } +#[inline] +#[track_caller] +pub(super) fn check_block_oob(chunk: &impl ChunkOps, x: u32, y: u32, z: u32) { + assert!( + x < 16 && y < chunk.height() && z < 16, + "chunk block offsets of ({x}, {y}, {z}) are out of bounds" + ); +} + +#[inline] +#[track_caller] +pub(super) fn check_biome_oob(chunk: &impl ChunkOps, x: u32, y: u32, z: u32) { + assert!( + x < 4 && y < chunk.height() / 4 && z < 4, + "chunk biome offsets of ({x}, {y}, {z}) are out of bounds" + ); +} + +#[inline] +#[track_caller] +pub(super) fn check_section_oob(chunk: &impl ChunkOps, sect_y: u32) { + assert!( + sect_y < chunk.height() / 16, + "chunk section offset of {sect_y} is out of bounds" + ); +} + #[cfg(test)] mod tests { use super::*; #[test] - fn unloaded_chunk_resize_removes_block_entities() { - let mut chunk = UnloadedChunk::with_height(32); + fn chunk_resize_removes_block_entities() { + let mut chunk = Chunk::with_height(32); assert_eq!(chunk.height(), 32); diff --git a/crates/valence_server/src/layer/entity.rs b/crates/valence_server/src/layer/entity.rs index fc902d713..85080efba 100644 --- a/crates/valence_server/src/layer/entity.rs +++ b/crates/valence_server/src/layer/entity.rs @@ -79,8 +79,8 @@ impl GetChunkPos for LocalMsg { match *self { LocalMsg::PacketAt { pos } => pos, LocalMsg::PacketAtExcept { pos, .. } => pos, - LocalMsg::RadiusAt { center, .. } => center.to_chunk_pos(), - LocalMsg::RadiusAtExcept { center, .. } => center.to_chunk_pos(), + LocalMsg::RadiusAt { center, .. } => center.into(), + LocalMsg::RadiusAtExcept { center, .. } => center.into(), LocalMsg::SpawnEntity { pos, .. } => pos, LocalMsg::SpawnEntityTransition { pos, .. } => pos, LocalMsg::DespawnEntity { pos, .. } => pos, @@ -379,8 +379,8 @@ fn change_entity_positions( mut layers: Query<&mut EntityLayer>, ) { for (entity, entity_id, pos, old_pos, layer_id, old_layer_id, despawned) in &entities { - let chunk_pos = pos.to_chunk_pos(); - let old_chunk_pos = old_pos.chunk_pos(); + let chunk_pos = ChunkPos::from(pos.0); + let old_chunk_pos = ChunkPos::from(old_pos.get()); if despawned { // Entity was deleted. Remove it from the layer. @@ -480,7 +480,7 @@ fn send_entity_update_messages( for cell in layer.entities.values_mut() { for &entity in cell.iter() { if let Ok((entity, update, is_client)) = entities.get(entity) { - let chunk_pos = update.pos.to_chunk_pos(); + let chunk_pos = ChunkPos::from(update.pos.0); // Send the update packets to all viewers. If the entity being updated is a // client, then we need to be careful to exclude the client itself from diff --git a/examples/advancement.rs b/examples/advancement.rs index 5d0e56daf..955a3e592 100644 --- a/examples/advancement.rs +++ b/examples/advancement.rs @@ -51,7 +51,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/anvil_loading.rs b/examples/anvil_loading.rs index 664ba21dd..9f7a8cece 100644 --- a/examples/anvil_loading.rs +++ b/examples/anvil_loading.rs @@ -127,7 +127,7 @@ fn handle_chunk_loads( ChunkLoadStatus::Empty => { // There's no chunk here so let's insert an empty chunk. If we were doing // terrain generation we would prepare that here. - layer.insert_chunk(event.pos, UnloadedChunk::new()); + layer.insert_chunk(event.pos, Chunk::new()); } ChunkLoadStatus::Failed(e) => { // Something went wrong. @@ -139,7 +139,7 @@ fn handle_chunk_loads( eprintln!("{errmsg}"); layer.send_chat_message(errmsg.color(Color::RED)); - layer.insert_chunk(event.pos, UnloadedChunk::new()); + layer.insert_chunk(event.pos, Chunk::new()); } } } diff --git a/examples/bench_players.rs b/examples/bench_players.rs index d5810c810..859db08bb 100644 --- a/examples/bench_players.rs +++ b/examples/bench_players.rs @@ -55,7 +55,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/biomes.rs b/examples/biomes.rs index 51a427d49..00fa70840 100644 --- a/examples/biomes.rs +++ b/examples/biomes.rs @@ -52,7 +52,7 @@ fn setup( for z in -SIZE..SIZE { for x in -SIZE..SIZE { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } @@ -82,7 +82,7 @@ fn set_biomes(mut layers: Query<&mut ChunkLayer>, biomes: Res) { .map(|(biome, _, _)| biome) .unwrap_or_default(); - layer.set_biome([x, SPAWN_Y, z], biome); + layer.set_biome(BlockPos::new(x, SPAWN_Y, z), biome); } } diff --git a/examples/block_entities.rs b/examples/block_entities.rs index 937db43ca..5887ec58b 100644 --- a/examples/block_entities.rs +++ b/examples/block_entities.rs @@ -30,7 +30,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/boss_bar.rs b/examples/boss_bar.rs index a8aba6968..4dcf417d1 100644 --- a/examples/boss_bar.rs +++ b/examples/boss_bar.rs @@ -36,7 +36,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/building.rs b/examples/building.rs index 7c00ef16f..d6b5aed35 100644 --- a/examples/building.rs +++ b/examples/building.rs @@ -33,7 +33,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/chest.rs b/examples/chest.rs index 57add8e28..d3af60f3c 100644 --- a/examples/chest.rs +++ b/examples/chest.rs @@ -32,7 +32,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/combat.rs b/examples/combat.rs index 6a79a267f..460a2539e 100644 --- a/examples/combat.rs +++ b/examples/combat.rs @@ -43,7 +43,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/cow_sphere.rs b/examples/cow_sphere.rs index e9f9ba124..60dd0af12 100644 --- a/examples/cow_sphere.rs +++ b/examples/cow_sphere.rs @@ -48,7 +48,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/ctf.rs b/examples/ctf.rs index 98bdc51e1..cedb8b5d7 100644 --- a/examples/ctf.rs +++ b/examples/ctf.rs @@ -69,7 +69,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } @@ -699,7 +699,7 @@ impl TriggerArea { } pub fn contains_pos(&self, pos: DVec3) -> bool { - self.contains(BlockPos::from_pos(pos)) + self.contains(pos.into()) } pub fn iter_block_pos(&self) -> impl Iterator { @@ -1031,7 +1031,7 @@ fn necromancy( if let Ok((mut visible_chunk_layer, mut respawn_pos, team, mut health)) = clients.get_mut(event.client) { - respawn_pos.pos = BlockPos::from_pos(team.spawn_pos()); + respawn_pos.pos = team.spawn_pos().into(); health.0 = PLAYER_MAX_HEALTH; let main_layer = layers.single(); diff --git a/examples/death.rs b/examples/death.rs index 645d76d45..a3e8b6315 100644 --- a/examples/death.rs +++ b/examples/death.rs @@ -36,7 +36,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/entity_hitbox.rs b/examples/entity_hitbox.rs index 59eff1a31..083b80026 100644 --- a/examples/entity_hitbox.rs +++ b/examples/entity_hitbox.rs @@ -33,7 +33,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/game_of_life.rs b/examples/game_of_life.rs index d7624fd43..421884797 100644 --- a/examples/game_of_life.rs +++ b/examples/game_of_life.rs @@ -51,7 +51,7 @@ fn setup( for z in -10..10 { for x in -10..10 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/parkour.rs b/examples/parkour.rs index 66fda594a..8a3acec1d 100644 --- a/examples/parkour.rs +++ b/examples/parkour.rs @@ -112,8 +112,8 @@ fn reset_clients( } // Init chunks. - for pos in ChunkView::new(ChunkPos::from_block_pos(START_POS), VIEW_DIST).iter() { - layer.insert_chunk(pos, UnloadedChunk::new()); + for pos in ChunkView::new(START_POS.into(), VIEW_DIST).iter() { + layer.insert_chunk(pos, Chunk::new()); } state.score = 0; diff --git a/examples/particles.rs b/examples/particles.rs index 89925a3a3..8613b4015 100644 --- a/examples/particles.rs +++ b/examples/particles.rs @@ -30,7 +30,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/player_list.rs b/examples/player_list.rs index 8b3635286..96c3c6564 100644 --- a/examples/player_list.rs +++ b/examples/player_list.rs @@ -35,7 +35,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/resource_pack.rs b/examples/resource_pack.rs index b337aadad..f8094952a 100644 --- a/examples/resource_pack.rs +++ b/examples/resource_pack.rs @@ -34,7 +34,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/terrain.rs b/examples/terrain.rs index bc3fe2ee6..f5a6cfad4 100644 --- a/examples/terrain.rs +++ b/examples/terrain.rs @@ -16,7 +16,7 @@ const SPAWN_POS: DVec3 = DVec3::new(0.0, 200.0, 0.0); const HEIGHT: u32 = 384; struct ChunkWorkerState { - sender: Sender<(ChunkPos, UnloadedChunk)>, + sender: Sender<(ChunkPos, Chunk)>, receiver: Receiver, // Noise functions density: SuperSimplex, @@ -32,7 +32,7 @@ struct GameState { /// been sent to the thread pool. pending: HashMap>, sender: Sender, - receiver: Receiver<(ChunkPos, UnloadedChunk)>, + receiver: Receiver<(ChunkPos, Chunk)>, } /// The order in which chunks should be processed by the thread pool. Smaller @@ -218,7 +218,7 @@ fn send_recv_chunks(mut layers: Query<&mut ChunkLayer>, state: ResMut fn chunk_worker(state: Arc) { while let Ok(pos) = state.receiver.recv() { - let mut chunk = UnloadedChunk::with_height(HEIGHT); + let mut chunk = Chunk::with_height(HEIGHT); for offset_z in 0..16 { for offset_x in 0..16 { diff --git a/examples/text.rs b/examples/text.rs index 8a7ed9eb7..0fcc76a0e 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -23,7 +23,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/weather.rs b/examples/weather.rs index e0c227668..6fdbb07c2 100644 --- a/examples/weather.rs +++ b/examples/weather.rs @@ -24,7 +24,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/examples/world_border.rs b/examples/world_border.rs index 1a7337377..e4ec82575 100644 --- a/examples/world_border.rs +++ b/examples/world_border.rs @@ -35,7 +35,7 @@ fn setup( for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk.insert_chunk([x, z], Chunk::new()); } } diff --git a/src/lib.rs b/src/lib.rs index a94058194..a8739b303 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,7 @@ pub mod prelude { pub use valence_server::ident::Ident; pub use valence_server::interact_entity::{EntityInteraction, InteractEntityEvent}; pub use valence_server::layer::chunk::{ - Block, BlockRef, Chunk, ChunkLayer, LoadedChunk, UnloadedChunk, + Block, BlockRef, ChunkOps, ChunkLayer, LoadedChunk, Chunk, }; pub use valence_server::layer::{EntityLayer, LayerBundle}; pub use valence_server::math::{DVec2, DVec3, Vec2, Vec3}; diff --git a/src/tests/client.rs b/src/tests/client.rs index 0055469f3..515c0197b 100644 --- a/src/tests/client.rs +++ b/src/tests/client.rs @@ -1,5 +1,5 @@ use crate::abilities::PlayerAbilitiesFlags; -use crate::layer::chunk::UnloadedChunk; +use crate::layer::chunk::Chunk; use crate::layer::ChunkLayer; use crate::math::DVec3; use crate::protocol::packets::play::{ @@ -21,7 +21,7 @@ fn client_teleport_and_move() { for z in -10..10 { for x in -10..10 { - layer.insert_chunk(ChunkPos::new(x, z), UnloadedChunk::new()); + layer.insert_chunk(ChunkPos::new(x, z), Chunk::new()); } } diff --git a/src/tests/layer.rs b/src/tests/layer.rs index dcb542b23..31aa49ac0 100644 --- a/src/tests/layer.rs +++ b/src/tests/layer.rs @@ -5,7 +5,7 @@ use bevy_ecs::world::EntityMut; use crate::client::{ViewDistance, VisibleEntityLayers}; use crate::entity::cow::CowEntityBundle; use crate::entity::{EntityLayerId, Position}; -use crate::layer::chunk::UnloadedChunk; +use crate::layer::chunk::Chunk; use crate::layer::{ChunkLayer, EntityLayer}; use crate::protocol::packets::play::{ BlockEntityUpdateS2c, ChunkDataS2c, ChunkDeltaUpdateS2c, EntitiesDestroyS2c, EntitySpawnS2c, @@ -27,7 +27,7 @@ fn block_create_destroy() { let mut layer = app.world.get_mut::(layer_ent).unwrap(); // Insert an empty chunk at (0, 0). - layer.insert_chunk([0, 0], UnloadedChunk::new()); + layer.insert_chunk([0, 0], Chunk::new()); // Wait until the next tick to start sending changes. app.update(); @@ -84,7 +84,7 @@ fn layer_chunk_view_change() { for z in -30..30 { for x in -30..30 { - layer.insert_chunk([x, z], UnloadedChunk::new()); + layer.insert_chunk([x, z], Chunk::new()); } } @@ -162,7 +162,7 @@ fn chunk_viewer_count() { let mut layer = app.world.get_mut::(layer_ent).unwrap(); // Create chunk at (0, 0). - layer.insert_chunk([0, 0], UnloadedChunk::new()); + layer.insert_chunk([0, 0], Chunk::new()); app.update(); // Tick. @@ -174,7 +174,7 @@ fn chunk_viewer_count() { // Create new chunk next to the first chunk and move the client away from it on // the same tick. - layer.insert_chunk([0, 1], UnloadedChunk::new()); + layer.insert_chunk([0, 1], Chunk::new()); let mut client = app.world.entity_mut(client_ent); client.get_mut::().unwrap().set([100.0, 0.0, 0.0]); @@ -195,7 +195,7 @@ fn chunk_viewer_count() { assert_eq!(layer.chunk([0, 1]).unwrap().viewer_count(), 0); // Create a third chunk adjacent to the others. - layer.insert_chunk([1, 0], UnloadedChunk::new()); + layer.insert_chunk([1, 0], Chunk::new()); // Move the client back in view of all three chunks. let mut client = app.world.entity_mut(client_ent); @@ -337,7 +337,7 @@ fn chunk_entity_spawn_despawn() { let mut layer = app.world.get_mut::(layer_ent).unwrap(); // Insert an empty chunk at (0, 0). - layer.insert_chunk([0, 0], UnloadedChunk::new()); + layer.insert_chunk([0, 0], Chunk::new()); // Put an entity in the new chunk. let cow_ent = app @@ -390,7 +390,7 @@ fn chunk_entity_spawn_despawn() { let mut layer = app.world.get_mut::(layer_ent).unwrap(); - assert!(layer.insert_chunk([0, 0], UnloadedChunk::new()).is_none()); + assert!(layer.insert_chunk([0, 0], Chunk::new()).is_none()); app.update(); @@ -446,7 +446,7 @@ fn chunk_entity_spawn_despawn() { let mut layer = app.world.get_mut::(layer_ent).unwrap(); - layer.insert_chunk([0, 1], UnloadedChunk::new()); + layer.insert_chunk([0, 1], Chunk::new()); layer.remove_chunk([0, 1]).unwrap(); app.world diff --git a/src/tests/player_list.rs b/src/tests/player_list.rs index 2a3e4954b..2b61e02a1 100644 --- a/src/tests/player_list.rs +++ b/src/tests/player_list.rs @@ -1,4 +1,4 @@ -use crate::layer::chunk::UnloadedChunk; +use crate::layer::chunk::Chunk; use crate::protocol::packets::play::{PlayerListS2c, PlayerSpawnS2c}; use crate::testing::{create_mock_client, ScenarioSingleClient}; use crate::ChunkLayer; @@ -16,7 +16,7 @@ fn player_list_arrives_before_player_spawn() { for z in -5..5 { for x in -5..5 { - layer.insert_chunk([x, z], UnloadedChunk::new()); + layer.insert_chunk([x, z], Chunk::new()); } } From a2a6e8401b1676baf7cb6b4b4f36f5a3c26a73b7 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 4 Sep 2023 07:57:21 -0700 Subject: [PATCH 02/16] pub block_pos --- crates/valence_protocol/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/valence_protocol/src/lib.rs b/crates/valence_protocol/src/lib.rs index 1d85f9923..89a06cff3 100644 --- a/crates/valence_protocol/src/lib.rs +++ b/crates/valence_protocol/src/lib.rs @@ -31,7 +31,7 @@ extern crate self as valence_protocol; mod array; mod bit_set; -mod block_pos; +pub mod block_pos; mod bounded; mod byte_angle; pub mod chunk_pos; From dc6c606870b7a682192edac57ee3ca33ca5d8ed0 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 12 Sep 2023 04:07:09 -0700 Subject: [PATCH 03/16] A bunch of stuff, very messy --- benches/many_players.rs | 4 +- crates/valence_anvil/src/parsing.rs | 5 +- crates/valence_entity/src/lib.rs | 10 +- crates/valence_protocol/src/block_pos.rs | 1 + crates/valence_registry/src/biome.rs | 4 +- crates/valence_registry/src/codec.rs | 4 +- crates/valence_registry/src/dimension_type.rs | 4 +- crates/valence_registry/src/lib.rs | 46 +- crates/valence_registry/src/tags.rs | 4 +- crates/valence_scoreboard/src/lib.rs | 2 +- crates/valence_server/src/client.rs | 21 +- crates/valence_server/src/dimension_layer.rs | 263 +++++++ .../src/dimension_layer/batch.rs | 402 ++++++++++ .../src/dimension_layer/block.rs | 59 ++ .../src/dimension_layer/chunk.rs | 375 +++++++++ .../src/dimension_layer/chunk/loaded.rs | 312 ++++++++ .../chunk/paletted_container.rs | 2 +- .../src/dimension_layer/chunk/unloaded.rs | 243 ++++++ .../src/dimension_layer/index.rs | 170 ++++ .../src/dimension_layer/plugin.rs | 169 ++++ crates/valence_server/src/entity_layer.rs | 15 + crates/valence_server/src/layer.rs | 275 ++++--- crates/valence_server/src/layer/action_buf.rs | 97 +++ .../src/layer/chunk_view_index.rs | 60 ++ crates/valence_server/src/layer/message.rs | 406 ++++------ crates/valence_server/src/layer/packet_buf.rs | 60 ++ crates/valence_server/src/layer_old.rs | 139 ++++ .../src/{layer => layer_old}/bvh.rs | 0 crates/valence_server/src/layer_old/chunk.rs | 726 ++++++++++++++++++ .../src/{layer => layer_old}/chunk/batch.rs | 14 +- .../{layer => layer_old}/chunk/batch/basic.rs | 8 +- .../src/{layer => layer_old}/chunk/biome.rs | 0 .../src/layer_old/chunk/block_update.rs | 357 +++++++++ .../src/{layer => layer_old}/chunk/chunk.rs | 9 +- .../src/layer_old/chunk/loaded.rs | 330 ++++++++ .../{layer => layer_old}/chunk/unloaded.rs | 0 .../src/{layer => layer_old}/entity.rs | 0 .../valence_server/src/layer_old/message.rs | 323 ++++++++ crates/valence_server/src/lib.rs | 4 +- crates/valence_server/src/movement.rs | 218 +++++- crates/valence_server/src/spawn.rs | 149 ++-- crates/valence_server/src/teleport.rs | 139 ---- .../valence_server_common/src/entity_event.rs | 20 + crates/valence_server_common/src/layer_id.rs | 16 + crates/valence_server_common/src/lib.rs | 5 +- src/lib.rs | 10 +- 46 files changed, 4766 insertions(+), 714 deletions(-) create mode 100644 crates/valence_server/src/dimension_layer.rs create mode 100644 crates/valence_server/src/dimension_layer/batch.rs create mode 100644 crates/valence_server/src/dimension_layer/block.rs create mode 100644 crates/valence_server/src/dimension_layer/chunk.rs create mode 100644 crates/valence_server/src/dimension_layer/chunk/loaded.rs rename crates/valence_server/src/{layer => dimension_layer}/chunk/paletted_container.rs (99%) create mode 100644 crates/valence_server/src/dimension_layer/chunk/unloaded.rs create mode 100644 crates/valence_server/src/dimension_layer/index.rs create mode 100644 crates/valence_server/src/dimension_layer/plugin.rs create mode 100644 crates/valence_server/src/entity_layer.rs create mode 100644 crates/valence_server/src/layer/action_buf.rs create mode 100644 crates/valence_server/src/layer/chunk_view_index.rs create mode 100644 crates/valence_server/src/layer/packet_buf.rs create mode 100644 crates/valence_server/src/layer_old.rs rename crates/valence_server/src/{layer => layer_old}/bvh.rs (100%) create mode 100644 crates/valence_server/src/layer_old/chunk.rs rename crates/valence_server/src/{layer => layer_old}/chunk/batch.rs (93%) rename crates/valence_server/src/{layer => layer_old}/chunk/batch/basic.rs (96%) rename crates/valence_server/src/{layer => layer_old}/chunk/biome.rs (100%) create mode 100644 crates/valence_server/src/layer_old/chunk/block_update.rs rename crates/valence_server/src/{layer => layer_old}/chunk/chunk.rs (97%) create mode 100644 crates/valence_server/src/layer_old/chunk/loaded.rs rename crates/valence_server/src/{layer => layer_old}/chunk/unloaded.rs (100%) rename crates/valence_server/src/{layer => layer_old}/entity.rs (100%) create mode 100644 crates/valence_server/src/layer_old/message.rs delete mode 100644 crates/valence_server/src/teleport.rs create mode 100644 crates/valence_server_common/src/entity_event.rs create mode 100644 crates/valence_server_common/src/layer_id.rs diff --git a/benches/many_players.rs b/benches/many_players.rs index 89354e4f7..53ec7617a 100644 --- a/benches/many_players.rs +++ b/benches/many_players.rs @@ -51,9 +51,7 @@ fn run_many_players( for z in -world_size..world_size { for x in -world_size..world_size { - layer - .chunk - .insert_chunk(ChunkPos::new(x, z), Chunk::new()); + layer.chunk.insert_chunk(ChunkPos::new(x, z), Chunk::new()); } } diff --git a/crates/valence_anvil/src/parsing.rs b/crates/valence_anvil/src/parsing.rs index 608b0054f..9d0ac03ae 100644 --- a/crates/valence_anvil/src/parsing.rs +++ b/crates/valence_anvil/src/parsing.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use num_integer::div_ceil; use thiserror::Error; use valence_server::block::{PropName, PropValue}; -use valence_server::layer_old::chunk::{ChunkOps, Chunk}; +use valence_server::layer_old::chunk::{Chunk, ChunkOps}; use valence_server::nbt::{Compound, List, Value}; use valence_server::protocol::BlockKind; use valence_server::registry::biome::BiomeId; @@ -128,8 +128,7 @@ fn parse_chunk( return Ok(Chunk::new()); } - let mut chunk = - Chunk::with_height((sections.len() * 16).try_into().unwrap_or(u32::MAX)); + let mut chunk = Chunk::with_height((sections.len() * 16).try_into().unwrap_or(u32::MAX)); let min_sect_y = sections .iter() diff --git a/crates/valence_entity/src/lib.rs b/crates/valence_entity/src/lib.rs index f66ffa79f..02a35decd 100644 --- a/crates/valence_entity/src/lib.rs +++ b/crates/valence_entity/src/lib.rs @@ -225,9 +225,7 @@ impl PartialEq for Position { } /// The value of [`Position`] from the end of the previous tick. -/// -/// **NOTE**: You should not modify this component after the entity is spawned. -#[derive(Component, Clone, PartialEq, Default, Debug, Deref)] +#[derive(Component, Clone, PartialEq, Debug, Deref)] pub struct OldPosition(DVec3); impl OldPosition { @@ -240,6 +238,12 @@ impl OldPosition { } } +impl Default for OldPosition { + fn default() -> Self { + Self(DVec3::NAN) + } +} + impl PartialEq for OldPosition { fn eq(&self, other: &Position) -> bool { self.0 == other.0 diff --git a/crates/valence_protocol/src/block_pos.rs b/crates/valence_protocol/src/block_pos.rs index 6ed491596..e9e6da3a8 100644 --- a/crates/valence_protocol/src/block_pos.rs +++ b/crates/valence_protocol/src/block_pos.rs @@ -1,5 +1,6 @@ use std::fmt; use std::io::Write; +use std::ops::{Add, Sub}; use anyhow::bail; use bitfield_struct::bitfield; diff --git a/crates/valence_registry/src/biome.rs b/crates/valence_registry/src/biome.rs index 6b8e5c288..0bad96070 100644 --- a/crates/valence_registry/src/biome.rs +++ b/crates/valence_registry/src/biome.rs @@ -18,7 +18,7 @@ use valence_ident::{ident, Ident}; use valence_nbt::serde::CompoundSerializer; use crate::codec::{RegistryCodec, RegistryValue}; -use crate::{Registry, RegistryIdx, RegistrySet}; +use crate::{Registry, RegistryIdx, UpdateRegistrySet}; pub struct BiomePlugin; @@ -26,7 +26,7 @@ impl Plugin for BiomePlugin { fn build(&self, app: &mut App) { app.init_resource::() .add_systems(PreStartup, load_default_biomes) - .add_systems(PostUpdate, update_biome_registry.before(RegistrySet)); + .add_systems(PostUpdate, update_biome_registry.before(UpdateRegistrySet)); } } diff --git a/crates/valence_registry/src/codec.rs b/crates/valence_registry/src/codec.rs index ee8d9f0ab..22cf32160 100644 --- a/crates/valence_registry/src/codec.rs +++ b/crates/valence_registry/src/codec.rs @@ -6,11 +6,11 @@ use tracing::error; use valence_ident::Ident; use valence_nbt::{compound, Compound, List, Value}; -use crate::RegistrySet; +use crate::UpdateRegistrySet; pub(super) fn build(app: &mut App) { app.init_resource::() - .add_systems(PostUpdate, cache_registry_codec.in_set(RegistrySet)); + .add_systems(PostUpdate, cache_registry_codec.in_set(UpdateRegistrySet)); } /// Contains the registry codec sent to all players while joining. This contains diff --git a/crates/valence_registry/src/dimension_type.rs b/crates/valence_registry/src/dimension_type.rs index 05786d8a3..e4e6057b3 100644 --- a/crates/valence_registry/src/dimension_type.rs +++ b/crates/valence_registry/src/dimension_type.rs @@ -16,7 +16,7 @@ use valence_ident::{ident, Ident}; use valence_nbt::serde::CompoundSerializer; use crate::codec::{RegistryCodec, RegistryValue}; -use crate::{Registry, RegistryIdx, RegistrySet}; +use crate::{Registry, RegistryIdx, UpdateRegistrySet}; pub struct DimensionTypePlugin; impl Plugin for DimensionTypePlugin { @@ -25,7 +25,7 @@ impl Plugin for DimensionTypePlugin { .add_systems(PreStartup, load_default_dimension_types) .add_systems( PostUpdate, - update_dimension_type_registry.before(RegistrySet), + update_dimension_type_registry.before(UpdateRegistrySet), ); } } diff --git a/crates/valence_registry/src/lib.rs b/crates/valence_registry/src/lib.rs index d9a59b105..a3c12be50 100644 --- a/crates/valence_registry/src/lib.rs +++ b/crates/valence_registry/src/lib.rs @@ -39,17 +39,15 @@ use valence_ident::Ident; pub struct RegistryPlugin; -/// The [`SystemSet`] where the [`RegistryCodec`](codec::RegistryCodec) and -/// [`TagsRegistry`](tags::TagsRegistry) caches are rebuilt. Systems that modify -/// the registry codec or tags registry should run _before_ this. -/// -/// This set lives in [`PostUpdate`]. +/// The [`SystemSet`] where the dynamic registry caches are rebuilt. Systems +/// that modify any of the dynamic registries should run _before_ this. Systems +/// that read the cached packets should run _after_ this. #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct RegistrySet; +pub struct UpdateRegistrySet; impl Plugin for RegistryPlugin { fn build(&self, app: &mut bevy_app::App) { - app.configure_set(PostUpdate, RegistrySet); + app.configure_set(PostUpdate, UpdateRegistrySet); codec::build(app); tags::build(app); @@ -109,10 +107,34 @@ impl Registry { self.items.get_mut(name.as_str()) } + #[track_caller] + pub fn by_index(&self, idx: I) -> (Ident<&str>, &V) { + let (k, v) = self + .items + .get_index(idx.to_index()) + .unwrap_or_else(|| panic!("out of bounds registry index of {}", idx.to_index())); + + (k.as_str_ident(), v) + } + + #[track_caller] + pub fn by_index_mut(&mut self, idx: I) -> (Ident<&str>, &mut V) { + let (k, v) = self + .items + .get_index_mut(idx.to_index()) + .unwrap_or_else(|| panic!("out of bounds registry index of {}", idx.to_index())); + + (k.as_str_ident(), v) + } + pub fn index_of(&self, name: Ident<&str>) -> Option { self.items.get_index_of(name.as_str()).map(I::from_index) } + pub fn len(&self) -> usize { + self.items.len() + } + pub fn iter( &self, ) -> impl DoubleEndedIterator, &V)> + ExactSizeIterator + '_ { @@ -136,19 +158,13 @@ impl Index for Registry { type Output = V; fn index(&self, index: I) -> &Self::Output { - self.items - .get_index(index.to_index()) - .unwrap_or_else(|| panic!("out of bounds registry index of {}", index.to_index())) - .1 + self.by_index(index).1 } } impl IndexMut for Registry { fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.items - .get_index_mut(index.to_index()) - .unwrap_or_else(|| panic!("out of bounds registry index of {}", index.to_index())) - .1 + self.by_index_mut(index).1 } } diff --git a/crates/valence_registry/src/tags.rs b/crates/valence_registry/src/tags.rs index abcd9b92e..17e689666 100644 --- a/crates/valence_registry/src/tags.rs +++ b/crates/valence_registry/src/tags.rs @@ -7,7 +7,7 @@ pub use valence_protocol::packets::play::synchronize_tags_s2c::RegistryMap; use valence_protocol::packets::play::SynchronizeTagsS2c; use valence_server_common::Server; -use crate::RegistrySet; +use crate::UpdateRegistrySet; #[derive(Debug, Resource, Default)] pub struct TagsRegistry { @@ -18,7 +18,7 @@ pub struct TagsRegistry { pub(super) fn build(app: &mut App) { app.init_resource::() .add_systems(PreStartup, init_tags_registry) - .add_systems(PostUpdate, cache_tags_packet.in_set(RegistrySet)); + .add_systems(PostUpdate, cache_tags_packet.in_set(UpdateRegistrySet)); } impl TagsRegistry { diff --git a/crates/valence_scoreboard/src/lib.rs b/crates/valence_scoreboard/src/lib.rs index e7a2b9a68..8e3ec0b45 100644 --- a/crates/valence_scoreboard/src/lib.rs +++ b/crates/valence_scoreboard/src/lib.rs @@ -28,7 +28,7 @@ pub use components::*; use tracing::{debug, warn}; use valence_server::client::{Client, OldVisibleEntityLayers, VisibleEntityLayers}; use valence_server::entity::EntityLayerId; -use valence_server::layer::UpdateLayersPreClientSet; +use valence_server::layer_old::UpdateLayersPreClientSet; use valence_server::protocol::packets::play::scoreboard_display_s2c::ScoreboardPosition; use valence_server::protocol::packets::play::scoreboard_objective_update_s2c::{ ObjectiveMode, ObjectiveRenderType, diff --git a/crates/valence_server/src/client.rs b/crates/valence_server/src/client.rs index 5bf669737..4c9fe461e 100644 --- a/crates/valence_server/src/client.rs +++ b/crates/valence_server/src/client.rs @@ -34,7 +34,7 @@ use valence_protocol::sound::{Sound, SoundCategory, SoundId}; use valence_protocol::text::{IntoText, Text}; use valence_protocol::var_int::VarInt; use valence_protocol::{BlockPos, Encode, GameMode, Packet}; -use valence_registry::RegistrySet; +use valence_registry::UpdateRegistrySet; use valence_server_common::{Despawned, UniqueId}; use crate::layer::{ChunkLayer, EntityLayer, UpdateLayersPostClientSet, UpdateLayersPreClientSet}; @@ -65,7 +65,7 @@ impl Plugin for ClientPlugin { PostUpdate, ( ( - crate::spawn::initial_join.after(RegistrySet), + crate::spawn::initial_join.after(UpdateRegistrySet), update_chunk_load_dist, handle_layer_messages.after(update_chunk_load_dist), update_view_and_layers @@ -120,7 +120,7 @@ pub struct ClientBundle { pub old_visible_entity_layers: OldVisibleEntityLayers, pub keepalive_state: crate::keepalive::KeepaliveState, pub ping: crate::keepalive::Ping, - pub teleport_state: crate::teleport::TeleportState, + pub teleport_state: crate::position::TeleportState, pub game_mode: GameMode, pub prev_game_mode: crate::spawn::PrevGameMode, pub death_location: crate::spawn::DeathLocation, @@ -161,7 +161,7 @@ impl ClientBundle { old_visible_entity_layers: OldVisibleEntityLayers(BTreeSet::new()), keepalive_state: crate::keepalive::KeepaliveState::new(), ping: Default::default(), - teleport_state: crate::teleport::TeleportState::new(), + teleport_state: crate::position::TeleportState::new(), game_mode: GameMode::default(), prev_game_mode: Default::default(), death_location: Default::default(), @@ -275,13 +275,9 @@ impl Client { } /// Flushes the packet queue to the underlying connection. - /// - /// This is called automatically at the end of the tick and when the client - /// is dropped. Unless you're in a hurry, there's usually no reason to - /// call this method yourself. - /// - /// Returns an error if flushing was unsuccessful. - pub fn flush_packets(&mut self) -> anyhow::Result<()> { + // Note: This is private because flushing packets early would make us miss + // prepended packets. + fn flush_packets(&mut self) -> anyhow::Result<()> { let bytes = self.enc.take(); if !bytes.is_empty() { self.conn.try_send(bytes) @@ -591,6 +587,7 @@ fn update_chunk_load_dist( } } +/* fn handle_layer_messages( mut clients: Query<( Entity, @@ -1061,7 +1058,7 @@ pub(crate) fn update_view_and_layers( } }, ); -} +}*/ pub(crate) fn update_game_mode(mut clients: Query<(&mut Client, &GameMode), Changed>) { for (mut client, game_mode) in &mut clients { diff --git a/crates/valence_server/src/dimension_layer.rs b/crates/valence_server/src/dimension_layer.rs new file mode 100644 index 000000000..4e1f80e79 --- /dev/null +++ b/crates/valence_server/src/dimension_layer.rs @@ -0,0 +1,263 @@ +pub mod batch; +pub mod block; +pub mod chunk; +pub mod index; +pub mod plugin; + +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +use bevy_ecs::query::WorldQuery; +use block::BlockRef; +use chunk::LoadedChunk; +pub use index::ChunkIndex; +use valence_protocol::packets::play::UnloadChunkS2c; +use valence_protocol::{BiomePos, BlockPos, ChunkPos, CompressionThreshold, WritePacket}; +use valence_registry::biome::BiomeId; +use valence_registry::dimension_type::DimensionTypeId; +use valence_registry::DimensionTypeRegistry; +use valence_server_common::Server; + +use self::batch::BlockBatch; +use self::block::Block; +use self::chunk::Chunk; +use crate::layer::message::{LayerMessages, MessageScope}; +use crate::layer::{ChunkViewIndex, LayerViewers}; + +#[derive(Component)] +pub struct DimensionLayerBundle { + pub chunk_index: ChunkIndex, + pub block_batch: BlockBatch, + pub chunk_view_index: ChunkViewIndex, + pub layer_viewers: LayerViewers, + pub layer_messages: LayerMessages, +} + +impl DimensionLayerBundle { + pub fn new( + dimension_type: DimensionTypeId, + dimensions: &DimensionTypeRegistry, + server: &Server, + ) -> Self { + let dim = &dimensions[dimension_type]; + + Self { + chunk_index: ChunkIndex::new(dimension_type, dimensions, server), + block_batch: Default::default(), + chunk_view_index: Default::default(), + layer_viewers: Default::default(), + layer_messages: LayerMessages::new(server.compression_threshold()), + } + } +} + +#[derive(WorldQuery)] +#[world_query(mutable)] +pub struct DimensionLayerQuery { + pub chunk_index: &'static mut ChunkIndex, + pub block_batch: &'static mut BlockBatch, + pub chunk_view_index: &'static mut ChunkViewIndex, + pub layer_viewers: &'static LayerViewers, + pub layer_messages: &'static mut LayerMessages, +} + +macro_rules! immutable_query_methods { + () => { + pub fn dimension_type(&self) -> DimensionTypeId { + self.chunk_index.dimension_type() + } + + pub fn height(&self) -> i32 { + self.chunk_index.height() + } + + pub fn min_y(&self) -> i32 { + self.chunk_index.height() + } + + pub fn biome(&self, pos: impl Into) -> Option { + todo!() + } + + pub fn block(&self, pos: impl Into) -> Option { + todo!() + } + + pub fn chunk(&self, pos: impl Into) -> Option<&LoadedChunk> { + self.chunk_index.get(pos) + } + }; +} + +impl DimensionLayerQueryItem<'_> { + immutable_query_methods!(); + + pub fn set_biome(&mut self, pos: impl Into, biome: BiomeId) -> Option { + todo!() + } + + pub fn set_block(&mut self, pos: impl Into, block: impl Into) { + todo!() + } + + pub fn chunk_mut(&mut self, pos: impl Into) -> Option<&mut LoadedChunk> { + self.chunk_index.get_mut(pos) + } + + pub fn insert_chunk(&mut self, pos: impl Into, chunk: Chunk) -> Option { + match self.chunk_entry(pos) { + Entry::Occupied(mut entry) => Some(entry.insert(chunk)), + Entry::Vacant(entry) => { + entry.insert(chunk); + None + } + } + } + + pub fn remove_chunk(&mut self, pos: impl Into) -> Option { + match self.chunk_entry(pos) { + Entry::Occupied(entry) => Some(entry.remove()), + Entry::Vacant(_) => None, + } + } + + pub fn chunk_entry(&mut self, pos: impl Into) -> Entry { + match self.chunk_index.entry(pos) { + index::Entry::Occupied(entry) => Entry::Occupied(OccupiedEntry { + chunk_index: self.chunk_index, + chunk_view_index: &*self.chunk_view_index, + layer_messages: self.layer_messages, + entry, + }), + index::Entry::Vacant(entry) => Entry::Vacant(VacantEntry { + chunk_index: self.chunk_index, + chunk_view_index: &*self.chunk_view_index, + layer_messages: self.layer_messages, + entry, + }), + } + } +} + +impl DimensionLayerQueryReadOnlyItem<'_> { + immutable_query_methods!(); +} + +struct DimensionInfo { + dimension_type: DimensionTypeId, + height: i32, + min_y: i32, + biome_registry_len: i32, + threshold: CompressionThreshold, +} + +#[derive(Debug)] +pub enum Entry<'a> { + Occupied(OccupiedEntry<'a>), + Vacant(VacantEntry<'a>), +} + +impl<'a> Entry<'a> { + pub fn or_default(self) -> &'a mut LoadedChunk { + match self { + Entry::Occupied(oe) => oe.into_mut(), + Entry::Vacant(ve) => ve.insert(Chunk::new()), + } + } +} + +#[derive(Debug)] +pub struct OccupiedEntry<'a> { + chunk_index: Mut<'a, ChunkIndex>, + chunk_view_index: &'a ChunkViewIndex, + layer_messages: Mut<'a, LayerMessages>, + entry: index::OccupiedEntry<'a>, +} + +impl<'a> OccupiedEntry<'a> { + pub fn get(&self) -> &LoadedChunk { + self.entry.get() + } + + pub fn get_mut(&mut self) -> &mut LoadedChunk { + self.entry.get_mut() + } + + pub fn insert(&mut self, chunk: Chunk) -> Chunk { + let pos = *self.key(); + + let viewer_count = self.entry.get().viewer_count; + + let res = self.entry.insert(chunk); + + if viewer_count > 0 { + let loaded = self.entry.get_mut(); + + let w = self + .layer_messages + .packet_writer(MessageScope::ChunkView { pos }); + + loaded.write_chunk_init_packet(w, pos, &self.chunk_index.info); + loaded.viewer_count = viewer_count; + } + + res + } + + pub fn into_mut(self) -> &'a mut LoadedChunk { + self.entry.into_mut() + } + + pub fn key(&self) -> &ChunkPos { + self.entry.key() + } + + pub fn remove(self) -> Chunk { + self.remove_entry().1 + } + + pub fn remove_entry(mut self) -> (ChunkPos, Chunk) { + if self.get().viewer_count > 0 { + self.layer_messages + .write_packet(&UnloadChunkS2c { pos: *self.key() }); + } + + self.entry.remove_entry() + } +} + +#[derive(Debug)] +pub struct VacantEntry<'a> { + chunk_index: Mut<'a, ChunkIndex>, + chunk_view_index: &'a ChunkViewIndex, + layer_messages: Mut<'a, LayerMessages>, + entry: index::VacantEntry<'a>, +} + +impl<'a> VacantEntry<'a> { + pub fn insert(mut self, chunk: Chunk) -> &'a mut LoadedChunk { + let pos = *self.key(); + + let viewer_count = self.chunk_view_index.get(pos).len(); + + let loaded = self.entry.insert(chunk); + + if viewer_count > 0 { + let w = self + .layer_messages + .packet_writer(MessageScope::ChunkView { pos }); + + loaded.write_chunk_init_packet(w, pos, &self.chunk_index.info); + loaded.viewer_count = viewer_count as u32; + } + + loaded + } + + pub fn into_key(self) -> ChunkPos { + self.entry.into_key() + } + + pub fn key(&self) -> &ChunkPos { + self.entry.key() + } +} diff --git a/crates/valence_server/src/dimension_layer/batch.rs b/crates/valence_server/src/dimension_layer/batch.rs new file mode 100644 index 000000000..4c40f309d --- /dev/null +++ b/crates/valence_server/src/dimension_layer/batch.rs @@ -0,0 +1,402 @@ +use bevy_ecs::prelude::*; +use bitfield_struct::bitfield; +use valence_generated::block::BlockEntityKind; +use valence_nbt::Compound; +use valence_protocol::encode::PacketWriter; +use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; +use valence_protocol::packets::play::{BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDeltaUpdateS2c}; +use valence_protocol::{BlockPos, ChunkPos, ChunkSectionPos, WritePacket}; + +use super::block::Block; +use super::ChunkIndex; +use crate::layer::{LayerViewers, PacketBuf}; +use crate::layer_old::chunk::LocalMsg; +use crate::{BlockState, ChunkLayer, Client, Layer}; + +#[derive(Component, Default)] +pub struct BlockBatch { + updates: Vec, + block_entities: Vec<(BlockPos, BlockEntityKind, Compound)>, +} + +impl BlockBatch { + pub fn set_block(&mut self, pos: impl Into, block: impl Into) { + let pos = pos.into(); + let block = block.into(); + + self.updates.push(BlockUpdate::from_parts(pos, block.state)); + + if let Some(nbt) = block.nbt { + self.block_entities + .push((pos, block.state.block_entity_kind(), nbt)); + } + } + + pub fn clear(&mut self) { + self.updates.clear(); + self.block_entities.clear(); + } + + pub fn shrink_to_fit(&mut self) { + self.updates.shrink_to_fit(); + self.block_entities.shrink_to_fit(); + } + + pub(super) fn apply( + &mut self, + chunks: &mut ChunkIndex, + viewers: &LayerViewers, + clients: &mut Query<&mut Client>, + buf: &mut PacketBuf, + ) { + todo!(); + + buf.broadcast(clients); + } +} + +#[bitfield(u128, order = Msb)] +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct BlockUpdate { + // Section coordinate. + #[bits(28)] + section_x: i32, + #[bits(28)] + section_z: i32, + #[bits(28)] + section_y: i32, + // Coordinate within the section. + #[bits(4)] + off_x: u32, + #[bits(4)] + off_z: u32, + #[bits(4)] + off_y: u32, + /// Bits of the [`BlockState`]. + state: u32, +} + +impl BlockUpdate { + const CHUNK_POS_MASK: u128 = u128::MAX << 72; + const SECTION_POS_MASK: u128 = u128::MAX << 44; + const BLOCK_POS_MASK: u128 = u128::MAX << 32; + + fn from_parts(pos: BlockPos, state: BlockState) { + Self::new() + .with_section_x(pos.x.div_euclid(16)) + .with_section_y(pos.y.div_euclid(16)) + .with_section_z(pos.z.div_euclid(16)) + .with_off_x(pos.x.rem_euclid(16) as u32) + .with_off_y(pos.y.rem_euclid(16) as u32) + .with_off_z(pos.z.rem_euclid(16) as u32) + .with_state(state.to_raw() as u32) + } + + fn to_parts(self) -> (BlockPos, BlockState) { + ( + BlockPos { + x: self.section_x() * 16 + self.off_x() as i32, + y: self.section_y() * 16 + self.off_y() as i32, + z: self.section_z() * 16 + self.off_z() as i32, + }, + BlockState::from_raw(self.state()).unwrap(), + ) + } + + // fn from_block_state(pos: BlockPos, state: BlockState) -> Self { + // Self::new() + // .with_section_x(pos.x.div_euclid(16)) + // .with_section_y(pos.y.div_euclid(16)) + // .with_section_z(pos.z.div_euclid(16)) + // .with_off_x(pos.x.rem_euclid(16) as u32) + // .with_off_y(pos.y.rem_euclid(16) as u32) + // .with_off_z(pos.z.rem_euclid(16) as u32) + // .with_state(state.to_raw() as u32) + // } + + // fn from_index(pos: BlockPos, idx: u32) -> Self { + // Self::new() + // .with_section_x(pos.x.div_euclid(16)) + // .with_section_y(pos.y.div_euclid(16)) + // .with_section_z(pos.z.div_euclid(16)) + // .with_off_x(pos.x.rem_euclid(16) as u32) + // .with_off_y(pos.y.rem_euclid(16) as u32) + // .with_off_z(pos.z.rem_euclid(16) as u32) + // .with_is_index(true) + // .with_state(idx) + // } + + // fn to_parts(self) -> (BlockPos, BlockState) { + // (self.block_pos(), self.block()) + // } + + // fn block_pos(self) -> BlockPos { + // BlockPos { + // x: self.section_x() * 16 + self.off_x() as i32, + // y: self.section_y() * 16 + self.off_y() as i32, + // z: self.section_z() * 16 + self.off_z() as i32, + // } + // } + + // fn chunk_pos(self) -> ChunkPos { + // ChunkPos { + // x: self.section_x(), + // z: self.section_z(), + // } + // } + + // fn block(self) -> BlockState { + // BlockState::from_raw(self.state() as u16).unwrap() + // } +} + +/* +impl ChunkLayer { + pub fn set_block( + &mut self, + pos: impl Into, + block: impl Into, + ) -> Option { + let pos = pos.into(); + let block: Block = block.into(); + + let chunk = self.chunk_mut(pos)?; + + let [x, y, z] = block_offsets(pos, self.info.min_y, self.info.height as i32)?; + + self.block_updates + .updates + .push(BlockUpdate::from_parts(pos, block.state)); + + if let (Some(data), Some(kind)) = (block.nbt, block.state.to_kind()) { + PacketWriter::new( + &mut self.block_updates.block_entity_buf, + self.info.threshold, + ) + .write_packet(&BlockEntityUpdateS2c { + position: pos, + kind, + data: Cow::Borrowed(&data), + }); + } + + Some(chunk.set_block(x, y, z, block)) + } + + pub fn flush_block_updates(&mut self) { + // Sort in reverse so the dedup keeps the last of consecutive elements. + self.block_updates.updates.sort_by(|a, b| b.cmp(a)); + + // Eliminate redundant block assignments. + self.block_updates + .updates + .dedup_by_key(|u| u.0 & BlockUpdate::BLOCK_POS_MASK); + + let sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); + + for update in self.block_updates.updates.drain(..) { + let (pos, state) = update.to_parts(); + + let new_sect_pos = ChunkSectionPos::from(pos); + + if sect_pos != new_sect_pos { + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + match self.block_updates.entry_buf.as_slice() { + // Zero updates. Do nothing. + &[] => {} + // One update. Send singular block update packet. + &[update] => self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { + position: BlockPos { + x: sect_pos.x * 16 + update.off_x() as i32, + y: sect_pos.y * 16 + update.off_y() as i32, + z: sect_pos.z * 16 + update.off_z() as i32, + }, + block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), + }); + }), + // >1 updates. Send special section update packet. + updates => { + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet( + &ChunkDeltaUpdateS2c { + chunk_section_pos: sect_pos, + blocks: Cow::Borrowed(updates), + }, + ) + }); + } + } + + self.block_updates.entry_buf.clear(); + } + } + + /* + let mut chunk: Option<&mut LoadedChunk> = None; + let mut sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); + for update in self.block_updates.updates.drain(..) { + let (pos, state, has_nbt) = update.to_parts(); + let new_sect_pos = ChunkSectionPos::from(pos); + + // Is this block in a new section? If it is, then flush the changes we've + // accumulated for the old section. + if sect_pos != new_sect_pos { + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + match self.block_updates.entry_buf.as_slice() { + // Zero updates. Do nothing. + &[] => {} + // One update. Send singular block update packet. + &[update] => self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { + position: BlockPos { + x: sect_pos.x * 16 + update.off_x() as i32, + y: sect_pos.y * 16 + update.off_y() as i32, + z: sect_pos.z * 16 + update.off_z() as i32, + }, + block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), + }); + }), + // >1 updates. Send special section update packet. + updates => { + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet( + &ChunkDeltaUpdateS2c { + chunk_section_pos: sect_pos, + blocks: Cow::Borrowed(updates), + }, + ) + }); + } + } + + self.block_updates.entry_buf.clear(); + + // Send the block entity update packets. + // It's important that this is ordered after block state updates are sent. + for (pos, kind, nbt) in nbt.take(nbt_count) { + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet( + &BlockEntityUpdateS2c { + position: pos, + kind, + data: Cow::Owned(nbt), + }, + ) + }); + } + + // Update the chunk ref if the chunk pos changed. + if sect_pos.x != new_sect_pos.x || sect_pos.z != new_sect_pos.z { + chunk = self.chunks.get_mut(&ChunkPos::from(new_sect_pos)); + } + + // Update section pos. + sect_pos = new_sect_pos; + } + + // // Send block entity updates for the current block. + // if has_nbt { + // let nbt = nbt.next().unwrap(); + + // if let Some(kind) = state.to_kind() { + // let msg = LocalMsg::PacketAt { + // pos: sect_pos.into(), + // }; + + // self.messages.send_local_infallible(msg, |w| { + // PacketWriter::new(w, self.info.threshold).write_packet( + // &BlockEntityUpdateS2c { + // position: pos, + // kind, + // data: Cow::Owned(nbt), + // }, + // ) + // }); + // } + // } + + if let Some(chunk) = &mut chunk { + let chunk_y = pos.y.wrapping_sub(self.info.min_y) as u32; + + // Is the block pos in bounds of the chunk? + if chunk_y < self.info.height { + let chunk_x = pos.x.rem_euclid(16); + let chunk_z = pos.z.rem_euclid(16); + + // Make change to the chunk and push section update. + + if chunk.viewer_count_mut() > 0 { + self.block_update_buf.push( + ChunkDeltaUpdateEntry::new() + .with_off_x(chunk_x as u8) + .with_off_y((chunk_y % 16) as u8) + .with_off_z(chunk_z as u8) + .with_block_state(block.state.to_raw() as u32), + ); + } + + chunk.set_block(chunk_x as u32, chunk_y, chunk_z as u32, block); + } + } + } + + // Flush any remaining block changes. + + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + match self.block_update_buf.as_slice() { + // Zero updates. Do nothing. + &[] => {} + // One update. Send singular block update packet. + &[update] => self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { + position: BlockPos { + x: sect_pos.x * 16 + update.off_x() as i32, + y: sect_pos.y * 16 + update.off_y() as i32, + z: sect_pos.z * 16 + update.off_z() as i32, + }, + block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), + }); + }), + // >1 updates. Send special section update packet. + updates => { + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&ChunkDeltaUpdateS2c { + chunk_section_pos: sect_pos, + blocks: Cow::Borrowed(updates), + }) + }); + } + } + + self.block_update_buf.clear();*/ + } +} + +pub(super) struct BlockUpdates { + updates: Vec, + block_entities: Vec<(BlockPos, BlockEntityKind, Compound)>, + entry_buf: Vec, +} + +impl BlockUpdates { + pub(super) fn shrink_to_fit(&mut self) { + self.updates.shrink_to_fit(); + self.block_entities.shrink_to_fit(); + self.entry_buf.shrink_to_fit(); + } +} +*/ diff --git a/crates/valence_server/src/dimension_layer/block.rs b/crates/valence_server/src/dimension_layer/block.rs new file mode 100644 index 000000000..335baeafb --- /dev/null +++ b/crates/valence_server/src/dimension_layer/block.rs @@ -0,0 +1,59 @@ +use valence_nbt::Compound; +pub use valence_protocol::BlockState; + +/// Represents a complete block, which is a pair of block state and optional NBT +/// data for the block entity. +#[derive(Clone, PartialEq, Default, Debug)] +pub struct Block { + pub state: BlockState, + pub nbt: Option, +} + +impl Block { + pub const fn new(state: BlockState, nbt: Option) -> Self { + Self { state, nbt } + } +} + +impl From for Block { + fn from(state: BlockState) -> Self { + Self { state, nbt: None } + } +} + +/// Like [`Block`] but immutably referenced. +#[derive(Copy, Clone, PartialEq, Default, Debug)] +pub struct BlockRef<'a> { + pub state: BlockState, + pub nbt: Option<&'a Compound>, +} + +impl<'a> BlockRef<'a> { + pub const fn new(state: BlockState, nbt: Option<&'a Compound>) -> Self { + Self { state, nbt } + } +} + +impl From> for Block { + fn from(value: BlockRef<'_>) -> Self { + Self { + state: value.state, + nbt: value.nbt.cloned(), + } + } +} + +impl From for BlockRef<'_> { + fn from(state: BlockState) -> Self { + Self { state, nbt: None } + } +} + +impl<'a> From<&'a Block> for BlockRef<'a> { + fn from(value: &'a Block) -> Self { + Self { + state: value.state, + nbt: value.nbt.as_ref(), + } + } +} diff --git a/crates/valence_server/src/dimension_layer/chunk.rs b/crates/valence_server/src/dimension_layer/chunk.rs new file mode 100644 index 000000000..d8b0b2ebc --- /dev/null +++ b/crates/valence_server/src/dimension_layer/chunk.rs @@ -0,0 +1,375 @@ +use valence_nbt::Compound; +use valence_protocol::{BlockPos, BlockState}; +use valence_registry::biome::BiomeId; + +mod loaded; +mod paletted_container; +mod unloaded; + +pub use loaded::LoadedChunk; +pub use unloaded::Chunk; + +use super::{BiomePos, Block, BlockRef}; + +/// Common operations on chunks. Notable implementors are +/// [`LoadedChunk`](super::loaded::LoadedChunk) and +/// [`UnloadedChunk`](super::unloaded::UnloadedChunk). +pub trait ChunkOps { + /// Gets the height of this chunk in meters or blocks. + fn height(&self) -> u32; + + /// Gets the block at the provided position in this chunk. `x` and `z` + /// are in the range `0..16` while `y` is in the range `0..height`. + /// + /// # Panics + /// + /// May panic if the position is out of bounds. + #[track_caller] + fn block(&self, x: u32, y: u32, z: u32) -> BlockRef { + BlockRef { + state: self.block_state(x, y, z), + nbt: self.block_entity(x, y, z), + } + } + + /// Sets the block at the provided position in this chunk. `x` and `z` + /// are in the range `0..16` while `y` is in the range `0..height`. The + /// previous block at the position is returned. + /// + /// # Panics + /// + /// May panic if the position is out of bounds. + #[track_caller] + fn set_block(&mut self, x: u32, y: u32, z: u32, block: impl Into) -> Block { + let block = block.into(); + let old_state = self.set_block_state(x, y, z, block.state); + + let old_nbt = if block.nbt.is_none() && block.state.block_entity_kind().is_some() { + // If the block state is associated with a block entity, make sure there's + // always some NBT data. Otherwise, the block will appear invisible to clients + // when loading the chunk. + self.set_block_entity(x, y, z, Some(Compound::default())) + } else { + self.set_block_entity(x, y, z, block.nbt) + }; + + Block { + state: old_state, + nbt: old_nbt, + } + } + + /* + /// Sets all the blocks in the entire chunk to the provided block. + fn fill_blocks(&mut self, block: impl Into) { + let block = block.into_block(); + + self.fill_block_states(block.state); + + if block.nbt.is_some() { + for x in 0..16 { + for z in 0..16 { + for y in 0..self.height() { + self.set_block_entity(x, y, z, block.nbt.clone()); + } + } + } + } else { + self.clear_block_entities(); + } + } + */ + + /// Gets the block state at the provided position in this chunk. `x` and `z` + /// are in the range `0..16` while `y` is in the range `0..height`. + /// + /// # Panics + /// + /// May panic if the position is out of bounds. + #[track_caller] + fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState; + + /// Sets the block state at the provided position in this chunk. `x` and `z` + /// are in the range `0..16` while `y` is in the range `0..height`. The + /// previous block state at the position is returned. + /// + /// **NOTE:** This is a low-level function which may break expected + /// invariants for block entities. Prefer [`Self::set_block`] if performance + /// is not a concern. + /// + /// # Panics + /// + /// May panic if the position is out of bounds. + #[track_caller] + fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState; + + /// Replaces all block states in the entire chunk with the provided block + /// state. + /// + /// **NOTE:** This is a low-level function which may break expected + /// invariants for block entities. Prefer [`Self::fill_blocks`] instead. + fn fill_block_states(&mut self, block: BlockState) { + for sect_y in 0..self.height() / 16 { + self.fill_block_state_section(sect_y, block); + } + } + + /// Replaces all the block states in a section with the provided block + /// state. + /// + /// **NOTE:** This is a low-level function which may break expected + /// invariants for block entities. Prefer [`Self::set_block`] if performance + /// is not a concern. + /// + /// # Panics + /// + /// May panic if the section offset is out of bounds. + #[track_caller] + fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState); + + /// Gets the block entity at the provided position in this chunk. `x` and + /// `z` are in the range `0..16` while `y` is in the range `0..height`. + /// + /// # Panics + /// + /// May panic if the position is out of bounds. + #[track_caller] + fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound>; + + /// Gets a mutable reference to the block entity at the provided position in + /// this chunk. `x` and `z` are in the range `0..16` while `y` is in the + /// range `0..height`. + /// + /// # Panics + /// + /// May panic if the position is out of bounds. + #[track_caller] + fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound>; + + /// Sets the block entity at the provided position in this chunk. `x` and + /// `z` are in the range `0..16` while `y` is in the range `0..height`. + /// The previous block entity at the position is returned. + /// + /// **NOTE:** This is a low-level function which may break expected + /// invariants for block entities. Prefer [`Self::set_block`] if performance + /// is not a concern. + /// + /// # Panics + /// + /// May panic if the position is out of bounds. + #[track_caller] + fn set_block_entity( + &mut self, + x: u32, + y: u32, + z: u32, + block_entity: Option, + ) -> Option; + + /// Removes all block entities from the chunk. + /// + /// **NOTE:** This is a low-level function which may break expected + /// invariants for block entities. Prefer [`Self::set_block`] if performance + /// is not a concern. + fn clear_block_entities(&mut self); + + /// Gets the biome at the provided position in this chunk. `x` and `z` are + /// in the range `0..4` while `y` is in the range `0..height / 4`. + /// + /// Note that biomes are 4x4x4 segments of a chunk, so the xyz arguments to + /// this method differ from those to [`Self::block_state`] and + /// [`Self::block_entity`]. + /// + /// # Panics + /// + /// May panic if the position is out of bounds. + #[track_caller] + fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId; + + /// Sets the biome at the provided position in this chunk. The Previous + /// biome at the position is returned. `x` and `z` are in the range `0..4` + /// while `y` is in the range `0..height / 4`. + /// + /// Note that biomes are 4x4x4 segments of a chunk, so the xyz arguments to + /// this method differ from those to [`Self::block_state`] and + /// [`Self::block_entity`]. + /// + /// # Panics + /// + /// May panic if the position is out of bounds. + #[track_caller] + fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId; + + /// Sets all the biomes in the entire chunk to the provided biome. + fn fill_biomes(&mut self, biome: BiomeId) { + for sect_y in 0..self.height() / 16 { + self.fill_biome_section(sect_y, biome); + } + } + + /// Replaces all the biomes in a section with the provided biome. + /// + /// # Panics + /// + /// May panic if the section offset is out of bounds. + #[track_caller] + fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId); + + /// Sets all blocks and biomes in this chunk to the default values. The + /// height of the chunk is not modified. + fn clear(&mut self) { + self.fill_block_states(BlockState::AIR); + self.fill_biomes(BiomeId::default()); + self.clear_block_entities(); + } + + /// Attempts to optimize this chunk by reducing its memory usage or other + /// characteristics. This may be a relatively expensive operation. + /// + /// This method must not alter the semantics of the chunk in any observable + /// way. + fn shrink_to_fit(&mut self); +} + +/// The maximum height of a chunk. +pub const MAX_HEIGHT: u32 = 4096; + +pub(super) const SECTION_BLOCK_COUNT: usize = 16 * 16 * 16; +pub(super) const SECTION_BIOME_COUNT: usize = 4 * 4 * 4; + +/// Returns the minimum number of bits needed to represent the integer `n`. +pub(super) const fn bit_width(n: usize) -> usize { + (usize::BITS - n.leading_zeros()) as _ +} + +pub(super) fn block_offsets(block_pos: BlockPos, min_y: i32, height: i32) -> Option<[u32; 3]> { + let off_x = block_pos.x.rem_euclid(16); + let off_z = block_pos.z.rem_euclid(16); + let off_y = block_pos.y.wrapping_sub(min_y); + + if off_y < height { + Some([off_x as u32, off_y as u32, off_z as u32]) + } else { + None + } +} + +pub(super) fn biome_offsets(biome_pos: BiomePos, min_y: i32, height: i32) -> Option<[u32; 3]> { + let off_x = biome_pos.x.rem_euclid(4); + let off_z = biome_pos.z.rem_euclid(4); + let off_y = biome_pos.y.wrapping_sub(min_y / 4); + + if off_y < height / 4 { + Some([off_x as u32, off_y as u32, off_z as u32]) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn chunk_get_set() { + fn check(mut chunk: impl ChunkOps) { + assert_eq!( + chunk.set_block_state(1, 2, 3, BlockState::CHAIN), + BlockState::AIR + ); + assert_eq!( + chunk.set_block_state(1, 2, 3, BlockState::AIR), + BlockState::CHAIN + ); + + assert_eq!(chunk.set_block_entity(1, 2, 3, Some(Compound::new())), None); + assert_eq!(chunk.set_block_entity(1, 2, 3, None), Some(Compound::new())); + } + + let unloaded = Chunk::with_height(512); + let loaded = LoadedChunk::new(512); + + check(unloaded); + check(loaded); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_0() { + let mut chunk = Chunk::with_height(512); + chunk.set_block_state(0, 0, 16, BlockState::AIR); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_1() { + let mut chunk = LoadedChunk::new(512); + chunk.set_block_state(0, 0, 16, BlockState::AIR); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_2() { + let mut chunk = Chunk::with_height(512); + chunk.set_block_entity(0, 0, 16, None); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_3() { + let mut chunk = LoadedChunk::new(512); + chunk.set_block_entity(0, 0, 16, None); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_4() { + let mut chunk = Chunk::with_height(512); + chunk.set_biome(0, 0, 4, BiomeId::DEFAULT); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_5() { + let mut chunk = LoadedChunk::new(512); + chunk.set_biome(0, 0, 4, BiomeId::DEFAULT); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_6() { + let mut chunk = Chunk::with_height(512); + chunk.fill_block_state_section(chunk.height() / 16, BlockState::AIR); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_7() { + let mut chunk = LoadedChunk::new(512); + chunk.fill_block_state_section(chunk.height() / 16, BlockState::AIR); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_8() { + let mut chunk = Chunk::with_height(512); + chunk.fill_biome_section(chunk.height() / 16, BiomeId::DEFAULT); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn chunk_debug_oob_9() { + let mut chunk = LoadedChunk::new(512); + chunk.fill_biome_section(chunk.height() / 16, BiomeId::DEFAULT); + } +} diff --git a/crates/valence_server/src/dimension_layer/chunk/loaded.rs b/crates/valence_server/src/dimension_layer/chunk/loaded.rs new file mode 100644 index 000000000..63d689e81 --- /dev/null +++ b/crates/valence_server/src/dimension_layer/chunk/loaded.rs @@ -0,0 +1,312 @@ +use std::borrow::Cow; +use std::mem; + +use valence_nbt::{compound, Compound}; +use valence_protocol::encode::{PacketWriter, WritePacket}; +use valence_protocol::packets::play::chunk_data_s2c::ChunkDataBlockEntity; +use valence_protocol::packets::play::ChunkDataS2c; +use valence_protocol::{BlockState, ChunkPos, Encode}; +use valence_registry::biome::BiomeId; +use valence_registry::RegistryIdx; + +use super::unloaded::Chunk; +use super::{bit_width, ChunkOps, SECTION_BLOCK_COUNT}; +use crate::dimension_layer::DimensionInfo; + +/// A chunk that is actively loaded in a dimension layer. This is only +/// accessible behind a reference. +/// +/// Like [`Chunk`], loaded chunks implement the [`ChunkOps`] trait so you can +/// use many of the same methods. +/// +/// **NOTE:** Loaded chunks are a low-level API. Mutations directly to loaded +/// chunks are intentionally not synchronized with clients. Consider using the +/// relevant methods from [`DimensionLayerQueryItem`] instead. +/// +/// [`DimensionLayerQueryItem`]: super::DimensionLayerQueryItem +#[derive(Debug)] +pub struct LoadedChunk { + /// Chunk data for this loaded chunk. + chunk: Chunk, + /// A count of the clients viewing this chunk. Useful for knowing if it's + /// necessary to record changes, since no client would be in view to receive + /// the changes if this were zero. + pub(in super::super) viewer_count: u32, + /// Cached bytes of the chunk initialization packet. The cache is considered + /// invalidated if empty. This should be cleared whenever the chunk is + /// modified in an observable way, even if the chunk is not viewed. + cached_chunk_packet: Vec, +} + +impl LoadedChunk { + pub(crate) fn new(height: i32) -> Self { + Self { + viewer_count: 0, + chunk: Chunk::with_height(height), + cached_chunk_packet: vec![], + } + } + + /// Sets the content of this loaded chunk to the supplied [`Chunk`]. The + /// given unloaded chunk is [resized] to match the height of this loaded + /// chunk prior to insertion. + /// + /// The previous chunk data is returned. + /// + /// [resized]: UnloadedChunk::set_height + pub fn replace(&mut self, mut chunk: Chunk) -> Chunk { + chunk.set_height(self.height()); + + self.cached_chunk_packet.clear(); + + mem::replace(&mut self.chunk, chunk) + } + + pub(in super::super) fn into_chunk(self) -> Chunk { + self.chunk + } + + /// Clones this chunk's data into the returned [`Chunk`]. + pub fn to_chunk(&self) -> Chunk { + self.chunk.clone() + } + + /// Returns the number of clients in view of this chunk. + pub fn viewer_count(&self) -> u32 { + self.viewer_count + } + + /// Writes the packet data needed to initialize this chunk. + pub(crate) fn write_chunk_init_packet( + &mut self, + mut writer: impl WritePacket, + pos: ChunkPos, + info: &DimensionInfo, + ) { + if self.cached_chunk_packet.is_empty() { + let heightmaps = compound! { + // TODO: MOTION_BLOCKING and WORLD_SURFACE heightmaps. + }; + + let mut blocks_and_biomes: Vec = vec![]; + + for sect in &self.chunk.sections { + sect.count_non_air_blocks() + .encode(&mut blocks_and_biomes) + .unwrap(); + + sect.block_states + .encode_mc_format( + &mut blocks_and_biomes, + |b| b.to_raw().into(), + 4, + 8, + bit_width(BlockState::max_raw().into()), + ) + .expect("paletted container encode should always succeed"); + + sect.biomes + .encode_mc_format( + &mut blocks_and_biomes, + |b| b.to_index() as _, + 0, + 3, + bit_width(info.biome_registry_len as usize - 1), + ) + .expect("paletted container encode should always succeed"); + } + + let block_entities: Vec<_> = self + .chunk + .block_entities + .iter() + .filter_map(|(&idx, nbt)| { + let x = idx % 16; + let z = idx / 16 % 16; + let y = idx / 16 / 16; + + let kind = self.chunk.sections[y as usize / 16] + .block_states + .get(idx as usize % SECTION_BLOCK_COUNT) + .block_entity_kind(); + + kind.map(|kind| ChunkDataBlockEntity { + packed_xz: ((x << 4) | z) as i8, + y: y as i16 + info.min_y as i16, + kind, + data: Cow::Borrowed(nbt), + }) + }) + .collect(); + + PacketWriter::new(&mut self.cached_chunk_packet, info.threshold).write_packet( + &ChunkDataS2c { + pos, + heightmaps: Cow::Owned(heightmaps), + blocks_and_biomes: &blocks_and_biomes, + block_entities: Cow::Owned(block_entities), + sky_light_mask: Cow::Borrowed(&[]), + block_light_mask: Cow::Borrowed(&[]), + empty_sky_light_mask: Cow::Borrowed(&[]), + empty_block_light_mask: Cow::Borrowed(&[]), + sky_light_arrays: Cow::Borrowed(&[]), + block_light_arrays: Cow::Borrowed(&[]), + }, + ) + } + + writer.write_packet_bytes(&self.cached_chunk_packet); + } +} + +impl ChunkOps for LoadedChunk { + fn height(&self) -> u32 { + self.chunk.height() + } + + fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState { + self.chunk.block_state(x, y, z) + } + + fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState { + let old_block = self.chunk.set_block_state(x, y, z, block); + + if block != old_block { + self.cached_chunk_packet.clear(); + } + + old_block + } + + fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) { + self.chunk.fill_block_state_section(sect_y, block); + + // TODO: do some checks to avoid calling this sometimes. + self.cached_chunk_packet.clear(); + } + + fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> { + self.chunk.block_entity(x, y, z) + } + + fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> { + let res = self.chunk.block_entity_mut(x, y, z); + + if res.is_some() { + self.cached_chunk_packet.clear(); + } + + res + } + + fn set_block_entity( + &mut self, + x: u32, + y: u32, + z: u32, + block_entity: Option, + ) -> Option { + self.cached_chunk_packet.clear(); + + self.chunk.set_block_entity(x, y, z, block_entity) + } + + fn clear_block_entities(&mut self) { + if self.chunk.block_entities.is_empty() { + return; + } + + self.chunk.clear_block_entities(); + + self.cached_chunk_packet.clear(); + } + + fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId { + self.chunk.biome(x, y, z) + } + + fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId { + let old_biome = self.chunk.set_biome(x, y, z, biome); + + if biome != old_biome { + self.cached_chunk_packet.clear(); + } + + old_biome + } + + fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) { + self.chunk.fill_biome_section(sect_y, biome); + + self.cached_chunk_packet.clear(); + } + + fn shrink_to_fit(&mut self) { + self.cached_chunk_packet.shrink_to_fit(); + self.chunk.shrink_to_fit(); + } +} + +#[cfg(test)] +mod tests { + use valence_protocol::CompressionThreshold; + use valence_registry::dimension_type::DimensionTypeId; + + use super::*; + + #[test] + fn loaded_chunk_changes_clear_packet_cache() { + #[track_caller] + fn check(chunk: &mut LoadedChunk, change: impl FnOnce(&mut LoadedChunk) -> T) { + let info = DimensionInfo { + dimension_type: DimensionTypeId::default(), + height: 512, + min_y: -16, + biome_registry_len: 200, + threshold: CompressionThreshold(-1), + }; + + let mut buf = vec![]; + let mut writer = PacketWriter::new(&mut buf, CompressionThreshold(-1)); + + // Rebuild cache. + chunk.write_chunk_init_packet(&mut writer, ChunkPos::new(3, 4), &info); + + // Check that the cache is built. + assert!(!chunk.cached_chunk_packet.is_empty()); + + // Making a change should clear the cache. + change(chunk); + assert!(chunk.cached_chunk_packet.is_empty()); + + // Rebuild cache again. + chunk.write_chunk_init_packet(&mut writer, ChunkPos::new(3, 4), &info); + assert!(!chunk.cached_chunk_packet.is_empty()); + } + + let mut chunk = LoadedChunk::new(512); + + check(&mut chunk, |c| { + c.set_block_state(0, 4, 0, BlockState::ACACIA_WOOD) + }); + check(&mut chunk, |c| c.set_biome(1, 2, 3, BiomeId::from_index(4))); + check(&mut chunk, |c| c.fill_biomes(BiomeId::DEFAULT)); + check(&mut chunk, |c| c.fill_block_states(BlockState::WET_SPONGE)); + check(&mut chunk, |c| { + c.set_block_entity(3, 40, 5, Some(compound! {})) + }); + check(&mut chunk, |c| { + c.block_entity_mut(3, 40, 5).unwrap(); + }); + check(&mut chunk, |c| c.set_block_entity(3, 40, 5, None)); + + // Old block state is the same as new block state, so the cache should still be + // intact. + assert_eq!( + chunk.set_block_state(0, 0, 0, BlockState::WET_SPONGE), + BlockState::WET_SPONGE + ); + + assert!(!chunk.cached_chunk_packet.is_empty()); + } +} diff --git a/crates/valence_server/src/layer/chunk/paletted_container.rs b/crates/valence_server/src/dimension_layer/chunk/paletted_container.rs similarity index 99% rename from crates/valence_server/src/layer/chunk/paletted_container.rs rename to crates/valence_server/src/dimension_layer/chunk/paletted_container.rs index 302151d83..12520af34 100644 --- a/crates/valence_server/src/layer/chunk/paletted_container.rs +++ b/crates/valence_server/src/dimension_layer/chunk/paletted_container.rs @@ -5,7 +5,7 @@ use arrayvec::ArrayVec; use num_integer::div_ceil; use valence_protocol::{Encode, VarInt}; -use super::chunk::bit_width; +use crate::dimension_layer::chunk::bit_width; /// `HALF_LEN` must be equal to `ceil(LEN / 2)`. #[derive(Clone, Debug)] diff --git a/crates/valence_server/src/dimension_layer/chunk/unloaded.rs b/crates/valence_server/src/dimension_layer/chunk/unloaded.rs new file mode 100644 index 000000000..710bd3fed --- /dev/null +++ b/crates/valence_server/src/dimension_layer/chunk/unloaded.rs @@ -0,0 +1,243 @@ +use std::cmp::Ordering; +use std::collections::BTreeMap; + +use valence_nbt::Compound; +use valence_protocol::BlockState; +use valence_registry::biome::BiomeId; + +use super::paletted_container::PalettedContainer; +use super::{ChunkOps, MAX_HEIGHT, SECTION_BIOME_COUNT, SECTION_BLOCK_COUNT}; + +#[derive(Clone, Default, Debug)] +pub struct Chunk { + pub(super) sections: Vec
, + pub(super) block_entities: BTreeMap, +} + +#[derive(Clone, Default, Debug)] +pub(super) struct Section { + pub(super) block_states: + PalettedContainer, + pub(super) biomes: PalettedContainer, +} + +impl Section { + pub(super) fn count_non_air_blocks(&self) -> u16 { + let mut count = 0; + + match &self.block_states { + PalettedContainer::Single(s) => { + if !s.is_air() { + count += SECTION_BLOCK_COUNT as u16; + } + } + PalettedContainer::Indirect(ind) => { + for i in 0..SECTION_BLOCK_COUNT { + if !ind.get(i).is_air() { + count += 1; + } + } + } + PalettedContainer::Direct(dir) => { + for s in dir.as_ref() { + if !s.is_air() { + count += 1; + } + } + } + } + + count + } +} + +impl Chunk { + pub const fn new() -> Self { + Self { + sections: vec![], + block_entities: BTreeMap::new(), + } + } + + pub fn with_height(height: i32) -> Self { + Self { + sections: vec![Section::default(); height.max(0) as usize / 16], + block_entities: BTreeMap::new(), + } + } + + /// Sets the height of this chunk in blocks. The chunk is truncated or + /// extended with [`BlockState::AIR`] and [`BiomeId::default()`] from the + /// top. + /// + /// The new height should be a multiple of 16 and no more than + /// [`MAX_HEIGHT`]. Otherwise, the height is rounded down to the nearest + /// valid height. + pub fn set_height(&mut self, height: u32) { + let new_count = height.min(MAX_HEIGHT) as usize / 16; + let old_count = self.sections.len(); + + match new_count.cmp(&old_count) { + Ordering::Less => { + self.sections.truncate(new_count); + self.sections.shrink_to_fit(); + + let cutoff = SECTION_BLOCK_COUNT as u32 * new_count as u32; + self.block_entities.retain(|idx, _| *idx < cutoff); + } + Ordering::Equal => {} + Ordering::Greater => { + let diff = new_count - old_count; + self.sections.reserve_exact(diff); + self.sections.extend((0..diff).map(|_| Section::default())); + } + } + } +} + +impl ChunkOps for Chunk { + fn height(&self) -> u32 { + self.sections.len() as u32 * 16 + } + + fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState { + check_block_oob(self, x, y, z); + + let idx = x + z * 16 + y % 16 * 16 * 16; + self.sections[y as usize / 16] + .block_states + .get(idx as usize) + } + + fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState { + check_block_oob(self, x, y, z); + + let idx = x + z * 16 + y % 16 * 16 * 16; + self.sections[y as usize / 16] + .block_states + .set(idx as usize, block) + } + + fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) { + check_section_oob(self, sect_y); + + self.sections[sect_y as usize].block_states.fill(block); + } + + fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> { + check_block_oob(self, x, y, z); + + let idx = x + z * 16 + y * 16 * 16; + self.block_entities.get(&idx) + } + + fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> { + check_block_oob(self, x, y, z); + + let idx = x + z * 16 + y * 16 * 16; + self.block_entities.get_mut(&idx) + } + + fn set_block_entity( + &mut self, + x: u32, + y: u32, + z: u32, + block_entity: Option, + ) -> Option { + check_block_oob(self, x, y, z); + + let idx = x + z * 16 + y * 16 * 16; + + match block_entity { + Some(be) => self.block_entities.insert(idx, be), + None => self.block_entities.remove(&idx), + } + } + + fn clear_block_entities(&mut self) { + self.block_entities.clear(); + } + + fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId { + check_biome_oob(self, x, y, z); + + let idx = x + z * 4 + y % 4 * 4 * 4; + self.sections[y as usize / 4].biomes.get(idx as usize) + } + + fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId { + check_biome_oob(self, x, y, z); + + let idx = x + z * 4 + y % 4 * 4 * 4; + self.sections[y as usize / 4] + .biomes + .set(idx as usize, biome) + } + + fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) { + check_section_oob(self, sect_y); + + self.sections[sect_y as usize].biomes.fill(biome); + } + + fn shrink_to_fit(&mut self) { + for sect in &mut self.sections { + sect.block_states.shrink_to_fit(); + sect.biomes.shrink_to_fit(); + } + } +} + +#[inline] +#[track_caller] +pub(super) fn check_block_oob(chunk: &impl ChunkOps, x: u32, y: u32, z: u32) { + assert!( + x < 16 && y < chunk.height() && z < 16, + "chunk block offsets of ({x}, {y}, {z}) are out of bounds" + ); +} + +#[inline] +#[track_caller] +pub(super) fn check_biome_oob(chunk: &impl ChunkOps, x: u32, y: u32, z: u32) { + assert!( + x < 4 && y < chunk.height() / 4 && z < 4, + "chunk biome offsets of ({x}, {y}, {z}) are out of bounds" + ); +} + +#[inline] +#[track_caller] +pub(super) fn check_section_oob(chunk: &impl ChunkOps, sect_y: u32) { + assert!( + sect_y < chunk.height() / 16, + "chunk section offset of {sect_y} is out of bounds" + ); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn chunk_resize_removes_block_entities() { + let mut chunk = Chunk::with_height(32); + + assert_eq!(chunk.height(), 32); + + // First block entity is in section 0. + chunk.set_block_entity(0, 5, 0, Some(Compound::new())); + + // Second block entity is in section 1. + chunk.set_block_entity(0, 16, 0, Some(Compound::new())); + + // Remove section 0. + chunk.set_height(16); + assert_eq!(chunk.height(), 16); + + assert_eq!(chunk.block_entity(0, 5, 0), Some(&Compound::new())); + assert_eq!(chunk.set_block_entity(0, 5, 0, None), Some(Compound::new())); + assert!(chunk.block_entities.is_empty()); + } +} diff --git a/crates/valence_server/src/dimension_layer/index.rs b/crates/valence_server/src/dimension_layer/index.rs new file mode 100644 index 000000000..757570ce0 --- /dev/null +++ b/crates/valence_server/src/dimension_layer/index.rs @@ -0,0 +1,170 @@ +pub use bevy_ecs::prelude::*; +use rustc_hash::FxHashMap; +use valence_protocol::ChunkPos; +use valence_registry::dimension_type::DimensionTypeId; +use valence_registry::DimensionTypeRegistry; +use valence_server_common::Server; + +use super::chunk::{Chunk, LoadedChunk}; +use super::DimensionInfo; + +/// The mapping of chunk positions to [`LoadedChunk`]s in a dimension layer. +/// +/// **NOTE**: Modifying the chunk index directly does not send packets to +/// clients and may lead to desync. +#[derive(Component, Debug)] +pub struct ChunkIndex { + map: FxHashMap, + pub(super) info: DimensionInfo, +} + +impl ChunkIndex { + pub fn new( + dimension_type: DimensionTypeId, + dimensions: &DimensionTypeRegistry, + server: &Server, + ) -> Self { + let dim = dimensions[dimension_type]; + + Self { + map: Default::default(), + info: DimensionInfo { + dimension_type, + height: dim.height, + min_y: dim.min_y, + biome_registry_len: dimensions.len() as i32, + threshold: server.compression_threshold(), + }, + } + } + + pub(super) fn info(&self) -> &DimensionInfo { + &self.info + } + + pub fn dimension_type(&self) -> DimensionTypeId { + self.info.dimension_type + } + + pub fn height(&self) -> i32 { + self.info.height + } + + pub fn min_y(&self) -> i32 { + self.info.min_y + } + + pub fn get(&self, pos: impl Into) -> Option<&LoadedChunk> { + self.map.get(&pos.into()) + } + + pub fn get_mut(&mut self, pos: impl Into) -> Option<&mut LoadedChunk> { + self.map.get_mut(&pos.into()) + } + + pub fn insert(&mut self, pos: impl Into, chunk: Chunk) -> Option { + match self.entry(pos.into()) { + Entry::Occupied(mut o) => Some(o.insert(chunk)), + Entry::Vacant(mut v) => { + v.insert(chunk); + None + } + } + } + + pub fn remove(&mut self, pos: impl Into) -> Option { + match self.entry(pos.into()) { + Entry::Occupied(o) => Some(o.remove()), + Entry::Vacant(_) => None, + } + } + + pub fn entry(&mut self, pos: impl Into) -> Entry { + match self.map.entry(pos.into()) { + std::collections::hash_map::Entry::Occupied(o) => { + Entry::Occupied(OccupiedEntry { entry: o }) + } + std::collections::hash_map::Entry::Vacant(v) => Entry::Vacant(VacantEntry { + entry: v, + height: self.info.height, + }), + } + } + + // TODO: iter, iter_mut, clear +} + +#[derive(Debug)] +pub enum Entry<'a> { + Occupied(OccupiedEntry<'a>), + Vacant(VacantEntry<'a>), +} + +impl<'a> Entry<'a> { + pub fn or_default(self) -> &'a mut LoadedChunk { + match self { + Entry::Occupied(oe) => oe.into_mut(), + Entry::Vacant(ve) => ve.insert(Chunk::new()), + } + } +} + +#[derive(Debug)] +pub struct OccupiedEntry<'a> { + entry: std::collections::hash_map::OccupiedEntry<'a, ChunkPos, LoadedChunk>, +} + +impl<'a> OccupiedEntry<'a> { + pub fn get(&self) -> &LoadedChunk { + self.entry.get() + } + + pub fn get_mut(&mut self) -> &mut LoadedChunk { + self.entry.get_mut() + } + + pub fn insert(&mut self, chunk: Chunk) -> Chunk { + self.entry.get_mut().replace(chunk) + } + + pub fn into_mut(self) -> &'a mut LoadedChunk { + self.entry.into_mut() + } + + pub fn key(&self) -> &ChunkPos { + self.entry.key() + } + + pub fn remove(self) -> Chunk { + self.remove_entry().1 + } + + pub fn remove_entry(self) -> (ChunkPos, Chunk) { + let (pos, chunk) = self.entry.remove_entry(); + + (pos, chunk.into_chunk()) + } +} + +#[derive(Debug)] +pub struct VacantEntry<'a> { + entry: std::collections::hash_map::VacantEntry<'a, ChunkPos, LoadedChunk>, + height: i32, +} + +impl<'a> VacantEntry<'a> { + pub fn insert(self, chunk: Chunk) -> &'a mut LoadedChunk { + let mut loaded = LoadedChunk::new(self.height); + loaded.replace(chunk); + + self.entry.insert(loaded) + } + + pub fn into_key(self) -> ChunkPos { + *self.entry.key() + } + + pub fn key(&self) -> &ChunkPos { + self.entry.key() + } +} diff --git a/crates/valence_server/src/dimension_layer/plugin.rs b/crates/valence_server/src/dimension_layer/plugin.rs new file mode 100644 index 000000000..f3809154d --- /dev/null +++ b/crates/valence_server/src/dimension_layer/plugin.rs @@ -0,0 +1,169 @@ +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +use bevy_ecs::query::Has; +use valence_entity::{OldPosition, Position}; +use valence_protocol::packets::play::{ChunkRenderDistanceCenterS2c, UnloadChunkS2c}; +use valence_protocol::{ChunkPos, WritePacket}; +use valence_server_common::Despawned; + +use super::ChunkIndex; +use crate::client::{Client, ClientMarker, OldView, View}; +use crate::layer::{LayerViewers, OldVisibleLayers, VisibleLayers}; +use crate::ChunkView; + +pub struct DimensionLayerPlugin; + +/// When dimension layers are updated. This is where chunk packets are sent to +/// clients and chunk viewer counts are updated as client views change. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct UpdateDimensionLayerSet; + +impl Plugin for DimensionLayerPlugin { + fn build(&self, app: &mut App) { + todo!() + } +} + +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct UpdateEntityLayers; + +#[derive(Event, Copy, Clone, PartialEq, Eq, Debug)] +pub struct ViewChunkEvent { + pub client: Entity, + pub pos: ChunkPos, +} + +#[derive(Event, Copy, Clone, PartialEq, Eq, Debug)] +pub struct UnviewChunkEvent { + pub client: Entity, + pub pos: ChunkPos, +} + +fn update_dimension_layer_views( + mut clients: Query< + ( + Entity, + &mut Client, + OldView, + View, + &OldVisibleLayers, + Ref, + ), + Changed, + >, + mut layers: Query<&mut ChunkIndex>, + mut view_events: EventWriter, + mut unview_events: EventWriter, +) { + for (client_id, mut client, old_view, view, old_visible, visible) in &mut clients { + let old_view = old_view.get(); + let view = view.get(); + + // Set render distance center before loading new chunks. Otherwise, client may + // ignore them. + if old_view.pos != view.pos { + client.write_packet(&ChunkRenderDistanceCenterS2c { + chunk_x: view.pos.x.into(), + chunk_z: view.pos.z.into(), + }); + } + + let mut changed_dimension = false; + + if visible.is_changed() { + // Send despawn packets for chunks in the old dimension layer. + for &layer in old_visible.difference(&visible) { + if let Ok(mut index) = layers.get_mut(layer) { + for pos in old_view.iter() { + if let Some(mut chunk) = index.get_mut(pos) { + client.write_packet(&UnloadChunkS2c { pos }); + chunk.viewer_count -= 1; + unview_events.send(UnviewChunkEvent { + client: client_id, + pos, + }); + } + } + + changed_dimension = true; + break; + } + } + + // Send spawn packets for chunks in the new layer. + for &layer in visible.difference(&old_visible) { + if let Ok(mut index) = layers.get(layer) { + for pos in view.iter() { + if let Some(mut chunk) = index.get_mut(pos) { + chunk.write_chunk_init_packet(&mut *client, pos, index.info()); + chunk.viewer_count += 1; + view_events.send(ViewChunkEvent { + client: client_id, + pos, + }); + } + } + + changed_dimension = true; + break; + } + } + } + + if !changed_dimension && old_view != view { + for &layer in visible.iter() { + if let Ok(mut index) = layers.get_mut(layer) { + // Unload old chunks in view. + for pos in old_view.diff(view) { + if let Some(mut chunk) = index.get_mut(pos) { + client.write_packet(&UnloadChunkS2c { pos }); + chunk.viewer_count -= 1; + unview_events.send(UnviewChunkEvent { + client: client_id, + pos, + }); + } + } + + // Load new chunks in view. + for pos in view.diff(old_view) { + if let Some(mut chunk) = index.get_mut(pos) { + chunk.write_chunk_init_packet(&mut *client, pos, index.info()); + chunk.viewer_count += 1; + view_events.send(ViewChunkEvent { + client: client_id, + pos, + }); + } + } + + break; + } + } + } + } +} + +fn update_dimension_layer_views_client_despawn( + mut clients: Query<(Entity, OldView, &OldVisibleLayers), (With, With)>, + mut chunks: Query<&mut ChunkIndex>, + mut unview_events: EventWriter, +) { + for (client_id, old_view, old_layers) in &mut clients { + for &layer in old_layers.iter() { + if let Ok(mut chunks) = chunks.get_mut(layer) { + for pos in old_view.get().iter() { + if let Some(chunk) = chunks.get_mut(pos) { + chunk.viewer_count -= 1; + unview_events.send(UnviewChunkEvent { + client: client_id, + pos, + }); + } + } + + break; + } + } + } +} diff --git a/crates/valence_server/src/entity_layer.rs b/crates/valence_server/src/entity_layer.rs new file mode 100644 index 000000000..add3a30ce --- /dev/null +++ b/crates/valence_server/src/entity_layer.rs @@ -0,0 +1,15 @@ +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; + +pub struct EntityLayerPlugin; + +impl Plugin for EntityLayerPlugin { + fn build(&self, app: &mut App) { + todo!() + } +} + +/// When entity changes are written to entity layers and clients are sent +/// spawn/despawn packets. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct UpdateEntityLayerSet; diff --git a/crates/valence_server/src/layer.rs b/crates/valence_server/src/layer.rs index 9a5d2fcbe..b6a524491 100644 --- a/crates/valence_server/src/layer.rs +++ b/crates/valence_server/src/layer.rs @@ -1,139 +1,176 @@ -//! Defines chunk layers and entity layers. Chunk layers contain the chunks and -//! dimension data of a world, while entity layers contain all the Minecraft -//! entities. -//! -//! These two together are analogous to Minecraft "levels" or "worlds". - -pub mod bvh; -pub mod chunk; -pub mod entity; +pub mod action_buf; +mod chunk_view_index; pub mod message; +mod packet_buf; + +use std::collections::BTreeSet; use bevy_app::prelude::*; use bevy_ecs::prelude::*; -pub use chunk::ChunkLayer; -pub use entity::EntityLayer; -use valence_entity::{InitEntitiesSet, UpdateTrackedDataSet}; -use valence_protocol::encode::WritePacket; -use valence_protocol::{BlockPos, ChunkPos, Ident}; -use valence_registry::{BiomeRegistry, DimensionTypeRegistry}; -use valence_server_common::Server; - +use bevy_ecs::query::Has; +pub use chunk_view_index::ChunkViewIndex; +use derive_more::{Deref, DerefMut}; +pub use packet_buf::PacketBuf; +use valence_entity::{OldPosition, Position}; +use valence_protocol::ChunkPos; +use valence_server_common::Despawned; + +use self::message::LayerMessages; +use crate::layer::message::MessageScope; +use crate::Client; + +/// Enables core functionality for layers. pub struct LayerPlugin; -/// When entity and chunk changes are written to layers. Systems that modify -/// chunks and entities should run _before_ this. Systems that need to read -/// layer messages should run _after_ this. -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct UpdateLayersPreClientSet; - -/// When layers are cleared and messages from this tick are lost. Systems that -/// read layer messages should run _before_ this. -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct UpdateLayersPostClientSet; +/// When queued messages in layers are written to the [`Client`] packet buffer +/// of all viewers. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct BroadcastLayerMessagesSet; impl Plugin for LayerPlugin { fn build(&self, app: &mut App) { - app.configure_sets( - PostUpdate, - ( - UpdateLayersPreClientSet - .after(InitEntitiesSet) - .after(UpdateTrackedDataSet), - UpdateLayersPostClientSet.after(UpdateLayersPreClientSet), - ), - ); - - chunk::build(app); - entity::build(app); + todo!() } } -/// Common functionality for layers. Notable implementors are [`ChunkLayer`] and -/// [`EntityLayer`]. -/// -/// Layers support sending packets to viewers of the layer under various -/// conditions. These are the "packet writers" exposed by this trait. +#[derive(Bundle)] +pub struct DimensionEntityLayerBundle { + // TODO +} + +/// The set of layers a client is viewing. +#[derive(Component, Clone, Default, DerefMut, Deref, Debug)] +pub struct VisibleLayers(pub BTreeSet); + +/// The contents of [`VisibleLayers`] from the previous tick. +#[derive(Component, Default, Deref, Debug)] +pub struct OldVisibleLayers(BTreeSet); + +/// The set of clients that are viewing a layer. /// -/// Layers themselves implement the [`WritePacket`] trait. Writing directly to a -/// layer will send packets to all viewers unconditionally. -pub trait Layer: WritePacket { - /// Packet writer returned by [`except_writer`](Self::except_writer). - type ExceptWriter<'a>: WritePacket - where - Self: 'a; - - /// Packet writer returned by [`view_writer`](Self::ViewWriter). - type ViewWriter<'a>: WritePacket - where - Self: 'a; - - /// Packet writer returned by - /// [`view_except_writer`](Self::ViewExceptWriter). - type ViewExceptWriter<'a>: WritePacket - where - Self: 'a; - - /// Packet writer returned by [`radius_writer`](Self::radius_writer). - type RadiusWriter<'a>: WritePacket - where - Self: 'a; - - /// Packet writer returned by - /// [`radius_except_writer`](Self::radius_except_writer). - type RadiusExceptWriter<'a>: WritePacket - where - Self: 'a; - - /// Returns a packet writer which sends packets to all viewers not - /// identified by `except`. - fn except_writer(&mut self, except: Entity) -> Self::ExceptWriter<'_>; - - /// Returns a packet writer which sends packets to viewers in view of - /// the chunk position `pos`. - fn view_writer(&mut self, pos: impl Into) -> Self::ViewWriter<'_>; - - /// Returns a packet writer which sends packets to viewers in - /// view of the chunk position `pos` and not identified by `except`. - fn view_except_writer( - &mut self, - pos: impl Into, - except: Entity, - ) -> Self::ViewExceptWriter<'_>; - - /// Returns a packet writer which sends packets to viewers within `radius` - /// blocks of the block position `pos`. - fn radius_writer(&mut self, pos: impl Into, radius: u32) -> Self::RadiusWriter<'_>; - - /// Returns a packet writer which sends packets to viewers within `radius` - /// blocks of the block position `pos` and not identified by `except`. - fn radius_except_writer( - &mut self, - pos: impl Into, - radius: u32, - except: Entity, - ) -> Self::RadiusExceptWriter<'_>; +/// This is updated automatically at the same time as [`ChunkViewIndex`]. +#[derive(Component, Clone, Default, Deref, Debug)] +pub struct LayerViewers(BTreeSet); + +fn update_view_index( + mut clients: Query<( + Entity, + Has, + &OldPosition, + Ref, + &OldVisibleLayers, + Ref, + )>, + mut layers: Query<(&mut LayerViewers, &mut ChunkViewIndex)>, +) { + for (client, is_despawned, old_pos, pos, old_visible, visible) in &clients { + if is_despawned { + // Remove from old layers. + for &layer in old_visible.iter() { + if let Ok((mut viewers, mut index)) = layers.get_mut(layer) { + let removed = viewers.remove(&client); + debug_assert!(removed); + + let removed = index.remove(old_pos.get(), client); + debug_assert!(removed); + } + } + } else if visible.is_changed() { + // Remove from old layers. + for &layer in old_visible.iter() { + if let Ok((mut viewers, mut index)) = layers.get_mut(layer) { + let removed = viewers.remove(&client); + debug_assert!(removed); + + let removed = index.remove(old_pos.get(), client); + debug_assert!(removed); + } + } + + // Insert in new layers. + for &layer in visible.iter() { + if let Ok((mut viewers, mut index)) = layers.get_mut(layer) { + let inserted = viewers.insert(client); + debug_assert!(inserted); + + let inserted = index.insert(pos.0, client); + debug_assert!(inserted); + } + } + } else if pos.is_changed() { + // Change chunk cell in layers. + + let old_pos = ChunkPos::from(old_pos.get()); + let pos = ChunkPos::from(pos.0); + + if old_pos != pos { + for &layer in visible.iter() { + if let Ok((_, mut index)) = layers.get_mut(layer) { + let removed = index.remove(old_pos, client); + debug_assert!(removed); + + let inserted = index.insert(pos, client); + debug_assert!(inserted); + } + } + } + } + } } -/// Convenience [`Bundle`] for spawning a layer entity with both [`ChunkLayer`] -/// and [`EntityLayer`] components. -#[derive(Bundle)] -pub struct LayerBundle { - pub chunk: ChunkLayer, - pub entity: EntityLayer, +fn update_old_visible_layers( + mut layers: Query<(&mut OldVisibleLayers, &VisibleLayers), Changed>, +) { + for (mut old, new) in &mut layers { + old.0.clone_from(&new.0); + } } -impl LayerBundle { - /// Returns a new layer bundle. - pub fn new( - dimension_type_name: impl Into>, - dimensions: &DimensionTypeRegistry, - biomes: &BiomeRegistry, - server: &Server, - ) -> Self { - Self { - chunk: ChunkLayer::new(dimension_type_name, dimensions, biomes, server), - entity: EntityLayer::new(server), +// fn remove_despawned_from_chunk_view_index( +// mut layers: Query<(&mut ChunkViewIndex, &mut LayerViewers)>, +// clients: Query<(Entity, &OldPosition, &OldVisibleLayers), +// (With, With)>, +// ) { for (client, pos, visible_layers) in &clients { let pos = +// ChunkPos::from(pos.get()); + +// for &layer in visible_layers.iter() { +// if let Ok((mut index, mut viewers)) = layers.get_mut(layer) { +// index.remove(pos, client); +// viewers.remove(&client); +// } +// } +// } +// } + +fn broadcast_layer_messages( + mut layers: Query<(&mut LayerMessages, &LayerViewers, &ChunkViewIndex)>, + mut clients: Query<(&mut Client, &OldPosition, &Position)>, +) { + for (mut messages, viewers, index) in &mut layers { + for (scope, kind) in messages.messages() { + let mut send = |client: Entity| { + if let Ok((client, old_pos, pos)) = clients.get_mut(client) { + match kind { + message::MessageKind::Packet { len } => todo!(), + message::MessageKind::EntityDespawn { entity } => todo!(), + } + } + }; + + match scope { + MessageScope::All => viewers.iter().copied().for_each(send), + MessageScope::Only { only } => send(only), + MessageScope::Except { except } => viewers + .iter() + .copied() + .filter(|&c| c != except) + .for_each(send), + MessageScope::ChunkView { pos } => todo!(), + MessageScope::ChunkViewExcept { pos, except } => todo!(), + MessageScope::TransitionChunkView { old_pos, pos } => todo!(), + } } + + todo!(); } } diff --git a/crates/valence_server/src/layer/action_buf.rs b/crates/valence_server/src/layer/action_buf.rs new file mode 100644 index 000000000..980b4572a --- /dev/null +++ b/crates/valence_server/src/layer/action_buf.rs @@ -0,0 +1,97 @@ +use valence_protocol::encode::PacketWriter; +use valence_protocol::{CompressionThreshold, Encode, Packet, WritePacket}; + +#[derive(Debug)] +pub(crate) struct ActionBuf { + bytes: Vec, + actions: Vec<(T, u32)>, +} + +impl ActionBuf { + pub const fn new() -> Self { + Self { + bytes: vec![], + actions: vec![], + } + } + + pub fn push(&mut self, action: T, f: impl FnOnce(&mut Vec) -> U) -> U { + let before = self.bytes.len(); + let res = f(&mut self.bytes); + let after = self.bytes.len(); + debug_assert!(before <= after); + let len = (after - before) as u32; + + if let Some((prev_action, prev_len)) = self.actions.last_mut() { + if action == *prev_action { + *prev_len += len; + return res; + } + } + + self.actions.push((action, len)); + res + } + + pub fn packet_writer( + &mut self, + action: T, + threshold: CompressionThreshold, + ) -> impl WritePacket + '_ + where + T: Clone, + { + struct Writer<'a, T> { + buf: &'a mut ActionBuf, + action: T, + threshold: CompressionThreshold, + } + + impl WritePacket for Writer<'_, T> { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + self.buf.push(self.action.clone(), |w| { + PacketWriter::new(&mut self.buf.bytes, self.threshold) + .write_packet_fallible(packet) + }) + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.buf + .push(self.action.clone(), |w| w.extend_from_slice(bytes)); + } + } + + Writer { + buf: self, + action, + threshold, + } + } + + pub fn write_packet

(&mut self, action: T, threshold: CompressionThreshold, packet: &P) + where + P: Packet + Encode, + { + self.push(action, |w| { + PacketWriter::new(&mut self.bytes, threshold).write_packet(packet) + }) + } + + pub fn actions(&self) -> impl Iterator { + let mut acc: usize = 0; + + self.actions.iter().map(move |(a, len)| { + let slice = &self.bytes[acc..acc + *len as usize]; + acc += *len as usize; + (a, slice) + }) + } + + pub fn clear(&mut self) { + self.bytes.clear(); + self.actions.clear(); + } +} diff --git a/crates/valence_server/src/layer/chunk_view_index.rs b/crates/valence_server/src/layer/chunk_view_index.rs new file mode 100644 index 000000000..3eef80971 --- /dev/null +++ b/crates/valence_server/src/layer/chunk_view_index.rs @@ -0,0 +1,60 @@ +use std::collections::hash_map::Entry; + +use bevy_ecs::prelude::*; +use rustc_hash::FxHashMap; +use valence_protocol::ChunkPos; + +/// Maps chunk positions to the set of clients in view of the chunk. +#[derive(Component, Default, Debug)] +pub struct ChunkViewIndex { + map: FxHashMap>, +} + +impl ChunkViewIndex { + pub fn get(&self, pos: impl Into) -> impl ExactSizeIterator + Clone + '_ { + self.map + .get(&pos.into()) + .map(|v| v.iter().copied()) + .unwrap_or_default() + } + + pub(super) fn insert(&mut self, pos: impl Into, client: Entity) -> bool { + match self.map.entry(pos.into()) { + Entry::Occupied(oe) => { + let v = oe.into_mut(); + + if !v.contains(&client) { + v.push(client); + true + } else { + false + } + } + Entry::Vacant(ve) => { + ve.insert(vec![client]); + true + } + } + } + + pub(super) fn remove(&mut self, pos: impl Into, client: Entity) -> bool { + match self.map.entry(pos.into()) { + Entry::Occupied(mut oe) => { + let v = oe.get_mut(); + + if let Some(idx) = v.iter().copied().position(|e| e == client) { + v.swap_remove(idx); + + if v.is_empty() { + oe.remove(); + } + + true + } else { + false + } + } + Entry::Vacant(_) => false, + } + } +} diff --git a/crates/valence_server/src/layer/message.rs b/crates/valence_server/src/layer/message.rs index c52269d1d..eb4f585c9 100644 --- a/crates/valence_server/src/layer/message.rs +++ b/crates/valence_server/src/layer/message.rs @@ -1,311 +1,165 @@ -use core::fmt; -use std::convert::Infallible; -use std::ops::Range; - -use valence_protocol::ChunkPos; - -use crate::layer::bvh::{ChunkBvh, GetChunkPos}; -use crate::ChunkView; - -/// A message buffer of global messages (`G`) and local messages (`L`) meant for -/// consumption by clients. Local messages are those that have some spatial -/// component to them and implement the [`GetChunkPos`] trait. Local messages -/// are placed in a bounding volume hierarchy for fast queries via -/// [`Self::query_local`]. Global messages do not necessarily have a spatial -/// component and all globals will be visited when using [`Self::iter_global`]. -/// -/// Every message is associated with an arbitrary span of bytes. The meaning of -/// the bytes is whatever the message needs it to be. -/// -/// At the end of the tick and before clients have access to the buffer, all -/// messages are sorted and then deduplicated by concatenating byte spans -/// together. This is done for a couple of reasons: -/// - Messages may rely on sorted message order for correctness, like in the -/// case of entity spawn & despawn messages. Sorting also makes deduplication -/// easy. -/// - Deduplication reduces the total number of messages that all clients must -/// examine. Consider the case of a message such as "send all clients in view -/// of this chunk position these packet bytes". If two of these messages have -/// the same chunk position, then they can just be combined together. -pub struct Messages { - global: Vec<(G, Range)>, - local: Vec<(L, Range)>, - bvh: ChunkBvh>, - staging: Vec, - ready: Vec, - is_ready: bool, +use bevy_ecs::prelude::*; +use valence_entity::{OldPosition, Position}; +use valence_protocol::encode::PacketWriter; +use valence_protocol::{ChunkPos, CompressionThreshold, Encode, Packet, WritePacket}; + +use super::ChunkViewIndex; +use crate::Client; + +#[derive(Component, Debug)] +pub struct LayerMessages { + bytes: Vec, + messages: Vec, + threshold: CompressionThreshold, } -impl Messages -where - G: Clone + Ord, - L: Clone + Ord + GetChunkPos, -{ - pub(crate) fn new() -> Self { - Self::default() - } - - /// Adds a global message to this message buffer. - pub(crate) fn send_global( - &mut self, - msg: G, - f: impl FnOnce(&mut Vec) -> Result<(), E>, - ) -> Result<(), E> { - debug_assert!(!self.is_ready); +pub(super) type Message = (MessageScope, MessageKind); + +#[derive(Debug, Copy, Clone, PartialEq, Default)] +#[non_exhaustive] +pub enum MessageScope { + /// All clients viewing the layer will receive the message. + #[default] + All, + /// Only the client identified by `only` will receive the message. + Only { + only: Entity, + }, + /// All clients viewing the layer will receive the message, except the + /// client identified by `except`. + Except { + except: Entity, + }, + /// All clients in view of the chunk position `pos` will receive the + /// message. + ChunkView { + pos: ChunkPos, + }, + ChunkViewExcept { + pos: ChunkPos, + except: Entity, + }, + TransitionChunkView { + old_pos: ChunkPos, + pos: ChunkPos, + }, +} - let start = self.staging.len(); - f(&mut self.staging)?; - let end = self.staging.len(); +#[derive(Debug, Copy, Clone)] +pub(super) enum MessageKind { + Packet { len: usize }, + EntityDespawn { entity: Entity }, +} - if let Some((m, range)) = self.global.last_mut() { - if msg == *m { - // Extend the existing message. - range.end = end as u32; - return Ok(()); - } +impl LayerMessages { + pub fn new(threshold: CompressionThreshold) -> Self { + Self { + bytes: vec![], + messages: vec![], + threshold, } - - self.global.push((msg, start as u32..end as u32)); - - Ok(()) } - /// Adds a local message to this message buffer. - pub(crate) fn send_local( - &mut self, - msg: L, - f: impl FnOnce(&mut Vec) -> Result<(), E>, - ) -> Result<(), E> { - debug_assert!(!self.is_ready); - - let start = self.staging.len(); - f(&mut self.staging)?; - let end = self.staging.len(); - - if let Some((m, range)) = self.local.last_mut() { - if msg == *m { - // Extend the existing message. - range.end = end as u32; - return Ok(()); - } - } - - self.local.push((msg, start as u32..end as u32)); - - Ok(()) + pub fn send_packet

(&mut self, scope: MessageScope, packet: &P) + where + P: Packet + Encode, + { + self.packet_writer(scope).write_packet(packet) } - /// Like [`Self::send_global`] but writing bytes cannot fail. - pub(crate) fn send_global_infallible(&mut self, msg: G, f: impl FnOnce(&mut Vec)) { - let _ = self.send_global::(msg, |b| { - f(b); - Ok(()) - }); - } - - /// Like [`Self::send_local`] but writing bytes cannot fail. - pub(crate) fn send_local_infallible(&mut self, msg: L, f: impl FnOnce(&mut Vec)) { - let _ = self.send_local::(msg, |b| { - f(b); - Ok(()) - }); - } - - /// Readies messages to be read by clients. - pub(crate) fn ready(&mut self) { - debug_assert!(!self.is_ready); - self.is_ready = true; - - debug_assert!(self.ready.is_empty()); + pub fn packet_writer(&mut self, scope: MessageScope) -> impl WritePacket + '_ { + struct Writer<'a> { + messages: &'a mut LayerMessages, + scope: MessageScope, + } - self.ready.reserve_exact(self.staging.len()); + impl WritePacket for Writer<'_> { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + let start = self.messages.bytes.len(); + let mut writer = + PacketWriter::new(&mut self.messages.bytes, self.messages.threshold); + + // Check if we're able to extend the last message instead of pushing a new one. + if let Some((last_scope, last_kind)) = self.messages.messages.last_mut() { + if let MessageKind::Packet { len } = last_kind { + if *last_scope == self.scope { + writer.write_packet_fallible(packet)?; + let end = writer.buf.len(); + + *len += end - start; + + return Ok(()); + } + } + } - fn sort_and_merge( - msgs: &mut Vec<(M, Range)>, - staging: &[u8], - ready: &mut Vec, - ) { - // Sort must be stable. - msgs.sort_by_key(|(msg, _)| msg.clone()); + writer.write_packet_fallible(packet); + let end = writer.buf.len(); - // Make sure the first element is already copied to "ready". - if let Some((_, range)) = msgs.first_mut() { - let start = ready.len(); - ready.extend_from_slice(&staging[range.start as usize..range.end as usize]); - let end = ready.len(); + self.messages + .messages + .push((self.scope, MessageKind::Packet { len: end - start })); - *range = start as u32..end as u32; + Ok(()) } - msgs.dedup_by(|(right_msg, right_range), (left_msg, left_range)| { - if *left_msg == *right_msg { - // Extend the left element with the right element. Then delete the right - // element. - - let right_bytes = - &staging[right_range.start as usize..right_range.end as usize]; + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.messages.bytes.extend_from_slice(bytes); - ready.extend_from_slice(right_bytes); + if let Some((last_scope, last_kind)) = self.messages.messages.last_mut() { + if let MessageKind::Packet { len } = last_kind { + if *last_scope == self.scope { + *len += bytes.len(); - left_range.end += right_bytes.len() as u32; - - true - } else { - // Copy right element to "ready". - - let right_bytes = - &staging[right_range.start as usize..right_range.end as usize]; - - let start = ready.len(); - ready.extend_from_slice(right_bytes); - let end = ready.len(); - - *right_range = start as u32..end as u32; - - false + return; + } + } } - }); - } - sort_and_merge(&mut self.global, &self.staging, &mut self.ready); - sort_and_merge(&mut self.local, &self.staging, &mut self.ready); - - self.bvh.build( - self.local - .iter() - .cloned() - .map(|(msg, range)| MessagePair { msg, range }), - ); - } - - pub(crate) fn unready(&mut self) { - assert!(self.is_ready); - self.is_ready = false; - - self.local.clear(); - self.global.clear(); - self.staging.clear(); - self.ready.clear(); - } - - pub(crate) fn shrink_to_fit(&mut self) { - self.global.shrink_to_fit(); - self.local.shrink_to_fit(); - self.bvh.shrink_to_fit(); - self.staging.shrink_to_fit(); - self.ready.shrink_to_fit(); - } - - /// All message bytes. Use this in conjunction with [`Self::iter_global`] - /// and [`Self::query_local`]. - pub fn bytes(&self) -> &[u8] { - debug_assert!(self.is_ready); - - &self.ready - } - - /// Returns an iterator over all global messages and their span of bytes in - /// [`Self::bytes`]. - pub fn iter_global(&self) -> impl Iterator)> + '_ { - debug_assert!(self.is_ready); + self.messages + .messages + .push((self.scope, MessageKind::Packet { len: bytes.len() })); + } + } - self.global - .iter() - .map(|(m, r)| (m.clone(), r.start as usize..r.end as usize)) + Writer { + messages: self, + scope, + } } - /// Takes a visitor function `f` and visits all local messages contained - /// within the chunk view `view`. `f` is called with the local - /// message and its span of bytes in [`Self::bytes`]. - pub fn query_local(&self, view: ChunkView, mut f: impl FnMut(L, Range)) { - debug_assert!(self.is_ready); - - self.bvh.query(view, |pair| { - f( - pair.msg.clone(), - pair.range.start as usize..pair.range.end as usize, - ) - }); + pub fn clear(&mut self) { + self.messages.clear(); + self.bytes.clear(); } -} -impl Default for Messages { - fn default() -> Self { - Self { - global: Default::default(), - local: Default::default(), - bvh: Default::default(), - staging: Default::default(), - ready: Default::default(), - is_ready: Default::default(), - } + pub fn threshold(&self) -> CompressionThreshold { + self.threshold } -} -impl fmt::Debug for Messages -where - G: fmt::Debug, - L: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Messages") - .field("global", &self.global) - .field("local", &self.local) - .field("is_ready", &self.is_ready) - .finish() + pub(super) fn bytes(&self) -> &[u8] { + &self.bytes } -} - -#[derive(Debug)] -struct MessagePair { - msg: M, - range: Range, -} -impl GetChunkPos for MessagePair { - fn chunk_pos(&self) -> ChunkPos { - self.msg.chunk_pos() + pub(super) fn messages(&self) -> impl Iterator + '_ { + self.messages.iter().copied() } } -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] - struct DummyLocal; - - impl GetChunkPos for DummyLocal { - fn chunk_pos(&self) -> ChunkPos { - unimplemented!() - } +impl WritePacket for LayerMessages { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + self.packet_writer(MessageScope::All) + .write_packet_fallible(packet) } - #[test] - fn send_global_message() { - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] - enum TestMsg { - Foo, - Bar, - } - - let mut messages = Messages::::new(); - - messages.send_global_infallible(TestMsg::Foo, |b| b.extend_from_slice(&[1, 2, 3])); - messages.send_global_infallible(TestMsg::Bar, |b| b.extend_from_slice(&[4, 5, 6])); - messages.send_global_infallible(TestMsg::Foo, |b| b.extend_from_slice(&[7, 8, 9])); - - messages.ready(); - - let bytes = messages.bytes(); - - for (msg, range) in messages.iter_global() { - match msg { - TestMsg::Foo => assert_eq!(&bytes[range.clone()], &[1, 2, 3, 7, 8, 9]), - TestMsg::Bar => assert_eq!(&bytes[range.clone()], &[4, 5, 6]), - } - } - - messages.unready(); + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.packet_writer(MessageScope::All) + .write_packet_bytes(bytes) } } diff --git a/crates/valence_server/src/layer/packet_buf.rs b/crates/valence_server/src/layer/packet_buf.rs new file mode 100644 index 000000000..1ec88285d --- /dev/null +++ b/crates/valence_server/src/layer/packet_buf.rs @@ -0,0 +1,60 @@ +use bevy_ecs::prelude::*; +use derive_more::{Deref, DerefMut}; +use valence_protocol::encode::PacketWriter; +use valence_protocol::{CompressionThreshold, Encode, Packet, WritePacket}; +use valence_server_common::Server; + +use crate::Client; + +#[derive(Component, Clone, Deref, DerefMut, Eq, Debug)] +pub struct PacketBuf { + #[deref] + #[deref_mut] + buf: Vec, + threshold: CompressionThreshold, +} + +impl PartialEq for PacketBuf { + fn eq(&self, other: &Self) -> bool { + self.buf.eq(&other.buf) + } +} + +impl WritePacket for PacketBuf { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + PacketWriter::new(&mut self.buf, self.threshold).write_packet_fallible(packet) + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.buf.extend_from_slice(bytes) + } +} + +impl PacketBuf { + /// Creates a new, empty packet buffer. + pub fn new(server: &Server) { + Self { + buf: vec![], + threshold: server.compression_threshold(), + } + } + + /// Sends all packet data in this buffer to all clients given by the + /// iterator. The buffer is then cleared. + pub fn broadcast(&mut self, clients: I) + where + I: IntoIterator, + C: DerefMut, + { + if !self.is_empty() { + for mut client in clients { + client.write_packet_bytes(&self); + } + + self.clear(); + } + } +} diff --git a/crates/valence_server/src/layer_old.rs b/crates/valence_server/src/layer_old.rs new file mode 100644 index 000000000..9a5d2fcbe --- /dev/null +++ b/crates/valence_server/src/layer_old.rs @@ -0,0 +1,139 @@ +//! Defines chunk layers and entity layers. Chunk layers contain the chunks and +//! dimension data of a world, while entity layers contain all the Minecraft +//! entities. +//! +//! These two together are analogous to Minecraft "levels" or "worlds". + +pub mod bvh; +pub mod chunk; +pub mod entity; +pub mod message; + +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +pub use chunk::ChunkLayer; +pub use entity::EntityLayer; +use valence_entity::{InitEntitiesSet, UpdateTrackedDataSet}; +use valence_protocol::encode::WritePacket; +use valence_protocol::{BlockPos, ChunkPos, Ident}; +use valence_registry::{BiomeRegistry, DimensionTypeRegistry}; +use valence_server_common::Server; + +pub struct LayerPlugin; + +/// When entity and chunk changes are written to layers. Systems that modify +/// chunks and entities should run _before_ this. Systems that need to read +/// layer messages should run _after_ this. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct UpdateLayersPreClientSet; + +/// When layers are cleared and messages from this tick are lost. Systems that +/// read layer messages should run _before_ this. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct UpdateLayersPostClientSet; + +impl Plugin for LayerPlugin { + fn build(&self, app: &mut App) { + app.configure_sets( + PostUpdate, + ( + UpdateLayersPreClientSet + .after(InitEntitiesSet) + .after(UpdateTrackedDataSet), + UpdateLayersPostClientSet.after(UpdateLayersPreClientSet), + ), + ); + + chunk::build(app); + entity::build(app); + } +} + +/// Common functionality for layers. Notable implementors are [`ChunkLayer`] and +/// [`EntityLayer`]. +/// +/// Layers support sending packets to viewers of the layer under various +/// conditions. These are the "packet writers" exposed by this trait. +/// +/// Layers themselves implement the [`WritePacket`] trait. Writing directly to a +/// layer will send packets to all viewers unconditionally. +pub trait Layer: WritePacket { + /// Packet writer returned by [`except_writer`](Self::except_writer). + type ExceptWriter<'a>: WritePacket + where + Self: 'a; + + /// Packet writer returned by [`view_writer`](Self::ViewWriter). + type ViewWriter<'a>: WritePacket + where + Self: 'a; + + /// Packet writer returned by + /// [`view_except_writer`](Self::ViewExceptWriter). + type ViewExceptWriter<'a>: WritePacket + where + Self: 'a; + + /// Packet writer returned by [`radius_writer`](Self::radius_writer). + type RadiusWriter<'a>: WritePacket + where + Self: 'a; + + /// Packet writer returned by + /// [`radius_except_writer`](Self::radius_except_writer). + type RadiusExceptWriter<'a>: WritePacket + where + Self: 'a; + + /// Returns a packet writer which sends packets to all viewers not + /// identified by `except`. + fn except_writer(&mut self, except: Entity) -> Self::ExceptWriter<'_>; + + /// Returns a packet writer which sends packets to viewers in view of + /// the chunk position `pos`. + fn view_writer(&mut self, pos: impl Into) -> Self::ViewWriter<'_>; + + /// Returns a packet writer which sends packets to viewers in + /// view of the chunk position `pos` and not identified by `except`. + fn view_except_writer( + &mut self, + pos: impl Into, + except: Entity, + ) -> Self::ViewExceptWriter<'_>; + + /// Returns a packet writer which sends packets to viewers within `radius` + /// blocks of the block position `pos`. + fn radius_writer(&mut self, pos: impl Into, radius: u32) -> Self::RadiusWriter<'_>; + + /// Returns a packet writer which sends packets to viewers within `radius` + /// blocks of the block position `pos` and not identified by `except`. + fn radius_except_writer( + &mut self, + pos: impl Into, + radius: u32, + except: Entity, + ) -> Self::RadiusExceptWriter<'_>; +} + +/// Convenience [`Bundle`] for spawning a layer entity with both [`ChunkLayer`] +/// and [`EntityLayer`] components. +#[derive(Bundle)] +pub struct LayerBundle { + pub chunk: ChunkLayer, + pub entity: EntityLayer, +} + +impl LayerBundle { + /// Returns a new layer bundle. + pub fn new( + dimension_type_name: impl Into>, + dimensions: &DimensionTypeRegistry, + biomes: &BiomeRegistry, + server: &Server, + ) -> Self { + Self { + chunk: ChunkLayer::new(dimension_type_name, dimensions, biomes, server), + entity: EntityLayer::new(server), + } + } +} diff --git a/crates/valence_server/src/layer/bvh.rs b/crates/valence_server/src/layer_old/bvh.rs similarity index 100% rename from crates/valence_server/src/layer/bvh.rs rename to crates/valence_server/src/layer_old/bvh.rs diff --git a/crates/valence_server/src/layer_old/chunk.rs b/crates/valence_server/src/layer_old/chunk.rs new file mode 100644 index 000000000..1c183ba24 --- /dev/null +++ b/crates/valence_server/src/layer_old/chunk.rs @@ -0,0 +1,726 @@ +pub mod batch; +mod biome; +mod block_update; +#[allow(clippy::module_inception)] +mod chunk; +mod loaded; +mod paletted_container; +mod unloaded; + +use std::borrow::Cow; +use std::collections::hash_map::{Entry, OccupiedEntry, VacantEntry}; + +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +pub use biome::*; +pub use chunk::*; +pub use loaded::LoadedChunk; +use rustc_hash::FxHashMap; +pub use unloaded::Chunk; +use valence_math::{DVec3, Vec3}; +use valence_nbt::Compound; +use valence_protocol::encode::{PacketWriter, WritePacket}; +use valence_protocol::packets::play::particle_s2c::Particle; +use valence_protocol::packets::play::{ParticleS2c, PlaySoundS2c}; +use valence_protocol::sound::{Sound, SoundCategory, SoundId}; +use valence_protocol::{ + BlockPos, BlockState, ChunkPos, CompressionThreshold, Encode, Ident, Packet, +}; +use valence_registry::biome::BiomeRegistry; +use valence_registry::DimensionTypeRegistry; +use valence_server_common::Server; + +use self::block_update::BlockUpdates; +use super::bvh::GetChunkPos; +use super::message::Messages; +use super::{Layer, UpdateLayersPostClientSet, UpdateLayersPreClientSet}; + +/// A [`Component`] containing the [chunks](LoadedChunk) and [dimension +/// information](valence_registry::dimension_type::DimensionTypeId) of a +/// Minecraft world. +#[derive(Component, Debug)] +pub struct ChunkLayer { + messages: ChunkLayerMessages, + chunks: FxHashMap, + info: ChunkLayerInfo, + block_updates: BlockUpdates, +} + +/// Chunk layer information. +#[derive(Debug)] +pub(crate) struct ChunkLayerInfo { + dimension_type_name: Ident, + height: u32, + min_y: i32, + biome_registry_len: usize, + threshold: CompressionThreshold, +} + +type ChunkLayerMessages = Messages; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub(crate) enum GlobalMsg { + /// Send packet data to all clients viewing the layer. + Packet, + /// Send packet data to all clients viewing the layer, except the client + /// identified by `except`. + PacketExcept { except: Entity }, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub(crate) enum LocalMsg { + /// Send packet data to all clients viewing the layer in view of `pos`. + PacketAt { + pos: ChunkPos, + }, + PacketAtExcept { + pos: ChunkPos, + except: Entity, + }, + RadiusAt { + center: BlockPos, + radius_squared: u32, + }, + RadiusAtExcept { + center: BlockPos, + radius_squared: u32, + except: Entity, + }, + /// Instruct clients to load or unload the chunk at `pos`. Loading and + /// unloading are combined into a single message so that load/unload order + /// is not lost when messages are sorted. + /// + /// Message content is a single byte indicating load (1) or unload (0). + ChangeChunkState { + pos: ChunkPos, + }, + /// Message content is the data for a single biome in the "change biomes" + /// packet. + ChangeBiome { + pos: ChunkPos, + }, +} + +impl GetChunkPos for LocalMsg { + fn chunk_pos(&self) -> ChunkPos { + match *self { + LocalMsg::PacketAt { pos } => pos, + LocalMsg::PacketAtExcept { pos, .. } => pos, + LocalMsg::RadiusAt { center, .. } => center.into(), + LocalMsg::RadiusAtExcept { center, .. } => center.into(), + LocalMsg::ChangeBiome { pos } => pos, + LocalMsg::ChangeChunkState { pos } => pos, + } + } +} + +impl ChunkLayer { + pub(crate) const LOAD: u8 = 0; + pub(crate) const UNLOAD: u8 = 1; + pub(crate) const OVERWRITE: u8 = 2; + + /// Creates a new chunk layer. + #[track_caller] + pub fn new( + dimension_type_name: impl Into>, + dimensions: &DimensionTypeRegistry, + biomes: &BiomeRegistry, + server: &Server, + ) -> Self { + let dimension_type_name = dimension_type_name.into(); + + let dim = &dimensions[dimension_type_name.as_str_ident()]; + + assert!( + (0..MAX_HEIGHT as i32).contains(&dim.height), + "invalid dimension height of {}", + dim.height + ); + + Self { + messages: Messages::new(), + chunks: Default::default(), + info: ChunkLayerInfo { + dimension_type_name, + height: dim.height as u32, + min_y: dim.min_y, + biome_registry_len: biomes.iter().len(), + threshold: server.compression_threshold(), + }, + } + } + + /// The name of the dimension this chunk layer is using. + pub fn dimension_type_name(&self) -> Ident<&str> { + self.info.dimension_type_name.as_str_ident() + } + + /// The height of this instance's dimension. + pub fn height(&self) -> u32 { + self.info.height + } + + /// The `min_y` of this instance's dimension. + pub fn min_y(&self) -> i32 { + self.info.min_y + } + + /// Get a reference to the chunk at the given position, if it is loaded. + pub fn chunk(&self, pos: impl Into) -> Option<&LoadedChunk> { + self.chunks.get(&pos.into()) + } + + /// Get a mutable reference to the chunk at the given position, if it is + /// loaded. + pub fn chunk_mut(&mut self, pos: impl Into) -> Option<&mut LoadedChunk> { + self.chunks.get_mut(&pos.into()) + } + + /// Insert a chunk into the instance at the given position. The previous + /// chunk data is returned. + pub fn insert_chunk(&mut self, pos: impl Into, chunk: Chunk) -> Option { + match self.chunk_entry(pos) { + ChunkEntry::Occupied(mut oe) => Some(oe.insert(chunk)), + ChunkEntry::Vacant(ve) => { + ve.insert(chunk); + None + } + } + } + + /// Unload the chunk at the given position, if it is loaded. Returns the + /// chunk if it was loaded. + pub fn remove_chunk(&mut self, pos: impl Into) -> Option { + match self.chunk_entry(pos) { + ChunkEntry::Occupied(oe) => Some(oe.remove()), + ChunkEntry::Vacant(_) => None, + } + } + + /// Unload all chunks in this instance. + pub fn clear_chunks(&mut self) { + self.retain_chunks(|_, _| false) + } + + /// Retain only the chunks for which the given predicate returns `true`. + pub fn retain_chunks(&mut self, mut f: F) + where + F: FnMut(ChunkPos, &mut LoadedChunk) -> bool, + { + self.chunks.retain(|pos, chunk| { + if !f(*pos, chunk) { + self.messages + .send_local_infallible(LocalMsg::ChangeChunkState { pos: *pos }, |b| { + b.push(Self::UNLOAD) + }); + + false + } else { + true + } + }); + } + + /// Get a [`ChunkEntry`] for the given position. + pub fn chunk_entry(&mut self, pos: impl Into) -> ChunkEntry { + match self.chunks.entry(pos.into()) { + Entry::Occupied(oe) => ChunkEntry::Occupied(OccupiedChunkEntry { + messages: &mut self.messages, + entry: oe, + }), + Entry::Vacant(ve) => ChunkEntry::Vacant(VacantChunkEntry { + height: self.info.height, + messages: &mut self.messages, + entry: ve, + }), + } + } + + /// Get an iterator over all loaded chunks in the instance. The order of the + /// chunks is undefined. + pub fn chunks(&self) -> impl Iterator + Clone + '_ { + self.chunks.iter().map(|(pos, chunk)| (*pos, chunk)) + } + + /// Get an iterator over all loaded chunks in the instance, mutably. The + /// order of the chunks is undefined. + pub fn chunks_mut(&mut self) -> impl Iterator + '_ { + self.chunks.iter_mut().map(|(pos, chunk)| (*pos, chunk)) + } + + /// Optimizes the memory usage of the instance. + pub fn shrink_to_fit(&mut self) { + for (_, chunk) in self.chunks_mut() { + chunk.shrink_to_fit(); + } + + self.chunks.shrink_to_fit(); + self.messages.shrink_to_fit(); + self.block_updates.shrink_to_fit(); + } + + pub(crate) fn info(&self) -> &ChunkLayerInfo { + &self.info + } + + pub(crate) fn messages(&self) -> &ChunkLayerMessages { + &self.messages + } + + // TODO: move to `valence_particle`. + /// Puts a particle effect at the given position in the world. The particle + /// effect is visible to all players in the instance with the + /// appropriate chunk in view. + pub fn play_particle( + &mut self, + particle: &Particle, + long_distance: bool, + position: impl Into, + offset: impl Into, + max_speed: f32, + count: i32, + ) { + let position = position.into(); + + self.view_writer(position).write_packet(&ParticleS2c { + particle: Cow::Borrowed(particle), + long_distance, + position, + offset: offset.into(), + max_speed, + count, + }); + } + + // TODO: move to `valence_sound`. + /// Plays a sound effect at the given position in the world. The sound + /// effect is audible to all players in the instance with the + /// appropriate chunk in view. + pub fn play_sound( + &mut self, + sound: Sound, + category: SoundCategory, + position: impl Into, + volume: f32, + pitch: f32, + ) { + let position = position.into(); + + self.view_writer(position).write_packet(&PlaySoundS2c { + id: SoundId::Direct { + id: sound.to_ident().into(), + range: None, + }, + category, + position: (position * 8.0).as_ivec3(), + volume, + pitch, + seed: rand::random(), + }); + } +} + +impl Layer for ChunkLayer { + type ExceptWriter<'a> = ExceptWriter<'a>; + + type ViewWriter<'a> = ViewWriter<'a>; + + type ViewExceptWriter<'a> = ViewExceptWriter<'a>; + + type RadiusWriter<'a> = RadiusWriter<'a>; + + type RadiusExceptWriter<'a> = RadiusExceptWriter<'a>; + + fn except_writer(&mut self, except: Entity) -> Self::ExceptWriter<'_> { + ExceptWriter { + layer: self, + except, + } + } + + fn view_writer(&mut self, pos: impl Into) -> Self::ViewWriter<'_> { + ViewWriter { + layer: self, + pos: pos.into(), + } + } + + fn view_except_writer( + &mut self, + pos: impl Into, + except: Entity, + ) -> Self::ViewExceptWriter<'_> { + ViewExceptWriter { + layer: self, + pos: pos.into(), + except, + } + } + + fn radius_writer( + &mut self, + center: impl Into, + radius: u32, + ) -> Self::RadiusWriter<'_> { + RadiusWriter { + layer: self, + center: center.into(), + radius, + } + } + + fn radius_except_writer( + &mut self, + center: impl Into, + radius: u32, + except: Entity, + ) -> Self::RadiusExceptWriter<'_> { + RadiusExceptWriter { + layer: self, + center: center.into(), + radius, + except, + } + } +} + +impl WritePacket for ChunkLayer { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + self.messages.send_global(GlobalMsg::Packet, |b| { + PacketWriter::new(b, self.info.threshold).write_packet_fallible(packet) + }) + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.messages + .send_global_infallible(GlobalMsg::Packet, |b| b.extend_from_slice(bytes)); + } +} + +pub struct ExceptWriter<'a> { + layer: &'a mut ChunkLayer, + except: Entity, +} + +impl WritePacket for ExceptWriter<'_> { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + self.layer.messages.send_global( + GlobalMsg::PacketExcept { + except: self.except, + }, + |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), + ) + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.layer.messages.send_global_infallible( + GlobalMsg::PacketExcept { + except: self.except, + }, + |b| b.extend_from_slice(bytes), + ) + } +} + +pub struct ViewWriter<'a> { + layer: &'a mut ChunkLayer, + pos: ChunkPos, +} + +impl WritePacket for ViewWriter<'_> { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + self.layer + .messages + .send_local(LocalMsg::PacketAt { pos: self.pos }, |b| { + PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet) + }) + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.layer + .messages + .send_local_infallible(LocalMsg::PacketAt { pos: self.pos }, |b| { + b.extend_from_slice(bytes) + }); + } +} + +pub struct ViewExceptWriter<'a> { + layer: &'a mut ChunkLayer, + pos: ChunkPos, + except: Entity, +} + +impl WritePacket for ViewExceptWriter<'_> { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + self.layer.messages.send_local( + LocalMsg::PacketAtExcept { + pos: self.pos, + except: self.except, + }, + |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), + ) + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.layer.messages.send_local_infallible( + LocalMsg::PacketAtExcept { + pos: self.pos, + except: self.except, + }, + |b| b.extend_from_slice(bytes), + ); + } +} + +pub struct RadiusWriter<'a> { + layer: &'a mut ChunkLayer, + center: BlockPos, + radius: u32, +} + +impl WritePacket for RadiusWriter<'_> { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + self.layer.messages.send_local( + LocalMsg::RadiusAt { + center: self.center, + radius_squared: self.radius, + }, + |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), + ) + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.layer.messages.send_local_infallible( + LocalMsg::RadiusAt { + center: self.center, + radius_squared: self.radius, + }, + |b| b.extend_from_slice(bytes), + ); + } +} + +pub struct RadiusExceptWriter<'a> { + layer: &'a mut ChunkLayer, + center: BlockPos, + radius: u32, + except: Entity, +} + +impl WritePacket for RadiusExceptWriter<'_> { + fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> + where + P: Packet + Encode, + { + self.layer.messages.send_local( + LocalMsg::RadiusAtExcept { + center: self.center, + radius_squared: self.radius, + except: self.except, + }, + |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), + ) + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.layer.messages.send_local_infallible( + LocalMsg::RadiusAtExcept { + center: self.center, + radius_squared: self.radius, + except: self.except, + }, + |b| b.extend_from_slice(bytes), + ); + } +} + +#[derive(Debug)] +pub enum ChunkEntry<'a> { + Occupied(OccupiedChunkEntry<'a>), + Vacant(VacantChunkEntry<'a>), +} + +impl<'a> ChunkEntry<'a> { + pub fn or_default(self) -> &'a mut LoadedChunk { + match self { + ChunkEntry::Occupied(oe) => oe.into_mut(), + ChunkEntry::Vacant(ve) => ve.insert(Chunk::new()), + } + } +} + +#[derive(Debug)] +pub struct OccupiedChunkEntry<'a> { + messages: &'a mut ChunkLayerMessages, + entry: OccupiedEntry<'a, ChunkPos, LoadedChunk>, +} + +impl<'a> OccupiedChunkEntry<'a> { + pub fn get(&self) -> &LoadedChunk { + self.entry.get() + } + + pub fn get_mut(&mut self) -> &mut LoadedChunk { + self.entry.get_mut() + } + + pub fn insert(&mut self, chunk: Chunk) -> Chunk { + self.messages.send_local_infallible( + LocalMsg::ChangeChunkState { + pos: *self.entry.key(), + }, + |b| b.push(ChunkLayer::OVERWRITE), + ); + + self.entry.get_mut().replace(chunk) + } + + pub fn into_mut(self) -> &'a mut LoadedChunk { + self.entry.into_mut() + } + + pub fn key(&self) -> &ChunkPos { + self.entry.key() + } + + pub fn remove(self) -> Chunk { + self.remove_entry().1 + } + + pub fn remove_entry(self) -> (ChunkPos, Chunk) { + let (pos, chunk) = self.entry.remove_entry(); + + self.messages + .send_local_infallible(LocalMsg::ChangeChunkState { pos }, |b| { + b.push(ChunkLayer::UNLOAD) + }); + + (pos, chunk.into_chunk()) + } +} + +#[derive(Debug)] +pub struct VacantChunkEntry<'a> { + height: u32, + messages: &'a mut ChunkLayerMessages, + entry: VacantEntry<'a, ChunkPos, LoadedChunk>, +} + +impl<'a> VacantChunkEntry<'a> { + pub fn insert(self, chunk: Chunk) -> &'a mut LoadedChunk { + let mut loaded = LoadedChunk::new(self.height); + loaded.replace(chunk); + + self.messages.send_local_infallible( + LocalMsg::ChangeChunkState { + pos: *self.entry.key(), + }, + |b| b.push(ChunkLayer::LOAD), + ); + + self.entry.insert(loaded) + } + + pub fn into_key(self) -> ChunkPos { + *self.entry.key() + } + + pub fn key(&self) -> &ChunkPos { + self.entry.key() + } +} + +/// Represents a complete block, which is a pair of block state and optional NBT +/// data for the block entity. +#[derive(Clone, PartialEq, Default, Debug)] +pub struct Block { + pub state: BlockState, + pub nbt: Option, +} + +impl Block { + pub const fn new(state: BlockState, nbt: Option) -> Self { + Self { state, nbt } + } +} + +impl From for Block { + fn from(state: BlockState) -> Self { + Self { state, nbt: None } + } +} + +/// Like [`Block`] but immutably referenced. +#[derive(Copy, Clone, PartialEq, Default, Debug)] +pub struct BlockRef<'a> { + pub state: BlockState, + pub nbt: Option<&'a Compound>, +} + +impl<'a> BlockRef<'a> { + pub const fn new(state: BlockState, nbt: Option<&'a Compound>) -> Self { + Self { state, nbt } + } +} + +impl From> for Block { + fn from(value: BlockRef<'_>) -> Self { + Self { + state: value.state, + nbt: value.nbt.cloned(), + } + } +} + +impl From for BlockRef<'_> { + fn from(state: BlockState) -> Self { + Self { state, nbt: None } + } +} + +impl<'a> From<&'a Block> for BlockRef<'a> { + fn from(value: &'a Block) -> Self { + Self { + state: value.state, + nbt: value.nbt.as_ref(), + } + } +} + +pub(super) fn build(app: &mut App) { + app.add_systems( + PostUpdate, + ( + update_chunk_layers_pre_client.in_set(UpdateLayersPreClientSet), + update_chunk_layers_post_client.in_set(UpdateLayersPostClientSet), + ), + ); +} + +fn update_chunk_layers_pre_client(mut layers: Query<&mut ChunkLayer>) { + for mut layer in &mut layers { + layer.messages.ready(); + } +} + +fn update_chunk_layers_post_client(mut layers: Query<&mut ChunkLayer>) { + for mut layer in &mut layers { + layer.messages.unready(); + } +} diff --git a/crates/valence_server/src/layer/chunk/batch.rs b/crates/valence_server/src/layer_old/chunk/batch.rs similarity index 93% rename from crates/valence_server/src/layer/chunk/batch.rs rename to crates/valence_server/src/layer_old/chunk/batch.rs index 93c83d3d6..b9ed754e2 100644 --- a/crates/valence_server/src/layer/chunk/batch.rs +++ b/crates/valence_server/src/layer_old/chunk/batch.rs @@ -4,14 +4,13 @@ mod basic; use std::borrow::Cow; -pub use basic::BasicBatch; use valence_protocol::encode::PacketWriter; use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; use valence_protocol::packets::play::{BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDeltaUpdateS2c}; use valence_protocol::{BlockPos, ChunkPos, ChunkSectionPos, WritePacket}; use super::{block_offsets, Block, BlockRef, ChunkOps, LoadedChunk}; -use crate::layer::chunk::LocalMsg; +use crate::layer_old::chunk::LocalMsg; use crate::{BlockState, ChunkLayer, Layer}; impl ChunkLayer { @@ -183,14 +182,3 @@ impl ChunkLayer { } } -pub trait Batch { - /// An iterator over the blocks modified by this batch. - /// - /// **NOTE:** For optimal performance, the blocks iterated over should be - /// sorted by chunk position and then chunk section position within those - /// groups. - type BlockStateIter: Iterator; - type BlockEntityIter: Iterator - - fn into_batch_iters(self) -> Self::BlockIter; -} diff --git a/crates/valence_server/src/layer/chunk/batch/basic.rs b/crates/valence_server/src/layer_old/chunk/batch/basic.rs similarity index 96% rename from crates/valence_server/src/layer/chunk/batch/basic.rs rename to crates/valence_server/src/layer_old/chunk/batch/basic.rs index fd586fcd7..d7e2ec668 100644 --- a/crates/valence_server/src/layer/chunk/batch/basic.rs +++ b/crates/valence_server/src/layer_old/chunk/batch/basic.rs @@ -1,3 +1,4 @@ +/*/ use bevy_ecs::prelude::Component; use bitfield_struct::bitfield; use valence_protocol::{BlockPos, BlockState, ChunkPos}; @@ -162,11 +163,7 @@ struct BlockUpdate { off_z: u32, #[bits(4)] off_y: u32, - /// `false` if `state` is a block state, `true` if it's an index into the - /// `full` array. - is_index: bool, - /// Bits of the [`BlockState`] or an index into the `full` array. - #[bits(31)] + /// Bits of the [`BlockState`]. state: u32, } @@ -243,3 +240,4 @@ impl Ord for BlockUpdate { } } */ +*/ \ No newline at end of file diff --git a/crates/valence_server/src/layer/chunk/biome.rs b/crates/valence_server/src/layer_old/chunk/biome.rs similarity index 100% rename from crates/valence_server/src/layer/chunk/biome.rs rename to crates/valence_server/src/layer_old/chunk/biome.rs diff --git a/crates/valence_server/src/layer_old/chunk/block_update.rs b/crates/valence_server/src/layer_old/chunk/block_update.rs new file mode 100644 index 000000000..2e028b3ca --- /dev/null +++ b/crates/valence_server/src/layer_old/chunk/block_update.rs @@ -0,0 +1,357 @@ +use std::borrow::Cow; + +use bitfield_struct::bitfield; +use valence_generated::block::BlockEntityKind; +use valence_nbt::Compound; +use valence_protocol::block_pos::PackedBlockPos; +use valence_protocol::encode::PacketWriter; +use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; +use valence_protocol::packets::play::{BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDeltaUpdateS2c}; +use valence_protocol::{BlockPos, BlockState, ChunkPos, ChunkSectionPos, WritePacket}; + +use super::{block_offsets, Block, ChunkOps, LoadedChunk, LocalMsg}; +use crate::ChunkLayer; + +impl ChunkLayer { + pub fn set_block( + &mut self, + pos: impl Into, + block: impl Into, + ) -> Option { + let pos = pos.into(); + let block: Block = block.into(); + + let chunk = self.chunk_mut(pos)?; + + let [x, y, z] = block_offsets(pos, self.info.min_y, self.info.height as i32)?; + + self.block_updates + .updates + .push(BlockUpdate::from_parts(pos, block.state)); + + if let (Some(data), Some(kind)) = (block.nbt, block.state.to_kind()) { + PacketWriter::new( + &mut self.block_updates.block_entity_buf, + self.info.threshold, + ) + .write_packet(&BlockEntityUpdateS2c { + position: pos, + kind, + data: Cow::Borrowed(&data), + }); + } + + Some(chunk.set_block(x, y, z, block)) + } + + pub fn flush_block_updates(&mut self) { + // Sort in reverse so the dedup keeps the last of consecutive elements. + self.block_updates.updates.sort_by(|a, b| b.cmp(a)); + + // Eliminate redundant block assignments. + self.block_updates + .updates + .dedup_by_key(|u| u.0 & BlockUpdate::BLOCK_POS_MASK); + + let sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); + + for update in self.block_updates.updates.drain(..) { + let (pos, state) = update.to_parts(); + + let new_sect_pos = ChunkSectionPos::from(pos); + + if sect_pos != new_sect_pos { + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + match self.block_updates.entry_buf.as_slice() { + // Zero updates. Do nothing. + &[] => {} + // One update. Send singular block update packet. + &[update] => self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { + position: BlockPos { + x: sect_pos.x * 16 + update.off_x() as i32, + y: sect_pos.y * 16 + update.off_y() as i32, + z: sect_pos.z * 16 + update.off_z() as i32, + }, + block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), + }); + }), + // >1 updates. Send special section update packet. + updates => { + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet( + &ChunkDeltaUpdateS2c { + chunk_section_pos: sect_pos, + blocks: Cow::Borrowed(updates), + }, + ) + }); + } + } + + self.block_updates.entry_buf.clear(); + } + } + + /* + let mut chunk: Option<&mut LoadedChunk> = None; + let mut sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); + for update in self.block_updates.updates.drain(..) { + let (pos, state, has_nbt) = update.to_parts(); + let new_sect_pos = ChunkSectionPos::from(pos); + + // Is this block in a new section? If it is, then flush the changes we've + // accumulated for the old section. + if sect_pos != new_sect_pos { + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + match self.block_updates.entry_buf.as_slice() { + // Zero updates. Do nothing. + &[] => {} + // One update. Send singular block update packet. + &[update] => self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { + position: BlockPos { + x: sect_pos.x * 16 + update.off_x() as i32, + y: sect_pos.y * 16 + update.off_y() as i32, + z: sect_pos.z * 16 + update.off_z() as i32, + }, + block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), + }); + }), + // >1 updates. Send special section update packet. + updates => { + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet( + &ChunkDeltaUpdateS2c { + chunk_section_pos: sect_pos, + blocks: Cow::Borrowed(updates), + }, + ) + }); + } + } + + self.block_updates.entry_buf.clear(); + + // Send the block entity update packets. + // It's important that this is ordered after block state updates are sent. + for (pos, kind, nbt) in nbt.take(nbt_count) { + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet( + &BlockEntityUpdateS2c { + position: pos, + kind, + data: Cow::Owned(nbt), + }, + ) + }); + } + + // Update the chunk ref if the chunk pos changed. + if sect_pos.x != new_sect_pos.x || sect_pos.z != new_sect_pos.z { + chunk = self.chunks.get_mut(&ChunkPos::from(new_sect_pos)); + } + + // Update section pos. + sect_pos = new_sect_pos; + } + + // // Send block entity updates for the current block. + // if has_nbt { + // let nbt = nbt.next().unwrap(); + + // if let Some(kind) = state.to_kind() { + // let msg = LocalMsg::PacketAt { + // pos: sect_pos.into(), + // }; + + // self.messages.send_local_infallible(msg, |w| { + // PacketWriter::new(w, self.info.threshold).write_packet( + // &BlockEntityUpdateS2c { + // position: pos, + // kind, + // data: Cow::Owned(nbt), + // }, + // ) + // }); + // } + // } + + if let Some(chunk) = &mut chunk { + let chunk_y = pos.y.wrapping_sub(self.info.min_y) as u32; + + // Is the block pos in bounds of the chunk? + if chunk_y < self.info.height { + let chunk_x = pos.x.rem_euclid(16); + let chunk_z = pos.z.rem_euclid(16); + + // Make change to the chunk and push section update. + + if chunk.viewer_count_mut() > 0 { + self.block_update_buf.push( + ChunkDeltaUpdateEntry::new() + .with_off_x(chunk_x as u8) + .with_off_y((chunk_y % 16) as u8) + .with_off_z(chunk_z as u8) + .with_block_state(block.state.to_raw() as u32), + ); + } + + chunk.set_block(chunk_x as u32, chunk_y, chunk_z as u32, block); + } + } + } + + // Flush any remaining block changes. + + let msg = LocalMsg::PacketAt { + pos: sect_pos.into(), + }; + + match self.block_update_buf.as_slice() { + // Zero updates. Do nothing. + &[] => {} + // One update. Send singular block update packet. + &[update] => self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { + position: BlockPos { + x: sect_pos.x * 16 + update.off_x() as i32, + y: sect_pos.y * 16 + update.off_y() as i32, + z: sect_pos.z * 16 + update.off_z() as i32, + }, + block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), + }); + }), + // >1 updates. Send special section update packet. + updates => { + self.messages.send_local_infallible(msg, |w| { + PacketWriter::new(w, self.info.threshold).write_packet(&ChunkDeltaUpdateS2c { + chunk_section_pos: sect_pos, + blocks: Cow::Borrowed(updates), + }) + }); + } + } + + self.block_update_buf.clear();*/ + } +} + +pub(super) struct BlockUpdates { + updates: Vec, + block_entities: Vec<(BlockPos, BlockEntityKind, Compound)>, + entry_buf: Vec, +} + +impl BlockUpdates { + pub(super) fn shrink_to_fit(&mut self) { + self.updates.shrink_to_fit(); + self.block_entities.shrink_to_fit(); + self.entry_buf.shrink_to_fit(); + } +} + +#[bitfield(u128, order = Msb)] +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct BlockUpdate { + // Section coordinate. + #[bits(28)] + section_x: i32, + #[bits(28)] + section_z: i32, + #[bits(28)] + section_y: i32, + // Coordinate within the section. + #[bits(4)] + off_x: u32, + #[bits(4)] + off_z: u32, + #[bits(4)] + off_y: u32, + /// Bits of the [`BlockState`]. + state: u32, +} + +impl BlockUpdate { + const CHUNK_POS_MASK: u128 = u128::MAX << 72; + const SECTION_POS_MASK: u128 = u128::MAX << 44; + const BLOCK_POS_MASK: u128 = u128::MAX << 32; + + fn from_parts(pos: BlockPos, state: BlockState) { + Self::new() + .with_section_x(pos.x.div_euclid(16)) + .with_section_y(pos.y.div_euclid(16)) + .with_section_z(pos.z.div_euclid(16)) + .with_off_x(pos.x.rem_euclid(16) as u32) + .with_off_y(pos.y.rem_euclid(16) as u32) + .with_off_z(pos.z.rem_euclid(16) as u32) + .with_state(state.to_raw() as u32) + } + + fn to_parts(self) -> (BlockPos, BlockState) { + ( + BlockPos { + x: self.section_x() * 16 + self.off_x() as i32, + y: self.section_y() * 16 + self.off_y() as i32, + z: self.section_z() * 16 + self.off_z() as i32, + }, + BlockState::from_raw(self.state()).unwrap(), + ) + } + + // fn from_block_state(pos: BlockPos, state: BlockState) -> Self { + // Self::new() + // .with_section_x(pos.x.div_euclid(16)) + // .with_section_y(pos.y.div_euclid(16)) + // .with_section_z(pos.z.div_euclid(16)) + // .with_off_x(pos.x.rem_euclid(16) as u32) + // .with_off_y(pos.y.rem_euclid(16) as u32) + // .with_off_z(pos.z.rem_euclid(16) as u32) + // .with_state(state.to_raw() as u32) + // } + + // fn from_index(pos: BlockPos, idx: u32) -> Self { + // Self::new() + // .with_section_x(pos.x.div_euclid(16)) + // .with_section_y(pos.y.div_euclid(16)) + // .with_section_z(pos.z.div_euclid(16)) + // .with_off_x(pos.x.rem_euclid(16) as u32) + // .with_off_y(pos.y.rem_euclid(16) as u32) + // .with_off_z(pos.z.rem_euclid(16) as u32) + // .with_is_index(true) + // .with_state(idx) + // } + + // fn to_parts(self) -> (BlockPos, BlockState) { + // (self.block_pos(), self.block()) + // } + + // fn block_pos(self) -> BlockPos { + // BlockPos { + // x: self.section_x() * 16 + self.off_x() as i32, + // y: self.section_y() * 16 + self.off_y() as i32, + // z: self.section_z() * 16 + self.off_z() as i32, + // } + // } + + // fn chunk_pos(self) -> ChunkPos { + // ChunkPos { + // x: self.section_x(), + // z: self.section_z(), + // } + // } + + // fn block(self) -> BlockState { + // BlockState::from_raw(self.state() as u16).unwrap() + // } +} diff --git a/crates/valence_server/src/layer/chunk/chunk.rs b/crates/valence_server/src/layer_old/chunk/chunk.rs similarity index 97% rename from crates/valence_server/src/layer/chunk/chunk.rs rename to crates/valence_server/src/layer_old/chunk/chunk.rs index 2e97b5282..4d92694d2 100644 --- a/crates/valence_server/src/layer/chunk/chunk.rs +++ b/crates/valence_server/src/layer_old/chunk/chunk.rs @@ -2,7 +2,7 @@ use valence_nbt::Compound; use valence_protocol::{BlockPos, BlockState, ChunkPos}; use valence_registry::biome::BiomeId; -use super::{BiomePos, BlockRef, Block}; +use super::{BiomePos, Block, BlockRef}; /// Common operations on chunks. Notable implementors are /// [`LoadedChunk`](super::loaded::LoadedChunk) and @@ -38,8 +38,9 @@ pub trait ChunkOps { let old_state = self.set_block_state(x, y, z, block.state); let old_nbt = if block.nbt.is_none() && block.state.block_entity_kind().is_some() { - // Make sure there's always NBT data for the block entity. Otherwise, it will - // appear invisible to clients when loading the chunk. + // If the block state is associated with a block entity, make sure there's + // always some NBT data. Otherwise, the block will appear invisible to clients + // when loading the chunk. self.set_block_entity(x, y, z, Some(Compound::default())) } else { self.set_block_entity(x, y, z, block.nbt) @@ -261,7 +262,7 @@ pub(super) fn biome_offsets(biome_pos: BiomePos, min_y: i32, height: i32) -> Opt #[cfg(test)] mod tests { use super::*; - use crate::layer::chunk::{Chunk, LoadedChunk}; + use crate::layer_old::chunk::{Chunk, LoadedChunk}; #[test] fn chunk_get_set() { diff --git a/crates/valence_server/src/layer_old/chunk/loaded.rs b/crates/valence_server/src/layer_old/chunk/loaded.rs new file mode 100644 index 000000000..705b5e742 --- /dev/null +++ b/crates/valence_server/src/layer_old/chunk/loaded.rs @@ -0,0 +1,330 @@ +use std::borrow::Cow; +use std::mem; +use std::sync::atomic::{AtomicU32, Ordering}; + +use parking_lot::Mutex; // Using nonstandard mutex to avoid poisoning API. +use valence_nbt::{compound, Compound}; +use valence_protocol::encode::{PacketWriter, WritePacket}; +use valence_protocol::packets::play::chunk_data_s2c::ChunkDataBlockEntity; +use valence_protocol::packets::play::ChunkDataS2c; +use valence_protocol::{BlockState, ChunkPos, Encode}; +use valence_registry::biome::BiomeId; +use valence_registry::RegistryIdx; + +use super::chunk::{bit_width, ChunkOps}; +use super::unloaded::Chunk; +use super::{ChunkLayerInfo, SECTION_BLOCK_COUNT}; + +/// A chunk that is actively loaded in a [`ChunkLayer`]. This is only accessible +/// behind a reference. +/// +/// Like [`Chunk`], loaded chunks implement the [`ChunkOps`] trait so you can +/// use many of the same methods. +/// +/// **NOTE:** Loaded chunks are a low-level API. Mutations directly to loaded +/// chunks are intentionally not synchronized with clients. Consider using the +/// relevant methods on [`ChunkLayer`] instead. +/// +/// [`ChunkLayer`]: super::ChunkLayer +#[derive(Debug)] +pub struct LoadedChunk { + /// Chunk data for this loaded chunk. + chunk: Chunk, + /// A count of the clients viewing this chunk. Useful for knowing if it's + /// necessary to record changes, since no client would be in view to receive + /// the changes if this were zero. + viewer_count: AtomicU32, + /// Cached bytes of the chunk initialization packet. The cache is considered + /// invalidated if empty. This should be cleared whenever the chunk is + /// modified in an observable way, even if the chunk is not viewed. + cached_init_packets: Mutex>, +} + +impl LoadedChunk { + pub(crate) fn new(height: u32) -> Self { + Self { + viewer_count: AtomicU32::new(0), + chunk: Chunk::with_height(height), + cached_init_packets: Mutex::new(vec![]), + } + } + + /// Sets the content of this chunk to the supplied [`UnloadedChunk`]. The + /// given unloaded chunk is [resized] to match the height of this loaded + /// chunk prior to insertion. + /// + /// The previous chunk data is returned. + /// + /// [resized]: UnloadedChunk::set_height + pub fn replace(&mut self, mut chunk: Chunk) -> Chunk { + chunk.set_height(self.height()); + + self.cached_init_packets.get_mut().clear(); + + mem::replace(&mut self.chunk, chunk) + } + + pub(super) fn into_chunk(self) -> Chunk { + self.chunk + } + + /// Clones this chunk's data into the returned [`Chunk`]. + pub fn to_chunk(&self) -> Chunk { + self.chunk.clone() + } + + /// Returns the number of clients in view of this chunk. + pub fn viewer_count(&self) -> u32 { + self.viewer_count.load(Ordering::Relaxed) + } + + /// Like [`Self::viewer_count`], but avoids an atomic operation. + pub fn viewer_count_mut(&mut self) -> u32 { + *self.viewer_count.get_mut() + } + + /// Increments the viewer count. + pub(crate) fn inc_viewer_count(&self) { + self.viewer_count.fetch_add(1, Ordering::Relaxed); + } + + /// Decrements the viewer count. + #[track_caller] + pub(crate) fn dec_viewer_count(&self) { + let old = self.viewer_count.fetch_sub(1, Ordering::Relaxed); + debug_assert_ne!(old, 0, "viewer count underflow!"); + } + + /// Writes the packet data needed to initialize this chunk. + pub(crate) fn write_init_packets( + &self, + mut writer: impl WritePacket, + pos: ChunkPos, + info: &ChunkLayerInfo, + ) { + let mut init_packets = self.cached_init_packets.lock(); + + if init_packets.is_empty() { + let heightmaps = compound! { + // TODO: MOTION_BLOCKING and WORLD_SURFACE heightmaps. + }; + + let mut blocks_and_biomes: Vec = vec![]; + + for sect in &self.chunk.sections { + sect.count_non_air_blocks() + .encode(&mut blocks_and_biomes) + .unwrap(); + + sect.block_states + .encode_mc_format( + &mut blocks_and_biomes, + |b| b.to_raw().into(), + 4, + 8, + bit_width(BlockState::max_raw().into()), + ) + .expect("paletted container encode should always succeed"); + + sect.biomes + .encode_mc_format( + &mut blocks_and_biomes, + |b| b.to_index() as _, + 0, + 3, + bit_width(info.biome_registry_len - 1), + ) + .expect("paletted container encode should always succeed"); + } + + let block_entities: Vec<_> = self + .chunk + .block_entities + .iter() + .filter_map(|(&idx, nbt)| { + let x = idx % 16; + let z = idx / 16 % 16; + let y = idx / 16 / 16; + + let kind = self.chunk.sections[y as usize / 16] + .block_states + .get(idx as usize % SECTION_BLOCK_COUNT) + .block_entity_kind(); + + kind.map(|kind| ChunkDataBlockEntity { + packed_xz: ((x << 4) | z) as i8, + y: y as i16 + info.min_y as i16, + kind, + data: Cow::Borrowed(nbt), + }) + }) + .collect(); + + PacketWriter::new(&mut init_packets, info.threshold).write_packet(&ChunkDataS2c { + pos, + heightmaps: Cow::Owned(heightmaps), + blocks_and_biomes: &blocks_and_biomes, + block_entities: Cow::Owned(block_entities), + sky_light_mask: Cow::Borrowed(&[]), + block_light_mask: Cow::Borrowed(&[]), + empty_sky_light_mask: Cow::Borrowed(&[]), + empty_block_light_mask: Cow::Borrowed(&[]), + sky_light_arrays: Cow::Borrowed(&[]), + block_light_arrays: Cow::Borrowed(&[]), + }) + } + + writer.write_packet_bytes(&init_packets); + } +} + +impl ChunkOps for LoadedChunk { + fn height(&self) -> u32 { + self.chunk.height() + } + + fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState { + self.chunk.block_state(x, y, z) + } + + fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState { + let old_block = self.chunk.set_block_state(x, y, z, block); + + if block != old_block { + self.cached_init_packets.get_mut().clear(); + } + + old_block + } + + fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) { + self.chunk.fill_block_state_section(sect_y, block); + + // TODO: do some checks to avoid calling this sometimes. + self.cached_init_packets.get_mut().clear(); + } + + fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> { + self.chunk.block_entity(x, y, z) + } + + fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> { + let res = self.chunk.block_entity_mut(x, y, z); + + if res.is_some() { + self.cached_init_packets.get_mut().clear(); + } + + res + } + + fn set_block_entity( + &mut self, + x: u32, + y: u32, + z: u32, + block_entity: Option, + ) -> Option { + self.cached_init_packets.get_mut().clear(); + + self.chunk.set_block_entity(x, y, z, block_entity) + } + + fn clear_block_entities(&mut self) { + if self.chunk.block_entities.is_empty() { + return; + } + + self.chunk.clear_block_entities(); + + self.cached_init_packets.get_mut().clear(); + } + + fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId { + self.chunk.biome(x, y, z) + } + + fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId { + let old_biome = self.chunk.set_biome(x, y, z, biome); + + if biome != old_biome { + self.cached_init_packets.get_mut().clear(); + } + + old_biome + } + + fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) { + self.chunk.fill_biome_section(sect_y, biome); + + self.cached_init_packets.get_mut().clear(); + } + + fn shrink_to_fit(&mut self) { + self.cached_init_packets.get_mut().shrink_to_fit(); + self.chunk.shrink_to_fit(); + } +} + +#[cfg(test)] +mod tests { + use valence_protocol::{ident, CompressionThreshold}; + + use super::*; + + #[test] + fn loaded_chunk_changes_clear_packet_cache() { + #[track_caller] + fn check(chunk: &mut LoadedChunk, change: impl FnOnce(&mut LoadedChunk) -> T) { + let info = ChunkLayerInfo { + dimension_type_name: ident!("whatever").into(), + height: 512, + min_y: -16, + biome_registry_len: 200, + threshold: CompressionThreshold(-1), + }; + + let mut buf = vec![]; + let mut writer = PacketWriter::new(&mut buf, CompressionThreshold(-1)); + + // Rebuild cache. + chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info); + + // Check that the cache is built. + assert!(!chunk.cached_init_packets.get_mut().is_empty()); + + // Making a change should clear the cache. + change(chunk); + assert!(chunk.cached_init_packets.get_mut().is_empty()); + + // Rebuild cache again. + chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info); + assert!(!chunk.cached_init_packets.get_mut().is_empty()); + } + + let mut chunk = LoadedChunk::new(512); + + check(&mut chunk, |c| { + c.set_block_state(0, 4, 0, BlockState::ACACIA_WOOD) + }); + check(&mut chunk, |c| c.set_biome(1, 2, 3, BiomeId::from_index(4))); + check(&mut chunk, |c| c.fill_biomes(BiomeId::DEFAULT)); + check(&mut chunk, |c| c.fill_block_states(BlockState::WET_SPONGE)); + check(&mut chunk, |c| { + c.set_block_entity(3, 40, 5, Some(compound! {})) + }); + check(&mut chunk, |c| { + c.block_entity_mut(3, 40, 5).unwrap(); + }); + check(&mut chunk, |c| c.set_block_entity(3, 40, 5, None)); + + // Old block state is the same as new block state, so the cache should still be + // intact. + assert_eq!( + chunk.set_block_state(0, 0, 0, BlockState::WET_SPONGE), + BlockState::WET_SPONGE + ); + + assert!(!chunk.cached_init_packets.get_mut().is_empty()); + } +} diff --git a/crates/valence_server/src/layer/chunk/unloaded.rs b/crates/valence_server/src/layer_old/chunk/unloaded.rs similarity index 100% rename from crates/valence_server/src/layer/chunk/unloaded.rs rename to crates/valence_server/src/layer_old/chunk/unloaded.rs diff --git a/crates/valence_server/src/layer/entity.rs b/crates/valence_server/src/layer_old/entity.rs similarity index 100% rename from crates/valence_server/src/layer/entity.rs rename to crates/valence_server/src/layer_old/entity.rs diff --git a/crates/valence_server/src/layer_old/message.rs b/crates/valence_server/src/layer_old/message.rs new file mode 100644 index 000000000..15bc6838c --- /dev/null +++ b/crates/valence_server/src/layer_old/message.rs @@ -0,0 +1,323 @@ +use core::fmt; +use std::convert::Infallible; +use std::ops::Range; + +use valence_protocol::ChunkPos; + +use crate::layer_old::bvh::{ChunkBvh, GetChunkPos}; +use crate::ChunkView; + +/// A message buffer of global messages (`G`) and local messages (`L`) meant for +/// consumption by clients. Local messages are those that have some spatial +/// component to them and implement the [`GetChunkPos`] trait. Local messages +/// are placed in a bounding volume hierarchy for fast queries via +/// [`Self::query_local`]. Global messages do not necessarily have a spatial +/// component and all globals will be visited when using [`Self::iter_global`]. +/// +/// Every message is associated with an arbitrary span of bytes. The meaning of +/// the bytes is whatever the message needs it to be. +/// +/// At the end of the tick and before clients have access to the buffer, all +/// messages are sorted and then deduplicated by concatenating byte spans +/// together. This is done for a couple of reasons: +/// - Messages may rely on sorted message order for correctness, like in the +/// case of entity spawn & despawn messages. Sorting also makes deduplication +/// easy. +/// - Deduplication reduces the total number of messages that all clients must +/// examine. Consider the case of a message such as "send all clients in view +/// of this chunk position these packet bytes". If two of these messages have +/// the same chunk position, then they can just be combined together. +pub struct Messages { + global: Vec<(G, Range)>, + local: Vec<(L, Range)>, + bvh: ChunkBvh>, + staging: Vec, + ready: Vec, + is_ready: bool, +} + +impl Messages +where + G: Clone + Ord, + L: Clone + Ord + GetChunkPos, +{ + pub(crate) fn new() -> Self { + Self::default() + } + + /// Adds a global message to this message buffer. + pub(crate) fn send_global( + &mut self, + msg: G, + f: impl FnOnce(&mut Vec) -> Result<(), E>, + ) -> Result<(), E> { + debug_assert!(!self.is_ready); + + let start = self.staging.len(); + f(&mut self.staging)?; + let end = self.staging.len(); + + if let Some((m, range)) = self.global.last_mut() { + if msg == *m { + // Extend the existing message. + range.end = end as u32; + return Ok(()); + } + } + + self.global.push((msg, start as u32..end as u32)); + + Ok(()) + } + + /// Adds a local message to this message buffer. + pub(crate) fn send_local( + &mut self, + msg: L, + f: impl FnOnce(&mut Vec) -> Result<(), E>, + ) -> Result<(), E> { + debug_assert!(!self.is_ready); + + let start = self.staging.len(); + f(&mut self.staging)?; + let end = self.staging.len(); + + if let Some((m, range)) = self.local.last_mut() { + if msg == *m { + // Extend the existing message. + range.end = end as u32; + return Ok(()); + } + } + + self.local.push((msg, start as u32..end as u32)); + + Ok(()) + } + + pub(crate) fn insert_local(&mut self, msg: L, rev_idx: usize, f: impl FnOnce(&mut Vec) -> Result<(), E>) -> Result<(), E> { + debug_assert!(!self.is_ready); + + let start = self.staging.len(); + f(&mut self.staging)?; + let end = self.staging.len(); + + self.local.insert(self.local.len() - rev_idx, (msg, start as u32..end as u32)); + + Ok(()) + } + + /// Like [`Self::send_global`] but writing bytes cannot fail. + pub(crate) fn send_global_infallible(&mut self, msg: G, f: impl FnOnce(&mut Vec)) { + let _ = self.send_global::(msg, |b| { + f(b); + Ok(()) + }); + } + + /// Like [`Self::send_local`] but writing bytes cannot fail. + pub(crate) fn send_local_infallible(&mut self, msg: L, f: impl FnOnce(&mut Vec)) { + let _ = self.send_local::(msg, |b| { + f(b); + Ok(()) + }); + } + + /// Readies messages to be read by clients. + pub(crate) fn ready(&mut self) { + debug_assert!(!self.is_ready); + self.is_ready = true; + + debug_assert!(self.ready.is_empty()); + + self.ready.reserve_exact(self.staging.len()); + + fn sort_and_merge( + msgs: &mut Vec<(M, Range)>, + staging: &[u8], + ready: &mut Vec, + ) { + // Sort must be stable. + msgs.sort_by_key(|(msg, _)| msg.clone()); + + // Make sure the first element is already copied to "ready". + if let Some((_, range)) = msgs.first_mut() { + let start = ready.len(); + ready.extend_from_slice(&staging[range.start as usize..range.end as usize]); + let end = ready.len(); + + *range = start as u32..end as u32; + } + + msgs.dedup_by(|(right_msg, right_range), (left_msg, left_range)| { + if *left_msg == *right_msg { + // Extend the left element with the right element. Then delete the right + // element. + + let right_bytes = + &staging[right_range.start as usize..right_range.end as usize]; + + ready.extend_from_slice(right_bytes); + + left_range.end += right_bytes.len() as u32; + + true + } else { + // Copy right element to "ready". + + let right_bytes = + &staging[right_range.start as usize..right_range.end as usize]; + + let start = ready.len(); + ready.extend_from_slice(right_bytes); + let end = ready.len(); + + *right_range = start as u32..end as u32; + + false + } + }); + } + + sort_and_merge(&mut self.global, &self.staging, &mut self.ready); + sort_and_merge(&mut self.local, &self.staging, &mut self.ready); + + self.bvh.build( + self.local + .iter() + .cloned() + .map(|(msg, range)| MessagePair { msg, range }), + ); + } + + pub(crate) fn unready(&mut self) { + assert!(self.is_ready); + self.is_ready = false; + + self.local.clear(); + self.global.clear(); + self.staging.clear(); + self.ready.clear(); + } + + pub(crate) fn shrink_to_fit(&mut self) { + self.global.shrink_to_fit(); + self.local.shrink_to_fit(); + self.bvh.shrink_to_fit(); + self.staging.shrink_to_fit(); + self.ready.shrink_to_fit(); + } + + /// All message bytes. Use this in conjunction with [`Self::iter_global`] + /// and [`Self::query_local`]. + pub fn bytes(&self) -> &[u8] { + debug_assert!(self.is_ready); + + &self.ready + } + + /// Returns an iterator over all global messages and their span of bytes in + /// [`Self::bytes`]. + pub fn iter_global(&self) -> impl Iterator)> + '_ { + debug_assert!(self.is_ready); + + self.global + .iter() + .map(|(m, r)| (m.clone(), r.start as usize..r.end as usize)) + } + + /// Takes a visitor function `f` and visits all local messages contained + /// within the chunk view `view`. `f` is called with the local + /// message and its span of bytes in [`Self::bytes`]. + pub fn query_local(&self, view: ChunkView, mut f: impl FnMut(L, Range)) { + debug_assert!(self.is_ready); + + self.bvh.query(view, |pair| { + f( + pair.msg.clone(), + pair.range.start as usize..pair.range.end as usize, + ) + }); + } +} + +impl Default for Messages { + fn default() -> Self { + Self { + global: Default::default(), + local: Default::default(), + bvh: Default::default(), + staging: Default::default(), + ready: Default::default(), + is_ready: Default::default(), + } + } +} + +impl fmt::Debug for Messages +where + G: fmt::Debug, + L: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Messages") + .field("global", &self.global) + .field("local", &self.local) + .field("is_ready", &self.is_ready) + .finish() + } +} + +#[derive(Debug)] +struct MessagePair { + msg: M, + range: Range, +} + +impl GetChunkPos for MessagePair { + fn chunk_pos(&self) -> ChunkPos { + self.msg.chunk_pos() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] + struct DummyLocal; + + impl GetChunkPos for DummyLocal { + fn chunk_pos(&self) -> ChunkPos { + unimplemented!() + } + } + + #[test] + fn send_global_message() { + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] + enum TestMsg { + Foo, + Bar, + } + + let mut messages = Messages::::new(); + + messages.send_global_infallible(TestMsg::Foo, |b| b.extend_from_slice(&[1, 2, 3])); + messages.send_global_infallible(TestMsg::Bar, |b| b.extend_from_slice(&[4, 5, 6])); + messages.send_global_infallible(TestMsg::Foo, |b| b.extend_from_slice(&[7, 8, 9])); + + messages.ready(); + + let bytes = messages.bytes(); + + for (msg, range) in messages.iter_global() { + match msg { + TestMsg::Foo => assert_eq!(&bytes[range.clone()], &[1, 2, 3, 7, 8, 9]), + TestMsg::Bar => assert_eq!(&bytes[range.clone()], &[4, 5, 6]), + } + } + + messages.unready(); + } +} diff --git a/crates/valence_server/src/lib.rs b/crates/valence_server/src/lib.rs index feb9a5902..78b97e487 100644 --- a/crates/valence_server/src/lib.rs +++ b/crates/valence_server/src/lib.rs @@ -25,6 +25,8 @@ pub mod client; pub mod client_command; pub mod client_settings; pub mod custom_payload; +pub mod dimension_layer; +pub mod entity_layer; pub mod event_loop; pub mod hand_swing; pub mod interact_block; @@ -38,10 +40,10 @@ pub mod op_level; pub mod resource_pack; pub mod spawn; pub mod status; -pub mod teleport; pub mod title; pub use chunk_view::ChunkView; +pub use client::Client; pub use event_loop::{EventLoopPostUpdate, EventLoopPreUpdate, EventLoopUpdate}; pub use layer::{ChunkLayer, EntityLayer, Layer, LayerBundle}; pub use valence_protocol::{ diff --git a/crates/valence_server/src/movement.rs b/crates/valence_server/src/movement.rs index 5b5835799..ba10b59f0 100644 --- a/crates/valence_server/src/movement.rs +++ b/crates/valence_server/src/movement.rs @@ -1,21 +1,175 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; +use tracing::warn; use valence_entity::{HeadYaw, Look, OnGround, Position}; use valence_math::DVec3; +use valence_protocol::packets::play::player_position_look_s2c::PlayerPositionLookFlags; use valence_protocol::packets::play::{ - FullC2s, LookAndOnGroundC2s, OnGroundOnlyC2s, PositionAndOnGroundC2s, VehicleMoveC2s, + FullC2s, LookAndOnGroundC2s, OnGroundOnlyC2s, PlayerPositionLookS2c, PlayerSpawnPositionS2c, + PositionAndOnGroundC2s, TeleportConfirmC2s, VehicleMoveC2s, }; +use valence_protocol::{BlockPos, WritePacket}; use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; -use crate::teleport::TeleportState; +use crate::Client; +use crate::layer::BroadcastLayerMessagesSet; -pub struct MovementPlugin; +/// Handles client movement and teleports. +pub struct PositionPlugin; -impl Plugin for MovementPlugin { +/// When client positions are synchronized by sending the clientbound position +/// packet. This set also includes the system that updates the client's respawn +/// position. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct SyncPositionSet; + +impl Plugin for PositionPlugin { fn build(&self, app: &mut App) { - app.init_resource::() + app + .init_resource::() .add_event::() - .add_systems(EventLoopPreUpdate, handle_client_movement); + .add_systems(EventLoopPreUpdate, (handle_teleport_confirmations, handle_client_movement)) + // Sync position after chunks are loaded so the client doesn't fall through the floor. + // Setting the respawn position also closes the "downloading terrain" screen. + .configure_set(PostUpdate, SyncPositionSet.after(BroadcastLayerMessagesSet)) + .add_systems(PostUpdate, (update_respawn_position, teleport).chain().in_set(SyncPositionSet)); + } +} + +#[derive(Component, Debug)] +pub struct TeleportState { + /// Counts up as teleports are made. + teleport_id_counter: u32, + /// The number of pending client teleports that have yet to receive a + /// confirmation. Inbound client position packets should be ignored while + /// this is nonzero. + pending_teleports: u32, + synced_pos: DVec3, + synced_look: Look, +} + +impl TeleportState { + pub fn teleport_id_counter(&self) -> u32 { + self.teleport_id_counter + } + + pub fn pending_teleports(&self) -> u32 { + self.pending_teleports + } +} + +impl Default for TeleportState { + fn default() -> Self { + Self { + teleport_id_counter: 0, + pending_teleports: 0, + // Set initial synced pos and look to NaN so a teleport always happens when first + // joining. + synced_pos: DVec3::NAN, + synced_look: Look { + yaw: f32::NAN, + pitch: f32::NAN, + }, + } + } +} + +/// Syncs the client's position and look with the server. +/// +/// This should happen after chunks are loaded so the client doesn't fall though +/// the floor. +#[allow(clippy::type_complexity)] +fn teleport( + mut clients: Query< + (&mut Client, &mut TeleportState, &Position, &Look), + Or<(Changed, Changed)>, + >, +) { + for (mut client, mut state, pos, look) in &mut clients { + let changed_pos = pos.0 != state.synced_pos; + let changed_yaw = look.yaw != state.synced_look.yaw; + let changed_pitch = look.pitch != state.synced_look.pitch; + + if changed_pos || changed_yaw || changed_pitch { + state.synced_pos = pos.0; + state.synced_look = *look; + + let flags = PlayerPositionLookFlags::new() + .with_x(!changed_pos) + .with_y(!changed_pos) + .with_z(!changed_pos) + .with_y_rot(!changed_yaw) + .with_x_rot(!changed_pitch); + + client.write_packet(&PlayerPositionLookS2c { + position: if changed_pos { pos.0 } else { DVec3::ZERO }, + yaw: if changed_yaw { look.yaw } else { 0.0 }, + pitch: if changed_pitch { look.pitch } else { 0.0 }, + flags, + teleport_id: (state.teleport_id_counter as i32).into(), + }); + + state.pending_teleports = state.pending_teleports.wrapping_add(1); + state.teleport_id_counter = state.teleport_id_counter.wrapping_add(1); + } + } +} + +fn handle_teleport_confirmations( + mut packets: EventReader, + mut clients: Query<&mut TeleportState>, + mut commands: Commands, +) { + for packet in packets.iter() { + if let Some(pkt) = packet.decode::() { + if let Ok(mut state) = clients.get_mut(packet.client) { + if state.pending_teleports == 0 { + warn!( + "unexpected teleport confirmation from client {:?}", + packet.client + ); + commands.entity(packet.client).remove::(); + } + + let got = pkt.teleport_id.0 as u32; + let expected = state + .teleport_id_counter + .wrapping_sub(state.pending_teleports); + + if got == expected { + state.pending_teleports -= 1; + } else { + warn!( + "unexpected teleport ID for client {:?} (expected {expected}, got {got})", + packet.client + ); + commands.entity(packet.client).remove::(); + } + } + } + } +} + +/// The position and angle that clients will respawn with. Also +/// controls the position that compasses point towards. +#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] +pub struct RespawnPosition { + /// The position that clients will respawn at. This can be changed at any + /// time to set the position that compasses point towards. + pub pos: BlockPos, + /// The yaw angle that clients will respawn with (in degrees). + pub yaw: f32, +} + +/// Sets the client's respawn and compass position. +fn update_respawn_position( + mut clients: Query<(&mut Client, &RespawnPosition), Changed>, +) { + for (mut client, respawn_pos) in &mut clients { + client.write_packet(&PlayerSpawnPositionS2c { + position: respawn_pos.pos, + angle: respawn_pos.yaw, + }); } } @@ -48,6 +202,32 @@ fn handle_client_movement( )>, mut movement_events: EventWriter, ) { + fn handle( + mov: MovementEvent, + mut pos: Mut, + mut look: Mut, + mut head_yaw: Mut, + mut on_ground: Mut, + mut teleport_state: Mut, + movement_events: &mut EventWriter, + ) { + if teleport_state.pending_teleports() != 0 { + return; + } + + // TODO: check that the client isn't moving too fast / flying. + // TODO: check that the client isn't clipping through blocks. + + pos.set_if_neq(Position(mov.position)); + teleport_state.synced_pos = mov.position; + look.set_if_neq(mov.look); + teleport_state.synced_look = mov.look; + head_yaw.set_if_neq(HeadYaw(mov.look.yaw)); + on_ground.set_if_neq(OnGround(mov.on_ground)); + + movement_events.send(mov); + } + for packet in packets.iter() { if let Some(pkt) = packet.decode::() { if let Ok((pos, look, head_yaw, on_ground, teleport_state)) = @@ -181,29 +361,3 @@ fn handle_client_movement( } } } - -fn handle( - mov: MovementEvent, - mut pos: Mut, - mut look: Mut, - mut head_yaw: Mut, - mut on_ground: Mut, - mut teleport_state: Mut, - movement_events: &mut EventWriter, -) { - if teleport_state.pending_teleports() != 0 { - return; - } - - // TODO: check that the client isn't moving too fast / flying. - // TODO: check that the client isn't clipping through blocks. - - pos.set_if_neq(Position(mov.position)); - teleport_state.synced_pos = mov.position; - look.set_if_neq(mov.look); - teleport_state.synced_look = mov.look; - head_yaw.set_if_neq(HeadYaw(mov.look.yaw)); - on_ground.set_if_neq(OnGround(mov.on_ground)); - - movement_events.send(mov); -} diff --git a/crates/valence_server/src/spawn.rs b/crates/valence_server/src/spawn.rs index 627c2dc5c..e99069886 100644 --- a/crates/valence_server/src/spawn.rs +++ b/crates/valence_server/src/spawn.rs @@ -3,23 +3,73 @@ use std::borrow::Cow; use std::collections::BTreeSet; +use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_ecs::query::WorldQuery; use derive_more::{Deref, DerefMut}; use valence_entity::EntityLayerId; -use valence_protocol::packets::play::{GameJoinS2c, PlayerRespawnS2c, PlayerSpawnPositionS2c}; +use valence_protocol::packets::play::{GameJoinS2c, PlayerRespawnS2c}; use valence_protocol::{BlockPos, GameMode, GlobalPos, Ident, VarInt, WritePacket}; use valence_registry::tags::TagsRegistry; -use valence_registry::{BiomeRegistry, RegistryCodec}; +use valence_registry::{DimensionTypeRegistry, RegistryCodec, UpdateRegistrySet}; + +use crate::client::{Client, FlushPacketsSet, ViewDistance, VisibleChunkLayer}; +use crate::dimension_layer::{ChunkIndex, UpdateDimensionLayerSet}; + +/// Handles spawning and respawning of clients. +pub struct SpawnPlugin; + +/// When clients are sent the "respawn" packet after their dimension layer has +/// changed. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct RespawnSystemSet; + +impl Plugin for SpawnPlugin { + fn build(&self, app: &mut App) { + app + // Send the respawn packet before chunks are sent. + .configure_set(PostUpdate, RespawnSystemSet.before(UpdateDimensionLayerSet)) + .add_systems(PostUpdate, respawn.in_set(RespawnSystemSet)) + // The join game packet is prepended to the client's packet buffer, so + // it can be sent any time before packets are flushed. Additionally, + // this must be scheduled after registries are updated because we read + // the cached packets. + .add_systems(PostUpdate, initial_join.after(UpdateRegistrySet).before(FlushPacketsSet)); + } +} -use crate::client::{Client, ViewDistance, VisibleChunkLayer}; -use crate::layer::ChunkLayer; +/// A convenient [`WorldQuery`] for obtaining client spawn components. Also see +/// [`ClientSpawnQueryReadOnly`]. +#[derive(WorldQuery)] +#[world_query(mutable)] +pub struct ClientSpawnQuery { + pub is_hardcore: &'static mut IsHardcore, + pub game_mode: &'static mut GameMode, + pub prev_game_mode: &'static mut PrevGameMode, + pub hashed_seed: &'static mut HashedSeed, + pub view_distance: &'static mut ViewDistance, + pub reduced_debug_info: &'static mut ReducedDebugInfo, + pub has_respawn_screen: &'static mut HasRespawnScreen, + pub is_debug: &'static mut IsDebug, + pub is_flat: &'static mut IsFlat, + pub death_loc: &'static mut DeathLocation, + pub portal_cooldown: &'static mut PortalCooldown, +} // Components for the join game and respawn packet. -#[derive(Component, Clone, PartialEq, Eq, Default, Debug)] +#[derive(Component, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)] pub struct DeathLocation(pub Option<(Ident, BlockPos)>); +impl DeathLocation { + pub fn as_global_pos(&self) -> Option { + self.0.as_ref().map(|(name, pos)| GlobalPos { + dimension_name: name.as_str_ident().into(), + position: *pos, + }) + } +} + #[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)] pub struct IsHardcore(pub bool); @@ -33,6 +83,12 @@ pub struct ReducedDebugInfo(pub bool); #[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref, DerefMut)] pub struct HasRespawnScreen(pub bool); +impl Default for HasRespawnScreen { + fn default() -> Self { + Self(true) + } +} + /// If the client is spawning into a debug world. #[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)] pub struct IsDebug(pub bool); @@ -48,64 +104,25 @@ pub struct PortalCooldown(pub i32); #[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)] pub struct PrevGameMode(pub Option); -impl Default for HasRespawnScreen { - fn default() -> Self { - Self(true) - } -} - -/// The position and angle that clients will respawn with. Also -/// controls the position that compasses point towards. -#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] -pub struct RespawnPosition { - /// The position that clients will respawn at. This can be changed at any - /// time to set the position that compasses point towards. - pub pos: BlockPos, - /// The yaw angle that clients will respawn with (in degrees). - pub yaw: f32, -} - -/// A convenient [`WorldQuery`] for obtaining client spawn components. Also see -/// [`ClientSpawnQueryReadOnly`]. -#[derive(WorldQuery)] -#[world_query(mutable)] -pub struct ClientSpawnQuery { - pub is_hardcore: &'static mut IsHardcore, - pub game_mode: &'static mut GameMode, - pub prev_game_mode: &'static mut PrevGameMode, - pub hashed_seed: &'static mut HashedSeed, - pub view_distance: &'static mut ViewDistance, - pub reduced_debug_info: &'static mut ReducedDebugInfo, - pub has_respawn_screen: &'static mut HasRespawnScreen, - pub is_debug: &'static mut IsDebug, - pub is_flat: &'static mut IsFlat, - pub death_loc: &'static mut DeathLocation, - pub portal_cooldown: &'static mut PortalCooldown, -} - pub(super) fn initial_join( + mut clients: Query<(&mut Client, &VisibleChunkLayer, ClientSpawnQueryReadOnly), Added>, codec: Res, tags: Res, - mut clients: Query<(&mut Client, &VisibleChunkLayer, ClientSpawnQueryReadOnly), Added>, - chunk_layers: Query<&ChunkLayer>, + dimensions: Res, + dimension_layers: Query<&ChunkIndex>, ) { for (mut client, visible_chunk_layer, spawn) in &mut clients { - let Ok(chunk_layer) = chunk_layers.get(visible_chunk_layer.0) else { + let Ok(chunk_index) = dimension_layers.get(visible_chunk_layer.0) else { continue; }; let dimension_names: BTreeSet>> = codec - .registry(BiomeRegistry::KEY) + .registry(DimensionTypeRegistry::KEY) .iter() .map(|value| value.name.as_str_ident().into()) .collect(); - let dimension_name: Ident> = chunk_layer.dimension_type_name().into(); - - let last_death_location = spawn.death_loc.0.as_ref().map(|(id, pos)| GlobalPos { - dimension_name: id.as_str_ident().into(), - position: *pos, - }); + let dimension_name = dimensions.by_index(chunk_index.dimension_type()).0; // The login packet is prepended so that it's sent before all the other packets. // Some packets don't work correctly when sent before the game join packet. @@ -116,17 +133,17 @@ pub(super) fn initial_join( previous_game_mode: spawn.prev_game_mode.0.into(), dimension_names: Cow::Owned(dimension_names), registry_codec: Cow::Borrowed(codec.cached_codec()), - dimension_type_name: dimension_name.clone(), - dimension_name, + dimension_type_name: dimension_name.into(), + dimension_name: dimension_name.into(), hashed_seed: spawn.hashed_seed.0 as i64, max_players: VarInt(0), // Ignored by clients. view_distance: VarInt(spawn.view_distance.get() as i32), - simulation_distance: VarInt(16), // TODO. + simulation_distance: VarInt(16), // Ignored? reduced_debug_info: spawn.reduced_debug_info.0, enable_respawn_screen: spawn.has_respawn_screen.0, is_debug: spawn.is_debug.0, is_flat: spawn.is_flat.0, - last_death_location, + last_death_location: spawn.death_loc.as_global_pos(), portal_cooldown: VarInt(spawn.portal_cooldown.0), }); @@ -141,7 +158,7 @@ pub(super) fn initial_join( } } -pub(super) fn respawn( +fn respawn( mut clients: Query< ( &mut Client, @@ -155,7 +172,8 @@ pub(super) fn respawn( ), Changed, >, - chunk_layers: Query<&ChunkLayer>, + chunk_layers: Query<&ChunkIndex>, + dimensions: Res, ) { for (mut client, loc, death_loc, hashed_seed, game_mode, prev_game_mode, is_debug, is_flat) in &mut clients @@ -165,11 +183,11 @@ pub(super) fn respawn( continue; } - let Ok(chunk_layer) = chunk_layers.get(loc.0) else { + let Ok(chunk_index) = chunk_layers.get(loc.0) else { continue; }; - let dimension_name = chunk_layer.dimension_type_name(); + let dimension_name = dimensions.by_index(chunk_index.dimension_type()).0; let last_death_location = death_loc.0.as_ref().map(|(id, pos)| GlobalPos { dimension_name: id.as_str_ident().into(), @@ -190,18 +208,3 @@ pub(super) fn respawn( }); } } - -/// Sets the client's respawn and compass position. -/// -/// This also closes the "downloading terrain" screen when first joining, so -/// it should happen after the initial chunks are written. -pub(super) fn update_respawn_position( - mut clients: Query<(&mut Client, &RespawnPosition), Changed>, -) { - for (mut client, respawn_pos) in &mut clients { - client.write_packet(&PlayerSpawnPositionS2c { - position: respawn_pos.pos, - angle: respawn_pos.yaw, - }); - } -} diff --git a/crates/valence_server/src/teleport.rs b/crates/valence_server/src/teleport.rs deleted file mode 100644 index 28b5d837b..000000000 --- a/crates/valence_server/src/teleport.rs +++ /dev/null @@ -1,139 +0,0 @@ -use bevy_app::prelude::*; -use bevy_ecs::prelude::*; -use tracing::warn; -use valence_entity::{Look, Position}; -use valence_math::DVec3; -use valence_protocol::packets::play::player_position_look_s2c::PlayerPositionLookFlags; -use valence_protocol::packets::play::{PlayerPositionLookS2c, TeleportConfirmC2s}; -use valence_protocol::WritePacket; - -use crate::client::{update_view_and_layers, Client, UpdateClientsSet}; -use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; -use crate::spawn::update_respawn_position; - -pub struct TeleportPlugin; - -impl Plugin for TeleportPlugin { - fn build(&self, app: &mut App) { - app.add_systems( - PostUpdate, - teleport - .after(update_view_and_layers) - .before(update_respawn_position) - .in_set(UpdateClientsSet), - ) - .add_systems(EventLoopPreUpdate, handle_teleport_confirmations); - } -} - -#[derive(Component, Debug)] -pub struct TeleportState { - /// Counts up as teleports are made. - teleport_id_counter: u32, - /// The number of pending client teleports that have yet to receive a - /// confirmation. Inbound client position packets should be ignored while - /// this is nonzero. - pending_teleports: u32, - pub(super) synced_pos: DVec3, - pub(super) synced_look: Look, -} - -impl TeleportState { - pub(super) fn new() -> Self { - Self { - teleport_id_counter: 0, - pending_teleports: 0, - // Set initial synced pos and look to NaN so a teleport always happens when first - // joining. - synced_pos: DVec3::NAN, - synced_look: Look { - yaw: f32::NAN, - pitch: f32::NAN, - }, - } - } - - pub fn teleport_id_counter(&self) -> u32 { - self.teleport_id_counter - } - - pub fn pending_teleports(&self) -> u32 { - self.pending_teleports - } -} - -/// Syncs the client's position and look with the server. -/// -/// This should happen after chunks are loaded so the client doesn't fall though -/// the floor. -#[allow(clippy::type_complexity)] -fn teleport( - mut clients: Query< - (&mut Client, &mut TeleportState, &Position, &Look), - Or<(Changed, Changed)>, - >, -) { - for (mut client, mut state, pos, look) in &mut clients { - let changed_pos = pos.0 != state.synced_pos; - let changed_yaw = look.yaw != state.synced_look.yaw; - let changed_pitch = look.pitch != state.synced_look.pitch; - - if changed_pos || changed_yaw || changed_pitch { - state.synced_pos = pos.0; - state.synced_look = *look; - - let flags = PlayerPositionLookFlags::new() - .with_x(!changed_pos) - .with_y(!changed_pos) - .with_z(!changed_pos) - .with_y_rot(!changed_yaw) - .with_x_rot(!changed_pitch); - - client.write_packet(&PlayerPositionLookS2c { - position: if changed_pos { pos.0 } else { DVec3::ZERO }, - yaw: if changed_yaw { look.yaw } else { 0.0 }, - pitch: if changed_pitch { look.pitch } else { 0.0 }, - flags, - teleport_id: (state.teleport_id_counter as i32).into(), - }); - - state.pending_teleports = state.pending_teleports.wrapping_add(1); - state.teleport_id_counter = state.teleport_id_counter.wrapping_add(1); - } - } -} - -fn handle_teleport_confirmations( - mut packets: EventReader, - mut clients: Query<&mut TeleportState>, - mut commands: Commands, -) { - for packet in packets.iter() { - if let Some(pkt) = packet.decode::() { - if let Ok(mut state) = clients.get_mut(packet.client) { - if state.pending_teleports == 0 { - warn!( - "unexpected teleport confirmation from client {:?}", - packet.client - ); - commands.entity(packet.client).remove::(); - } - - let got = pkt.teleport_id.0 as u32; - let expected = state - .teleport_id_counter - .wrapping_sub(state.pending_teleports); - - if got == expected { - state.pending_teleports -= 1; - } else { - warn!( - "unexpected teleport ID for client {:?} (expected {expected}, got {got}", - packet.client - ); - commands.entity(packet.client).remove::(); - } - } - } - } -} diff --git a/crates/valence_server_common/src/entity_event.rs b/crates/valence_server_common/src/entity_event.rs new file mode 100644 index 000000000..e53846d53 --- /dev/null +++ b/crates/valence_server_common/src/entity_event.rs @@ -0,0 +1,20 @@ +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +use derive_more::{Deref, DerefMut}; + +pub trait AddEntityEvent { + fn add_entity_event(&mut self); +} + +impl AddEntityEvent for App { + fn add_entity_event(&mut self) { + self.add_systems(Last, clear_entity_events::); + } +} + +fn clear_entity_events(mut events: Query<&mut EntityEvents>) { + events.iter_mut().for_each(|mut e| e.clear()); +} + +#[derive(Component, Clone, DerefMut, Deref)] +pub struct EntityEvents(pub Vec); diff --git a/crates/valence_server_common/src/layer_id.rs b/crates/valence_server_common/src/layer_id.rs new file mode 100644 index 000000000..c555a8bb3 --- /dev/null +++ b/crates/valence_server_common/src/layer_id.rs @@ -0,0 +1,16 @@ +use bevy_ecs::prelude::*; + +#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] +pub struct LayerId(pub Entity); + +impl PartialEq for LayerId { + fn eq(&self, other: &Entity) -> bool { + self.0.eq(other) + } +} + +impl PartialEq for Entity { + fn eq(&self, other: &LayerId) -> bool { + self.eq(&other.0) + } +} diff --git a/crates/valence_server_common/src/lib.rs b/crates/valence_server_common/src/lib.rs index 06e105978..13ab212c8 100644 --- a/crates/valence_server_common/src/lib.rs +++ b/crates/valence_server_common/src/lib.rs @@ -19,6 +19,8 @@ #![allow(clippy::unusual_byte_groupings)] mod despawn; +pub mod entity_event; +mod layer_id; mod uuid; use std::num::NonZeroU32; @@ -31,7 +33,8 @@ pub use despawn::*; use valence_protocol::CompressionThreshold; use crate::despawn::despawn_marked_entities; -pub use crate::uuid::*; +pub use crate::layer_id::LayerId; +pub use crate::uuid::UniqueId; /// Minecraft's standard ticks per second (TPS). pub const DEFAULT_TPS: NonZeroU32 = match NonZeroU32::new(20) { diff --git a/src/lib.rs b/src/lib.rs index 6f59d6d8e..7d0867656 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,13 +67,13 @@ use valence_server::interact_block::InteractBlockPlugin; use valence_server::interact_entity::InteractEntityPlugin; use valence_server::interact_item::InteractItemPlugin; use valence_server::keepalive::KeepalivePlugin; -use valence_server::layer::LayerPlugin; +use valence_server::layer_old::LayerPlugin; use valence_server::message::MessagePlugin; use valence_server::movement::MovementPlugin; use valence_server::op_level::OpLevelPlugin; +use valence_server::position::TeleportPlugin; use valence_server::resource_pack::ResourcePackPlugin; use valence_server::status::StatusPlugin; -use valence_server::teleport::TeleportPlugin; pub use valence_server::*; #[cfg(feature = "weather")] pub use valence_weather as weather; @@ -134,10 +134,10 @@ pub mod prelude { }; pub use valence_server::ident::Ident; pub use valence_server::interact_entity::{EntityInteraction, InteractEntityEvent}; - pub use valence_server::layer::chunk::{ - Block, BlockRef, ChunkOps, ChunkLayer, LoadedChunk, Chunk, + pub use valence_server::layer_old::chunk::{ + Block, BlockRef, Chunk, ChunkLayer, ChunkOps, LoadedChunk, }; - pub use valence_server::layer::{EntityLayer, LayerBundle}; + pub use valence_server::layer_old::{EntityLayer, LayerBundle}; pub use valence_server::math::{DVec2, DVec3, Vec2, Vec3}; pub use valence_server::message::SendMessage as _; pub use valence_server::nbt::Compound; From 830d6fb76a7aee546273430e696603fb63888798 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 13 Sep 2023 19:48:49 -0700 Subject: [PATCH 04/16] Get valence_server compiling --- crates/valence_server/src/client.rs | 109 ++--- crates/valence_server/src/dimension_layer.rs | 58 ++- .../src/dimension_layer/batch.rs | 438 ++++++------------ .../src/dimension_layer/index.rs | 59 +-- .../src/dimension_layer/plugin.rs | 57 +-- crates/valence_server/src/keepalive.rs | 26 +- crates/valence_server/src/layer.rs | 9 +- crates/valence_server/src/layer/action_buf.rs | 97 ---- .../src/layer/chunk_view_index.rs | 5 +- crates/valence_server/src/layer/packet_buf.rs | 60 --- crates/valence_server/src/lib.rs | 2 +- crates/valence_server/src/movement.rs | 2 +- crates/valence_server/src/spawn.rs | 14 +- 13 files changed, 291 insertions(+), 645 deletions(-) delete mode 100644 crates/valence_server/src/layer/action_buf.rs delete mode 100644 crates/valence_server/src/layer/packet_buf.rs diff --git a/crates/valence_server/src/client.rs b/crates/valence_server/src/client.rs index 4c9fe461e..1645437d2 100644 --- a/crates/valence_server/src/client.rs +++ b/crates/valence_server/src/client.rs @@ -8,36 +8,28 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_ecs::query::WorldQuery; use bevy_ecs::system::Command; -use byteorder::{NativeEndian, ReadBytesExt}; use bytes::{Bytes, BytesMut}; use derive_more::{Deref, DerefMut, From, Into}; use tracing::warn; use uuid::Uuid; use valence_entity::player::PlayerEntityBundle; -use valence_entity::query::EntityInitQuery; use valence_entity::tracked_data::TrackedData; -use valence_entity::{ - ClearEntityChangesSet, EntityId, EntityStatus, OldPosition, Position, Velocity, -}; +use valence_entity::{EntityStatus, OldPosition, Position, Velocity}; use valence_math::{DVec3, Vec3}; use valence_protocol::encode::{PacketEncoder, WritePacket}; -use valence_protocol::packets::play::chunk_biome_data_s2c::ChunkBiome; use valence_protocol::packets::play::game_state_change_s2c::GameEventKind; use valence_protocol::packets::play::particle_s2c::Particle; use valence_protocol::packets::play::{ - ChunkBiomeDataS2c, ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, DeathMessageS2c, - DisconnectS2c, EntitiesDestroyS2c, EntityStatusS2c, EntityTrackerUpdateS2c, - EntityVelocityUpdateS2c, GameStateChangeS2c, ParticleS2c, PlaySoundS2c, UnloadChunkS2c, + ChunkLoadDistanceS2c, DeathMessageS2c, DisconnectS2c, EntitiesDestroyS2c, EntityStatusS2c, + EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, GameStateChangeS2c, ParticleS2c, PlaySoundS2c, }; use valence_protocol::profile::Property; use valence_protocol::sound::{Sound, SoundCategory, SoundId}; use valence_protocol::text::{IntoText, Text}; use valence_protocol::var_int::VarInt; -use valence_protocol::{BlockPos, Encode, GameMode, Packet}; -use valence_registry::UpdateRegistrySet; +use valence_protocol::{Encode, GameMode, Packet}; use valence_server_common::{Despawned, UniqueId}; -use crate::layer::{ChunkLayer, EntityLayer, UpdateLayersPostClientSet, UpdateLayersPreClientSet}; use crate::ChunkView; pub struct ClientPlugin; @@ -61,40 +53,45 @@ pub struct UpdateClientsSet; impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { - app.add_systems( - PostUpdate, - ( - ( - crate::spawn::initial_join.after(UpdateRegistrySet), - update_chunk_load_dist, - handle_layer_messages.after(update_chunk_load_dist), - update_view_and_layers - .after(crate::spawn::initial_join) - .after(handle_layer_messages), - cleanup_chunks_after_client_despawn.after(update_view_and_layers), - crate::spawn::update_respawn_position.after(update_view_and_layers), - crate::spawn::respawn.after(crate::spawn::update_respawn_position), - update_old_view_dist.after(update_view_and_layers), - update_game_mode, - update_tracked_data, - init_tracked_data, - ) - .in_set(UpdateClientsSet), - flush_packets.in_set(FlushPacketsSet), - ), - ) - .configure_set(PreUpdate, SpawnClientsSet) - .configure_sets( - PostUpdate, - ( - UpdateClientsSet - .after(UpdateLayersPreClientSet) - .before(UpdateLayersPostClientSet) - .before(FlushPacketsSet), - ClearEntityChangesSet.after(UpdateClientsSet), - FlushPacketsSet, - ), - ); + todo!() + + // app.add_systems( + // PostUpdate, + // ( + // ( + // crate::spawn::initial_join.after(UpdateRegistrySet), + // update_chunk_load_dist, + // handle_layer_messages.after(update_chunk_load_dist), + // update_view_and_layers + // .after(crate::spawn::initial_join) + // .after(handle_layer_messages), + // + // cleanup_chunks_after_client_despawn.after(update_view_and_layers), + // + // crate::spawn::update_respawn_position.after(update_view_and_layers), + // + // crate::spawn::respawn.after(crate::spawn::update_respawn_position), + // update_old_view_dist.after(update_view_and_layers), + // update_game_mode, + // update_tracked_data, + // init_tracked_data, + // ) + // .in_set(UpdateClientsSet), + // flush_packets.in_set(FlushPacketsSet), + // ), + // ) + // .configure_set(PreUpdate, SpawnClientsSet) + // .configure_sets( + // PostUpdate, + // ( + // UpdateClientsSet + // .after(UpdateLayersPreClientSet) + // .before(UpdateLayersPostClientSet) + // .before(FlushPacketsSet), + // ClearEntityChangesSet.after(UpdateClientsSet), + // FlushPacketsSet, + // ), + // ); } } @@ -109,7 +106,7 @@ pub struct ClientBundle { pub username: Username, pub ip: Ip, pub properties: Properties, - pub respawn_pos: crate::spawn::RespawnPosition, + pub respawn_pos: crate::movement::RespawnPosition, pub op_level: crate::op_level::OpLevel, pub action_sequence: crate::action::ActionSequence, pub view_distance: ViewDistance, @@ -120,7 +117,7 @@ pub struct ClientBundle { pub old_visible_entity_layers: OldVisibleEntityLayers, pub keepalive_state: crate::keepalive::KeepaliveState, pub ping: crate::keepalive::Ping, - pub teleport_state: crate::position::TeleportState, + pub teleport_state: crate::movement::TeleportState, pub game_mode: GameMode, pub prev_game_mode: crate::spawn::PrevGameMode, pub death_location: crate::spawn::DeathLocation, @@ -156,12 +153,12 @@ impl ClientBundle { view_distance: Default::default(), old_view_distance: OldViewDistance(2), visible_chunk_layer: Default::default(), - old_visible_chunk_layer: OldVisibleChunkLayer(Entity::PLACEHOLDER), + old_visible_chunk_layer: OldVisibleChunkLayer::default(), visible_entity_layers: Default::default(), - old_visible_entity_layers: OldVisibleEntityLayers(BTreeSet::new()), - keepalive_state: crate::keepalive::KeepaliveState::new(), + old_visible_entity_layers: OldVisibleEntityLayers::default(), + keepalive_state: crate::keepalive::KeepaliveState::default(), ping: Default::default(), - teleport_state: crate::position::TeleportState::new(), + teleport_state: crate::movement::TeleportState::default(), game_mode: GameMode::default(), prev_game_mode: Default::default(), death_location: Default::default(), @@ -530,6 +527,12 @@ impl Default for VisibleChunkLayer { #[derive(Component, PartialEq, Eq, Debug, Deref)] pub struct OldVisibleChunkLayer(Entity); +impl Default for OldVisibleChunkLayer { + fn default() -> Self { + Self(Entity::PLACEHOLDER) + } +} + impl OldVisibleChunkLayer { pub fn get(&self) -> Entity { self.0 @@ -1116,6 +1119,7 @@ fn update_tracked_data(mut clients: Query<(&mut Client, &TrackedData)>) { } } +/* /// Decrement viewer count of chunks when the client is despawned. fn cleanup_chunks_after_client_despawn( mut clients: Query<(View, &VisibleChunkLayer), (With, With)>, @@ -1131,3 +1135,4 @@ fn cleanup_chunks_after_client_despawn( } } } +*/ diff --git a/crates/valence_server/src/dimension_layer.rs b/crates/valence_server/src/dimension_layer.rs index 4e1f80e79..d59b8743c 100644 --- a/crates/valence_server/src/dimension_layer.rs +++ b/crates/valence_server/src/dimension_layer.rs @@ -2,19 +2,19 @@ pub mod batch; pub mod block; pub mod chunk; pub mod index; -pub mod plugin; +mod plugin; -use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_ecs::query::WorldQuery; use block::BlockRef; use chunk::LoadedChunk; pub use index::ChunkIndex; +pub use plugin::*; use valence_protocol::packets::play::UnloadChunkS2c; use valence_protocol::{BiomePos, BlockPos, ChunkPos, CompressionThreshold, WritePacket}; use valence_registry::biome::BiomeId; use valence_registry::dimension_type::DimensionTypeId; -use valence_registry::DimensionTypeRegistry; +use valence_registry::{BiomeRegistry, DimensionTypeRegistry}; use valence_server_common::Server; use self::batch::BlockBatch; @@ -27,6 +27,7 @@ use crate::layer::{ChunkViewIndex, LayerViewers}; pub struct DimensionLayerBundle { pub chunk_index: ChunkIndex, pub block_batch: BlockBatch, + pub dimension_info: DimensionInfo, pub chunk_view_index: ChunkViewIndex, pub layer_viewers: LayerViewers, pub layer_messages: LayerMessages, @@ -36,13 +37,21 @@ impl DimensionLayerBundle { pub fn new( dimension_type: DimensionTypeId, dimensions: &DimensionTypeRegistry, + biomes: &BiomeRegistry, server: &Server, ) -> Self { let dim = &dimensions[dimension_type]; Self { - chunk_index: ChunkIndex::new(dimension_type, dimensions, server), + chunk_index: ChunkIndex::new(dim.height), block_batch: Default::default(), + dimension_info: DimensionInfo { + dimension_type, + height: dim.height, + min_y: dim.min_y, + biome_registry_len: biomes.iter().len() as i32, + threshold: server.compression_threshold(), + }, chunk_view_index: Default::default(), layer_viewers: Default::default(), layer_messages: LayerMessages::new(server.compression_threshold()), @@ -55,6 +64,7 @@ impl DimensionLayerBundle { pub struct DimensionLayerQuery { pub chunk_index: &'static mut ChunkIndex, pub block_batch: &'static mut BlockBatch, + pub dimension_info: &'static DimensionInfo, pub chunk_view_index: &'static mut ChunkViewIndex, pub layer_viewers: &'static LayerViewers, pub layer_messages: &'static mut LayerMessages, @@ -63,15 +73,15 @@ pub struct DimensionLayerQuery { macro_rules! immutable_query_methods { () => { pub fn dimension_type(&self) -> DimensionTypeId { - self.chunk_index.dimension_type() + self.dimension_info.dimension_type } pub fn height(&self) -> i32 { - self.chunk_index.height() + self.dimension_info.height } pub fn min_y(&self) -> i32 { - self.chunk_index.height() + self.dimension_info.min_y } pub fn biome(&self, pos: impl Into) -> Option { @@ -123,15 +133,15 @@ impl DimensionLayerQueryItem<'_> { pub fn chunk_entry(&mut self, pos: impl Into) -> Entry { match self.chunk_index.entry(pos) { index::Entry::Occupied(entry) => Entry::Occupied(OccupiedEntry { - chunk_index: self.chunk_index, chunk_view_index: &*self.chunk_view_index, - layer_messages: self.layer_messages, + layer_messages: self.layer_messages.reborrow(), + info: &self.dimension_info, entry, }), index::Entry::Vacant(entry) => Entry::Vacant(VacantEntry { - chunk_index: self.chunk_index, chunk_view_index: &*self.chunk_view_index, - layer_messages: self.layer_messages, + layer_messages: self.layer_messages.reborrow(), + info: &self.dimension_info, entry, }), } @@ -142,7 +152,8 @@ impl DimensionLayerQueryReadOnlyItem<'_> { immutable_query_methods!(); } -struct DimensionInfo { +#[derive(Component, Clone, Debug)] +pub struct DimensionInfo { dimension_type: DimensionTypeId, height: i32, min_y: i32, @@ -150,6 +161,20 @@ struct DimensionInfo { threshold: CompressionThreshold, } +impl DimensionInfo { + pub fn dimension_type(&self) -> DimensionTypeId { + self.dimension_type + } + + pub fn height(&self) -> i32 { + self.height + } + + pub fn min_y(&self) -> i32 { + self.height + } +} + #[derive(Debug)] pub enum Entry<'a> { Occupied(OccupiedEntry<'a>), @@ -167,9 +192,9 @@ impl<'a> Entry<'a> { #[derive(Debug)] pub struct OccupiedEntry<'a> { - chunk_index: Mut<'a, ChunkIndex>, chunk_view_index: &'a ChunkViewIndex, layer_messages: Mut<'a, LayerMessages>, + info: &'a DimensionInfo, entry: index::OccupiedEntry<'a>, } @@ -186,7 +211,6 @@ impl<'a> OccupiedEntry<'a> { let pos = *self.key(); let viewer_count = self.entry.get().viewer_count; - let res = self.entry.insert(chunk); if viewer_count > 0 { @@ -196,7 +220,7 @@ impl<'a> OccupiedEntry<'a> { .layer_messages .packet_writer(MessageScope::ChunkView { pos }); - loaded.write_chunk_init_packet(w, pos, &self.chunk_index.info); + loaded.write_chunk_init_packet(w, pos, self.info); loaded.viewer_count = viewer_count; } @@ -227,9 +251,9 @@ impl<'a> OccupiedEntry<'a> { #[derive(Debug)] pub struct VacantEntry<'a> { - chunk_index: Mut<'a, ChunkIndex>, chunk_view_index: &'a ChunkViewIndex, layer_messages: Mut<'a, LayerMessages>, + info: &'a DimensionInfo, entry: index::VacantEntry<'a>, } @@ -246,7 +270,7 @@ impl<'a> VacantEntry<'a> { .layer_messages .packet_writer(MessageScope::ChunkView { pos }); - loaded.write_chunk_init_packet(w, pos, &self.chunk_index.info); + loaded.write_chunk_init_packet(w, pos, self.info); loaded.viewer_count = viewer_count as u32; } diff --git a/crates/valence_server/src/dimension_layer/batch.rs b/crates/valence_server/src/dimension_layer/batch.rs index 4c40f309d..ffab1752b 100644 --- a/crates/valence_server/src/dimension_layer/batch.rs +++ b/crates/valence_server/src/dimension_layer/batch.rs @@ -1,215 +1,85 @@ +use std::borrow::Cow; + use bevy_ecs::prelude::*; use bitfield_struct::bitfield; use valence_generated::block::BlockEntityKind; use valence_nbt::Compound; -use valence_protocol::encode::PacketWriter; use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; use valence_protocol::packets::play::{BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDeltaUpdateS2c}; -use valence_protocol::{BlockPos, ChunkPos, ChunkSectionPos, WritePacket}; +use valence_protocol::{BlockPos, ChunkSectionPos, WritePacket}; use super::block::Block; -use super::ChunkIndex; -use crate::layer::{LayerViewers, PacketBuf}; -use crate::layer_old::chunk::LocalMsg; -use crate::{BlockState, ChunkLayer, Client, Layer}; +use super::chunk::LoadedChunk; +use super::{ChunkIndex, DimensionInfo}; +use crate::dimension_layer::chunk::ChunkOps; +use crate::layer::message::{LayerMessages, MessageScope}; +use crate::BlockState; #[derive(Component, Default)] pub struct BlockBatch { - updates: Vec, + state_updates: Vec, block_entities: Vec<(BlockPos, BlockEntityKind, Compound)>, + sect_update_buf: Vec, } impl BlockBatch { - pub fn set_block(&mut self, pos: impl Into, block: impl Into) { + pub fn set_block(&mut self, pos: impl Into, block: impl Into) { let pos = pos.into(); let block = block.into(); - self.updates.push(BlockUpdate::from_parts(pos, block.state)); + self.state_updates + .push(StateUpdate::from_parts(pos, block.state)); - if let Some(nbt) = block.nbt { - self.block_entities - .push((pos, block.state.block_entity_kind(), nbt)); + if let (Some(nbt), Some(kind)) = (block.nbt, block.state.block_entity_kind()) { + self.block_entities.push((pos, kind, nbt)); } } pub fn clear(&mut self) { - self.updates.clear(); + self.state_updates.clear(); self.block_entities.clear(); } pub fn shrink_to_fit(&mut self) { - self.updates.shrink_to_fit(); + self.state_updates.shrink_to_fit(); self.block_entities.shrink_to_fit(); } + pub fn is_empty(&self) -> bool { + self.state_updates.is_empty() + } + pub(super) fn apply( &mut self, chunks: &mut ChunkIndex, - viewers: &LayerViewers, - clients: &mut Query<&mut Client>, - buf: &mut PacketBuf, + info: &DimensionInfo, + messages: &mut LayerMessages, ) { - todo!(); - - buf.broadcast(clients); - } -} - -#[bitfield(u128, order = Msb)] -#[derive(PartialEq, Eq, PartialOrd, Ord)] -struct BlockUpdate { - // Section coordinate. - #[bits(28)] - section_x: i32, - #[bits(28)] - section_z: i32, - #[bits(28)] - section_y: i32, - // Coordinate within the section. - #[bits(4)] - off_x: u32, - #[bits(4)] - off_z: u32, - #[bits(4)] - off_y: u32, - /// Bits of the [`BlockState`]. - state: u32, -} - -impl BlockUpdate { - const CHUNK_POS_MASK: u128 = u128::MAX << 72; - const SECTION_POS_MASK: u128 = u128::MAX << 44; - const BLOCK_POS_MASK: u128 = u128::MAX << 32; - - fn from_parts(pos: BlockPos, state: BlockState) { - Self::new() - .with_section_x(pos.x.div_euclid(16)) - .with_section_y(pos.y.div_euclid(16)) - .with_section_z(pos.z.div_euclid(16)) - .with_off_x(pos.x.rem_euclid(16) as u32) - .with_off_y(pos.y.rem_euclid(16) as u32) - .with_off_z(pos.z.rem_euclid(16) as u32) - .with_state(state.to_raw() as u32) - } - - fn to_parts(self) -> (BlockPos, BlockState) { - ( - BlockPos { - x: self.section_x() * 16 + self.off_x() as i32, - y: self.section_y() * 16 + self.off_y() as i32, - z: self.section_z() * 16 + self.off_z() as i32, - }, - BlockState::from_raw(self.state()).unwrap(), - ) - } - - // fn from_block_state(pos: BlockPos, state: BlockState) -> Self { - // Self::new() - // .with_section_x(pos.x.div_euclid(16)) - // .with_section_y(pos.y.div_euclid(16)) - // .with_section_z(pos.z.div_euclid(16)) - // .with_off_x(pos.x.rem_euclid(16) as u32) - // .with_off_y(pos.y.rem_euclid(16) as u32) - // .with_off_z(pos.z.rem_euclid(16) as u32) - // .with_state(state.to_raw() as u32) - // } - - // fn from_index(pos: BlockPos, idx: u32) -> Self { - // Self::new() - // .with_section_x(pos.x.div_euclid(16)) - // .with_section_y(pos.y.div_euclid(16)) - // .with_section_z(pos.z.div_euclid(16)) - // .with_off_x(pos.x.rem_euclid(16) as u32) - // .with_off_y(pos.y.rem_euclid(16) as u32) - // .with_off_z(pos.z.rem_euclid(16) as u32) - // .with_is_index(true) - // .with_state(idx) - // } - - // fn to_parts(self) -> (BlockPos, BlockState) { - // (self.block_pos(), self.block()) - // } - - // fn block_pos(self) -> BlockPos { - // BlockPos { - // x: self.section_x() * 16 + self.off_x() as i32, - // y: self.section_y() * 16 + self.off_y() as i32, - // z: self.section_z() * 16 + self.off_z() as i32, - // } - // } - - // fn chunk_pos(self) -> ChunkPos { - // ChunkPos { - // x: self.section_x(), - // z: self.section_z(), - // } - // } - - // fn block(self) -> BlockState { - // BlockState::from_raw(self.state() as u16).unwrap() - // } -} - -/* -impl ChunkLayer { - pub fn set_block( - &mut self, - pos: impl Into, - block: impl Into, - ) -> Option { - let pos = pos.into(); - let block: Block = block.into(); - - let chunk = self.chunk_mut(pos)?; - - let [x, y, z] = block_offsets(pos, self.info.min_y, self.info.height as i32)?; - - self.block_updates - .updates - .push(BlockUpdate::from_parts(pos, block.state)); - - if let (Some(data), Some(kind)) = (block.nbt, block.state.to_kind()) { - PacketWriter::new( - &mut self.block_updates.block_entity_buf, - self.info.threshold, - ) - .write_packet(&BlockEntityUpdateS2c { - position: pos, - kind, - data: Cow::Borrowed(&data), - }); - } - - Some(chunk.set_block(x, y, z, block)) - } + debug_assert!(self.sect_update_buf.is_empty()); - pub fn flush_block_updates(&mut self) { + // Sort block state updates so that they're grouped by chunk section. // Sort in reverse so the dedup keeps the last of consecutive elements. - self.block_updates.updates.sort_by(|a, b| b.cmp(a)); + self.state_updates.sort_unstable_by(|a, b| b.cmp(a)); // Eliminate redundant block assignments. - self.block_updates - .updates - .dedup_by_key(|u| u.0 & BlockUpdate::BLOCK_POS_MASK); - - let sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); - - for update in self.block_updates.updates.drain(..) { - let (pos, state) = update.to_parts(); + self.state_updates + .dedup_by_key(|u| u.0 & StateUpdate::BLOCK_POS_MASK); - let new_sect_pos = ChunkSectionPos::from(pos); + let mut chunk: Option<&mut LoadedChunk> = None; + let mut sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); - if sect_pos != new_sect_pos { - let msg = LocalMsg::PacketAt { + let mut flush_sect_updates = + |sect_pos: ChunkSectionPos, buf: &mut Vec| { + let mut w = messages.packet_writer(MessageScope::ChunkView { pos: sect_pos.into(), - }; + }); - match self.block_updates.entry_buf.as_slice() { + match buf.as_slice() { // Zero updates. Do nothing. &[] => {} // One update. Send singular block update packet. - &[update] => self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { + &[update] => { + w.write_packet(&BlockUpdateS2c { position: BlockPos { x: sect_pos.x * 16 + update.off_x() as i32, y: sect_pos.y * 16 + update.off_y() as i32, @@ -217,186 +87,144 @@ impl ChunkLayer { }, block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), }); - }), + + buf.clear(); + } // >1 updates. Send special section update packet. updates => { - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet( - &ChunkDeltaUpdateS2c { - chunk_section_pos: sect_pos, - blocks: Cow::Borrowed(updates), - }, - ) + w.write_packet(&ChunkDeltaUpdateS2c { + chunk_sect_pos: sect_pos, + blocks: Cow::Borrowed(updates), }); + + buf.clear(); } } + }; - self.block_updates.entry_buf.clear(); - } - } - - /* - let mut chunk: Option<&mut LoadedChunk> = None; - let mut sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); - for update in self.block_updates.updates.drain(..) { - let (pos, state, has_nbt) = update.to_parts(); + // For each block state change... + for (pos, state) in self.state_updates.drain(..).map(StateUpdate::to_parts) { let new_sect_pos = ChunkSectionPos::from(pos); // Is this block in a new section? If it is, then flush the changes we've // accumulated for the old section. if sect_pos != new_sect_pos { - let msg = LocalMsg::PacketAt { - pos: sect_pos.into(), - }; - - match self.block_updates.entry_buf.as_slice() { - // Zero updates. Do nothing. - &[] => {} - // One update. Send singular block update packet. - &[update] => self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { - position: BlockPos { - x: sect_pos.x * 16 + update.off_x() as i32, - y: sect_pos.y * 16 + update.off_y() as i32, - z: sect_pos.z * 16 + update.off_z() as i32, - }, - block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), - }); - }), - // >1 updates. Send special section update packet. - updates => { - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet( - &ChunkDeltaUpdateS2c { - chunk_section_pos: sect_pos, - blocks: Cow::Borrowed(updates), - }, - ) - }); - } - } - - self.block_updates.entry_buf.clear(); - - // Send the block entity update packets. - // It's important that this is ordered after block state updates are sent. - for (pos, kind, nbt) in nbt.take(nbt_count) { - let msg = LocalMsg::PacketAt { - pos: sect_pos.into(), - }; - - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet( - &BlockEntityUpdateS2c { - position: pos, - kind, - data: Cow::Owned(nbt), - }, - ) - }); - } + flush_sect_updates(sect_pos, &mut self.sect_update_buf); // Update the chunk ref if the chunk pos changed. if sect_pos.x != new_sect_pos.x || sect_pos.z != new_sect_pos.z { - chunk = self.chunks.get_mut(&ChunkPos::from(new_sect_pos)); + chunk = chunks.get_mut(new_sect_pos); } - // Update section pos. + // Update section pos sect_pos = new_sect_pos; } - // // Send block entity updates for the current block. - // if has_nbt { - // let nbt = nbt.next().unwrap(); - - // if let Some(kind) = state.to_kind() { - // let msg = LocalMsg::PacketAt { - // pos: sect_pos.into(), - // }; - - // self.messages.send_local_infallible(msg, |w| { - // PacketWriter::new(w, self.info.threshold).write_packet( - // &BlockEntityUpdateS2c { - // position: pos, - // kind, - // data: Cow::Owned(nbt), - // }, - // ) - // }); - // } - // } - + // Apply block state update to chunk. if let Some(chunk) = &mut chunk { - let chunk_y = pos.y.wrapping_sub(self.info.min_y) as u32; + let chunk_y = pos.y.wrapping_sub(info.min_y) as u32; // Is the block pos in bounds of the chunk? - if chunk_y < self.info.height { + if chunk_y < info.height as u32 { let chunk_x = pos.x.rem_euclid(16); let chunk_z = pos.z.rem_euclid(16); - // Make change to the chunk and push section update. + // Note that we're using `set_block` and not `set_block_state`. + let old_state = chunk + .set_block(chunk_x as u32, chunk_y, chunk_z as u32, state) + .state; - if chunk.viewer_count_mut() > 0 { - self.block_update_buf.push( + // Was the change observable? + if old_state != state && chunk.viewer_count > 0 { + self.sect_update_buf.push( ChunkDeltaUpdateEntry::new() .with_off_x(chunk_x as u8) .with_off_y((chunk_y % 16) as u8) .with_off_z(chunk_z as u8) - .with_block_state(block.state.to_raw() as u32), + .with_block_state(state.to_raw() as u32), ); } - - chunk.set_block(chunk_x as u32, chunk_y, chunk_z as u32, block); } } } - // Flush any remaining block changes. - - let msg = LocalMsg::PacketAt { - pos: sect_pos.into(), - }; - - match self.block_update_buf.as_slice() { - // Zero updates. Do nothing. - &[] => {} - // One update. Send singular block update packet. - &[update] => self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { - position: BlockPos { - x: sect_pos.x * 16 + update.off_x() as i32, - y: sect_pos.y * 16 + update.off_y() as i32, - z: sect_pos.z * 16 + update.off_z() as i32, - }, - block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), - }); - }), - // >1 updates. Send special section update packet. - updates => { - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&ChunkDeltaUpdateS2c { - chunk_section_pos: sect_pos, - blocks: Cow::Borrowed(updates), - }) - }); + // Flush remaining block state changes. + flush_sect_updates(sect_pos, &mut self.sect_update_buf); + + // Send block entity updates. This needs to happen after block states are set. + for (pos, kind, nbt) in self.block_entities.drain(..) { + let min_y = info.min_y; + let height = info.height; + + if let Some(chunk) = chunks.get_mut(pos) { + let chunk_y = pos.y.wrapping_sub(min_y) as u32; + + // Is the block pos in bounds of the chunk? + if chunk_y < height as u32 { + let chunk_x = pos.x.rem_euclid(16); + let chunk_z = pos.z.rem_euclid(16); + + let mut w = messages.packet_writer(MessageScope::ChunkView { pos: pos.into() }); + w.write_packet(&BlockEntityUpdateS2c { + position: pos, + kind, + data: Cow::Borrowed(&nbt), + }); + + chunk.set_block_entity(chunk_x as u32, chunk_y, chunk_z as u32, Some(nbt)); + } } } - - self.block_update_buf.clear();*/ } } -pub(super) struct BlockUpdates { - updates: Vec, - block_entities: Vec<(BlockPos, BlockEntityKind, Compound)>, - entry_buf: Vec, +#[bitfield(u128, order = Msb)] +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct StateUpdate { + // Section coordinate. + #[bits(28)] + section_x: i32, + #[bits(28)] + section_z: i32, + #[bits(28)] + section_y: i32, + // Coordinate within the section. + #[bits(4)] + off_x: u32, + #[bits(4)] + off_z: u32, + #[bits(4)] + off_y: u32, + /// Bits of the [`BlockState`]. + state: u32, } -impl BlockUpdates { - pub(super) fn shrink_to_fit(&mut self) { - self.updates.shrink_to_fit(); - self.block_entities.shrink_to_fit(); - self.entry_buf.shrink_to_fit(); +impl StateUpdate { + const CHUNK_POS_MASK: u128 = u128::MAX << 72; + const SECTION_POS_MASK: u128 = u128::MAX << 44; + const BLOCK_POS_MASK: u128 = u128::MAX << 32; + + fn from_parts(pos: BlockPos, state: BlockState) -> Self { + Self::new() + .with_section_x(pos.x.div_euclid(16)) + .with_section_y(pos.y.div_euclid(16)) + .with_section_z(pos.z.div_euclid(16)) + .with_off_x(pos.x.rem_euclid(16) as u32) + .with_off_y(pos.y.rem_euclid(16) as u32) + .with_off_z(pos.z.rem_euclid(16) as u32) + .with_state(state.to_raw() as u32) + } + + fn to_parts(self) -> (BlockPos, BlockState) { + ( + BlockPos { + x: self.section_x() * 16 + self.off_x() as i32, + y: self.section_y() * 16 + self.off_y() as i32, + z: self.section_z() * 16 + self.off_z() as i32, + }, + BlockState::from_raw(self.state() as u16).unwrap(), + ) } } -*/ + +// TODO: unit test. diff --git a/crates/valence_server/src/dimension_layer/index.rs b/crates/valence_server/src/dimension_layer/index.rs index 757570ce0..c3010b1b4 100644 --- a/crates/valence_server/src/dimension_layer/index.rs +++ b/crates/valence_server/src/dimension_layer/index.rs @@ -1,59 +1,28 @@ pub use bevy_ecs::prelude::*; use rustc_hash::FxHashMap; -use valence_protocol::ChunkPos; -use valence_registry::dimension_type::DimensionTypeId; -use valence_registry::DimensionTypeRegistry; -use valence_server_common::Server; +use valence_protocol::{BlockPos, ChunkPos}; +use super::block::{Block, BlockRef}; use super::chunk::{Chunk, LoadedChunk}; -use super::DimensionInfo; /// The mapping of chunk positions to [`LoadedChunk`]s in a dimension layer. /// -/// **NOTE**: Modifying the chunk index directly does not send packets to -/// clients and may lead to desync. +/// **NOTE**: By design, directly modifying the chunk index does not send +/// packets to synchronize state with clients. #[derive(Component, Debug)] pub struct ChunkIndex { map: FxHashMap, - pub(super) info: DimensionInfo, + height: i32, } impl ChunkIndex { - pub fn new( - dimension_type: DimensionTypeId, - dimensions: &DimensionTypeRegistry, - server: &Server, - ) -> Self { - let dim = dimensions[dimension_type]; - + pub(super) fn new(height: i32) -> Self { Self { map: Default::default(), - info: DimensionInfo { - dimension_type, - height: dim.height, - min_y: dim.min_y, - biome_registry_len: dimensions.len() as i32, - threshold: server.compression_threshold(), - }, + height, } } - pub(super) fn info(&self) -> &DimensionInfo { - &self.info - } - - pub fn dimension_type(&self) -> DimensionTypeId { - self.info.dimension_type - } - - pub fn height(&self) -> i32 { - self.info.height - } - - pub fn min_y(&self) -> i32 { - self.info.min_y - } - pub fn get(&self, pos: impl Into) -> Option<&LoadedChunk> { self.map.get(&pos.into()) } @@ -86,11 +55,23 @@ impl ChunkIndex { } std::collections::hash_map::Entry::Vacant(v) => Entry::Vacant(VacantEntry { entry: v, - height: self.info.height, + height: self.height, }), } } + pub fn block(&self, pos: impl Into) -> Option { + todo!() + } + + pub fn set_block( + &mut self, + pos: impl Into, + block: impl Into, + ) -> Option { + todo!() + } + // TODO: iter, iter_mut, clear } diff --git a/crates/valence_server/src/dimension_layer/plugin.rs b/crates/valence_server/src/dimension_layer/plugin.rs index f3809154d..a9952c60e 100644 --- a/crates/valence_server/src/dimension_layer/plugin.rs +++ b/crates/valence_server/src/dimension_layer/plugin.rs @@ -1,15 +1,13 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use bevy_ecs::query::Has; -use valence_entity::{OldPosition, Position}; +use valence_entity::Position; use valence_protocol::packets::play::{ChunkRenderDistanceCenterS2c, UnloadChunkS2c}; -use valence_protocol::{ChunkPos, WritePacket}; +use valence_protocol::WritePacket; use valence_server_common::Despawned; -use super::ChunkIndex; +use super::{ChunkIndex, DimensionInfo}; use crate::client::{Client, ClientMarker, OldView, View}; -use crate::layer::{LayerViewers, OldVisibleLayers, VisibleLayers}; -use crate::ChunkView; +use crate::layer::{OldVisibleLayers, VisibleLayers}; pub struct DimensionLayerPlugin; @@ -27,18 +25,6 @@ impl Plugin for DimensionLayerPlugin { #[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct UpdateEntityLayers; -#[derive(Event, Copy, Clone, PartialEq, Eq, Debug)] -pub struct ViewChunkEvent { - pub client: Entity, - pub pos: ChunkPos, -} - -#[derive(Event, Copy, Clone, PartialEq, Eq, Debug)] -pub struct UnviewChunkEvent { - pub client: Entity, - pub pos: ChunkPos, -} - fn update_dimension_layer_views( mut clients: Query< ( @@ -51,9 +37,7 @@ fn update_dimension_layer_views( ), Changed, >, - mut layers: Query<&mut ChunkIndex>, - mut view_events: EventWriter, - mut unview_events: EventWriter, + mut layers: Query<(&mut ChunkIndex, &DimensionInfo)>, ) { for (client_id, mut client, old_view, view, old_visible, visible) in &mut clients { let old_view = old_view.get(); @@ -73,15 +57,11 @@ fn update_dimension_layer_views( if visible.is_changed() { // Send despawn packets for chunks in the old dimension layer. for &layer in old_visible.difference(&visible) { - if let Ok(mut index) = layers.get_mut(layer) { + if let Ok((mut index, _)) = layers.get_mut(layer) { for pos in old_view.iter() { if let Some(mut chunk) = index.get_mut(pos) { client.write_packet(&UnloadChunkS2c { pos }); chunk.viewer_count -= 1; - unview_events.send(UnviewChunkEvent { - client: client_id, - pos, - }); } } @@ -92,15 +72,11 @@ fn update_dimension_layer_views( // Send spawn packets for chunks in the new layer. for &layer in visible.difference(&old_visible) { - if let Ok(mut index) = layers.get(layer) { + if let Ok((mut index, info)) = layers.get_mut(layer) { for pos in view.iter() { if let Some(mut chunk) = index.get_mut(pos) { - chunk.write_chunk_init_packet(&mut *client, pos, index.info()); + chunk.write_chunk_init_packet(&mut *client, pos, info); chunk.viewer_count += 1; - view_events.send(ViewChunkEvent { - client: client_id, - pos, - }); } } @@ -112,28 +88,20 @@ fn update_dimension_layer_views( if !changed_dimension && old_view != view { for &layer in visible.iter() { - if let Ok(mut index) = layers.get_mut(layer) { + if let Ok((mut index, info)) = layers.get_mut(layer) { // Unload old chunks in view. for pos in old_view.diff(view) { if let Some(mut chunk) = index.get_mut(pos) { client.write_packet(&UnloadChunkS2c { pos }); chunk.viewer_count -= 1; - unview_events.send(UnviewChunkEvent { - client: client_id, - pos, - }); } } // Load new chunks in view. for pos in view.diff(old_view) { if let Some(mut chunk) = index.get_mut(pos) { - chunk.write_chunk_init_packet(&mut *client, pos, index.info()); + chunk.write_chunk_init_packet(&mut *client, pos, info); chunk.viewer_count += 1; - view_events.send(ViewChunkEvent { - client: client_id, - pos, - }); } } @@ -147,7 +115,6 @@ fn update_dimension_layer_views( fn update_dimension_layer_views_client_despawn( mut clients: Query<(Entity, OldView, &OldVisibleLayers), (With, With)>, mut chunks: Query<&mut ChunkIndex>, - mut unview_events: EventWriter, ) { for (client_id, old_view, old_layers) in &mut clients { for &layer in old_layers.iter() { @@ -155,10 +122,6 @@ fn update_dimension_layer_views_client_despawn( for pos in old_view.get().iter() { if let Some(chunk) = chunks.get_mut(pos) { chunk.viewer_count -= 1; - unview_events.send(UnviewChunkEvent { - client: client_id, - pos, - }); } } diff --git a/crates/valence_server/src/keepalive.rs b/crates/valence_server/src/keepalive.rs index a13a7c333..1f5570d4e 100644 --- a/crates/valence_server/src/keepalive.rs +++ b/crates/valence_server/src/keepalive.rs @@ -41,28 +41,30 @@ pub struct KeepaliveState { last_send: Instant, } -/// Delay measured in milliseconds. Negative values indicate absence. -#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deref)] -pub struct Ping(pub i32); - -impl Default for Ping { - fn default() -> Self { - Self(-1) +impl KeepaliveState { + /// When the last keepalive was sent for this client. + pub fn last_send(&self) -> Instant { + self.last_send } } -impl KeepaliveState { - pub(super) fn new() -> Self { +impl Default for KeepaliveState { + fn default() -> Self { Self { got_keepalive: true, last_keepalive_id: 0, last_send: Instant::now(), } } +} - /// When the last keepalive was sent for this client. - pub fn last_send(&self) -> Instant { - self.last_send +/// Delay measured in milliseconds. Negative values indicate absence. +#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deref)] +pub struct Ping(pub i32); + +impl Default for Ping { + fn default() -> Self { + Self(-1) } } diff --git a/crates/valence_server/src/layer.rs b/crates/valence_server/src/layer.rs index b6a524491..a048b6d9e 100644 --- a/crates/valence_server/src/layer.rs +++ b/crates/valence_server/src/layer.rs @@ -1,7 +1,5 @@ -pub mod action_buf; mod chunk_view_index; pub mod message; -mod packet_buf; use std::collections::BTreeSet; @@ -10,7 +8,6 @@ use bevy_ecs::prelude::*; use bevy_ecs::query::Has; pub use chunk_view_index::ChunkViewIndex; use derive_more::{Deref, DerefMut}; -pub use packet_buf::PacketBuf; use valence_entity::{OldPosition, Position}; use valence_protocol::ChunkPos; use valence_server_common::Despawned; @@ -68,7 +65,7 @@ fn update_view_index( // Remove from old layers. for &layer in old_visible.iter() { if let Ok((mut viewers, mut index)) = layers.get_mut(layer) { - let removed = viewers.remove(&client); + let removed = viewers.0.remove(&client); debug_assert!(removed); let removed = index.remove(old_pos.get(), client); @@ -79,7 +76,7 @@ fn update_view_index( // Remove from old layers. for &layer in old_visible.iter() { if let Ok((mut viewers, mut index)) = layers.get_mut(layer) { - let removed = viewers.remove(&client); + let removed = viewers.0.remove(&client); debug_assert!(removed); let removed = index.remove(old_pos.get(), client); @@ -90,7 +87,7 @@ fn update_view_index( // Insert in new layers. for &layer in visible.iter() { if let Ok((mut viewers, mut index)) = layers.get_mut(layer) { - let inserted = viewers.insert(client); + let inserted = viewers.0.insert(client); debug_assert!(inserted); let inserted = index.insert(pos.0, client); diff --git a/crates/valence_server/src/layer/action_buf.rs b/crates/valence_server/src/layer/action_buf.rs deleted file mode 100644 index 980b4572a..000000000 --- a/crates/valence_server/src/layer/action_buf.rs +++ /dev/null @@ -1,97 +0,0 @@ -use valence_protocol::encode::PacketWriter; -use valence_protocol::{CompressionThreshold, Encode, Packet, WritePacket}; - -#[derive(Debug)] -pub(crate) struct ActionBuf { - bytes: Vec, - actions: Vec<(T, u32)>, -} - -impl ActionBuf { - pub const fn new() -> Self { - Self { - bytes: vec![], - actions: vec![], - } - } - - pub fn push(&mut self, action: T, f: impl FnOnce(&mut Vec) -> U) -> U { - let before = self.bytes.len(); - let res = f(&mut self.bytes); - let after = self.bytes.len(); - debug_assert!(before <= after); - let len = (after - before) as u32; - - if let Some((prev_action, prev_len)) = self.actions.last_mut() { - if action == *prev_action { - *prev_len += len; - return res; - } - } - - self.actions.push((action, len)); - res - } - - pub fn packet_writer( - &mut self, - action: T, - threshold: CompressionThreshold, - ) -> impl WritePacket + '_ - where - T: Clone, - { - struct Writer<'a, T> { - buf: &'a mut ActionBuf, - action: T, - threshold: CompressionThreshold, - } - - impl WritePacket for Writer<'_, T> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.buf.push(self.action.clone(), |w| { - PacketWriter::new(&mut self.buf.bytes, self.threshold) - .write_packet_fallible(packet) - }) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.buf - .push(self.action.clone(), |w| w.extend_from_slice(bytes)); - } - } - - Writer { - buf: self, - action, - threshold, - } - } - - pub fn write_packet

(&mut self, action: T, threshold: CompressionThreshold, packet: &P) - where - P: Packet + Encode, - { - self.push(action, |w| { - PacketWriter::new(&mut self.bytes, threshold).write_packet(packet) - }) - } - - pub fn actions(&self) -> impl Iterator { - let mut acc: usize = 0; - - self.actions.iter().map(move |(a, len)| { - let slice = &self.bytes[acc..acc + *len as usize]; - acc += *len as usize; - (a, slice) - }) - } - - pub fn clear(&mut self) { - self.bytes.clear(); - self.actions.clear(); - } -} diff --git a/crates/valence_server/src/layer/chunk_view_index.rs b/crates/valence_server/src/layer/chunk_view_index.rs index 3eef80971..1a9f72cb2 100644 --- a/crates/valence_server/src/layer/chunk_view_index.rs +++ b/crates/valence_server/src/layer/chunk_view_index.rs @@ -11,7 +11,10 @@ pub struct ChunkViewIndex { } impl ChunkViewIndex { - pub fn get(&self, pos: impl Into) -> impl ExactSizeIterator + Clone + '_ { + pub fn get( + &self, + pos: impl Into, + ) -> impl ExactSizeIterator + Clone + '_ { self.map .get(&pos.into()) .map(|v| v.iter().copied()) diff --git a/crates/valence_server/src/layer/packet_buf.rs b/crates/valence_server/src/layer/packet_buf.rs deleted file mode 100644 index 1ec88285d..000000000 --- a/crates/valence_server/src/layer/packet_buf.rs +++ /dev/null @@ -1,60 +0,0 @@ -use bevy_ecs::prelude::*; -use derive_more::{Deref, DerefMut}; -use valence_protocol::encode::PacketWriter; -use valence_protocol::{CompressionThreshold, Encode, Packet, WritePacket}; -use valence_server_common::Server; - -use crate::Client; - -#[derive(Component, Clone, Deref, DerefMut, Eq, Debug)] -pub struct PacketBuf { - #[deref] - #[deref_mut] - buf: Vec, - threshold: CompressionThreshold, -} - -impl PartialEq for PacketBuf { - fn eq(&self, other: &Self) -> bool { - self.buf.eq(&other.buf) - } -} - -impl WritePacket for PacketBuf { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - PacketWriter::new(&mut self.buf, self.threshold).write_packet_fallible(packet) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.buf.extend_from_slice(bytes) - } -} - -impl PacketBuf { - /// Creates a new, empty packet buffer. - pub fn new(server: &Server) { - Self { - buf: vec![], - threshold: server.compression_threshold(), - } - } - - /// Sends all packet data in this buffer to all clients given by the - /// iterator. The buffer is then cleared. - pub fn broadcast(&mut self, clients: I) - where - I: IntoIterator, - C: DerefMut, - { - if !self.is_empty() { - for mut client in clients { - client.write_packet_bytes(&self); - } - - self.clear(); - } - } -} diff --git a/crates/valence_server/src/lib.rs b/crates/valence_server/src/lib.rs index 78b97e487..ba692ee6c 100644 --- a/crates/valence_server/src/lib.rs +++ b/crates/valence_server/src/lib.rs @@ -44,8 +44,8 @@ pub mod title; pub use chunk_view::ChunkView; pub use client::Client; +pub use dimension_layer::DimensionLayerBundle; pub use event_loop::{EventLoopPostUpdate, EventLoopPreUpdate, EventLoopUpdate}; -pub use layer::{ChunkLayer, EntityLayer, Layer, LayerBundle}; pub use valence_protocol::{ block, ident, item, math, text, uuid, BiomePos, BlockPos, BlockState, ChunkPos, CompressionThreshold, Difficulty, Direction, GameMode, Hand, Ident, ItemKind, ItemStack, Text, diff --git a/crates/valence_server/src/movement.rs b/crates/valence_server/src/movement.rs index ba10b59f0..e5625ef8e 100644 --- a/crates/valence_server/src/movement.rs +++ b/crates/valence_server/src/movement.rs @@ -11,8 +11,8 @@ use valence_protocol::packets::play::{ use valence_protocol::{BlockPos, WritePacket}; use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; -use crate::Client; use crate::layer::BroadcastLayerMessagesSet; +use crate::Client; /// Handles client movement and teleports. pub struct PositionPlugin; diff --git a/crates/valence_server/src/spawn.rs b/crates/valence_server/src/spawn.rs index e99069886..b0330f214 100644 --- a/crates/valence_server/src/spawn.rs +++ b/crates/valence_server/src/spawn.rs @@ -14,7 +14,7 @@ use valence_registry::tags::TagsRegistry; use valence_registry::{DimensionTypeRegistry, RegistryCodec, UpdateRegistrySet}; use crate::client::{Client, FlushPacketsSet, ViewDistance, VisibleChunkLayer}; -use crate::dimension_layer::{ChunkIndex, UpdateDimensionLayerSet}; +use crate::dimension_layer::{ChunkIndex, DimensionInfo, UpdateDimensionLayerSet}; /// Handles spawning and respawning of clients. pub struct SpawnPlugin; @@ -109,10 +109,10 @@ pub(super) fn initial_join( codec: Res, tags: Res, dimensions: Res, - dimension_layers: Query<&ChunkIndex>, + dimension_layers: Query<(&ChunkIndex, &DimensionInfo)>, ) { for (mut client, visible_chunk_layer, spawn) in &mut clients { - let Ok(chunk_index) = dimension_layers.get(visible_chunk_layer.0) else { + let Ok((chunk_index, info)) = dimension_layers.get(visible_chunk_layer.0) else { continue; }; @@ -122,7 +122,7 @@ pub(super) fn initial_join( .map(|value| value.name.as_str_ident().into()) .collect(); - let dimension_name = dimensions.by_index(chunk_index.dimension_type()).0; + let dimension_name = dimensions.by_index(info.dimension_type()).0; // The login packet is prepended so that it's sent before all the other packets. // Some packets don't work correctly when sent before the game join packet. @@ -172,7 +172,7 @@ fn respawn( ), Changed, >, - chunk_layers: Query<&ChunkIndex>, + chunk_layers: Query<(&ChunkIndex, &DimensionInfo)>, dimensions: Res, ) { for (mut client, loc, death_loc, hashed_seed, game_mode, prev_game_mode, is_debug, is_flat) in @@ -183,11 +183,11 @@ fn respawn( continue; } - let Ok(chunk_index) = chunk_layers.get(loc.0) else { + let Ok((chunk_index, info)) = chunk_layers.get(loc.0) else { continue; }; - let dimension_name = dimensions.by_index(chunk_index.dimension_type()).0; + let dimension_name = dimensions.by_index(info.dimension_type()).0; let last_death_location = death_loc.0.as_ref().map(|(id, pos)| GlobalPos { dimension_name: id.as_str_ident().into(), From 02eb777d6bfcac86333b957b94ae016f0968b59c Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 15 Sep 2023 18:20:20 -0700 Subject: [PATCH 05/16] Initial schedule redesign --- crates/valence_entity/src/lib.rs | 1 + crates/valence_player_list/src/lib.rs | 4 +- crates/valence_registry/src/biome.rs | 9 +- crates/valence_registry/src/codec.rs | 4 +- crates/valence_registry/src/dimension_type.rs | 6 +- crates/valence_registry/src/lib.rs | 6 +- crates/valence_registry/src/tags.rs | 4 +- crates/valence_scoreboard/src/lib.rs | 38 +- crates/valence_server/src/abilities.rs | 9 +- crates/valence_server/src/action.rs | 4 +- crates/valence_server/src/client.rs | 676 ++---------------- crates/valence_server/src/dimension_layer.rs | 3 +- .../src/dimension_layer/index.rs | 2 +- .../src/dimension_layer/plugin.rs | 33 +- crates/valence_server/src/entity_layer.rs | 17 +- crates/valence_server/src/game_mode.rs | 35 + crates/valence_server/src/keepalive.rs | 8 +- crates/valence_server/src/layer.rs | 111 ++- crates/valence_server/src/layer/message.rs | 8 +- crates/valence_server/src/lib.rs | 4 +- crates/valence_server/src/movement.rs | 7 +- crates/valence_server/src/op_level.rs | 8 +- crates/valence_server/src/spawn.rs | 53 +- crates/valence_weather/src/lib.rs | 91 +-- crates/valence_world_border/src/lib.rs | 93 +-- examples/bench_players.rs | 9 +- src/lib.rs | 28 +- 27 files changed, 413 insertions(+), 858 deletions(-) create mode 100644 crates/valence_server/src/game_mode.rs diff --git a/crates/valence_entity/src/lib.rs b/crates/valence_entity/src/lib.rs index 02a35decd..8db874956 100644 --- a/crates/valence_entity/src/lib.rs +++ b/crates/valence_entity/src/lib.rs @@ -165,6 +165,7 @@ fn clear_tracked_data_changes(mut tracked_data: Query<&mut TrackedData, Changed< /// Contains the entity layer an entity is on. #[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref)] +#[deprecated] pub struct EntityLayerId(pub Entity); impl Default for EntityLayerId { diff --git a/crates/valence_player_list/src/lib.rs b/crates/valence_player_list/src/lib.rs index 5b8a0b655..8a50f687f 100644 --- a/crates/valence_player_list/src/lib.rs +++ b/crates/valence_player_list/src/lib.rs @@ -24,8 +24,8 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use derive_more::{Deref, DerefMut}; use valence_server::client::{Client, Properties, Username}; +use valence_server::entity_layer::UpdateEntityLayerSet; use valence_server::keepalive::Ping; -use valence_server::layer_old::UpdateLayersPreClientSet; use valence_server::protocol::encode::PacketWriter; use valence_server::protocol::packets::play::{ player_list_s2c as packet, PlayerListHeaderS2c, PlayerListS2c, PlayerRemoveS2c, @@ -47,7 +47,7 @@ impl Plugin for PlayerListPlugin { PostUpdate, // Needs to happen before player entities are initialized. Otherwise, they will // appear invisible. - PlayerListSet.before(UpdateLayersPreClientSet), + PlayerListSet.before(UpdateEntityLayerSet), ) .add_systems( PostUpdate, diff --git a/crates/valence_registry/src/biome.rs b/crates/valence_registry/src/biome.rs index 0bad96070..c69c803b8 100644 --- a/crates/valence_registry/src/biome.rs +++ b/crates/valence_registry/src/biome.rs @@ -18,7 +18,7 @@ use valence_ident::{ident, Ident}; use valence_nbt::serde::CompoundSerializer; use crate::codec::{RegistryCodec, RegistryValue}; -use crate::{Registry, RegistryIdx, UpdateRegistrySet}; +use crate::{CachePacketsSet, Registry, RegistryIdx, UpdateRegistrySet}; pub struct BiomePlugin; @@ -26,7 +26,12 @@ impl Plugin for BiomePlugin { fn build(&self, app: &mut App) { app.init_resource::() .add_systems(PreStartup, load_default_biomes) - .add_systems(PostUpdate, update_biome_registry.before(UpdateRegistrySet)); + .add_systems( + PostUpdate, + update_biome_registry + .in_set(UpdateRegistrySet) + .before(CachePacketsSet), + ); } } diff --git a/crates/valence_registry/src/codec.rs b/crates/valence_registry/src/codec.rs index 22cf32160..729109889 100644 --- a/crates/valence_registry/src/codec.rs +++ b/crates/valence_registry/src/codec.rs @@ -6,11 +6,11 @@ use tracing::error; use valence_ident::Ident; use valence_nbt::{compound, Compound, List, Value}; -use crate::UpdateRegistrySet; +use crate::CachePacketsSet; pub(super) fn build(app: &mut App) { app.init_resource::() - .add_systems(PostUpdate, cache_registry_codec.in_set(UpdateRegistrySet)); + .add_systems(PostUpdate, cache_registry_codec.in_set(CachePacketsSet)); } /// Contains the registry codec sent to all players while joining. This contains diff --git a/crates/valence_registry/src/dimension_type.rs b/crates/valence_registry/src/dimension_type.rs index e4e6057b3..1ab2085fc 100644 --- a/crates/valence_registry/src/dimension_type.rs +++ b/crates/valence_registry/src/dimension_type.rs @@ -16,7 +16,7 @@ use valence_ident::{ident, Ident}; use valence_nbt::serde::CompoundSerializer; use crate::codec::{RegistryCodec, RegistryValue}; -use crate::{Registry, RegistryIdx, UpdateRegistrySet}; +use crate::{CachePacketsSet, Registry, RegistryIdx, UpdateRegistrySet}; pub struct DimensionTypePlugin; impl Plugin for DimensionTypePlugin { @@ -25,7 +25,9 @@ impl Plugin for DimensionTypePlugin { .add_systems(PreStartup, load_default_dimension_types) .add_systems( PostUpdate, - update_dimension_type_registry.before(UpdateRegistrySet), + update_dimension_type_registry + .in_set(UpdateRegistrySet) + .before(CachePacketsSet), ); } } diff --git a/crates/valence_registry/src/lib.rs b/crates/valence_registry/src/lib.rs index a3c12be50..1651c7e7d 100644 --- a/crates/valence_registry/src/lib.rs +++ b/crates/valence_registry/src/lib.rs @@ -45,9 +45,13 @@ pub struct RegistryPlugin; #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct UpdateRegistrySet; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct CachePacketsSet; + impl Plugin for RegistryPlugin { fn build(&self, app: &mut bevy_app::App) { - app.configure_set(PostUpdate, UpdateRegistrySet); + app.configure_set(PostUpdate, UpdateRegistrySet) + .configure_set(PostUpdate, CachePacketsSet.in_set(UpdateRegistrySet)); codec::build(app); tags::build(app); diff --git a/crates/valence_registry/src/tags.rs b/crates/valence_registry/src/tags.rs index 17e689666..e3851fd99 100644 --- a/crates/valence_registry/src/tags.rs +++ b/crates/valence_registry/src/tags.rs @@ -7,7 +7,7 @@ pub use valence_protocol::packets::play::synchronize_tags_s2c::RegistryMap; use valence_protocol::packets::play::SynchronizeTagsS2c; use valence_server_common::Server; -use crate::UpdateRegistrySet; +use crate::CachePacketsSet; #[derive(Debug, Resource, Default)] pub struct TagsRegistry { @@ -18,7 +18,7 @@ pub struct TagsRegistry { pub(super) fn build(app: &mut App) { app.init_resource::() .add_systems(PreStartup, init_tags_registry) - .add_systems(PostUpdate, cache_tags_packet.in_set(UpdateRegistrySet)); + .add_systems(PostUpdate, cache_tags_packet.in_set(CachePacketsSet)); } impl TagsRegistry { diff --git a/crates/valence_scoreboard/src/lib.rs b/crates/valence_scoreboard/src/lib.rs index 8e3ec0b45..80c97dbe0 100644 --- a/crates/valence_scoreboard/src/lib.rs +++ b/crates/valence_scoreboard/src/lib.rs @@ -28,6 +28,7 @@ pub use components::*; use tracing::{debug, warn}; use valence_server::client::{Client, OldVisibleEntityLayers, VisibleEntityLayers}; use valence_server::entity::EntityLayerId; +use valence_server::layer::BroadcastLayerMessagesSet; use valence_server::layer_old::UpdateLayersPreClientSet; use valence_server::protocol::packets::play::scoreboard_display_s2c::ScoreboardPosition; use valence_server::protocol::packets::play::scoreboard_objective_update_s2c::{ @@ -39,35 +40,26 @@ use valence_server::protocol::packets::play::{ }; use valence_server::protocol::{VarInt, WritePacket}; use valence_server::text::IntoText; -use valence_server::{Despawned, EntityLayer}; +use valence_server::Despawned; /// Provides all necessary systems to manage scoreboards. pub struct ScoreboardPlugin; impl Plugin for ScoreboardPlugin { fn build(&self, app: &mut App) { - app.configure_set(PostUpdate, ScoreboardSet.before(UpdateLayersPreClientSet)); - - app.add_systems( - PostUpdate, - ( - create_or_update_objectives, - display_objectives.after(create_or_update_objectives), - ) - .in_set(ScoreboardSet), - ) - .add_systems( - PostUpdate, - remove_despawned_objectives.in_set(ScoreboardSet), - ) - .add_systems(PostUpdate, handle_new_clients.in_set(ScoreboardSet)) - .add_systems( - PostUpdate, - update_scores - .after(create_or_update_objectives) - .after(handle_new_clients) - .in_set(ScoreboardSet), - ); + app.configure_set(PostUpdate, ScoreboardSet.before(BroadcastLayerMessagesSet)) + .add_systems( + PostUpdate, + ( + create_or_update_objectives, + display_objectives.after(create_or_update_objectives), + remove_despawned_objectives, + update_scores + .after(create_or_update_objectives) + .after(handle_new_clients), + ) + .in_set(ScoreboardSet), + ); } } diff --git a/crates/valence_server/src/abilities.rs b/crates/valence_server/src/abilities.rs index 35b22f913..f4b7ca9e7 100644 --- a/crates/valence_server/src/abilities.rs +++ b/crates/valence_server/src/abilities.rs @@ -5,7 +5,7 @@ pub use valence_protocol::packets::play::player_abilities_s2c::PlayerAbilitiesFl use valence_protocol::packets::play::{PlayerAbilitiesS2c, UpdatePlayerAbilitiesC2s}; use valence_protocol::{GameMode, WritePacket}; -use crate::client::{update_game_mode, Client, UpdateClientsSet}; +use crate::client::Client; use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; /// [`Component`] that stores the player's flying speed ability. @@ -34,13 +34,13 @@ impl Default for FovModifier { } /// Send if the client sends [`UpdatePlayerAbilitiesC2s::StartFlying`] -#[derive(Event)] +#[derive(Event, Debug)] pub struct PlayerStartFlyingEvent { pub client: Entity, } /// Send if the client sends [`UpdatePlayerAbilitiesC2s::StopFlying`] -#[derive(Event)] +#[derive(Event, Debug)] pub struct PlayerStopFlyingEvent { pub client: Entity, } @@ -64,6 +64,8 @@ pub struct AbilitiesPlugin; impl Plugin for AbilitiesPlugin { fn build(&self, app: &mut App) { + + /* app.add_event::() .add_event::() .add_systems( @@ -76,6 +78,7 @@ impl Plugin for AbilitiesPlugin { .after(update_game_mode), ) .add_systems(EventLoopPreUpdate, update_server_player_abilities); + */ } } diff --git a/crates/valence_server/src/action.rs b/crates/valence_server/src/action.rs index cb45fb71e..1b0076c32 100644 --- a/crates/valence_server/src/action.rs +++ b/crates/valence_server/src/action.rs @@ -5,19 +5,21 @@ use valence_protocol::packets::play::player_action_c2s::PlayerAction; use valence_protocol::packets::play::{PlayerActionC2s, PlayerActionResponseS2c}; use valence_protocol::{BlockPos, Direction, VarInt, WritePacket}; -use crate::client::{Client, UpdateClientsSet}; +use crate::client::Client; use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; pub struct ActionPlugin; impl Plugin for ActionPlugin { fn build(&self, app: &mut App) { + /* app.add_event::() .add_systems(EventLoopPreUpdate, handle_player_action) .add_systems( PostUpdate, acknowledge_player_actions.in_set(UpdateClientsSet), ); + */ } } diff --git a/crates/valence_server/src/client.rs b/crates/valence_server/src/client.rs index 1645437d2..09f8eab8e 100644 --- a/crates/valence_server/src/client.rs +++ b/crates/valence_server/src/client.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::collections::BTreeSet; use std::fmt; use std::net::IpAddr; use std::time::Instant; @@ -20,8 +19,8 @@ use valence_protocol::encode::{PacketEncoder, WritePacket}; use valence_protocol::packets::play::game_state_change_s2c::GameEventKind; use valence_protocol::packets::play::particle_s2c::Particle; use valence_protocol::packets::play::{ - ChunkLoadDistanceS2c, DeathMessageS2c, DisconnectS2c, EntitiesDestroyS2c, EntityStatusS2c, - EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, GameStateChangeS2c, ParticleS2c, PlaySoundS2c, + DeathMessageS2c, DisconnectS2c, EntitiesDestroyS2c, EntityStatusS2c, EntityTrackerUpdateS2c, + EntityVelocityUpdateS2c, GameStateChangeS2c, ParticleS2c, PlaySoundS2c, }; use valence_protocol::profile::Property; use valence_protocol::sound::{Sound, SoundCategory, SoundId}; @@ -30,6 +29,7 @@ use valence_protocol::var_int::VarInt; use valence_protocol::{Encode, GameMode, Packet}; use valence_server_common::{Despawned, UniqueId}; +use crate::layer::{OldVisibleLayers, VisibleLayers}; use crate::ChunkView; pub struct ClientPlugin; @@ -46,52 +46,22 @@ pub struct FlushPacketsSet; #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct SpawnClientsSet; -/// The system set where various facets of the client are updated. Systems that -/// modify layers should run _before_ this. #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct UpdateClientsSet; +pub struct SelfTrackedDataSet; impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { - todo!() - - // app.add_systems( - // PostUpdate, - // ( - // ( - // crate::spawn::initial_join.after(UpdateRegistrySet), - // update_chunk_load_dist, - // handle_layer_messages.after(update_chunk_load_dist), - // update_view_and_layers - // .after(crate::spawn::initial_join) - // .after(handle_layer_messages), - // - // cleanup_chunks_after_client_despawn.after(update_view_and_layers), - // - // crate::spawn::update_respawn_position.after(update_view_and_layers), - // - // crate::spawn::respawn.after(crate::spawn::update_respawn_position), - // update_old_view_dist.after(update_view_and_layers), - // update_game_mode, - // update_tracked_data, - // init_tracked_data, - // ) - // .in_set(UpdateClientsSet), - // flush_packets.in_set(FlushPacketsSet), - // ), - // ) - // .configure_set(PreUpdate, SpawnClientsSet) - // .configure_sets( - // PostUpdate, - // ( - // UpdateClientsSet - // .after(UpdateLayersPreClientSet) - // .before(UpdateLayersPostClientSet) - // .before(FlushPacketsSet), - // ClearEntityChangesSet.after(UpdateClientsSet), - // FlushPacketsSet, - // ), - // ); + app.configure_set(PostUpdate, SelfTrackedDataSet.before(FlushPacketsSet)) + .add_systems( + PostUpdate, + ( + flush_packets.in_set(FlushPacketsSet), + (init_self_tracked_data, update_self_tracked_data) + .chain() + .in_set(SelfTrackedDataSet), + despawn_disconnected_clients, + ), + ); } } @@ -101,6 +71,8 @@ impl Plugin for ClientPlugin { pub struct ClientBundle { pub marker: ClientMarker, pub client: Client, + pub visible_layers: VisibleLayers, + pub old_visible_layers: OldVisibleLayers, pub settings: crate::client_settings::ClientSettings, pub entity_remove_buf: EntityRemoveBuf, pub username: Username, @@ -111,10 +83,6 @@ pub struct ClientBundle { pub action_sequence: crate::action::ActionSequence, pub view_distance: ViewDistance, pub old_view_distance: OldViewDistance, - pub visible_chunk_layer: VisibleChunkLayer, - pub old_visible_chunk_layer: OldVisibleChunkLayer, - pub visible_entity_layers: VisibleEntityLayers, - pub old_visible_entity_layers: OldVisibleEntityLayers, pub keepalive_state: crate::keepalive::KeepaliveState, pub ping: crate::keepalive::Ping, pub teleport_state: crate::movement::TeleportState, @@ -142,6 +110,8 @@ impl ClientBundle { conn: args.conn, enc: args.enc, }, + visible_layers: Default::default(), + old_visible_layers: Default::default(), settings: Default::default(), entity_remove_buf: Default::default(), username: Username(args.username), @@ -151,14 +121,10 @@ impl ClientBundle { op_level: Default::default(), action_sequence: Default::default(), view_distance: Default::default(), - old_view_distance: OldViewDistance(2), - visible_chunk_layer: Default::default(), - old_visible_chunk_layer: OldVisibleChunkLayer::default(), - visible_entity_layers: Default::default(), - old_visible_entity_layers: OldVisibleEntityLayers::default(), - keepalive_state: crate::keepalive::KeepaliveState::default(), + old_view_distance: Default::default(), + keepalive_state: Default::default(), ping: Default::default(), - teleport_state: crate::movement::TeleportState::default(), + teleport_state: Default::default(), game_mode: GameMode::default(), prev_game_mode: Default::default(), death_location: Default::default(), @@ -476,9 +442,15 @@ impl Default for ViewDistance { /// The [`ViewDistance`] at the end of the previous tick. Automatically updated /// as [`ViewDistance`] is changed. -#[derive(Component, Clone, PartialEq, Eq, Default, Debug, Deref)] +#[derive(Component, Clone, PartialEq, Eq, Debug, Deref)] pub struct OldViewDistance(u8); +impl Default for OldViewDistance { + fn default() -> Self { + Self(2) + } +} + impl OldViewDistance { pub fn get(&self) -> u8 { self.0 @@ -509,571 +481,35 @@ impl OldViewItem<'_> { } } -/// A [`Component`] containing a handle to the [`ChunkLayer`] a client can -/// see. -/// -/// A client can only see one chunk layer at a time. Mutating this component -/// will cause the client to respawn in the new chunk layer. -#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref, DerefMut)] -pub struct VisibleChunkLayer(pub Entity); - -impl Default for VisibleChunkLayer { - fn default() -> Self { - Self(Entity::PLACEHOLDER) - } +#[derive(Resource, Debug)] +pub struct ClientDespawnSettings { + /// If disconnected clients should automatically have the [`Despawned`] + /// component added to them. Without this enabled, clients entities must be + /// removed from the world manually. + pub despawn_disconnected_clients: bool, } -/// The value of [`VisibleChunkLayer`] from the end of the previous tick. -#[derive(Component, PartialEq, Eq, Debug, Deref)] -pub struct OldVisibleChunkLayer(Entity); - -impl Default for OldVisibleChunkLayer { +impl Default for ClientDespawnSettings { fn default() -> Self { - Self(Entity::PLACEHOLDER) - } -} - -impl OldVisibleChunkLayer { - pub fn get(&self) -> Entity { - self.0 - } -} - -/// A [`Component`] containing the set of [`EntityLayer`]s a client can see. -/// All Minecraft entities from all layers in this set are potentially visible -/// to the client. -/// -/// This set can be mutated at any time to change which entity layers are -/// visible to the client. [`Despawned`] entity layers are automatically -/// removed. -#[derive(Component, Default, Debug)] -pub struct VisibleEntityLayers(pub BTreeSet); - -/// The value of [`VisibleEntityLayers`] from the end of the previous tick. -#[derive(Component, Default, Debug, Deref)] -pub struct OldVisibleEntityLayers(BTreeSet); - -impl OldVisibleEntityLayers { - pub fn get(&self) -> &BTreeSet { - &self.0 + Self { + despawn_disconnected_clients: true, + } } } /// A system for adding [`Despawned`] components to disconnected clients. This /// works by listening for removed [`Client`] components. -pub fn despawn_disconnected_clients( +fn despawn_disconnected_clients( mut commands: Commands, mut disconnected_clients: RemovedComponents, + cfg: Res, ) { - for entity in disconnected_clients.iter() { - if let Some(mut entity) = commands.get_entity(entity) { - entity.insert(Despawned); - } - } -} - -fn update_chunk_load_dist( - mut clients: Query<(&mut Client, &ViewDistance, &OldViewDistance), Changed>, -) { - for (mut client, dist, old_dist) in &mut clients { - if client.is_added() { - // Join game packet includes the view distance. - continue; - } - - if dist.0 != old_dist.0 { - // Note: This packet is just aesthetic. - client.write_packet(&ChunkLoadDistanceS2c { - view_distance: VarInt(dist.0.into()), - }); - } - } -} - -/* -fn handle_layer_messages( - mut clients: Query<( - Entity, - &EntityId, - &mut Client, - &mut EntityRemoveBuf, - OldView, - &OldVisibleChunkLayer, - &mut VisibleEntityLayers, - &OldVisibleEntityLayers, - )>, - chunk_layers: Query<&ChunkLayer>, - entity_layers: Query<&EntityLayer>, - entities: Query<(EntityInitQuery, &OldPosition)>, -) { - clients.par_iter_mut().for_each_mut( - |( - self_entity, - self_entity_id, - mut client, - mut remove_buf, - old_view, - old_visible_chunk_layer, - mut visible_entity_layers, - old_visible_entity_layers, - )| { - let block_pos = BlockPos::from(old_view.old_pos.get()); - let old_view = old_view.get(); - - fn in_radius(p0: BlockPos, p1: BlockPos, radius_squared: u32) -> bool { - let dist_squared = - (p1.x - p0.x).pow(2) + (p1.y - p0.y).pow(2) + (p1.z - p0.z).pow(2); - - dist_squared as u32 <= radius_squared + if cfg.despawn_disconnected_clients { + for entity in disconnected_clients.iter() { + if let Some(mut entity) = commands.get_entity(entity) { + entity.insert(Despawned); } - - // Chunk layer messages - if let Ok(chunk_layer) = chunk_layers.get(old_visible_chunk_layer.get()) { - let messages = chunk_layer.messages(); - let bytes = messages.bytes(); - - // Global messages - for (msg, range) in messages.iter_global() { - match msg { - crate::layer::chunk::GlobalMsg::Packet => { - client.write_packet_bytes(&bytes[range]); - } - crate::layer::chunk::GlobalMsg::PacketExcept { except } => { - if self_entity != except { - client.write_packet_bytes(&bytes[range]); - } - } - } - } - - let mut chunk_biome_buf = vec![]; - - // Local messages - messages.query_local(old_view, |msg, range| match msg { - crate::layer::chunk::LocalMsg::PacketAt { .. } => { - client.write_packet_bytes(&bytes[range]); - } - crate::layer::chunk::LocalMsg::PacketAtExcept { except, .. } => { - if self_entity != except { - client.write_packet_bytes(&bytes[range]); - } - } - crate::layer::chunk::LocalMsg::RadiusAt { - center, - radius_squared, - } => { - if in_radius(block_pos, center, radius_squared) { - client.write_packet_bytes(&bytes[range]); - } - } - crate::layer::chunk::LocalMsg::RadiusAtExcept { - center, - radius_squared, - except, - } => { - if self_entity != except && in_radius(block_pos, center, radius_squared) { - client.write_packet_bytes(&bytes[range]); - } - } - crate::layer::chunk::LocalMsg::ChangeBiome { pos } => { - chunk_biome_buf.push(ChunkBiome { - pos, - data: &bytes[range], - }); - } - crate::layer::chunk::LocalMsg::ChangeChunkState { pos } => { - match &bytes[range] { - [ChunkLayer::LOAD, .., ChunkLayer::UNLOAD] => { - // Chunk is being loaded and unloaded on the - // same tick, so there's no need to do anything. - debug_assert!(chunk_layer.chunk(pos).is_none()); - } - [.., ChunkLayer::LOAD | ChunkLayer::OVERWRITE] => { - // Load chunk. - let chunk = chunk_layer.chunk(pos).expect("chunk must exist"); - chunk.write_init_packets(&mut *client, pos, chunk_layer.info()); - chunk.inc_viewer_count(); - } - [.., ChunkLayer::UNLOAD] => { - // Unload chunk. - client.write_packet(&UnloadChunkS2c { pos }); - debug_assert!(chunk_layer.chunk(pos).is_none()); - } - _ => unreachable!("invalid message data while changing chunk state"), - } - } - }); - - if !chunk_biome_buf.is_empty() { - client.write_packet(&ChunkBiomeDataS2c { - chunks: chunk_biome_buf.into(), - }); - } - } - - // Entity layer messages - for &layer_id in &old_visible_entity_layers.0 { - if let Ok(layer) = entity_layers.get(layer_id) { - let messages = layer.messages(); - let bytes = messages.bytes(); - - // Global messages - for (msg, range) in messages.iter_global() { - match msg { - crate::layer::entity::GlobalMsg::Packet => { - client.write_packet_bytes(&bytes[range]); - } - crate::layer::entity::GlobalMsg::PacketExcept { except } => { - if self_entity != except { - client.write_packet_bytes(&bytes[range]); - } - } - crate::layer::entity::GlobalMsg::DespawnLayer => { - // Remove this entity layer. The changes to the visible entity layer - // set will be detected by the `update_view_and_layers` system and - // despawning of entities will happen there. - visible_entity_layers.0.remove(&layer_id); - } - } - } - - // Local messages - messages.query_local(old_view, |msg, range| match msg { - crate::layer::entity::LocalMsg::DespawnEntity { pos: _, dest_layer } => { - if !old_visible_entity_layers.0.contains(&dest_layer) { - let mut bytes = &bytes[range]; - - while let Ok(id) = bytes.read_i32::() { - if self_entity_id.get() != id { - remove_buf.push(id); - } - } - } - } - crate::layer::entity::LocalMsg::DespawnEntityTransition { - pos: _, - dest_pos, - } => { - if !old_view.contains(dest_pos) { - let mut bytes = &bytes[range]; - - while let Ok(id) = bytes.read_i32::() { - if self_entity_id.get() != id { - remove_buf.push(id); - } - } - } - } - crate::layer::entity::LocalMsg::SpawnEntity { pos: _, src_layer } => { - if !old_visible_entity_layers.0.contains(&src_layer) { - let mut bytes = &bytes[range]; - - while let Ok(u64) = bytes.read_u64::() { - let entity = Entity::from_bits(u64); - - if self_entity != entity { - if let Ok((init, old_pos)) = entities.get(entity) { - remove_buf.send_and_clear(&mut *client); - - // Spawn at the entity's old position since we may get a - // relative movement packet for this entity in a later - // iteration of the loop. - init.write_init_packets(old_pos.get(), &mut *client); - } - } - } - } - } - crate::layer::entity::LocalMsg::SpawnEntityTransition { - pos: _, - src_pos, - } => { - if !old_view.contains(src_pos) { - let mut bytes = &bytes[range]; - - while let Ok(u64) = bytes.read_u64::() { - let entity = Entity::from_bits(u64); - - if self_entity != entity { - if let Ok((init, old_pos)) = entities.get(entity) { - remove_buf.send_and_clear(&mut *client); - - // Spawn at the entity's old position since we may get a - // relative movement packet for this entity in a later - // iteration of the loop. - init.write_init_packets(old_pos.get(), &mut *client); - } - } - } - } - } - crate::layer::entity::LocalMsg::PacketAt { pos: _ } => { - client.write_packet_bytes(&bytes[range]); - } - crate::layer::entity::LocalMsg::PacketAtExcept { pos: _, except } => { - if self_entity != except { - client.write_packet_bytes(&bytes[range]); - } - } - crate::layer::entity::LocalMsg::RadiusAt { - center, - radius_squared, - } => { - if in_radius(block_pos, center, radius_squared) { - client.write_packet_bytes(&bytes[range]); - } - } - crate::layer::entity::LocalMsg::RadiusAtExcept { - center, - radius_squared, - except, - } => { - if self_entity != except && in_radius(block_pos, center, radius_squared) - { - client.write_packet_bytes(&bytes[range]); - } - } - }); - - remove_buf.send_and_clear(&mut *client); - } - } - }, - ); -} - -pub(crate) fn update_view_and_layers( - mut clients: Query< - ( - Entity, - &mut Client, - &mut EntityRemoveBuf, - &VisibleChunkLayer, - &mut OldVisibleChunkLayer, - Ref, - &mut OldVisibleEntityLayers, - &Position, - &OldPosition, - &ViewDistance, - &OldViewDistance, - ), - Or<( - Changed, - Changed, - Changed, - Changed, - )>, - >, - chunk_layers: Query<&ChunkLayer>, - entity_layers: Query<&EntityLayer>, - entity_ids: Query<&EntityId>, - entity_init: Query<(EntityInitQuery, &Position)>, -) { - clients.par_iter_mut().for_each_mut( - |( - self_entity, - mut client, - mut remove_buf, - chunk_layer, - mut old_chunk_layer, - visible_entity_layers, - mut old_visible_entity_layers, - pos, - old_pos, - view_dist, - old_view_dist, - )| { - let view = ChunkView::new(ChunkPos::from(pos.0), view_dist.0); - let old_view = ChunkView::new(ChunkPos::from(old_pos.get()), old_view_dist.0); - - // Make sure the center chunk is set before loading chunks! Otherwise the client - // may ignore the chunk. - if old_view.pos != view.pos { - client.write_packet(&ChunkRenderDistanceCenterS2c { - chunk_x: VarInt(view.pos.x), - chunk_z: VarInt(view.pos.z), - }); - } - - // Was the client's chunk layer changed? - if old_chunk_layer.0 != chunk_layer.0 { - // Unload all chunks in the old view. - // TODO: can we skip this step if old dimension != new dimension? - if let Ok(layer) = chunk_layers.get(old_chunk_layer.0) { - for pos in old_view.iter() { - if let Some(chunk) = layer.chunk(pos) { - client.write_packet(&UnloadChunkS2c { pos }); - chunk.dec_viewer_count(); - } - } - } - - // Load all chunks in the new view. - if let Ok(layer) = chunk_layers.get(chunk_layer.0) { - for pos in view.iter() { - if let Some(chunk) = layer.chunk(pos) { - chunk.write_init_packets(&mut *client, pos, layer.info()); - chunk.inc_viewer_count(); - } - } - } - - // Unload all entities from the old view in all old visible entity layers. - // TODO: can we skip this step if old dimension != new dimension? - for &layer in &old_visible_entity_layers.0 { - if let Ok(layer) = entity_layers.get(layer) { - for pos in old_view.iter() { - for entity in layer.entities_at(pos) { - if self_entity != entity { - if let Ok(id) = entity_ids.get(entity) { - remove_buf.push(id.get()); - } - } - } - } - } - } - - remove_buf.send_and_clear(&mut *client); - - // Load all entities in the new view from all new visible entity layers. - for &layer in &visible_entity_layers.0 { - if let Ok(layer) = entity_layers.get(layer) { - for pos in view.iter() { - for entity in layer.entities_at(pos) { - if self_entity != entity { - if let Ok((init, pos)) = entity_init.get(entity) { - init.write_init_packets(pos.get(), &mut *client); - } - } - } - } - } - } - } else { - // Update the client's visible entity layers. - if visible_entity_layers.is_changed() { - // Unload all entity layers that are no longer visible in the old view. - for &layer in old_visible_entity_layers - .0 - .difference(&visible_entity_layers.0) - { - if let Ok(layer) = entity_layers.get(layer) { - for pos in old_view.iter() { - for entity in layer.entities_at(pos) { - if self_entity != entity { - if let Ok(id) = entity_ids.get(entity) { - remove_buf.push(id.get()); - } - } - } - } - } - } - - remove_buf.send_and_clear(&mut *client); - - // Load all entity layers that are newly visible in the old view. - for &layer in visible_entity_layers - .0 - .difference(&old_visible_entity_layers.0) - { - if let Ok(layer) = entity_layers.get(layer) { - for pos in old_view.iter() { - for entity in layer.entities_at(pos) { - if self_entity != entity { - if let Ok((init, pos)) = entity_init.get(entity) { - init.write_init_packets(pos.get(), &mut *client); - } - } - } - } - } - } - } - - // Update the client's view (chunk position and view distance) - if old_view != view { - // Unload chunks and entities in the old view and load chunks and entities in - // the new view. We don't need to do any work where the old and new view - // overlap. - - // Unload chunks in the old view. - if let Ok(layer) = chunk_layers.get(chunk_layer.0) { - for pos in old_view.diff(view) { - if let Some(chunk) = layer.chunk(pos) { - client.write_packet(&UnloadChunkS2c { pos }); - chunk.dec_viewer_count(); - } - } - } - - // Load chunks in the new view. - if let Ok(layer) = chunk_layers.get(chunk_layer.0) { - for pos in view.diff(old_view) { - if let Some(chunk) = layer.chunk(pos) { - chunk.write_init_packets(&mut *client, pos, layer.info()); - chunk.inc_viewer_count(); - } - } - } - - // Unload entities from the new visible layers (since we updated it above). - for &layer in &visible_entity_layers.0 { - if let Ok(layer) = entity_layers.get(layer) { - for pos in old_view.diff(view) { - for entity in layer.entities_at(pos) { - if self_entity != entity { - if let Ok(id) = entity_ids.get(entity) { - remove_buf.push(id.get()); - } - } - } - } - } - } - - // Load entities from the new visible layers. - for &layer in &visible_entity_layers.0 { - if let Ok(layer) = entity_layers.get(layer) { - for pos in view.diff(old_view) { - for entity in layer.entities_at(pos) { - if self_entity != entity { - if let Ok((init, pos)) = entity_init.get(entity) { - init.write_init_packets(pos.get(), &mut *client); - } - } - } - } - } - } - } - } - - // Update the old layers. - - old_chunk_layer.0 = chunk_layer.0; - - if visible_entity_layers.is_changed() { - old_visible_entity_layers - .0 - .clone_from(&visible_entity_layers.0); - } - }, - ); -}*/ - -pub(crate) fn update_game_mode(mut clients: Query<(&mut Client, &GameMode), Changed>) { - for (mut client, game_mode) in &mut clients { - if client.is_added() { - // Game join packet includes the initial game mode. - continue; } - - client.write_packet(&GameStateChangeS2c { - kind: GameEventKind::ChangeGameMode, - value: *game_mode as i32 as f32, - }) } } @@ -1097,7 +533,7 @@ fn flush_packets( } } -fn init_tracked_data(mut clients: Query<(&mut Client, &TrackedData), Added>) { +fn init_self_tracked_data(mut clients: Query<(&mut Client, &TrackedData), Added>) { for (mut client, tracked_data) in &mut clients { if let Some(init_data) = tracked_data.init_data() { client.write_packet(&EntityTrackerUpdateS2c { @@ -1108,7 +544,7 @@ fn init_tracked_data(mut clients: Query<(&mut Client, &TrackedData), Added) { +fn update_self_tracked_data(mut clients: Query<(&mut Client, &TrackedData)>) { for (mut client, tracked_data) in &mut clients { if let Some(update_data) = tracked_data.update_data() { client.write_packet(&EntityTrackerUpdateS2c { @@ -1118,21 +554,3 @@ fn update_tracked_data(mut clients: Query<(&mut Client, &TrackedData)>) { } } } - -/* -/// Decrement viewer count of chunks when the client is despawned. -fn cleanup_chunks_after_client_despawn( - mut clients: Query<(View, &VisibleChunkLayer), (With, With)>, - chunk_layers: Query<&ChunkLayer>, -) { - for (view, layer) in &mut clients { - if let Ok(layer) = chunk_layers.get(layer.0) { - for pos in view.get().iter() { - if let Some(chunk) = layer.chunk(pos) { - chunk.dec_viewer_count(); - } - } - } - } -} -*/ diff --git a/crates/valence_server/src/dimension_layer.rs b/crates/valence_server/src/dimension_layer.rs index d59b8743c..48966c522 100644 --- a/crates/valence_server/src/dimension_layer.rs +++ b/crates/valence_server/src/dimension_layer.rs @@ -7,7 +7,7 @@ mod plugin; use bevy_ecs::prelude::*; use bevy_ecs::query::WorldQuery; use block::BlockRef; -use chunk::LoadedChunk; +pub use chunk::{Chunk, LoadedChunk}; pub use index::ChunkIndex; pub use plugin::*; use valence_protocol::packets::play::UnloadChunkS2c; @@ -19,7 +19,6 @@ use valence_server_common::Server; use self::batch::BlockBatch; use self::block::Block; -use self::chunk::Chunk; use crate::layer::message::{LayerMessages, MessageScope}; use crate::layer::{ChunkViewIndex, LayerViewers}; diff --git a/crates/valence_server/src/dimension_layer/index.rs b/crates/valence_server/src/dimension_layer/index.rs index c3010b1b4..c79e437d4 100644 --- a/crates/valence_server/src/dimension_layer/index.rs +++ b/crates/valence_server/src/dimension_layer/index.rs @@ -16,7 +16,7 @@ pub struct ChunkIndex { } impl ChunkIndex { - pub(super) fn new(height: i32) -> Self { + pub(crate) fn new(height: i32) -> Self { Self { map: Default::default(), height, diff --git a/crates/valence_server/src/dimension_layer/plugin.rs b/crates/valence_server/src/dimension_layer/plugin.rs index a9952c60e..bb3eae1a1 100644 --- a/crates/valence_server/src/dimension_layer/plugin.rs +++ b/crates/valence_server/src/dimension_layer/plugin.rs @@ -1,13 +1,15 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use valence_entity::Position; -use valence_protocol::packets::play::{ChunkRenderDistanceCenterS2c, UnloadChunkS2c}; -use valence_protocol::WritePacket; +use valence_protocol::packets::play::{ + ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, UnloadChunkS2c, +}; +use valence_protocol::{VarInt, WritePacket}; use valence_server_common::Despawned; use super::{ChunkIndex, DimensionInfo}; use crate::client::{Client, ClientMarker, OldView, View}; -use crate::layer::{OldVisibleLayers, VisibleLayers}; +use crate::layer::{BroadcastLayerMessagesSet, OldVisibleLayers, VisibleLayers}; pub struct DimensionLayerPlugin; @@ -18,13 +20,22 @@ pub struct UpdateDimensionLayerSet; impl Plugin for DimensionLayerPlugin { fn build(&self, app: &mut App) { - todo!() + app.configure_set( + PostUpdate, + UpdateDimensionLayerSet.before(BroadcastLayerMessagesSet), + ) + .add_systems( + PostUpdate, + ( + update_dimension_layer_views, + update_dimension_layer_views_client_despawn, + ) + .chain() + .in_set(UpdateDimensionLayerSet), + ); } } -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct UpdateEntityLayers; - fn update_dimension_layer_views( mut clients: Query< ( @@ -52,6 +63,14 @@ fn update_dimension_layer_views( }); } + // Update view distance fog. + // Note: this is just aesthetic. + if old_view.dist() != view.dist() { + client.write_packet(&ChunkLoadDistanceS2c { + view_distance: VarInt(view.dist().into()), + }); + } + let mut changed_dimension = false; if visible.is_changed() { diff --git a/crates/valence_server/src/entity_layer.rs b/crates/valence_server/src/entity_layer.rs index add3a30ce..6497620af 100644 --- a/crates/valence_server/src/entity_layer.rs +++ b/crates/valence_server/src/entity_layer.rs @@ -1,15 +1,20 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; -pub struct EntityLayerPlugin; +use crate::layer::BroadcastLayerMessagesSet; -impl Plugin for EntityLayerPlugin { - fn build(&self, app: &mut App) { - todo!() - } -} +pub struct EntityLayerPlugin; /// When entity changes are written to entity layers and clients are sent /// spawn/despawn packets. #[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct UpdateEntityLayerSet; + +impl Plugin for EntityLayerPlugin { + fn build(&self, app: &mut App) { + app.configure_set( + PostUpdate, + UpdateEntityLayerSet.before(BroadcastLayerMessagesSet), + ); + } +} diff --git a/crates/valence_server/src/game_mode.rs b/crates/valence_server/src/game_mode.rs new file mode 100644 index 000000000..8f0fe826b --- /dev/null +++ b/crates/valence_server/src/game_mode.rs @@ -0,0 +1,35 @@ +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +use valence_protocol::packets::play::game_state_change_s2c::GameEventKind; +use valence_protocol::packets::play::GameStateChangeS2c; +pub use valence_protocol::GameMode; +use valence_protocol::WritePacket; + +use crate::client::FlushPacketsSet; +use crate::Client; + +pub struct UpdateGameModePlugin; + +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct UpdateGameModeSet; + +impl Plugin for UpdateGameModePlugin { + fn build(&self, app: &mut App) { + app.configure_set(PostUpdate, UpdateGameModeSet.before(FlushPacketsSet)) + .add_systems(PostUpdate, update_game_mode.in_set(UpdateGameModeSet)); + } +} + +pub(crate) fn update_game_mode(mut clients: Query<(&mut Client, &GameMode), Changed>) { + for (mut client, game_mode) in &mut clients { + if client.is_added() { + // Game join packet includes the initial game mode. + continue; + } + + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::ChangeGameMode, + value: *game_mode as i32 as f32, + }); + } +} diff --git a/crates/valence_server/src/keepalive.rs b/crates/valence_server/src/keepalive.rs index 1f5570d4e..49d04c697 100644 --- a/crates/valence_server/src/keepalive.rs +++ b/crates/valence_server/src/keepalive.rs @@ -7,15 +7,19 @@ use tracing::warn; use valence_protocol::packets::play::{KeepAliveC2s, KeepAliveS2c}; use valence_protocol::WritePacket; -use crate::client::{Client, UpdateClientsSet}; +use crate::client::{Client, FlushPacketsSet}; use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; pub struct KeepalivePlugin; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct SendKeepaliveSet; + impl Plugin for KeepalivePlugin { fn build(&self, app: &mut App) { app.init_resource::() - .add_systems(PostUpdate, send_keepalive.in_set(UpdateClientsSet)) + .configure_set(PostUpdate, SendKeepaliveSet.before(FlushPacketsSet)) + .add_systems(PostUpdate, send_keepalive.in_set(SendKeepaliveSet)) .add_systems(EventLoopPreUpdate, handle_keepalive_response); } } diff --git a/crates/valence_server/src/layer.rs b/crates/valence_server/src/layer.rs index a048b6d9e..53d2f4a55 100644 --- a/crates/valence_server/src/layer.rs +++ b/crates/valence_server/src/layer.rs @@ -9,12 +9,17 @@ use bevy_ecs::query::Has; pub use chunk_view_index::ChunkViewIndex; use derive_more::{Deref, DerefMut}; use valence_entity::{OldPosition, Position}; -use valence_protocol::ChunkPos; -use valence_server_common::Despawned; - -use self::message::LayerMessages; +use valence_protocol::{ChunkPos, WritePacket}; +use valence_registry::dimension_type::DimensionTypeId; +use valence_registry::{BiomeRegistry, DimensionTypeRegistry}; +use valence_server_common::{Despawned, Server}; + +use self::message::{LayerMessages, MessageKind}; +use crate::client::FlushPacketsSet; +use crate::dimension_layer::batch::BlockBatch; +use crate::dimension_layer::{ChunkIndex, DimensionInfo}; use crate::layer::message::MessageScope; -use crate::Client; +use crate::{Client, DimensionLayerBundle}; /// Enables core functionality for layers. pub struct LayerPlugin; @@ -26,13 +31,59 @@ pub struct BroadcastLayerMessagesSet; impl Plugin for LayerPlugin { fn build(&self, app: &mut App) { - todo!() + app.configure_set( + PostUpdate, + BroadcastLayerMessagesSet.before(FlushPacketsSet), + ) + .add_systems( + PostUpdate, + ( + update_view_index, + update_old_visible_layers, + broadcast_layer_messages, + ) + .chain() + .in_set(BroadcastLayerMessagesSet), + ); } } #[derive(Bundle)] pub struct DimensionEntityLayerBundle { - // TODO + pub chunk_index: ChunkIndex, + pub block_batch: BlockBatch, + pub dimension_info: DimensionInfo, + // TODO: entity layer components. + pub chunk_view_index: ChunkViewIndex, + pub layer_viewers: LayerViewers, + pub layer_messages: LayerMessages, +} + +impl DimensionEntityLayerBundle { + pub fn new( + dimension_type: DimensionTypeId, + dimensions: &DimensionTypeRegistry, + biomes: &BiomeRegistry, + server: &Server, + ) -> Self { + let DimensionLayerBundle { + chunk_index, + block_batch, + dimension_info, + chunk_view_index, + layer_viewers, + layer_messages, + } = DimensionLayerBundle::new(dimension_type, dimensions, biomes, server); + + Self { + chunk_index, + block_batch, + dimension_info, + chunk_view_index, + layer_viewers, + layer_messages, + } + } } /// The set of layers a client is viewing. @@ -50,7 +101,7 @@ pub struct OldVisibleLayers(BTreeSet); pub struct LayerViewers(BTreeSet); fn update_view_index( - mut clients: Query<( + clients: Query<( Entity, Has, &OldPosition, @@ -123,33 +174,21 @@ fn update_old_visible_layers( } } -// fn remove_despawned_from_chunk_view_index( -// mut layers: Query<(&mut ChunkViewIndex, &mut LayerViewers)>, -// clients: Query<(Entity, &OldPosition, &OldVisibleLayers), -// (With, With)>, -// ) { for (client, pos, visible_layers) in &clients { let pos = -// ChunkPos::from(pos.get()); - -// for &layer in visible_layers.iter() { -// if let Ok((mut index, mut viewers)) = layers.get_mut(layer) { -// index.remove(pos, client); -// viewers.remove(&client); -// } -// } -// } -// } - fn broadcast_layer_messages( mut layers: Query<(&mut LayerMessages, &LayerViewers, &ChunkViewIndex)>, - mut clients: Query<(&mut Client, &OldPosition, &Position)>, + mut clients: Query<&mut Client>, ) { - for (mut messages, viewers, index) in &mut layers { + for (mut messages, viewers, view_index) in &mut layers { + let mut acc = 0; + for (scope, kind) in messages.messages() { let mut send = |client: Entity| { - if let Ok((client, old_pos, pos)) = clients.get_mut(client) { + if let Ok(mut client) = clients.get_mut(client) { match kind { - message::MessageKind::Packet { len } => todo!(), - message::MessageKind::EntityDespawn { entity } => todo!(), + MessageKind::Packet { len } => { + client.write_packet_bytes(&messages.bytes()[acc..acc + len]); + } + MessageKind::EntityDespawn { entity } => todo!(), } } }; @@ -162,12 +201,18 @@ fn broadcast_layer_messages( .copied() .filter(|&c| c != except) .for_each(send), - MessageScope::ChunkView { pos } => todo!(), - MessageScope::ChunkViewExcept { pos, except } => todo!(), - MessageScope::TransitionChunkView { old_pos, pos } => todo!(), + MessageScope::ChunkView { pos } => view_index.get(pos).for_each(send), + MessageScope::ChunkViewExcept { pos, except } => { + view_index.get(pos).filter(|&e| e != except).for_each(send) + } + MessageScope::TransitionChunkView { include, exclude } => todo!(), + } + + if let MessageKind::Packet { len } = kind { + acc += len; } } - todo!(); + messages.clear(); } } diff --git a/crates/valence_server/src/layer/message.rs b/crates/valence_server/src/layer/message.rs index eb4f585c9..868e83f47 100644 --- a/crates/valence_server/src/layer/message.rs +++ b/crates/valence_server/src/layer/message.rs @@ -39,9 +39,11 @@ pub enum MessageScope { pos: ChunkPos, except: Entity, }, + /// All clients in view of `include` but _not_ in view of `exclude` will + /// receive the message. TransitionChunkView { - old_pos: ChunkPos, - pos: ChunkPos, + include: ChunkPos, + exclude: ChunkPos, }, } @@ -96,7 +98,7 @@ impl LayerMessages { } } - writer.write_packet_fallible(packet); + writer.write_packet_fallible(packet)?; let end = writer.buf.len(); self.messages diff --git a/crates/valence_server/src/lib.rs b/crates/valence_server/src/lib.rs index ba692ee6c..6e77dd61a 100644 --- a/crates/valence_server/src/lib.rs +++ b/crates/valence_server/src/lib.rs @@ -28,6 +28,7 @@ pub mod custom_payload; pub mod dimension_layer; pub mod entity_layer; pub mod event_loop; +pub mod game_mode; pub mod hand_swing; pub mod interact_block; pub mod interact_entity; @@ -44,8 +45,9 @@ pub mod title; pub use chunk_view::ChunkView; pub use client::Client; -pub use dimension_layer::DimensionLayerBundle; +pub use dimension_layer::{Chunk, DimensionLayerBundle, LoadedChunk}; pub use event_loop::{EventLoopPostUpdate, EventLoopPreUpdate, EventLoopUpdate}; +pub use layer::DimensionEntityLayerBundle; pub use valence_protocol::{ block, ident, item, math, text, uuid, BiomePos, BlockPos, BlockState, ChunkPos, CompressionThreshold, Difficulty, Direction, GameMode, Hand, Ident, ItemKind, ItemStack, Text, diff --git a/crates/valence_server/src/movement.rs b/crates/valence_server/src/movement.rs index e5625ef8e..ff941c870 100644 --- a/crates/valence_server/src/movement.rs +++ b/crates/valence_server/src/movement.rs @@ -10,12 +10,13 @@ use valence_protocol::packets::play::{ }; use valence_protocol::{BlockPos, WritePacket}; +use crate::client::FlushPacketsSet; use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; use crate::layer::BroadcastLayerMessagesSet; use crate::Client; /// Handles client movement and teleports. -pub struct PositionPlugin; +pub struct MovementPlugin; /// When client positions are synchronized by sending the clientbound position /// packet. This set also includes the system that updates the client's respawn @@ -23,7 +24,7 @@ pub struct PositionPlugin; #[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct SyncPositionSet; -impl Plugin for PositionPlugin { +impl Plugin for MovementPlugin { fn build(&self, app: &mut App) { app .init_resource::() @@ -31,7 +32,7 @@ impl Plugin for PositionPlugin { .add_systems(EventLoopPreUpdate, (handle_teleport_confirmations, handle_client_movement)) // Sync position after chunks are loaded so the client doesn't fall through the floor. // Setting the respawn position also closes the "downloading terrain" screen. - .configure_set(PostUpdate, SyncPositionSet.after(BroadcastLayerMessagesSet)) + .configure_set(PostUpdate, SyncPositionSet.after(BroadcastLayerMessagesSet).before(FlushPacketsSet)) .add_systems(PostUpdate, (update_respawn_position, teleport).chain().in_set(SyncPositionSet)); } } diff --git a/crates/valence_server/src/op_level.rs b/crates/valence_server/src/op_level.rs index 55da0b13c..29442ff38 100644 --- a/crates/valence_server/src/op_level.rs +++ b/crates/valence_server/src/op_level.rs @@ -4,13 +4,17 @@ use derive_more::Deref; use valence_protocol::packets::play::EntityStatusS2c; use valence_protocol::WritePacket; -use crate::client::{Client, UpdateClientsSet}; +use crate::client::{Client, FlushPacketsSet}; pub struct OpLevelPlugin; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct UpdateOpLevelSet; + impl Plugin for OpLevelPlugin { fn build(&self, app: &mut App) { - app.add_systems(PostUpdate, update_op_level.in_set(UpdateClientsSet)); + app.configure_set(PostUpdate, UpdateOpLevelSet.before(FlushPacketsSet)) + .add_systems(PostUpdate, update_op_level.in_set(UpdateOpLevelSet)); } } diff --git a/crates/valence_server/src/spawn.rs b/crates/valence_server/src/spawn.rs index b0330f214..00222c960 100644 --- a/crates/valence_server/src/spawn.rs +++ b/crates/valence_server/src/spawn.rs @@ -7,14 +7,14 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_ecs::query::WorldQuery; use derive_more::{Deref, DerefMut}; -use valence_entity::EntityLayerId; use valence_protocol::packets::play::{GameJoinS2c, PlayerRespawnS2c}; use valence_protocol::{BlockPos, GameMode, GlobalPos, Ident, VarInt, WritePacket}; use valence_registry::tags::TagsRegistry; use valence_registry::{DimensionTypeRegistry, RegistryCodec, UpdateRegistrySet}; -use crate::client::{Client, FlushPacketsSet, ViewDistance, VisibleChunkLayer}; -use crate::dimension_layer::{ChunkIndex, DimensionInfo, UpdateDimensionLayerSet}; +use crate::client::{Client, FlushPacketsSet, ViewDistance}; +use crate::dimension_layer::{DimensionInfo, UpdateDimensionLayerSet}; +use crate::layer::VisibleLayers; /// Handles spawning and respawning of clients. pub struct SpawnPlugin; @@ -22,19 +22,24 @@ pub struct SpawnPlugin; /// When clients are sent the "respawn" packet after their dimension layer has /// changed. #[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct RespawnSystemSet; +pub struct RespawnSet; + +/// When the initial join packets are written to clients. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct JoinGameSet; impl Plugin for SpawnPlugin { fn build(&self, app: &mut App) { app // Send the respawn packet before chunks are sent. - .configure_set(PostUpdate, RespawnSystemSet.before(UpdateDimensionLayerSet)) - .add_systems(PostUpdate, respawn.in_set(RespawnSystemSet)) + .configure_set(PostUpdate, RespawnSet.before(UpdateDimensionLayerSet)) + .add_systems(PostUpdate, respawn.in_set(RespawnSet)) // The join game packet is prepended to the client's packet buffer, so // it can be sent any time before packets are flushed. Additionally, // this must be scheduled after registries are updated because we read // the cached packets. - .add_systems(PostUpdate, initial_join.after(UpdateRegistrySet).before(FlushPacketsSet)); + .configure_set(PostUpdate, JoinGameSet.after(UpdateRegistrySet).before(FlushPacketsSet)) + .add_systems(PostUpdate, initial_join.in_set(JoinGameSet)); } } @@ -105,14 +110,17 @@ pub struct PortalCooldown(pub i32); pub struct PrevGameMode(pub Option); pub(super) fn initial_join( - mut clients: Query<(&mut Client, &VisibleChunkLayer, ClientSpawnQueryReadOnly), Added>, + mut clients: Query<(&mut Client, &VisibleLayers, ClientSpawnQueryReadOnly), Added>, codec: Res, tags: Res, dimensions: Res, - dimension_layers: Query<(&ChunkIndex, &DimensionInfo)>, + dimension_layers: Query<&DimensionInfo>, ) { - for (mut client, visible_chunk_layer, spawn) in &mut clients { - let Ok((chunk_index, info)) = dimension_layers.get(visible_chunk_layer.0) else { + for (mut client, vis_layers, spawn) in &mut clients { + let Some(info) = vis_layers + .iter() + .find_map(|&layer| dimension_layers.get(layer).ok()) + else { continue; }; @@ -162,7 +170,7 @@ fn respawn( mut clients: Query< ( &mut Client, - &EntityLayerId, + &VisibleLayers, &DeathLocation, &HashedSeed, &GameMode, @@ -170,20 +178,31 @@ fn respawn( &IsDebug, &IsFlat, ), - Changed, + Changed, >, - chunk_layers: Query<(&ChunkIndex, &DimensionInfo)>, + dimension_layers: Query<&DimensionInfo>, dimensions: Res, ) { - for (mut client, loc, death_loc, hashed_seed, game_mode, prev_game_mode, is_debug, is_flat) in - &mut clients + for ( + mut client, + vis_layers, + death_loc, + hashed_seed, + game_mode, + prev_game_mode, + is_debug, + is_flat, + ) in &mut clients { if client.is_added() { // No need to respawn since we are sending the game join packet this tick. continue; } - let Ok((chunk_index, info)) = chunk_layers.get(loc.0) else { + let Some(info) = vis_layers + .iter() + .find_map(|&layer| dimension_layers.get(layer).ok()) + else { continue; }; diff --git a/crates/valence_weather/src/lib.rs b/crates/valence_weather/src/lib.rs index 49fbe6b45..64c47bbdb 100644 --- a/crates/valence_weather/src/lib.rs +++ b/crates/valence_weather/src/lib.rs @@ -21,35 +21,39 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use derive_more::{Deref, DerefMut}; -use valence_server::client::{Client, FlushPacketsSet, UpdateClientsSet, VisibleChunkLayer}; +use valence_server::client::Client; +use valence_server::layer::message::LayerMessages; +use valence_server::layer::{BroadcastLayerMessagesSet, OldVisibleLayers, VisibleLayers}; use valence_server::protocol::packets::play::game_state_change_s2c::GameEventKind; use valence_server::protocol::packets::play::GameStateChangeS2c; use valence_server::protocol::WritePacket; -use valence_server::ChunkLayer; pub struct WeatherPlugin; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct UpdateWeatherSet; + impl Plugin for WeatherPlugin { fn build(&self, app: &mut App) { - app.add_systems( + app.configure_set( PostUpdate, - ( - init_weather_on_layer_join, - change_client_rain_level, - change_client_thunder_level, - ) - .before(FlushPacketsSet), + UpdateWeatherSet.before(BroadcastLayerMessagesSet), ) .add_systems( PostUpdate, - (change_layer_rain_level, change_layer_thunder_level).before(UpdateClientsSet), + ( + init_weather_on_layer_join, + update_rain_level, + update_thunder_level, + ) + .in_set(UpdateWeatherSet), ); } } -/// Bundle containing rain and thunder components. `valence_weather` allows this -/// to be added to clients and chunk layer entities. -#[derive(Bundle, Default, PartialEq, PartialOrd)] +/// Bundle containing rain and thunder components. This can be added to any +/// layer. +#[derive(Bundle, Default, Debug)] pub struct WeatherBundle { pub rain: Rain, pub thunder: Thunder, @@ -57,44 +61,41 @@ pub struct WeatherBundle { /// Component containing the rain level. Valid values are in \[0, 1] with 0 /// being no rain and 1 being full rain. -#[derive(Component, Default, PartialEq, PartialOrd, Deref, DerefMut)] +#[derive(Component, Default, PartialEq, PartialOrd, Deref, DerefMut, Debug)] pub struct Rain(pub f32); /// Component containing the thunder level. Valid values are in \[0, 1] with 0 /// being no rain and 1 being full rain. -#[derive(Component, Default, PartialEq, PartialOrd, Deref, DerefMut)] +#[derive(Component, Default, PartialEq, PartialOrd, Deref, DerefMut, Debug)] pub struct Thunder(pub f32); fn init_weather_on_layer_join( - mut clients: Query<(&mut Client, &VisibleChunkLayer), Changed>, - layers: Query<(Option<&Rain>, Option<&Thunder>), With>, + mut clients: Query<(&mut Client, &VisibleLayers, &OldVisibleLayers), Changed>, + layers: Query<(Option<&Rain>, Option<&Thunder>)>, ) { - for (mut client, visible_chunk_layer) in &mut clients { - if let Ok((rain, thunder)) = layers.get(visible_chunk_layer.0) { + for (mut client, vis_layers, old_vis_layers) in &mut clients { + if let Some((rain, thunder)) = vis_layers + .difference(old_vis_layers) + .find_map(|&layer| layers.get(layer).ok()) + { if let Some(rain) = rain { - if rain.0 != 0.0 { - client.write_packet(&GameStateChangeS2c { - kind: GameEventKind::RainLevelChange, - value: rain.0, - }); - } + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::RainLevelChange, + value: rain.0, + }); } if let Some(thunder) = thunder { - if thunder.0 != 0.0 { - client.write_packet(&GameStateChangeS2c { - kind: GameEventKind::ThunderLevelChange, - value: thunder.0, - }); - } + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::ThunderLevelChange, + value: thunder.0, + }); } } } } -fn change_layer_rain_level( - mut layers: Query<(&mut ChunkLayer, &Rain), (Changed, Without)>, -) { +fn update_rain_level(mut layers: Query<(&mut LayerMessages, &Rain), Changed>) { for (mut layer, rain) in &mut layers { layer.write_packet(&GameStateChangeS2c { kind: GameEventKind::RainLevelChange, @@ -103,9 +104,7 @@ fn change_layer_rain_level( } } -fn change_layer_thunder_level( - mut layers: Query<(&mut ChunkLayer, &Thunder), (Changed, Without)>, -) { +fn update_thunder_level(mut layers: Query<(&mut LayerMessages, &Thunder), Changed>) { for (mut layer, thunder) in &mut layers { layer.write_packet(&GameStateChangeS2c { kind: GameEventKind::ThunderLevelChange, @@ -113,21 +112,3 @@ fn change_layer_thunder_level( }); } } - -fn change_client_rain_level(mut clients: Query<(&mut Client, &Rain), Changed>) { - for (mut client, rain) in &mut clients { - client.write_packet(&GameStateChangeS2c { - kind: GameEventKind::RainLevelChange, - value: rain.0, - }); - } -} - -fn change_client_thunder_level(mut clients: Query<(&mut Client, &Thunder), Changed>) { - for (mut client, thunder) in &mut clients { - client.write_packet(&GameStateChangeS2c { - kind: GameEventKind::RainLevelChange, - value: thunder.0, - }); - } -} diff --git a/crates/valence_world_border/src/lib.rs b/crates/valence_world_border/src/lib.rs index c5553627c..ab56f92e6 100644 --- a/crates/valence_world_border/src/lib.rs +++ b/crates/valence_world_border/src/lib.rs @@ -21,14 +21,16 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use derive_more::{Deref, DerefMut}; -use valence_server::client::{Client, UpdateClientsSet, VisibleChunkLayer}; +use valence_server::client::Client; +use valence_server::layer::message::LayerMessages; +use valence_server::layer::{BroadcastLayerMessagesSet, OldVisibleLayers, VisibleLayers}; use valence_server::protocol::packets::play::{ WorldBorderCenterChangedS2c, WorldBorderInitializeS2c, WorldBorderInterpolateSizeS2c, WorldBorderSizeChangedS2c, WorldBorderWarningBlocksChangedS2c, WorldBorderWarningTimeChangedS2c, }; use valence_server::protocol::WritePacket; -use valence_server::{ChunkLayer, Server}; +use valence_server::Server; // https://minecraft.fandom.com/wiki/World_border pub const DEFAULT_PORTAL_LIMIT: i32 = 29999984; @@ -43,24 +45,27 @@ pub struct UpdateWorldBorderSet; impl Plugin for WorldBorderPlugin { fn build(&self, app: &mut App) { - app.configure_set(PostUpdate, UpdateWorldBorderSet.before(UpdateClientsSet)) - .add_systems( - PostUpdate, - ( - init_world_border_for_new_clients, - tick_world_border_lerp, - change_world_border_center, - change_world_border_warning_blocks, - change_world_border_warning_time, - change_world_border_portal_tp_boundary, - ) - .in_set(UpdateWorldBorderSet), - ); + app.configure_set( + PostUpdate, + UpdateWorldBorderSet.before(BroadcastLayerMessagesSet), + ) + .add_systems( + PostUpdate, + ( + init_world_border_on_layer_join, + tick_world_border_lerp, + update_world_border_center, + update_world_border_warning_blocks, + update_world_border_warning_time, + update_world_border_portal_tp_boundary, + ) + .in_set(UpdateWorldBorderSet), + ); } } /// A bundle containing necessary components to enable world border -/// functionality. Add this to an entity with the [`ChunkLayer`] component. +/// functionality. This can be added to "layer" entities. #[derive(Bundle, Default, Debug)] pub struct WorldBorderBundle { pub center: WorldBorderCenter, @@ -97,6 +102,7 @@ pub struct WorldBorderLerp { /// automatically. pub remaining_ticks: u64, } + #[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deref, DerefMut)] pub struct WorldBorderWarnTime(pub i32); @@ -134,9 +140,9 @@ impl Default for WorldBorderLerp { } } -fn init_world_border_for_new_clients( - mut clients: Query<(&mut Client, &VisibleChunkLayer), Changed>, - wbs: Query<( +fn init_world_border_on_layer_join( + mut clients: Query<(&mut Client, &VisibleLayers, &OldVisibleLayers), Changed>, + layers: Query<( &WorldBorderCenter, &WorldBorderLerp, &WorldBorderPortalTpBoundary, @@ -145,8 +151,11 @@ fn init_world_border_for_new_clients( )>, server: Res, ) { - for (mut client, layer) in &mut clients { - if let Ok((center, lerp, portal_tp_boundary, warn_time, warn_blocks)) = wbs.get(layer.0) { + for (mut client, vis_layers, old_vis_layers) in &mut clients { + if let Some((center, lerp, portal_tp_boundary, warn_time, warn_blocks)) = vis_layers + .difference(old_vis_layers) + .find_map(|&layer| layers.get(layer).ok()) + { let millis = lerp.remaining_ticks as i64 * 1000 / server.tick_rate().get() as i64; client.write_packet(&WorldBorderInitializeS2c { @@ -164,13 +173,13 @@ fn init_world_border_for_new_clients( } fn tick_world_border_lerp( - mut wbs: Query<(&mut ChunkLayer, &mut WorldBorderLerp)>, + mut layers: Query<(&mut LayerMessages, &mut WorldBorderLerp)>, server: Res, ) { - for (mut layer, mut lerp) in &mut wbs { + for (mut msgs, mut lerp) in &mut layers { if lerp.is_changed() { if lerp.remaining_ticks == 0 { - layer.write_packet(&WorldBorderSizeChangedS2c { + msgs.write_packet(&WorldBorderSizeChangedS2c { diameter: lerp.target_diameter, }); @@ -178,7 +187,7 @@ fn tick_world_border_lerp( } else { let millis = lerp.remaining_ticks as i64 * 1000 / server.tick_rate().get() as i64; - layer.write_packet(&WorldBorderInterpolateSizeS2c { + msgs.write_packet(&WorldBorderInterpolateSizeS2c { old_diameter: lerp.current_diameter, new_diameter: lerp.target_diameter, duration_millis: millis.into(), @@ -195,41 +204,41 @@ fn tick_world_border_lerp( } } -fn change_world_border_center( - mut wbs: Query<(&mut ChunkLayer, &WorldBorderCenter), Changed>, +fn update_world_border_center( + mut layers: Query<(&mut LayerMessages, &WorldBorderCenter), Changed>, ) { - for (mut layer, center) in &mut wbs { - layer.write_packet(&WorldBorderCenterChangedS2c { + for (mut msgs, center) in &mut layers { + msgs.write_packet(&WorldBorderCenterChangedS2c { x_pos: center.x, z_pos: center.z, }); } } -fn change_world_border_warning_blocks( - mut wbs: Query<(&mut ChunkLayer, &WorldBorderWarnBlocks), Changed>, +fn update_world_border_warning_blocks( + mut layers: Query<(&mut LayerMessages, &WorldBorderWarnBlocks), Changed>, ) { - for (mut layer, warn_blocks) in &mut wbs { - layer.write_packet(&WorldBorderWarningBlocksChangedS2c { + for (mut msgs, warn_blocks) in &mut layers { + msgs.write_packet(&WorldBorderWarningBlocksChangedS2c { warning_blocks: warn_blocks.0.into(), }); } } -fn change_world_border_warning_time( - mut wbs: Query<(&mut ChunkLayer, &WorldBorderWarnTime), Changed>, +fn update_world_border_warning_time( + mut layers: Query<(&mut LayerMessages, &WorldBorderWarnTime), Changed>, ) { - for (mut layer, warn_time) in &mut wbs { - layer.write_packet(&WorldBorderWarningTimeChangedS2c { + for (mut msgs, warn_time) in &mut layers { + msgs.write_packet(&WorldBorderWarningTimeChangedS2c { warning_time: warn_time.0.into(), }); } } -fn change_world_border_portal_tp_boundary( - mut wbs: Query< +fn update_world_border_portal_tp_boundary( + mut layers: Query< ( - &mut ChunkLayer, + &mut LayerMessages, &WorldBorderCenter, &WorldBorderLerp, &WorldBorderPortalTpBoundary, @@ -240,10 +249,10 @@ fn change_world_border_portal_tp_boundary( >, server: Res, ) { - for (mut layer, center, lerp, portal_tp_boundary, warn_time, warn_blocks) in &mut wbs { + for (mut msgs, center, lerp, portal_tp_boundary, warn_time, warn_blocks) in &mut layers { let millis = lerp.remaining_ticks as i64 * 1000 / server.tick_rate().get() as i64; - layer.write_packet(&WorldBorderInitializeS2c { + msgs.write_packet(&WorldBorderInitializeS2c { x: center.x, z: center.z, old_diameter: lerp.current_diameter, diff --git a/examples/bench_players.rs b/examples/bench_players.rs index 859db08bb..9cb400c66 100644 --- a/examples/bench_players.rs +++ b/examples/bench_players.rs @@ -26,7 +26,7 @@ fn main() { .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(First, record_tick_start_time) - .add_systems(Update, (init_clients, despawn_disconnected_clients)) + .add_systems(Update, init_clients) .add_systems(Last, print_tick_time) .run(); } @@ -51,18 +51,19 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = + DimensionEntityLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in -50..50 { for x in -50..50 { layer - .chunk + .chunk_index .set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK); } } diff --git a/src/lib.rs b/src/lib.rs index 7d0867656..25c8add01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,20 +59,23 @@ use valence_server::client::ClientPlugin; use valence_server::client_command::ClientCommandPlugin; use valence_server::client_settings::ClientSettingsPlugin; use valence_server::custom_payload::CustomPayloadPlugin; +use valence_server::dimension_layer::DimensionLayerPlugin; use valence_server::entity::hitbox::HitboxPlugin; use valence_server::entity::EntityPlugin; +use valence_server::entity_layer::EntityLayerPlugin; use valence_server::event_loop::EventLoopPlugin; +use valence_server::game_mode::UpdateGameModePlugin; use valence_server::hand_swing::HandSwingPlugin; use valence_server::interact_block::InteractBlockPlugin; use valence_server::interact_entity::InteractEntityPlugin; use valence_server::interact_item::InteractItemPlugin; use valence_server::keepalive::KeepalivePlugin; -use valence_server::layer_old::LayerPlugin; +use valence_server::layer::LayerPlugin; use valence_server::message::MessagePlugin; use valence_server::movement::MovementPlugin; use valence_server::op_level::OpLevelPlugin; -use valence_server::position::TeleportPlugin; use valence_server::resource_pack::ResourcePackPlugin; +use valence_server::spawn::SpawnPlugin; use valence_server::status::StatusPlugin; pub use valence_server::*; #[cfg(feature = "weather")] @@ -117,8 +120,7 @@ pub mod prelude { pub use valence_server::action::{DiggingEvent, DiggingState}; pub use valence_server::block::{BlockKind, BlockState, PropName, PropValue}; pub use valence_server::client::{ - despawn_disconnected_clients, Client, Ip, OldView, OldViewDistance, Properties, Username, - View, ViewDistance, VisibleChunkLayer, VisibleEntityLayers, + Client, Ip, OldView, OldViewDistance, Properties, Username, View, ViewDistance, }; pub use valence_server::client_command::{ ClientCommand, JumpWithHorseEvent, JumpWithHorseState, LeaveBedEvent, SneakEvent, @@ -134,20 +136,17 @@ pub mod prelude { }; pub use valence_server::ident::Ident; pub use valence_server::interact_entity::{EntityInteraction, InteractEntityEvent}; - pub use valence_server::layer_old::chunk::{ - Block, BlockRef, Chunk, ChunkLayer, ChunkOps, LoadedChunk, - }; - pub use valence_server::layer_old::{EntityLayer, LayerBundle}; pub use valence_server::math::{DVec2, DVec3, Vec2, Vec3}; pub use valence_server::message::SendMessage as _; + pub use valence_server::movement::{MovementEvent, RespawnPosition}; pub use valence_server::nbt::Compound; pub use valence_server::protocol::packets::play::particle_s2c::Particle; pub use valence_server::protocol::text::{Color, IntoText, Text}; - pub use valence_server::spawn::{ClientSpawnQuery, ClientSpawnQueryReadOnly, RespawnPosition}; + pub use valence_server::spawn::{ClientSpawnQuery, ClientSpawnQueryReadOnly}; pub use valence_server::title::SetTitle as _; pub use valence_server::{ - ident, BlockPos, ChunkPos, ChunkView, Despawned, Direction, GameMode, Hand, ItemKind, - ItemStack, Server, UniqueId, + ident, BlockPos, Chunk, ChunkPos, ChunkView, Despawned, DimensionEntityLayerBundle, + Direction, GameMode, Hand, ItemKind, ItemStack, LoadedChunk, Server, UniqueId, }; pub use super::DefaultPlugins; @@ -169,18 +168,21 @@ impl PluginGroup for DefaultPlugins { .add(RegistryPlugin) .add(BiomePlugin) .add(DimensionTypePlugin) + .add(LayerPlugin) + .add(DimensionLayerPlugin) + .add(EntityLayerPlugin) .add(EntityPlugin) .add(HitboxPlugin) - .add(LayerPlugin) .add(ClientPlugin) .add(EventLoopPlugin) + .add(SpawnPlugin) .add(MovementPlugin) + .add(UpdateGameModePlugin) .add(ClientCommandPlugin) .add(KeepalivePlugin) .add(InteractEntityPlugin) .add(ClientSettingsPlugin) .add(ActionPlugin) - .add(TeleportPlugin) .add(MessagePlugin) .add(CustomPayloadPlugin) .add(HandSwingPlugin) From 6a1044a77c06e12571d31c23a4f3ca897c3c47ae Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 00:19:35 -0700 Subject: [PATCH 06/16] update valence_boss_bar --- crates/valence_boss_bar/src/components.rs | 65 ---- crates/valence_boss_bar/src/lib.rs | 347 ++++++++++------------ 2 files changed, 155 insertions(+), 257 deletions(-) delete mode 100644 crates/valence_boss_bar/src/components.rs diff --git a/crates/valence_boss_bar/src/components.rs b/crates/valence_boss_bar/src/components.rs deleted file mode 100644 index 6548fff5c..000000000 --- a/crates/valence_boss_bar/src/components.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::borrow::Cow; - -use bevy_ecs::prelude::{Bundle, Component}; -use derive_more::{Deref, DerefMut}; -use valence_entity::EntityLayerId; -use valence_server::protocol::packets::play::boss_bar_s2c::{ - BossBarAction, BossBarColor, BossBarDivision, BossBarFlags, -}; -use valence_server::{Text, UniqueId}; - -/// The bundle of components that make up a boss bar. -#[derive(Bundle, Default)] -pub struct BossBarBundle { - pub id: UniqueId, - pub title: BossBarTitle, - pub health: BossBarHealth, - pub style: BossBarStyle, - pub flags: BossBarFlags, - pub layer: EntityLayerId, -} - -/// The title of a boss bar. -#[derive(Component, Clone, Default, Deref, DerefMut)] -pub struct BossBarTitle(pub Text); - -impl ToPacketAction for BossBarTitle { - fn to_packet_action(&self) -> BossBarAction { - BossBarAction::UpdateTitle(Cow::Borrowed(&self.0)) - } -} - -/// The health of a boss bar. -#[derive(Component, Default, Deref, DerefMut)] -pub struct BossBarHealth(pub f32); - -impl ToPacketAction for BossBarHealth { - fn to_packet_action(&self) -> BossBarAction { - BossBarAction::UpdateHealth(self.0) - } -} - -/// The style of a boss bar. This includes the color and division of the boss -/// bar. -#[derive(Component, Default)] -pub struct BossBarStyle { - pub color: BossBarColor, - pub division: BossBarDivision, -} - -impl ToPacketAction for BossBarStyle { - fn to_packet_action(&self) -> BossBarAction { - BossBarAction::UpdateStyle(self.color, self.division) - } -} - -impl ToPacketAction for BossBarFlags { - fn to_packet_action(&self) -> BossBarAction { - BossBarAction::UpdateFlags(*self) - } -} - -/// Trait for converting a component to a boss bar action. -pub(crate) trait ToPacketAction { - fn to_packet_action(&self) -> BossBarAction; -} diff --git a/crates/valence_boss_bar/src/lib.rs b/crates/valence_boss_bar/src/lib.rs index ea4b9ec29..5bce76181 100644 --- a/crates/valence_boss_bar/src/lib.rs +++ b/crates/valence_boss_bar/src/lib.rs @@ -22,236 +22,199 @@ use std::borrow::Cow; use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use valence_server::client::{ - Client, OldViewDistance, OldVisibleEntityLayers, ViewDistance, VisibleEntityLayers, -}; -use valence_server::layer::UpdateLayersPreClientSet; +use bevy_ecs::query::WorldQuery; +use derive_more::{Deref, DerefMut}; +use valence_server::client::Client; +use valence_server::layer::message::LayerMessages; +use valence_server::layer::{BroadcastLayerMessagesSet, OldVisibleLayers, VisibleLayers}; pub use valence_server::protocol::packets::play::boss_bar_s2c::{ BossBarAction, BossBarColor, BossBarDivision, BossBarFlags, }; use valence_server::protocol::packets::play::BossBarS2c; use valence_server::protocol::WritePacket; -use valence_server::{ChunkView, Despawned, EntityLayer, Layer, UniqueId}; - -mod components; -pub use components::*; -use valence_entity::{EntityLayerId, OldPosition, Position}; +use valence_server::{Despawned, LayerId, OldLayerId, Text, UniqueId}; pub struct BossBarPlugin; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct BossBarSet; + impl Plugin for BossBarPlugin { fn build(&self, app: &mut bevy_app::App) { - app.add_systems( - PostUpdate, - ( - update_boss_bar::, - update_boss_bar::, - update_boss_bar::, - update_boss_bar::, - update_boss_bar_layer_view, - update_boss_bar_chunk_view, - boss_bar_despawn, - ) - .before(UpdateLayersPreClientSet), - ); + app.configure_set(PostUpdate, BossBarSet.before(BroadcastLayerMessagesSet)) + .add_systems( + PostUpdate, + ( + init_boss_bar_for_client, + update_boss_bar_layer, + update_boss_bar_title, + update_boss_bar_health, + update_boss_bar_style, + update_boss_bar_flags, + despawn_boss_bar, + ) + .chain() + .in_set(BossBarSet), + ); } } -fn update_boss_bar( - boss_bars_query: Query<(&UniqueId, &T, &EntityLayerId, Option<&Position>), Changed>, - mut entity_layers_query: Query<&mut EntityLayer>, -) { - for (id, part, entity_layer_id, pos) in boss_bars_query.iter() { - if let Ok(mut entity_layer) = entity_layers_query.get_mut(entity_layer_id.0) { - let packet = BossBarS2c { - id: id.0, - action: part.to_packet_action(), - }; - if let Some(pos) = pos { - entity_layer.view_writer(pos.0).write_packet(&packet); - } else { - entity_layer.write_packet(&packet); - } - } - } +/// The bundle of components that make up a boss bar. +#[derive(Bundle, Default)] +pub struct BossBarBundle { + pub uuid: UniqueId, + pub title: BossBarTitle, + pub health: BossBarHealth, + pub color: BossBarColor, + pub division: BossBarDivision, + pub flags: BossBarFlags, + pub layer: LayerId, + pub old_layer: OldLayerId, } -fn update_boss_bar_layer_view( - mut clients_query: Query< - ( - &mut Client, - &VisibleEntityLayers, - &OldVisibleEntityLayers, - &Position, - &OldPosition, - &ViewDistance, - &OldViewDistance, - ), - Changed, - >, - boss_bars_query: Query<( - &UniqueId, - &BossBarTitle, - &BossBarHealth, - &BossBarStyle, - &BossBarFlags, - &EntityLayerId, - Option<&Position>, - )>, -) { - for ( - mut client, - visible_entity_layers, - old_visible_entity_layers, - position, - _old_position, - view_distance, - _old_view_distance, - ) in clients_query.iter_mut() - { - let view = ChunkView::new(position.0.into(), view_distance.get()); +/// The title of a boss bar. +#[derive(Component, Clone, Default, Deref, DerefMut)] +pub struct BossBarTitle(pub Text); - let old_layers = old_visible_entity_layers.get(); - let current_layers = &visible_entity_layers.0; +/// The health of a boss bar. +#[derive(Component, Default, Deref, DerefMut)] +pub struct BossBarHealth(pub f32); - for &added_layer in current_layers.difference(old_layers) { - for (id, title, health, style, flags, _, boss_bar_position) in boss_bars_query - .iter() - .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == added_layer) - { - if let Some(position) = boss_bar_position { - if view.contains(position.0.into()) { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::Add { - title: Cow::Borrowed(&title.0), - health: health.0, - color: style.color, - division: style.division, - flags: *flags, - }, - }); - } - } else { +#[derive(WorldQuery)] +struct FullBossBarQuery { + uuid: &'static UniqueId, + title: &'static BossBarTitle, + health: &'static BossBarHealth, + color: &'static BossBarColor, + division: &'static BossBarDivision, + flags: &'static BossBarFlags, + layer: &'static LayerId, +} + +fn init_boss_bar_for_client( + mut clients: Query<(&mut Client, &VisibleLayers, &OldVisibleLayers), Changed>, + boss_bars: Query, +) { + for (mut client, layers, old_layers) in &mut clients { + for &layer in layers.difference(&old_layers) { + // Find every boss bar that points at this layer. + // TODO: This could be improved with fragmenting relations. + for bb in &boss_bars { + if bb.layer.0 == layer { client.write_packet(&BossBarS2c { - id: id.0, + id: bb.uuid.0, action: BossBarAction::Add { - title: Cow::Borrowed(&title.0), - health: health.0, - color: style.color, - division: style.division, - flags: *flags, + title: Cow::Borrowed(&bb.title.0), + health: bb.health.0, + color: *bb.color, + division: *bb.division, + flags: *bb.flags, }, }); } } } + } +} - for &removed_layer in old_layers.difference(current_layers) { - for (id, _, _, _, _, _, boss_bar_position) in boss_bars_query - .iter() - .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == removed_layer) - { - if let Some(position) = boss_bar_position { - if view.contains(position.0.into()) { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::Remove, - }); - } - } else { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::Remove, - }); - } - } +fn update_boss_bar_layer( + boss_bars: Query<(FullBossBarQuery, &OldLayerId), Changed>, + mut layers: Query<&mut LayerMessages>, +) { + for (bb, old_layer) in &boss_bars { + // Remove from old layer. + if let Ok(mut msgs) = layers.get_mut(old_layer.get()) { + msgs.write_packet(&BossBarS2c { + id: bb.uuid.0, + action: BossBarAction::Remove, + }) + } + + // Init in new layer. + if let Ok(mut msgs) = layers.get_mut(bb.layer.0) { + msgs.write_packet(&BossBarS2c { + id: bb.uuid.0, + action: BossBarAction::Add { + title: Cow::Borrowed(&bb.title.0), + health: bb.health.0, + color: *bb.color, + division: *bb.division, + flags: *bb.flags, + }, + }); } } } -fn update_boss_bar_chunk_view( - mut clients_query: Query< - ( - &mut Client, - &VisibleEntityLayers, - &OldVisibleEntityLayers, - &Position, - &OldPosition, - &ViewDistance, - &OldViewDistance, - ), - Changed, +fn update_boss_bar_title( + boss_bars: Query<(&UniqueId, &LayerId, &BossBarTitle), Changed>, + mut layers: Query<&mut LayerMessages>, +) { + for (uuid, layer, title) in &boss_bars { + if let Ok(mut msgs) = layers.get_mut(layer.0) { + msgs.write_packet(&BossBarS2c { + id: uuid.0, + action: BossBarAction::UpdateTitle(Cow::Borrowed(&title.0)), + }); + } + } +} + +fn update_boss_bar_health( + boss_bars: Query<(&UniqueId, &LayerId, &BossBarHealth), Changed>, + mut layers: Query<&mut LayerMessages>, +) { + for (uuid, layer, health) in &boss_bars { + if let Ok(mut msgs) = layers.get_mut(layer.0) { + msgs.write_packet(&BossBarS2c { + id: uuid.0, + action: BossBarAction::UpdateHealth(health.0), + }); + } + } +} + +fn update_boss_bar_style( + boss_bars: Query< + (&UniqueId, &LayerId, &BossBarColor, &BossBarDivision), + Or<(Changed, Changed)>, >, - boss_bars_query: Query<( - &UniqueId, - &BossBarTitle, - &BossBarHealth, - &BossBarStyle, - &BossBarFlags, - &EntityLayerId, - &Position, - )>, + mut layers: Query<&mut LayerMessages>, ) { - for ( - mut client, - visible_entity_layers, - _old_visible_entity_layers, - position, - old_position, - view_distance, - old_view_distance, - ) in clients_query.iter_mut() - { - let view = ChunkView::new(position.0.into(), view_distance.get()); - let old_view = ChunkView::new(old_position.get().into(), old_view_distance.get()); + for (uuid, layer, color, division) in &boss_bars { + if let Ok(mut msgs) = layers.get_mut(layer.0) { + msgs.write_packet(&BossBarS2c { + id: uuid.0, + action: BossBarAction::UpdateStyle(*color, *division), + }); + } + } +} - for layer in visible_entity_layers.0.iter() { - for (id, title, health, style, flags, _, boss_bar_position) in boss_bars_query - .iter() - .filter(|(_, _, _, _, _, layer_id, _)| layer_id.0 == *layer) - { - if view.contains(boss_bar_position.0.into()) - && !old_view.contains(boss_bar_position.0.into()) - { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::Add { - title: Cow::Borrowed(&title.0), - health: health.0, - color: style.color, - division: style.division, - flags: *flags, - }, - }); - } else if !view.contains(boss_bar_position.0.into()) - && old_view.contains(boss_bar_position.0.into()) - { - client.write_packet(&BossBarS2c { - id: id.0, - action: BossBarAction::Remove, - }); - } - } +fn update_boss_bar_flags( + boss_bars: Query<(&UniqueId, &LayerId, &BossBarFlags), Changed>, + mut layers: Query<&mut LayerMessages>, +) { + for (uuid, layer, flags) in &boss_bars { + if let Ok(mut msgs) = layers.get_mut(layer.0) { + msgs.write_packet(&BossBarS2c { + id: uuid.0, + action: BossBarAction::UpdateFlags(*flags), + }); } } } -fn boss_bar_despawn( - boss_bars_query: Query<(&UniqueId, &EntityLayerId, Option<&Position>), With>, - mut entity_layer_query: Query<&mut EntityLayer>, +fn despawn_boss_bar( + boss_bars: Query<(&UniqueId, &LayerId), With>, + mut layers: Query<&mut LayerMessages>, ) { - for (id, entity_layer_id, position) in boss_bars_query.iter() { - if let Ok(mut entity_layer) = entity_layer_query.get_mut(entity_layer_id.0) { - let packet = BossBarS2c { - id: id.0, + for (uuid, layer_id) in &boss_bars { + if let Ok(mut msgs) = layers.get_mut(layer_id.0) { + msgs.write_packet(&BossBarS2c { + id: uuid.0, action: BossBarAction::Remove, - }; - if let Some(pos) = position { - entity_layer.view_writer(pos.0).write_packet(&packet); - } else { - entity_layer.write_packet(&packet); - } + }); } } } From d09668e49b071bba6319ef0484da84d1ede0085a Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 01:38:35 -0700 Subject: [PATCH 07/16] update valence_scoreboard --- crates/valence_scoreboard/README.md | 2 +- crates/valence_scoreboard/src/components.rs | 7 ++-- crates/valence_scoreboard/src/lib.rs | 43 +++++++++------------ 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/crates/valence_scoreboard/README.md b/crates/valence_scoreboard/README.md index 886df9e36..11a03e3e5 100644 --- a/crates/valence_scoreboard/README.md +++ b/crates/valence_scoreboard/README.md @@ -2,7 +2,7 @@ This crate provides functionality for creating and managing scoreboards. In Minecraft, a scoreboard references an [`Objective`], which is a mapping from strings to scores. Typically, the string is a player name, and the score is a number of points, but the string can be any arbitrary string <= 40 chars, and the score can be any integer. -In Valence, scoreboards obey the rules implied by [`EntityLayer`]s, meaning that every Objective must have an [`EntityLayerId`] associated with it. Scoreboards are only transmitted to clients if the [`EntityLayer`] is visible to the client. +In Valence, scoreboards obey the rules implied by layers, meaning that every Objective must have an [`EntityLayerId`] associated with it. Scoreboards are only transmitted to clients if the [`EntityLayer`] is visible to the client. To create a scoreboard, spawn an [`ObjectiveBundle`]. The [`Objective`] component represents the identifier that the client uses to reference the scoreboard. diff --git a/crates/valence_scoreboard/src/components.rs b/crates/valence_scoreboard/src/components.rs index 46974782a..779410e20 100644 --- a/crates/valence_scoreboard/src/components.rs +++ b/crates/valence_scoreboard/src/components.rs @@ -2,14 +2,13 @@ use std::collections::HashMap; use bevy_ecs::prelude::*; use derive_more::{Deref, DerefMut}; -use valence_server::entity::EntityLayerId; use valence_server::protocol::packets::play::scoreboard_display_s2c::ScoreboardPosition; use valence_server::protocol::packets::play::scoreboard_objective_update_s2c::ObjectiveRenderType; use valence_server::text::IntoText; -use valence_server::Text; +use valence_server::{Text, LayerId}; /// A string that identifies an objective. There is one scoreboard per -/// objective.It's generally not safe to modify this after it's been created. +/// objective. It's generally not safe to modify this after it's been created. /// Limited to 16 characters. /// /// Directly analogous to an Objective's Name. @@ -103,7 +102,7 @@ pub struct ObjectiveBundle { pub scores: ObjectiveScores, pub old_scores: OldObjectiveScores, pub position: ScoreboardPosition, - pub layer: EntityLayerId, + pub layer: LayerId, } impl Default for ObjectiveBundle { diff --git a/crates/valence_scoreboard/src/lib.rs b/crates/valence_scoreboard/src/lib.rs index 80c97dbe0..e41b58e7e 100644 --- a/crates/valence_scoreboard/src/lib.rs +++ b/crates/valence_scoreboard/src/lib.rs @@ -26,10 +26,9 @@ use bevy_ecs::change_detection::DetectChanges; use bevy_ecs::prelude::*; pub use components::*; use tracing::{debug, warn}; -use valence_server::client::{Client, OldVisibleEntityLayers, VisibleEntityLayers}; -use valence_server::entity::EntityLayerId; -use valence_server::layer::BroadcastLayerMessagesSet; -use valence_server::layer_old::UpdateLayersPreClientSet; +use valence_server::client::Client; +use valence_server::layer::message::LayerMessages; +use valence_server::layer::{BroadcastLayerMessagesSet, OldVisibleLayers, VisibleLayers}; use valence_server::protocol::packets::play::scoreboard_display_s2c::ScoreboardPosition; use valence_server::protocol::packets::play::scoreboard_objective_update_s2c::{ ObjectiveMode, ObjectiveRenderType, @@ -40,7 +39,7 @@ use valence_server::protocol::packets::play::{ }; use valence_server::protocol::{VarInt, WritePacket}; use valence_server::text::IntoText; -use valence_server::Despawned; +use valence_server::{Despawned, LayerId}; /// Provides all necessary systems to manage scoreboards. pub struct ScoreboardPlugin; @@ -51,6 +50,7 @@ impl Plugin for ScoreboardPlugin { .add_systems( PostUpdate, ( + handle_new_clients, create_or_update_objectives, display_objectives.after(create_or_update_objectives), remove_despawned_objectives, @@ -72,11 +72,11 @@ fn create_or_update_objectives( Ref, &ObjectiveDisplay, &ObjectiveRenderType, - &EntityLayerId, + &LayerId, ), Or<(Changed, Changed)>, >, - mut layers: Query<&mut EntityLayer>, + mut layers: Query<&mut LayerMessages>, ) { for (objective, display, render_type, entity_layer) in objectives.iter() { if objective.name().is_empty() { @@ -111,11 +111,8 @@ fn create_or_update_objectives( /// Must occur after `create_or_update_objectives`. fn display_objectives( - objectives: Query< - (&Objective, Ref, &EntityLayerId), - Changed, - >, - mut layers: Query<&mut EntityLayer>, + objectives: Query<(&Objective, Ref, &LayerId), Changed>, + mut layers: Query<&mut LayerMessages>, ) { for (objective, position, entity_layer) in objectives.iter() { let packet = ScoreboardDisplayS2c { @@ -137,8 +134,8 @@ fn display_objectives( fn remove_despawned_objectives( mut commands: Commands, - objectives: Query<(Entity, &Objective, &EntityLayerId), With>, - mut layers: Query<&mut EntityLayer>, + objectives: Query<(Entity, &Objective, &LayerId), With>, + mut layers: Query<&mut LayerMessages>, ) { for (entity, objective, entity_layer) in objectives.iter() { commands.entity(entity).despawn(); @@ -159,8 +156,8 @@ fn remove_despawned_objectives( fn handle_new_clients( mut clients: Query< - (&mut Client, &VisibleEntityLayers, &OldVisibleEntityLayers), - Or<(Added, Changed)>, + (&mut Client, &VisibleLayers, &OldVisibleLayers), + Or<(Added, Changed)>, >, objectives: Query< ( @@ -169,7 +166,7 @@ fn handle_new_clients( &ObjectiveRenderType, &ScoreboardPosition, &ObjectiveScores, - &EntityLayerId, + &LayerId, ), Without, >, @@ -177,10 +174,8 @@ fn handle_new_clients( // Remove objectives from the old visible layers that are not in the new visible // layers. for (mut client, visible_layers, old_visible_layers) in clients.iter_mut() { - let removed_layers: BTreeSet<_> = old_visible_layers - .get() - .difference(&visible_layers.0) - .collect(); + let removed_layers: BTreeSet<_> = + old_visible_layers.difference(&visible_layers.0).collect(); for (objective, _, _, _, _, layer) in objectives.iter() { if !removed_layers.contains(&layer.0) { @@ -203,7 +198,7 @@ fn handle_new_clients( } else { visible_layers .0 - .difference(old_visible_layers.get()) + .difference(&old_visible_layers) .copied() .collect::>() }; @@ -246,11 +241,11 @@ fn update_scores( &Objective, &ObjectiveScores, &mut OldObjectiveScores, - &EntityLayerId, + &LayerId, ), (Changed, Without), >, - mut layers: Query<&mut EntityLayer>, + mut layers: Query<&mut LayerMessages>, ) { for (objective, scores, mut old_scores, entity_layer) in objectives.iter_mut() { let Ok(mut layer) = layers.get_mut(entity_layer.0) else { From fad396d96b0e96bcfd0be42759a2a543fa51ded4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 01:43:26 -0700 Subject: [PATCH 08/16] update valence_boss_bar again --- crates/valence_boss_bar/src/lib.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/valence_boss_bar/src/lib.rs b/crates/valence_boss_bar/src/lib.rs index 5bce76181..c9e61cae2 100644 --- a/crates/valence_boss_bar/src/lib.rs +++ b/crates/valence_boss_bar/src/lib.rs @@ -96,9 +96,22 @@ fn init_boss_bar_for_client( boss_bars: Query, ) { for (mut client, layers, old_layers) in &mut clients { + // TODO: this could be improved with fragmenting relations. + + // Remove boss bars from old layers. + for &layer in old_layers.difference(&layers) { + for bb in &boss_bars { + if bb.layer.0 == layer { + client.write_packet(&BossBarS2c { + id: bb.uuid.0, + action: BossBarAction::Remove, + }); + } + } + } + + // Add boss bars from new layers. for &layer in layers.difference(&old_layers) { - // Find every boss bar that points at this layer. - // TODO: This could be improved with fragmenting relations. for bb in &boss_bars { if bb.layer.0 == layer { client.write_packet(&BossBarS2c { From 6c6a83e45be2a9fec5d04195e1ee25e98ce1e919 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 01:43:55 -0700 Subject: [PATCH 09/16] some other stuff --- crates/valence_anvil/src/lib.rs | 4 +- .../valence_anvil/src/{bevy.rs => plugin.rs} | 0 .../src/packets/play/boss_bar_s2c.rs | 4 +- crates/valence_server/src/abilities.rs | 24 ++++++----- crates/valence_server_common/src/layer_id.rs | 42 ++++++++++++++++--- crates/valence_server_common/src/lib.rs | 12 +++++- 6 files changed, 65 insertions(+), 21 deletions(-) rename crates/valence_anvil/src/{bevy.rs => plugin.rs} (100%) diff --git a/crates/valence_anvil/src/lib.rs b/crates/valence_anvil/src/lib.rs index 3770f1d94..1a64cc0c4 100644 --- a/crates/valence_anvil/src/lib.rs +++ b/crates/valence_anvil/src/lib.rs @@ -24,7 +24,7 @@ use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(feature = "bevy_plugin")] -pub use bevy::*; +pub use plugin::*; use bitfield_struct::bitfield; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use flate2::bufread::{GzDecoder, ZlibDecoder}; @@ -34,7 +34,7 @@ use thiserror::Error; use valence_nbt::Compound; #[cfg(feature = "bevy_plugin")] -mod bevy; +mod plugin; #[cfg(feature = "parsing")] pub mod parsing; diff --git a/crates/valence_anvil/src/bevy.rs b/crates/valence_anvil/src/plugin.rs similarity index 100% rename from crates/valence_anvil/src/bevy.rs rename to crates/valence_anvil/src/plugin.rs diff --git a/crates/valence_protocol/src/packets/play/boss_bar_s2c.rs b/crates/valence_protocol/src/packets/play/boss_bar_s2c.rs index 409417c2c..988e28002 100644 --- a/crates/valence_protocol/src/packets/play/boss_bar_s2c.rs +++ b/crates/valence_protocol/src/packets/play/boss_bar_s2c.rs @@ -30,7 +30,7 @@ pub enum BossBarAction<'a> { } /// The color of a boss bar. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Default)] +#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Default)] pub enum BossBarColor { #[default] Pink, @@ -43,7 +43,7 @@ pub enum BossBarColor { } /// The division of a boss bar. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Default)] +#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Default)] pub enum BossBarDivision { #[default] NoDivision, diff --git a/crates/valence_server/src/abilities.rs b/crates/valence_server/src/abilities.rs index f4b7ca9e7..e46e8955c 100644 --- a/crates/valence_server/src/abilities.rs +++ b/crates/valence_server/src/abilities.rs @@ -5,8 +5,9 @@ pub use valence_protocol::packets::play::player_abilities_s2c::PlayerAbilitiesFl use valence_protocol::packets::play::{PlayerAbilitiesS2c, UpdatePlayerAbilitiesC2s}; use valence_protocol::{GameMode, WritePacket}; -use crate::client::Client; +use crate::client::{Client, FlushPacketsSet}; use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; +use crate::game_mode::UpdateGameModeSet; /// [`Component`] that stores the player's flying speed ability. /// @@ -62,23 +63,26 @@ pub struct PlayerStopFlyingEvent { /// according to the packet pub struct AbilitiesPlugin; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct AbilitiesSet; + impl Plugin for AbilitiesPlugin { fn build(&self, app: &mut App) { - - /* app.add_event::() .add_event::() + .configure_set( + PostUpdate, + AbilitiesSet + .before(FlushPacketsSet) + .after(UpdateGameModeSet), + ) .add_systems( PostUpdate, - ( - update_client_player_abilities, - update_player_abilities.before(update_client_player_abilities), - ) - .in_set(UpdateClientsSet) - .after(update_game_mode), + (update_player_abilities, update_client_player_abilities) + .chain() + .in_set(AbilitiesSet), ) .add_systems(EventLoopPreUpdate, update_server_player_abilities); - */ } } diff --git a/crates/valence_server_common/src/layer_id.rs b/crates/valence_server_common/src/layer_id.rs index c555a8bb3..ae60ec1e5 100644 --- a/crates/valence_server_common/src/layer_id.rs +++ b/crates/valence_server_common/src/layer_id.rs @@ -1,16 +1,48 @@ use bevy_ecs::prelude::*; +/// The pointer to the layer this entity is a member of. #[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] pub struct LayerId(pub Entity); -impl PartialEq for LayerId { - fn eq(&self, other: &Entity) -> bool { - self.0.eq(other) +impl Default for LayerId { + fn default() -> Self { + Self(Entity::PLACEHOLDER) } } -impl PartialEq for Entity { +impl PartialEq for LayerId { + fn eq(&self, other: &OldLayerId) -> bool { + self.0 == other.0 + } +} + +/// Value of [`LayerId`] from the previous tick. Not intended to be modified +/// manually. +#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] +pub struct OldLayerId(Entity); + +impl OldLayerId { + pub fn get(&self) -> Entity { + self.0 + } +} + +impl Default for OldLayerId { + fn default() -> Self { + Self(Entity::PLACEHOLDER) + } +} + +impl PartialEq for OldLayerId { fn eq(&self, other: &LayerId) -> bool { - self.eq(&other.0) + self.0 == other.0 + } +} + +pub(super) fn update_old_layer_id( + mut entities: Query<(&LayerId, &mut OldLayerId), Changed>, +) { + for (new, mut old) in &mut entities { + old.0 = new.0 } } diff --git a/crates/valence_server_common/src/lib.rs b/crates/valence_server_common/src/lib.rs index 13ab212c8..1339e0954 100644 --- a/crates/valence_server_common/src/lib.rs +++ b/crates/valence_server_common/src/lib.rs @@ -33,7 +33,8 @@ pub use despawn::*; use valence_protocol::CompressionThreshold; use crate::despawn::despawn_marked_entities; -pub use crate::layer_id::LayerId; +use crate::layer_id::update_old_layer_id; +pub use crate::layer_id::{LayerId, OldLayerId}; pub use crate::uuid::UniqueId; /// Minecraft's standard ticks per second (TPS). @@ -106,7 +107,14 @@ impl Plugin for ServerPlugin { server.current_tick += 1; } - app.add_systems(Last, (increment_tick_counter, despawn_marked_entities)); + app.add_systems( + Last, + ( + increment_tick_counter, + despawn_marked_entities, + update_old_layer_id, + ), + ); } } From 2e17f7ff0e4d64a1d808a381c3a91dd2d974f07f Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 14:56:15 -0700 Subject: [PATCH 10/16] Update anvil --- crates/valence_anvil/src/parsing.rs | 8 ++--- crates/valence_anvil/src/plugin.rs | 29 +++++++++++-------- crates/valence_server/src/dimension_layer.rs | 27 +++++++++++++---- .../{index.rs => chunk_index.rs} | 12 ++++++++ 4 files changed, 54 insertions(+), 22 deletions(-) rename crates/valence_server/src/dimension_layer/{index.rs => chunk_index.rs} (92%) diff --git a/crates/valence_anvil/src/parsing.rs b/crates/valence_anvil/src/parsing.rs index 9d0ac03ae..45dee9363 100644 --- a/crates/valence_anvil/src/parsing.rs +++ b/crates/valence_anvil/src/parsing.rs @@ -5,12 +5,12 @@ use std::path::PathBuf; use num_integer::div_ceil; use thiserror::Error; use valence_server::block::{PropName, PropValue}; -use valence_server::layer_old::chunk::{Chunk, ChunkOps}; +use valence_server::dimension_layer::chunk::ChunkOps; use valence_server::nbt::{Compound, List, Value}; use valence_server::protocol::BlockKind; use valence_server::registry::biome::BiomeId; use valence_server::registry::BiomeRegistry; -use valence_server::{ChunkPos, Ident}; +use valence_server::{Chunk, ChunkPos, Ident}; use crate::{RegionError, RegionFolder}; @@ -55,7 +55,7 @@ impl DimensionFolder { /// A chunk parsed to show block information, biome information etc. pub struct ParsedChunk { - pub chunk: UnloadedChunk, + pub chunk: Chunk, pub timestamp: u32, } @@ -128,7 +128,7 @@ fn parse_chunk( return Ok(Chunk::new()); } - let mut chunk = Chunk::with_height((sections.len() * 16).try_into().unwrap_or(u32::MAX)); + let mut chunk = Chunk::with_height((sections.len() * 16).try_into().unwrap_or(i32::MAX)); let min_sect_y = sections .iter() diff --git a/crates/valence_anvil/src/plugin.rs b/crates/valence_anvil/src/plugin.rs index af4ebd16d..bec87310d 100644 --- a/crates/valence_anvil/src/plugin.rs +++ b/crates/valence_anvil/src/plugin.rs @@ -7,11 +7,12 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use flume::{Receiver, Sender}; use valence_server::client::{Client, OldView, View}; -use valence_server::entity::{EntityLayerId, OldEntityLayerId}; -use valence_server::layer_old::UpdateLayersPreClientSet; +use valence_server::dimension_layer::{ + ChunkIndex, DimensionInfo, DimensionLayerQuery, UpdateDimensionLayerSet, +}; use valence_server::protocol::anyhow; use valence_server::registry::BiomeRegistry; -use valence_server::{ChunkLayer, ChunkPos}; +use valence_server::{ChunkPos, LayerId, OldLayerId}; use crate::parsing::{DimensionFolder, ParsedChunk}; @@ -92,21 +93,25 @@ struct ChunkWorkerState { pub struct AnvilPlugin; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct AnvilSet; + impl Plugin for AnvilPlugin { fn build(&self, app: &mut App) { app.add_event::() .add_event::() .add_systems(PreUpdate, remove_unviewed_chunks) + .configure_set(PostUpdate, AnvilSet.before(UpdateDimensionLayerSet)) .add_systems( PostUpdate, (init_anvil, update_client_views, send_recv_chunks) .chain() - .before(UpdateLayersPreClientSet), + .in_set(AnvilSet), ); } } -fn init_anvil(mut query: Query<&mut AnvilLevel, (Added, With)>) { +fn init_anvil(mut query: Query<&mut AnvilLevel, (Added, With)>) { for mut level in &mut query { if let Some(state) = level.worker_state.take() { thread::spawn(move || anvil_worker(state)); @@ -119,12 +124,12 @@ fn init_anvil(mut query: Query<&mut AnvilLevel, (Added, With, + mut chunk_layers: Query<(Entity, DimensionLayerQuery, &AnvilLevel)>, mut unload_events: EventWriter, ) { for (entity, mut layer, anvil) in &mut chunk_layers { layer.retain_chunks(|pos, chunk| { - if chunk.viewer_count_mut() > 0 || anvil.ignored_chunks.contains(&pos) { + if chunk.viewer_count() > 0 || anvil.ignored_chunks.contains(&pos) { true } else { unload_events.send(ChunkUnloadEvent { @@ -138,20 +143,20 @@ fn remove_unviewed_chunks( } fn update_client_views( - clients: Query<(&EntityLayerId, Ref, View, OldView), With>, - mut chunk_layers: Query<(&ChunkLayer, &mut AnvilLevel)>, + clients: Query<(&LayerId, Ref, View, OldView), With>, + mut chunk_layers: Query<(&ChunkIndex, &mut AnvilLevel)>, ) { for (loc, old_loc, view, old_view) in &clients { let view = view.get(); let old_view = old_view.get(); if loc != &*old_loc || view != old_view || old_loc.is_added() { - let Ok((layer, mut anvil)) = chunk_layers.get_mut(loc.0) else { + let Ok((chunk_index, mut anvil)) = chunk_layers.get_mut(loc.0) else { continue; }; let queue_pos = |pos| { - if !anvil.ignored_chunks.contains(&pos) && layer.chunk(pos).is_none() { + if !anvil.ignored_chunks.contains(&pos) && chunk_index.get(pos).is_none() { // Chunks closer to clients are prioritized. match anvil.pending.entry(pos) { Entry::Occupied(mut oe) => { @@ -179,7 +184,7 @@ fn update_client_views( } fn send_recv_chunks( - mut layers: Query<(Entity, &mut ChunkLayer, &mut AnvilLevel)>, + mut layers: Query<(Entity, DimensionLayerQuery, &mut AnvilLevel)>, mut to_send: Local>, mut load_events: EventWriter, ) { diff --git a/crates/valence_server/src/dimension_layer.rs b/crates/valence_server/src/dimension_layer.rs index 48966c522..992d73e48 100644 --- a/crates/valence_server/src/dimension_layer.rs +++ b/crates/valence_server/src/dimension_layer.rs @@ -1,14 +1,14 @@ pub mod batch; pub mod block; pub mod chunk; -pub mod index; +pub mod chunk_index; mod plugin; use bevy_ecs::prelude::*; use bevy_ecs::query::WorldQuery; use block::BlockRef; pub use chunk::{Chunk, LoadedChunk}; -pub use index::ChunkIndex; +pub use chunk_index::ChunkIndex; pub use plugin::*; use valence_protocol::packets::play::UnloadChunkS2c; use valence_protocol::{BiomePos, BlockPos, ChunkPos, CompressionThreshold, WritePacket}; @@ -131,13 +131,13 @@ impl DimensionLayerQueryItem<'_> { pub fn chunk_entry(&mut self, pos: impl Into) -> Entry { match self.chunk_index.entry(pos) { - index::Entry::Occupied(entry) => Entry::Occupied(OccupiedEntry { + chunk_index::Entry::Occupied(entry) => Entry::Occupied(OccupiedEntry { chunk_view_index: &*self.chunk_view_index, layer_messages: self.layer_messages.reborrow(), info: &self.dimension_info, entry, }), - index::Entry::Vacant(entry) => Entry::Vacant(VacantEntry { + chunk_index::Entry::Vacant(entry) => Entry::Vacant(VacantEntry { chunk_view_index: &*self.chunk_view_index, layer_messages: self.layer_messages.reborrow(), info: &self.dimension_info, @@ -145,6 +145,21 @@ impl DimensionLayerQueryItem<'_> { }), } } + + pub fn retain_chunks(&mut self, mut f: impl FnMut(ChunkPos, &mut LoadedChunk) -> bool) { + self.chunk_index.retain(|pos, chunk| { + if f(pos, chunk) { + true + } else { + if chunk.viewer_count > 0 { + self.layer_messages + .send_packet(MessageScope::ChunkView { pos }, &UnloadChunkS2c { pos }); + } + + false + } + }); + } } impl DimensionLayerQueryReadOnlyItem<'_> { @@ -194,7 +209,7 @@ pub struct OccupiedEntry<'a> { chunk_view_index: &'a ChunkViewIndex, layer_messages: Mut<'a, LayerMessages>, info: &'a DimensionInfo, - entry: index::OccupiedEntry<'a>, + entry: chunk_index::OccupiedEntry<'a>, } impl<'a> OccupiedEntry<'a> { @@ -253,7 +268,7 @@ pub struct VacantEntry<'a> { chunk_view_index: &'a ChunkViewIndex, layer_messages: Mut<'a, LayerMessages>, info: &'a DimensionInfo, - entry: index::VacantEntry<'a>, + entry: chunk_index::VacantEntry<'a>, } impl<'a> VacantEntry<'a> { diff --git a/crates/valence_server/src/dimension_layer/index.rs b/crates/valence_server/src/dimension_layer/chunk_index.rs similarity index 92% rename from crates/valence_server/src/dimension_layer/index.rs rename to crates/valence_server/src/dimension_layer/chunk_index.rs index c79e437d4..5c1bcf311 100644 --- a/crates/valence_server/src/dimension_layer/index.rs +++ b/crates/valence_server/src/dimension_layer/chunk_index.rs @@ -72,6 +72,18 @@ impl ChunkIndex { todo!() } + pub fn retain(&mut self, mut f: impl FnMut(ChunkPos, &mut LoadedChunk) -> bool) { + self.map.retain(|pos, chunk| f(*pos, chunk)) + } + + pub fn clear(&mut self) { + self.map.clear(); + } + + pub fn shrink_to_fit(&mut self) { + self.map.shrink_to_fit() + } + // TODO: iter, iter_mut, clear } From e3e416341761acebdff81861396dfc5a7b7fb9bb Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 15:02:48 -0700 Subject: [PATCH 11/16] Get everything compiling --- crates/valence_server/src/layer.rs | 7 +++++-- crates/valence_server/src/lib.rs | 2 +- examples/bench_players.rs | 2 +- src/lib.rs | 2 +- src/testing.rs | 30 ++++++++++++++++-------------- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/crates/valence_server/src/layer.rs b/crates/valence_server/src/layer.rs index 53d2f4a55..e7197ea2c 100644 --- a/crates/valence_server/src/layer.rs +++ b/crates/valence_server/src/layer.rs @@ -48,8 +48,11 @@ impl Plugin for LayerPlugin { } } +/// Combination of components from [`DimensionLayerBundle`] and +/// [`EntityLayerBundle`]. The spawned entity from this bundle is considered +/// both a "dimension layer" and an "entity layer". #[derive(Bundle)] -pub struct DimensionEntityLayerBundle { +pub struct CombinedLayerBundle { pub chunk_index: ChunkIndex, pub block_batch: BlockBatch, pub dimension_info: DimensionInfo, @@ -59,7 +62,7 @@ pub struct DimensionEntityLayerBundle { pub layer_messages: LayerMessages, } -impl DimensionEntityLayerBundle { +impl CombinedLayerBundle { pub fn new( dimension_type: DimensionTypeId, dimensions: &DimensionTypeRegistry, diff --git a/crates/valence_server/src/lib.rs b/crates/valence_server/src/lib.rs index 6e77dd61a..ccd12630b 100644 --- a/crates/valence_server/src/lib.rs +++ b/crates/valence_server/src/lib.rs @@ -47,7 +47,7 @@ pub use chunk_view::ChunkView; pub use client::Client; pub use dimension_layer::{Chunk, DimensionLayerBundle, LoadedChunk}; pub use event_loop::{EventLoopPostUpdate, EventLoopPreUpdate, EventLoopUpdate}; -pub use layer::DimensionEntityLayerBundle; +pub use layer::CombinedLayerBundle; pub use valence_protocol::{ block, ident, item, math, text, uuid, BiomePos, BlockPos, BlockState, ChunkPos, CompressionThreshold, Difficulty, Direction, GameMode, Hand, Ident, ItemKind, ItemStack, Text, diff --git a/examples/bench_players.rs b/examples/bench_players.rs index 9cb400c66..2d33acd12 100644 --- a/examples/bench_players.rs +++ b/examples/bench_players.rs @@ -52,7 +52,7 @@ fn setup( biomes: Res, ) { let mut layer = - DimensionEntityLayerBundle::new(Default::default(), &dimensions, &biomes, &server); + CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { diff --git a/src/lib.rs b/src/lib.rs index 25c8add01..e1753fe3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,7 @@ pub mod prelude { pub use valence_server::spawn::{ClientSpawnQuery, ClientSpawnQueryReadOnly}; pub use valence_server::title::SetTitle as _; pub use valence_server::{ - ident, BlockPos, Chunk, ChunkPos, ChunkView, Despawned, DimensionEntityLayerBundle, + ident, BlockPos, Chunk, ChunkPos, ChunkView, Despawned, CombinedLayerBundle, Direction, GameMode, Hand, ItemKind, ItemStack, LoadedChunk, Server, UniqueId, }; diff --git a/src/testing.rs b/src/testing.rs index ba83c02d3..44bc2bedd 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -12,13 +12,15 @@ use valence_network::NetworkPlugin; use valence_registry::{BiomeRegistry, DimensionTypeRegistry}; use valence_server::client::ClientBundleArgs; use valence_server::keepalive::KeepaliveSettings; +use valence_server::layer::CombinedLayerBundle; use valence_server::protocol::decode::PacketFrame; use valence_server::protocol::packets::play::{PlayerPositionLookS2c, TeleportConfirmC2s}; use valence_server::protocol::{Decode, Encode, Packet, PacketDecoder, PacketEncoder, VarInt}; -use valence_server::{ChunkLayer, EntityLayer, Server, ServerSettings}; +use valence_server::{Server, ServerSettings}; use crate::client::{ClientBundle, ClientConnection, ReceivedPacket}; use crate::DefaultPlugins; + pub struct ScenarioSingleClient { /// The new bevy application. pub app: App, @@ -26,13 +28,13 @@ pub struct ScenarioSingleClient { pub client: Entity, /// Helper for sending and receiving packets from the mock client. pub helper: MockClientHelper, - /// Entity with [`ChunkLayer`] and [`EntityLayer`] components. + /// Entity with [`CombinedLayerBundle`] components. pub layer: Entity, } impl ScenarioSingleClient { - /// Sets up Valence with a single mock client and entity+chunk layer. The - /// client is configured to be placed within the layer. + /// Sets up Valence with a single mock client and dimension+entity layer. + /// The client is configured to be placed within the layer. /// /// Reduces boilerplate in unit tests. pub fn new() -> Self { @@ -49,19 +51,19 @@ impl ScenarioSingleClient { app.update(); // Initialize plugins. - let chunk_layer = ChunkLayer::new( - ident!("overworld"), - app.world.resource::(), - app.world.resource::(), - app.world.resource::(), - ); - let entity_layer = EntityLayer::new(app.world.resource::()); - let layer = app.world.spawn((chunk_layer, entity_layer)).id(); + let layer = app + .world + .spawn(CombinedLayerBundle::new( + Default::default(), + app.world.resource::(), + app.world.resource::(), + app.world.resource::(), + )) + .id(); let (mut client, helper) = create_mock_client("test"); client.player.layer.0 = layer; - client.visible_chunk_layer.0 = layer; - client.visible_entity_layers.0.insert(layer); + client.visible_layers.insert(layer); let client = app.world.spawn(client).id(); ScenarioSingleClient { From e2c693d20059f515a36ae9fdcf71dd8d19ce9a40 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 16:30:43 -0700 Subject: [PATCH 12/16] EntityLayerId -> LayerId --- crates/valence_anvil/src/lib.rs | 8 ++-- crates/valence_entity/build.rs | 4 +- crates/valence_entity/src/lib.rs | 48 +-------------------- crates/valence_entity/src/query.rs | 10 ++--- crates/valence_scoreboard/src/components.rs | 2 +- crates/valence_server/src/lib.rs | 4 +- examples/bench_players.rs | 24 +++-------- src/lib.rs | 9 ++-- src/testing.rs | 1 - 9 files changed, 27 insertions(+), 83 deletions(-) diff --git a/crates/valence_anvil/src/lib.rs b/crates/valence_anvil/src/lib.rs index 1a64cc0c4..5f23f9ba8 100644 --- a/crates/valence_anvil/src/lib.rs +++ b/crates/valence_anvil/src/lib.rs @@ -23,20 +23,20 @@ use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(feature = "bevy_plugin")] -pub use plugin::*; use bitfield_struct::bitfield; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use flate2::bufread::{GzDecoder, ZlibDecoder}; use flate2::write::{GzEncoder, ZlibEncoder}; use lru::LruCache; +#[cfg(feature = "bevy_plugin")] +pub use plugin::*; use thiserror::Error; use valence_nbt::Compound; -#[cfg(feature = "bevy_plugin")] -mod plugin; #[cfg(feature = "parsing")] pub mod parsing; +#[cfg(feature = "bevy_plugin")] +mod plugin; const LRU_CACHE_SIZE: NonZeroUsize = match NonZeroUsize::new(256) { Some(n) => n, diff --git a/crates/valence_entity/build.rs b/crates/valence_entity/build.rs index 7f9384946..400af7bcc 100644 --- a/crates/valence_entity/build.rs +++ b/crates/valence_entity/build.rs @@ -386,8 +386,8 @@ fn build() -> anyhow::Result { pub kind: super::EntityKind, pub id: super::EntityId, pub uuid: super::UniqueId, - pub layer: super::EntityLayerId, - pub old_layer: super::OldEntityLayerId, + pub layer: super::LayerId, + pub old_layer: super::OldLayerId, pub position: super::Position, pub old_position: super::OldPosition, pub look: super::Look, diff --git a/crates/valence_entity/src/lib.rs b/crates/valence_entity/src/lib.rs index 8db874956..2ddbd68f2 100644 --- a/crates/valence_entity/src/lib.rs +++ b/crates/valence_entity/src/lib.rs @@ -33,7 +33,7 @@ use tracing::warn; use tracked_data::TrackedData; use valence_math::{DVec3, Vec3}; use valence_protocol::{Decode, Encode, VarInt}; -use valence_server_common::{Despawned, UniqueId}; +use valence_server_common::{Despawned, LayerId, OldLayerId, UniqueId}; include!(concat!(env!("OUT_DIR"), "/entity.rs")); pub struct EntityPlugin; @@ -90,7 +90,6 @@ impl Plugin for EntityPlugin { clear_animation_changes, clear_tracked_data_changes, update_old_position, - update_old_layer_id, ) .in_set(ClearEntityChangesSet), ); @@ -105,12 +104,6 @@ fn update_old_position(mut query: Query<(&Position, &mut OldPosition)>) { } } -fn update_old_layer_id(mut query: Query<(&EntityLayerId, &mut OldEntityLayerId)>) { - for (loc, mut old_loc) in &mut query { - old_loc.0 = loc.0; - } -} - fn remove_despawned_from_manager( entities: Query<&EntityId, (With, With)>, mut manager: ResMut, @@ -163,45 +156,6 @@ fn clear_tracked_data_changes(mut tracked_data: Query<&mut TrackedData, Changed< } } -/// Contains the entity layer an entity is on. -#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref)] -#[deprecated] -pub struct EntityLayerId(pub Entity); - -impl Default for EntityLayerId { - fn default() -> Self { - Self(Entity::PLACEHOLDER) - } -} - -impl PartialEq for EntityLayerId { - fn eq(&self, other: &OldEntityLayerId) -> bool { - self.0 == other.0 - } -} - -/// The value of [`EntityLayerId`] from the end of the previous tick. -#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref)] -pub struct OldEntityLayerId(Entity); - -impl OldEntityLayerId { - pub fn get(&self) -> Entity { - self.0 - } -} - -impl Default for OldEntityLayerId { - fn default() -> Self { - Self(Entity::PLACEHOLDER) - } -} - -impl PartialEq for OldEntityLayerId { - fn eq(&self, other: &EntityLayerId) -> bool { - self.0 == other.0 - } -} - #[derive(Component, Copy, Clone, PartialEq, Default, Debug, Deref, DerefMut)] pub struct Position(pub DVec3); diff --git a/crates/valence_entity/src/query.rs b/crates/valence_entity/src/query.rs index eff6dbd90..2e3649d66 100644 --- a/crates/valence_entity/src/query.rs +++ b/crates/valence_entity/src/query.rs @@ -12,12 +12,12 @@ use valence_protocol::packets::play::{ }; use valence_protocol::var_int::VarInt; use valence_protocol::ByteAngle; -use valence_server_common::UniqueId; +use valence_server_common::{LayerId, OldLayerId, UniqueId}; use crate::tracked_data::TrackedData; use crate::{ - EntityAnimations, EntityId, EntityKind, EntityLayerId, EntityStatuses, HeadYaw, Look, - ObjectData, OldEntityLayerId, OldPosition, OnGround, Position, Velocity, + EntityAnimations, EntityId, EntityKind, EntityStatuses, HeadYaw, Look, ObjectData, OldPosition, + OnGround, Position, Velocity, }; #[derive(WorldQuery)] @@ -89,8 +89,8 @@ pub struct UpdateEntityQuery { pub id: &'static EntityId, pub pos: &'static Position, pub old_pos: &'static OldPosition, - pub loc: &'static EntityLayerId, - pub old_loc: &'static OldEntityLayerId, + pub layer: &'static LayerId, + pub old_layer: &'static OldLayerId, pub look: Ref<'static, Look>, pub head_yaw: Ref<'static, HeadYaw>, pub on_ground: &'static OnGround, diff --git a/crates/valence_scoreboard/src/components.rs b/crates/valence_scoreboard/src/components.rs index 779410e20..ceaa2259e 100644 --- a/crates/valence_scoreboard/src/components.rs +++ b/crates/valence_scoreboard/src/components.rs @@ -5,7 +5,7 @@ use derive_more::{Deref, DerefMut}; use valence_server::protocol::packets::play::scoreboard_display_s2c::ScoreboardPosition; use valence_server::protocol::packets::play::scoreboard_objective_update_s2c::ObjectiveRenderType; use valence_server::text::IntoText; -use valence_server::{Text, LayerId}; +use valence_server::{LayerId, Text}; /// A string that identifies an objective. There is one scoreboard per /// objective. It's generally not safe to modify this after it's been created. diff --git a/crates/valence_server/src/lib.rs b/crates/valence_server/src/lib.rs index ccd12630b..194d8ea42 100644 --- a/crates/valence_server/src/lib.rs +++ b/crates/valence_server/src/lib.rs @@ -47,13 +47,13 @@ pub use chunk_view::ChunkView; pub use client::Client; pub use dimension_layer::{Chunk, DimensionLayerBundle, LoadedChunk}; pub use event_loop::{EventLoopPostUpdate, EventLoopPreUpdate, EventLoopUpdate}; -pub use layer::CombinedLayerBundle; +pub use layer::{CombinedLayerBundle, OldVisibleLayers, VisibleLayers}; pub use valence_protocol::{ block, ident, item, math, text, uuid, BiomePos, BlockPos, BlockState, ChunkPos, CompressionThreshold, Difficulty, Direction, GameMode, Hand, Ident, ItemKind, ItemStack, Text, MINECRAFT_VERSION, PROTOCOL_VERSION, }; -pub use valence_server_common::*; +pub use valence_server_common::{LayerId, OldLayerId, *}; pub use { bevy_app as app, bevy_ecs as ecs, rand, valence_entity as entity, valence_nbt as nbt, valence_protocol as protocol, valence_registry as registry, diff --git a/examples/bench_players.rs b/examples/bench_players.rs index 2d33acd12..132ddab98 100644 --- a/examples/bench_players.rs +++ b/examples/bench_players.rs @@ -2,9 +2,9 @@ use std::time::Instant; -use valence::client::{VisibleChunkLayer, VisibleEntityLayers}; use valence::prelude::*; use valence::ServerSettings; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_Y: i32 = 64; @@ -51,8 +51,7 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = - CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { @@ -74,29 +73,20 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.insert(layer); pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); *game_mode = GameMode::Creative; } diff --git a/src/lib.rs b/src/lib.rs index e1753fe3c..1c697c1c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,8 +128,8 @@ pub mod prelude { }; pub use valence_server::entity::hitbox::{Hitbox, HitboxShape}; pub use valence_server::entity::{ - EntityAnimation, EntityKind, EntityLayerId, EntityManager, EntityStatus, HeadYaw, Look, - OldEntityLayerId, OldPosition, Position, + EntityAnimation, EntityKind, EntityManager, EntityStatus, HeadYaw, Look, OldPosition, + Position, }; pub use valence_server::event_loop::{ EventLoopPostUpdate, EventLoopPreUpdate, EventLoopUpdate, @@ -145,8 +145,9 @@ pub mod prelude { pub use valence_server::spawn::{ClientSpawnQuery, ClientSpawnQueryReadOnly}; pub use valence_server::title::SetTitle as _; pub use valence_server::{ - ident, BlockPos, Chunk, ChunkPos, ChunkView, Despawned, CombinedLayerBundle, - Direction, GameMode, Hand, ItemKind, ItemStack, LoadedChunk, Server, UniqueId, + ident, BlockPos, Chunk, ChunkPos, ChunkView, CombinedLayerBundle, Despawned, Direction, + GameMode, Hand, ItemKind, ItemStack, LayerId, LoadedChunk, OldLayerId, OldVisibleLayers, + Server, UniqueId, VisibleLayers, MINECRAFT_VERSION, }; pub use super::DefaultPlugins; diff --git a/src/testing.rs b/src/testing.rs index 44bc2bedd..832574e5c 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -7,7 +7,6 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bytes::{Buf, BufMut, BytesMut}; use uuid::Uuid; -use valence_ident::ident; use valence_network::NetworkPlugin; use valence_registry::{BiomeRegistry, DimensionTypeRegistry}; use valence_server::client::ClientBundleArgs; From 9f02bc7ec861c1a450055bee8e0c04521477fd06 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 17:42:05 -0700 Subject: [PATCH 13/16] get bench_players working --- crates/valence_server/src/action.rs | 2 +- crates/valence_server/src/client.rs | 3 +- crates/valence_server/src/dimension_layer.rs | 33 +++++++++------ .../src/dimension_layer/batch.rs | 28 ++++++++++--- .../src/dimension_layer/chunk.rs | 2 + .../src/dimension_layer/chunk_index.rs | 42 +++++++++++++++---- .../src/dimension_layer/plugin.rs | 8 ++-- crates/valence_server/src/layer.rs | 4 +- 8 files changed, 90 insertions(+), 32 deletions(-) diff --git a/crates/valence_server/src/action.rs b/crates/valence_server/src/action.rs index 1b0076c32..6c8d284c4 100644 --- a/crates/valence_server/src/action.rs +++ b/crates/valence_server/src/action.rs @@ -11,7 +11,7 @@ use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; pub struct ActionPlugin; impl Plugin for ActionPlugin { - fn build(&self, app: &mut App) { + fn build(&self, _app: &mut App) { /* app.add_event::() .add_systems(EventLoopPreUpdate, handle_player_action) diff --git a/crates/valence_server/src/client.rs b/crates/valence_server/src/client.rs index 09f8eab8e..d8ec8a73f 100644 --- a/crates/valence_server/src/client.rs +++ b/crates/valence_server/src/client.rs @@ -51,7 +51,8 @@ pub struct SelfTrackedDataSet; impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { - app.configure_set(PostUpdate, SelfTrackedDataSet.before(FlushPacketsSet)) + app.init_resource::() + .configure_set(PostUpdate, SelfTrackedDataSet.before(FlushPacketsSet)) .add_systems( PostUpdate, ( diff --git a/crates/valence_server/src/dimension_layer.rs b/crates/valence_server/src/dimension_layer.rs index 992d73e48..949b8f01d 100644 --- a/crates/valence_server/src/dimension_layer.rs +++ b/crates/valence_server/src/dimension_layer.rs @@ -17,7 +17,7 @@ use valence_registry::dimension_type::DimensionTypeId; use valence_registry::{BiomeRegistry, DimensionTypeRegistry}; use valence_server_common::Server; -use self::batch::BlockBatch; +use self::batch::Batch; use self::block::Block; use crate::layer::message::{LayerMessages, MessageScope}; use crate::layer::{ChunkViewIndex, LayerViewers}; @@ -25,7 +25,7 @@ use crate::layer::{ChunkViewIndex, LayerViewers}; #[derive(Component)] pub struct DimensionLayerBundle { pub chunk_index: ChunkIndex, - pub block_batch: BlockBatch, + pub block_batch: Batch, pub dimension_info: DimensionInfo, pub chunk_view_index: ChunkViewIndex, pub layer_viewers: LayerViewers, @@ -42,7 +42,7 @@ impl DimensionLayerBundle { let dim = &dimensions[dimension_type]; Self { - chunk_index: ChunkIndex::new(dim.height), + chunk_index: ChunkIndex::new(dim.height, dim.min_y), block_batch: Default::default(), dimension_info: DimensionInfo { dimension_type, @@ -62,7 +62,7 @@ impl DimensionLayerBundle { #[world_query(mutable)] pub struct DimensionLayerQuery { pub chunk_index: &'static mut ChunkIndex, - pub block_batch: &'static mut BlockBatch, + pub batch: &'static mut Batch, pub dimension_info: &'static DimensionInfo, pub chunk_view_index: &'static mut ChunkViewIndex, pub layer_viewers: &'static LayerViewers, @@ -84,11 +84,11 @@ macro_rules! immutable_query_methods { } pub fn biome(&self, pos: impl Into) -> Option { - todo!() + self.chunk_index.biome(pos) } pub fn block(&self, pos: impl Into) -> Option { - todo!() + self.chunk_index.block(pos) } pub fn chunk(&self, pos: impl Into) -> Option<&LoadedChunk> { @@ -100,12 +100,23 @@ macro_rules! immutable_query_methods { impl DimensionLayerQueryItem<'_> { immutable_query_methods!(); - pub fn set_biome(&mut self, pos: impl Into, biome: BiomeId) -> Option { - todo!() + pub fn set_block(&mut self, pos: impl Into, block: impl Into) { + self.batch.set_block(pos, block) } - pub fn set_block(&mut self, pos: impl Into, block: impl Into) { - todo!() + pub fn set_biome(&mut self, pos: impl Into, biome: BiomeId) { + self.batch.set_biome(pos, biome) + } + + /// Apply the batched block and biome mutations to the layer. + pub fn apply_batch(&mut self) { + self.batch.apply( + &mut *self.chunk_index, + self.dimension_info, + &mut *self.layer_messages, + ); + + self.batch.clear(); } pub fn chunk_mut(&mut self, pos: impl Into) -> Option<&mut LoadedChunk> { @@ -132,7 +143,6 @@ impl DimensionLayerQueryItem<'_> { pub fn chunk_entry(&mut self, pos: impl Into) -> Entry { match self.chunk_index.entry(pos) { chunk_index::Entry::Occupied(entry) => Entry::Occupied(OccupiedEntry { - chunk_view_index: &*self.chunk_view_index, layer_messages: self.layer_messages.reborrow(), info: &self.dimension_info, entry, @@ -206,7 +216,6 @@ impl<'a> Entry<'a> { #[derive(Debug)] pub struct OccupiedEntry<'a> { - chunk_view_index: &'a ChunkViewIndex, layer_messages: Mut<'a, LayerMessages>, info: &'a DimensionInfo, entry: chunk_index::OccupiedEntry<'a>, diff --git a/crates/valence_server/src/dimension_layer/batch.rs b/crates/valence_server/src/dimension_layer/batch.rs index ffab1752b..51928b186 100644 --- a/crates/valence_server/src/dimension_layer/batch.rs +++ b/crates/valence_server/src/dimension_layer/batch.rs @@ -6,7 +6,8 @@ use valence_generated::block::BlockEntityKind; use valence_nbt::Compound; use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; use valence_protocol::packets::play::{BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDeltaUpdateS2c}; -use valence_protocol::{BlockPos, ChunkSectionPos, WritePacket}; +use valence_protocol::{BiomePos, BlockPos, ChunkSectionPos, WritePacket}; +use valence_registry::biome::BiomeId; use super::block::Block; use super::chunk::LoadedChunk; @@ -15,14 +16,20 @@ use crate::dimension_layer::chunk::ChunkOps; use crate::layer::message::{LayerMessages, MessageScope}; use crate::BlockState; +/// Batched block and biome mutations. +/// +/// Changes are automatically applied at the end of the tick or when +/// [`apply_batch`] is called. +/// +/// [`apply_batch`]: super::DimensionLayerQueryItem::apply_batch #[derive(Component, Default)] -pub struct BlockBatch { +pub struct Batch { state_updates: Vec, block_entities: Vec<(BlockPos, BlockEntityKind, Compound)>, sect_update_buf: Vec, } -impl BlockBatch { +impl Batch { pub fn set_block(&mut self, pos: impl Into, block: impl Into) { let pos = pos.into(); let block = block.into(); @@ -35,14 +42,25 @@ impl BlockBatch { } } + pub fn set_biome(&mut self, pos: impl Into, biome: BiomeId) { + todo!() + } + pub fn clear(&mut self) { self.state_updates.clear(); self.block_entities.clear(); } pub fn shrink_to_fit(&mut self) { - self.state_updates.shrink_to_fit(); - self.block_entities.shrink_to_fit(); + let Self { + state_updates, + block_entities, + sect_update_buf, + } = self; + + state_updates.shrink_to_fit(); + block_entities.shrink_to_fit(); + sect_update_buf.shrink_to_fit(); } pub fn is_empty(&self) -> bool { diff --git a/crates/valence_server/src/dimension_layer/chunk.rs b/crates/valence_server/src/dimension_layer/chunk.rs index d8b0b2ebc..db016b29d 100644 --- a/crates/valence_server/src/dimension_layer/chunk.rs +++ b/crates/valence_server/src/dimension_layer/chunk.rs @@ -242,6 +242,7 @@ pub(super) const fn bit_width(n: usize) -> usize { (usize::BITS - n.leading_zeros()) as _ } +#[inline] pub(super) fn block_offsets(block_pos: BlockPos, min_y: i32, height: i32) -> Option<[u32; 3]> { let off_x = block_pos.x.rem_euclid(16); let off_z = block_pos.z.rem_euclid(16); @@ -254,6 +255,7 @@ pub(super) fn block_offsets(block_pos: BlockPos, min_y: i32, height: i32) -> Opt } } +#[inline] pub(super) fn biome_offsets(biome_pos: BiomePos, min_y: i32, height: i32) -> Option<[u32; 3]> { let off_x = biome_pos.x.rem_euclid(4); let off_z = biome_pos.z.rem_euclid(4); diff --git a/crates/valence_server/src/dimension_layer/chunk_index.rs b/crates/valence_server/src/dimension_layer/chunk_index.rs index 5c1bcf311..363df53d0 100644 --- a/crates/valence_server/src/dimension_layer/chunk_index.rs +++ b/crates/valence_server/src/dimension_layer/chunk_index.rs @@ -1,9 +1,10 @@ pub use bevy_ecs::prelude::*; use rustc_hash::FxHashMap; -use valence_protocol::{BlockPos, ChunkPos}; +use valence_protocol::{BiomePos, BlockPos, ChunkPos}; +use valence_registry::biome::BiomeId; use super::block::{Block, BlockRef}; -use super::chunk::{Chunk, LoadedChunk}; +use super::chunk::{biome_offsets, block_offsets, Chunk, ChunkOps, LoadedChunk}; /// The mapping of chunk positions to [`LoadedChunk`]s in a dimension layer. /// @@ -13,13 +14,15 @@ use super::chunk::{Chunk, LoadedChunk}; pub struct ChunkIndex { map: FxHashMap, height: i32, + min_y: i32, } impl ChunkIndex { - pub(crate) fn new(height: i32) -> Self { + pub(crate) fn new(height: i32, min_y: i32) -> Self { Self { map: Default::default(), height, + min_y, } } @@ -34,7 +37,7 @@ impl ChunkIndex { pub fn insert(&mut self, pos: impl Into, chunk: Chunk) -> Option { match self.entry(pos.into()) { Entry::Occupied(mut o) => Some(o.insert(chunk)), - Entry::Vacant(mut v) => { + Entry::Vacant(v) => { v.insert(chunk); None } @@ -61,7 +64,11 @@ impl ChunkIndex { } pub fn block(&self, pos: impl Into) -> Option { - todo!() + let pos = pos.into(); + let chunk = self.get(pos)?; + + let [x, y, z] = block_offsets(pos, self.min_y, self.height)?; + Some(chunk.block(x, y, z)) } pub fn set_block( @@ -69,7 +76,28 @@ impl ChunkIndex { pos: impl Into, block: impl Into, ) -> Option { - todo!() + let pos = pos.into(); + let chunk = self.map.get_mut(&ChunkPos::from(pos))?; + + let [x, y, z] = block_offsets(pos, self.min_y, self.height)?; + Some(chunk.set_block(x, y, z, block)) + } + + pub fn biome(&self, pos: impl Into) -> Option { + let pos = pos.into(); + let chunk_pos = ChunkPos::from(pos); + + let chunk = self.get(chunk_pos)?; + let [x, y, z] = biome_offsets(pos, self.min_y, self.height)?; + Some(chunk.biome(x, y, z)) + } + + pub fn set_biome(&mut self, pos: impl Into, biome: BiomeId) -> Option { + let pos = pos.into(); + let chunk = self.map.get_mut(&ChunkPos::from(pos))?; + + let [x, y, z] = biome_offsets(pos, self.min_y, self.height)?; + Some(chunk.set_biome(x, y, z, biome)) } pub fn retain(&mut self, mut f: impl FnMut(ChunkPos, &mut LoadedChunk) -> bool) { @@ -84,7 +112,7 @@ impl ChunkIndex { self.map.shrink_to_fit() } - // TODO: iter, iter_mut, clear + // TODO: iter, iter_mut } #[derive(Debug)] diff --git a/crates/valence_server/src/dimension_layer/plugin.rs b/crates/valence_server/src/dimension_layer/plugin.rs index bb3eae1a1..6b151ffa3 100644 --- a/crates/valence_server/src/dimension_layer/plugin.rs +++ b/crates/valence_server/src/dimension_layer/plugin.rs @@ -78,7 +78,7 @@ fn update_dimension_layer_views( for &layer in old_visible.difference(&visible) { if let Ok((mut index, _)) = layers.get_mut(layer) { for pos in old_view.iter() { - if let Some(mut chunk) = index.get_mut(pos) { + if let Some(chunk) = index.get_mut(pos) { client.write_packet(&UnloadChunkS2c { pos }); chunk.viewer_count -= 1; } @@ -93,7 +93,7 @@ fn update_dimension_layer_views( for &layer in visible.difference(&old_visible) { if let Ok((mut index, info)) = layers.get_mut(layer) { for pos in view.iter() { - if let Some(mut chunk) = index.get_mut(pos) { + if let Some(chunk) = index.get_mut(pos) { chunk.write_chunk_init_packet(&mut *client, pos, info); chunk.viewer_count += 1; } @@ -110,7 +110,7 @@ fn update_dimension_layer_views( if let Ok((mut index, info)) = layers.get_mut(layer) { // Unload old chunks in view. for pos in old_view.diff(view) { - if let Some(mut chunk) = index.get_mut(pos) { + if let Some(chunk) = index.get_mut(pos) { client.write_packet(&UnloadChunkS2c { pos }); chunk.viewer_count -= 1; } @@ -118,7 +118,7 @@ fn update_dimension_layer_views( // Load new chunks in view. for pos in view.diff(old_view) { - if let Some(mut chunk) = index.get_mut(pos) { + if let Some(chunk) = index.get_mut(pos) { chunk.write_chunk_init_packet(&mut *client, pos, info); chunk.viewer_count += 1; } diff --git a/crates/valence_server/src/layer.rs b/crates/valence_server/src/layer.rs index e7197ea2c..79bb12164 100644 --- a/crates/valence_server/src/layer.rs +++ b/crates/valence_server/src/layer.rs @@ -16,7 +16,7 @@ use valence_server_common::{Despawned, Server}; use self::message::{LayerMessages, MessageKind}; use crate::client::FlushPacketsSet; -use crate::dimension_layer::batch::BlockBatch; +use crate::dimension_layer::batch::Batch; use crate::dimension_layer::{ChunkIndex, DimensionInfo}; use crate::layer::message::MessageScope; use crate::{Client, DimensionLayerBundle}; @@ -54,7 +54,7 @@ impl Plugin for LayerPlugin { #[derive(Bundle)] pub struct CombinedLayerBundle { pub chunk_index: ChunkIndex, - pub block_batch: BlockBatch, + pub block_batch: Batch, pub dimension_info: DimensionInfo, // TODO: entity layer components. pub chunk_view_index: ChunkViewIndex, From 081ce238baaea046cafaaff819dda07f44815619 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 17:47:18 -0700 Subject: [PATCH 14/16] player actions --- crates/valence_server/src/action.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/valence_server/src/action.rs b/crates/valence_server/src/action.rs index 6c8d284c4..8e0c16a82 100644 --- a/crates/valence_server/src/action.rs +++ b/crates/valence_server/src/action.rs @@ -5,21 +5,20 @@ use valence_protocol::packets::play::player_action_c2s::PlayerAction; use valence_protocol::packets::play::{PlayerActionC2s, PlayerActionResponseS2c}; use valence_protocol::{BlockPos, Direction, VarInt, WritePacket}; -use crate::client::Client; +use crate::client::{Client, FlushPacketsSet}; use crate::event_loop::{EventLoopPreUpdate, PacketEvent}; pub struct ActionPlugin; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ActionSet; + impl Plugin for ActionPlugin { - fn build(&self, _app: &mut App) { - /* + fn build(&self, app: &mut App) { app.add_event::() + .configure_set(PostUpdate, ActionSet.before(FlushPacketsSet)) .add_systems(EventLoopPreUpdate, handle_player_action) - .add_systems( - PostUpdate, - acknowledge_player_actions.in_set(UpdateClientsSet), - ); - */ + .add_systems(PostUpdate, acknowledge_player_actions.in_set(ActionSet)); } } From 7bb5c6194436a736dd566aa5836c90c75c4c43b6 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 17:55:05 -0700 Subject: [PATCH 15/16] update valence_advancement, valence_inventory --- crates/valence_advancement/src/lib.rs | 21 +++------ crates/valence_inventory/src/lib.rs | 63 +++++++++++++++------------ 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/crates/valence_advancement/src/lib.rs b/crates/valence_advancement/src/lib.rs index a5229f025..e2a37e2ac 100644 --- a/crates/valence_advancement/src/lib.rs +++ b/crates/valence_advancement/src/lib.rs @@ -9,6 +9,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use bevy_app::prelude::*; use bevy_ecs::prelude::*; +use bevy_ecs::schedule::SystemSet; use bevy_ecs::system::SystemParam; pub use bevy_hierarchy; use bevy_hierarchy::{Children, HierarchyPlugin, Parent}; @@ -27,21 +28,12 @@ use valence_server::{Ident, ItemStack, Text}; pub struct AdvancementPlugin; #[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)] -pub struct WriteAdvancementPacketToClientsSet; - -#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)] -pub struct WriteAdvancementToCacheSet; +pub struct AdvancementSet; impl Plugin for AdvancementPlugin { fn build(&self, app: &mut bevy_app::App) { app.add_plugins(HierarchyPlugin) - .configure_sets( - PostUpdate, - ( - WriteAdvancementPacketToClientsSet.before(FlushPacketsSet), - WriteAdvancementToCacheSet.before(WriteAdvancementPacketToClientsSet), - ), - ) + .configure_set(PostUpdate, AdvancementSet.before(FlushPacketsSet)) .add_event::() .add_systems( PreUpdate, @@ -53,9 +45,10 @@ impl Plugin for AdvancementPlugin { .add_systems( PostUpdate, ( - update_advancement_cached_bytes.in_set(WriteAdvancementToCacheSet), - send_advancement_update_packet.in_set(WriteAdvancementPacketToClientsSet), - ), + update_advancement_cached_bytes.before(send_advancement_update_packet), + send_advancement_update_packet, + ) + .in_set(AdvancementSet), ); } } diff --git a/crates/valence_inventory/src/lib.rs b/crates/valence_inventory/src/lib.rs index 4498b9aac..604aa2491 100644 --- a/crates/valence_inventory/src/lib.rs +++ b/crates/valence_inventory/src/lib.rs @@ -25,6 +25,7 @@ use std::ops::Range; use bevy_app::prelude::*; use bevy_ecs::prelude::*; +use bevy_ecs::schedule::SystemSet; use derive_more::{Deref, DerefMut}; use tracing::{debug, warn}; use valence_server::client::{Client, FlushPacketsSet, SpawnClientsSet}; @@ -44,36 +45,44 @@ mod validate; pub struct InventoryPlugin; +#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)] +pub struct InventorySet; + impl Plugin for InventoryPlugin { fn build(&self, app: &mut bevy_app::App) { - app.add_systems( - PreUpdate, - init_new_client_inventories.after(SpawnClientsSet), - ) - .add_systems( - PostUpdate, - ( - update_client_on_close_inventory.before(update_open_inventories), - update_open_inventories, - update_player_inventories, + app.configure_set(PostUpdate, InventorySet.before(FlushPacketsSet)) + .add_systems( + PreUpdate, + init_new_client_inventories + .after(SpawnClientsSet) + .in_set(InventorySet), + ) + .add_systems( + PostUpdate, + ( + update_client_on_close_inventory.before(update_open_inventories), + update_open_inventories, + update_player_inventories, + ) + .before(FlushPacketsSet) + .in_set(InventorySet), + ) + .add_systems( + EventLoopPreUpdate, + ( + handle_update_selected_slot, + handle_click_slot, + handle_creative_inventory_action, + handle_close_handled_screen, + handle_player_actions, + ) + .in_set(InventorySet), ) - .before(FlushPacketsSet), - ) - .add_systems( - EventLoopPreUpdate, - ( - handle_update_selected_slot, - handle_click_slot, - handle_creative_inventory_action, - handle_close_handled_screen, - handle_player_actions, - ), - ) - .init_resource::() - .add_event::() - .add_event::() - .add_event::() - .add_event::(); + .init_resource::() + .add_event::() + .add_event::() + .add_event::() + .add_event::(); } } From 4ac9dd356957a1ac23e2bb27ed564dac0988b7d4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 18 Sep 2023 19:41:06 -0700 Subject: [PATCH 16/16] mostly update examples, remove old code --- benches/idle.rs | 8 +- benches/many_players.rs | 2 +- crates/valence_scoreboard/README.md | 2 +- crates/valence_server/src/layer/chunk.rs | 823 ------------------ .../valence_server/src/layer/chunk/loaded.rs | 330 ------- crates/valence_server/src/layer_old.rs | 139 --- crates/valence_server/src/layer_old/bvh.rs | 339 -------- crates/valence_server/src/layer_old/chunk.rs | 726 --------------- .../src/layer_old/chunk/batch.rs | 184 ---- .../src/layer_old/chunk/batch/basic.rs | 243 ------ .../src/layer_old/chunk/biome.rs | 135 --- .../src/layer_old/chunk/block_update.rs | 357 -------- .../src/layer_old/chunk/chunk.rs | 369 -------- .../src/layer_old/chunk/loaded.rs | 330 ------- .../src/layer_old/chunk/unloaded.rs | 244 ------ crates/valence_server/src/layer_old/entity.rs | 529 ----------- .../valence_server/src/layer_old/message.rs | 323 ------- examples/advancement.rs | 28 +- examples/anvil_loading.rs | 17 +- examples/biomes.rs | 29 +- examples/block_entities.rs | 35 +- examples/boss_bar.rs | 35 +- examples/building.rs | 26 +- examples/chest.rs | 33 +- examples/combat.rs | 32 +- examples/cow_sphere.rs | 38 +- examples/ctf.rs | 76 +- examples/death.rs | 51 +- examples/entity_hitbox.rs | 31 +- examples/game_of_life.rs | 30 +- examples/parkour.rs | 3 +- examples/particles.rs | 33 +- examples/player_list.rs | 32 +- examples/resource_pack.rs | 38 +- examples/terrain.rs | 40 +- examples/text.rs | 29 +- examples/weather.rs | 35 +- examples/world_border.rs | 47 +- src/tests/boss_bar.rs | 6 +- src/tests/layer.rs | 12 +- src/tests/scoreboard.rs | 9 +- tools/playground/src/playground.template.rs | 27 +- website/book/1-getting-started/setup.md | 23 +- 43 files changed, 280 insertions(+), 5598 deletions(-) delete mode 100644 crates/valence_server/src/layer/chunk.rs delete mode 100644 crates/valence_server/src/layer/chunk/loaded.rs delete mode 100644 crates/valence_server/src/layer_old.rs delete mode 100644 crates/valence_server/src/layer_old/bvh.rs delete mode 100644 crates/valence_server/src/layer_old/chunk.rs delete mode 100644 crates/valence_server/src/layer_old/chunk/batch.rs delete mode 100644 crates/valence_server/src/layer_old/chunk/batch/basic.rs delete mode 100644 crates/valence_server/src/layer_old/chunk/biome.rs delete mode 100644 crates/valence_server/src/layer_old/chunk/block_update.rs delete mode 100644 crates/valence_server/src/layer_old/chunk/chunk.rs delete mode 100644 crates/valence_server/src/layer_old/chunk/loaded.rs delete mode 100644 crates/valence_server/src/layer_old/chunk/unloaded.rs delete mode 100644 crates/valence_server/src/layer_old/entity.rs delete mode 100644 crates/valence_server/src/layer_old/message.rs diff --git a/benches/idle.rs b/benches/idle.rs index f879da39e..174f6cb31 100644 --- a/benches/idle.rs +++ b/benches/idle.rs @@ -25,17 +25,19 @@ fn setup( biomes: Res, server: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in -50..50 { for x in -50..50 { - layer.chunk.set_block([x, 64, z], BlockState::GRASS_BLOCK); + layer + .chunk_index + .set_block([x, 64, z], BlockState::GRASS_BLOCK); } } diff --git a/benches/many_players.rs b/benches/many_players.rs index 53ec7617a..026bec535 100644 --- a/benches/many_players.rs +++ b/benches/many_players.rs @@ -51,7 +51,7 @@ fn run_many_players( for z in -world_size..world_size { for x in -world_size..world_size { - layer.chunk.insert_chunk(ChunkPos::new(x, z), Chunk::new()); + layer.chunk_index.insert(ChunkPos::new(x, z), Chunk::new()); } } diff --git a/crates/valence_scoreboard/README.md b/crates/valence_scoreboard/README.md index 11a03e3e5..7dbb77b67 100644 --- a/crates/valence_scoreboard/README.md +++ b/crates/valence_scoreboard/README.md @@ -2,7 +2,7 @@ This crate provides functionality for creating and managing scoreboards. In Minecraft, a scoreboard references an [`Objective`], which is a mapping from strings to scores. Typically, the string is a player name, and the score is a number of points, but the string can be any arbitrary string <= 40 chars, and the score can be any integer. -In Valence, scoreboards obey the rules implied by layers, meaning that every Objective must have an [`EntityLayerId`] associated with it. Scoreboards are only transmitted to clients if the [`EntityLayer`] is visible to the client. +In Valence, scoreboards obey the rules implied by layers, meaning that every Objective must have an [`LayerId`] associated with it. Scoreboards are only transmitted to clients if the [`EntityLayer`] is visible to the client. To create a scoreboard, spawn an [`ObjectiveBundle`]. The [`Objective`] component represents the identifier that the client uses to reference the scoreboard. diff --git a/crates/valence_server/src/layer/chunk.rs b/crates/valence_server/src/layer/chunk.rs deleted file mode 100644 index 2308c25b3..000000000 --- a/crates/valence_server/src/layer/chunk.rs +++ /dev/null @@ -1,823 +0,0 @@ -pub mod batch; -mod biome; -#[allow(clippy::module_inception)] -mod chunk; -mod loaded; -mod paletted_container; -mod unloaded; - -use std::borrow::Cow; -use std::collections::hash_map::{Entry, OccupiedEntry, VacantEntry}; - -use bevy_app::prelude::*; -use bevy_ecs::prelude::*; -pub use biome::*; -pub use chunk::*; -pub use loaded::LoadedChunk; -use rustc_hash::FxHashMap; -pub use unloaded::Chunk; -use valence_math::{DVec3, Vec3}; -use valence_nbt::Compound; -use valence_protocol::encode::{PacketWriter, WritePacket}; -use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; -use valence_protocol::packets::play::particle_s2c::Particle; -use valence_protocol::packets::play::{ParticleS2c, PlaySoundS2c}; -use valence_protocol::sound::{Sound, SoundCategory, SoundId}; -use valence_protocol::{BiomePos, BlockPos, ChunkPos, CompressionThreshold, Encode, Ident, Packet}; -use valence_registry::biome::{BiomeId, BiomeRegistry}; -use valence_registry::DimensionTypeRegistry; -use valence_server_common::Server; - -use super::bvh::GetChunkPos; -use super::message::Messages; -use super::{Layer, UpdateLayersPostClientSet, UpdateLayersPreClientSet}; - -/// A [`Component`] containing the [chunks](LoadedChunk) and [dimension -/// information](valence_registry::dimension_type::DimensionTypeId) of a -/// Minecraft world. -#[derive(Component, Debug)] -pub struct ChunkLayer { - messages: ChunkLayerMessages, - chunks: FxHashMap, - info: ChunkLayerInfo, - block_update_buf: Vec, -} - -/// Chunk layer information. -#[derive(Debug)] -pub(crate) struct ChunkLayerInfo { - dimension_type_name: Ident, - height: u32, - min_y: i32, - biome_registry_len: usize, - threshold: CompressionThreshold, -} - -type ChunkLayerMessages = Messages; - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub(crate) enum GlobalMsg { - /// Send packet data to all clients viewing the layer. - Packet, - /// Send packet data to all clients viewing the layer, except the client - /// identified by `except`. - PacketExcept { except: Entity }, -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub(crate) enum LocalMsg { - /// Send packet data to all clients viewing the layer in view of `pos`. - PacketAt { - pos: ChunkPos, - }, - PacketAtExcept { - pos: ChunkPos, - except: Entity, - }, - RadiusAt { - center: BlockPos, - radius_squared: u32, - }, - RadiusAtExcept { - center: BlockPos, - radius_squared: u32, - except: Entity, - }, - /// Instruct clients to load or unload the chunk at `pos`. Loading and - /// unloading are combined into a single message so that load/unload order - /// is not lost when messages are sorted. - /// - /// Message content is a single byte indicating load (1) or unload (0). - ChangeChunkState { - pos: ChunkPos, - }, - /// Message content is the data for a single biome in the "change biomes" - /// packet. - ChangeBiome { - pos: ChunkPos, - }, -} - -impl GetChunkPos for LocalMsg { - fn chunk_pos(&self) -> ChunkPos { - match *self { - LocalMsg::PacketAt { pos } => pos, - LocalMsg::PacketAtExcept { pos, .. } => pos, - LocalMsg::RadiusAt { center, .. } => center.into(), - LocalMsg::RadiusAtExcept { center, .. } => center.into(), - LocalMsg::ChangeBiome { pos } => pos, - LocalMsg::ChangeChunkState { pos } => pos, - } - } -} - -impl ChunkLayer { - pub(crate) const LOAD: u8 = 0; - pub(crate) const UNLOAD: u8 = 1; - pub(crate) const OVERWRITE: u8 = 2; - - /// Creates a new chunk layer. - #[track_caller] - pub fn new( - dimension_type_name: impl Into>, - dimensions: &DimensionTypeRegistry, - biomes: &BiomeRegistry, - server: &Server, - ) -> Self { - let dimension_type_name = dimension_type_name.into(); - - let dim = &dimensions[dimension_type_name.as_str_ident()]; - - assert!( - (0..MAX_HEIGHT as i32).contains(&dim.height), - "invalid dimension height of {}", - dim.height - ); - - Self { - messages: Messages::new(), - chunks: Default::default(), - info: ChunkLayerInfo { - dimension_type_name, - height: dim.height as u32, - min_y: dim.min_y, - biome_registry_len: biomes.iter().len(), - threshold: server.compression_threshold(), - }, - block_update_buf: vec![], - } - } - - /// The name of the dimension this chunk layer is using. - pub fn dimension_type_name(&self) -> Ident<&str> { - self.info.dimension_type_name.as_str_ident() - } - - /// The height of this instance's dimension. - pub fn height(&self) -> u32 { - self.info.height - } - - /// The `min_y` of this instance's dimension. - pub fn min_y(&self) -> i32 { - self.info.min_y - } - - /// Get a reference to the chunk at the given position, if it is loaded. - pub fn chunk(&self, pos: impl Into) -> Option<&LoadedChunk> { - self.chunks.get(&pos.into()) - } - - /// Get a mutable reference to the chunk at the given position, if it is - /// loaded. - pub fn chunk_mut(&mut self, pos: impl Into) -> Option<&mut LoadedChunk> { - self.chunks.get_mut(&pos.into()) - } - - /// Insert a chunk into the instance at the given position. The previous - /// chunk data is returned. - pub fn insert_chunk(&mut self, pos: impl Into, chunk: Chunk) -> Option { - match self.chunk_entry(pos) { - ChunkEntry::Occupied(mut oe) => Some(oe.insert(chunk)), - ChunkEntry::Vacant(ve) => { - ve.insert(chunk); - None - } - } - } - - /// Unload the chunk at the given position, if it is loaded. Returns the - /// chunk if it was loaded. - pub fn remove_chunk(&mut self, pos: impl Into) -> Option { - match self.chunk_entry(pos) { - ChunkEntry::Occupied(oe) => Some(oe.remove()), - ChunkEntry::Vacant(_) => None, - } - } - - /// Unload all chunks in this instance. - pub fn clear_chunks(&mut self) { - self.retain_chunks(|_, _| false) - } - - /// Retain only the chunks for which the given predicate returns `true`. - pub fn retain_chunks(&mut self, mut f: F) - where - F: FnMut(ChunkPos, &mut LoadedChunk) -> bool, - { - self.chunks.retain(|pos, chunk| { - if !f(*pos, chunk) { - self.messages - .send_local_infallible(LocalMsg::ChangeChunkState { pos: *pos }, |b| { - b.push(Self::UNLOAD) - }); - - false - } else { - true - } - }); - } - - /// Get a [`ChunkEntry`] for the given position. - pub fn chunk_entry(&mut self, pos: impl Into) -> ChunkEntry { - match self.chunks.entry(pos.into()) { - Entry::Occupied(oe) => ChunkEntry::Occupied(OccupiedChunkEntry { - messages: &mut self.messages, - entry: oe, - }), - Entry::Vacant(ve) => ChunkEntry::Vacant(VacantChunkEntry { - height: self.info.height, - messages: &mut self.messages, - entry: ve, - }), - } - } - - /// Get an iterator over all loaded chunks in the instance. The order of the - /// chunks is undefined. - pub fn chunks(&self) -> impl Iterator + Clone + '_ { - self.chunks.iter().map(|(pos, chunk)| (*pos, chunk)) - } - - /// Get an iterator over all loaded chunks in the instance, mutably. The - /// order of the chunks is undefined. - pub fn chunks_mut(&mut self) -> impl Iterator + '_ { - self.chunks.iter_mut().map(|(pos, chunk)| (*pos, chunk)) - } - - /// Optimizes the memory usage of the instance. - pub fn shrink_to_fit(&mut self) { - for (_, chunk) in self.chunks_mut() { - chunk.shrink_to_fit(); - } - - self.chunks.shrink_to_fit(); - self.messages.shrink_to_fit(); - } - - pub fn block(&self, pos: impl Into) -> Option { - let pos = pos.into(); - - let y = pos - .y - .checked_sub(self.info.min_y) - .and_then(|y| y.try_into().ok())?; - - if y >= self.info.height { - return None; - } - - let chunk = self.chunk(pos)?; - - let x = pos.x.rem_euclid(16) as u32; - let z = pos.z.rem_euclid(16) as u32; - - Some(chunk.block(x, y, z)) - } - - pub fn set_block(&mut self, pos: impl Into, block: impl IntoBlock) -> Option { - let pos = pos.into(); - - let y = pos - .y - .checked_sub(self.info.min_y) - .and_then(|y| y.try_into().ok())?; - - if y >= self.info.height { - return None; - } - - let chunk = self.chunk_mut(pos)?; - - let x = pos.x.rem_euclid(16) as u32; - let z = pos.z.rem_euclid(16) as u32; - - Some(chunk.set_block(x, y, z, block)) - } - - pub fn block_entity_mut(&mut self, pos: impl Into) -> Option<&mut Compound> { - let pos = pos.into(); - - let y = pos - .y - .checked_sub(self.info.min_y) - .and_then(|y| y.try_into().ok())?; - - if y >= self.info.height { - return None; - } - - let chunk = self.chunk_mut(pos)?; - - let x = pos.x.rem_euclid(16) as u32; - let z = pos.z.rem_euclid(16) as u32; - - chunk.block_entity_mut(x, y, z) - } - - pub fn biome(&self, pos: impl Into) -> Option { - let pos = pos.into(); - - let y = pos - .y - .checked_sub(self.info.min_y / 4) - .and_then(|y| y.try_into().ok())?; - - if y >= self.info.height / 4 { - return None; - } - - let chunk = self.chunk(pos)?; - - let x = pos.x.rem_euclid(4) as u32; - let z = pos.z.rem_euclid(4) as u32; - - Some(chunk.biome(x, y, z)) - } - - pub fn set_biome(&mut self, pos: impl Into, biome: BiomeId) -> Option { - let pos = pos.into(); - - let y = pos - .y - .checked_sub(self.info.min_y / 4) - .and_then(|y| y.try_into().ok())?; - - if y >= self.info.height / 4 { - return None; - } - - let chunk = self.chunk_mut(pos)?; - - let x = pos.x.rem_euclid(4) as u32; - let z = pos.z.rem_euclid(4) as u32; - - Some(chunk.set_biome(x, y, z, biome)) - } - - pub(crate) fn info(&self) -> &ChunkLayerInfo { - &self.info - } - - pub(crate) fn messages(&self) -> &ChunkLayerMessages { - &self.messages - } - - // TODO: move to `valence_particle`. - /// Puts a particle effect at the given position in the world. The particle - /// effect is visible to all players in the instance with the - /// appropriate chunk in view. - pub fn play_particle( - &mut self, - particle: &Particle, - long_distance: bool, - position: impl Into, - offset: impl Into, - max_speed: f32, - count: i32, - ) { - let position = position.into(); - - self.view_writer(position).write_packet(&ParticleS2c { - particle: Cow::Borrowed(particle), - long_distance, - position, - offset: offset.into(), - max_speed, - count, - }); - } - - // TODO: move to `valence_sound`. - /// Plays a sound effect at the given position in the world. The sound - /// effect is audible to all players in the instance with the - /// appropriate chunk in view. - pub fn play_sound( - &mut self, - sound: Sound, - category: SoundCategory, - position: impl Into, - volume: f32, - pitch: f32, - ) { - let position = position.into(); - - self.view_writer(position).write_packet(&PlaySoundS2c { - id: SoundId::Direct { - id: sound.to_ident().into(), - range: None, - }, - category, - position: (position * 8.0).as_ivec3(), - volume, - pitch, - seed: rand::random(), - }); - } -} - -impl Layer for ChunkLayer { - type ExceptWriter<'a> = ExceptWriter<'a>; - - type ViewWriter<'a> = ViewWriter<'a>; - - type ViewExceptWriter<'a> = ViewExceptWriter<'a>; - - type RadiusWriter<'a> = RadiusWriter<'a>; - - type RadiusExceptWriter<'a> = RadiusExceptWriter<'a>; - - fn except_writer(&mut self, except: Entity) -> Self::ExceptWriter<'_> { - ExceptWriter { - layer: self, - except, - } - } - - fn view_writer(&mut self, pos: impl Into) -> Self::ViewWriter<'_> { - ViewWriter { - layer: self, - pos: pos.into(), - } - } - - fn view_except_writer( - &mut self, - pos: impl Into, - except: Entity, - ) -> Self::ViewExceptWriter<'_> { - ViewExceptWriter { - layer: self, - pos: pos.into(), - except, - } - } - - fn radius_writer( - &mut self, - center: impl Into, - radius: u32, - ) -> Self::RadiusWriter<'_> { - RadiusWriter { - layer: self, - center: center.into(), - radius, - } - } - - fn radius_except_writer( - &mut self, - center: impl Into, - radius: u32, - except: Entity, - ) -> Self::RadiusExceptWriter<'_> { - RadiusExceptWriter { - layer: self, - center: center.into(), - radius, - except, - } - } -} - -impl WritePacket for ChunkLayer { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.messages.send_global(GlobalMsg::Packet, |b| { - PacketWriter::new(b, self.info.threshold).write_packet_fallible(packet) - }) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.messages - .send_global_infallible(GlobalMsg::Packet, |b| b.extend_from_slice(bytes)); - } -} - -pub struct ExceptWriter<'a> { - layer: &'a mut ChunkLayer, - except: Entity, -} - -impl WritePacket for ExceptWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_global( - GlobalMsg::PacketExcept { - except: self.except, - }, - |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_global_infallible( - GlobalMsg::PacketExcept { - except: self.except, - }, - |b| b.extend_from_slice(bytes), - ) - } -} - -pub struct ViewWriter<'a> { - layer: &'a mut ChunkLayer, - pos: ChunkPos, -} - -impl WritePacket for ViewWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer - .messages - .send_local(LocalMsg::PacketAt { pos: self.pos }, |b| { - PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet) - }) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer - .messages - .send_local_infallible(LocalMsg::PacketAt { pos: self.pos }, |b| { - b.extend_from_slice(bytes) - }); - } -} - -pub struct ViewExceptWriter<'a> { - layer: &'a mut ChunkLayer, - pos: ChunkPos, - except: Entity, -} - -impl WritePacket for ViewExceptWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_local( - LocalMsg::PacketAtExcept { - pos: self.pos, - except: self.except, - }, - |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_local_infallible( - LocalMsg::PacketAtExcept { - pos: self.pos, - except: self.except, - }, - |b| b.extend_from_slice(bytes), - ); - } -} - -pub struct RadiusWriter<'a> { - layer: &'a mut ChunkLayer, - center: BlockPos, - radius: u32, -} - -impl WritePacket for RadiusWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_local( - LocalMsg::RadiusAt { - center: self.center, - radius_squared: self.radius, - }, - |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_local_infallible( - LocalMsg::RadiusAt { - center: self.center, - radius_squared: self.radius, - }, - |b| b.extend_from_slice(bytes), - ); - } -} - -pub struct RadiusExceptWriter<'a> { - layer: &'a mut ChunkLayer, - center: BlockPos, - radius: u32, - except: Entity, -} - -impl WritePacket for RadiusExceptWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_local( - LocalMsg::RadiusAtExcept { - center: self.center, - radius_squared: self.radius, - except: self.except, - }, - |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_local_infallible( - LocalMsg::RadiusAtExcept { - center: self.center, - radius_squared: self.radius, - except: self.except, - }, - |b| b.extend_from_slice(bytes), - ); - } -} - -#[derive(Debug)] -pub enum ChunkEntry<'a> { - Occupied(OccupiedChunkEntry<'a>), - Vacant(VacantChunkEntry<'a>), -} - -impl<'a> ChunkEntry<'a> { - pub fn or_default(self) -> &'a mut LoadedChunk { - match self { - ChunkEntry::Occupied(oe) => oe.into_mut(), - ChunkEntry::Vacant(ve) => ve.insert(Chunk::new()), - } - } -} - -#[derive(Debug)] -pub struct OccupiedChunkEntry<'a> { - messages: &'a mut ChunkLayerMessages, - entry: OccupiedEntry<'a, ChunkPos, LoadedChunk>, -} - -impl<'a> OccupiedChunkEntry<'a> { - pub fn get(&self) -> &LoadedChunk { - self.entry.get() - } - - pub fn get_mut(&mut self) -> &mut LoadedChunk { - self.entry.get_mut() - } - - pub fn insert(&mut self, chunk: Chunk) -> Chunk { - self.messages.send_local_infallible( - LocalMsg::ChangeChunkState { - pos: *self.entry.key(), - }, - |b| b.push(ChunkLayer::OVERWRITE), - ); - - self.entry.get_mut().replace(chunk) - } - - pub fn into_mut(self) -> &'a mut LoadedChunk { - self.entry.into_mut() - } - - pub fn key(&self) -> &ChunkPos { - self.entry.key() - } - - pub fn remove(self) -> Chunk { - self.remove_entry().1 - } - - pub fn remove_entry(self) -> (ChunkPos, Chunk) { - let (pos, chunk) = self.entry.remove_entry(); - - self.messages - .send_local_infallible(LocalMsg::ChangeChunkState { pos }, |b| { - b.push(ChunkLayer::UNLOAD) - }); - - (pos, chunk.into_chunk()) - } -} - -#[derive(Debug)] -pub struct VacantChunkEntry<'a> { - height: u32, - messages: &'a mut ChunkLayerMessages, - entry: VacantEntry<'a, ChunkPos, LoadedChunk>, -} - -impl<'a> VacantChunkEntry<'a> { - pub fn insert(self, chunk: Chunk) -> &'a mut LoadedChunk { - let mut loaded = LoadedChunk::new(self.height); - loaded.replace(chunk); - - self.messages.send_local_infallible( - LocalMsg::ChangeChunkState { - pos: *self.entry.key(), - }, - |b| b.push(ChunkLayer::LOAD), - ); - - self.entry.insert(loaded) - } - - pub fn into_key(self) -> ChunkPos { - *self.entry.key() - } - - pub fn key(&self) -> &ChunkPos { - self.entry.key() - } -} - -/// Represents a complete block, which is a pair of block state and optional NBT -/// data for the block entity. -#[derive(Clone, PartialEq, Default, Debug)] -pub struct Block { - pub state: BlockState, - pub nbt: Option, -} - -impl Block { - pub const fn new(state: BlockState, nbt: Option) -> Self { - Self { state, nbt } - } -} - -impl From for Block { - fn from(state: BlockState) -> Self { - Self { state, nbt: None } - } -} - -/// Like [`Block`] but immutably referenced. -#[derive(Copy, Clone, PartialEq, Default, Debug)] -pub struct BlockRef<'a> { - pub state: BlockState, - pub nbt: Option<&'a Compound>, -} - -impl<'a> BlockRef<'a> { - pub const fn new(state: BlockState, nbt: Option<&'a Compound>) -> Self { - Self { state, nbt } - } -} - -impl From> for Block { - fn from(value: BlockRef<'_>) -> Self { - Self { - state: value.state, - nbt: value.nbt.cloned(), - } - } -} - -impl From for BlockRef<'_> { - fn from(state: BlockState) -> Self { - Self { state, nbt: None } - } -} - -impl<'a> From<&'a Block> for BlockRef<'a> { - fn from(value: &'a Block) -> Self { - Self { - state: value.state, - nbt: value.nbt.as_ref(), - } - } -} - -pub(super) fn build(app: &mut App) { - app.add_systems( - PostUpdate, - ( - update_chunk_layers_pre_client.in_set(UpdateLayersPreClientSet), - update_chunk_layers_post_client.in_set(UpdateLayersPostClientSet), - ), - ); -} - -fn update_chunk_layers_pre_client(mut layers: Query<&mut ChunkLayer>) { - for mut layer in &mut layers { - layer.messages.ready(); - } -} - -fn update_chunk_layers_post_client(mut layers: Query<&mut ChunkLayer>) { - for mut layer in &mut layers { - layer.messages.unready(); - } -} diff --git a/crates/valence_server/src/layer/chunk/loaded.rs b/crates/valence_server/src/layer/chunk/loaded.rs deleted file mode 100644 index 705b5e742..000000000 --- a/crates/valence_server/src/layer/chunk/loaded.rs +++ /dev/null @@ -1,330 +0,0 @@ -use std::borrow::Cow; -use std::mem; -use std::sync::atomic::{AtomicU32, Ordering}; - -use parking_lot::Mutex; // Using nonstandard mutex to avoid poisoning API. -use valence_nbt::{compound, Compound}; -use valence_protocol::encode::{PacketWriter, WritePacket}; -use valence_protocol::packets::play::chunk_data_s2c::ChunkDataBlockEntity; -use valence_protocol::packets::play::ChunkDataS2c; -use valence_protocol::{BlockState, ChunkPos, Encode}; -use valence_registry::biome::BiomeId; -use valence_registry::RegistryIdx; - -use super::chunk::{bit_width, ChunkOps}; -use super::unloaded::Chunk; -use super::{ChunkLayerInfo, SECTION_BLOCK_COUNT}; - -/// A chunk that is actively loaded in a [`ChunkLayer`]. This is only accessible -/// behind a reference. -/// -/// Like [`Chunk`], loaded chunks implement the [`ChunkOps`] trait so you can -/// use many of the same methods. -/// -/// **NOTE:** Loaded chunks are a low-level API. Mutations directly to loaded -/// chunks are intentionally not synchronized with clients. Consider using the -/// relevant methods on [`ChunkLayer`] instead. -/// -/// [`ChunkLayer`]: super::ChunkLayer -#[derive(Debug)] -pub struct LoadedChunk { - /// Chunk data for this loaded chunk. - chunk: Chunk, - /// A count of the clients viewing this chunk. Useful for knowing if it's - /// necessary to record changes, since no client would be in view to receive - /// the changes if this were zero. - viewer_count: AtomicU32, - /// Cached bytes of the chunk initialization packet. The cache is considered - /// invalidated if empty. This should be cleared whenever the chunk is - /// modified in an observable way, even if the chunk is not viewed. - cached_init_packets: Mutex>, -} - -impl LoadedChunk { - pub(crate) fn new(height: u32) -> Self { - Self { - viewer_count: AtomicU32::new(0), - chunk: Chunk::with_height(height), - cached_init_packets: Mutex::new(vec![]), - } - } - - /// Sets the content of this chunk to the supplied [`UnloadedChunk`]. The - /// given unloaded chunk is [resized] to match the height of this loaded - /// chunk prior to insertion. - /// - /// The previous chunk data is returned. - /// - /// [resized]: UnloadedChunk::set_height - pub fn replace(&mut self, mut chunk: Chunk) -> Chunk { - chunk.set_height(self.height()); - - self.cached_init_packets.get_mut().clear(); - - mem::replace(&mut self.chunk, chunk) - } - - pub(super) fn into_chunk(self) -> Chunk { - self.chunk - } - - /// Clones this chunk's data into the returned [`Chunk`]. - pub fn to_chunk(&self) -> Chunk { - self.chunk.clone() - } - - /// Returns the number of clients in view of this chunk. - pub fn viewer_count(&self) -> u32 { - self.viewer_count.load(Ordering::Relaxed) - } - - /// Like [`Self::viewer_count`], but avoids an atomic operation. - pub fn viewer_count_mut(&mut self) -> u32 { - *self.viewer_count.get_mut() - } - - /// Increments the viewer count. - pub(crate) fn inc_viewer_count(&self) { - self.viewer_count.fetch_add(1, Ordering::Relaxed); - } - - /// Decrements the viewer count. - #[track_caller] - pub(crate) fn dec_viewer_count(&self) { - let old = self.viewer_count.fetch_sub(1, Ordering::Relaxed); - debug_assert_ne!(old, 0, "viewer count underflow!"); - } - - /// Writes the packet data needed to initialize this chunk. - pub(crate) fn write_init_packets( - &self, - mut writer: impl WritePacket, - pos: ChunkPos, - info: &ChunkLayerInfo, - ) { - let mut init_packets = self.cached_init_packets.lock(); - - if init_packets.is_empty() { - let heightmaps = compound! { - // TODO: MOTION_BLOCKING and WORLD_SURFACE heightmaps. - }; - - let mut blocks_and_biomes: Vec = vec![]; - - for sect in &self.chunk.sections { - sect.count_non_air_blocks() - .encode(&mut blocks_and_biomes) - .unwrap(); - - sect.block_states - .encode_mc_format( - &mut blocks_and_biomes, - |b| b.to_raw().into(), - 4, - 8, - bit_width(BlockState::max_raw().into()), - ) - .expect("paletted container encode should always succeed"); - - sect.biomes - .encode_mc_format( - &mut blocks_and_biomes, - |b| b.to_index() as _, - 0, - 3, - bit_width(info.biome_registry_len - 1), - ) - .expect("paletted container encode should always succeed"); - } - - let block_entities: Vec<_> = self - .chunk - .block_entities - .iter() - .filter_map(|(&idx, nbt)| { - let x = idx % 16; - let z = idx / 16 % 16; - let y = idx / 16 / 16; - - let kind = self.chunk.sections[y as usize / 16] - .block_states - .get(idx as usize % SECTION_BLOCK_COUNT) - .block_entity_kind(); - - kind.map(|kind| ChunkDataBlockEntity { - packed_xz: ((x << 4) | z) as i8, - y: y as i16 + info.min_y as i16, - kind, - data: Cow::Borrowed(nbt), - }) - }) - .collect(); - - PacketWriter::new(&mut init_packets, info.threshold).write_packet(&ChunkDataS2c { - pos, - heightmaps: Cow::Owned(heightmaps), - blocks_and_biomes: &blocks_and_biomes, - block_entities: Cow::Owned(block_entities), - sky_light_mask: Cow::Borrowed(&[]), - block_light_mask: Cow::Borrowed(&[]), - empty_sky_light_mask: Cow::Borrowed(&[]), - empty_block_light_mask: Cow::Borrowed(&[]), - sky_light_arrays: Cow::Borrowed(&[]), - block_light_arrays: Cow::Borrowed(&[]), - }) - } - - writer.write_packet_bytes(&init_packets); - } -} - -impl ChunkOps for LoadedChunk { - fn height(&self) -> u32 { - self.chunk.height() - } - - fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState { - self.chunk.block_state(x, y, z) - } - - fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState { - let old_block = self.chunk.set_block_state(x, y, z, block); - - if block != old_block { - self.cached_init_packets.get_mut().clear(); - } - - old_block - } - - fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) { - self.chunk.fill_block_state_section(sect_y, block); - - // TODO: do some checks to avoid calling this sometimes. - self.cached_init_packets.get_mut().clear(); - } - - fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> { - self.chunk.block_entity(x, y, z) - } - - fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> { - let res = self.chunk.block_entity_mut(x, y, z); - - if res.is_some() { - self.cached_init_packets.get_mut().clear(); - } - - res - } - - fn set_block_entity( - &mut self, - x: u32, - y: u32, - z: u32, - block_entity: Option, - ) -> Option { - self.cached_init_packets.get_mut().clear(); - - self.chunk.set_block_entity(x, y, z, block_entity) - } - - fn clear_block_entities(&mut self) { - if self.chunk.block_entities.is_empty() { - return; - } - - self.chunk.clear_block_entities(); - - self.cached_init_packets.get_mut().clear(); - } - - fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId { - self.chunk.biome(x, y, z) - } - - fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId { - let old_biome = self.chunk.set_biome(x, y, z, biome); - - if biome != old_biome { - self.cached_init_packets.get_mut().clear(); - } - - old_biome - } - - fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) { - self.chunk.fill_biome_section(sect_y, biome); - - self.cached_init_packets.get_mut().clear(); - } - - fn shrink_to_fit(&mut self) { - self.cached_init_packets.get_mut().shrink_to_fit(); - self.chunk.shrink_to_fit(); - } -} - -#[cfg(test)] -mod tests { - use valence_protocol::{ident, CompressionThreshold}; - - use super::*; - - #[test] - fn loaded_chunk_changes_clear_packet_cache() { - #[track_caller] - fn check(chunk: &mut LoadedChunk, change: impl FnOnce(&mut LoadedChunk) -> T) { - let info = ChunkLayerInfo { - dimension_type_name: ident!("whatever").into(), - height: 512, - min_y: -16, - biome_registry_len: 200, - threshold: CompressionThreshold(-1), - }; - - let mut buf = vec![]; - let mut writer = PacketWriter::new(&mut buf, CompressionThreshold(-1)); - - // Rebuild cache. - chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info); - - // Check that the cache is built. - assert!(!chunk.cached_init_packets.get_mut().is_empty()); - - // Making a change should clear the cache. - change(chunk); - assert!(chunk.cached_init_packets.get_mut().is_empty()); - - // Rebuild cache again. - chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info); - assert!(!chunk.cached_init_packets.get_mut().is_empty()); - } - - let mut chunk = LoadedChunk::new(512); - - check(&mut chunk, |c| { - c.set_block_state(0, 4, 0, BlockState::ACACIA_WOOD) - }); - check(&mut chunk, |c| c.set_biome(1, 2, 3, BiomeId::from_index(4))); - check(&mut chunk, |c| c.fill_biomes(BiomeId::DEFAULT)); - check(&mut chunk, |c| c.fill_block_states(BlockState::WET_SPONGE)); - check(&mut chunk, |c| { - c.set_block_entity(3, 40, 5, Some(compound! {})) - }); - check(&mut chunk, |c| { - c.block_entity_mut(3, 40, 5).unwrap(); - }); - check(&mut chunk, |c| c.set_block_entity(3, 40, 5, None)); - - // Old block state is the same as new block state, so the cache should still be - // intact. - assert_eq!( - chunk.set_block_state(0, 0, 0, BlockState::WET_SPONGE), - BlockState::WET_SPONGE - ); - - assert!(!chunk.cached_init_packets.get_mut().is_empty()); - } -} diff --git a/crates/valence_server/src/layer_old.rs b/crates/valence_server/src/layer_old.rs deleted file mode 100644 index 9a5d2fcbe..000000000 --- a/crates/valence_server/src/layer_old.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! Defines chunk layers and entity layers. Chunk layers contain the chunks and -//! dimension data of a world, while entity layers contain all the Minecraft -//! entities. -//! -//! These two together are analogous to Minecraft "levels" or "worlds". - -pub mod bvh; -pub mod chunk; -pub mod entity; -pub mod message; - -use bevy_app::prelude::*; -use bevy_ecs::prelude::*; -pub use chunk::ChunkLayer; -pub use entity::EntityLayer; -use valence_entity::{InitEntitiesSet, UpdateTrackedDataSet}; -use valence_protocol::encode::WritePacket; -use valence_protocol::{BlockPos, ChunkPos, Ident}; -use valence_registry::{BiomeRegistry, DimensionTypeRegistry}; -use valence_server_common::Server; - -pub struct LayerPlugin; - -/// When entity and chunk changes are written to layers. Systems that modify -/// chunks and entities should run _before_ this. Systems that need to read -/// layer messages should run _after_ this. -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct UpdateLayersPreClientSet; - -/// When layers are cleared and messages from this tick are lost. Systems that -/// read layer messages should run _before_ this. -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct UpdateLayersPostClientSet; - -impl Plugin for LayerPlugin { - fn build(&self, app: &mut App) { - app.configure_sets( - PostUpdate, - ( - UpdateLayersPreClientSet - .after(InitEntitiesSet) - .after(UpdateTrackedDataSet), - UpdateLayersPostClientSet.after(UpdateLayersPreClientSet), - ), - ); - - chunk::build(app); - entity::build(app); - } -} - -/// Common functionality for layers. Notable implementors are [`ChunkLayer`] and -/// [`EntityLayer`]. -/// -/// Layers support sending packets to viewers of the layer under various -/// conditions. These are the "packet writers" exposed by this trait. -/// -/// Layers themselves implement the [`WritePacket`] trait. Writing directly to a -/// layer will send packets to all viewers unconditionally. -pub trait Layer: WritePacket { - /// Packet writer returned by [`except_writer`](Self::except_writer). - type ExceptWriter<'a>: WritePacket - where - Self: 'a; - - /// Packet writer returned by [`view_writer`](Self::ViewWriter). - type ViewWriter<'a>: WritePacket - where - Self: 'a; - - /// Packet writer returned by - /// [`view_except_writer`](Self::ViewExceptWriter). - type ViewExceptWriter<'a>: WritePacket - where - Self: 'a; - - /// Packet writer returned by [`radius_writer`](Self::radius_writer). - type RadiusWriter<'a>: WritePacket - where - Self: 'a; - - /// Packet writer returned by - /// [`radius_except_writer`](Self::radius_except_writer). - type RadiusExceptWriter<'a>: WritePacket - where - Self: 'a; - - /// Returns a packet writer which sends packets to all viewers not - /// identified by `except`. - fn except_writer(&mut self, except: Entity) -> Self::ExceptWriter<'_>; - - /// Returns a packet writer which sends packets to viewers in view of - /// the chunk position `pos`. - fn view_writer(&mut self, pos: impl Into) -> Self::ViewWriter<'_>; - - /// Returns a packet writer which sends packets to viewers in - /// view of the chunk position `pos` and not identified by `except`. - fn view_except_writer( - &mut self, - pos: impl Into, - except: Entity, - ) -> Self::ViewExceptWriter<'_>; - - /// Returns a packet writer which sends packets to viewers within `radius` - /// blocks of the block position `pos`. - fn radius_writer(&mut self, pos: impl Into, radius: u32) -> Self::RadiusWriter<'_>; - - /// Returns a packet writer which sends packets to viewers within `radius` - /// blocks of the block position `pos` and not identified by `except`. - fn radius_except_writer( - &mut self, - pos: impl Into, - radius: u32, - except: Entity, - ) -> Self::RadiusExceptWriter<'_>; -} - -/// Convenience [`Bundle`] for spawning a layer entity with both [`ChunkLayer`] -/// and [`EntityLayer`] components. -#[derive(Bundle)] -pub struct LayerBundle { - pub chunk: ChunkLayer, - pub entity: EntityLayer, -} - -impl LayerBundle { - /// Returns a new layer bundle. - pub fn new( - dimension_type_name: impl Into>, - dimensions: &DimensionTypeRegistry, - biomes: &BiomeRegistry, - server: &Server, - ) -> Self { - Self { - chunk: ChunkLayer::new(dimension_type_name, dimensions, biomes, server), - entity: EntityLayer::new(server), - } - } -} diff --git a/crates/valence_server/src/layer_old/bvh.rs b/crates/valence_server/src/layer_old/bvh.rs deleted file mode 100644 index 4f63cd01b..000000000 --- a/crates/valence_server/src/layer_old/bvh.rs +++ /dev/null @@ -1,339 +0,0 @@ -use std::mem; -use std::ops::Range; - -use valence_protocol::ChunkPos; - -use crate::ChunkView; - -/// A bounding volume hierarchy for chunk positions. -#[derive(Clone, Debug)] -pub struct ChunkBvh { - nodes: Vec, - values: Vec, -} - -impl Default for ChunkBvh { - fn default() -> Self { - Self::new() - } -} - -#[derive(Clone, Debug)] -enum Node { - Internal { - bounds: Aabb, - left: NodeIdx, - right: NodeIdx, - }, - Leaf { - bounds: Aabb, - /// Range of values in the values array. - values: Range, - }, -} - -#[cfg(test)] -impl Node { - fn bounds(&self) -> Aabb { - match self { - Node::Internal { bounds, .. } => *bounds, - Node::Leaf { bounds, .. } => *bounds, - } - } -} - -type NodeIdx = u32; - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -struct Aabb { - min: ChunkPos, - max: ChunkPos, -} - -impl Aabb { - fn point(pos: ChunkPos) -> Self { - Self { min: pos, max: pos } - } - - /// Sum of side lengths. - fn surface_area(self) -> i32 { - (self.length_x() + self.length_z()) * 2 - } - - /// Returns the smallest AABB containing `self` and `other`. - fn union(self, other: Self) -> Self { - Self { - min: ChunkPos::new(self.min.x.min(other.min.x), self.min.z.min(other.min.z)), - max: ChunkPos::new(self.max.x.max(other.max.x), self.max.z.max(other.max.z)), - } - } - - fn length_x(self) -> i32 { - self.max.x - self.min.x - } - - fn length_z(self) -> i32 { - self.max.z - self.min.z - } - - fn intersects(self, other: Self) -> bool { - self.min.x <= other.max.x - && self.max.x >= other.min.x - && self.min.z <= other.max.z - && self.max.z >= other.min.z - } -} - -/// Obtains a chunk position for the purpose of placement in the BVH. -pub trait GetChunkPos { - fn chunk_pos(&self) -> ChunkPos; -} - -impl GetChunkPos for ChunkPos { - fn chunk_pos(&self) -> ChunkPos { - *self - } -} - -impl ChunkBvh { - pub fn new() -> Self { - assert!(MAX_SURFACE_AREA > 0); - - Self { - nodes: vec![], - values: vec![], - } - } -} - -impl ChunkBvh { - pub fn build(&mut self, items: impl IntoIterator) { - self.nodes.clear(); - self.values.clear(); - - self.values.extend(items); - - if let Some(bounds) = value_bounds(&self.values) { - self.build_rec(bounds, 0..self.values.len()); - } - } - - fn build_rec(&mut self, bounds: Aabb, value_range: Range) { - if bounds.surface_area() <= MAX_SURFACE_AREA { - self.nodes.push(Node::Leaf { - bounds, - values: value_range.start as u32..value_range.end as u32, - }); - - return; - } - - let values = &mut self.values[value_range.clone()]; - - // Determine splitting axis based on the side that's longer. Then split along - // the spatial midpoint. We could use a more advanced heuristic like SAH, - // but it's probably not worth it. - - let point = if bounds.length_x() >= bounds.length_z() { - // Split on Z axis. - - let mid = middle(bounds.min.x, bounds.max.x); - partition(values, |v| v.chunk_pos().x >= mid) - } else { - // Split on X axis. - - let mid = middle(bounds.min.z, bounds.max.z); - partition(values, |v| v.chunk_pos().z >= mid) - }; - - let left_range = value_range.start..value_range.start + point; - let right_range = left_range.end..value_range.end; - - let left_bounds = - value_bounds(&self.values[left_range.clone()]).expect("left half should be nonempty"); - - let right_bounds = - value_bounds(&self.values[right_range.clone()]).expect("right half should be nonempty"); - - self.build_rec(left_bounds, left_range); - let left_idx = (self.nodes.len() - 1) as NodeIdx; - - self.build_rec(right_bounds, right_range); - let right_idx = (self.nodes.len() - 1) as NodeIdx; - - self.nodes.push(Node::Internal { - bounds, - left: left_idx, - right: right_idx, - }); - } - - pub fn query(&self, view: ChunkView, mut f: impl FnMut(&T)) { - if let Some(root) = self.nodes.last() { - let (min, max) = view.bounding_box(); - self.query_rec(root, view, Aabb { min, max }, &mut f); - } - } - - fn query_rec(&self, node: &Node, view: ChunkView, view_aabb: Aabb, f: &mut impl FnMut(&T)) { - match node { - Node::Internal { - bounds, - left, - right, - } => { - if bounds.intersects(view_aabb) { - self.query_rec(&self.nodes[*left as usize], view, view_aabb, f); - self.query_rec(&self.nodes[*right as usize], view, view_aabb, f); - } - } - Node::Leaf { bounds, values } => { - if bounds.intersects(view_aabb) { - for val in &self.values[values.start as usize..values.end as usize] { - if view.contains(val.chunk_pos()) { - f(val) - } - } - } - } - } - } - - pub fn shrink_to_fit(&mut self) { - self.nodes.shrink_to_fit(); - self.values.shrink_to_fit(); - } - - #[cfg(test)] - fn check_invariants(&self) { - if let Some(root) = self.nodes.last() { - self.check_invariants_rec(root); - } - } - - #[cfg(test)] - fn check_invariants_rec(&self, node: &Node) { - match node { - Node::Internal { - bounds, - left, - right, - } => { - let left = &self.nodes[*left as usize]; - let right = &self.nodes[*right as usize]; - - assert_eq!(left.bounds().union(right.bounds()), *bounds); - - self.check_invariants_rec(left); - self.check_invariants_rec(right); - } - Node::Leaf { - bounds: leaf_bounds, - values, - } => { - let bounds = value_bounds(&self.values[values.start as usize..values.end as usize]) - .expect("leaf should be nonempty"); - - assert_eq!(*leaf_bounds, bounds); - } - } - } -} - -fn value_bounds(values: &[T]) -> Option { - values - .iter() - .map(|v| Aabb::point(v.chunk_pos())) - .reduce(Aabb::union) -} - -fn middle(min: i32, max: i32) -> i32 { - // Cast to i64 to avoid intermediate overflow. - ((min as i64 + max as i64) / 2) as i32 -} - -/// Partitions the slice in place and returns the partition point. Why this -/// isn't in Rust's stdlib I don't know. -fn partition(s: &mut [T], mut pred: impl FnMut(&T) -> bool) -> usize { - let mut it = s.iter_mut(); - let mut true_count = 0; - - while let Some(head) = it.find(|x| { - if pred(x) { - true_count += 1; - false - } else { - true - } - }) { - if let Some(tail) = it.rfind(|x| pred(x)) { - mem::swap(head, tail); - true_count += 1; - } else { - break; - } - } - true_count -} - -#[cfg(test)] -mod tests { - use rand::Rng; - - use super::*; - - #[test] - fn partition_middle() { - let mut arr = [2, 3, 4, 5]; - let mid = middle(arr[0], arr[arr.len() - 1]); - - let point = partition(&mut arr, |&x| mid >= x); - - assert_eq!(point, 2); - assert_eq!(&arr[..point], &[2, 3]); - assert_eq!(&arr[point..], &[4, 5]); - } - - #[test] - fn query_visits_correct_nodes() { - let mut bvh = ChunkBvh::::new(); - - let mut positions = vec![]; - - let size = 500; - let mut rng = rand::thread_rng(); - - // Create a bunch of positions in a large area. - for _ in 0..100_000 { - positions.push(ChunkPos { - x: rng.gen_range(-size / 2..size / 2), - z: rng.gen_range(-size / 2..size / 2), - }); - } - - // Put the view in the center of that area. - let view = ChunkView::new(ChunkPos::default(), 32); - - let mut viewed_positions = vec![]; - - // Create a list of positions the view contains. - for &pos in &positions { - if view.contains(pos) { - viewed_positions.push(pos); - } - } - - bvh.build(positions); - - bvh.check_invariants(); - - // Check that we query exactly the positions that we know the view can see. - - bvh.query(view, |pos| { - let idx = viewed_positions.iter().position(|p| p == pos).expect("😔"); - viewed_positions.remove(idx); - }); - - assert!(viewed_positions.is_empty()); - } -} diff --git a/crates/valence_server/src/layer_old/chunk.rs b/crates/valence_server/src/layer_old/chunk.rs deleted file mode 100644 index 1c183ba24..000000000 --- a/crates/valence_server/src/layer_old/chunk.rs +++ /dev/null @@ -1,726 +0,0 @@ -pub mod batch; -mod biome; -mod block_update; -#[allow(clippy::module_inception)] -mod chunk; -mod loaded; -mod paletted_container; -mod unloaded; - -use std::borrow::Cow; -use std::collections::hash_map::{Entry, OccupiedEntry, VacantEntry}; - -use bevy_app::prelude::*; -use bevy_ecs::prelude::*; -pub use biome::*; -pub use chunk::*; -pub use loaded::LoadedChunk; -use rustc_hash::FxHashMap; -pub use unloaded::Chunk; -use valence_math::{DVec3, Vec3}; -use valence_nbt::Compound; -use valence_protocol::encode::{PacketWriter, WritePacket}; -use valence_protocol::packets::play::particle_s2c::Particle; -use valence_protocol::packets::play::{ParticleS2c, PlaySoundS2c}; -use valence_protocol::sound::{Sound, SoundCategory, SoundId}; -use valence_protocol::{ - BlockPos, BlockState, ChunkPos, CompressionThreshold, Encode, Ident, Packet, -}; -use valence_registry::biome::BiomeRegistry; -use valence_registry::DimensionTypeRegistry; -use valence_server_common::Server; - -use self::block_update::BlockUpdates; -use super::bvh::GetChunkPos; -use super::message::Messages; -use super::{Layer, UpdateLayersPostClientSet, UpdateLayersPreClientSet}; - -/// A [`Component`] containing the [chunks](LoadedChunk) and [dimension -/// information](valence_registry::dimension_type::DimensionTypeId) of a -/// Minecraft world. -#[derive(Component, Debug)] -pub struct ChunkLayer { - messages: ChunkLayerMessages, - chunks: FxHashMap, - info: ChunkLayerInfo, - block_updates: BlockUpdates, -} - -/// Chunk layer information. -#[derive(Debug)] -pub(crate) struct ChunkLayerInfo { - dimension_type_name: Ident, - height: u32, - min_y: i32, - biome_registry_len: usize, - threshold: CompressionThreshold, -} - -type ChunkLayerMessages = Messages; - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub(crate) enum GlobalMsg { - /// Send packet data to all clients viewing the layer. - Packet, - /// Send packet data to all clients viewing the layer, except the client - /// identified by `except`. - PacketExcept { except: Entity }, -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub(crate) enum LocalMsg { - /// Send packet data to all clients viewing the layer in view of `pos`. - PacketAt { - pos: ChunkPos, - }, - PacketAtExcept { - pos: ChunkPos, - except: Entity, - }, - RadiusAt { - center: BlockPos, - radius_squared: u32, - }, - RadiusAtExcept { - center: BlockPos, - radius_squared: u32, - except: Entity, - }, - /// Instruct clients to load or unload the chunk at `pos`. Loading and - /// unloading are combined into a single message so that load/unload order - /// is not lost when messages are sorted. - /// - /// Message content is a single byte indicating load (1) or unload (0). - ChangeChunkState { - pos: ChunkPos, - }, - /// Message content is the data for a single biome in the "change biomes" - /// packet. - ChangeBiome { - pos: ChunkPos, - }, -} - -impl GetChunkPos for LocalMsg { - fn chunk_pos(&self) -> ChunkPos { - match *self { - LocalMsg::PacketAt { pos } => pos, - LocalMsg::PacketAtExcept { pos, .. } => pos, - LocalMsg::RadiusAt { center, .. } => center.into(), - LocalMsg::RadiusAtExcept { center, .. } => center.into(), - LocalMsg::ChangeBiome { pos } => pos, - LocalMsg::ChangeChunkState { pos } => pos, - } - } -} - -impl ChunkLayer { - pub(crate) const LOAD: u8 = 0; - pub(crate) const UNLOAD: u8 = 1; - pub(crate) const OVERWRITE: u8 = 2; - - /// Creates a new chunk layer. - #[track_caller] - pub fn new( - dimension_type_name: impl Into>, - dimensions: &DimensionTypeRegistry, - biomes: &BiomeRegistry, - server: &Server, - ) -> Self { - let dimension_type_name = dimension_type_name.into(); - - let dim = &dimensions[dimension_type_name.as_str_ident()]; - - assert!( - (0..MAX_HEIGHT as i32).contains(&dim.height), - "invalid dimension height of {}", - dim.height - ); - - Self { - messages: Messages::new(), - chunks: Default::default(), - info: ChunkLayerInfo { - dimension_type_name, - height: dim.height as u32, - min_y: dim.min_y, - biome_registry_len: biomes.iter().len(), - threshold: server.compression_threshold(), - }, - } - } - - /// The name of the dimension this chunk layer is using. - pub fn dimension_type_name(&self) -> Ident<&str> { - self.info.dimension_type_name.as_str_ident() - } - - /// The height of this instance's dimension. - pub fn height(&self) -> u32 { - self.info.height - } - - /// The `min_y` of this instance's dimension. - pub fn min_y(&self) -> i32 { - self.info.min_y - } - - /// Get a reference to the chunk at the given position, if it is loaded. - pub fn chunk(&self, pos: impl Into) -> Option<&LoadedChunk> { - self.chunks.get(&pos.into()) - } - - /// Get a mutable reference to the chunk at the given position, if it is - /// loaded. - pub fn chunk_mut(&mut self, pos: impl Into) -> Option<&mut LoadedChunk> { - self.chunks.get_mut(&pos.into()) - } - - /// Insert a chunk into the instance at the given position. The previous - /// chunk data is returned. - pub fn insert_chunk(&mut self, pos: impl Into, chunk: Chunk) -> Option { - match self.chunk_entry(pos) { - ChunkEntry::Occupied(mut oe) => Some(oe.insert(chunk)), - ChunkEntry::Vacant(ve) => { - ve.insert(chunk); - None - } - } - } - - /// Unload the chunk at the given position, if it is loaded. Returns the - /// chunk if it was loaded. - pub fn remove_chunk(&mut self, pos: impl Into) -> Option { - match self.chunk_entry(pos) { - ChunkEntry::Occupied(oe) => Some(oe.remove()), - ChunkEntry::Vacant(_) => None, - } - } - - /// Unload all chunks in this instance. - pub fn clear_chunks(&mut self) { - self.retain_chunks(|_, _| false) - } - - /// Retain only the chunks for which the given predicate returns `true`. - pub fn retain_chunks(&mut self, mut f: F) - where - F: FnMut(ChunkPos, &mut LoadedChunk) -> bool, - { - self.chunks.retain(|pos, chunk| { - if !f(*pos, chunk) { - self.messages - .send_local_infallible(LocalMsg::ChangeChunkState { pos: *pos }, |b| { - b.push(Self::UNLOAD) - }); - - false - } else { - true - } - }); - } - - /// Get a [`ChunkEntry`] for the given position. - pub fn chunk_entry(&mut self, pos: impl Into) -> ChunkEntry { - match self.chunks.entry(pos.into()) { - Entry::Occupied(oe) => ChunkEntry::Occupied(OccupiedChunkEntry { - messages: &mut self.messages, - entry: oe, - }), - Entry::Vacant(ve) => ChunkEntry::Vacant(VacantChunkEntry { - height: self.info.height, - messages: &mut self.messages, - entry: ve, - }), - } - } - - /// Get an iterator over all loaded chunks in the instance. The order of the - /// chunks is undefined. - pub fn chunks(&self) -> impl Iterator + Clone + '_ { - self.chunks.iter().map(|(pos, chunk)| (*pos, chunk)) - } - - /// Get an iterator over all loaded chunks in the instance, mutably. The - /// order of the chunks is undefined. - pub fn chunks_mut(&mut self) -> impl Iterator + '_ { - self.chunks.iter_mut().map(|(pos, chunk)| (*pos, chunk)) - } - - /// Optimizes the memory usage of the instance. - pub fn shrink_to_fit(&mut self) { - for (_, chunk) in self.chunks_mut() { - chunk.shrink_to_fit(); - } - - self.chunks.shrink_to_fit(); - self.messages.shrink_to_fit(); - self.block_updates.shrink_to_fit(); - } - - pub(crate) fn info(&self) -> &ChunkLayerInfo { - &self.info - } - - pub(crate) fn messages(&self) -> &ChunkLayerMessages { - &self.messages - } - - // TODO: move to `valence_particle`. - /// Puts a particle effect at the given position in the world. The particle - /// effect is visible to all players in the instance with the - /// appropriate chunk in view. - pub fn play_particle( - &mut self, - particle: &Particle, - long_distance: bool, - position: impl Into, - offset: impl Into, - max_speed: f32, - count: i32, - ) { - let position = position.into(); - - self.view_writer(position).write_packet(&ParticleS2c { - particle: Cow::Borrowed(particle), - long_distance, - position, - offset: offset.into(), - max_speed, - count, - }); - } - - // TODO: move to `valence_sound`. - /// Plays a sound effect at the given position in the world. The sound - /// effect is audible to all players in the instance with the - /// appropriate chunk in view. - pub fn play_sound( - &mut self, - sound: Sound, - category: SoundCategory, - position: impl Into, - volume: f32, - pitch: f32, - ) { - let position = position.into(); - - self.view_writer(position).write_packet(&PlaySoundS2c { - id: SoundId::Direct { - id: sound.to_ident().into(), - range: None, - }, - category, - position: (position * 8.0).as_ivec3(), - volume, - pitch, - seed: rand::random(), - }); - } -} - -impl Layer for ChunkLayer { - type ExceptWriter<'a> = ExceptWriter<'a>; - - type ViewWriter<'a> = ViewWriter<'a>; - - type ViewExceptWriter<'a> = ViewExceptWriter<'a>; - - type RadiusWriter<'a> = RadiusWriter<'a>; - - type RadiusExceptWriter<'a> = RadiusExceptWriter<'a>; - - fn except_writer(&mut self, except: Entity) -> Self::ExceptWriter<'_> { - ExceptWriter { - layer: self, - except, - } - } - - fn view_writer(&mut self, pos: impl Into) -> Self::ViewWriter<'_> { - ViewWriter { - layer: self, - pos: pos.into(), - } - } - - fn view_except_writer( - &mut self, - pos: impl Into, - except: Entity, - ) -> Self::ViewExceptWriter<'_> { - ViewExceptWriter { - layer: self, - pos: pos.into(), - except, - } - } - - fn radius_writer( - &mut self, - center: impl Into, - radius: u32, - ) -> Self::RadiusWriter<'_> { - RadiusWriter { - layer: self, - center: center.into(), - radius, - } - } - - fn radius_except_writer( - &mut self, - center: impl Into, - radius: u32, - except: Entity, - ) -> Self::RadiusExceptWriter<'_> { - RadiusExceptWriter { - layer: self, - center: center.into(), - radius, - except, - } - } -} - -impl WritePacket for ChunkLayer { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.messages.send_global(GlobalMsg::Packet, |b| { - PacketWriter::new(b, self.info.threshold).write_packet_fallible(packet) - }) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.messages - .send_global_infallible(GlobalMsg::Packet, |b| b.extend_from_slice(bytes)); - } -} - -pub struct ExceptWriter<'a> { - layer: &'a mut ChunkLayer, - except: Entity, -} - -impl WritePacket for ExceptWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_global( - GlobalMsg::PacketExcept { - except: self.except, - }, - |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_global_infallible( - GlobalMsg::PacketExcept { - except: self.except, - }, - |b| b.extend_from_slice(bytes), - ) - } -} - -pub struct ViewWriter<'a> { - layer: &'a mut ChunkLayer, - pos: ChunkPos, -} - -impl WritePacket for ViewWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer - .messages - .send_local(LocalMsg::PacketAt { pos: self.pos }, |b| { - PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet) - }) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer - .messages - .send_local_infallible(LocalMsg::PacketAt { pos: self.pos }, |b| { - b.extend_from_slice(bytes) - }); - } -} - -pub struct ViewExceptWriter<'a> { - layer: &'a mut ChunkLayer, - pos: ChunkPos, - except: Entity, -} - -impl WritePacket for ViewExceptWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_local( - LocalMsg::PacketAtExcept { - pos: self.pos, - except: self.except, - }, - |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_local_infallible( - LocalMsg::PacketAtExcept { - pos: self.pos, - except: self.except, - }, - |b| b.extend_from_slice(bytes), - ); - } -} - -pub struct RadiusWriter<'a> { - layer: &'a mut ChunkLayer, - center: BlockPos, - radius: u32, -} - -impl WritePacket for RadiusWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_local( - LocalMsg::RadiusAt { - center: self.center, - radius_squared: self.radius, - }, - |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_local_infallible( - LocalMsg::RadiusAt { - center: self.center, - radius_squared: self.radius, - }, - |b| b.extend_from_slice(bytes), - ); - } -} - -pub struct RadiusExceptWriter<'a> { - layer: &'a mut ChunkLayer, - center: BlockPos, - radius: u32, - except: Entity, -} - -impl WritePacket for RadiusExceptWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_local( - LocalMsg::RadiusAtExcept { - center: self.center, - radius_squared: self.radius, - except: self.except, - }, - |b| PacketWriter::new(b, self.layer.info.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_local_infallible( - LocalMsg::RadiusAtExcept { - center: self.center, - radius_squared: self.radius, - except: self.except, - }, - |b| b.extend_from_slice(bytes), - ); - } -} - -#[derive(Debug)] -pub enum ChunkEntry<'a> { - Occupied(OccupiedChunkEntry<'a>), - Vacant(VacantChunkEntry<'a>), -} - -impl<'a> ChunkEntry<'a> { - pub fn or_default(self) -> &'a mut LoadedChunk { - match self { - ChunkEntry::Occupied(oe) => oe.into_mut(), - ChunkEntry::Vacant(ve) => ve.insert(Chunk::new()), - } - } -} - -#[derive(Debug)] -pub struct OccupiedChunkEntry<'a> { - messages: &'a mut ChunkLayerMessages, - entry: OccupiedEntry<'a, ChunkPos, LoadedChunk>, -} - -impl<'a> OccupiedChunkEntry<'a> { - pub fn get(&self) -> &LoadedChunk { - self.entry.get() - } - - pub fn get_mut(&mut self) -> &mut LoadedChunk { - self.entry.get_mut() - } - - pub fn insert(&mut self, chunk: Chunk) -> Chunk { - self.messages.send_local_infallible( - LocalMsg::ChangeChunkState { - pos: *self.entry.key(), - }, - |b| b.push(ChunkLayer::OVERWRITE), - ); - - self.entry.get_mut().replace(chunk) - } - - pub fn into_mut(self) -> &'a mut LoadedChunk { - self.entry.into_mut() - } - - pub fn key(&self) -> &ChunkPos { - self.entry.key() - } - - pub fn remove(self) -> Chunk { - self.remove_entry().1 - } - - pub fn remove_entry(self) -> (ChunkPos, Chunk) { - let (pos, chunk) = self.entry.remove_entry(); - - self.messages - .send_local_infallible(LocalMsg::ChangeChunkState { pos }, |b| { - b.push(ChunkLayer::UNLOAD) - }); - - (pos, chunk.into_chunk()) - } -} - -#[derive(Debug)] -pub struct VacantChunkEntry<'a> { - height: u32, - messages: &'a mut ChunkLayerMessages, - entry: VacantEntry<'a, ChunkPos, LoadedChunk>, -} - -impl<'a> VacantChunkEntry<'a> { - pub fn insert(self, chunk: Chunk) -> &'a mut LoadedChunk { - let mut loaded = LoadedChunk::new(self.height); - loaded.replace(chunk); - - self.messages.send_local_infallible( - LocalMsg::ChangeChunkState { - pos: *self.entry.key(), - }, - |b| b.push(ChunkLayer::LOAD), - ); - - self.entry.insert(loaded) - } - - pub fn into_key(self) -> ChunkPos { - *self.entry.key() - } - - pub fn key(&self) -> &ChunkPos { - self.entry.key() - } -} - -/// Represents a complete block, which is a pair of block state and optional NBT -/// data for the block entity. -#[derive(Clone, PartialEq, Default, Debug)] -pub struct Block { - pub state: BlockState, - pub nbt: Option, -} - -impl Block { - pub const fn new(state: BlockState, nbt: Option) -> Self { - Self { state, nbt } - } -} - -impl From for Block { - fn from(state: BlockState) -> Self { - Self { state, nbt: None } - } -} - -/// Like [`Block`] but immutably referenced. -#[derive(Copy, Clone, PartialEq, Default, Debug)] -pub struct BlockRef<'a> { - pub state: BlockState, - pub nbt: Option<&'a Compound>, -} - -impl<'a> BlockRef<'a> { - pub const fn new(state: BlockState, nbt: Option<&'a Compound>) -> Self { - Self { state, nbt } - } -} - -impl From> for Block { - fn from(value: BlockRef<'_>) -> Self { - Self { - state: value.state, - nbt: value.nbt.cloned(), - } - } -} - -impl From for BlockRef<'_> { - fn from(state: BlockState) -> Self { - Self { state, nbt: None } - } -} - -impl<'a> From<&'a Block> for BlockRef<'a> { - fn from(value: &'a Block) -> Self { - Self { - state: value.state, - nbt: value.nbt.as_ref(), - } - } -} - -pub(super) fn build(app: &mut App) { - app.add_systems( - PostUpdate, - ( - update_chunk_layers_pre_client.in_set(UpdateLayersPreClientSet), - update_chunk_layers_post_client.in_set(UpdateLayersPostClientSet), - ), - ); -} - -fn update_chunk_layers_pre_client(mut layers: Query<&mut ChunkLayer>) { - for mut layer in &mut layers { - layer.messages.ready(); - } -} - -fn update_chunk_layers_post_client(mut layers: Query<&mut ChunkLayer>) { - for mut layer in &mut layers { - layer.messages.unready(); - } -} diff --git a/crates/valence_server/src/layer_old/chunk/batch.rs b/crates/valence_server/src/layer_old/chunk/batch.rs deleted file mode 100644 index b9ed754e2..000000000 --- a/crates/valence_server/src/layer_old/chunk/batch.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! Handles getting and setting blocks in chunk layers. - -mod basic; - -use std::borrow::Cow; - -use valence_protocol::encode::PacketWriter; -use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; -use valence_protocol::packets::play::{BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDeltaUpdateS2c}; -use valence_protocol::{BlockPos, ChunkPos, ChunkSectionPos, WritePacket}; - -use super::{block_offsets, Block, BlockRef, ChunkOps, LoadedChunk}; -use crate::layer_old::chunk::LocalMsg; -use crate::{BlockState, ChunkLayer, Layer}; - -impl ChunkLayer { - pub fn block(&self, pos: impl Into) -> Option { - let pos = pos.into(); - let chunk_pos = ChunkPos::from(pos); - - let chunk = self.chunk(chunk_pos)?; - let [x, y, z] = block_offsets(pos, self.info.min_y, self.info.height as i32)?; - - Some(chunk.block(x, y, z)) - } - - pub fn set_block( - &mut self, - pos: impl Into, - block: impl Into, - ) -> Option { - let pos = pos.into(); - let chunk_pos = ChunkPos::from(pos); - let block: Block = block.into(); - - let [x, y, z] = block_offsets(pos, self.info.min_y, self.info.height as i32)?; - - let mut writer = self.view_writer(chunk_pos); - - writer.write_packet(&BlockUpdateS2c { - position: pos, - block_id: block.state, - }); - - if let (Some(nbt), Some(kind)) = (&block.nbt, block.state.block_entity_kind()) { - writer.write_packet(&BlockEntityUpdateS2c { - position: pos, - kind, - data: Cow::Borrowed(nbt), - }); - } - - let chunk = self.chunk_mut(chunk_pos)?; - - Some(chunk.set_block(x, y, z, block)) - } - - pub fn apply_batch(&mut self, batch: impl Batch) { - let block_iter = batch.into_batch_iters(); - - let mut chunk: Option<&mut LoadedChunk> = None; - let mut sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); - - for (pos, block) in block_iter { - let new_sect_pos = ChunkSectionPos::from(pos); - - // Is this block in a new section? If it is, then flush the changes we've - // accumulated for the old section. - if sect_pos != new_sect_pos { - let msg = LocalMsg::PacketAt { - pos: sect_pos.into(), - }; - - match self.block_update_buf.as_slice() { - // Zero updates. Do nothing. - &[] => {} - // One update. Send singular block update packet. - &[update] => self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { - position: BlockPos { - x: sect_pos.x * 16 + update.off_x() as i32, - y: sect_pos.y * 16 + update.off_y() as i32, - z: sect_pos.z * 16 + update.off_z() as i32, - }, - block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), - }); - }), - // >1 updates. Send special section update packet. - updates => { - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet( - &ChunkDeltaUpdateS2c { - chunk_section_pos: sect_pos, - blocks: Cow::Borrowed(updates), - }, - ) - }); - } - } - - // Send block entity update. - if let (Some(nbt), Some(kind)) = (&block.nbt, block.state.block_entity_kind()) { - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet( - &BlockEntityUpdateS2c { - position: pos, - kind, - data: Cow::Borrowed(nbt), - }, - ) - }); - } - - self.block_update_buf.clear(); - - // Update the chunk ref if the chunk pos changed. - if sect_pos.x != new_sect_pos.x || sect_pos.z != new_sect_pos.z { - chunk = self.chunks.get_mut(&ChunkPos::from(new_sect_pos)); - } - - // Update section pos. - sect_pos = new_sect_pos; - } - - if let Some(chunk) = &mut chunk { - let chunk_y = pos.y.wrapping_sub(self.info.min_y) as u32; - - // Is the block pos in bounds of the chunk? - if chunk_y < self.info.height { - let chunk_x = pos.x.rem_euclid(16); - let chunk_z = pos.z.rem_euclid(16); - - // Make change to the chunk and push section update. - - if chunk.viewer_count_mut() > 0 { - self.block_update_buf.push( - ChunkDeltaUpdateEntry::new() - .with_off_x(chunk_x as u8) - .with_off_y((chunk_y % 16) as u8) - .with_off_z(chunk_z as u8) - .with_block_state(block.state.to_raw() as u32), - ); - } - - chunk.set_block(chunk_x as u32, chunk_y, chunk_z as u32, block); - } - } - } - - // Flush any remaining block changes. - - let msg = LocalMsg::PacketAt { - pos: sect_pos.into(), - }; - - match self.block_update_buf.as_slice() { - // Zero updates. Do nothing. - &[] => {} - // One update. Send singular block update packet. - &[update] => self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { - position: BlockPos { - x: sect_pos.x * 16 + update.off_x() as i32, - y: sect_pos.y * 16 + update.off_y() as i32, - z: sect_pos.z * 16 + update.off_z() as i32, - }, - block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), - }); - }), - // >1 updates. Send special section update packet. - updates => { - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&ChunkDeltaUpdateS2c { - chunk_section_pos: sect_pos, - blocks: Cow::Borrowed(updates), - }) - }); - } - } - - self.block_update_buf.clear(); - } -} - diff --git a/crates/valence_server/src/layer_old/chunk/batch/basic.rs b/crates/valence_server/src/layer_old/chunk/batch/basic.rs deleted file mode 100644 index d7e2ec668..000000000 --- a/crates/valence_server/src/layer_old/chunk/batch/basic.rs +++ /dev/null @@ -1,243 +0,0 @@ -/*/ -use bevy_ecs::prelude::Component; -use bitfield_struct::bitfield; -use valence_protocol::{BlockPos, BlockState, ChunkPos}; - -use super::{Batch, Block}; - -#[derive(Clone, PartialEq, Default, Debug, Component)] -pub struct BasicBatch { - updates: Vec, - full: Vec, -} - -impl BasicBatch { - pub fn new() -> Self { - Self::default() - } - - pub fn set_block(&mut self, pos: impl Into, block: impl Into) { - let pos = pos.into(); - let block = block.into(); - - if block.nbt.is_none() { - self.updates - .push(BlockUpdate::from_block_state(pos, block.state)) - } else { - let idx = self.full.len() as u32; - self.full.push(block); - self.updates.push(BlockUpdate::from_index(pos, idx)); - } - } - - pub fn clear(&mut self) { - self.updates.clear(); - self.full.clear(); - } - - pub fn reserve(&mut self, additional: usize) { - self.updates.reserve(additional); - } - - pub fn shrink_to_fit(&mut self) { - self.updates.shrink_to_fit(); - self.full.shrink_to_fit(); - } -} - -impl FromIterator<(P, B)> for BasicBatch -where - P: Into, - B: Into, -{ - fn from_iter>(iter: T) -> Self { - let mut res = Self::new(); - - res.extend(iter); - - res - } -} - -impl Extend<(P, B)> for BasicBatch -where - P: Into, - B: Into, -{ - fn extend>(&mut self, iter: T) { - self.updates.extend(iter.into_iter().map(|(p, b)| { - let pos = p.into(); - let block = b.into(); - - if block.nbt.is_none() { - BlockUpdate::from_block_state(pos, block.state) - } else { - let idx = self.full.len() as u32; - self.full.push(block); - BlockUpdate::from_index(pos, idx) - } - })); - } -} - -/// The basic batch is cleared after application so you can reuse the buffer if -/// desired. -impl<'a> Batch for &'a mut BasicBatch { - type BlockIter = BlockIter<'a>; - - fn into_batch_iters(mut self) -> Self::BlockIter { - // Sort in reverse so the dedup keeps the last of consecutive elements. - self.updates.sort_by(|a, b| b.cmp(a)); - - // Eliminate redundant block assignments. - self.updates - .dedup_by_key(|u| u.0 & BlockUpdate::BLOCK_POS_MASK); - - BlockIter { - updates: self.updates.drain(..), - full: &mut self.full, - } - } -} - -pub struct BlockIter<'a> { - updates: std::vec::Drain<'a, BlockUpdate>, - full: &'a mut Vec, -} - -impl<'a> Iterator for BlockIter<'a> { - type Item = (BlockPos, Block); - - fn next(&mut self) -> Option { - let u = self.updates.next()?; - let pos = u.block_pos(); - - let block = if u.is_index() { - let full = &mut self.full[u.state() as usize]; - - Block { - state: full.state, - nbt: full.nbt.take(), - } - } else { - Block { - state: BlockState::from_raw(u.state() as u16).unwrap(), - nbt: None, - } - }; - - Some((pos, block)) - } - - fn size_hint(&self) -> (usize, Option) { - self.updates.size_hint() - } -} - -impl<'a> ExactSizeIterator for BlockIter<'a> { - fn len(&self) -> usize { - self.updates.len() - } -} - -impl<'a> Drop for BlockIter<'a> { - fn drop(&mut self) { - self.full.clear(); - } -} - -#[bitfield(u128, order = Msb)] -#[derive(PartialEq, Eq, PartialOrd, Ord)] -struct BlockUpdate { - // Section coordinate. - #[bits(28)] - section_x: i32, - #[bits(28)] - section_z: i32, - #[bits(28)] - section_y: i32, - // Coordinate within the section. - #[bits(4)] - off_x: u32, - #[bits(4)] - off_z: u32, - #[bits(4)] - off_y: u32, - /// Bits of the [`BlockState`]. - state: u32, -} - -impl BlockUpdate { - const CHUNK_POS_MASK: u128 = u128::MAX << 72; - const SECTION_POS_MASK: u128 = u128::MAX << 44; - const BLOCK_POS_MASK: u128 = u128::MAX << 32; - - fn from_block_state(pos: BlockPos, state: BlockState) -> Self { - Self::new() - .with_section_x(pos.x.div_euclid(16)) - .with_section_y(pos.y.div_euclid(16)) - .with_section_z(pos.z.div_euclid(16)) - .with_off_x(pos.x.rem_euclid(16) as u32) - .with_off_y(pos.y.rem_euclid(16) as u32) - .with_off_z(pos.z.rem_euclid(16) as u32) - .with_state(state.to_raw() as u32) - } - - fn from_index(pos: BlockPos, idx: u32) -> Self { - Self::new() - .with_section_x(pos.x.div_euclid(16)) - .with_section_y(pos.y.div_euclid(16)) - .with_section_z(pos.z.div_euclid(16)) - .with_off_x(pos.x.rem_euclid(16) as u32) - .with_off_y(pos.y.rem_euclid(16) as u32) - .with_off_z(pos.z.rem_euclid(16) as u32) - .with_is_index(true) - .with_state(idx) - } - - // fn to_parts(self) -> (BlockPos, BlockState) { - // (self.block_pos(), self.block()) - // } - - fn block_pos(self) -> BlockPos { - BlockPos { - x: self.section_x() * 16 + self.off_x() as i32, - y: self.section_y() * 16 + self.off_y() as i32, - z: self.section_z() * 16 + self.off_z() as i32, - } - } - - fn chunk_pos(self) -> ChunkPos { - ChunkPos { - x: self.section_x(), - z: self.section_z(), - } - } - - fn block(self) -> BlockState { - BlockState::from_raw(self.state() as u16).unwrap() - } -} - -/* -impl PartialEq for BlockUpdate { - fn eq(&self, other: &Self) -> bool { - self.cmp(other).is_eq() - } -} - -impl Eq for BlockUpdate {} - -impl PartialOrd for BlockUpdate { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.ord(other)) - } -} - -impl Ord for BlockUpdate { - fn cmp(&self, other: &Self) -> Ordering { - (self.0 & Self::BLOCK_POS_MASK).cmp(&(other.0 & Self::BLOCK_POS_MASK)) - } -} -*/ -*/ \ No newline at end of file diff --git a/crates/valence_server/src/layer_old/chunk/biome.rs b/crates/valence_server/src/layer_old/chunk/biome.rs deleted file mode 100644 index d677a8281..000000000 --- a/crates/valence_server/src/layer_old/chunk/biome.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! Handles getting and setting biomes in chunk layers. - -use valence_math::DVec3; -use valence_protocol::{BlockPos, ChunkPos}; -use valence_registry::biome::BiomeId; - -use super::{ChunkOps, LoadedChunk}; -use crate::ChunkLayer; - -/// Identifies the position of a biome in a world. -/// -/// Every biome occupies a 4m³ area, so conversion from [`BlockPos`] is done by -/// dividing all components by 4 (rounding towards negative infinity). -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug)] -pub struct BiomePos { - pub x: i32, - pub y: i32, - pub z: i32, -} - -impl From for BiomePos { - fn from(pos: BlockPos) -> Self { - Self { - x: pos.x.div_euclid(4), - y: pos.y.div_euclid(4), - z: pos.z.div_euclid(4), - } - } -} - -impl From for ChunkPos { - fn from(pos: BiomePos) -> Self { - Self { - x: pos.x.div_euclid(4), - z: pos.z.div_euclid(4), - } - } -} - -impl From for BiomePos { - fn from(pos: DVec3) -> Self { - Self { - x: (pos.x / 4.0).floor() as i32, - y: (pos.y / 4.0).floor() as i32, - z: (pos.z / 4.0).floor() as i32, - } - } -} - -impl ChunkLayer { - pub fn biome(&self, pos: impl Into) -> Option { - let pos = pos.into(); - - let (chunk, x, y, z) = self.chunk_and_biome_offsets(pos)?; - Some(chunk.biome(x, y, z)) - - // Biomes - // if self.changed_biomes { - // self.changed_biomes = false; - - // messages.send_local_infallible(LocalMsg::ChangeBiome { pos }, - // |buf| { for sect in self.sections.iter() { - // sect.biomes - // .encode_mc_format( - // &mut *buf, - // |b| b.to_index() as _, - // 0, - // 3, - // bit_width(info.biome_registry_len - 1), - // ) - // .expect("paletted container encode should always - // succeed"); } - // }); - // } - } - - pub fn set_biome(&mut self, pos: impl Into, biome: BiomeId) -> Option { - let pos = pos.into(); - - let (chunk, x, y, z) = self.chunk_and_biome_offsets_mut(pos)?; - - todo!() - } - - #[inline] - fn chunk_and_biome_offsets(&self, pos: BiomePos) -> Option<(&LoadedChunk, u32, u32, u32)> { - let Some(y) = pos - .y - .checked_sub(self.info.min_y.div_euclid(4)) - .and_then(|y| y.try_into().ok()) - else { - return None; - }; - - if y >= self.info.height / 4 { - return None; - } - - let Some(chunk) = self.chunk(pos) else { - return None; - }; - - let x = pos.x.rem_euclid(4) as u32; - let z = pos.z.rem_euclid(4) as u32; - - Some((chunk, x, y, z)) - } - - #[inline] - fn chunk_and_biome_offsets_mut( - &mut self, - pos: BiomePos, - ) -> Option<(&mut LoadedChunk, u32, u32, u32)> { - let Some(y) = pos - .y - .checked_sub(self.info.min_y.div_euclid(4)) - .and_then(|y| y.try_into().ok()) - else { - return None; - }; - - if y >= self.info.height / 4 { - return None; - } - - let Some(chunk) = self.chunk_mut(pos) else { - return None; - }; - - let x = pos.x.rem_euclid(4) as u32; - let z = pos.z.rem_euclid(4) as u32; - - Some((chunk, x, y, z)) - } -} diff --git a/crates/valence_server/src/layer_old/chunk/block_update.rs b/crates/valence_server/src/layer_old/chunk/block_update.rs deleted file mode 100644 index 2e028b3ca..000000000 --- a/crates/valence_server/src/layer_old/chunk/block_update.rs +++ /dev/null @@ -1,357 +0,0 @@ -use std::borrow::Cow; - -use bitfield_struct::bitfield; -use valence_generated::block::BlockEntityKind; -use valence_nbt::Compound; -use valence_protocol::block_pos::PackedBlockPos; -use valence_protocol::encode::PacketWriter; -use valence_protocol::packets::play::chunk_delta_update_s2c::ChunkDeltaUpdateEntry; -use valence_protocol::packets::play::{BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDeltaUpdateS2c}; -use valence_protocol::{BlockPos, BlockState, ChunkPos, ChunkSectionPos, WritePacket}; - -use super::{block_offsets, Block, ChunkOps, LoadedChunk, LocalMsg}; -use crate::ChunkLayer; - -impl ChunkLayer { - pub fn set_block( - &mut self, - pos: impl Into, - block: impl Into, - ) -> Option { - let pos = pos.into(); - let block: Block = block.into(); - - let chunk = self.chunk_mut(pos)?; - - let [x, y, z] = block_offsets(pos, self.info.min_y, self.info.height as i32)?; - - self.block_updates - .updates - .push(BlockUpdate::from_parts(pos, block.state)); - - if let (Some(data), Some(kind)) = (block.nbt, block.state.to_kind()) { - PacketWriter::new( - &mut self.block_updates.block_entity_buf, - self.info.threshold, - ) - .write_packet(&BlockEntityUpdateS2c { - position: pos, - kind, - data: Cow::Borrowed(&data), - }); - } - - Some(chunk.set_block(x, y, z, block)) - } - - pub fn flush_block_updates(&mut self) { - // Sort in reverse so the dedup keeps the last of consecutive elements. - self.block_updates.updates.sort_by(|a, b| b.cmp(a)); - - // Eliminate redundant block assignments. - self.block_updates - .updates - .dedup_by_key(|u| u.0 & BlockUpdate::BLOCK_POS_MASK); - - let sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); - - for update in self.block_updates.updates.drain(..) { - let (pos, state) = update.to_parts(); - - let new_sect_pos = ChunkSectionPos::from(pos); - - if sect_pos != new_sect_pos { - let msg = LocalMsg::PacketAt { - pos: sect_pos.into(), - }; - - match self.block_updates.entry_buf.as_slice() { - // Zero updates. Do nothing. - &[] => {} - // One update. Send singular block update packet. - &[update] => self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { - position: BlockPos { - x: sect_pos.x * 16 + update.off_x() as i32, - y: sect_pos.y * 16 + update.off_y() as i32, - z: sect_pos.z * 16 + update.off_z() as i32, - }, - block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), - }); - }), - // >1 updates. Send special section update packet. - updates => { - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet( - &ChunkDeltaUpdateS2c { - chunk_section_pos: sect_pos, - blocks: Cow::Borrowed(updates), - }, - ) - }); - } - } - - self.block_updates.entry_buf.clear(); - } - } - - /* - let mut chunk: Option<&mut LoadedChunk> = None; - let mut sect_pos = ChunkSectionPos::new(i32::MIN, i32::MIN, i32::MIN); - for update in self.block_updates.updates.drain(..) { - let (pos, state, has_nbt) = update.to_parts(); - let new_sect_pos = ChunkSectionPos::from(pos); - - // Is this block in a new section? If it is, then flush the changes we've - // accumulated for the old section. - if sect_pos != new_sect_pos { - let msg = LocalMsg::PacketAt { - pos: sect_pos.into(), - }; - - match self.block_updates.entry_buf.as_slice() { - // Zero updates. Do nothing. - &[] => {} - // One update. Send singular block update packet. - &[update] => self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { - position: BlockPos { - x: sect_pos.x * 16 + update.off_x() as i32, - y: sect_pos.y * 16 + update.off_y() as i32, - z: sect_pos.z * 16 + update.off_z() as i32, - }, - block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), - }); - }), - // >1 updates. Send special section update packet. - updates => { - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet( - &ChunkDeltaUpdateS2c { - chunk_section_pos: sect_pos, - blocks: Cow::Borrowed(updates), - }, - ) - }); - } - } - - self.block_updates.entry_buf.clear(); - - // Send the block entity update packets. - // It's important that this is ordered after block state updates are sent. - for (pos, kind, nbt) in nbt.take(nbt_count) { - let msg = LocalMsg::PacketAt { - pos: sect_pos.into(), - }; - - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet( - &BlockEntityUpdateS2c { - position: pos, - kind, - data: Cow::Owned(nbt), - }, - ) - }); - } - - // Update the chunk ref if the chunk pos changed. - if sect_pos.x != new_sect_pos.x || sect_pos.z != new_sect_pos.z { - chunk = self.chunks.get_mut(&ChunkPos::from(new_sect_pos)); - } - - // Update section pos. - sect_pos = new_sect_pos; - } - - // // Send block entity updates for the current block. - // if has_nbt { - // let nbt = nbt.next().unwrap(); - - // if let Some(kind) = state.to_kind() { - // let msg = LocalMsg::PacketAt { - // pos: sect_pos.into(), - // }; - - // self.messages.send_local_infallible(msg, |w| { - // PacketWriter::new(w, self.info.threshold).write_packet( - // &BlockEntityUpdateS2c { - // position: pos, - // kind, - // data: Cow::Owned(nbt), - // }, - // ) - // }); - // } - // } - - if let Some(chunk) = &mut chunk { - let chunk_y = pos.y.wrapping_sub(self.info.min_y) as u32; - - // Is the block pos in bounds of the chunk? - if chunk_y < self.info.height { - let chunk_x = pos.x.rem_euclid(16); - let chunk_z = pos.z.rem_euclid(16); - - // Make change to the chunk and push section update. - - if chunk.viewer_count_mut() > 0 { - self.block_update_buf.push( - ChunkDeltaUpdateEntry::new() - .with_off_x(chunk_x as u8) - .with_off_y((chunk_y % 16) as u8) - .with_off_z(chunk_z as u8) - .with_block_state(block.state.to_raw() as u32), - ); - } - - chunk.set_block(chunk_x as u32, chunk_y, chunk_z as u32, block); - } - } - } - - // Flush any remaining block changes. - - let msg = LocalMsg::PacketAt { - pos: sect_pos.into(), - }; - - match self.block_update_buf.as_slice() { - // Zero updates. Do nothing. - &[] => {} - // One update. Send singular block update packet. - &[update] => self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&BlockUpdateS2c { - position: BlockPos { - x: sect_pos.x * 16 + update.off_x() as i32, - y: sect_pos.y * 16 + update.off_y() as i32, - z: sect_pos.z * 16 + update.off_z() as i32, - }, - block_id: BlockState::from_raw(update.block_state() as u16).unwrap(), - }); - }), - // >1 updates. Send special section update packet. - updates => { - self.messages.send_local_infallible(msg, |w| { - PacketWriter::new(w, self.info.threshold).write_packet(&ChunkDeltaUpdateS2c { - chunk_section_pos: sect_pos, - blocks: Cow::Borrowed(updates), - }) - }); - } - } - - self.block_update_buf.clear();*/ - } -} - -pub(super) struct BlockUpdates { - updates: Vec, - block_entities: Vec<(BlockPos, BlockEntityKind, Compound)>, - entry_buf: Vec, -} - -impl BlockUpdates { - pub(super) fn shrink_to_fit(&mut self) { - self.updates.shrink_to_fit(); - self.block_entities.shrink_to_fit(); - self.entry_buf.shrink_to_fit(); - } -} - -#[bitfield(u128, order = Msb)] -#[derive(PartialEq, Eq, PartialOrd, Ord)] -struct BlockUpdate { - // Section coordinate. - #[bits(28)] - section_x: i32, - #[bits(28)] - section_z: i32, - #[bits(28)] - section_y: i32, - // Coordinate within the section. - #[bits(4)] - off_x: u32, - #[bits(4)] - off_z: u32, - #[bits(4)] - off_y: u32, - /// Bits of the [`BlockState`]. - state: u32, -} - -impl BlockUpdate { - const CHUNK_POS_MASK: u128 = u128::MAX << 72; - const SECTION_POS_MASK: u128 = u128::MAX << 44; - const BLOCK_POS_MASK: u128 = u128::MAX << 32; - - fn from_parts(pos: BlockPos, state: BlockState) { - Self::new() - .with_section_x(pos.x.div_euclid(16)) - .with_section_y(pos.y.div_euclid(16)) - .with_section_z(pos.z.div_euclid(16)) - .with_off_x(pos.x.rem_euclid(16) as u32) - .with_off_y(pos.y.rem_euclid(16) as u32) - .with_off_z(pos.z.rem_euclid(16) as u32) - .with_state(state.to_raw() as u32) - } - - fn to_parts(self) -> (BlockPos, BlockState) { - ( - BlockPos { - x: self.section_x() * 16 + self.off_x() as i32, - y: self.section_y() * 16 + self.off_y() as i32, - z: self.section_z() * 16 + self.off_z() as i32, - }, - BlockState::from_raw(self.state()).unwrap(), - ) - } - - // fn from_block_state(pos: BlockPos, state: BlockState) -> Self { - // Self::new() - // .with_section_x(pos.x.div_euclid(16)) - // .with_section_y(pos.y.div_euclid(16)) - // .with_section_z(pos.z.div_euclid(16)) - // .with_off_x(pos.x.rem_euclid(16) as u32) - // .with_off_y(pos.y.rem_euclid(16) as u32) - // .with_off_z(pos.z.rem_euclid(16) as u32) - // .with_state(state.to_raw() as u32) - // } - - // fn from_index(pos: BlockPos, idx: u32) -> Self { - // Self::new() - // .with_section_x(pos.x.div_euclid(16)) - // .with_section_y(pos.y.div_euclid(16)) - // .with_section_z(pos.z.div_euclid(16)) - // .with_off_x(pos.x.rem_euclid(16) as u32) - // .with_off_y(pos.y.rem_euclid(16) as u32) - // .with_off_z(pos.z.rem_euclid(16) as u32) - // .with_is_index(true) - // .with_state(idx) - // } - - // fn to_parts(self) -> (BlockPos, BlockState) { - // (self.block_pos(), self.block()) - // } - - // fn block_pos(self) -> BlockPos { - // BlockPos { - // x: self.section_x() * 16 + self.off_x() as i32, - // y: self.section_y() * 16 + self.off_y() as i32, - // z: self.section_z() * 16 + self.off_z() as i32, - // } - // } - - // fn chunk_pos(self) -> ChunkPos { - // ChunkPos { - // x: self.section_x(), - // z: self.section_z(), - // } - // } - - // fn block(self) -> BlockState { - // BlockState::from_raw(self.state() as u16).unwrap() - // } -} diff --git a/crates/valence_server/src/layer_old/chunk/chunk.rs b/crates/valence_server/src/layer_old/chunk/chunk.rs deleted file mode 100644 index 4d92694d2..000000000 --- a/crates/valence_server/src/layer_old/chunk/chunk.rs +++ /dev/null @@ -1,369 +0,0 @@ -use valence_nbt::Compound; -use valence_protocol::{BlockPos, BlockState, ChunkPos}; -use valence_registry::biome::BiomeId; - -use super::{BiomePos, Block, BlockRef}; - -/// Common operations on chunks. Notable implementors are -/// [`LoadedChunk`](super::loaded::LoadedChunk) and -/// [`UnloadedChunk`](super::unloaded::UnloadedChunk). -pub trait ChunkOps { - /// Gets the height of this chunk in meters or blocks. - fn height(&self) -> u32; - - /// Gets the block at the provided position in this chunk. `x` and `z` - /// are in the range `0..16` while `y` is in the range `0..height`. - /// - /// # Panics - /// - /// May panic if the position is out of bounds. - #[track_caller] - fn block(&self, x: u32, y: u32, z: u32) -> BlockRef { - BlockRef { - state: self.block_state(x, y, z), - nbt: self.block_entity(x, y, z), - } - } - - /// Sets the block at the provided position in this chunk. `x` and `z` - /// are in the range `0..16` while `y` is in the range `0..height`. The - /// previous block at the position is returned. - /// - /// # Panics - /// - /// May panic if the position is out of bounds. - #[track_caller] - fn set_block(&mut self, x: u32, y: u32, z: u32, block: impl Into) -> Block { - let block = block.into(); - let old_state = self.set_block_state(x, y, z, block.state); - - let old_nbt = if block.nbt.is_none() && block.state.block_entity_kind().is_some() { - // If the block state is associated with a block entity, make sure there's - // always some NBT data. Otherwise, the block will appear invisible to clients - // when loading the chunk. - self.set_block_entity(x, y, z, Some(Compound::default())) - } else { - self.set_block_entity(x, y, z, block.nbt) - }; - - Block { - state: old_state, - nbt: old_nbt, - } - } - - /* - /// Sets all the blocks in the entire chunk to the provided block. - fn fill_blocks(&mut self, block: impl Into) { - let block = block.into_block(); - - self.fill_block_states(block.state); - - if block.nbt.is_some() { - for x in 0..16 { - for z in 0..16 { - for y in 0..self.height() { - self.set_block_entity(x, y, z, block.nbt.clone()); - } - } - } - } else { - self.clear_block_entities(); - } - } - */ - - /// Gets the block state at the provided position in this chunk. `x` and `z` - /// are in the range `0..16` while `y` is in the range `0..height`. - /// - /// # Panics - /// - /// May panic if the position is out of bounds. - #[track_caller] - fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState; - - /// Sets the block state at the provided position in this chunk. `x` and `z` - /// are in the range `0..16` while `y` is in the range `0..height`. The - /// previous block state at the position is returned. - /// - /// **NOTE:** This is a low-level function which may break expected - /// invariants for block entities. Prefer [`Self::set_block`] if performance - /// is not a concern. - /// - /// # Panics - /// - /// May panic if the position is out of bounds. - #[track_caller] - fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState; - - /// Replaces all block states in the entire chunk with the provided block - /// state. - /// - /// **NOTE:** This is a low-level function which may break expected - /// invariants for block entities. Prefer [`Self::fill_blocks`] instead. - fn fill_block_states(&mut self, block: BlockState) { - for sect_y in 0..self.height() / 16 { - self.fill_block_state_section(sect_y, block); - } - } - - /// Replaces all the block states in a section with the provided block - /// state. - /// - /// **NOTE:** This is a low-level function which may break expected - /// invariants for block entities. Prefer [`Self::set_block`] if performance - /// is not a concern. - /// - /// # Panics - /// - /// May panic if the section offset is out of bounds. - #[track_caller] - fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState); - - /// Gets the block entity at the provided position in this chunk. `x` and - /// `z` are in the range `0..16` while `y` is in the range `0..height`. - /// - /// # Panics - /// - /// May panic if the position is out of bounds. - #[track_caller] - fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound>; - - /// Gets a mutable reference to the block entity at the provided position in - /// this chunk. `x` and `z` are in the range `0..16` while `y` is in the - /// range `0..height`. - /// - /// # Panics - /// - /// May panic if the position is out of bounds. - #[track_caller] - fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound>; - - /// Sets the block entity at the provided position in this chunk. `x` and - /// `z` are in the range `0..16` while `y` is in the range `0..height`. - /// The previous block entity at the position is returned. - /// - /// **NOTE:** This is a low-level function which may break expected - /// invariants for block entities. Prefer [`Self::set_block`] if performance - /// is not a concern. - /// - /// # Panics - /// - /// May panic if the position is out of bounds. - #[track_caller] - fn set_block_entity( - &mut self, - x: u32, - y: u32, - z: u32, - block_entity: Option, - ) -> Option; - - /// Removes all block entities from the chunk. - /// - /// **NOTE:** This is a low-level function which may break expected - /// invariants for block entities. Prefer [`Self::set_block`] if performance - /// is not a concern. - fn clear_block_entities(&mut self); - - /// Gets the biome at the provided position in this chunk. `x` and `z` are - /// in the range `0..4` while `y` is in the range `0..height / 4`. - /// - /// Note that biomes are 4x4x4 segments of a chunk, so the xyz arguments to - /// this method differ from those to [`Self::block_state`] and - /// [`Self::block_entity`]. - /// - /// # Panics - /// - /// May panic if the position is out of bounds. - #[track_caller] - fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId; - - /// Sets the biome at the provided position in this chunk. The Previous - /// biome at the position is returned. `x` and `z` are in the range `0..4` - /// while `y` is in the range `0..height / 4`. - /// - /// Note that biomes are 4x4x4 segments of a chunk, so the xyz arguments to - /// this method differ from those to [`Self::block_state`] and - /// [`Self::block_entity`]. - /// - /// # Panics - /// - /// May panic if the position is out of bounds. - #[track_caller] - fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId; - - /// Sets all the biomes in the entire chunk to the provided biome. - fn fill_biomes(&mut self, biome: BiomeId) { - for sect_y in 0..self.height() / 16 { - self.fill_biome_section(sect_y, biome); - } - } - - /// Replaces all the biomes in a section with the provided biome. - /// - /// # Panics - /// - /// May panic if the section offset is out of bounds. - #[track_caller] - fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId); - - /// Sets all blocks and biomes in this chunk to the default values. The - /// height of the chunk is not modified. - fn clear(&mut self) { - self.fill_block_states(BlockState::AIR); - self.fill_biomes(BiomeId::default()); - self.clear_block_entities(); - } - - /// Attempts to optimize this chunk by reducing its memory usage or other - /// characteristics. This may be a relatively expensive operation. - /// - /// This method must not alter the semantics of the chunk in any observable - /// way. - fn shrink_to_fit(&mut self); -} - -/// The maximum height of a chunk. -pub const MAX_HEIGHT: u32 = 4096; - -pub(super) const SECTION_BLOCK_COUNT: usize = 16 * 16 * 16; -pub(super) const SECTION_BIOME_COUNT: usize = 4 * 4 * 4; - -/// Returns the minimum number of bits needed to represent the integer `n`. -pub(super) const fn bit_width(n: usize) -> usize { - (usize::BITS - n.leading_zeros()) as _ -} - -pub(super) fn block_offsets(block_pos: BlockPos, min_y: i32, height: i32) -> Option<[u32; 3]> { - let off_x = block_pos.x.rem_euclid(16); - let off_z = block_pos.z.rem_euclid(16); - let off_y = block_pos.y.wrapping_sub(min_y); - - if off_y < height { - Some([off_x as u32, off_y as u32, off_z as u32]) - } else { - None - } -} - -pub(super) fn biome_offsets(biome_pos: BiomePos, min_y: i32, height: i32) -> Option<[u32; 3]> { - let off_x = biome_pos.x.rem_euclid(4); - let off_z = biome_pos.z.rem_euclid(4); - let off_y = biome_pos.y.wrapping_sub(min_y / 4); - - if off_y < height / 4 { - Some([off_x as u32, off_y as u32, off_z as u32]) - } else { - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::layer_old::chunk::{Chunk, LoadedChunk}; - - #[test] - fn chunk_get_set() { - fn check(mut chunk: impl ChunkOps) { - assert_eq!( - chunk.set_block_state(1, 2, 3, BlockState::CHAIN), - BlockState::AIR - ); - assert_eq!( - chunk.set_block_state(1, 2, 3, BlockState::AIR), - BlockState::CHAIN - ); - - assert_eq!(chunk.set_block_entity(1, 2, 3, Some(Compound::new())), None); - assert_eq!(chunk.set_block_entity(1, 2, 3, None), Some(Compound::new())); - } - - let unloaded = Chunk::with_height(512); - let loaded = LoadedChunk::new(512); - - check(unloaded); - check(loaded); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_0() { - let mut chunk = Chunk::with_height(512); - chunk.set_block_state(0, 0, 16, BlockState::AIR); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_1() { - let mut chunk = LoadedChunk::new(512); - chunk.set_block_state(0, 0, 16, BlockState::AIR); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_2() { - let mut chunk = Chunk::with_height(512); - chunk.set_block_entity(0, 0, 16, None); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_3() { - let mut chunk = LoadedChunk::new(512); - chunk.set_block_entity(0, 0, 16, None); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_4() { - let mut chunk = Chunk::with_height(512); - chunk.set_biome(0, 0, 4, BiomeId::DEFAULT); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_5() { - let mut chunk = LoadedChunk::new(512); - chunk.set_biome(0, 0, 4, BiomeId::DEFAULT); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_6() { - let mut chunk = Chunk::with_height(512); - chunk.fill_block_state_section(chunk.height() / 16, BlockState::AIR); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_7() { - let mut chunk = LoadedChunk::new(512); - chunk.fill_block_state_section(chunk.height() / 16, BlockState::AIR); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_8() { - let mut chunk = Chunk::with_height(512); - chunk.fill_biome_section(chunk.height() / 16, BiomeId::DEFAULT); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn chunk_debug_oob_9() { - let mut chunk = LoadedChunk::new(512); - chunk.fill_biome_section(chunk.height() / 16, BiomeId::DEFAULT); - } -} diff --git a/crates/valence_server/src/layer_old/chunk/loaded.rs b/crates/valence_server/src/layer_old/chunk/loaded.rs deleted file mode 100644 index 705b5e742..000000000 --- a/crates/valence_server/src/layer_old/chunk/loaded.rs +++ /dev/null @@ -1,330 +0,0 @@ -use std::borrow::Cow; -use std::mem; -use std::sync::atomic::{AtomicU32, Ordering}; - -use parking_lot::Mutex; // Using nonstandard mutex to avoid poisoning API. -use valence_nbt::{compound, Compound}; -use valence_protocol::encode::{PacketWriter, WritePacket}; -use valence_protocol::packets::play::chunk_data_s2c::ChunkDataBlockEntity; -use valence_protocol::packets::play::ChunkDataS2c; -use valence_protocol::{BlockState, ChunkPos, Encode}; -use valence_registry::biome::BiomeId; -use valence_registry::RegistryIdx; - -use super::chunk::{bit_width, ChunkOps}; -use super::unloaded::Chunk; -use super::{ChunkLayerInfo, SECTION_BLOCK_COUNT}; - -/// A chunk that is actively loaded in a [`ChunkLayer`]. This is only accessible -/// behind a reference. -/// -/// Like [`Chunk`], loaded chunks implement the [`ChunkOps`] trait so you can -/// use many of the same methods. -/// -/// **NOTE:** Loaded chunks are a low-level API. Mutations directly to loaded -/// chunks are intentionally not synchronized with clients. Consider using the -/// relevant methods on [`ChunkLayer`] instead. -/// -/// [`ChunkLayer`]: super::ChunkLayer -#[derive(Debug)] -pub struct LoadedChunk { - /// Chunk data for this loaded chunk. - chunk: Chunk, - /// A count of the clients viewing this chunk. Useful for knowing if it's - /// necessary to record changes, since no client would be in view to receive - /// the changes if this were zero. - viewer_count: AtomicU32, - /// Cached bytes of the chunk initialization packet. The cache is considered - /// invalidated if empty. This should be cleared whenever the chunk is - /// modified in an observable way, even if the chunk is not viewed. - cached_init_packets: Mutex>, -} - -impl LoadedChunk { - pub(crate) fn new(height: u32) -> Self { - Self { - viewer_count: AtomicU32::new(0), - chunk: Chunk::with_height(height), - cached_init_packets: Mutex::new(vec![]), - } - } - - /// Sets the content of this chunk to the supplied [`UnloadedChunk`]. The - /// given unloaded chunk is [resized] to match the height of this loaded - /// chunk prior to insertion. - /// - /// The previous chunk data is returned. - /// - /// [resized]: UnloadedChunk::set_height - pub fn replace(&mut self, mut chunk: Chunk) -> Chunk { - chunk.set_height(self.height()); - - self.cached_init_packets.get_mut().clear(); - - mem::replace(&mut self.chunk, chunk) - } - - pub(super) fn into_chunk(self) -> Chunk { - self.chunk - } - - /// Clones this chunk's data into the returned [`Chunk`]. - pub fn to_chunk(&self) -> Chunk { - self.chunk.clone() - } - - /// Returns the number of clients in view of this chunk. - pub fn viewer_count(&self) -> u32 { - self.viewer_count.load(Ordering::Relaxed) - } - - /// Like [`Self::viewer_count`], but avoids an atomic operation. - pub fn viewer_count_mut(&mut self) -> u32 { - *self.viewer_count.get_mut() - } - - /// Increments the viewer count. - pub(crate) fn inc_viewer_count(&self) { - self.viewer_count.fetch_add(1, Ordering::Relaxed); - } - - /// Decrements the viewer count. - #[track_caller] - pub(crate) fn dec_viewer_count(&self) { - let old = self.viewer_count.fetch_sub(1, Ordering::Relaxed); - debug_assert_ne!(old, 0, "viewer count underflow!"); - } - - /// Writes the packet data needed to initialize this chunk. - pub(crate) fn write_init_packets( - &self, - mut writer: impl WritePacket, - pos: ChunkPos, - info: &ChunkLayerInfo, - ) { - let mut init_packets = self.cached_init_packets.lock(); - - if init_packets.is_empty() { - let heightmaps = compound! { - // TODO: MOTION_BLOCKING and WORLD_SURFACE heightmaps. - }; - - let mut blocks_and_biomes: Vec = vec![]; - - for sect in &self.chunk.sections { - sect.count_non_air_blocks() - .encode(&mut blocks_and_biomes) - .unwrap(); - - sect.block_states - .encode_mc_format( - &mut blocks_and_biomes, - |b| b.to_raw().into(), - 4, - 8, - bit_width(BlockState::max_raw().into()), - ) - .expect("paletted container encode should always succeed"); - - sect.biomes - .encode_mc_format( - &mut blocks_and_biomes, - |b| b.to_index() as _, - 0, - 3, - bit_width(info.biome_registry_len - 1), - ) - .expect("paletted container encode should always succeed"); - } - - let block_entities: Vec<_> = self - .chunk - .block_entities - .iter() - .filter_map(|(&idx, nbt)| { - let x = idx % 16; - let z = idx / 16 % 16; - let y = idx / 16 / 16; - - let kind = self.chunk.sections[y as usize / 16] - .block_states - .get(idx as usize % SECTION_BLOCK_COUNT) - .block_entity_kind(); - - kind.map(|kind| ChunkDataBlockEntity { - packed_xz: ((x << 4) | z) as i8, - y: y as i16 + info.min_y as i16, - kind, - data: Cow::Borrowed(nbt), - }) - }) - .collect(); - - PacketWriter::new(&mut init_packets, info.threshold).write_packet(&ChunkDataS2c { - pos, - heightmaps: Cow::Owned(heightmaps), - blocks_and_biomes: &blocks_and_biomes, - block_entities: Cow::Owned(block_entities), - sky_light_mask: Cow::Borrowed(&[]), - block_light_mask: Cow::Borrowed(&[]), - empty_sky_light_mask: Cow::Borrowed(&[]), - empty_block_light_mask: Cow::Borrowed(&[]), - sky_light_arrays: Cow::Borrowed(&[]), - block_light_arrays: Cow::Borrowed(&[]), - }) - } - - writer.write_packet_bytes(&init_packets); - } -} - -impl ChunkOps for LoadedChunk { - fn height(&self) -> u32 { - self.chunk.height() - } - - fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState { - self.chunk.block_state(x, y, z) - } - - fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState { - let old_block = self.chunk.set_block_state(x, y, z, block); - - if block != old_block { - self.cached_init_packets.get_mut().clear(); - } - - old_block - } - - fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) { - self.chunk.fill_block_state_section(sect_y, block); - - // TODO: do some checks to avoid calling this sometimes. - self.cached_init_packets.get_mut().clear(); - } - - fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> { - self.chunk.block_entity(x, y, z) - } - - fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> { - let res = self.chunk.block_entity_mut(x, y, z); - - if res.is_some() { - self.cached_init_packets.get_mut().clear(); - } - - res - } - - fn set_block_entity( - &mut self, - x: u32, - y: u32, - z: u32, - block_entity: Option, - ) -> Option { - self.cached_init_packets.get_mut().clear(); - - self.chunk.set_block_entity(x, y, z, block_entity) - } - - fn clear_block_entities(&mut self) { - if self.chunk.block_entities.is_empty() { - return; - } - - self.chunk.clear_block_entities(); - - self.cached_init_packets.get_mut().clear(); - } - - fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId { - self.chunk.biome(x, y, z) - } - - fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId { - let old_biome = self.chunk.set_biome(x, y, z, biome); - - if biome != old_biome { - self.cached_init_packets.get_mut().clear(); - } - - old_biome - } - - fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) { - self.chunk.fill_biome_section(sect_y, biome); - - self.cached_init_packets.get_mut().clear(); - } - - fn shrink_to_fit(&mut self) { - self.cached_init_packets.get_mut().shrink_to_fit(); - self.chunk.shrink_to_fit(); - } -} - -#[cfg(test)] -mod tests { - use valence_protocol::{ident, CompressionThreshold}; - - use super::*; - - #[test] - fn loaded_chunk_changes_clear_packet_cache() { - #[track_caller] - fn check(chunk: &mut LoadedChunk, change: impl FnOnce(&mut LoadedChunk) -> T) { - let info = ChunkLayerInfo { - dimension_type_name: ident!("whatever").into(), - height: 512, - min_y: -16, - biome_registry_len: 200, - threshold: CompressionThreshold(-1), - }; - - let mut buf = vec![]; - let mut writer = PacketWriter::new(&mut buf, CompressionThreshold(-1)); - - // Rebuild cache. - chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info); - - // Check that the cache is built. - assert!(!chunk.cached_init_packets.get_mut().is_empty()); - - // Making a change should clear the cache. - change(chunk); - assert!(chunk.cached_init_packets.get_mut().is_empty()); - - // Rebuild cache again. - chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info); - assert!(!chunk.cached_init_packets.get_mut().is_empty()); - } - - let mut chunk = LoadedChunk::new(512); - - check(&mut chunk, |c| { - c.set_block_state(0, 4, 0, BlockState::ACACIA_WOOD) - }); - check(&mut chunk, |c| c.set_biome(1, 2, 3, BiomeId::from_index(4))); - check(&mut chunk, |c| c.fill_biomes(BiomeId::DEFAULT)); - check(&mut chunk, |c| c.fill_block_states(BlockState::WET_SPONGE)); - check(&mut chunk, |c| { - c.set_block_entity(3, 40, 5, Some(compound! {})) - }); - check(&mut chunk, |c| { - c.block_entity_mut(3, 40, 5).unwrap(); - }); - check(&mut chunk, |c| c.set_block_entity(3, 40, 5, None)); - - // Old block state is the same as new block state, so the cache should still be - // intact. - assert_eq!( - chunk.set_block_state(0, 0, 0, BlockState::WET_SPONGE), - BlockState::WET_SPONGE - ); - - assert!(!chunk.cached_init_packets.get_mut().is_empty()); - } -} diff --git a/crates/valence_server/src/layer_old/chunk/unloaded.rs b/crates/valence_server/src/layer_old/chunk/unloaded.rs deleted file mode 100644 index a7b6ad824..000000000 --- a/crates/valence_server/src/layer_old/chunk/unloaded.rs +++ /dev/null @@ -1,244 +0,0 @@ -use std::cmp::Ordering; -use std::collections::BTreeMap; - -use valence_nbt::Compound; -use valence_protocol::BlockState; -use valence_registry::biome::BiomeId; - -use super::chunk::{ChunkOps, MAX_HEIGHT}; -use super::paletted_container::PalettedContainer; -use super::{SECTION_BIOME_COUNT, SECTION_BLOCK_COUNT}; - -#[derive(Clone, Default, Debug)] -pub struct Chunk { - pub(super) sections: Vec

, - pub(super) block_entities: BTreeMap, -} - -#[derive(Clone, Default, Debug)] -pub(super) struct Section { - pub(super) block_states: - PalettedContainer, - pub(super) biomes: PalettedContainer, -} - -impl Section { - pub(super) fn count_non_air_blocks(&self) -> u16 { - let mut count = 0; - - match &self.block_states { - PalettedContainer::Single(s) => { - if !s.is_air() { - count += SECTION_BLOCK_COUNT as u16; - } - } - PalettedContainer::Indirect(ind) => { - for i in 0..SECTION_BLOCK_COUNT { - if !ind.get(i).is_air() { - count += 1; - } - } - } - PalettedContainer::Direct(dir) => { - for s in dir.as_ref() { - if !s.is_air() { - count += 1; - } - } - } - } - - count - } -} - -impl Chunk { - pub const fn new() -> Self { - Self { - sections: vec![], - block_entities: BTreeMap::new(), - } - } - - pub fn with_height(height: u32) -> Self { - Self { - sections: vec![Section::default(); height as usize / 16], - block_entities: BTreeMap::new(), - } - } - - /// Sets the height of this chunk in blocks. The chunk is truncated or - /// extended with [`BlockState::AIR`] and [`BiomeId::default()`] from the - /// top. - /// - /// The new height should be a multiple of 16 and no more than - /// [`MAX_HEIGHT`]. Otherwise, the height is rounded down to the nearest - /// valid height. - pub fn set_height(&mut self, height: u32) { - let new_count = height.min(MAX_HEIGHT) as usize / 16; - let old_count = self.sections.len(); - - match new_count.cmp(&old_count) { - Ordering::Less => { - self.sections.truncate(new_count); - self.sections.shrink_to_fit(); - - let cutoff = SECTION_BLOCK_COUNT as u32 * new_count as u32; - self.block_entities.retain(|idx, _| *idx < cutoff); - } - Ordering::Equal => {} - Ordering::Greater => { - let diff = new_count - old_count; - self.sections.reserve_exact(diff); - self.sections.extend((0..diff).map(|_| Section::default())); - } - } - } -} - -impl ChunkOps for Chunk { - fn height(&self) -> u32 { - self.sections.len() as u32 * 16 - } - - fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState { - check_block_oob(self, x, y, z); - - let idx = x + z * 16 + y % 16 * 16 * 16; - self.sections[y as usize / 16] - .block_states - .get(idx as usize) - } - - fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState { - check_block_oob(self, x, y, z); - - let idx = x + z * 16 + y % 16 * 16 * 16; - self.sections[y as usize / 16] - .block_states - .set(idx as usize, block) - } - - fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) { - check_section_oob(self, sect_y); - - self.sections[sect_y as usize].block_states.fill(block); - } - - fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> { - check_block_oob(self, x, y, z); - - let idx = x + z * 16 + y * 16 * 16; - self.block_entities.get(&idx) - } - - fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> { - check_block_oob(self, x, y, z); - - let idx = x + z * 16 + y * 16 * 16; - self.block_entities.get_mut(&idx) - } - - fn set_block_entity( - &mut self, - x: u32, - y: u32, - z: u32, - block_entity: Option, - ) -> Option { - check_block_oob(self, x, y, z); - - let idx = x + z * 16 + y * 16 * 16; - - match block_entity { - Some(be) => self.block_entities.insert(idx, be), - None => self.block_entities.remove(&idx), - } - } - - fn clear_block_entities(&mut self) { - self.block_entities.clear(); - } - - fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId { - check_biome_oob(self, x, y, z); - - let idx = x + z * 4 + y % 4 * 4 * 4; - self.sections[y as usize / 4].biomes.get(idx as usize) - } - - fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId { - check_biome_oob(self, x, y, z); - - let idx = x + z * 4 + y % 4 * 4 * 4; - self.sections[y as usize / 4] - .biomes - .set(idx as usize, biome) - } - - fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) { - check_section_oob(self, sect_y); - - self.sections[sect_y as usize].biomes.fill(biome); - } - - fn shrink_to_fit(&mut self) { - for sect in &mut self.sections { - sect.block_states.shrink_to_fit(); - sect.biomes.shrink_to_fit(); - } - } -} - -#[inline] -#[track_caller] -pub(super) fn check_block_oob(chunk: &impl ChunkOps, x: u32, y: u32, z: u32) { - assert!( - x < 16 && y < chunk.height() && z < 16, - "chunk block offsets of ({x}, {y}, {z}) are out of bounds" - ); -} - -#[inline] -#[track_caller] -pub(super) fn check_biome_oob(chunk: &impl ChunkOps, x: u32, y: u32, z: u32) { - assert!( - x < 4 && y < chunk.height() / 4 && z < 4, - "chunk biome offsets of ({x}, {y}, {z}) are out of bounds" - ); -} - -#[inline] -#[track_caller] -pub(super) fn check_section_oob(chunk: &impl ChunkOps, sect_y: u32) { - assert!( - sect_y < chunk.height() / 16, - "chunk section offset of {sect_y} is out of bounds" - ); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn chunk_resize_removes_block_entities() { - let mut chunk = Chunk::with_height(32); - - assert_eq!(chunk.height(), 32); - - // First block entity is in section 0. - chunk.set_block_entity(0, 5, 0, Some(Compound::new())); - - // Second block entity is in section 1. - chunk.set_block_entity(0, 16, 0, Some(Compound::new())); - - // Remove section 0. - chunk.set_height(16); - assert_eq!(chunk.height(), 16); - - assert_eq!(chunk.block_entity(0, 5, 0), Some(&Compound::new())); - assert_eq!(chunk.set_block_entity(0, 5, 0, None), Some(Compound::new())); - assert!(chunk.block_entities.is_empty()); - } -} diff --git a/crates/valence_server/src/layer_old/entity.rs b/crates/valence_server/src/layer_old/entity.rs deleted file mode 100644 index 85080efba..000000000 --- a/crates/valence_server/src/layer_old/entity.rs +++ /dev/null @@ -1,529 +0,0 @@ -use std::collections::hash_map::Entry; -use std::collections::BTreeSet; - -use bevy_app::prelude::*; -use bevy_ecs::prelude::*; -use bevy_ecs::query::Has; -use rustc_hash::FxHashMap; -use valence_entity::query::UpdateEntityQuery; -use valence_entity::{EntityId, EntityLayerId, OldEntityLayerId, OldPosition, Position}; -use valence_protocol::encode::{PacketWriter, WritePacket}; -use valence_protocol::{BlockPos, ChunkPos, CompressionThreshold, Encode, Packet}; -use valence_server_common::{Despawned, Server}; - -use super::bvh::GetChunkPos; -use super::message::Messages; -use super::{Layer, UpdateLayersPostClientSet, UpdateLayersPreClientSet}; -use crate::client::Client; - -/// A [`Component`] containing Minecraft entities. -#[derive(Component, Debug)] -pub struct EntityLayer { - messages: EntityLayerMessages, - entities: FxHashMap>, - threshold: CompressionThreshold, -} - -type EntityLayerMessages = Messages; - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub(crate) enum GlobalMsg { - /// Send packet data to all clients viewing the layer. Message data is - /// serialized packet data. - Packet, - /// Send packet data to all clients viewing layer, except the client - /// identified by `except`. - PacketExcept { except: Entity }, - /// This layer was despawned and should be removed from the set of visible - /// entity layers. Message data is empty. - DespawnLayer, -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -// NOTE: Variant order is significant. Despawns should be ordered before spawns. -pub(crate) enum LocalMsg { - /// Despawn entities if the client is not already viewing `dest_layer`. - /// Message data is the serialized form of `EntityId`. - DespawnEntity { pos: ChunkPos, dest_layer: Entity }, - /// Despawn entities if the client is not in view of `dest_pos`. Message - /// data is the serialized form of `EntityId`. - DespawnEntityTransition { pos: ChunkPos, dest_pos: ChunkPos }, - /// Spawn entities if the client is not already viewing `src_layer`. Message - /// data is the serialized form of [`Entity`]. - SpawnEntity { pos: ChunkPos, src_layer: Entity }, - /// Spawn entities if the client is not in view of `src_pos`. Message data - /// is the serialized form of [`Entity`]. - SpawnEntityTransition { pos: ChunkPos, src_pos: ChunkPos }, - /// Send packet data to all clients viewing the layer in view of `pos`. - /// Message data is serialized packet data. - PacketAt { pos: ChunkPos }, - /// Send packet data to all clients viewing the layer in view of `pos`, - /// except the client identified by `except`. Message data is serialized - /// packet data. - PacketAtExcept { pos: ChunkPos, except: Entity }, - /// Send packet data to all clients in a sphere. - RadiusAt { - center: BlockPos, - radius_squared: u32, - }, - /// Send packet data to all clients in a sphere, except the client `except`. - RadiusAtExcept { - center: BlockPos, - radius_squared: u32, - except: Entity, - }, -} - -impl GetChunkPos for LocalMsg { - fn chunk_pos(&self) -> ChunkPos { - match *self { - LocalMsg::PacketAt { pos } => pos, - LocalMsg::PacketAtExcept { pos, .. } => pos, - LocalMsg::RadiusAt { center, .. } => center.into(), - LocalMsg::RadiusAtExcept { center, .. } => center.into(), - LocalMsg::SpawnEntity { pos, .. } => pos, - LocalMsg::SpawnEntityTransition { pos, .. } => pos, - LocalMsg::DespawnEntity { pos, .. } => pos, - LocalMsg::DespawnEntityTransition { pos, .. } => pos, - } - } -} - -impl EntityLayer { - /// Creates a new entity layer. - pub fn new(server: &Server) -> Self { - Self { - messages: Messages::new(), - entities: Default::default(), - threshold: server.compression_threshold(), - } - } - - /// Returns an iterator over all entities contained within the given chunk - /// position in this layer. - pub fn entities_at( - &self, - pos: impl Into, - ) -> impl Iterator + Clone + '_ { - self.entities - .get(&pos.into()) - .into_iter() - .flat_map(|entities| entities.iter().copied()) - } - - pub(crate) fn messages(&self) -> &EntityLayerMessages { - &self.messages - } -} - -impl Layer for EntityLayer { - type ExceptWriter<'a> = ExceptWriter<'a>; - - type ViewWriter<'a> = ViewWriter<'a>; - - type ViewExceptWriter<'a> = ViewExceptWriter<'a>; - - type RadiusWriter<'a> = RadiusWriter<'a>; - - type RadiusExceptWriter<'a> = RadiusExceptWriter<'a>; - - fn except_writer(&mut self, except: Entity) -> Self::ExceptWriter<'_> { - ExceptWriter { - layer: self, - except, - } - } - - fn view_writer(&mut self, pos: impl Into) -> Self::ViewWriter<'_> { - ViewWriter { - layer: self, - pos: pos.into(), - } - } - - fn view_except_writer( - &mut self, - pos: impl Into, - except: Entity, - ) -> Self::ViewExceptWriter<'_> { - ViewExceptWriter { - layer: self, - pos: pos.into(), - except, - } - } - - fn radius_writer( - &mut self, - center: impl Into, - radius: u32, - ) -> Self::RadiusWriter<'_> { - RadiusWriter { - layer: self, - center: center.into(), - radius_squared: radius.saturating_mul(radius), - } - } - - fn radius_except_writer( - &mut self, - center: impl Into, - radius: u32, - except: Entity, - ) -> Self::RadiusExceptWriter<'_> { - RadiusExceptWriter { - layer: self, - center: center.into(), - radius_squared: radius.saturating_mul(radius), - except, - } - } -} - -impl WritePacket for EntityLayer { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.messages.send_global(GlobalMsg::Packet, |b| { - PacketWriter::new(b, self.threshold).write_packet_fallible(packet) - }) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.messages - .send_global_infallible(GlobalMsg::Packet, |b| b.extend_from_slice(bytes)); - } -} - -pub struct ExceptWriter<'a> { - layer: &'a mut EntityLayer, - except: Entity, -} - -impl WritePacket for ExceptWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_global( - GlobalMsg::PacketExcept { - except: self.except, - }, - |b| PacketWriter::new(b, self.layer.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_global_infallible( - GlobalMsg::PacketExcept { - except: self.except, - }, - |b| b.extend_from_slice(bytes), - ) - } -} - -pub struct ViewWriter<'a> { - layer: &'a mut EntityLayer, - pos: ChunkPos, -} - -impl<'a> WritePacket for ViewWriter<'a> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer - .messages - .send_local(LocalMsg::PacketAt { pos: self.pos }, |b| { - PacketWriter::new(b, self.layer.threshold).write_packet_fallible(packet) - }) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer - .messages - .send_local_infallible(LocalMsg::PacketAt { pos: self.pos }, |b| { - b.extend_from_slice(bytes) - }); - } -} - -pub struct ViewExceptWriter<'a> { - layer: &'a mut EntityLayer, - pos: ChunkPos, - except: Entity, -} - -impl WritePacket for ViewExceptWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_local( - LocalMsg::PacketAtExcept { - pos: self.pos, - except: self.except, - }, - |b| PacketWriter::new(b, self.layer.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_local_infallible( - LocalMsg::PacketAtExcept { - pos: self.pos, - except: self.except, - }, - |b| b.extend_from_slice(bytes), - ); - } -} - -pub struct RadiusWriter<'a> { - layer: &'a mut EntityLayer, - center: BlockPos, - radius_squared: u32, -} - -impl WritePacket for RadiusWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_local( - LocalMsg::RadiusAt { - center: self.center, - radius_squared: self.radius_squared, - }, - |b| PacketWriter::new(b, self.layer.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_local_infallible( - LocalMsg::RadiusAt { - center: self.center, - radius_squared: self.radius_squared, - }, - |b| b.extend_from_slice(bytes), - ); - } -} - -pub struct RadiusExceptWriter<'a> { - layer: &'a mut EntityLayer, - center: BlockPos, - radius_squared: u32, - except: Entity, -} - -impl WritePacket for RadiusExceptWriter<'_> { - fn write_packet_fallible

(&mut self, packet: &P) -> anyhow::Result<()> - where - P: Packet + Encode, - { - self.layer.messages.send_local( - LocalMsg::RadiusAtExcept { - center: self.center, - radius_squared: self.radius_squared, - except: self.except, - }, - |b| PacketWriter::new(b, self.layer.threshold).write_packet_fallible(packet), - ) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.layer.messages.send_local_infallible( - LocalMsg::RadiusAtExcept { - center: self.center, - radius_squared: self.radius_squared, - except: self.except, - }, - |b| b.extend_from_slice(bytes), - ); - } -} - -pub(super) fn build(app: &mut App) { - app.add_systems( - PostUpdate, - ( - ( - change_entity_positions, - send_entity_update_messages, - send_layer_despawn_messages, - ready_entity_layers, - ) - .chain() - .in_set(UpdateLayersPreClientSet), - unready_entity_layers.in_set(UpdateLayersPostClientSet), - ), - ); -} - -fn change_entity_positions( - entities: Query< - ( - Entity, - &EntityId, - &Position, - &OldPosition, - &EntityLayerId, - &OldEntityLayerId, - Has, - ), - Or<(Changed, Changed, With)>, - >, - mut layers: Query<&mut EntityLayer>, -) { - for (entity, entity_id, pos, old_pos, layer_id, old_layer_id, despawned) in &entities { - let chunk_pos = ChunkPos::from(pos.0); - let old_chunk_pos = ChunkPos::from(old_pos.get()); - - if despawned { - // Entity was deleted. Remove it from the layer. - - if let Ok(old_layer) = layers.get_mut(layer_id.0) { - let old_layer = old_layer.into_inner(); - - if let Entry::Occupied(mut old_cell) = old_layer.entities.entry(old_chunk_pos) { - if old_cell.get_mut().remove(&entity) { - old_layer.messages.send_local_infallible( - LocalMsg::DespawnEntity { - pos: old_chunk_pos, - dest_layer: Entity::PLACEHOLDER, - }, - |b| b.extend_from_slice(&entity_id.get().to_ne_bytes()), - ); - - if old_cell.get().is_empty() { - old_cell.remove(); - } - } - } - } - } else if old_layer_id != layer_id { - // Entity changed their layer. Remove it from old layer and insert it in the new - // layer. - - if let Ok(old_layer) = layers.get_mut(old_layer_id.get()) { - let old_layer = old_layer.into_inner(); - - if let Entry::Occupied(mut old_cell) = old_layer.entities.entry(old_chunk_pos) { - if old_cell.get_mut().remove(&entity) { - old_layer.messages.send_local_infallible( - LocalMsg::DespawnEntity { - pos: old_chunk_pos, - dest_layer: layer_id.0, - }, - |b| b.extend_from_slice(&entity_id.get().to_ne_bytes()), - ); - - if old_cell.get().is_empty() { - old_cell.remove(); - } - } - } - } - - if let Ok(mut layer) = layers.get_mut(layer_id.0) { - if layer.entities.entry(chunk_pos).or_default().insert(entity) { - layer.messages.send_local_infallible( - LocalMsg::SpawnEntity { - pos: chunk_pos, - src_layer: old_layer_id.get(), - }, - |b| b.extend_from_slice(&entity.to_bits().to_ne_bytes()), - ); - } - } - } else if chunk_pos != old_chunk_pos { - // Entity changed their chunk position without changing layers. Remove it from - // old cell and insert it in the new cell. - - if let Ok(mut layer) = layers.get_mut(layer_id.0) { - if let Entry::Occupied(mut old_cell) = layer.entities.entry(old_chunk_pos) { - if old_cell.get_mut().remove(&entity) { - layer.messages.send_local_infallible( - LocalMsg::DespawnEntityTransition { - pos: old_chunk_pos, - dest_pos: chunk_pos, - }, - |b| b.extend_from_slice(&entity_id.get().to_ne_bytes()), - ); - } - } - - if layer.entities.entry(chunk_pos).or_default().insert(entity) { - layer.messages.send_local_infallible( - LocalMsg::SpawnEntityTransition { - pos: chunk_pos, - src_pos: old_chunk_pos, - }, - |b| b.extend_from_slice(&entity.to_bits().to_ne_bytes()), - ); - } - } - } - } -} - -fn send_entity_update_messages( - entities: Query<(Entity, UpdateEntityQuery, Has), Without>, - mut layers: Query<&mut EntityLayer>, -) { - for layer in layers.iter_mut() { - let layer = layer.into_inner(); - - for cell in layer.entities.values_mut() { - for &entity in cell.iter() { - if let Ok((entity, update, is_client)) = entities.get(entity) { - let chunk_pos = ChunkPos::from(update.pos.0); - - // Send the update packets to all viewers. If the entity being updated is a - // client, then we need to be careful to exclude the client itself from - // receiving the update packets. - let msg = if is_client { - LocalMsg::PacketAtExcept { - pos: chunk_pos, - except: entity, - } - } else { - LocalMsg::PacketAt { pos: chunk_pos } - }; - - layer.messages.send_local_infallible(msg, |b| { - update.write_update_packets(PacketWriter::new(b, layer.threshold)) - }); - } else { - panic!( - "Entity {entity:?} was not properly removed from entity layer. Did you \ - forget to use the `Despawned` component?" - ); - } - } - } - } -} - -fn send_layer_despawn_messages(mut layers: Query<&mut EntityLayer, With>) { - for mut layer in &mut layers { - layer - .messages - .send_global_infallible(GlobalMsg::DespawnLayer, |_| {}); - } -} - -fn ready_entity_layers(mut layers: Query<&mut EntityLayer>) { - for mut layer in &mut layers { - layer.messages.ready(); - } -} - -fn unready_entity_layers(mut layers: Query<&mut EntityLayer>) { - for mut layer in &mut layers { - layer.messages.unready(); - } -} diff --git a/crates/valence_server/src/layer_old/message.rs b/crates/valence_server/src/layer_old/message.rs deleted file mode 100644 index 15bc6838c..000000000 --- a/crates/valence_server/src/layer_old/message.rs +++ /dev/null @@ -1,323 +0,0 @@ -use core::fmt; -use std::convert::Infallible; -use std::ops::Range; - -use valence_protocol::ChunkPos; - -use crate::layer_old::bvh::{ChunkBvh, GetChunkPos}; -use crate::ChunkView; - -/// A message buffer of global messages (`G`) and local messages (`L`) meant for -/// consumption by clients. Local messages are those that have some spatial -/// component to them and implement the [`GetChunkPos`] trait. Local messages -/// are placed in a bounding volume hierarchy for fast queries via -/// [`Self::query_local`]. Global messages do not necessarily have a spatial -/// component and all globals will be visited when using [`Self::iter_global`]. -/// -/// Every message is associated with an arbitrary span of bytes. The meaning of -/// the bytes is whatever the message needs it to be. -/// -/// At the end of the tick and before clients have access to the buffer, all -/// messages are sorted and then deduplicated by concatenating byte spans -/// together. This is done for a couple of reasons: -/// - Messages may rely on sorted message order for correctness, like in the -/// case of entity spawn & despawn messages. Sorting also makes deduplication -/// easy. -/// - Deduplication reduces the total number of messages that all clients must -/// examine. Consider the case of a message such as "send all clients in view -/// of this chunk position these packet bytes". If two of these messages have -/// the same chunk position, then they can just be combined together. -pub struct Messages { - global: Vec<(G, Range)>, - local: Vec<(L, Range)>, - bvh: ChunkBvh>, - staging: Vec, - ready: Vec, - is_ready: bool, -} - -impl Messages -where - G: Clone + Ord, - L: Clone + Ord + GetChunkPos, -{ - pub(crate) fn new() -> Self { - Self::default() - } - - /// Adds a global message to this message buffer. - pub(crate) fn send_global( - &mut self, - msg: G, - f: impl FnOnce(&mut Vec) -> Result<(), E>, - ) -> Result<(), E> { - debug_assert!(!self.is_ready); - - let start = self.staging.len(); - f(&mut self.staging)?; - let end = self.staging.len(); - - if let Some((m, range)) = self.global.last_mut() { - if msg == *m { - // Extend the existing message. - range.end = end as u32; - return Ok(()); - } - } - - self.global.push((msg, start as u32..end as u32)); - - Ok(()) - } - - /// Adds a local message to this message buffer. - pub(crate) fn send_local( - &mut self, - msg: L, - f: impl FnOnce(&mut Vec) -> Result<(), E>, - ) -> Result<(), E> { - debug_assert!(!self.is_ready); - - let start = self.staging.len(); - f(&mut self.staging)?; - let end = self.staging.len(); - - if let Some((m, range)) = self.local.last_mut() { - if msg == *m { - // Extend the existing message. - range.end = end as u32; - return Ok(()); - } - } - - self.local.push((msg, start as u32..end as u32)); - - Ok(()) - } - - pub(crate) fn insert_local(&mut self, msg: L, rev_idx: usize, f: impl FnOnce(&mut Vec) -> Result<(), E>) -> Result<(), E> { - debug_assert!(!self.is_ready); - - let start = self.staging.len(); - f(&mut self.staging)?; - let end = self.staging.len(); - - self.local.insert(self.local.len() - rev_idx, (msg, start as u32..end as u32)); - - Ok(()) - } - - /// Like [`Self::send_global`] but writing bytes cannot fail. - pub(crate) fn send_global_infallible(&mut self, msg: G, f: impl FnOnce(&mut Vec)) { - let _ = self.send_global::(msg, |b| { - f(b); - Ok(()) - }); - } - - /// Like [`Self::send_local`] but writing bytes cannot fail. - pub(crate) fn send_local_infallible(&mut self, msg: L, f: impl FnOnce(&mut Vec)) { - let _ = self.send_local::(msg, |b| { - f(b); - Ok(()) - }); - } - - /// Readies messages to be read by clients. - pub(crate) fn ready(&mut self) { - debug_assert!(!self.is_ready); - self.is_ready = true; - - debug_assert!(self.ready.is_empty()); - - self.ready.reserve_exact(self.staging.len()); - - fn sort_and_merge( - msgs: &mut Vec<(M, Range)>, - staging: &[u8], - ready: &mut Vec, - ) { - // Sort must be stable. - msgs.sort_by_key(|(msg, _)| msg.clone()); - - // Make sure the first element is already copied to "ready". - if let Some((_, range)) = msgs.first_mut() { - let start = ready.len(); - ready.extend_from_slice(&staging[range.start as usize..range.end as usize]); - let end = ready.len(); - - *range = start as u32..end as u32; - } - - msgs.dedup_by(|(right_msg, right_range), (left_msg, left_range)| { - if *left_msg == *right_msg { - // Extend the left element with the right element. Then delete the right - // element. - - let right_bytes = - &staging[right_range.start as usize..right_range.end as usize]; - - ready.extend_from_slice(right_bytes); - - left_range.end += right_bytes.len() as u32; - - true - } else { - // Copy right element to "ready". - - let right_bytes = - &staging[right_range.start as usize..right_range.end as usize]; - - let start = ready.len(); - ready.extend_from_slice(right_bytes); - let end = ready.len(); - - *right_range = start as u32..end as u32; - - false - } - }); - } - - sort_and_merge(&mut self.global, &self.staging, &mut self.ready); - sort_and_merge(&mut self.local, &self.staging, &mut self.ready); - - self.bvh.build( - self.local - .iter() - .cloned() - .map(|(msg, range)| MessagePair { msg, range }), - ); - } - - pub(crate) fn unready(&mut self) { - assert!(self.is_ready); - self.is_ready = false; - - self.local.clear(); - self.global.clear(); - self.staging.clear(); - self.ready.clear(); - } - - pub(crate) fn shrink_to_fit(&mut self) { - self.global.shrink_to_fit(); - self.local.shrink_to_fit(); - self.bvh.shrink_to_fit(); - self.staging.shrink_to_fit(); - self.ready.shrink_to_fit(); - } - - /// All message bytes. Use this in conjunction with [`Self::iter_global`] - /// and [`Self::query_local`]. - pub fn bytes(&self) -> &[u8] { - debug_assert!(self.is_ready); - - &self.ready - } - - /// Returns an iterator over all global messages and their span of bytes in - /// [`Self::bytes`]. - pub fn iter_global(&self) -> impl Iterator)> + '_ { - debug_assert!(self.is_ready); - - self.global - .iter() - .map(|(m, r)| (m.clone(), r.start as usize..r.end as usize)) - } - - /// Takes a visitor function `f` and visits all local messages contained - /// within the chunk view `view`. `f` is called with the local - /// message and its span of bytes in [`Self::bytes`]. - pub fn query_local(&self, view: ChunkView, mut f: impl FnMut(L, Range)) { - debug_assert!(self.is_ready); - - self.bvh.query(view, |pair| { - f( - pair.msg.clone(), - pair.range.start as usize..pair.range.end as usize, - ) - }); - } -} - -impl Default for Messages { - fn default() -> Self { - Self { - global: Default::default(), - local: Default::default(), - bvh: Default::default(), - staging: Default::default(), - ready: Default::default(), - is_ready: Default::default(), - } - } -} - -impl fmt::Debug for Messages -where - G: fmt::Debug, - L: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Messages") - .field("global", &self.global) - .field("local", &self.local) - .field("is_ready", &self.is_ready) - .finish() - } -} - -#[derive(Debug)] -struct MessagePair { - msg: M, - range: Range, -} - -impl GetChunkPos for MessagePair { - fn chunk_pos(&self) -> ChunkPos { - self.msg.chunk_pos() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] - struct DummyLocal; - - impl GetChunkPos for DummyLocal { - fn chunk_pos(&self) -> ChunkPos { - unimplemented!() - } - } - - #[test] - fn send_global_message() { - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] - enum TestMsg { - Foo, - Bar, - } - - let mut messages = Messages::::new(); - - messages.send_global_infallible(TestMsg::Foo, |b| b.extend_from_slice(&[1, 2, 3])); - messages.send_global_infallible(TestMsg::Bar, |b| b.extend_from_slice(&[4, 5, 6])); - messages.send_global_infallible(TestMsg::Foo, |b| b.extend_from_slice(&[7, 8, 9])); - - messages.ready(); - - let bytes = messages.bytes(); - - for (msg, range) in messages.iter_global() { - match msg { - TestMsg::Foo => assert_eq!(&bytes[range.clone()], &[1, 2, 3, 7, 8, 9]), - TestMsg::Bar => assert_eq!(&bytes[range.clone()], &[4, 5, 6]), - } - } - - messages.unready(); - } -} diff --git a/examples/advancement.rs b/examples/advancement.rs index ac91b1957..b56e641b8 100644 --- a/examples/advancement.rs +++ b/examples/advancement.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use valence::advancement::bevy_hierarchy::{BuildChildren, Children, Parent}; use valence::advancement::ForceTabUpdate; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; #[derive(Component)] struct RootCriteria; @@ -47,17 +48,19 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in -25..25 { for x in -25..25 { - layer.chunk.set_block([x, 64, z], BlockState::GRASS_BLOCK); + layer + .chunk_index + .set_block([x, 64, z], BlockState::GRASS_BLOCK); } } @@ -167,29 +170,20 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.5, 65.0, 0.5]); *game_mode = GameMode::Creative; } diff --git a/examples/anvil_loading.rs b/examples/anvil_loading.rs index 9f7a8cece..07955412a 100644 --- a/examples/anvil_loading.rs +++ b/examples/anvil_loading.rs @@ -7,6 +7,7 @@ use valence::abilities::{FlyingSpeed, FovModifier, PlayerAbilitiesFlags}; use valence::message::SendMessage; use valence::prelude::*; use valence_anvil::{AnvilLevel, ChunkLoadEvent, ChunkLoadStatus}; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_POS: DVec3 = DVec3::new(0.0, 256.0, 0.0); @@ -40,7 +41,6 @@ pub fn main() { .add_systems( Update, ( - despawn_disconnected_clients, (init_clients, handle_chunk_loads).chain(), display_loaded_chunk_count, ), @@ -55,7 +55,7 @@ fn setup( server: Res, cli: Res, ) { - let layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); let mut level = AnvilLevel::new(&cli.path, &biomes); // Force a 16x16 area of chunks around the origin to be loaded at all times. @@ -76,9 +76,8 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, &mut PlayerAbilitiesFlags, @@ -87,12 +86,11 @@ fn init_clients( ), Added, >, - layers: Query>, + layers: Query>, ) { for ( mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, + mut visible_layers, mut pos, mut game_mode, mut abilities, @@ -103,8 +101,7 @@ fn init_clients( let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set(SPAWN_POS); *game_mode = GameMode::Adventure; abilities.set_allow_flying(true); diff --git a/examples/biomes.rs b/examples/biomes.rs index 730131cf4..2bb4b1de5 100644 --- a/examples/biomes.rs +++ b/examples/biomes.rs @@ -4,6 +4,7 @@ use rand::seq::IteratorRandom; use rand::Rng; use valence::prelude::*; use valence::registry::biome::BiomeEffects; +use valence_server::dimension_layer::DimensionInfo; use valence_server::BiomePos; const SPAWN_Y: i32 = 0; @@ -13,10 +14,7 @@ pub fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems( - Update, - (init_clients, despawn_disconnected_clients, set_biomes), - ) + .add_systems(Update, (init_clients, set_biomes)) .run(); } @@ -49,11 +47,11 @@ fn setup( biomes.insert(name, biome); } - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -SIZE..SIZE { for x in -SIZE..SIZE { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } @@ -90,29 +88,20 @@ fn set_biomes(mut layers: Query<&mut ChunkLayer>, biomes: Res) { fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); *game_mode = GameMode::Creative; } diff --git a/examples/block_entities.rs b/examples/block_entities.rs index 5887ec58b..67876bccd 100644 --- a/examples/block_entities.rs +++ b/examples/block_entities.rs @@ -4,6 +4,7 @@ use valence::interact_block::InteractBlockEvent; use valence::message::ChatMessageEvent; use valence::nbt::{compound, List}; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; const FLOOR_Y: i32 = 64; const SIGN_POS: [i32; 3] = [3, FLOOR_Y + 1, 2]; @@ -13,10 +14,7 @@ pub fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems( - Update, - (event_handler, init_clients, despawn_disconnected_clients), - ) + .add_systems(Update, (event_handler, init_clients)) .run(); } @@ -26,11 +24,11 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } @@ -42,12 +40,12 @@ fn setup( } } - layer.chunk.set_block( + layer.chunk_index.set_block( [3, FLOOR_Y + 1, 1], BlockState::CHEST.set(PropName::Facing, PropValue::West), ); - layer.chunk.set_block( + layer.chunk_index.set_block( SIGN_POS, Block { state: BlockState::OAK_SIGN.set(PropName::Rotation, PropValue::_4), @@ -65,7 +63,7 @@ fn setup( }, ); - layer.chunk.set_block( + layer.chunk_index.set_block( SKULL_POS, BlockState::PLAYER_HEAD.set(PropName::Rotation, PropValue::_12), ); @@ -76,29 +74,20 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, FLOOR_Y as f64 + 1.0, 0.0]); *game_mode = GameMode::Creative; } diff --git a/examples/boss_bar.rs b/examples/boss_bar.rs index 4dcf417d1..6e9527f20 100644 --- a/examples/boss_bar.rs +++ b/examples/boss_bar.rs @@ -6,6 +6,7 @@ use valence_boss_bar::{ BossBarBundle, BossBarColor, BossBarDivision, BossBarFlags, BossBarHealth, BossBarStyle, BossBarTitle, }; +use valence_server::dimension_layer::DimensionInfo; use valence_server::entity::cow::CowEntityBundle; use valence_server::message::ChatMessageEvent; use valence_text::color::NamedColor; @@ -19,10 +20,7 @@ pub fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems( - Update, - (init_clients, despawn_disconnected_clients, listen_messages), - ) + .add_systems(Update, (init_clients, listen_messages)) .run(); } @@ -32,11 +30,11 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } @@ -54,7 +52,7 @@ fn setup( BossBarBundle { title: BossBarTitle("Boss Bar".into_text()), health: BossBarHealth(0.5), - layer: EntityLayerId(layer_id), + layer: LayerId(layer_id), ..Default::default() }, CustomBossBar, @@ -63,7 +61,7 @@ fn setup( commands.spawn(( CowEntityBundle { position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]), - layer: EntityLayerId(layer_id), + layer: LayerId(layer_id), ..Default::default() }, BossBarTitle("Louis XVI".color(NamedColor::Red)), @@ -80,30 +78,21 @@ fn init_clients( mut clients_query: Query< ( &mut Client, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers_query: Query, With)>, + layers_query: Query>, ) { let layer = layers_query.single(); - for ( - mut client, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients_query + for (mut client, mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients_query { layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.5, SPAWN_Y as f64 + 1.0, 0.5]); *game_mode = GameMode::Creative; @@ -144,7 +133,7 @@ fn listen_messages( &mut BossBarFlags, &mut BossBarHealth, &mut BossBarTitle, - &EntityLayerId, + &LayerId, ), With, >, diff --git a/examples/building.rs b/examples/building.rs index 3b3de6516..3f0cdadd2 100644 --- a/examples/building.rs +++ b/examples/building.rs @@ -3,6 +3,7 @@ use valence::interact_block::InteractBlockEvent; use valence::inventory::HeldItem; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_Y: i32 = 64; @@ -14,7 +15,6 @@ pub fn main() { Update, ( init_clients, - despawn_disconnected_clients, toggle_gamemode_on_sneak, digging, place_blocks, @@ -29,11 +29,11 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } @@ -52,30 +52,20 @@ fn init_clients( mut clients: Query< ( &mut Client, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut client, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut client, mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); *game_mode = GameMode::Creative; diff --git a/examples/chest.rs b/examples/chest.rs index d3af60f3c..4f20c7bf6 100644 --- a/examples/chest.rs +++ b/examples/chest.rs @@ -2,6 +2,7 @@ use valence::interact_block::InteractBlockEvent; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_Y: i32 = 64; const CHEST_POS: [i32; 3] = [0, SPAWN_Y + 1, 3]; @@ -10,15 +11,7 @@ pub fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems( - Update, - ( - init_clients, - toggle_gamemode_on_sneak, - open_chest, - despawn_disconnected_clients, - ), - ) + .add_systems(Update, (init_clients, toggle_gamemode_on_sneak, open_chest)) .run(); } @@ -28,11 +21,11 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } @@ -44,7 +37,7 @@ fn setup( } } - layer.chunk.set_block(CHEST_POS, BlockState::CHEST); + layer.chunk_index.set_block(CHEST_POS, BlockState::CHEST); commands.spawn(layer); @@ -59,24 +52,16 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; diff --git a/examples/combat.rs b/examples/combat.rs index 460a2539e..b19b447c9 100644 --- a/examples/combat.rs +++ b/examples/combat.rs @@ -5,6 +5,7 @@ use rand::Rng; use valence::entity::EntityStatuses; use valence::math::Vec3Swizzles; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_Y: i32 = 64; const ARENA_RADIUS: i32 = 32; @@ -22,14 +23,7 @@ pub fn main() { .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(EventLoopUpdate, handle_combat_events) - .add_systems( - Update, - ( - init_clients, - despawn_disconnected_clients, - teleport_oob_clients, - ), - ) + .add_systems(Update, (init_clients, teleport_oob_clients)) .run(); } @@ -39,11 +33,11 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } @@ -65,7 +59,7 @@ fn setup( }; for y in 0..SPAWN_Y { - layer.chunk.set_block([x, y, z], block); + layer.chunk_index.set_block([x, y, z], block); } } } @@ -76,24 +70,16 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; diff --git a/examples/cow_sphere.rs b/examples/cow_sphere.rs index 60dd0af12..43041efc8 100644 --- a/examples/cow_sphere.rs +++ b/examples/cow_sphere.rs @@ -6,6 +6,7 @@ use valence::abilities::{PlayerStartFlyingEvent, PlayerStopFlyingEvent}; use valence::math::{DQuat, EulerRot}; use valence::message::SendMessage; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; use valence_text::color::NamedColor; type SpherePartBundle = valence::entity::cow::CowEntityBundle; @@ -26,15 +27,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems( - Update, - ( - init_clients, - update_sphere, - despawn_disconnected_clients, - display_is_flying, - ), - ) + .add_systems(Update, (init_clients, update_sphere, display_is_flying)) .run(); } @@ -44,22 +37,22 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } - layer.chunk.set_block(SPAWN_POS, BlockState::BEDROCK); + layer.chunk_index.set_block(SPAWN_POS, BlockState::BEDROCK); let layer_id = commands.spawn(layer).id(); commands.spawn_batch([0; SPHERE_AMOUNT].map(|_| { ( SpherePartBundle { - layer: EntityLayerId(layer_id), + layer: LayerId(layer_id), ..Default::default() }, SpherePart, @@ -70,29 +63,20 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([ SPAWN_POS.x as f64 + 0.5, SPAWN_POS.y as f64 + 1.0, diff --git a/examples/ctf.rs b/examples/ctf.rs index 63d41630a..f399d2cad 100644 --- a/examples/ctf.rs +++ b/examples/ctf.rs @@ -17,6 +17,7 @@ use valence::nbt::{compound, List}; use valence::prelude::*; use valence::scoreboard::*; use valence::status::RequestRespawnEvent; +use valence_server::dimension_layer::DimensionInfo; const ARENA_Y: i32 = 64; const ARENA_MID_WIDTH: i32 = 2; @@ -43,7 +44,6 @@ pub fn main() { Update, ( init_clients, - despawn_disconnected_clients, digging, place_blocks, do_team_selector_portals, @@ -65,11 +65,11 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } @@ -80,7 +80,7 @@ fn setup( x if x > ARENA_MID_WIDTH => BlockState::BLUE_CONCRETE, _ => BlockState::WHITE_CONCRETE, }; - layer.chunk.set_block([x, ARENA_Y, z], block); + layer.chunk_index.set_block([x, ARENA_Y, z], block); } } @@ -111,7 +111,7 @@ fn setup( let ctf_objective = ObjectiveBundle { name: Objective::new("ctf-captures"), display: ObjectiveDisplay("Captures".into_text()), - layer: EntityLayerId(ctf_objective_layer), + layer: LayerId(ctf_objective_layer), ..Default::default() }; commands.spawn(ctf_objective); @@ -142,7 +142,7 @@ fn setup( let mut flags = Flags::default(); flags.set_glowing(true); let mut pig = commands.spawn(PigEntityBundle { - layer: EntityLayerId(ctf_team_layers.friendly_layers[&Team::Red]), + layer: LayerId(ctf_team_layers.friendly_layers[&Team::Red]), position: Position([-30.0, 65.0, 2.0].into()), entity_flags: flags.clone(), ..Default::default() @@ -150,7 +150,7 @@ fn setup( pig.insert(Team::Red); let mut cow = commands.spawn(CowEntityBundle { - layer: EntityLayerId(ctf_team_layers.friendly_layers[&Team::Blue]), + layer: LayerId(ctf_team_layers.friendly_layers[&Team::Blue]), position: Position([30.0, 65.0, 2.0].into()), entity_flags: flags, ..Default::default() @@ -170,11 +170,11 @@ fn build_flag(layer: &mut LayerBundle, team: Team, pos: impl Into) -> // build the flag pole for _ in 0..3 { - layer.chunk.set_block(pos, BlockState::OAK_FENCE); + layer.chunk_index.set_block(pos, BlockState::OAK_FENCE); pos.y += 1; } let moving_east = pos.x < 0; - layer.chunk.set_block( + layer.chunk_index.set_block( pos, BlockState::OAK_FENCE.set( if moving_east { @@ -186,14 +186,14 @@ fn build_flag(layer: &mut LayerBundle, team: Team, pos: impl Into) -> ), ); pos.x += if pos.x < 0 { 1 } else { -1 }; - layer.chunk.set_block( + layer.chunk_index.set_block( pos, BlockState::OAK_FENCE .set(PropName::East, PropValue::True) .set(PropName::West, PropValue::True), ); pos.x += if pos.x < 0 { 1 } else { -1 }; - layer.chunk.set_block( + layer.chunk_index.set_block( pos, BlockState::OAK_FENCE.set( if moving_east { @@ -207,7 +207,7 @@ fn build_flag(layer: &mut LayerBundle, team: Team, pos: impl Into) -> pos.y -= 1; // build the flag - layer.chunk.set_block( + layer.chunk_index.set_block( pos, match team { Team::Red => BlockState::RED_WOOL, @@ -229,7 +229,7 @@ fn build_spawn_box(layer: &mut LayerBundle, pos: impl Into, commands: layer .chunk .set_block([pos.x + x, pos.y, pos.z + z], spawn_box_block); - layer.chunk.set_block( + layer.chunk_index.set_block( [pos.x + x, pos.y + SPAWN_BOX_HEIGHT, pos.z + z], spawn_box_block, ); @@ -270,7 +270,7 @@ fn build_spawn_box(layer: &mut LayerBundle, pos: impl Into, commands: ] { for z in 0..3 { for x in 0..3 { - layer.chunk.set_block( + layer.chunk_index.set_block( [pos.x + offset.x + x, pos.y + offset.y, pos.z + offset.z + z], block, ); @@ -296,7 +296,7 @@ fn build_spawn_box(layer: &mut LayerBundle, pos: impl Into, commands: for area in portals.portals.values() { for pos in area.iter_block_pos() { - layer.chunk.set_block(pos, BlockState::AIR); + layer.chunk_index.set_block(pos, BlockState::AIR); } layer .chunk @@ -308,7 +308,7 @@ fn build_spawn_box(layer: &mut LayerBundle, pos: impl Into, commands: // build instruction signs let sign_pos = pos.offset(0, 2, SPAWN_BOX_WIDTH - 1); - layer.chunk.set_block( + layer.chunk_index.set_block( sign_pos, Block { state: BlockState::OAK_WALL_SIGN.set(PropName::Rotation, PropValue::_3), @@ -325,7 +325,7 @@ fn build_spawn_box(layer: &mut LayerBundle, pos: impl Into, commands: }, ); - layer.chunk.set_block( + layer.chunk_index.set_block( sign_pos.offset(-1, 0, 0), Block { state: BlockState::OAK_WALL_SIGN.set(PropName::Rotation, PropValue::_3), @@ -342,7 +342,7 @@ fn build_spawn_box(layer: &mut LayerBundle, pos: impl Into, commands: }, ); - layer.chunk.set_block( + layer.chunk_index.set_block( sign_pos.offset(1, 0, 0), Block { state: BlockState::OAK_WALL_SIGN.set(PropName::Rotation, PropValue::_3), @@ -364,34 +364,25 @@ fn init_clients( mut clients: Query< ( &mut Client, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, &mut Health, ), Added, >, - main_layers: Query, With)>, + main_layers: Query>, globals: Res, ) { - for ( - mut client, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - mut health, - ) in &mut clients + for (mut client, mut layer_id, mut visible_layers, mut pos, mut game_mode, mut health) in + &mut clients { let layer = main_layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); - visible_entity_layers.0.insert(globals.scoreboard_layer); + visible_layers.0.insert(layer); + visible_layers.0.insert(globals.scoreboard_layer); pos.set(SPAWN_POS); *game_mode = GameMode::Adventure; health.0 = PLAYER_MAX_HEALTH; @@ -567,7 +558,7 @@ fn do_team_selector_portals( portals: Res, mut commands: Commands, ctf_layers: Res, - main_layers: Query, With)>, + main_layers: Query>, ) { for player in players.iter_mut() { let ( @@ -640,7 +631,7 @@ fn do_team_selector_portals( let mut flags = Flags::default(); flags.set_glowing(true); let mut player_glowing = commands.spawn(PlayerEntityBundle { - layer: EntityLayerId(friendly_layer), + layer: LayerId(friendly_layer), uuid: *unique_id, entity_flags: flags, position: *pos, @@ -650,7 +641,7 @@ fn do_team_selector_portals( let enemy_layer = ctf_layers.enemy_layers[&team]; let mut player_enemy = commands.spawn(PlayerEntityBundle { - layer: EntityLayerId(enemy_layer), + layer: LayerId(enemy_layer), uuid: *unique_id, position: *pos, ..Default::default() @@ -1013,17 +1004,12 @@ fn teleport_oob_clients(mut clients: Query<(&mut Position, &Team), With> /// Handles respawning dead players. fn necromancy( - mut clients: Query<( - &mut VisibleChunkLayer, - &mut RespawnPosition, - &Team, - &mut Health, - )>, + mut clients: Query<(&mut VisibleLayers, &mut RespawnPosition, &Team, &mut Health)>, mut events: EventReader, - layers: Query, With)>, + layers: Query>, ) { for event in events.iter() { - if let Ok((mut visible_chunk_layer, mut respawn_pos, team, mut health)) = + if let Ok((mut visible_layers, mut respawn_pos, team, mut health)) = clients.get_mut(event.client) { respawn_pos.pos = team.spawn_pos().into(); diff --git a/examples/death.rs b/examples/death.rs index a3e8b6315..c5feb0297 100644 --- a/examples/death.rs +++ b/examples/death.rs @@ -2,6 +2,7 @@ use valence::prelude::*; use valence::status::RequestRespawnEvent; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_Y: i32 = 64; @@ -9,15 +10,7 @@ pub fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems( - Update, - ( - init_clients, - squat_and_die, - necromancy, - despawn_disconnected_clients, - ), - ) + .add_systems(Update, (init_clients, squat_and_die, necromancy)) .run(); } @@ -32,17 +25,17 @@ fn setup( BlockState::DEEPSLATE, BlockState::MAGMA_BLOCK, ] { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in -25..25 { for x in -25..25 { - layer.chunk.set_block([x, SPAWN_Y, z], block); + layer.chunk_index.set_block([x, SPAWN_Y, z], block); } } @@ -54,30 +47,20 @@ fn init_clients( mut clients: Query< ( &mut Client, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut client, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut client, mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.iter().next().unwrap(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); *game_mode = GameMode::Creative; @@ -99,21 +82,17 @@ fn squat_and_die(mut clients: Query<&mut Client>, mut events: EventReader, mut events: EventReader, - layers: Query, With)>, + layers: Query>, ) { for event in events.iter() { - if let Ok(( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut respawn_pos, - )) = clients.get_mut(event.client) + if let Ok((mut layer_id, mut visible_layers, mut respawn_pos)) = + clients.get_mut(event.client) { respawn_pos.pos = BlockPos::new(0, SPAWN_Y, 0); diff --git a/examples/entity_hitbox.rs b/examples/entity_hitbox.rs index 083b80026..93fe9f2b3 100644 --- a/examples/entity_hitbox.rs +++ b/examples/entity_hitbox.rs @@ -14,6 +14,7 @@ use valence::entity::zombie_horse::ZombieHorseEntityBundle; use valence::entity::{entity, Pose}; use valence::prelude::*; use valence::rand::Rng; +use valence_server::dimension_layer::DimensionInfo; pub fn main() { App::new() @@ -29,17 +30,19 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in -25..25 { for x in -25..25 { - layer.chunk.set_block([x, 64, z], BlockState::GRASS_BLOCK); + layer + .chunk_index + .set_block([x, 64, z], BlockState::GRASS_BLOCK); } } @@ -50,30 +53,20 @@ fn init_clients( mut clients: Query< ( &mut Client, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut client, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut client, mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, 65.0, 0.0]); *game_mode = GameMode::Creative; @@ -84,7 +77,7 @@ fn init_clients( fn spawn_entity( mut commands: Commands, mut sneaking: EventReader, - client_query: Query<(&Position, &EntityLayerId)>, + client_query: Query<(&Position, &LayerId)>, ) { for sneaking in sneaking.iter() { if sneaking.state == SneakState::Start { diff --git a/examples/game_of_life.rs b/examples/game_of_life.rs index 421884797..c8559dfad 100644 --- a/examples/game_of_life.rs +++ b/examples/game_of_life.rs @@ -3,6 +3,7 @@ use std::mem; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; const BOARD_MIN_X: i32 = -30; const BOARD_MAX_X: i32 = 30; @@ -27,7 +28,6 @@ pub fn main() { Update, ( init_clients, - despawn_disconnected_clients, toggle_cell_on_dig, update_board, pause_on_crouch, @@ -47,17 +47,19 @@ fn setup( biome.effects.grass_color = Some(0x00ff00); } - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -10..10 { for x in -10..10 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in BOARD_MIN_Z..=BOARD_MAX_Z { for x in BOARD_MIN_X..=BOARD_MAX_X { - layer.chunk.set_block([x, BOARD_Y, z], BlockState::DIRT); + layer + .chunk_index + .set_block([x, BOARD_Y, z], BlockState::DIRT); } } @@ -74,30 +76,20 @@ fn init_clients( mut clients: Query< ( &mut Client, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut client, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut client, mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, 65.0, 0.0]); *game_mode = GameMode::Survival; diff --git a/examples/parkour.rs b/examples/parkour.rs index 806d1cdd7..58b60669d 100644 --- a/examples/parkour.rs +++ b/examples/parkour.rs @@ -32,7 +32,6 @@ pub fn main() { reset_clients.after(init_clients), manage_chunks.after(reset_clients).before(manage_blocks), manage_blocks, - despawn_disconnected_clients, ), ) .run(); @@ -52,7 +51,7 @@ fn init_clients( ( Entity, &mut Client, - &mut VisibleChunkLayer, + &mut VisibleLayers, &mut IsFlat, &mut GameMode, ), diff --git a/examples/particles.rs b/examples/particles.rs index 5144531d0..0e4663a36 100644 --- a/examples/particles.rs +++ b/examples/particles.rs @@ -3,6 +3,7 @@ use std::fmt; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_Y: i32 = 64; @@ -10,10 +11,7 @@ pub fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems( - Update, - (init_clients, despawn_disconnected_clients, manage_particles), - ) + .add_systems(Update, (init_clients, manage_particles)) .run(); } @@ -26,15 +24,17 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } - layer.chunk.set_block([0, SPAWN_Y, 0], BlockState::BEDROCK); + layer + .chunk_index + .set_block([0, SPAWN_Y, 0], BlockState::BEDROCK); commands.spawn(layer); @@ -44,29 +44,20 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); *game_mode = GameMode::Creative; } diff --git a/examples/player_list.rs b/examples/player_list.rs index 96c3c6564..3e3ff0b9c 100644 --- a/examples/player_list.rs +++ b/examples/player_list.rs @@ -4,6 +4,7 @@ use rand::Rng; use valence::keepalive::Ping; use valence::player_list::{DisplayName, PlayerListEntryBundle}; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_Y: i32 = 64; const PLAYER_UUID_1: Uuid = Uuid::from_u128(1); @@ -15,12 +16,7 @@ fn main() { .add_systems(Startup, setup) .add_systems( Update, - ( - init_clients, - override_display_name, - update_player_list, - despawn_disconnected_clients, - ), + (init_clients, override_display_name, update_player_list), ) .run(); } @@ -31,11 +27,11 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } @@ -60,30 +56,20 @@ fn init_clients( mut clients: Query< ( &mut Client, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut client, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut client, mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); *game_mode = GameMode::Creative; diff --git a/examples/resource_pack.rs b/examples/resource_pack.rs index f8094952a..02fc8fff4 100644 --- a/examples/resource_pack.rs +++ b/examples/resource_pack.rs @@ -5,6 +5,7 @@ use valence::message::SendMessage; use valence::prelude::*; use valence::protocol::packets::play::ResourcePackStatusC2s; use valence::resource_pack::ResourcePackStatusEvent; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_Y: i32 = 64; @@ -14,12 +15,7 @@ pub fn main() { .add_systems(Startup, setup) .add_systems( Update, - ( - init_clients, - prompt_on_punch, - on_resource_pack_status, - despawn_disconnected_clients, - ), + (init_clients, prompt_on_punch, on_resource_pack_status), ) .run(); } @@ -30,24 +26,26 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in -25..25 { for x in -25..25 { - layer.chunk.set_block([x, SPAWN_Y, z], BlockState::BEDROCK); + layer + .chunk_index + .set_block([x, SPAWN_Y, z], BlockState::BEDROCK); } } let layer_ent = commands.spawn(layer).id(); commands.spawn(SheepEntityBundle { - layer: EntityLayerId(layer_ent), + layer: LayerId(layer_ent), position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 2.0]), look: Look::new(180.0, 0.0), head_yaw: HeadYaw(180.0), @@ -59,30 +57,20 @@ fn init_clients( mut clients: Query< ( &mut Client, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut client, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut client, mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); *game_mode = GameMode::Creative; diff --git a/examples/terrain.rs b/examples/terrain.rs index 5dfb0ab3f..40ca52f4b 100644 --- a/examples/terrain.rs +++ b/examples/terrain.rs @@ -11,6 +11,7 @@ use noise::{NoiseFn, SuperSimplex}; use tracing::info; use valence::prelude::*; use valence::spawn::IsFlat; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_POS: DVec3 = DVec3::new(0.0, 200.0, 0.0); const HEIGHT: u32 = 384; @@ -45,16 +46,13 @@ pub fn main() { .add_systems(Startup, setup) .add_systems( Update, - ( - ( - init_clients, - remove_unviewed_chunks, - update_client_views, - send_recv_chunks, - ) - .chain(), - despawn_disconnected_clients, - ), + (( + init_clients, + remove_unviewed_chunks, + update_client_views, + send_recv_chunks, + ) + .chain(),), ) .run(); } @@ -105,7 +103,7 @@ fn setup( receiver: finished_receiver, }); - let layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); commands.spawn(layer); } @@ -113,31 +111,21 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, &mut IsFlat, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - mut is_flat, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode, mut is_flat) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set(SPAWN_POS); *game_mode = GameMode::Creative; is_flat.0 = true; diff --git a/examples/text.rs b/examples/text.rs index 0fcc76a0e..4d848dcc9 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -2,6 +2,7 @@ use valence::lang::keys; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; const SPAWN_Y: i32 = 64; @@ -9,7 +10,7 @@ pub fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems(Update, (init_clients, despawn_disconnected_clients)) + .add_systems(Update, init_clients) .run(); } @@ -19,18 +20,18 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in -25..25 { for x in -25..25 { layer - .chunk + .chunk_index .set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK); } } @@ -43,30 +44,20 @@ fn init_clients( ( &mut Client, &mut Position, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut client, - mut pos, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut game_mode, - ) in &mut clients - { + for (mut client, mut pos, mut layer_id, mut visible_layers, mut game_mode) in &mut clients { let layer = layers.single(); pos.0 = [0.0, SPAWN_Y as f64 + 1.0, 0.0].into(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.insert(layer); *game_mode = GameMode::Creative; client.send_chat_message("Welcome to the text example.".bold()); diff --git a/examples/weather.rs b/examples/weather.rs index 6fdbb07c2..8c3fbd723 100644 --- a/examples/weather.rs +++ b/examples/weather.rs @@ -2,15 +2,13 @@ use std::f64::consts::TAU; use valence::prelude::*; use valence::weather::{Rain, Thunder, WeatherBundle}; +use valence_server::dimension_layer::DimensionInfo; pub fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems( - Update, - (init_clients, despawn_disconnected_clients, change_weather), - ) + .add_systems(Update, (init_clients, change_weather)) .run(); } @@ -20,17 +18,19 @@ fn setup( dimensions: Res, biomes: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in -25..25 { for x in -25..25 { - layer.chunk.set_block([x, 64, z], BlockState::GRASS_BLOCK); + layer + .chunk_index + .set_block([x, 64, z], BlockState::GRASS_BLOCK); } } @@ -40,36 +40,27 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.insert(layer); pos.set([0.0, 65.0, 0.0]); *game_mode = GameMode::Creative; } } fn change_weather( - mut layers: Query<(&mut Rain, &mut Thunder), With>, + mut layers: Query<(&mut Rain, &mut Thunder), With>, server: Res, ) { let period = 5.0; diff --git a/examples/world_border.rs b/examples/world_border.rs index ad42166a9..471ee4537 100644 --- a/examples/world_border.rs +++ b/examples/world_border.rs @@ -1,11 +1,12 @@ #![allow(clippy::type_complexity)] use bevy_app::App; -use valence::client::despawn_disconnected_clients; use valence::inventory::HeldItem; use valence::message::{ChatMessageEvent, SendMessage}; use valence::prelude::*; use valence::world_border::*; +use valence_server::dimension_layer::DimensionInfo; +use valence_server::layer::message::LayerMessages; const SPAWN_Y: i32 = 64; @@ -13,15 +14,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems( - Update, - ( - despawn_disconnected_clients, - init_clients, - border_controls, - display_diameter, - ), - ) + .add_systems(Update, (init_clients, border_controls, display_diameter)) .run(); } @@ -31,18 +24,18 @@ fn setup( biomes: Res, dimensions: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], Chunk::new()); + layer.chunk_index.insert([x, z], Chunk::new()); } } for z in -25..25 { for x in -25..25 { layer - .chunk + .chunk_index .set_block([x, SPAWN_Y, z], BlockState::MOSSY_COBBLESTONE); } } @@ -63,32 +56,22 @@ fn init_clients( mut clients: Query< ( &mut Client, - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut Inventory, &HeldItem, ), Added, >, - layers: Query>, + layers: Query>, ) { - for ( - mut client, - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut inv, - main_slot, - ) in &mut clients + for (mut client, mut layer_id, mut visible_layers, mut pos, mut inv, main_slot) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.5, SPAWN_Y as f64 + 1.0, 0.5]); let pickaxe = ItemStack::new(ItemKind::WoodenPickaxe, 1, None); inv.set_slot(main_slot.slot(), pickaxe); @@ -97,17 +80,17 @@ fn init_clients( } } -fn display_diameter(mut layers: Query<(&mut ChunkLayer, &WorldBorderLerp)>) { - for (mut layer, lerp) in &mut layers { +fn display_diameter(mut layers: Query<(&mut LayerMessages, &WorldBorderLerp)>) { + for (mut msgs, lerp) in &mut layers { if lerp.remaining_ticks > 0 { - layer.send_chat_message(format!("diameter = {}", lerp.current_diameter)); + msgs.send_chat_message(format!("diameter = {}", lerp.current_diameter)); } } } fn border_controls( mut events: EventReader, - mut layers: Query<(&mut WorldBorderCenter, &mut WorldBorderLerp), With>, + mut layers: Query<(&mut WorldBorderCenter, &mut WorldBorderLerp), With>, ) { for x in events.iter() { let parts: Vec<&str> = x.message.split(' ').collect(); diff --git a/src/tests/boss_bar.rs b/src/tests/boss_bar.rs index c2b6cf2a6..7790d63b6 100644 --- a/src/tests/boss_bar.rs +++ b/src/tests/boss_bar.rs @@ -3,7 +3,7 @@ use valence_boss_bar::{ BossBarTitle, }; use valence_server::client::VisibleEntityLayers; -use valence_server::entity::EntityLayerId; +use valence_server::entity::LayerId; use valence_server::protocol::packets::play::BossBarS2c; use valence_server::text::IntoText; use valence_server::Despawned; @@ -23,7 +23,7 @@ fn test_initialize_on_join() { .insert(BossBarBundle { title: BossBarTitle("Boss Bar".into_text()), health: BossBarHealth(0.5), - layer: EntityLayerId(scenario.layer), + layer: LayerId(scenario.layer), ..Default::default() }); @@ -183,7 +183,7 @@ fn prepare() -> ScenarioSingleClient { s.app.world.entity_mut(s.layer).insert(BossBarBundle { title: BossBarTitle("Boss Bar".into_text()), health: BossBarHealth(0.5), - layer: EntityLayerId(s.layer), + layer: LayerId(s.layer), ..Default::default() }); diff --git a/src/tests/layer.rs b/src/tests/layer.rs index c203aee6f..6c9cffe68 100644 --- a/src/tests/layer.rs +++ b/src/tests/layer.rs @@ -4,7 +4,7 @@ use bevy_ecs::world::EntityMut; use crate::client::{ViewDistance, VisibleEntityLayers}; use crate::entity::cow::CowEntityBundle; -use crate::entity::{EntityLayerId, Position}; +use crate::entity::Position; use crate::layer::chunk::Chunk; use crate::layer::{ChunkLayer, EntityLayer}; use crate::protocol::packets::play::{ @@ -234,17 +234,17 @@ fn entity_layer_switching() { // Spawn three entities and put them all on the main layer to start. let e1 = CowEntityBundle { - layer: EntityLayerId(l1), + layer: LayerId(l1), ..Default::default() }; let e2 = CowEntityBundle { - layer: EntityLayerId(l1), + layer: LayerId(l1), ..Default::default() }; let e3 = CowEntityBundle { - layer: EntityLayerId(l1), + layer: LayerId(l1), ..Default::default() }; @@ -258,7 +258,7 @@ fn entity_layer_switching() { helper.collect_received().assert_count::(3); // Move e1 to l2 and add l2 to the visible layers set. - app.world.get_mut::(e1).unwrap().0 = l2; + app.world.get_mut::(e1).unwrap().0 = l2; app.world .get_mut::(client_ent) .unwrap() @@ -344,7 +344,7 @@ fn chunk_entity_spawn_despawn() { .world .spawn(CowEntityBundle { position: Position::new([8.0, 0.0, 8.0]), - layer: EntityLayerId(layer_ent), + layer: LayerId(layer_ent), ..Default::default() }) .id(); diff --git a/src/tests/scoreboard.rs b/src/tests/scoreboard.rs index e6a064909..4c10ef11e 100644 --- a/src/tests/scoreboard.rs +++ b/src/tests/scoreboard.rs @@ -1,7 +1,6 @@ use valence_scoreboard::*; use crate::client::VisibleEntityLayers; -use crate::entity::EntityLayerId; use crate::layer_old::EntityLayer; use crate::protocol::packets::play::{ ScoreboardDisplayS2c, ScoreboardObjectiveUpdateS2c, ScoreboardPlayerUpdateS2c, @@ -38,7 +37,7 @@ fn show_scoreboard_when_added_to_layer() { name: Objective::new("foo"), display: ObjectiveDisplay("Foo".into_text()), scores: ObjectiveScores::new(), - layer: EntityLayerId(obj_layer), + layer: LayerId(obj_layer), ..Default::default() }); @@ -78,7 +77,7 @@ fn show_scoreboard_when_client_join() { name: Objective::new("foo"), display: ObjectiveDisplay("Foo".into_text()), scores: ObjectiveScores::new(), - layer: EntityLayerId(obj_layer), + layer: LayerId(obj_layer), ..Default::default() }); @@ -121,7 +120,7 @@ fn should_update_score() { name: Objective::new("foo"), display: ObjectiveDisplay("Foo".into_text()), scores: ObjectiveScores::with_map([("foo".to_owned(), 0)]), - layer: EntityLayerId(obj_layer), + layer: LayerId(obj_layer), ..Default::default() }) .id(); @@ -169,7 +168,7 @@ fn should_only_update_score_diff() { name: Objective::new("foo"), display: ObjectiveDisplay("Foo".into_text()), scores: ObjectiveScores::with_map([("foo".to_owned(), 0), ("bar".to_owned(), 0)]), - layer: EntityLayerId(obj_layer), + layer: LayerId(obj_layer), ..Default::default() }) .id(); diff --git a/tools/playground/src/playground.template.rs b/tools/playground/src/playground.template.rs index aa17ada01..e924030bf 100644 --- a/tools/playground/src/playground.template.rs +++ b/tools/playground/src/playground.template.rs @@ -1,7 +1,7 @@ -use valence::client::despawn_disconnected_clients; use valence::log::LogPlugin; use valence::network::ConnectionMode; use valence::prelude::*; +use valence_server::dimension_layer::DimensionInfo; #[allow(unused_imports)] use crate::extras::*; @@ -16,7 +16,7 @@ pub fn build_app(app: &mut App) { .add_plugins(DefaultPlugins.build().disable::()) .add_systems(Startup, setup) .add_systems(EventLoopUpdate, toggle_gamemode_on_sneak) - .add_systems(Update, (init_clients, despawn_disconnected_clients)) + .add_systems(Update, init_clients) .run(); } @@ -26,11 +26,11 @@ fn setup( biomes: Res, dimensions: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk_index.insert([x, z], UnloadedChunk::new()); } } @@ -48,29 +48,20 @@ fn setup( fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.0.insert(layer); + visible_layers.0.insert(layer); pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); *game_mode = GameMode::Creative; } diff --git a/website/book/1-getting-started/setup.md b/website/book/1-getting-started/setup.md index ea0758c43..2684e2138 100644 --- a/website/book/1-getting-started/setup.md +++ b/website/book/1-getting-started/setup.md @@ -39,12 +39,12 @@ fn setup( biomes: Res, dimensions: Res, ) { - let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + let mut layer = CombinedLayerBundle::new(Default::default(), &dimensions, &biomes, &server); // We have to add chunks to the world first, they start empty. for z in -5..5 { for x in -5..5 { - layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + layer.chunk_index.insert([x, z], UnloadedChunk::new()); } } @@ -64,29 +64,20 @@ Now we need to handle clients when they join the server. Valence automatically s fn init_clients( mut clients: Query< ( - &mut EntityLayerId, - &mut VisibleChunkLayer, - &mut VisibleEntityLayers, + &mut LayerId, + &mut VisibleLayers, &mut Position, &mut GameMode, ), Added, >, - layers: Query, With)>, + layers: Query>, ) { - for ( - mut layer_id, - mut visible_chunk_layer, - mut visible_entity_layers, - mut pos, - mut game_mode, - ) in &mut clients - { + for (mut layer_id, mut visible_layers, mut pos, mut game_mode) in &mut clients { let layer = layers.single(); layer_id.0 = layer; - visible_chunk_layer.0 = layer; - visible_entity_layers.insert(layer); + visible_layers.insert(layer); pos.set([0.5, 65.0, 0.5]); *game_mode = GameMode::Creative; }