Correctly encode biomes (#112)

This commit is contained in:
Ryan Johnson 2022-10-13 18:19:35 -07:00 committed by GitHub
parent a29542b467
commit 056b4ebd32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 163 additions and 160 deletions

View file

@ -1,3 +1,4 @@
use std::iter;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
@ -43,7 +44,9 @@ struct ClientState {
const MAX_PLAYERS: usize = 10; const MAX_PLAYERS: usize = 10;
const BIOME_COUNT: usize = 4 * 4; const BIOME_COUNT: usize = 10;
const MIN_Y: i32 = -64;
#[async_trait] #[async_trait]
impl Config for Game { impl Config for Game {
@ -67,7 +70,7 @@ impl Config for Game {
} }
fn biomes(&self) -> Vec<Biome> { fn biomes(&self) -> Vec<Biome> {
(0..BIOME_COUNT) (1..BIOME_COUNT)
.map(|i| { .map(|i| {
let color = (0xffffff / BIOME_COUNT * i) as u32; let color = (0xffffff / BIOME_COUNT * i) as u32;
Biome { Biome {
@ -81,6 +84,10 @@ impl Config for Game {
..Default::default() ..Default::default()
} }
}) })
.chain(iter::once(Biome {
name: ident!("plains"),
..Default::default()
}))
.collect() .collect()
} }
@ -102,7 +109,9 @@ impl Config for Game {
fn init(&self, server: &mut Server<Self>) { fn init(&self, server: &mut Server<Self>) {
let world = server.worlds.insert(DimensionId::default(), ()).1; let world = server.worlds.insert(DimensionId::default(), ()).1;
server.state.player_list = Some(server.player_lists.insert(()).0); server.state.player_list = Some(server.player_lists.insert(()).0);
let height = server.shared.dimensions().next().unwrap().1.height as usize;
let height = world.chunks.height();
assert_eq!(world.chunks.min_y(), MIN_Y);
for chunk_z in 0..3 { for chunk_z in 0..3 {
for chunk_x in 0..3 { for chunk_x in 0..3 {
@ -112,16 +121,21 @@ impl Config for Game {
// Set chunk blocks // Set chunk blocks
for z in 0..16 { for z in 0..16 {
for x in 0..16 { for x in 0..16 {
chunk.set_block_state(x, 50, z, BlockState::GRASS_BLOCK); chunk.set_block_state(x, 1, z, BlockState::GRASS_BLOCK);
} }
} }
// Set chunk biomes // Set chunk biomes
for z in 0..4 { for z in 0..4 {
for x in 0..4 { for x in 0..4 {
let biome_id = server.shared.biomes().nth(x + z * 4).unwrap().0;
for y in 0..height / 4 { for y in 0..height / 4 {
let biome_id = server
.shared
.biomes()
.nth((x + z * 4 + y * 4 * 4) % BIOME_COUNT)
.unwrap()
.0;
chunk.set_biome(x, y, z, biome_id); chunk.set_biome(x, y, z, biome_id);
} }
} }
@ -194,7 +208,7 @@ impl Config for Game {
return false; return false;
} }
if client.position().y <= -20.0 { if client.position().y < MIN_Y as _ {
client.teleport(spawn_pos, client.yaw(), client.pitch()); client.teleport(spawn_pos, client.yaw(), client.pitch());
} }

View file

@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use log::LevelFilter; use log::LevelFilter;
use num::Integer; use num::Integer;
use valence::biome::Biome; use valence::async_trait;
use valence::block::BlockState; use valence::block::BlockState;
use valence::chunk::{Chunk, UnloadedChunk}; use valence::chunk::{Chunk, UnloadedChunk};
use valence::client::{handle_event_default, ClientEvent, DiggingStatus, GameMode, Hand}; use valence::client::{handle_event_default, ClientEvent, DiggingStatus, GameMode, Hand};
@ -13,7 +13,6 @@ use valence::entity::{EntityId, EntityKind};
use valence::player_list::PlayerListId; use valence::player_list::PlayerListId;
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::{async_trait, ident};
pub fn main() -> ShutdownResult { pub fn main() -> ShutdownResult {
env_logger::Builder::new() env_logger::Builder::new()
@ -68,14 +67,6 @@ impl Config for Game {
}] }]
} }
fn biomes(&self) -> Vec<Biome> {
vec![Biome {
name: ident!("valence:default_biome"),
grass_color: Some(0x00ff00),
..Biome::default()
}]
}
async fn server_list_ping( async fn server_list_ping(
&self, &self,
_server: &SharedServer<Self>, _server: &SharedServer<Self>,

View file

@ -84,7 +84,7 @@ impl Config for Game {
fn biomes(&self) -> Vec<Biome> { fn biomes(&self) -> Vec<Biome> {
vec![Biome { vec![Biome {
name: ident!("valence:default_biome"), name: ident!("plains"),
grass_color: Some(0x00ff00), grass_color: Some(0x00ff00),
..Biome::default() ..Biome::default()
}] }]

View file

@ -12,7 +12,7 @@ use std::io::Write;
use std::iter::FusedIterator; use std::iter::FusedIterator;
use bitvec::vec::BitVec; use bitvec::vec::BitVec;
use paletted_container::{PalettedContainer, PalettedContainerElement}; use paletted_container::PalettedContainer;
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use valence_nbt::compound; use valence_nbt::compound;
@ -505,26 +505,6 @@ impl Default for ChunkSection {
const SECTION_BLOCK_COUNT: usize = 4096; const SECTION_BLOCK_COUNT: usize = 4096;
const USIZE_BITS: usize = usize::BITS as _; const USIZE_BITS: usize = usize::BITS as _;
impl PalettedContainerElement for BlockState {
const DIRECT_BITS: usize = log2_ceil(BlockState::max_raw() as _);
const MAX_INDIRECT_BITS: usize = 8;
const MIN_INDIRECT_BITS: usize = 4;
fn to_bits(self) -> u64 {
self.to_raw() as _
}
}
impl PalettedContainerElement for BiomeId {
const DIRECT_BITS: usize = 6;
const MAX_INDIRECT_BITS: usize = 4;
const MIN_INDIRECT_BITS: usize = 0;
fn to_bits(self) -> u64 {
self.0 as _
}
}
impl ChunkSection { impl ChunkSection {
fn mark_block_as_modified(&mut self, idx: usize) { fn mark_block_as_modified(&mut self, idx: usize) {
if !self.is_block_modified(idx) { if !self.is_block_modified(idx) {
@ -560,13 +540,35 @@ impl<C: Config> LoadedChunk<C> {
} }
/// Gets the chunk data packet for this chunk with the given position. /// Gets the chunk data packet for this chunk with the given position.
pub(crate) fn chunk_data_packet(&self, pos: ChunkPos) -> ChunkDataAndUpdateLight { pub(crate) fn chunk_data_packet(
&self,
pos: ChunkPos,
biome_registry_len: usize,
) -> ChunkDataAndUpdateLight {
let mut blocks_and_biomes = Vec::new(); let mut blocks_and_biomes = Vec::new();
for sect in self.sections.iter() { for sect in self.sections.iter() {
sect.non_air_count.encode(&mut blocks_and_biomes).unwrap(); sect.non_air_count.encode(&mut blocks_and_biomes).unwrap();
sect.block_states.encode(&mut blocks_and_biomes).unwrap();
sect.biomes.encode(&mut blocks_and_biomes).unwrap(); sect.block_states
.encode_mc_format(
&mut blocks_and_biomes,
|b| b.to_raw().into(),
4,
8,
log2_ceil(BlockState::max_raw().into()),
)
.unwrap();
sect.biomes
.encode_mc_format(
&mut blocks_and_biomes,
|b| b.0.into(),
0,
3,
log2_ceil(biome_registry_len),
)
.unwrap();
} }
ChunkDataAndUpdateLight { ChunkDataAndUpdateLight {

View file

@ -9,39 +9,21 @@ use crate::util::log2_ceil;
/// `HALF_LEN` must be equal to `ceil(LEN / 2)`. /// `HALF_LEN` must be equal to `ceil(LEN / 2)`.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum PalettedContainer<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize> { pub enum PalettedContainer<T, const LEN: usize, const HALF_LEN: usize> {
Single(T), Single(T),
Indirect(Box<Indirect<T, LEN, HALF_LEN>>), Indirect(Box<Indirect<T, LEN, HALF_LEN>>),
Direct(Box<[T; LEN]>), Direct(Box<[T; LEN]>),
} }
pub trait PalettedContainerElement: Copy + Eq + Default {
/// The minimum number of bits required to represent all instances of the
/// element type. If `N` is the total number of possible values, then
/// `DIRECT_BITS` is `ceil(log2(N))`.
const DIRECT_BITS: usize;
/// The maximum number of bits per element allowed in the indirect
/// representation while encoding. Any higher than this will force
/// conversion to the direct representation while encoding.
const MAX_INDIRECT_BITS: usize;
/// The minimum number of bits used to represent the element type in the
/// indirect representation while encoding. If the bits per index is lower,
/// it will be rounded up to this.
const MIN_INDIRECT_BITS: usize;
/// Converts the element type to bits. The output must be less than two to
/// the power of `DIRECT_BITS`.
fn to_bits(self) -> u64;
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Indirect<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize> { pub struct Indirect<T, const LEN: usize, const HALF_LEN: usize> {
/// Each element is a unique instance of `T`. /// Each element is a unique instance of `T`.
palette: ArrayVec<T, 16>, palette: ArrayVec<T, 16>,
/// Each half-byte is an index into `palette`. /// Each half-byte is an index into `palette`.
indices: [u8; HALF_LEN], indices: [u8; HALF_LEN],
} }
impl<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize> impl<T: Copy + Eq + Default, const LEN: usize, const HALF_LEN: usize>
PalettedContainer<T, LEN, HALF_LEN> PalettedContainer<T, LEN, HALF_LEN>
{ {
pub fn new() -> Self { pub fn new() -> Self {
@ -150,9 +132,108 @@ impl<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize>
"index {idx} is out of bounds in paletted container of length {LEN}" "index {idx} is out of bounds in paletted container of length {LEN}"
); );
} }
/// Encodes the paletted container in the format that Minecraft expects.
///
/// - **`writer`**: The [`Write`] instance to write the paletted container
/// to.
/// - **`to_bits`**: A function to convert the element type to bits. The
/// output must be less than two to the power of `direct_bits`.
/// - **`min_indirect_bits`**: The minimum number of bits used to represent
/// the element type in the indirect representation. If the bits per index
/// is lower, it will be rounded up to this.
/// - **`max_indirect_bits`**: The maximum number of bits per element
/// allowed in the indirect representation. Any higher than this will
/// force conversion to the direct representation while encoding.
/// - **`direct_bits`**: The minimum number of bits required to represent
/// all instances of the element type. If `N` is the total number of
/// possible values, then `DIRECT_BITS` is `ceil(log2(N))`.
pub fn encode_mc_format<W, F>(
&self,
mut writer: W,
mut to_bits: F,
min_indirect_bits: usize,
max_indirect_bits: usize,
direct_bits: usize,
) -> anyhow::Result<()>
where
W: Write,
F: FnMut(T) -> u64,
{
debug_assert!(min_indirect_bits <= 4);
debug_assert!(min_indirect_bits <= max_indirect_bits);
debug_assert!(max_indirect_bits <= 64);
debug_assert!(direct_bits <= 64);
match self {
Self::Single(val) => {
// Bits per entry
0_u8.encode(&mut writer)?;
// Palette
VarInt(to_bits(*val) as i32).encode(&mut writer)?;
// Number of longs
VarInt(0).encode(&mut writer)?;
}
Self::Indirect(ind) => {
let bits_per_entry = min_indirect_bits.max(log2_ceil(ind.palette.len()));
// Encode as direct if necessary.
if bits_per_entry > max_indirect_bits {
// Bits per entry
(direct_bits as u8).encode(&mut writer)?;
// Number of longs in data array.
VarInt(compact_u64s_len(LEN, direct_bits) as _).encode(&mut writer)?;
// Data array
encode_compact_u64s(
&mut writer,
(0..LEN).map(|i| to_bits(ind.get(i))),
direct_bits,
)?;
} else {
// Bits per entry
(bits_per_entry as u8).encode(&mut writer)?;
// Palette len
VarInt(ind.palette.len() as i32).encode(&mut writer)?;
// Palette
for val in &ind.palette {
VarInt(to_bits(*val) as i32).encode(&mut writer)?;
}
// Number of longs in data array.
VarInt(compact_u64s_len(LEN, bits_per_entry) as _).encode(&mut writer)?;
// Data array
encode_compact_u64s(
&mut writer,
ind.indices
.iter()
.cloned()
.flat_map(|byte| [byte & 0b1111, byte >> 4])
.map(u64::from)
.take(LEN),
bits_per_entry,
)?;
}
}
Self::Direct(dir) => {
// Bits per entry
(direct_bits as u8).encode(&mut writer)?;
// Number of longs in data array.
VarInt(compact_u64s_len(LEN, direct_bits) as _).encode(&mut writer)?;
// Data array
encode_compact_u64s(&mut writer, dir.iter().cloned().map(to_bits), direct_bits)?;
}
}
Ok(())
}
} }
impl<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize> Default impl<T: Copy + Eq + Default, const LEN: usize, const HALF_LEN: usize> Default
for PalettedContainer<T, LEN, HALF_LEN> for PalettedContainer<T, LEN, HALF_LEN>
{ {
fn default() -> Self { fn default() -> Self {
@ -160,9 +241,7 @@ impl<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize> Defau
} }
} }
impl<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize> impl<T: Copy + Eq + Default, const LEN: usize, const HALF_LEN: usize> Indirect<T, LEN, HALF_LEN> {
Indirect<T, LEN, HALF_LEN>
{
pub fn get(&self, idx: usize) -> T { pub fn get(&self, idx: usize) -> T {
let palette_idx = self.indices[idx / 2] >> (idx % 2 * 4) & 0b1111; let palette_idx = self.indices[idx / 2] >> (idx % 2 * 4) & 0b1111;
self.palette[palette_idx as usize] self.palette[palette_idx as usize]
@ -184,79 +263,13 @@ impl<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize>
} }
} }
/// Encodes the paletted container in the format that Minecraft expects.
impl<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize> Encode
for PalettedContainer<T, LEN, HALF_LEN>
{
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
assert!(T::DIRECT_BITS <= 64);
assert!(T::MAX_INDIRECT_BITS <= 64);
assert!(T::MIN_INDIRECT_BITS <= T::MAX_INDIRECT_BITS);
assert!(T::MIN_INDIRECT_BITS <= 4);
match self {
Self::Single(val) => {
// Bits per entry
0_u8.encode(w)?;
// Palette
VarInt(val.to_bits() as i32).encode(w)?;
// Number of longs
VarInt(0).encode(w)?;
}
Self::Indirect(ind) => {
let bits_per_entry = T::MIN_INDIRECT_BITS.max(log2_ceil(ind.palette.len()));
// TODO: if bits_per_entry > MAX_INDIRECT_BITS, encode as direct.
debug_assert!(bits_per_entry <= T::MAX_INDIRECT_BITS);
// Bits per entry
(bits_per_entry as u8).encode(w)?;
// Palette len
VarInt(ind.palette.len() as i32).encode(w)?;
// Palette
for val in &ind.palette {
VarInt(val.to_bits() as i32).encode(w)?;
}
// Number of longs in data array.
VarInt(compact_u64s_len(LEN, bits_per_entry) as _).encode(w)?;
// Data array
encode_compact_u64s(
w,
ind.indices
.iter()
.cloned()
.flat_map(|byte| [byte & 0b1111, byte >> 4])
.map(u64::from)
.take(LEN),
bits_per_entry,
)?;
}
Self::Direct(dir) => {
// Bits per entry
(T::DIRECT_BITS as u8).encode(w)?;
// Number of longs in data array.
VarInt(compact_u64s_len(LEN, T::DIRECT_BITS) as _).encode(w)?;
// Data array
encode_compact_u64s(w, dir.iter().map(|v| v.to_bits()), T::DIRECT_BITS)?;
}
}
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use rand::Rng; use rand::Rng;
use super::*; use super::*;
fn check<T: PalettedContainerElement, const LEN: usize, const HALF_LEN: usize>( fn check<T: Copy + Eq + Default, const LEN: usize, const HALF_LEN: usize>(
p: &PalettedContainer<T, LEN, HALF_LEN>, p: &PalettedContainer<T, LEN, HALF_LEN>,
s: &[T], s: &[T],
) -> bool { ) -> bool {
@ -264,16 +277,6 @@ mod tests {
(0..LEN).all(|i| p.get(i) == s[i]) (0..LEN).all(|i| p.get(i) == s[i])
} }
impl PalettedContainerElement for u32 {
const DIRECT_BITS: usize = 0;
const MAX_INDIRECT_BITS: usize = 0;
const MIN_INDIRECT_BITS: usize = 0;
fn to_bits(self) -> u64 {
self.into()
}
}
#[test] #[test]
fn random_assignments() { fn random_assignments() {
const LEN: usize = 100; const LEN: usize = 100;

View file

@ -1138,7 +1138,7 @@ impl<C: Config> Client<C> {
registry_codec: shared.registry_codec().clone(), registry_codec: shared.registry_codec().clone(),
dimension_type_name: world.meta.dimension().dimension_type_name(), dimension_type_name: world.meta.dimension().dimension_type_name(),
dimension_name: world.meta.dimension().dimension_name(), dimension_name: world.meta.dimension().dimension_name(),
hashed_seed: 0, hashed_seed: 10,
max_players: VarInt(0), max_players: VarInt(0),
view_distance: BoundedInt(VarInt(self.view_distance() as i32)), view_distance: BoundedInt(VarInt(self.view_distance() as i32)),
simulation_distance: VarInt(16), simulation_distance: VarInt(16),
@ -1332,7 +1332,7 @@ impl<C: Config> Client<C> {
for pos in chunks_in_view_distance(center, self.view_distance) { for pos in chunks_in_view_distance(center, self.view_distance) {
if let Some(chunk) = world.chunks.get(pos) { if let Some(chunk) = world.chunks.get(pos) {
if self.loaded_chunks.insert(pos) { if self.loaded_chunks.insert(pos) {
self.send_packet(chunk.chunk_data_packet(pos)); self.send_packet(chunk.chunk_data_packet(pos, shared.biomes().len()));
} }
} }
} }

View file

@ -176,6 +176,10 @@ pub trait Config: Sized + Send + Sync + 'static {
/// Additionally, the documented requirements on the fields of [`Biome`] /// Additionally, the documented requirements on the fields of [`Biome`]
/// must be met. /// must be met.
/// ///
/// **NOTE**: As of 1.19.2, there is a bug in the client which prevents
/// joining the game when a biome named "minecraft:plains" is not present.
/// Ensure there is a biome named "plains".
///
/// # Default Implementation /// # Default Implementation
/// ///
/// Returns `vec![Biome::default()]`. /// Returns `vec![Biome::default()]`.

View file

@ -393,24 +393,13 @@ fn make_registry_codec(dimensions: &[Dimension], biomes: &[Biome]) -> Compound {
ident!("worldgen/biome") => compound! { ident!("worldgen/biome") => compound! {
"type" => ident!("worldgen/biome"), "type" => ident!("worldgen/biome"),
"value" => { "value" => {
let mut biomes: Vec<_> = biomes List::Compound(biomes
.iter() .iter()
.enumerate() .enumerate()
.map(|(id, biome)| biome.to_biome_registry_item(id as i32)) .map(|(id, biome)| biome.to_biome_registry_item(id as i32))
.collect(); .collect())
// The client needs a biome named "minecraft:plains" in the registry to
// connect. This is probably a bug in the client.
//
// If the issue is resolved, remove this if.
if !biomes.iter().any(|b| b["name"] == "plains".into()) {
let biome = Biome::default();
assert_eq!(biome.name, ident!("plains"));
biomes.push(biome.to_biome_registry_item(biomes.len() as i32));
} }
List::Compound(biomes)
}
}, },
ident!("chat_type_registry") => compound! { ident!("chat_type_registry") => compound! {
"type" => ident!("chat_type"), "type" => ident!("chat_type"),