Give World ownership over entities, clients, and chunks.

This change was made to make it easier for invariants to be upheld. When
the spatial partition is added, we can ensure that changes to entities
are immediately reflected in the partition. Additionally, chunks being
shared between worlds was a leaky abstraction to begin with and is now
removed. A method in `Config` is now necessary to determine what world a
client should join.

Along with this, most mutable references have been wrapped in a newtype
to ensure that `mem::swap` cannot be used on them, which would break
invariants. This is analogous to `Pin<&mut T>`. The reason we can't use
Pin directly is because it would require unnecessary unsafe code
within the library.
This commit is contained in:
Ryan 2022-05-16 02:36:14 -07:00
parent a9005d7a59
commit adc8a4faae
20 changed files with 1783 additions and 1199 deletions

View file

@ -10,19 +10,21 @@ build = "build/main.rs"
[dependencies] [dependencies]
aes = "0.7" aes = "0.7"
anyhow = "1" anyhow = "1"
arrayvec = "0.7"
ascii = "1" ascii = "1"
async-trait = "0.1" async-trait = "0.1"
base64 = "0.13" base64 = "0.13"
bitfield-struct = "0.1"
bitvec = "1" bitvec = "1"
bytes = "1"
byteorder = "1" byteorder = "1"
bytes = "1"
cfb8 = "0.7" cfb8 = "0.7"
flate2 = "1" flate2 = "1"
flume = "0.10" flume = "0.10"
futures = "0.3" futures = "0.3"
hematite-nbt = "0.5" hematite-nbt = "0.5"
log = "0.4" log = "0.4"
nalgebra-glm = "0.16" nalgebra-glm = "0.17"
num = "0.4" num = "0.4"
parking_lot = "0.12" parking_lot = "0.12"
paste = "1" paste = "1"
@ -36,7 +38,7 @@ sha1 = "0.10"
sha2 = "0.10" sha2 = "0.10"
thiserror = "1" thiserror = "1"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
uuid = "0.8" uuid = "1"
[dependencies.reqwest] [dependencies.reqwest]
version = "0.11" version = "0.11"

View file

@ -513,6 +513,7 @@ pub fn build() -> anyhow::Result<()> {
} }
} }
/// Converts a `bool` to a `True` or `False` property value.
pub const fn from_bool(b: bool) -> Self { pub const fn from_bool(b: bool) -> Self {
if b { if b {
Self::True Self::True
@ -521,6 +522,8 @@ pub fn build() -> anyhow::Result<()> {
} }
} }
/// Convers a `True` or `False` property value to a `bool`.
/// Returns `None` if this property value is not `True` or `False`
pub const fn to_bool(self) -> Option<bool> { pub const fn to_bool(self) -> Option<bool> {
match self { match self {
Self::True => Some(true), Self::True => Some(true),

View file

@ -41,7 +41,6 @@ enum Type {
Nbt, Nbt,
Particle, Particle,
VillagerData, VillagerData,
OptVarInt,
Pose, Pose,
// ==== Specialized ==== // // ==== Specialized ==== //
OptEntityId, OptEntityId,
@ -49,6 +48,76 @@ enum Type {
MainHand, MainHand,
} }
impl Type {
pub fn default_expr(&self) -> TokenStream {
match self {
Type::BitFields(bfs) => {
let mut default = 0;
for bf in *bfs {
default = (bf.default as u8) << bf.offset;
}
quote! { #default }
}
Type::Byte(d) => quote! { #d },
Type::VarInt(d) => quote! { VarInt(#d) },
Type::Float(d) => quote! { #d },
Type::String(d) => quote! { #d.into() },
Type::Text => quote! { Default::default() },
Type::OptText(d) => match d {
Some(d) => quote! { Some(Box::new(Text::from(#d))) },
None => quote! { None },
},
Type::Slot => quote! { () }, // TODO
Type::Bool(d) => quote! { #d },
Type::ArmorStandRotations(x, y, z) => {
quote! { ArmorStandRotations::new(#x, #y, #z) }
}
Type::BlockPos(x, y, z) => quote! { BlockPos::new(#x, #y, #z) },
Type::OptBlockPos(d) => match d {
Some((x, y, z)) => quote! { Some(BlockPos::new(#x, #y, #z)) },
None => quote! { None },
},
Type::Direction => quote! { Direction::Down },
Type::OptUuid => quote! { None },
Type::BlockState => quote! { BlockState::AIR },
Type::Nbt => quote! { nbt::Blob::new() },
Type::Particle => quote! { () }, // TODO
Type::VillagerData => quote! { VillagerData::default() },
Type::Pose => quote! { Pose::default() },
Type::OptEntityId => quote! { None },
Type::BoatVariant => quote! { BoatVariant::default() },
Type::MainHand => quote! { MainHand::default() },
}
}
pub fn type_id(&self) -> i32 {
match self {
Type::BitFields(_) => 0,
Type::Byte(_) => 0,
Type::VarInt(_) => 1,
Type::Float(_) => 2,
Type::String(_) => 3,
Type::Text => 4,
Type::OptText(_) => 5,
Type::Slot => 6,
Type::Bool(_) => 7,
Type::ArmorStandRotations(_, _, _) => 8,
Type::BlockPos(_, _, _) => 9,
Type::OptBlockPos(_) => 10,
Type::Direction => 11,
Type::OptUuid => 12,
Type::BlockState => 13,
Type::Nbt => 14,
Type::Particle => 15,
Type::VillagerData => 16,
Type::Pose => 18,
Type::OptEntityId => 17,
Type::BoatVariant => 1,
Type::MainHand => 0,
}
}
}
struct BitField { struct BitField {
name: &'static str, name: &'static str,
offset: u8, offset: u8,
@ -60,7 +129,7 @@ const BASE_ENTITY: Class = Class {
inherit: None, inherit: None,
fields: &[ fields: &[
Field { Field {
name: "base_entity_bits", name: "base_entity_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "on_fire", name: "on_fire",
@ -135,7 +204,7 @@ const ABSTRACT_ARROW: Class = Class {
inherit: Some(&BASE_ENTITY), inherit: Some(&BASE_ENTITY),
fields: &[ fields: &[
Field { Field {
name: "abstract_arrow_bits", name: "abstract_arrow_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "critical", name: "critical",
@ -161,7 +230,7 @@ const LIVING_ENTITY: Class = Class {
inherit: Some(&BASE_ENTITY), inherit: Some(&BASE_ENTITY),
fields: &[ fields: &[
Field { Field {
name: "living_entity_bits", name: "living_entity_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "hand_active", name: "hand_active",
@ -211,7 +280,7 @@ const MOB: Class = Class {
name: "mob", name: "mob",
inherit: Some(&LIVING_ENTITY), inherit: Some(&LIVING_ENTITY),
fields: &[Field { fields: &[Field {
name: "mob_bits", name: "mob_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "ai_disabled", name: "ai_disabled",
@ -279,7 +348,7 @@ const ABSTRACT_HORSE: Class = Class {
inherit: Some(&ANIMAL), inherit: Some(&ANIMAL),
fields: &[ fields: &[
Field { Field {
name: "horse_bits", name: "horse_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "tame", name: "tame",
@ -339,7 +408,7 @@ const TAMEABLE_ANIMAL: Class = Class {
name: "tameable_animal", name: "tameable_animal",
inherit: Some(&ANIMAL), inherit: Some(&ANIMAL),
fields: &[Field { fields: &[Field {
name: "tameable_animal_bits", name: "tameable_animal_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "sitting", name: "sitting",
@ -435,7 +504,7 @@ const SPIDER: Class = Class {
name: "spider", name: "spider",
inherit: Some(&MONSTER), inherit: Some(&MONSTER),
fields: &[Field { fields: &[Field {
name: "spider_bits", name: "spider_flags",
typ: Type::BitFields(&[BitField { typ: Type::BitFields(&[BitField {
name: "climbing", name: "climbing",
offset: 0, offset: 0,
@ -843,7 +912,7 @@ const ENTITIES: &[Class] = &[
inherit: Some(&LIVING_ENTITY), inherit: Some(&LIVING_ENTITY),
fields: &[ fields: &[
Field { Field {
name: "armor_stand_bits", name: "armor_stand_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "small", name: "small",
@ -851,7 +920,7 @@ const ENTITIES: &[Class] = &[
default: false, default: false,
}, },
BitField { BitField {
name: "has_arms", name: "arms",
offset: 1, offset: 1,
default: false, default: false,
}, },
@ -861,7 +930,7 @@ const ENTITIES: &[Class] = &[
default: false, default: false,
}, },
BitField { BitField {
name: "is_marker", name: "marker",
offset: 3, offset: 3,
default: false, default: false,
}, },
@ -897,7 +966,7 @@ const ENTITIES: &[Class] = &[
name: "bat", name: "bat",
inherit: Some(&AMBIENT_CREATURE), inherit: Some(&AMBIENT_CREATURE),
fields: &[Field { fields: &[Field {
name: "bat_bits", name: "bat_flags",
typ: Type::BitFields(&[BitField { typ: Type::BitFields(&[BitField {
name: "hanging", name: "hanging",
offset: 0, offset: 0,
@ -1034,7 +1103,7 @@ const ENTITIES: &[Class] = &[
inherit: Some(&ANIMAL), inherit: Some(&ANIMAL),
fields: &[ fields: &[
Field { Field {
name: "bee_bits", name: "bee_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "angry", name: "angry",
@ -1068,7 +1137,7 @@ const ENTITIES: &[Class] = &[
typ: Type::VarInt(0), // TODO: 0 for red, 1 for snow typ: Type::VarInt(0), // TODO: 0 for red, 1 for snow
}, },
Field { Field {
name: "fox_bits", name: "fox_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "sitting", name: "sitting",
@ -1151,7 +1220,7 @@ const ENTITIES: &[Class] = &[
typ: Type::Byte(0), typ: Type::Byte(0),
}, },
Field { Field {
name: "panda_bits", name: "panda_flags",
typ: Type::BitFields(&[ typ: Type::BitFields(&[
BitField { BitField {
name: "sneezing", name: "sneezing",
@ -1355,7 +1424,7 @@ const ENTITIES: &[Class] = &[
name: "iron_golem", name: "iron_golem",
inherit: Some(&ABSTRACT_GOLEM), inherit: Some(&ABSTRACT_GOLEM),
fields: &[Field { fields: &[Field {
name: "iron_golem_bits", name: "iron_golem_flags",
typ: Type::BitFields(&[BitField { typ: Type::BitFields(&[BitField {
name: "player_created", name: "player_created",
offset: 0, offset: 0,
@ -1367,7 +1436,7 @@ const ENTITIES: &[Class] = &[
name: "snow_golem", name: "snow_golem",
inherit: Some(&ABSTRACT_GOLEM), inherit: Some(&ABSTRACT_GOLEM),
fields: &[Field { fields: &[Field {
name: "snow_golem_bits", name: "snow_golem_flags",
typ: Type::BitFields(&[BitField { typ: Type::BitFields(&[BitField {
name: "pumpkin_hat", name: "pumpkin_hat",
offset: 4, offset: 4,
@ -1430,7 +1499,7 @@ const ENTITIES: &[Class] = &[
name: "blaze", name: "blaze",
inherit: Some(&MONSTER), inherit: Some(&MONSTER),
fields: &[Field { fields: &[Field {
name: "blaze_bits", name: "blaze_flags",
typ: Type::BitFields(&[BitField { typ: Type::BitFields(&[BitField {
name: "blaze_on_fire", // TODO: better name for this? name: "blaze_on_fire", // TODO: better name for this?
offset: 0, offset: 0,
@ -1522,7 +1591,7 @@ const ENTITIES: &[Class] = &[
name: "vex", name: "vex",
inherit: Some(&MONSTER), inherit: Some(&MONSTER),
fields: &[Field { fields: &[Field {
name: "vex_bits", name: "vex_flags",
typ: Type::BitFields(&[BitField { typ: Type::BitFields(&[BitField {
name: "attacking", name: "attacking",
offset: 0, offset: 0,
@ -1665,7 +1734,10 @@ const ENTITIES: &[Class] = &[
Class { Class {
name: "magma_cube", name: "magma_cube",
inherit: Some(&MOB), inherit: Some(&MOB),
fields: &[], // TODO: what are the fields? fields: &[Field {
name: "size",
typ: Type::VarInt(1),
}],
}, },
Class { Class {
name: "llama_spit", name: "llama_spit",
@ -1777,6 +1849,49 @@ pub fn build() -> anyhow::Result<()> {
.map(|c| ident(c.name.to_pascal_case())) .map(|c| ident(c.name.to_pascal_case()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
/*
let set_type_arms = entities.iter().map(|&entity| {
let entity_name = ident(entity.name.to_pascal_case());
let mut old_fields = Vec::new();
collect_class_fields(entity, &mut old_fields);
let new_type_arms = entities.iter().map(|&new_entity| {
let new_entity_name = ident(new_entity.name.to_pascal_case());
let mut new_fields = Vec::new();
collect_class_fields(new_entity, &mut new_fields);
let assign_fields = new_fields
.iter()
.cloned()
.filter(|&new_field| old_fields.iter().any(|&f| f.name == new_field.name))
.map(|new_field| {
let name = ident(new_field.name.to_snake_case());
quote! {
new.#name = old.#name;
}
});
quote! {
EntityType::#new_entity_name => {
let mut new = #new_entity_name::new();
#(#assign_fields)*
*self = Self::#new_entity_name(new);
}
}
});
quote! {
Self::#entity_name(old) => match new_type {
#(#new_type_arms)*
},
}
});
*/
let entity_structs = entities.iter().map(|&class| { let entity_structs = entities.iter().map(|&class| {
let mut fields = Vec::new(); let mut fields = Vec::new();
collect_class_fields(class, &mut fields); collect_class_fields(class, &mut fields);
@ -1787,7 +1902,7 @@ pub fn build() -> anyhow::Result<()> {
let typ = match f.typ { let typ = match f.typ {
Type::BitFields(_) => quote! { u8 }, Type::BitFields(_) => quote! { u8 },
Type::Byte(_) => quote! { u8 }, Type::Byte(_) => quote! { u8 },
Type::VarInt(_) => quote! { i32 }, Type::VarInt(_) => quote! { VarInt },
Type::Float(_) => quote! { f32 }, Type::Float(_) => quote! { f32 },
Type::String(_) => quote! { Box<str> }, Type::String(_) => quote! { Box<str> },
Type::Text => quote! { Box<Text> }, Type::Text => quote! { Box<Text> },
@ -1803,7 +1918,6 @@ pub fn build() -> anyhow::Result<()> {
Type::Nbt => quote! { nbt::Blob }, Type::Nbt => quote! { nbt::Blob },
Type::Particle => quote! { () }, // TODO Type::Particle => quote! { () }, // TODO
Type::VillagerData => quote! { VillagerData }, Type::VillagerData => quote! { VillagerData },
Type::OptVarInt => quote! { OptVarInt },
Type::Pose => quote! { Pose }, Type::Pose => quote! { Pose },
Type::OptEntityId => quote! { Option<EntityId> }, Type::OptEntityId => quote! { Option<EntityId> },
Type::BoatVariant => quote! { BoatVariant }, Type::BoatVariant => quote! { BoatVariant },
@ -1816,46 +1930,7 @@ pub fn build() -> anyhow::Result<()> {
let constructor_fields = fields.iter().map(|field| { let constructor_fields = fields.iter().map(|field| {
let name = ident(field.name.to_snake_case()); let name = ident(field.name.to_snake_case());
let val = match field.typ { let val = field.typ.default_expr();
Type::BitFields(bfs) => {
let mut default = 0;
for bf in bfs {
default = (bf.default as u8) << bf.offset;
}
quote! { #default }
}
Type::Byte(d) => quote! { #d },
Type::VarInt(d) => quote! { #d },
Type::Float(d) => quote! { #d },
Type::String(d) => quote! { #d.into() },
Type::Text => quote! { Default::default() },
Type::OptText(d) => match d {
Some(d) => quote! { Some(Box::new(Text::from(#d))) },
None => quote! { None },
},
Type::Slot => quote! { () }, // TODO
Type::Bool(d) => quote! { #d },
Type::ArmorStandRotations(x, y, z) => {
quote! { ArmorStandRotations::new(#x, #y, #z) }
}
Type::BlockPos(x, y, z) => quote! { BlockPos::new(#x, #y, #z) },
Type::OptBlockPos(d) => match d {
Some((x, y, z)) => quote! { Some(BlockPos::new(#x, #y, #z)) },
None => quote! { None },
},
Type::Direction => quote! { Direction::Down },
Type::OptUuid => quote! { None },
Type::BlockState => quote! { BlockState::AIR },
Type::Nbt => quote! { nbt::Blob::new() },
Type::Particle => quote! { () }, // TODO
Type::VillagerData => quote! { VillagerData::default() },
Type::OptVarInt => quote! { 0 },
Type::Pose => quote! { Pose::default() },
Type::OptEntityId => quote! { None },
Type::BoatVariant => quote! { BoatVariant::default() },
Type::MainHand => quote! { MainHand::default() },
};
quote! { quote! {
#name: #val, #name: #val,
} }
@ -1881,7 +1956,7 @@ pub fn build() -> anyhow::Result<()> {
pub fn #setter_name(&mut self, #name: #type_name) { pub fn #setter_name(&mut self, #name: #type_name) {
if self.#name != #name { if self.#name != #name {
self.modified_bits |= 1 << #field_offset; self.modified_flags |= 1 << #field_offset;
} }
self.#name = #name; self.#name = #name;
@ -1892,9 +1967,6 @@ pub fn build() -> anyhow::Result<()> {
Type::BitFields(bfs) => bfs Type::BitFields(bfs) => bfs
.iter() .iter()
.map(|bf| { .map(|bf| {
if bf.name.to_snake_case().is_empty() {
eprintln!("{}", field.name);
}
let bit_name = ident(bf.name.to_snake_case()); let bit_name = ident(bf.name.to_snake_case());
let getter_name = ident(format!("get_{}", bit_name.to_string())); let getter_name = ident(format!("get_{}", bit_name.to_string()));
@ -1908,19 +1980,28 @@ pub fn build() -> anyhow::Result<()> {
} }
pub fn #setter_name(&mut self, #bit_name: bool) { pub fn #setter_name(&mut self, #bit_name: bool) {
let orig = self.#getter_name(); if self.#getter_name() != #bit_name {
self.#name = (self.#name & !(1 << #offset)) | ((#bit_name as u8) << #offset);
self.#name = (self.#name & !(1 << #offset)) | ((#bit_name as u8) << #offset); self.modified_flags |= 1 << #field_offset;
if orig != self.#getter_name() {
self.modified_bits |= 1 << #field_offset;
} }
} }
} }
}) })
.collect(), .collect(),
Type::Byte(_) => standard_getter_setter(quote!(u8)), Type::Byte(_) => standard_getter_setter(quote!(u8)),
Type::VarInt(_) => standard_getter_setter(quote!(i32)), Type::VarInt(_) => quote! {
pub fn #getter_name(&self) -> i32 {
self.#name.0
}
pub fn #setter_name(&mut self, #name: i32) {
if self.#name.0 != #name {
self.modified_flags |= 1 << #field_offset;
}
self.#name = VarInt(#name);
}
},
Type::Float(_) => standard_getter_setter(quote!(f32)), Type::Float(_) => standard_getter_setter(quote!(f32)),
Type::String(_) => quote! { Type::String(_) => quote! {
pub fn #getter_name(&self) -> &str { pub fn #getter_name(&self) -> &str {
@ -1931,7 +2012,7 @@ pub fn build() -> anyhow::Result<()> {
let #name = #name.into(); let #name = #name.into();
if self.#name != #name { if self.#name != #name {
self.modified_bits |= 1 << #field_offset; self.modified_flags |= 1 << #field_offset;
} }
self.#name = #name; self.#name = #name;
@ -1946,7 +2027,7 @@ pub fn build() -> anyhow::Result<()> {
let #name = Box::new(#name.into()); let #name = Box::new(#name.into());
if self.#name != #name { if self.#name != #name {
self.modified_bits |= 1 << #field_offset; self.modified_flags |= 1 << #field_offset;
} }
self.#name = #name; self.#name = #name;
@ -1961,7 +2042,7 @@ pub fn build() -> anyhow::Result<()> {
let #name = #name.map(|x| Box::new(x.into())); let #name = #name.map(|x| Box::new(x.into()));
if self.#name != #name { if self.#name != #name {
self.modified_bits |= 1 << #field_offset; self.modified_flags |= 1 << #field_offset;
} }
self.#name = #name; self.#name = #name;
@ -1982,7 +2063,7 @@ pub fn build() -> anyhow::Result<()> {
pub fn #setter_name(&mut self, #name: nbt::Blob) { pub fn #setter_name(&mut self, #name: nbt::Blob) {
if self.#name != #name { if self.#name != #name {
self.modified_bits |= 1 << #field_offset; self.modified_flags |= 1 << #field_offset;
} }
self.#name = #name; self.#name = #name;
@ -1990,21 +2071,8 @@ pub fn build() -> anyhow::Result<()> {
}, },
Type::Particle => quote! {}, // TODO Type::Particle => quote! {}, // TODO
Type::VillagerData => standard_getter_setter(quote!(VillagerData)), Type::VillagerData => standard_getter_setter(quote!(VillagerData)),
Type::OptVarInt => quote! {
pub fn #getter_name(&self) -> i32 {
self.#name.0
}
pub fn #setter_name(&mut self, #name: i32) {
if self.#name.0 != #name {
self.modified_bits |= 1 << #field_offset;
}
self.#name = OptVarInt(#name);
}
},
Type::Pose => standard_getter_setter(quote!(Pose)), Type::Pose => standard_getter_setter(quote!(Pose)),
Type::OptEntityId => quote! {}, // TODO Type::OptEntityId => standard_getter_setter(quote!(Option<EntityId>)),
Type::BoatVariant => standard_getter_setter(quote!(BoatVariant)), Type::BoatVariant => standard_getter_setter(quote!(BoatVariant)),
Type::MainHand => standard_getter_setter(quote!(MainHand)), Type::MainHand => standard_getter_setter(quote!(MainHand)),
} }
@ -2014,14 +2082,14 @@ pub fn build() -> anyhow::Result<()> {
quote! { quote! {
pub struct #name { pub struct #name {
/// Contains a set bit for each modified field. /// Contains a set bit for each modified field.
modified_bits: u32, modified_flags: u32,
#(#struct_fields)* #(#struct_fields)*
} }
impl #name { impl #name {
pub(super) fn new() -> Self { pub(super) fn new() -> Self {
Self { Self {
modified_bits: 0, modified_flags: 0,
#(#constructor_fields)* #(#constructor_fields)*
} }
} }
@ -2031,23 +2099,64 @@ pub fn build() -> anyhow::Result<()> {
} }
}); });
let finished = quote! { let initial_metadata_arms = entities.iter().map(|&entity| {
pub enum EntityData { let name = ident(entity.name.to_pascal_case());
#(#entity_type_variants(#entity_type_variants),)* let mut fields = Vec::new();
} collect_class_fields(entity, &mut fields);
impl EntityData { let check_fields = fields.into_iter().enumerate().map(|(idx, f)| {
pub(super) fn new() -> Self { let name = ident(f.name.to_snake_case());
Self::Marker(Marker::new()) let default = f.typ.default_expr();
} let index: u8 = idx.try_into().unwrap();
let type_id = f.typ.type_id();
pub fn typ(&self) -> EntityType { quote! {
match self { if m.#name != #default {
#(Self::#entity_type_variants(_) => EntityType::#entity_type_variants,)* data.push(#index);
VarInt(#type_id).encode(&mut data).unwrap();
m.#name.encode(&mut data).unwrap();
} }
} }
} });
quote! {
Self::#name(m) => {
#(#check_fields)*
}
}
});
let updated_metadata_arms = entities.iter().map(|&entity| {
let name = ident(entity.name.to_pascal_case());
let mut fields = Vec::new();
collect_class_fields(entity, &mut fields);
let update_fields = fields.into_iter().enumerate().map(|(idx, f)| {
let name = ident(f.name.to_snake_case());
let u8_index: u8 = idx.try_into().unwrap();
let u32_index = idx as u32;
let type_id = f.typ.type_id();
quote! {
if (m.modified_flags >> #u32_index) & 1 == 1 {
data.push(#u8_index);
VarInt(#type_id).encode(&mut data).unwrap();
m.#name.encode(&mut data).unwrap();
}
}
});
quote! {
Self::#name(m) => {
if m.modified_flags == 0 {
return None;
}
#(#update_fields)*
}
}
});
let finished = quote! {
/// Identifies a type of entity, such as `chicken`, `zombie` or `item`.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum EntityType { pub enum EntityType {
#(#entity_type_variants,)* #(#entity_type_variants,)*
@ -2060,6 +2169,64 @@ pub fn build() -> anyhow::Result<()> {
} }
#(#entity_structs)* #(#entity_structs)*
/// An enum encoding the type of n entity along with its metadata.
///
/// Metadata encompases most of an entity's state, except for some
/// basic pieces of information such as position and rotation.
pub enum EntityMeta {
#(#entity_type_variants(#entity_type_variants),)*
}
impl EntityMeta {
pub(super) fn new(typ: EntityType) -> Self {
match typ {
#(EntityType::#entity_type_variants => Self::#entity_type_variants(#entity_type_variants::new()),)*
}
}
pub(super) fn typ(&self) -> EntityType {
match self {
#(Self::#entity_type_variants(_) => EntityType::#entity_type_variants,)*
}
}
pub(super) fn initial_metadata(&self) -> Option<Vec<u8>> {
let mut data = Vec::new();
match self {
#(#initial_metadata_arms)*
}
if data.is_empty() {
None
} else {
data.push(0xff);
Some(data)
}
}
pub(super) fn updated_metadata(&self) -> Option<Vec<u8>> {
let mut data = Vec::new();
match self {
#(#updated_metadata_arms)*
}
if data.is_empty() {
None
} else {
data.push(0xff);
Some(data)
}
}
pub(super) fn clear_modifications(&mut self) {
match self {
#(Self::#entity_type_variants(m) => m.modified_flags = 0,)*
}
}
}
}; };
write_to_out_path("entity.rs", &finished.to_string()) write_to_out_path("entity.rs", &finished.to_string())

View file

@ -4,15 +4,16 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use log::LevelFilter; use log::LevelFilter;
use valence::block::BlockState; use valence::block::BlockState;
use valence::client::GameMode; use valence::client::GameMode;
use valence::config::{Config, Login, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::text::Color; use valence::text::Color;
use valence::{ use valence::{
async_trait, DimensionId, NewClientData, Server, SharedServer, ShutdownResult, TextFormat, async_trait, ChunkPos, ClientMut, DimensionId, Server, ShutdownResult, Text,
TextFormat, WorldId, WorldsMut,
}; };
pub fn main() -> ShutdownResult { pub fn main() -> ShutdownResult {
env_logger::Builder::new() env_logger::Builder::new()
.filter_level(LevelFilter::Trace) .filter_module("valence", LevelFilter::Trace)
.parse_default_env() .parse_default_env()
.init(); .init();
@ -39,63 +40,7 @@ impl Config for Game {
false false
} }
fn init(&self, server: &mut Server) { async fn server_list_ping(&self, _server: &Server, _remote_addr: SocketAddr) -> ServerListPing {
let world_id = server.worlds.create(DimensionId::default());
let world = server.worlds.get_mut(world_id).unwrap();
let chunk_radius = 5;
for z in -chunk_radius..chunk_radius {
for x in -chunk_radius..chunk_radius {
let chunk_id = server.chunks.create(384);
let chunk = server.chunks.get_mut(chunk_id).unwrap();
// Chunks are only visible to clients if all adjacent chunks are loaded.
// This will make the perimiter chunks contain only air.
if x != -chunk_radius
&& x != chunk_radius - 1
&& z != -chunk_radius
&& z != chunk_radius - 1
{
for z in 0..16 {
for x in 0..16 {
for y in 0..50 {
chunk.set_block_state(x, y, z, BlockState::STONE);
}
}
}
}
world.chunks_mut().insert((x, z).into(), chunk_id);
}
}
}
fn update(&self, server: &mut Server) {
let world_id = server.worlds.iter().next().unwrap().0;
server.clients.retain(|_, client| {
if client.created_tick() == server.other.current_tick() {
client.set_world(Some(world_id));
client.set_game_mode(GameMode::Creative);
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
}
if client.is_disconnected() {
server.entities.delete(client.entity());
self.player_count.fetch_sub(1, Ordering::SeqCst);
false
} else {
true
}
});
}
async fn server_list_ping(
&self,
_server: &SharedServer,
_remote_addr: SocketAddr,
) -> ServerListPing {
ServerListPing::Respond { ServerListPing::Respond {
online_players: self.player_count.load(Ordering::SeqCst) as i32, online_players: self.player_count.load(Ordering::SeqCst) as i32,
max_players: MAX_PLAYERS as i32, max_players: MAX_PLAYERS as i32,
@ -104,17 +49,65 @@ impl Config for Game {
} }
} }
async fn login(&self, _server: &SharedServer, _ncd: &NewClientData) -> Login { fn join(
let res = self &self,
_server: &Server,
_client: ClientMut,
worlds: WorldsMut,
) -> Result<WorldId, Text> {
if let Ok(_) = self
.player_count .player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
(count < MAX_PLAYERS).then(|| count + 1) (count < MAX_PLAYERS).then(|| count + 1)
}); })
{
if res.is_ok() { Ok(worlds.iter().next().unwrap().0)
Login::Join
} else { } else {
Login::Disconnect("The server is full!".into()) Err("The server is full!".into())
} }
} }
fn init(&self, _server: &Server, mut worlds: WorldsMut) {
let world_id = worlds.create(DimensionId::default());
let mut world = worlds.get_mut(world_id).unwrap();
let size = 5;
for z in -size..size {
for x in -size..size {
let pos = ChunkPos::new(x, z);
world.chunks.create(pos);
let mut chunk = world.chunks.get_mut(pos).unwrap();
// Chunks are only visible to clients if all adjacent chunks are loaded.
// This will make the perimiter chunks contain only air.
if x != -size && x != size - 1 && z != -size && z != size - 1 {
for z in 0..16 {
for x in 0..16 {
for y in 0..50 {
chunk.set_block_state(x, y, z, BlockState::STONE);
}
}
}
}
}
}
}
fn update(&self, server: &Server, mut worlds: WorldsMut) {
let mut world = worlds.iter_mut().next().unwrap().1;
world.clients.retain(|_, mut client| {
if client.created_tick() == server.current_tick() {
client.set_game_mode(GameMode::Creative);
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0);
}
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
false
} else {
true
}
});
}
} }

View file

@ -1,9 +1,16 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use crate::glm::{self, Number, RealNumber, TVec}; use num::cast::AsPrimitive;
/// An Axis-aligned bounding box in an arbitrary dimension. use crate::glm::{self, Number, RealNumber, TVec, TVec3};
/// An Axis-aligned bounding box in an arbitrary dimension, defined by its
/// minimum and maximum corners.
///
/// This type maintains the invariant that `min <= max` componentwise.
///
/// The generic type `T` can be an integer, which is useful for AABBs on grids.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Aabb<T, const D: usize> { pub struct Aabb<T, const D: usize> {
min: TVec<T, D>, min: TVec<T, D>,
@ -20,7 +27,8 @@ impl<T: Number, const D: usize> Aabb<T, D> {
} }
} }
pub fn point(pos: TVec<T, D>) -> Self { pub fn point(pos: impl Into<TVec<T, D>>) -> Self {
let pos = pos.into();
Self { min: pos, max: pos } Self { min: pos, max: pos }
} }
@ -36,6 +44,24 @@ impl<T: Number, const D: usize> Aabb<T, D> {
self.max - self.min self.max - self.min
} }
/// Moves this AABB by some vector.
pub fn translate(&self, v: impl Into<TVec<T, D>>) -> Self {
let v = v.into();
Self {
min: self.min + v,
max: self.max + v,
}
}
/// Calculates the AABB union, which is the smallest AABB completely
/// encompassing both AABBs.
pub fn union(&self, other: Self) -> Self {
Self {
min: glm::min2(&self.min, &other.min),
max: glm::max2(&self.max, &other.max),
}
}
pub fn collides_with_aabb(&self, other: &Self) -> bool { pub fn collides_with_aabb(&self, other: &Self) -> bool {
let l = glm::less_than_equal(&self.min, &other.max); let l = glm::less_than_equal(&self.min, &other.max);
let r = glm::greater_than_equal(&self.max, &other.min); let r = glm::greater_than_equal(&self.max, &other.min);
@ -43,31 +69,71 @@ impl<T: Number, const D: usize> Aabb<T, D> {
} }
} }
impl<T: Number, const D: usize> Aabb<T, D>
where
i32: AsPrimitive<T>,
{
/// Returns the center (centroid) of this AABB.
pub fn center(&self) -> TVec<T, D> {
(self.min + self.max).map(|c| c / 2.as_())
}
}
impl<T: RealNumber, const D: usize> Aabb<T, D> { impl<T: RealNumber, const D: usize> Aabb<T, D> {
/// Construct an AABB from a center (centroid) and the dimensions of the box /// Construct an AABB from a center (centroid) and the dimensions of the box
/// along each axis. /// along each axis.
pub fn from_center_and_dimensions(center: TVec<T, D>, dims: TVec<T, D>) -> Self { pub fn from_center_and_dimensions(
let half = dims * T::from_subset(&0.5); center: impl Into<TVec<T, D>>,
dims: impl Into<TVec<T, D>>,
) -> Self {
let half = dims.into() * T::from_subset(&0.5);
let center = center.into();
Self { Self {
min: center - half, min: center - half,
max: center + half, max: center + half,
} }
} }
pub fn center(&self) -> TVec<T, D> { pub fn collides_with_sphere(
// TODO: distribute multiplication to avoid intermediate overflow? &self,
(self.min + self.max) * T::from_subset(&0.5) center: impl Into<TVec<T, D>>,
radius: impl Into<T>,
) -> bool {
self.distance_to_point(center.into()) <= radius.into()
} }
pub fn collides_with_sphere(&self, center: TVec<T, D>, radius: T) -> bool { pub fn distance_to_point(&self, p: impl Into<TVec<T, D>>) -> T {
self.distance_to_point(center) <= radius let p = p.into();
}
pub fn distance_to_point(&self, p: TVec<T, D>) -> T {
glm::distance(&p, &glm::clamp_vec(&p, &self.min, &self.max)) glm::distance(&p, &glm::clamp_vec(&p, &self.min, &self.max))
} }
} }
impl<T: Number> Aabb<T, 3>
where
i32: AsPrimitive<T>,
{
pub fn surface_area(&self) -> T {
let d = self.dimensions();
(d.x * d.y + d.y * d.z + d.z * d.x) * 2.as_()
}
}
impl<T: RealNumber> Aabb<T, 3> {
/// Constructs an AABB from a position and the dimensions of the box along
/// each axis. The position is the center of the bottom face of the AABB.
pub fn from_bottom_and_dimensions(
bottom: impl Into<TVec3<T>>,
dims: impl Into<TVec3<T>>,
) -> Self {
let dims = dims.into();
Self::from_center_and_dimensions(bottom, dims).translate([
T::from_subset(&0.0),
dims.y * T::from_subset(&0.5),
T::from_subset(&0.0),
])
}
}
impl<T: Number + Default, const D: usize> Default for Aabb<T, D> { impl<T: Number + Default, const D: usize> Default for Aabb<T, D> {
fn default() -> Self { fn default() -> Self {
let d = T::default(); let d = T::default();

View file

@ -1,4 +1,4 @@
#![allow(clippy::all)] #![allow(clippy::all, missing_docs)]
use std::fmt; use std::fmt;
use std::io::{Read, Write}; use std::io::{Read, Write};

View file

@ -47,6 +47,18 @@ impl Decode for BlockPos {
} }
} }
impl From<(i32, i32, i32)> for BlockPos {
fn from((x, y, z): (i32, i32, i32)) -> Self {
BlockPos::new(x, y, z)
}
}
impl From<[i32; 3]> for BlockPos {
fn from([x, y, z]: [i32; 3]) -> Self {
BlockPos::new(x, y, z)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -1,6 +1,3 @@
// TODO: rename to ByteAngle?
use std::f64::consts::TAU;
use std::io::{Read, Write}; use std::io::{Read, Write};
use crate::protocol::{Decode, Encode}; use crate::protocol::{Decode, Encode};
@ -10,12 +7,12 @@ use crate::protocol::{Decode, Encode};
pub struct ByteAngle(pub u8); pub struct ByteAngle(pub u8);
impl ByteAngle { impl ByteAngle {
pub fn from_radians_f64(f: f64) -> ByteAngle { pub fn from_degrees(f: f32) -> ByteAngle {
ByteAngle((f.rem_euclid(TAU) / TAU * 256.0).round() as u8) ByteAngle((f.rem_euclid(360.0) / 360.0 * 256.0).round() as u8)
} }
pub fn to_radians_f64(self) -> f64 { pub fn to_degrees(self) -> f32 {
self.0 as f64 / 256.0 * TAU self.0 as f32 / 256.0 * 360.0
} }
} }

View file

@ -1,12 +1,14 @@
// TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil // TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil
use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::ops::Deref;
use bitvec::bitvec; use bitvec::bitvec;
use bitvec::vec::BitVec; use bitvec::vec::BitVec;
use num::Integer; use num::Integer;
use rayon::iter::ParallelIterator; use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use crate::block::BlockState; use crate::block::BlockState;
use crate::glm::DVec2; use crate::glm::DVec2;
@ -14,73 +16,99 @@ use crate::packets::play::{
BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange, BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange,
}; };
use crate::protocol::{Encode, Nbt}; use crate::protocol::{Encode, Nbt};
use crate::slotmap::{Key, SlotMap};
use crate::var_int::VarInt; use crate::var_int::VarInt;
use crate::BiomeId; use crate::{BiomeId, Server, Ticks};
pub struct ChunkStore { pub struct Chunks {
sm: SlotMap<Chunk>, chunks: HashMap<ChunkPos, Chunk>,
server: Server,
section_count: u32,
} }
impl ChunkStore { impl Chunks {
pub(crate) fn new() -> Self { pub(crate) fn new(server: Server, section_count: u32) -> Self {
Self { sm: SlotMap::new() } Self {
chunks: HashMap::new(),
server,
section_count,
}
} }
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
self.sm.count() self.chunks.len()
} }
pub fn create(&mut self, section_count: usize) -> ChunkId { pub fn get(&self, pos: ChunkPos) -> Option<&Chunk> {
ChunkId(self.sm.insert(Chunk::new(section_count))) self.chunks.get(&pos)
}
pub fn delete(&mut self, chunk: ChunkId) -> bool {
self.sm.remove(chunk.0).is_some()
}
pub fn get(&self, chunk: ChunkId) -> Option<&Chunk> {
self.sm.get(chunk.0)
}
pub fn get_mut(&mut self, chunk: ChunkId) -> Option<&mut Chunk> {
self.sm.get_mut(chunk.0)
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.sm.clear(); self.chunks.clear();
} }
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkId, &Chunk)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (ChunkId(k), v)) self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
} }
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkId, &mut Chunk)> + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
self.sm.iter_mut().map(|(k, v)| (ChunkId(k), v)) self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkId, &Chunk)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (ChunkId(k), v))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkId, &mut Chunk)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (ChunkId(k), v))
} }
} }
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] impl<'a> ChunksMut<'a> {
pub struct ChunkId(Key); pub(crate) fn new(chunks: &'a mut Chunks) -> Self {
Self(chunks)
}
pub fn create(&mut self, pos: ChunkPos) -> bool {
let chunk = Chunk::new(self.section_count, self.server.current_tick());
self.0.chunks.insert(pos, chunk).is_none()
}
pub fn delete(&mut self, pos: ChunkPos) -> bool {
self.0.chunks.remove(&pos).is_some()
}
pub fn get_mut(&mut self, pos: ChunkPos) -> Option<ChunkMut> {
self.0.chunks.get_mut(&pos).map(ChunkMut)
}
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, ChunkMut)> + '_ {
self.0
.chunks
.iter_mut()
.map(|(&pos, chunk)| (pos, ChunkMut(chunk)))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, ChunkMut)> + '_ {
self.0
.chunks
.par_iter_mut()
.map(|(&pos, chunk)| (pos, ChunkMut(chunk)))
}
}
pub struct ChunksMut<'a>(&'a mut Chunks);
impl<'a> Deref for ChunksMut<'a> {
type Target = Chunks;
fn deref(&self) -> &Self::Target {
self.0
}
}
pub struct Chunk { pub struct Chunk {
sections: Box<[ChunkSection]>, sections: Box<[ChunkSection]>,
// TODO block_entities: HashMap<u32, BlockEntity>, // TODO block_entities: HashMap<u32, BlockEntity>,
/// The MOTION_BLOCKING heightmap
heightmap: Vec<i64>, heightmap: Vec<i64>,
modified: bool, modified: bool,
created_this_tick: bool, created_tick: Ticks,
} }
impl Chunk { impl Chunk {
pub(crate) fn new(section_count: usize) -> Self { pub(crate) fn new(section_count: u32, current_tick: Ticks) -> Self {
let sect = ChunkSection { let sect = ChunkSection {
blocks: [BlockState::default(); 4096], blocks: [BlockState::default(); 4096],
biomes: [BiomeId::default(); 64], biomes: [BiomeId::default(); 64],
@ -89,22 +117,18 @@ impl Chunk {
}; };
let mut chunk = Self { let mut chunk = Self {
sections: vec![sect; section_count].into(), sections: vec![sect; section_count as usize].into(),
heightmap: Vec::new(), heightmap: Vec::new(),
modified: true, modified: true,
created_this_tick: true, created_tick: current_tick,
}; };
chunk.apply_modifications(); ChunkMut(&mut chunk).apply_modifications();
chunk chunk
} }
pub fn created_this_tick(&self) -> bool { pub fn created_tick(&self) -> Ticks {
self.created_this_tick self.created_tick
}
pub(crate) fn clear_created_this_tick(&mut self) {
self.created_this_tick = false;
} }
pub fn height(&self) -> usize { pub fn height(&self) -> usize {
@ -119,20 +143,6 @@ impl Chunk {
} }
} }
pub fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: BlockState) {
if x < 16 && y < self.height() && z < 16 {
let sec = &mut self.sections[y / 16];
let idx = x + z * 16 + y % 16 * 16 * 16;
if block != sec.blocks[idx] {
sec.blocks[idx] = block;
// TODO: set the modified bit.
sec.modified = true;
self.modified = true;
// TODO: update block entity if b could have block entity data.
}
}
}
pub fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId { pub fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId {
if x < 4 && y < self.height() / 4 && z < 4 { if x < 4 && y < self.height() / 4 && z < 4 {
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4]
@ -141,57 +151,21 @@ impl Chunk {
} }
} }
pub fn set_biome(&mut self, x: usize, y: usize, z: usize, b: BiomeId) { /// Gets the chunk data packet for this chunk with the given position. This
if x < 4 && y < self.height() / 4 && z < 4 { /// does not include unapplied changes.
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = b; pub(crate) fn chunk_data_packet(&self, pos: ChunkPos) -> ChunkDataAndUpdateLight {
}
}
/// Gets the chunk data packet with the given number of sections for this
/// chunk. This does not include unapplied changes.
pub(crate) fn chunk_data_packet(
&self,
pos: ChunkPos,
section_count: usize,
) -> ChunkDataAndUpdateLight {
let mut blocks_and_biomes = Vec::new(); let mut blocks_and_biomes = Vec::new();
for i in 0..section_count { for sect in self.sections.iter() {
match self.sections.get(i) { blocks_and_biomes.extend_from_slice(&sect.compact_data);
Some(sect) => {
blocks_and_biomes.extend_from_slice(&sect.compact_data);
}
None => {
// Extra chunk sections are encoded as empty with the default biome.
// non air block count
0i16.encode(&mut blocks_and_biomes).unwrap();
// blocks
encode_paletted_container_single(0, &mut blocks_and_biomes).unwrap();
// biomes
encode_paletted_container_single(0, &mut blocks_and_biomes).unwrap();
}
}
} }
let motion_blocking = if section_count == self.sections.len() {
self.heightmap.clone()
} else {
// This is bad for two reasons:
// - Rebuilding the heightmap from scratch is slow.
// - The new heightmap is subtly wrong because we are building it from blocks
// with the modifications applied already.
// But you shouldn't be using chunks with heights different than the dimensions
// they're residing in anyway, so whatever.
let mut heightmap = Vec::new();
build_heightmap(&self.sections, &mut heightmap);
heightmap
};
ChunkDataAndUpdateLight { ChunkDataAndUpdateLight {
chunk_x: pos.x, chunk_x: pos.x,
chunk_z: pos.z, chunk_z: pos.z,
heightmaps: Nbt(ChunkDataHeightmaps { motion_blocking }), heightmaps: Nbt(ChunkDataHeightmaps {
motion_blocking: self.heightmap.clone(),
}),
blocks_and_biomes, blocks_and_biomes,
block_entities: Vec::new(), // TODO block_entities: Vec::new(), // TODO
trust_edges: true, trust_edges: true,
@ -215,12 +189,44 @@ impl Chunk {
// TODO // TODO
None None
} }
}
pub struct ChunkMut<'a>(&'a mut Chunk);
impl<'a> Deref for ChunkMut<'a> {
type Target = Chunk;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<'a> ChunkMut<'a> {
pub fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: BlockState) {
if x < 16 && y < self.height() && z < 16 {
let sec = &mut self.0.sections[y / 16];
let idx = x + z * 16 + y % 16 * 16 * 16;
if block != sec.blocks[idx] {
sec.blocks[idx] = block;
// TODO: set the modified bit.
sec.modified = true;
self.0.modified = true;
// TODO: update block entity if b could have block entity data.
}
}
}
pub fn set_biome(&mut self, x: usize, y: usize, z: usize, b: BiomeId) {
if x < 4 && y < self.height() / 4 && z < 4 {
self.0.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = b;
}
}
pub(crate) fn apply_modifications(&mut self) { pub(crate) fn apply_modifications(&mut self) {
if self.modified { if self.modified {
self.modified = false; self.0.modified = false;
for sect in self.sections.iter_mut() { for sect in self.0.sections.iter_mut() {
if sect.modified { if sect.modified {
sect.modified = false; sect.modified = false;
@ -253,7 +259,7 @@ impl Chunk {
} }
} }
build_heightmap(&self.sections, &mut self.heightmap); build_heightmap(&self.0.sections, &mut self.0.heightmap);
} }
} }
} }
@ -303,6 +309,24 @@ impl From<(i32, i32)> for ChunkPos {
} }
} }
impl Into<(i32, i32)> for ChunkPos {
fn into(self) -> (i32, i32) {
(self.x, self.z)
}
}
impl From<[i32; 2]> for ChunkPos {
fn from([x, z]: [i32; 2]) -> Self {
(x, z).into()
}
}
impl Into<[i32; 2]> for ChunkPos {
fn into(self) -> [i32; 2] {
[self.x, self.z]
}
}
/// A 16x16x16 section of blocks, biomes, and light in a chunk. /// A 16x16x16 section of blocks, biomes, and light in a chunk.
#[derive(Clone)] #[derive(Clone)]
struct ChunkSection { struct ChunkSection {

View file

@ -1,41 +1,50 @@
use std::collections::hash_map::Entry; use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::ops::Deref;
use flume::{Receiver, Sender, TrySendError}; use flume::{Receiver, Sender, TrySendError};
use glm::DVec3; use glm::DVec3;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use uuid::Uuid;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::chunk::ChunkId;
use crate::config::{ use crate::config::{
Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId, Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId,
}; };
use crate::entity::EntityType;
pub use crate::packets::play::GameMode; pub use crate::packets::play::GameMode;
use crate::packets::play::{ use crate::packets::play::{
Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic,
BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState, BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState,
ChangeGameStateReason, ClientPlayPacket, DimensionCodec, DimensionType, DimensionTypeRegistry, ChangeGameStateReason, ClientPlayPacket, DestroyEntities, DimensionCodec, DimensionType,
DimensionTypeRegistryEntry, Disconnect, JoinGame, KeepAliveClientbound, PlayerPositionAndLook, DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, JoinGame, KeepAliveClientbound,
PlayerPositionAndLookFlags, ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, PlayerPositionAndLook, PlayerPositionAndLookFlags, ServerPlayPacket, SpawnPosition,
UpdateViewPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition,
}; };
use crate::protocol::{BoundedInt, Nbt}; use crate::protocol::{BoundedInt, Nbt};
use crate::server::{Other, ServerPacketChannels}; use crate::server::ServerPacketChannels;
use crate::slotmap::{Key, SlotMap}; use crate::slotmap::{Key, SlotMap};
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance}; use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
use crate::var_int::VarInt; use crate::var_int::VarInt;
use crate::world::WorldId;
use crate::{ use crate::{
glm, ident, ChunkPos, ChunkStore, EntityId, EntityStore, Server, Text, Ticks, WorldStore, glm, ident, ChunkPos, Chunks, Entities, EntityId, Server, Text, Ticks, LIBRARY_NAMESPACE,
LIBRARY_NAMESPACE,
}; };
pub struct ClientStore { pub struct Clients {
sm: SlotMap<Client>, sm: SlotMap<Client>,
} }
impl ClientStore { pub struct ClientsMut<'a>(&'a mut Clients);
impl<'a> Deref for ClientsMut<'a> {
type Target = Clients;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Clients {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { sm: SlotMap::new() } Self { sm: SlotMap::new() }
} }
@ -44,43 +53,58 @@ impl ClientStore {
self.sm.count() self.sm.count()
} }
pub(crate) fn create(&mut self, client: Client) -> ClientId {
ClientId(self.sm.insert(client))
}
pub fn delete(&mut self, client: ClientId) -> bool {
self.sm.remove(client.0).is_some()
}
pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client) -> bool) {
self.sm.retain(|k, v| f(ClientId(k), v))
}
pub fn get(&self, client: ClientId) -> Option<&Client> { pub fn get(&self, client: ClientId) -> Option<&Client> {
self.sm.get(client.0) self.sm.get(client.0)
} }
pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client> {
self.sm.get_mut(client.0)
}
pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (ClientId(k), v)) self.sm.iter().map(|(k, v)| (ClientId(k), v))
} }
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client)> + '_ {
self.sm.iter_mut().map(|(k, v)| (ClientId(k), v))
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (ClientId(k), v)) self.sm.par_iter().map(|(k, v)| (ClientId(k), v))
} }
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, &mut Client)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v))
}
} }
impl<'a> ClientsMut<'a> {
pub(crate) fn new(c: &'a mut Clients) -> Self {
Self(c)
}
pub fn reborrow(&mut self) -> ClientsMut {
ClientsMut(self.0)
}
pub(crate) fn create(&mut self, client: Client) -> ClientId {
ClientId(self.0.sm.insert(client))
}
pub fn delete(&mut self, client: ClientId) -> bool {
self.0.sm.remove(client.0).is_some()
}
pub fn retain(&mut self, mut f: impl FnMut(ClientId, ClientMut) -> bool) {
self.0.sm.retain(|k, v| f(ClientId(k), ClientMut(v)))
}
pub fn get_mut(&mut self, client: ClientId) -> Option<ClientMut> {
self.0.sm.get_mut(client.0).map(ClientMut)
}
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, ClientMut)> + '_ {
self.0
.sm
.iter_mut()
.map(|(k, v)| (ClientId(k), ClientMut(v)))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, ClientMut)> + '_ {
self.0
.sm
.par_iter_mut()
.map(|(k, v)| (ClientId(k), ClientMut(v)))
}
}
pub struct ClientId(Key); pub struct ClientId(Key);
/// Represents a client connected to the server after logging in. /// Represents a client connected to the server after logging in.
@ -88,11 +112,10 @@ pub struct Client {
/// Setting this to `None` disconnects the client. /// Setting this to `None` disconnects the client.
send: Option<Sender<ClientPlayPacket>>, send: Option<Sender<ClientPlayPacket>>,
recv: Receiver<ServerPlayPacket>, recv: Receiver<ServerPlayPacket>,
/// The entity this client is associated with.
entity: EntityId,
/// The tick this client was created. /// The tick this client was created.
created_tick: Ticks, created_tick: Ticks,
username: String, username: String,
uuid: Uuid,
on_ground: bool, on_ground: bool,
new_position: DVec3, new_position: DVec3,
old_position: DVec3, old_position: DVec3,
@ -113,9 +136,6 @@ pub struct Client {
spawn_position_yaw: f32, spawn_position_yaw: f32,
/// If spawn_position or spawn_position_yaw were modified this tick. /// If spawn_position or spawn_position_yaw were modified this tick.
modified_spawn_position: bool, modified_spawn_position: bool,
/// The world that this client was in at the end of the previous tick.
new_world: Option<WorldId>,
old_world: Option<WorldId>,
events: Vec<Event>, events: Vec<Event>,
/// The ID of the last keepalive sent. /// The ID of the last keepalive sent.
last_keepalive_id: i64, last_keepalive_id: i64,
@ -127,9 +147,7 @@ pub struct Client {
/// This is used to determine what entity create/destroy packets should be /// This is used to determine what entity create/destroy packets should be
/// sent. /// sent.
loaded_entities: HashSet<EntityId>, loaded_entities: HashSet<EntityId>,
hidden_entities: HashSet<EntityId>, loaded_chunks: HashSet<ChunkPos>,
/// Loaded chunks and their positions.
loaded_chunks: HashMap<ChunkPos, ChunkId>,
new_game_mode: GameMode, new_game_mode: GameMode,
old_game_mode: GameMode, old_game_mode: GameMode,
settings: Option<Settings>, settings: Option<Settings>,
@ -137,11 +155,21 @@ pub struct Client {
// TODO: time, weather // TODO: time, weather
} }
pub struct ClientMut<'a>(&'a mut Client);
impl<'a> Deref for ClientMut<'a> {
type Target = Client;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Client { impl Client {
pub(crate) fn new( pub(crate) fn new(
packet_channels: ServerPacketChannels, packet_channels: ServerPacketChannels,
entity: EntityId,
username: String, username: String,
uuid: Uuid,
server: &Server, server: &Server,
) -> Self { ) -> Self {
let (send, recv) = packet_channels; let (send, recv) = packet_channels;
@ -149,9 +177,9 @@ impl Client {
Self { Self {
send: Some(send), send: Some(send),
recv, recv,
entity,
created_tick: server.current_tick(), created_tick: server.current_tick(),
username, username,
uuid,
on_ground: false, on_ground: false,
new_position: DVec3::default(), new_position: DVec3::default(),
old_position: DVec3::default(), old_position: DVec3::default(),
@ -163,16 +191,13 @@ impl Client {
spawn_position: BlockPos::default(), spawn_position: BlockPos::default(),
spawn_position_yaw: 0.0, spawn_position_yaw: 0.0,
modified_spawn_position: true, modified_spawn_position: true,
old_world: None,
new_world: None,
events: Vec::new(), events: Vec::new(),
last_keepalive_id: 0, last_keepalive_id: 0,
got_keepalive: true, got_keepalive: true,
new_max_view_distance: 16, new_max_view_distance: 16,
old_max_view_distance: 0, old_max_view_distance: 0,
loaded_entities: HashSet::new(), loaded_entities: HashSet::new(),
hidden_entities: HashSet::new(), loaded_chunks: HashSet::new(),
loaded_chunks: HashMap::new(),
new_game_mode: GameMode::Survival, new_game_mode: GameMode::Survival,
old_game_mode: GameMode::Survival, old_game_mode: GameMode::Survival,
settings: None, settings: None,
@ -187,6 +212,10 @@ impl Client {
&self.username &self.username
} }
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn position(&self) -> DVec3 { pub fn position(&self) -> DVec3 {
self.new_position self.new_position
} }
@ -199,80 +228,14 @@ impl Client {
self.pitch self.pitch
} }
pub fn teleport(&mut self, pos: impl Into<DVec3>, yaw: f32, pitch: f32) {
self.new_position = pos.into();
self.yaw = yaw;
self.pitch = pitch;
if !self.teleported_this_tick {
self.teleported_this_tick = true;
self.pending_teleports = match self.pending_teleports.checked_add(1) {
Some(n) => n,
None => {
self.disconnect("Too many pending teleports");
return;
}
};
self.teleport_id_counter = self.teleport_id_counter.wrapping_add(1);
}
}
pub fn game_mode(&self) -> GameMode { pub fn game_mode(&self) -> GameMode {
self.new_game_mode self.new_game_mode
} }
pub fn set_game_mode(&mut self, new_game_mode: GameMode) {
self.new_game_mode = new_game_mode;
}
pub fn on_ground(&self) -> bool { pub fn on_ground(&self) -> bool {
self.on_ground self.on_ground
} }
/// Changes the point at which compasses point at.
pub fn set_spawn_position(&mut self, pos: impl Into<BlockPos>, yaw_degrees: f32) {
let pos = pos.into();
if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw {
self.spawn_position = pos;
self.spawn_position_yaw = yaw_degrees;
self.modified_spawn_position = true;
}
}
pub fn world(&self) -> Option<WorldId> {
self.new_world
}
pub fn set_world(&mut self, new_world: Option<WorldId>) {
self.new_world = new_world;
}
/// Attempts to enqueue a play packet to be sent to this client. The client
/// is disconnected if the clientbound packet buffer is full.
pub(crate) fn send_packet(&mut self, packet: impl Into<ClientPlayPacket>) {
send_packet(&mut self.send, packet);
}
pub fn disconnect(&mut self, reason: impl Into<Text>) {
if self.send.is_some() {
let txt = reason.into();
log::info!("disconnecting client '{}': \"{txt}\"", self.username);
self.send_packet(Disconnect { reason: txt });
self.send = None;
}
}
pub fn disconnect_no_reason(&mut self) {
if self.send.is_some() {
log::info!("disconnecting client '{}' (no reason)", self.username);
self.send = None;
}
}
pub fn is_disconnected(&self) -> bool { pub fn is_disconnected(&self) -> bool {
self.send.is_none() self.send.is_none()
} }
@ -285,28 +248,91 @@ impl Client {
self.new_max_view_distance self.new_max_view_distance
} }
/// The new view distance is clamped to `2..=32`.
pub fn set_max_view_distance(&mut self, dist: u8) {
self.new_max_view_distance = dist.clamp(2, 32);
}
pub fn settings(&self) -> Option<&Settings> { pub fn settings(&self) -> Option<&Settings> {
self.settings.as_ref() self.settings.as_ref()
} }
}
/// Returns the entity this client is backing. impl<'a> ClientMut<'a> {
pub fn entity(&self) -> EntityId { pub(crate) fn new(client: &'a mut Client) -> Self {
self.entity Self(client)
}
pub fn reborrow(&mut self) -> ClientMut {
ClientMut(self.0)
}
pub fn teleport(&mut self, pos: impl Into<DVec3>, yaw: f32, pitch: f32) {
self.0.new_position = pos.into();
self.0.yaw = yaw;
self.0.pitch = pitch;
if !self.teleported_this_tick {
self.0.teleported_this_tick = true;
self.0.pending_teleports = match self.0.pending_teleports.checked_add(1) {
Some(n) => n,
None => {
self.disconnect("Too many pending teleports");
return;
}
};
self.0.teleport_id_counter = self.0.teleport_id_counter.wrapping_add(1);
}
}
pub fn set_game_mode(&mut self, new_game_mode: GameMode) {
self.0.new_game_mode = new_game_mode;
}
/// Changes the point at which compasses point at.
pub fn set_spawn_position(&mut self, pos: impl Into<BlockPos>, yaw_degrees: f32) {
let pos = pos.into();
if pos != self.0.spawn_position || yaw_degrees != self.0.spawn_position_yaw {
self.0.spawn_position = pos;
self.0.spawn_position_yaw = yaw_degrees;
self.0.modified_spawn_position = true;
}
}
/// Attempts to enqueue a play packet to be sent to this client. The client
/// is disconnected if the clientbound packet buffer is full.
pub(crate) fn send_packet(&mut self, packet: impl Into<ClientPlayPacket>) {
send_packet(&mut self.0.send, packet);
}
pub fn disconnect(&mut self, reason: impl Into<Text>) {
if self.0.send.is_some() {
let txt = reason.into();
log::info!("disconnecting client '{}': \"{txt}\"", self.0.username);
self.send_packet(Disconnect { reason: txt });
self.0.send = None;
}
}
pub fn disconnect_no_reason(&mut self) {
if self.0.send.is_some() {
log::info!("disconnecting client '{}' (no reason)", self.0.username);
self.0.send = None;
}
}
/// The new view distance is clamped to `2..=32`.
pub fn set_max_view_distance(&mut self, dist: u8) {
self.0.new_max_view_distance = dist.clamp(2, 32);
} }
pub(crate) fn update( pub(crate) fn update(
&mut self, &mut self,
entities: &EntityStore, server: &Server,
worlds: &WorldStore, entities: &Entities,
chunks: &ChunkStore, chunks: &Chunks,
other: &Other, dimension_id: DimensionId,
) { ) {
self.events.clear(); self.0.events.clear();
if self.is_disconnected() { if self.is_disconnected() {
return; return;
@ -318,34 +344,28 @@ impl Client {
// Mark the client as disconnected when appropriate. // Mark the client as disconnected when appropriate.
// We do this check after handling serverbound packets so that none are lost. // We do this check after handling serverbound packets so that none are lost.
if self.recv.is_disconnected() if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) {
|| self.send.as_ref().map_or(true, |s| s.is_disconnected()) self.0.send = None;
|| entities.get(self.entity).is_none()
{
self.send = None;
return; return;
} }
let world = self.new_world.and_then(|w| worlds.get(w)); let dimension = server.dimension(dimension_id);
let dim_id = world.map_or(DimensionId::default(), |w| w.dimension());
let dim = other.dimension(dim_id);
// Send the join game packet and other initial packets. We defer this until now // Send the join game packet and other initial packets. We defer this until now
// so that the user can set the client's location, game mode, etc. // so that the user can set the client's location, game mode, etc.
if self.created_tick == other.current_tick() { if self.created_tick == server.current_tick() {
self.send_packet(JoinGame { self.send_packet(JoinGame {
entity_id: self.entity.to_network_id(), entity_id: 0, // EntityId 0 is reserved for clients.
is_hardcore: false, // TODO is_hardcore: false, // TODO
gamemode: self.new_game_mode, gamemode: self.new_game_mode,
previous_gamemode: self.old_game_mode, previous_gamemode: self.old_game_mode,
dimension_names: other dimension_names: server
.dimensions() .dimensions()
.map(|(id, _)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0)) .map(|(id, _)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0))
.collect(), .collect(),
dimension_codec: Nbt(make_dimension_codec(other)), dimension_codec: Nbt(make_dimension_codec(server)),
dimension: Nbt(to_dimension_registry_item(dim)), dimension: Nbt(to_dimension_registry_item(dimension)),
dimension_name: ident!("{LIBRARY_NAMESPACE}:dimension_{}", dim_id.0), dimension_name: ident!("{LIBRARY_NAMESPACE}:dimension_{}", dimension_id.0),
hashed_seed: 0, hashed_seed: 0,
max_players: VarInt(0), max_players: VarInt(0),
view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)), view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)),
@ -357,17 +377,17 @@ impl Client {
}); });
self.teleport(self.position(), self.yaw(), self.pitch()); self.teleport(self.position(), self.yaw(), self.pitch());
} else if self.old_game_mode != self.new_game_mode { } else if self.0.old_game_mode != self.0.new_game_mode {
self.old_game_mode = self.new_game_mode; self.0.old_game_mode = self.0.new_game_mode;
self.send_packet(ChangeGameState { self.send_packet(ChangeGameState {
reason: ChangeGameStateReason::ChangeGameMode, reason: ChangeGameStateReason::ChangeGameMode,
value: self.new_game_mode as i32 as f32, value: self.0.new_game_mode as i32 as f32,
}); });
} }
// Update the players spawn position (compass position) // Update the players spawn position (compass position)
if self.modified_spawn_position { if self.0.modified_spawn_position {
self.modified_spawn_position = false; self.0.modified_spawn_position = false;
self.send_packet(SpawnPosition { self.send_packet(SpawnPosition {
location: self.spawn_position, location: self.spawn_position,
@ -376,61 +396,42 @@ impl Client {
} }
// Update view distance fog on the client if necessary. // Update view distance fog on the client if necessary.
if self.old_max_view_distance != self.new_max_view_distance { if self.0.old_max_view_distance != self.0.new_max_view_distance {
self.old_max_view_distance = self.new_max_view_distance; self.0.old_max_view_distance = self.0.new_max_view_distance;
if self.created_tick != other.current_tick() { if self.0.created_tick != server.current_tick() {
self.send_packet(UpdateViewDistance { self.send_packet(UpdateViewDistance {
view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)), view_distance: BoundedInt(VarInt(self.0.new_max_view_distance as i32)),
}) })
} }
} }
// Check if it's time to send another keepalive. // Check if it's time to send another keepalive.
if other.current_tick() % (other.tick_rate() * 8) == 0 { if server.current_tick() % (server.tick_rate() * 8) == 0 {
if self.got_keepalive { if self.0.got_keepalive {
let id = rand::random(); let id = rand::random();
self.send_packet(KeepAliveClientbound { id }); self.send_packet(KeepAliveClientbound { id });
self.last_keepalive_id = id; self.0.last_keepalive_id = id;
self.got_keepalive = false; self.0.got_keepalive = false;
} else { } else {
self.disconnect("Timed out (no keepalive response)"); self.disconnect("Timed out (no keepalive response)");
} }
} }
// Load, update, and unload chunks. // The actual view distance.
if self.old_world != self.new_world {
let old_dim = self
.old_world
.and_then(|w| worlds.get(w))
.map_or(DimensionId::default(), |w| w.dimension());
let new_dim = dim_id;
if old_dim != new_dim {
// Changing dimensions automatically unloads all chunks and
// entities.
self.loaded_chunks.clear();
self.loaded_entities.clear();
todo!("need to send respawn packet for new dimension");
}
self.old_world = self.new_world;
}
let view_dist = self let view_dist = self
.0
.settings .settings
.as_ref() .as_ref()
.map_or(2, |s| s.view_distance) .map_or(2, |s| s.view_distance)
.min(self.new_max_view_distance); .min(self.new_max_view_distance);
let center = ChunkPos::from_xz(self.new_position.xz()); let center = ChunkPos::from_xz(self.0.new_position.xz());
// Send the update view position packet if the client changes the chunk section // Send the update view position packet if the client changes the chunk section
// they're in. // they're in.
{ {
let old_section = self.old_position.map(|n| (n / 16.0) as i32); let old_section = self.0.old_position.map(|n| (n / 16.0) as i32);
let new_section = self.new_position.map(|n| (n / 16.0) as i32); let new_section = self.0.new_position.map(|n| (n / 16.0) as i32);
if old_section != new_section { if old_section != new_section {
self.send_packet(UpdateViewPosition { self.send_packet(UpdateViewPosition {
@ -440,50 +441,41 @@ impl Client {
} }
} }
// Unload deleted chunks and those outside the view distance. Also update // Update existing chunks and unload those outside the view distance. Chunks
// existing chunks. // that have been overwritten also need to be unloaded.
self.loaded_chunks.retain(|&pos, &mut chunk_id| { self.0.loaded_chunks.retain(|&pos| {
if let Some(chunk) = chunks.get(chunk_id) { // The cache stops chunk data packets from needing to be sent when a player
// The cache stops chunk data packets from needing to be sent when a player is // moves to an adjacent chunk and back to the original.
// jumping between adjacent chunks. let cache = 2;
let cache = 2;
if is_chunk_in_view_distance(center, pos, view_dist + cache) { if let Some(chunk) = chunks.get(pos) {
if is_chunk_in_view_distance(center, pos, view_dist + cache)
&& chunk.created_tick() != server.current_tick()
{
if let Some(pkt) = chunk.block_change_packet(pos) { if let Some(pkt) = chunk.block_change_packet(pos) {
send_packet(&mut self.send, pkt); send_packet(&mut self.0.send, pkt);
} }
true return true;
} else {
send_packet(
&mut self.send,
UnloadChunk {
chunk_x: pos.x,
chunk_z: pos.z,
},
);
false
} }
} else {
send_packet(
&mut self.send,
UnloadChunk {
chunk_x: pos.x,
chunk_z: pos.z,
},
);
false
} }
send_packet(
&mut self.0.send,
UnloadChunk {
chunk_x: pos.x,
chunk_z: pos.z,
},
);
false
}); });
// Load new chunks within the view distance // Load new chunks within the view distance
for pos in chunks_in_view_distance(center, view_dist) { for pos in chunks_in_view_distance(center, view_dist) {
if let Entry::Vacant(ve) = self.loaded_chunks.entry(pos) { if let Some(chunk) = chunks.get(pos) {
if let Some(&chunk_id) = world.and_then(|w| w.chunks().get(&pos)) { if self.0.loaded_chunks.insert(pos) {
if let Some(chunk) = chunks.get(chunk_id) { self.send_packet(chunk.chunk_data_packet(pos));
ve.insert(chunk_id); if let Some(pkt) = chunk.block_change_packet(pos) {
self.send_packet(chunk.chunk_data_packet(pos, (dim.height / 16) as usize)); self.send_packet(pkt);
if let Some(pkt) = chunk.block_change_packet(pos) {
self.send_packet(pkt);
}
} }
} }
} }
@ -491,25 +483,69 @@ impl Client {
// This is done after the chunks are loaded so that the "downloading terrain" // This is done after the chunks are loaded so that the "downloading terrain"
// screen is closed at the appropriate time. // screen is closed at the appropriate time.
if self.teleported_this_tick {
self.teleported_this_tick = false; // TODO: temporarily broken
// if self.0.teleported_this_tick {
// self.0.teleported_this_tick = false;
self.send_packet(PlayerPositionAndLook { // self.send_packet(dbg!(PlayerPositionAndLook {
x: self.new_position.x, // position: self.new_position,
y: self.new_position.y, // yaw: self.yaw,
z: self.new_position.z, // pitch: self.pitch,
yaw: self.yaw, // flags: PlayerPositionAndLookFlags::new(false, false, false, false, false),
pitch: self.pitch, // teleport_id: VarInt((self.teleport_id_counter - 1) as i32),
flags: PlayerPositionAndLookFlags::new(false, false, false, false, false), // dismount_vehicle: false,
teleport_id: VarInt((self.teleport_id_counter - 1) as i32), // }));
dismount_vehicle: false, // }
let mut entities_to_unload = Vec::new();
// Update all entities that are visible and unload entities that are no
// longer visible.
self.0.loaded_entities.retain(|&id| {
if let Some(entity) = entities.get(id) {
if glm::distance(&self.0.new_position, &entity.position())
<= view_dist as f64 * 16.0
{
todo!("update entity");
return true;
}
}
entities_to_unload.push(VarInt(id.to_network_id()));
false
});
if !entities_to_unload.is_empty() {
self.send_packet(DestroyEntities {
entities: entities_to_unload,
}); });
} }
self.old_position = self.new_position; // Spawn new entities within the view distance.
for (id, entity) in entities.iter() {
if glm::distance(&self.position(), &entity.position()) <= view_dist as f64 * 16.0
&& entity.typ() != EntityType::Marker
&& self.0.loaded_entities.insert(id)
{
self.send_packet(
entity
.spawn_packet(id)
.expect("should not be a marker entity"),
);
if let Some(meta) = entity.initial_metadata_packet(id) {
self.send_packet(meta);
}
}
}
self.0.old_position = self.0.new_position;
} }
fn handle_serverbound_packet(&mut self, pkt: ServerPlayPacket) { fn handle_serverbound_packet(&mut self, pkt: ServerPlayPacket) {
let client = &mut self.0;
fn handle_movement_packet( fn handle_movement_packet(
client: &mut Client, client: &mut Client,
new_position: DVec3, new_position: DVec3,
@ -536,18 +572,18 @@ impl Client {
match pkt { match pkt {
ServerPlayPacket::TeleportConfirm(p) => { ServerPlayPacket::TeleportConfirm(p) => {
if self.pending_teleports == 0 { if client.pending_teleports == 0 {
self.disconnect("Unexpected teleport confirmation"); self.disconnect("Unexpected teleport confirmation");
return; return;
} }
let got = p.teleport_id.0 as u32; let got = p.teleport_id.0 as u32;
let expected = self let expected = client
.teleport_id_counter .teleport_id_counter
.wrapping_sub(self.pending_teleports); .wrapping_sub(client.pending_teleports);
if got == expected { if got == expected {
self.pending_teleports -= 1; client.pending_teleports -= 1;
} else { } else {
self.disconnect(format!( self.disconnect(format!(
"Unexpected teleport ID (expected {expected}, got {got})" "Unexpected teleport ID (expected {expected}, got {got})"
@ -559,7 +595,7 @@ impl Client {
ServerPlayPacket::ChatMessageServerbound(_) => {} ServerPlayPacket::ChatMessageServerbound(_) => {}
ServerPlayPacket::ClientStatus(_) => {} ServerPlayPacket::ClientStatus(_) => {}
ServerPlayPacket::ClientSettings(p) => { ServerPlayPacket::ClientSettings(p) => {
let old = self.settings.replace(Settings { let old = client.settings.replace(Settings {
locale: p.locale.0, locale: p.locale.0,
view_distance: p.view_distance.0, view_distance: p.view_distance.0,
chat_mode: p.chat_mode, chat_mode: p.chat_mode,
@ -569,7 +605,7 @@ impl Client {
allow_server_listings: p.allow_server_listings, allow_server_listings: p.allow_server_listings,
}); });
self.events.push(Event::SettingsChanged(old)); client.events.push(Event::SettingsChanged(old));
} }
ServerPlayPacket::TabCompleteServerbound(_) => {} ServerPlayPacket::TabCompleteServerbound(_) => {}
ServerPlayPacket::ClickWindowButton(_) => {} ServerPlayPacket::ClickWindowButton(_) => {}
@ -581,39 +617,36 @@ impl Client {
ServerPlayPacket::InteractEntity(_) => {} ServerPlayPacket::InteractEntity(_) => {}
ServerPlayPacket::GenerateStructure(_) => {} ServerPlayPacket::GenerateStructure(_) => {}
ServerPlayPacket::KeepAliveServerbound(p) => { ServerPlayPacket::KeepAliveServerbound(p) => {
if self.got_keepalive { let last_keepalive_id = client.last_keepalive_id;
if client.got_keepalive {
self.disconnect("Unexpected keepalive"); self.disconnect("Unexpected keepalive");
} else if p.id != self.last_keepalive_id { } else if p.id != last_keepalive_id {
self.disconnect(format!( self.disconnect(format!(
"Keepalive ids don't match (expected {}, got {})", "Keepalive ids don't match (expected {}, got {})",
self.last_keepalive_id, p.id last_keepalive_id, p.id
)); ));
} else { } else {
self.got_keepalive = true; client.got_keepalive = true;
} }
} }
ServerPlayPacket::LockDifficulty(_) => {} ServerPlayPacket::LockDifficulty(_) => {}
ServerPlayPacket::PlayerPosition(p) => handle_movement_packet( ServerPlayPacket::PlayerPosition(p) => {
self, handle_movement_packet(client, p.position, client.yaw, client.pitch, p.on_ground)
glm::vec3(p.x, p.feet_y, p.z), }
self.yaw, ServerPlayPacket::PlayerPositionAndRotation(p) => {
self.pitch, handle_movement_packet(client, p.position, p.yaw, p.pitch, p.on_ground)
p.on_ground, }
),
ServerPlayPacket::PlayerPositionAndRotation(p) => handle_movement_packet(
self,
glm::vec3(p.x, p.feet_y, p.z),
p.yaw,
p.pitch,
p.on_ground,
),
ServerPlayPacket::PlayerRotation(p) => { ServerPlayPacket::PlayerRotation(p) => {
handle_movement_packet(self, self.new_position, p.yaw, p.pitch, p.on_ground) handle_movement_packet(client, client.new_position, p.yaw, p.pitch, p.on_ground)
} }
ServerPlayPacket::PlayerMovement(p) => { ServerPlayPacket::PlayerMovement(p) => handle_movement_packet(
handle_movement_packet(self, self.new_position, self.yaw, self.pitch, p.on_ground) client,
} client.new_position,
client.yaw,
client.pitch,
p.on_ground,
),
ServerPlayPacket::VehicleMoveServerbound(_) => {} ServerPlayPacket::VehicleMoveServerbound(_) => {}
ServerPlayPacket::SteerBoat(_) => {} ServerPlayPacket::SteerBoat(_) => {}
ServerPlayPacket::PickItem(_) => {} ServerPlayPacket::PickItem(_) => {}
@ -700,9 +733,9 @@ fn send_packet(send_opt: &mut Option<Sender<ClientPlayPacket>>, pkt: impl Into<C
} }
} }
fn make_dimension_codec(other: &Other) -> DimensionCodec { fn make_dimension_codec(server: &Server) -> DimensionCodec {
let mut dims = Vec::new(); let mut dims = Vec::new();
for (id, dim) in other.dimensions() { for (id, dim) in server.dimensions() {
let id = id.0 as i32; let id = id.0 as i32;
dims.push(DimensionTypeRegistryEntry { dims.push(DimensionTypeRegistryEntry {
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"), name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
@ -712,7 +745,7 @@ fn make_dimension_codec(other: &Other) -> DimensionCodec {
} }
let mut biomes = Vec::new(); let mut biomes = Vec::new();
for (id, biome) in other.biomes() { for (id, biome) in server.biomes() {
biomes.push(to_biome_registry_item(biome, id.0 as i32)); biomes.push(to_biome_registry_item(biome, id.0 as i32));
} }

View file

@ -5,15 +5,17 @@ use std::panic::{RefUnwindSafe, UnwindSafe};
use async_trait::async_trait; use async_trait::async_trait;
use tokio::runtime::Handle as TokioHandle; use tokio::runtime::Handle as TokioHandle;
use crate::{ident, Id, Identifier, NewClientData, Server, SharedServer, Text, Ticks}; use crate::client::ClientMut;
use crate::{ident, Identifier, NewClientData, Server, Text, Ticks, WorldId, WorldsMut};
/// A trait containing callbacks which are invoked by the running Minecraft /// A trait containing callbacks which are invoked by the running Minecraft
/// server. /// server.
/// ///
/// The config is used from multiple threads and must therefore implement /// The config is used from multiple threads and must therefore implement
/// `Send` and `Sync`. From within a single thread, methods are never invoked /// `Send` and `Sync`. From within a single thread, methods are never invoked
/// recursively. In other words, a mutex can always be aquired at the beginning /// recursively by the library. In other words, a mutex can be aquired at
/// of a method and released at the end without risk of deadlocking. /// the beginning of a method and released at the end without risk of
/// deadlocking.
/// ///
/// This trait uses the [async_trait](https://docs.rs/async-trait/latest/async_trait/) attribute macro. /// This trait uses the [async_trait](https://docs.rs/async-trait/latest/async_trait/) attribute macro.
/// This will be removed once `impl Trait` in return position in traits is /// This will be removed once `impl Trait` in return position in traits is
@ -48,6 +50,9 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// ///
/// The tick rate must be greater than zero. /// The tick rate must be greater than zero.
/// ///
/// Note that the official Minecraft client only processes packets at 20hz,
/// so there is little benefit to a tick rate higher than 20.
///
/// # Default Implementation /// # Default Implementation
/// Returns `20`, which is the same as Minecraft's official server. /// Returns `20`, which is the same as Minecraft's official server.
fn tick_rate(&self) -> Ticks { fn tick_rate(&self) -> Ticks {
@ -140,6 +145,45 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
vec![Biome::default()] vec![Biome::default()]
} }
/// Called when the server receives a Server List Ping query.
/// Data for the response can be provided or the query can be ignored.
///
/// This method is called from within a tokio runtime.
///
/// # Default Implementation
/// The query is ignored.
async fn server_list_ping(&self, server: &Server, remote_addr: SocketAddr) -> ServerListPing {
ServerListPing::Ignore
}
/// Called asynchronously for each client after successful authentication
/// (if online mode is enabled) to determine if they can continue to join
/// the server. On success, [`Config::join`] is called with the new
/// client. If this method returns with `Err(reason)`, then the client is
/// immediately disconnected with the given reason.
///
/// This method is the appropriate place to perform asynchronous
/// operations such as database queries which may take some time to
/// complete. If you need access to the worlds on the server and don't need
/// async, see [`Config::join`].
///
/// This method is called from within a tokio runtime.
///
/// # Default Implementation
/// The client is allowed to join unconditionally.
async fn login(&self, server: &Server, ncd: &NewClientData) -> Result<(), Text> {
Ok(())
}
/// Called after a successful [`Config::login`] to determine what world the
/// new client should join. If this method returns with `Err(reason)`, then
/// the client is immediately disconnected with the given reason.
///
/// If the returned [`WorldId`] is invalid, then the client is disconnected.
///
/// This method is called from within a tokio runtime.
fn join(&self, server: &Server, client: ClientMut, worlds: WorldsMut) -> Result<WorldId, Text>;
/// Called after the server is created, but prior to accepting connections /// Called after the server is created, but prior to accepting connections
/// and entering the update loop. /// and entering the update loop.
/// ///
@ -147,10 +191,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// no connections to the server will be made until this function returns. /// no connections to the server will be made until this function returns.
/// ///
/// This method is called from within a tokio runtime. /// This method is called from within a tokio runtime.
/// fn init(&self, server: &Server, worlds: WorldsMut) {}
/// # Default Implementation
/// The default implementation does nothing.
fn init(&self, server: &mut Server) {}
/// Called once at the beginning of every server update (also known as /// Called once at the beginning of every server update (also known as
/// a "tick"). /// a "tick").
@ -162,37 +203,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// ///
/// # Default Implementation /// # Default Implementation
/// The default implementation does nothing. /// The default implementation does nothing.
fn update(&self, server: &mut Server) {} fn update(&self, server: &Server, worlds: WorldsMut);
/// Called when the server receives a Server List Ping query.
/// Data for the response can be provided or the query can be ignored.
///
/// This method is called from within a tokio runtime.
///
/// # Default Implementation
/// The query is ignored.
async fn server_list_ping(
&self,
server: &SharedServer,
remote_addr: SocketAddr,
) -> ServerListPing {
ServerListPing::Ignore
}
/// Called asynchronously for each client after successful authentication
/// (if online mode is enabled) to determine if they are allowed to join the
/// server. On success, a client-backed entity is spawned.
///
/// This function is the appropriate place to perform
/// player count checks, whitelist checks, database queries, etc.
///
/// This method is called from within a tokio runtime.
///
/// # Default Implementation
/// The client is allowed to join unconditionally.
async fn login(&self, server: &SharedServer, ncd: &NewClientData) -> Login {
Login::Join
}
} }
/// The result of the [`server_list_ping`](Handler::server_list_ping) callback. /// The result of the [`server_list_ping`](Handler::server_list_ping) callback.
@ -213,25 +224,14 @@ pub enum ServerListPing<'a> {
Ignore, Ignore,
} }
/// The result of the [`login`](Handler::login) callback.
#[derive(Debug)]
pub enum Login {
/// The client may join the server.
Join,
/// The client may not join the server and will be disconnected with the
/// provided reason.
Disconnect(Text),
}
/// A handle to a particular [`Dimension`] on the server. /// A handle to a particular [`Dimension`] on the server.
/// ///
/// Dimension IDs must only be used on servers from which they originate. /// Dimension IDs must only be used on servers from which they originate.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct DimensionId(pub(crate) u16); pub struct DimensionId(pub(crate) u16);
/// All dimension IDs are valid. impl DimensionId {
impl Id for DimensionId { pub fn to_index(self) -> usize {
fn idx(self) -> usize {
self.0 as usize self.0 as usize
} }
} }
@ -313,9 +313,8 @@ pub enum DimensionEffects {
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct BiomeId(pub(crate) u16); pub struct BiomeId(pub(crate) u16);
/// All Biome IDs are valid. impl BiomeId {
impl Id for BiomeId { pub fn to_index(self) -> usize {
fn idx(self) -> usize {
self.0 as usize self.0 as usize
} }
} }

View file

@ -4,77 +4,44 @@ pub mod types;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::collections::HashMap; use std::collections::HashMap;
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::ops::Deref;
use bitfield_struct::bitfield;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use uuid::Uuid; use uuid::Uuid;
use crate::byte_angle::ByteAngle;
use crate::chunk::ChunkPos; use crate::chunk::ChunkPos;
use crate::glm::DVec3; use crate::glm::{DVec3, I16Vec3, Vec3};
use crate::packets::play::{
ClientPlayPacket, EntityMetadata, SpawnEntity, SpawnExperienceOrb, SpawnLivingEntity,
SpawnPainting, SpawnPlayer,
};
use crate::protocol::ReadToEnd;
use crate::slotmap::{Key, SlotMap}; use crate::slotmap::{Key, SlotMap};
use crate::{Aabb, Id, WorldId}; use crate::var_int::VarInt;
use crate::{Aabb, WorldId};
pub struct EntityStore { pub struct Entities {
sm: SlotMap<Entity>, sm: SlotMap<Entity>,
uuid_to_entity: HashMap<Uuid, EntityId>, uuid_to_entity: HashMap<Uuid, EntityId>,
/// Maps chunk positions to the set of all entities with bounding volumes
/// intersecting that chunk.
partition: HashMap<(WorldId, ChunkPos), Vec<EntityId>>,
} }
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct EntitiesMut<'a>(&'a mut Entities);
pub struct EntityId(Key);
impl Id for EntityId { impl<'a> Deref for EntitiesMut<'a> {
fn idx(self) -> usize { type Target = Entities;
self.0.index() as usize
fn deref(&self) -> &Self::Target {
self.0
} }
} }
impl EntityId { impl Entities {
pub(crate) fn to_network_id(self) -> i32 {
// TODO: is ID 0 reserved?
self.0.index() as i32
}
}
pub struct Entity {
data: EntityData,
old_type: EntityType,
new_position: DVec3,
old_position: DVec3,
new_world: Option<WorldId>,
old_world: Option<WorldId>,
uuid: Uuid,
}
impl Entity {
pub fn data(&self) -> &EntityData {
&self.data
}
pub fn typ(&self) -> EntityType {
self.data.typ()
}
/// Changes the type of this entity.
pub fn change_type(&mut self, new_type: EntityType) {
todo!(); // TODO
}
fn hitbox(&self) -> Aabb<f64, 3> {
// TODO
Aabb::default()
}
}
pub use types::{EntityData, EntityType};
impl EntityStore {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
sm: SlotMap::new(), sm: SlotMap::new(),
uuid_to_entity: HashMap::new(), uuid_to_entity: HashMap::new(),
partition: HashMap::new(),
} }
} }
@ -83,11 +50,38 @@ impl EntityStore {
self.sm.count() self.sm.count()
} }
/// Spawns a new entity with the default data. The new entity'd [`EntityId`] /// Gets the [`EntityId`] of the entity with the given UUID in an efficient
/// manner.
///
/// Returns `None` if there is no entity with the provided UUID. Returns
/// `Some` otherwise.
pub fn get_with_uuid(&self, uuid: Uuid) -> Option<EntityId> {
self.uuid_to_entity.get(&uuid).cloned()
}
pub fn get(&self, entity: EntityId) -> Option<&Entity> {
self.sm.get(entity.0)
}
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (EntityId(k), v))
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
}
}
impl<'a> EntitiesMut<'a> {
pub(crate) fn new(entities: &'a mut Entities) -> Self {
Self(entities)
}
/// Spawns a new entity with the default data. The new entity's [`EntityId`]
/// is returned. /// is returned.
/// ///
/// To actually see the new entity, set its position to somewhere nearby and /// To actually see the new entity, set its position to somewhere nearby and
/// [change its type](EntityData::change_type) to something visible. /// [set its type](EntityData::set_type) to something visible.
pub fn create(&mut self) -> EntityId { pub fn create(&mut self) -> EntityId {
loop { loop {
let uuid = Uuid::from_bytes(rand::random()); let uuid = Uuid::from_bytes(rand::random());
@ -100,19 +94,22 @@ impl EntityStore {
/// Like [`create`](Entities::create), but requires specifying the new /// Like [`create`](Entities::create), but requires specifying the new
/// entity's UUID. This is useful for deserialization. /// entity's UUID. This is useful for deserialization.
/// ///
/// The provided UUID must not conflict with an existing entity UUID. If it /// The provided UUID must not conflict with an existing entity UUID in this
/// does, `None` is returned and the entity is not spawned. /// world. If it does, `None` is returned and the entity is not spawned.
pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option<EntityId> { pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option<EntityId> {
match self.uuid_to_entity.entry(uuid) { match self.0.uuid_to_entity.entry(uuid) {
Entry::Occupied(_) => None, Entry::Occupied(_) => None,
Entry::Vacant(ve) => { Entry::Vacant(ve) => {
let entity = EntityId(self.sm.insert(Entity { let entity = EntityId(self.0.sm.insert(Entity {
data: EntityData::Marker(types::Marker::new()), flags: EntityFlags(0),
old_type: EntityType::Marker, meta: EntityMeta::new(EntityType::Marker),
new_position: DVec3::default(), new_position: DVec3::default(),
old_position: DVec3::default(), old_position: DVec3::default(),
new_world: None, yaw: 0.0,
old_world: None, pitch: 0.0,
head_yaw: 0.0,
head_pitch: 0.0,
velocity: Vec3::default(),
uuid, uuid,
})); }));
@ -125,18 +122,10 @@ impl EntityStore {
} }
} }
/// Gets the [`EntityId`] of the entity with the given UUID in an efficient
/// manner.
///
/// Returns `None` if there is no entity with the provided UUID. Returns
/// `Some` otherwise.
pub fn get_with_uuid(&self, uuid: Uuid) -> Option<EntityId> {
self.uuid_to_entity.get(&uuid).cloned()
}
pub fn delete(&mut self, entity: EntityId) -> bool { pub fn delete(&mut self, entity: EntityId) -> bool {
if let Some(e) = self.sm.remove(entity.0) { if let Some(e) = self.0.sm.remove(entity.0) {
self.uuid_to_entity self.0
.uuid_to_entity
.remove(&e.uuid) .remove(&e.uuid)
.expect("UUID should have been in UUID map"); .expect("UUID should have been in UUID map");
@ -147,197 +136,466 @@ impl EntityStore {
} }
} }
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) { pub fn retain(&mut self, mut f: impl FnMut(EntityId, EntityMut) -> bool) {
self.sm.retain(|k, v| f(EntityId(k), v)) // TODO
self.0.sm.retain(|k, v| f(EntityId(k), EntityMut(v)))
} }
pub fn get(&self, entity: EntityId) -> Option<&Entity> { pub fn get_mut(&mut self, entity: EntityId) -> Option<EntityMut> {
self.sm.get(entity.0) self.0.sm.get_mut(entity.0).map(EntityMut)
} }
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, EntityMut)> + '_ {
self.sm.get_mut(entity.0) self.0
.sm
.iter_mut()
.map(|(k, v)| (EntityId(k), EntityMut(v)))
} }
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ { pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, EntityMut)> + '_ {
self.sm.iter().map(|(k, v)| (EntityId(k), v)) self.0
} .sm
.par_iter_mut()
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ { .map(|(k, v)| (EntityId(k), EntityMut(v)))
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
}
pub(crate) fn from_network_id(&self, network_id: i32) -> Option<EntityId> {
self.sm.key_at_index(network_id as usize).map(EntityId)
}
fn partition_insert(&mut self, entity: EntityId, world: WorldId, aabb: Aabb<f64, 3>) {
let min_corner = ChunkPos::from_xz(aabb.min().xz());
let max_corner = ChunkPos::from_xz(aabb.max().xz());
for z in min_corner.z..=max_corner.z {
for x in min_corner.x..=max_corner.x {
self.partition_insert_at(entity, world, ChunkPos { x, z })
}
}
}
fn partition_insert_at(&mut self, entity: EntityId, world: WorldId, pos: ChunkPos) {
match self.partition.entry((world, pos)) {
Entry::Occupied(mut oe) => {
debug_assert!(
!oe.get_mut().contains(&entity),
"spatial partition: entity already present"
);
oe.get_mut().push(entity);
}
Entry::Vacant(ve) => {
ve.insert(vec![entity]);
}
}
}
fn partition_remove(&mut self, entity: EntityId, world: WorldId, aabb: Aabb<f64, 3>) {
let min_corner = ChunkPos::from_xz(aabb.min().xz());
let max_corner = ChunkPos::from_xz(aabb.max().xz());
for z in min_corner.z..=max_corner.z {
for x in min_corner.x..=max_corner.x {
self.partition_remove_at(entity, world, ChunkPos::new(x, z));
}
}
}
fn partition_remove_at(&mut self, entity: EntityId, world: WorldId, pos: ChunkPos) {
let errmsg = "spatial partition: entity removal failed";
match self.partition.entry((world, pos)) {
Entry::Occupied(mut oe) => {
let v = oe.get_mut();
let idx = v.iter().position(|e| *e == entity).expect(errmsg);
v.swap_remove(idx);
if v.is_empty() {
oe.remove();
}
}
Entry::Vacant(_) => panic!("{errmsg}"),
}
}
fn partition_modify(
&mut self,
entity: EntityId,
old_world: WorldId,
old_aabb: Aabb<f64, 3>,
new_world: WorldId,
new_aabb: Aabb<f64, 3>,
) {
if old_world != new_world {
self.partition_remove(entity, old_world, old_aabb);
self.partition_insert(entity, new_world, new_aabb);
} else {
let old_min_corner = ChunkPos::from_xz(old_aabb.min().xz());
let old_max_corner = ChunkPos::from_xz(old_aabb.max().xz());
let new_min_corner = ChunkPos::from_xz(new_aabb.min().xz());
let new_max_corner = ChunkPos::from_xz(new_aabb.max().xz());
for z in new_min_corner.z..=new_max_corner.z {
for x in new_min_corner.x..=new_max_corner.x {
if x < old_min_corner.x
|| x > old_max_corner.x
|| z < old_min_corner.z
|| z > old_max_corner.z
{
self.partition_insert_at(entity, old_world, ChunkPos::new(x, z));
}
}
}
for z in old_min_corner.z..=old_max_corner.z {
for x in old_min_corner.x..=old_max_corner.x {
if x < new_min_corner.x
|| x > new_max_corner.x
|| z < new_min_corner.z
|| z > new_max_corner.z
{
self.partition_remove_at(entity, old_world, ChunkPos::new(x, z))
}
}
}
}
}
/// Returns an iterator over all entities with bounding volumes intersecting
/// the given AABB in an arbitrary order.
pub fn intersecting_aabb(
&self,
world: WorldId,
aabb: Aabb<f64, 3>,
) -> impl FusedIterator<Item = EntityId> + '_ {
let min_corner = ChunkPos::from_xz(aabb.min().xz());
let max_corner = ChunkPos::from_xz(aabb.max().xz());
(min_corner.z..=max_corner.z).flat_map(move |z| {
(min_corner.x..=max_corner.x).flat_map(move |x| {
self.partition
.get(&(world, ChunkPos::new(x, z)))
.into_iter()
.flat_map(move |v| {
v.iter().cloned().filter(move |&e| {
self.get(e)
.expect("spatial partition contains deleted entity")
.hitbox()
.collides_with_aabb(&aabb)
})
})
})
})
} }
pub(crate) fn update(&mut self) { pub(crate) fn update(&mut self) {
for (_, e) in self.iter_mut() { for (_, e) in self.iter_mut() {
e.old_position = e.new_position; e.0.old_position = e.new_position;
e.old_world = e.new_world; e.0.meta.clear_modifications();
e.0.flags = EntityFlags(0);
// TODO: update entity old_type.
// TODO: clear changed bits in metadata.
} }
} }
} }
//#[cfg(test)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
//mod tests { pub struct EntityId(Key);
// use appearance::Player;
// impl EntityId {
// use super::*; pub(crate) fn to_network_id(self) -> i32 {
// use crate::glm; // TODO: is ID 0 reserved?
// self.0.index() as i32
// // TODO: better test: spawn a bunch of random entities, spawn a random }
// AABB, // assert collides_with_aabb consistency. }
//
// #[test] pub struct Entity {
// fn space_partition() { flags: EntityFlags,
// let mut entities = EntityStore::new(); meta: EntityMeta,
// new_position: DVec3,
// let ids = [(16.0, 16.0, 16.0), (8.0, 8.0, 8.0), (10.0, 50.0, 10.0)] old_position: DVec3,
// .into_iter() yaw: f32,
// .map(|(x, y, z)| entities.create(Player::new(glm::vec3(x, y, z), pitch: f32,
// WorldId::NULL))) .collect::<Vec<_>>(); head_yaw: f32,
// head_pitch: f32,
// let outside = *ids.last().unwrap(); velocity: Vec3,
// uuid: Uuid,
// assert!(entities }
// .intersecting_aabb(
// WorldId::NULL, pub struct EntityMut<'a>(&'a mut Entity);
// Aabb::new(glm::vec3(8.0, 8.0, 8.0), glm::vec3(16.0, 16.0,
// 16.0)), ) impl<'a> Deref for EntityMut<'a> {
// .all(|id| ids.contains(&id) && id != outside)); type Target = Entity;
// }
//} fn deref(&self) -> &Self::Target {
self.0
}
}
/// Contains a bit for certain fields in [`Entity`] to track if they have been
/// modified.
#[bitfield(u8)]
pub(crate) struct EntityFlags {
meta_modified: bool,
yaw_or_pitch_modified: bool,
head_yaw_modified: bool,
head_pitch_modified: bool,
velocity_modified: bool,
#[bits(3)]
_pad: u8,
}
impl Entity {
pub(crate) fn flags(&self) -> EntityFlags {
self.flags
}
/// Returns a reference to this entity's [`EntityMeta`].
pub fn meta(&self) -> &EntityMeta {
&self.meta
}
/// Returns the [`EntityType`] of this entity.
pub fn typ(&self) -> EntityType {
self.meta.typ()
}
/// Returns the position of this entity in the world it inhabits.
pub fn position(&self) -> DVec3 {
self.new_position
}
/// Returns the position of this entity as it existed at the end of the
/// previous tick.
pub fn old_position(&self) -> DVec3 {
self.old_position
}
/// Gets the yaw of this entity (in degrees).
pub fn yaw(&self) -> f32 {
self.yaw
}
/// Gets the pitch of this entity (in degrees).
pub fn pitch(&self) -> f32 {
self.pitch
}
/// Gets the head yaw of this entity (in degrees).
pub fn head_yaw(&self) -> f32 {
self.head_yaw
}
/// Gets the head pitch of this entity (in degrees).
pub fn head_pitch(&self) -> f32 {
self.head_pitch
}
/// Gets the velocity of this entity in meters per second.
pub fn velocity(&self) -> Vec3 {
self.velocity
}
/// Gets the metadata packet to send to clients after this entity has been
/// spawned.
///
/// Is `None` if there is no initial metadata.
pub(crate) fn initial_metadata_packet(&self, this_id: EntityId) -> Option<EntityMetadata> {
self.meta.initial_metadata().map(|meta| EntityMetadata {
entity_id: VarInt(this_id.to_network_id()),
metadata: ReadToEnd(meta),
})
}
/// Gets the metadata packet to send to clients when the entity is modified.
///
/// Is `None` if this entity's metadata has not been modified.
pub(crate) fn updated_metadata_packet(&self, this_id: EntityId) -> Option<EntityMetadata> {
self.meta.updated_metadata().map(|meta| EntityMetadata {
entity_id: VarInt(this_id.to_network_id()),
metadata: ReadToEnd(meta),
})
}
pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option<EntitySpawnPacket> {
use EntityMeta::*;
match &self.meta {
Marker(_) => None,
ExperienceOrb(_) => Some(EntitySpawnPacket::SpawnExperienceOrb(SpawnExperienceOrb {
entity_id: VarInt(this_id.to_network_id()),
position: self.new_position,
count: 0, // TODO
})),
Painting(_) => todo!(),
Player(_) => todo!(),
AreaEffectCloud(_)
| Arrow(_)
| Boat(_)
| DragonFireball(_)
| EndCrystal(_)
| EvokerFangs(_)
| EyeOfEnder(_)
| FallingBlock(_)
| FireworkRocket(_)
| GlowItemFrame(_)
| Item(_)
| ItemFrame(_)
| Fireball(_)
| LeashKnot(_)
| LightningBolt(_)
| LlamaSpit(_)
| Minecart(_)
| ChestMinecart(_)
| CommandBlockMinecart(_)
| FurnaceMinecart(_)
| HopperMinecart(_)
| SpawnerMinecart(_)
| TntMinecart(_)
| Tnt(_)
| ShulkerBullet(_)
| SmallFireball(_)
| Snowball(_)
| SpectralArrow(_)
| Egg(_)
| EnderPearl(_)
| ExperienceBottle(_)
| Potion(_)
| Trident(_)
| WitherSkull(_)
| FishingBobber(_) => Some(EntitySpawnPacket::SpawnEntity(SpawnEntity {
entity_id: VarInt(this_id.to_network_id()),
object_uuid: self.uuid,
typ: VarInt(self.typ() as i32),
position: self.new_position,
pitch: ByteAngle::from_degrees(self.pitch),
yaw: ByteAngle::from_degrees(self.yaw),
data: 1, // TODO
velocity: velocity_to_packet_units(self.velocity),
})),
ArmorStand(_) | Axolotl(_) | Bat(_) | Bee(_) | Blaze(_) | Cat(_) | CaveSpider(_)
| Chicken(_) | Cod(_) | Cow(_) | Creeper(_) | Dolphin(_) | Donkey(_) | Drowned(_)
| ElderGuardian(_) | EnderDragon(_) | Enderman(_) | Endermite(_) | Evoker(_)
| Fox(_) | Ghast(_) | Giant(_) | GlowSquid(_) | Goat(_) | Guardian(_) | Hoglin(_)
| Horse(_) | Husk(_) | Illusioner(_) | IronGolem(_) | Llama(_) | MagmaCube(_)
| Mule(_) | Mooshroom(_) | Ocelot(_) | Panda(_) | Parrot(_) | Phantom(_) | Pig(_)
| Piglin(_) | PiglinBrute(_) | Pillager(_) | PolarBear(_) | Pufferfish(_)
| Rabbit(_) | Ravager(_) | Salmon(_) | Sheep(_) | Shulker(_) | Silverfish(_)
| Skeleton(_) | SkeletonHorse(_) | Slime(_) | SnowGolem(_) | Spider(_) | Squid(_)
| Stray(_) | Strider(_) | TraderLlama(_) | TropicalFish(_) | Turtle(_) | Vex(_)
| Villager(_) | Vindicator(_) | WanderingTrader(_) | Witch(_) | Wither(_)
| WitherSkeleton(_) | Wolf(_) | Zoglin(_) | Zombie(_) | ZombieHorse(_)
| ZombieVillager(_) | ZombifiedPiglin(_) => {
Some(EntitySpawnPacket::SpawnLivingEntity(SpawnLivingEntity {
entity_id: VarInt(this_id.to_network_id()),
entity_uuid: self.uuid,
typ: VarInt(self.typ() as i32),
position: self.new_position,
yaw: ByteAngle::from_degrees(self.yaw),
pitch: ByteAngle::from_degrees(self.pitch),
head_pitch: ByteAngle::from_degrees(self.head_pitch),
velocity: velocity_to_packet_units(self.velocity),
}))
}
}
}
pub fn hitbox(&self) -> Aabb<f64, 3> {
let dims = match &self.meta {
EntityMeta::AreaEffectCloud(e) => [
e.get_radius() as f64 * 2.0,
0.5,
e.get_radius() as f64 * 2.0,
],
EntityMeta::ArmorStand(e) => {
if e.get_marker() {
[0.0, 0.0, 0.0]
} else if e.get_small() {
[0.5, 0.9875, 0.5]
} else {
[0.5, 1.975, 0.5]
}
}
EntityMeta::Arrow(_) => [0.5, 0.5, 0.5],
EntityMeta::Axolotl(_) => [1.3, 0.6, 1.3],
EntityMeta::Bat(_) => [0.5, 0.9, 0.5],
EntityMeta::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size?
EntityMeta::Blaze(_) => [0.6, 1.8, 0.6],
EntityMeta::Boat(_) => [1.375, 0.5625, 1.375],
EntityMeta::Cat(_) => [0.6, 0.7, 0.6],
EntityMeta::CaveSpider(_) => [0.7, 0.5, 0.7],
EntityMeta::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size?
EntityMeta::Cod(_) => [0.5, 0.3, 0.5],
EntityMeta::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityMeta::Creeper(_) => [0.6, 1.7, 0.6],
EntityMeta::Dolphin(_) => [0.9, 0.6, 0.9],
EntityMeta::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size?
EntityMeta::DragonFireball(_) => [1.0, 1.0, 1.0],
EntityMeta::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityMeta::ElderGuardian(_) => [1.9975, 1.9975, 1.9975],
EntityMeta::EndCrystal(_) => [2.0, 2.0, 2.0],
EntityMeta::EnderDragon(_) => [16.0, 8.0, 16.0],
EntityMeta::Enderman(_) => [0.6, 2.9, 0.6],
EntityMeta::Endermite(_) => [0.4, 0.3, 0.4],
EntityMeta::Evoker(_) => [0.6, 1.95, 0.6],
EntityMeta::EvokerFangs(_) => [0.5, 0.8, 0.5],
EntityMeta::ExperienceOrb(_) => [0.5, 0.5, 0.5],
EntityMeta::EyeOfEnder(_) => [0.25, 0.25, 0.25],
EntityMeta::FallingBlock(_) => [0.98, 0.98, 0.98],
EntityMeta::FireworkRocket(_) => [0.25, 0.25, 0.25],
EntityMeta::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityMeta::Ghast(_) => [4.0, 4.0, 4.0],
EntityMeta::Giant(_) => [3.6, 12.0, 3.6],
EntityMeta::GlowItemFrame(_) => todo!("account for rotation"),
EntityMeta::GlowSquid(_) => [0.8, 0.8, 0.8],
EntityMeta::Goat(e) => [1.3, 0.9, 1.3], // TODO: baby size?
EntityMeta::Guardian(_) => [0.85, 0.85, 0.85],
EntityMeta::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityMeta::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityMeta::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityMeta::Illusioner(_) => [0.6, 1.95, 0.6],
EntityMeta::IronGolem(_) => [1.4, 2.7, 1.4],
EntityMeta::Item(_) => [0.25, 0.25, 0.25],
EntityMeta::ItemFrame(_) => todo!("account for rotation"),
EntityMeta::Fireball(_) => [1.0, 1.0, 1.0],
EntityMeta::LeashKnot(_) => [0.375, 0.5, 0.375],
EntityMeta::LightningBolt(_) => [0.0, 0.0, 0.0],
EntityMeta::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size?
EntityMeta::LlamaSpit(_) => [0.25, 0.25, 0.25],
EntityMeta::MagmaCube(e) => {
let s = e.get_size() as f64 * 0.51000005;
[s, s, s]
}
EntityMeta::Marker(_) => [0.0, 0.0, 0.0],
EntityMeta::Minecart(_) => [0.98, 0.7, 0.98],
EntityMeta::ChestMinecart(_) => [0.98, 0.7, 0.98],
EntityMeta::CommandBlockMinecart(_) => [0.98, 0.7, 0.98],
EntityMeta::FurnaceMinecart(_) => [0.98, 0.7, 0.98],
EntityMeta::HopperMinecart(_) => [0.98, 0.7, 0.98],
EntityMeta::SpawnerMinecart(_) => [0.98, 0.7, 0.98],
EntityMeta::TntMinecart(_) => [0.98, 0.7, 0.98],
EntityMeta::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityMeta::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityMeta::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityMeta::Painting(_) => todo!("account for rotation and type"),
EntityMeta::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityMeta::Parrot(_) => [0.5, 0.9, 0.5],
EntityMeta::Phantom(_) => [0.9, 0.5, 0.9],
EntityMeta::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size?
EntityMeta::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityMeta::PiglinBrute(_) => [0.6, 1.95, 0.6],
EntityMeta::Pillager(_) => [0.6, 1.95, 0.6],
EntityMeta::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size?
EntityMeta::Tnt(_) => [0.98, 0.98, 0.98],
EntityMeta::Pufferfish(_) => [0.7, 0.7, 0.7],
EntityMeta::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size?
EntityMeta::Ravager(_) => [1.95, 2.2, 1.95],
EntityMeta::Salmon(_) => [0.7, 0.4, 0.7],
EntityMeta::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size?
EntityMeta::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated?
EntityMeta::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125],
EntityMeta::Silverfish(_) => [0.4, 0.3, 0.4],
EntityMeta::Skeleton(_) => [0.6, 1.99, 0.6],
EntityMeta::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityMeta::Slime(e) => {
let s = 0.51000005 * e.get_size() as f64;
[s, s, s]
}
EntityMeta::SmallFireball(_) => [0.3125, 0.3125, 0.3125],
EntityMeta::SnowGolem(_) => [0.7, 1.9, 0.7],
EntityMeta::Snowball(_) => [0.25, 0.25, 0.25],
EntityMeta::SpectralArrow(_) => [0.5, 0.5, 0.5],
EntityMeta::Spider(_) => [1.4, 0.9, 1.4],
EntityMeta::Squid(_) => [0.8, 0.8, 0.8],
EntityMeta::Stray(_) => [0.6, 1.99, 0.6],
EntityMeta::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size?
EntityMeta::Egg(_) => [0.25, 0.25, 0.25],
EntityMeta::EnderPearl(_) => [0.25, 0.25, 0.25],
EntityMeta::ExperienceBottle(_) => [0.25, 0.25, 0.25],
EntityMeta::Potion(_) => [0.25, 0.25, 0.25],
EntityMeta::Trident(_) => [0.5, 0.5, 0.5],
EntityMeta::TraderLlama(_) => [0.9, 1.87, 0.9],
EntityMeta::TropicalFish(_) => [0.5, 0.4, 0.5],
EntityMeta::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size?
EntityMeta::Vex(_) => [0.4, 0.8, 0.4],
EntityMeta::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityMeta::Vindicator(_) => [0.6, 1.95, 0.6],
EntityMeta::WanderingTrader(_) => [0.6, 1.95, 0.6],
EntityMeta::Witch(_) => [0.6, 1.95, 0.6],
EntityMeta::Wither(_) => [0.9, 3.5, 0.9],
EntityMeta::WitherSkeleton(_) => [0.7, 2.4, 0.7],
EntityMeta::WitherSkull(_) => [0.3125, 0.3125, 0.3125],
EntityMeta::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size?
EntityMeta::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityMeta::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityMeta::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityMeta::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityMeta::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityMeta::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose.
EntityMeta::FishingBobber(_) => [0.25, 0.25, 0.25],
};
Aabb::from_bottom_and_dimensions(self.new_position, dims)
}
}
fn velocity_to_packet_units(vel: Vec3) -> I16Vec3 {
// The saturating cast to i16 is desirable.
vel.map(|v| (v * 400.0) as i16)
}
impl<'a> EntityMut<'a> {
// TODO: exposing &mut EntityMeta is unsound?
/// Returns a mutable reference to this entity's [`EntityMeta`].
///
/// **NOTE:** Never call [`std::mem::swap`] on the returned reference or any
/// part of `EntityMeta` as this would break invariants within the
/// library.
pub fn meta_mut(&mut self) -> &mut EntityMeta {
&mut self.0.meta
}
/// Changes the [`EntityType`] of this entity to the provided type.
///
/// All metadata of this entity is reset to the default values.
pub fn set_type(&mut self, typ: EntityType) {
self.0.meta = EntityMeta::new(typ);
// All metadata is lost, so we must mark it as modified unconditionally.
self.0.flags.set_meta_modified(true);
}
/// Sets the position of this entity in the world it inhabits.
pub fn set_position(&mut self, pos: impl Into<DVec3>) {
self.0.new_position = pos.into();
}
/// Sets the yaw of this entity (in degrees).
pub fn set_yaw(&mut self, yaw: f32) {
self.0.yaw = yaw;
if ByteAngle::from_degrees(self.yaw) != ByteAngle::from_degrees(yaw) {
self.0.flags.set_yaw_or_pitch_modified(true);
}
}
/// Sets the pitch of this entity (in degrees).
pub fn set_pitch(&mut self, pitch: f32) {
self.0.pitch = pitch;
if ByteAngle::from_degrees(self.pitch) != ByteAngle::from_degrees(pitch) {
self.0.flags.set_yaw_or_pitch_modified(true);
}
}
/// Sets the head yaw of this entity (in degrees).
pub fn set_head_yaw(&mut self, head_yaw: f32) {
self.0.head_yaw = head_yaw;
if ByteAngle::from_degrees(self.head_yaw) != ByteAngle::from_degrees(head_yaw) {
self.0.flags.set_head_yaw_modified(true);
}
}
/// Sets the head pitch of this entity (in degrees).
pub fn set_head_pitch(&mut self, head_pitch: f32) {
self.0.head_pitch = head_pitch;
if ByteAngle::from_degrees(self.head_pitch) != ByteAngle::from_degrees(head_pitch) {
self.0.flags.set_head_pitch_modified(true);
}
}
pub fn set_velocity(&mut self, velocity: Vec3) {
self.0.velocity = velocity;
if velocity_to_packet_units(self.velocity) != velocity_to_packet_units(velocity) {
self.0.flags.set_velocity_modified(true);
}
}
}
pub(crate) enum EntitySpawnPacket {
SpawnEntity(SpawnEntity),
SpawnExperienceOrb(SpawnExperienceOrb),
SpawnLivingEntity(SpawnLivingEntity),
SpawnPainting(SpawnPainting),
SpawnPlayer(SpawnPlayer),
}
impl From<EntitySpawnPacket> for ClientPlayPacket {
fn from(pkt: EntitySpawnPacket) -> Self {
match pkt {
EntitySpawnPacket::SpawnEntity(pkt) => pkt.into(),
EntitySpawnPacket::SpawnExperienceOrb(pkt) => pkt.into(),
EntitySpawnPacket::SpawnLivingEntity(pkt) => pkt.into(),
EntitySpawnPacket::SpawnPainting(pkt) => pkt.into(),
EntitySpawnPacket::SpawnPlayer(pkt) => pkt.into(),
}
}
}
pub use types::{EntityMeta, EntityType};

View file

@ -1,3 +1,5 @@
#![allow(missing_docs)]
use std::io::Write; use std::io::Write;
use anyhow::Context; use anyhow::Context;
@ -10,26 +12,25 @@ use crate::Text;
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)] #[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
pub struct ArmorStandRotations { pub struct ArmorStandRotations {
pub x_degrees: f32, /// Rotation on the X axis in degrees.
pub y_degrees: f32, pub x: f32,
pub z_degrees: f32, /// Rotation on the Y axis in degrees.
pub y: f32,
/// Rotation on the Z axis in degrees.
pub z: f32,
} }
impl ArmorStandRotations { impl ArmorStandRotations {
pub fn new(x_degrees: f32, y_degrees: f32, z_degrees: f32) -> Self { pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { Self { x, y, z }
x_degrees,
y_degrees,
z_degrees,
}
} }
} }
impl Encode for ArmorStandRotations { impl Encode for ArmorStandRotations {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.x_degrees.encode(w)?; self.x.encode(w)?;
self.y_degrees.encode(w)?; self.y.encode(w)?;
self.z_degrees.encode(w) self.z.encode(w)
} }
} }
@ -126,22 +127,6 @@ impl Default for VillagerProfession {
} }
} }
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
struct OptVarInt(Option<i32>);
impl Encode for OptVarInt {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
match self.0 {
Some(n) => VarInt(
n.checked_add(1)
.context("i32::MAX is unrepresentable as an optional VarInt")?,
)
.encode(w),
None => VarInt(0).encode(w),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Pose { pub enum Pose {
Standing, Standing,
@ -166,6 +151,7 @@ impl Encode for Pose {
} }
} }
/// The main hand of a player.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum MainHand { pub enum MainHand {
Left, Left,

View file

@ -1,5 +1,9 @@
#![allow(clippy::all, missing_docs)]
use crate::block::BlockState; use crate::block::BlockState;
use crate::entity::meta::*; use crate::entity::meta::*;
use crate::protocol::Encode;
use crate::var_int::VarInt;
use crate::{BlockPos, EntityId, Text, Uuid}; use crate::{BlockPos, EntityId, Text, Uuid};
include!(concat!(env!("OUT_DIR"), "/entity.rs")); include!(concat!(env!("OUT_DIR"), "/entity.rs"));

View file

@ -4,7 +4,7 @@
trivial_numeric_casts, trivial_numeric_casts,
unused_lifetimes, unused_lifetimes,
unused_import_braces, unused_import_braces,
missing_docs // missing_docs
)] )]
mod aabb; mod aabb;
@ -30,15 +30,15 @@ pub mod world;
pub use aabb::Aabb; pub use aabb::Aabb;
pub use async_trait::async_trait; pub use async_trait::async_trait;
pub use block_pos::BlockPos; pub use block_pos::BlockPos;
pub use chunk::{Chunk, ChunkPos, ChunkStore}; pub use chunk::{Chunk, ChunkPos, Chunks, ChunksMut};
pub use client::{Client, ClientStore}; pub use client::{Client, ClientMut, Clients, ClientsMut};
pub use config::{Biome, BiomeId, Config, Dimension, DimensionId}; pub use config::{Biome, BiomeId, Config, Dimension, DimensionId};
pub use entity::{Entity, EntityId, EntityStore}; pub use entity::{Entities, EntitiesMut, Entity, EntityId};
pub use identifier::Identifier; pub use identifier::Identifier;
pub use server::{start_server, NewClientData, Server, SharedServer, ShutdownResult}; pub use server::{start_server, NewClientData, Server, ShutdownResult};
pub use text::{Text, TextFormat}; pub use text::{Text, TextFormat};
pub use uuid::Uuid; pub use uuid::Uuid;
pub use world::{World, WorldId, WorldStore}; pub use world::{WorldId, WorldMut, WorldRef, Worlds, WorldsMut};
pub use {nalgebra_glm as glm, nbt, uuid}; pub use {nalgebra_glm as glm, nbt, uuid};
/// The Minecraft protocol version that this library targets. /// The Minecraft protocol version that this library targets.
@ -55,18 +55,3 @@ const LIBRARY_NAMESPACE: &str = "valence";
/// The duration of a game update depends on the current configuration, which /// The duration of a game update depends on the current configuration, which
/// may or may not be the same as Minecraft's standard 20 ticks/second. /// may or may not be the same as Minecraft's standard 20 ticks/second.
pub type Ticks = i64; pub type Ticks = i64;
/// Types such as [`EntityId`], [`WorldId`], and [`ChunkId`] which can be used
/// as indices into an array.
///
/// Every ID is either valid or invalid. Valid IDs point to living values. For
/// instance, a valid [`EntityId`] points to a living entity on the server. When
/// that entity is deleted, the corresponding [`EntityId`] becomes invalid.
pub trait Id: Copy + Send + Sync + PartialEq + Eq {
/// Returns the index of this ID.
///
/// For all IDs `a` and `b`, `a == b` implies `a.idx() == b.idx()`. If
/// both `a` and `b` are currently valid, then `a != b` implies `a.idx() !=
/// b.idx()`.
fn idx(self) -> usize;
}

View file

@ -16,8 +16,9 @@ use uuid::Uuid;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::byte_angle::ByteAngle; use crate::byte_angle::ByteAngle;
use crate::glm::{DVec3, I16Vec3, Vec3};
use crate::identifier::Identifier; use crate::identifier::Identifier;
use crate::protocol::{BoundedArray, BoundedString, Decode, Encode, Nbt, ReadToEnd}; use crate::protocol::{BoundedArray, BoundedInt, BoundedString, Decode, Encode, Nbt, ReadToEnd};
use crate::var_int::VarInt; use crate::var_int::VarInt;
use crate::var_long::VarLong; use crate::var_long::VarLong;
use crate::Text; use crate::Text;
@ -311,13 +312,13 @@ macro_rules! def_bitfield {
} }
} }
impl Encode for $name { impl $crate::protocol::Encode for $name {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.0.encode(w) self.0.encode(w)
} }
} }
impl Decode for $name { impl $crate::protocol::Decode for $name {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> { fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
<$inner_ty>::decode(r).map(Self) <$inner_ty>::decode(r).map(Self)
} }
@ -436,7 +437,6 @@ pub mod login {
/// Packets and types used during the play state. /// Packets and types used during the play state.
pub mod play { pub mod play {
use super::*; use super::*;
use crate::protocol::BoundedInt;
// ==== Clientbound ==== // ==== Clientbound ====
@ -444,26 +444,19 @@ pub mod play {
SpawnEntity 0x00 { SpawnEntity 0x00 {
entity_id: VarInt, entity_id: VarInt,
object_uuid: Uuid, object_uuid: Uuid,
typ: VarInt, // TODO: entity type enum typ: VarInt,
x: f64, position: DVec3,
y: f64,
z: f64,
pitch: ByteAngle, pitch: ByteAngle,
yaw: ByteAngle, yaw: ByteAngle,
data: i32, data: i32,
// TODO: entity velocity unit? velocity: I16Vec3,
velocity_x: i16,
velocity_y: i16,
velocity_z: i16,
} }
} }
def_struct! { def_struct! {
SpawnExperienceOrb 0x01 { SpawnExperienceOrb 0x01 {
entity_id: VarInt, entity_id: VarInt,
x: f64, position: DVec3,
y: f64,
z: f64,
count: i16, count: i16,
} }
} }
@ -472,17 +465,12 @@ pub mod play {
SpawnLivingEntity 0x02 { SpawnLivingEntity 0x02 {
entity_id: VarInt, entity_id: VarInt,
entity_uuid: Uuid, entity_uuid: Uuid,
typ: VarInt, // TODO: entity type enum typ: VarInt,
x: f64, position: DVec3,
y: f64,
z: f64,
yaw: ByteAngle, yaw: ByteAngle,
pitch: ByteAngle, pitch: ByteAngle,
head_pitch: ByteAngle, head_pitch: ByteAngle,
// TODO: entity velocity unit? velocity: I16Vec3,
velocity_x: i16,
velocity_y: i16,
velocity_z: i16,
} }
} }
@ -490,7 +478,7 @@ pub mod play {
SpawnPainting 0x03 { SpawnPainting 0x03 {
entity_id: VarInt, entity_id: VarInt,
entity_uuid: Uuid, entity_uuid: Uuid,
motive: VarInt, // TODO: painting ID enum variant: VarInt, // TODO: painting ID enum
location: BlockPos, location: BlockPos,
direction: PaintingDirection, direction: PaintingDirection,
} }
@ -509,9 +497,7 @@ pub mod play {
SpawnPlayer 0x04 { SpawnPlayer 0x04 {
entity_id: VarInt, entity_id: VarInt,
player_uuid: Uuid, player_uuid: Uuid,
x: f64, position: DVec3,
y: f64,
z: f64,
yaw: ByteAngle, yaw: ByteAngle,
pitch: ByteAngle, pitch: ByteAngle,
} }
@ -980,9 +966,7 @@ pub mod play {
def_struct! { def_struct! {
PlayerPositionAndLook 0x38 { PlayerPositionAndLook 0x38 {
x: f64, position: DVec3,
y: f64,
z: f64,
yaw: f32, yaw: f32,
pitch: f32, pitch: f32,
flags: PlayerPositionAndLookFlags, flags: PlayerPositionAndLookFlags,
@ -1001,6 +985,12 @@ pub mod play {
} }
} }
def_struct! {
DestroyEntities 0x3a {
entities: Vec<VarInt>,
}
}
def_struct! { def_struct! {
MultiBlockChange 0x3f { MultiBlockChange 0x3f {
chunk_section_position: u64, chunk_section_position: u64,
@ -1035,6 +1025,13 @@ pub mod play {
} }
} }
def_struct! {
EntityMetadata 0x4d {
entity_id: VarInt,
metadata: ReadToEnd,
}
}
def_struct! { def_struct! {
TimeUpdate 0x59 { TimeUpdate 0x59 {
/// The age of the world in 1/20ths of a second. /// The age of the world in 1/20ths of a second.
@ -1119,11 +1116,13 @@ pub mod play {
ChunkDataAndUpdateLight, ChunkDataAndUpdateLight,
JoinGame, JoinGame,
PlayerPositionAndLook, PlayerPositionAndLook,
DestroyEntities,
MultiBlockChange, MultiBlockChange,
HeldItemChangeClientbound, HeldItemChangeClientbound,
UpdateViewPosition, UpdateViewPosition,
UpdateViewDistance, UpdateViewDistance,
SpawnPosition, SpawnPosition,
EntityMetadata,
TimeUpdate, TimeUpdate,
} }
@ -1291,9 +1290,7 @@ pub mod play {
def_struct! { def_struct! {
InteractAtData { InteractAtData {
target_x: f32, target: Vec3,
target_y: f32,
target_z: f32,
hand: Hand, hand: Hand,
} }
} }
@ -1320,25 +1317,15 @@ pub mod play {
def_struct! { def_struct! {
PlayerPosition 0x11 { PlayerPosition 0x11 {
/// Absolute position position: DVec3,
x: f64,
/// Y position of the player's feet, normally 1.62 blocks below head.
feet_y: f64,
/// Absolute position
z: f64,
/// True if the client is on the ground, false otherwise.
on_ground: bool, on_ground: bool,
} }
} }
def_struct! { def_struct! {
PlayerPositionAndRotation 0x12 { PlayerPositionAndRotation 0x12 {
/// Absolute position // Absolute position
x: f64, position: DVec3,
/// Y position of the player's feet, normally 1.62 blocks below head.
feet_y: f64,
/// Absolute position
z: f64,
/// Absolute rotation on X axis in degrees. /// Absolute rotation on X axis in degrees.
yaw: f32, yaw: f32,
/// Absolute rotation on Y axis in degrees. /// Absolute rotation on Y axis in degrees.
@ -1366,11 +1353,7 @@ pub mod play {
def_struct! { def_struct! {
VehicleMoveServerbound 0x15 { VehicleMoveServerbound 0x15 {
/// Absolute position /// Absolute position
x: f64, position: DVec3,
/// Absolute position
y: f64,
/// Absolute position
z: f64,
/// Degrees /// Degrees
yaw: f32, yaw: f32,
/// Degrees /// Degrees
@ -1608,12 +1591,8 @@ pub mod play {
action: StructureBlockAction, action: StructureBlockAction,
mode: StructureBlockMode, mode: StructureBlockMode,
name: String, name: String,
offset_x: BoundedInt<i8, -32, 32>, offset_xyz: [BoundedInt<i8, -32, 32>; 3],
offset_y: BoundedInt<i8, -32, 32>, size_xyz: [BoundedInt<i8, 0, 32>; 3],
offset_z: BoundedInt<i8, -32, 32>,
size_x: BoundedInt<i8, 0, 32>,
size_y: BoundedInt<i8, 0, 32>,
size_z: BoundedInt<i8, 0, 32>,
mirror: StructureBlockMirror, mirror: StructureBlockMirror,
rotation: StructureBlockRotation, rotation: StructureBlockRotation,
metadata: String, metadata: String,
@ -1669,10 +1648,7 @@ pub mod play {
def_struct! { def_struct! {
UpdateSign 0x2b { UpdateSign 0x2b {
location: BlockPos, location: BlockPos,
line_1: BoundedString<0, 384>, lines: [BoundedString<0, 384>; 4],
line_2: BoundedString<0, 384>,
line_3: BoundedString<0, 384>,
line_4: BoundedString<0, 384>,
} }
} }
@ -1693,9 +1669,7 @@ pub mod play {
hand: Hand, hand: Hand,
location: BlockPos, location: BlockPos,
face: BlockFace, face: BlockFace,
cursor_pos_x: f32, cursor_pos: Vec3,
cursor_pos_y: f32,
cursor_pos_z: f32,
head_inside_block: bool, head_inside_block: bool,
} }
} }

View file

@ -1,13 +1,16 @@
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::mem; use std::mem;
use anyhow::{anyhow, ensure}; use anyhow::{anyhow, ensure, Context};
use arrayvec::ArrayVec;
use bitvec::prelude::*; use bitvec::prelude::*;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use nalgebra_glm::{Number, TVec};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use crate::var_int::VarInt; use crate::var_int::VarInt;
use crate::EntityId;
/// Trait for types that can be written to the Minecraft protocol. /// Trait for types that can be written to the Minecraft protocol.
pub trait Encode { pub trait Encode {
@ -220,6 +223,30 @@ impl<T: Decode> Decode for Option<T> {
} }
} }
impl<T: Encode> Encode for Box<T> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.as_ref().encode(w)
}
}
impl<T: Decode> Decode for Box<T> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(Box::new(T::decode(r)?))
}
}
impl Encode for Box<str> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_string_bounded(self, 0, 32767, w)
}
}
impl Decode for Box<str> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(String::decode(r)?.into_boxed_str())
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct BoundedInt<T, const MIN: i64, const MAX: i64>(pub T); pub struct BoundedInt<T, const MIN: i64, const MAX: i64>(pub T);
@ -335,11 +362,25 @@ impl<T: Encode, const N: usize> Encode for [T; N] {
impl<T: Decode, const N: usize> Decode for [T; N] { impl<T: Decode, const N: usize> Decode for [T; N] {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> { fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let vec = decode_array_bounded(N, N, r)?; let mut elems = ArrayVec::new();
match vec.try_into() { for _ in 0..N {
Ok(arr) => Ok(arr), elems.push(T::decode(r)?);
Err(_) => unreachable!("array size does not match"),
} }
elems
.into_inner()
.map_err(|_| unreachable!("mismatched array size"))
}
}
impl<T: Encode, const N: usize> Encode for TVec<T, N> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_array_bounded(self.as_slice(), N, N, w)
}
}
impl<T: Decode + Number, const N: usize> Decode for TVec<T, N> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(<[T; N]>::decode(r)?.into())
} }
} }
@ -459,6 +500,20 @@ impl Encode for ReadToEnd {
} }
} }
impl Encode for Option<EntityId> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
match self {
Some(id) => VarInt(
id.to_network_id()
.checked_add(1)
.context("i32::MAX is unrepresentable as an optional VarInt")?,
),
None => VarInt(0),
}
.encode(w)
}
}
fn encode_array_bounded<T: Encode>( fn encode_array_bounded<T: Encode>(
s: &[T], s: &[T],
min: usize, min: usize,

View file

@ -2,7 +2,7 @@ use std::collections::HashSet;
use std::error::Error; use std::error::Error;
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -27,7 +27,7 @@ use tokio::sync::{oneshot, Semaphore};
use uuid::Uuid; use uuid::Uuid;
use crate::codec::{Decoder, Encoder}; use crate::codec::{Decoder, Encoder};
use crate::config::{Biome, BiomeId, Config, Dimension, DimensionId, Login, ServerListPing}; use crate::config::{Biome, BiomeId, Config, Dimension, DimensionId, ServerListPing};
use crate::packets::handshake::{Handshake, HandshakeNextState}; use crate::packets::handshake::{Handshake, HandshakeNextState};
use crate::packets::login::{ use crate::packets::login::{
self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression, self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression,
@ -37,45 +37,16 @@ use crate::packets::status::{Ping, Pong, Request, Response};
use crate::protocol::{BoundedArray, BoundedString}; use crate::protocol::{BoundedArray, BoundedString};
use crate::util::valid_username; use crate::util::valid_username;
use crate::var_int::VarInt; use crate::var_int::VarInt;
use crate::{ use crate::world::Worlds;
ChunkStore, Client, ClientStore, EntityStore, Ticks, WorldStore, PROTOCOL_VERSION, VERSION_NAME, use crate::{Client, ClientMut, Ticks, WorldsMut, PROTOCOL_VERSION, VERSION_NAME};
};
/// Holds the state of a running Minecraft server which is accessible inside the /// A handle to a running Minecraft server containing state which is accessible
/// update loop. To start a server, see [`ServerConfig`]. /// outside the update loop. Servers are internally refcounted and can be shared
/// /// between threads.
/// Fields of this struct are made public to enable disjoint borrows. For
/// instance, it is possible to mutate the list of entities while simultaneously
/// reading world data.
///
/// Note the `Deref` and `DerefMut` impls on this struct are (ab)used to
/// allow convenient access to the `other` field.
#[non_exhaustive]
pub struct Server {
pub entities: EntityStore,
pub clients: ClientStore,
pub worlds: WorldStore,
pub chunks: ChunkStore,
pub other: Other,
}
pub struct Other {
/// The shared portion of the server.
shared: SharedServer,
new_players_rx: Receiver<NewClientMessage>,
/// Incremented on every game tick.
tick_counter: Ticks,
/// The instant the current game tick began.
tick_start: Instant,
}
/// A server handle providing the subset of functionality which can be performed
/// outside the update loop. `SharedServer`s are interally refcounted and can be
/// freely cloned and shared between threads.
#[derive(Clone)] #[derive(Clone)]
pub struct SharedServer(Arc<SharedServerInner>); pub struct Server(Arc<ServerInner>);
struct SharedServerInner { struct ServerInner {
cfg: Box<dyn Config>, cfg: Box<dyn Config>,
address: SocketAddr, address: SocketAddr,
tick_rate: Ticks, tick_rate: Ticks,
@ -90,6 +61,11 @@ struct SharedServerInner {
biomes: Vec<Biome>, biomes: Vec<Biome>,
/// The instant the server was started. /// The instant the server was started.
start_instant: Instant, start_instant: Instant,
/// Receiver for new clients past the login stage.
new_clients_rx: Receiver<NewClientMessage>,
new_clients_tx: Sender<NewClientMessage>,
/// Incremented on every game tick.
tick_counter: AtomicI64,
/// A semaphore used to limit the number of simultaneous connections to the /// A semaphore used to limit the number of simultaneous connections to the
/// server. Closing this semaphore stops new connections. /// server. Closing this semaphore stops new connections.
connection_sema: Arc<Semaphore>, connection_sema: Arc<Semaphore>,
@ -102,7 +78,6 @@ struct SharedServerInner {
public_key_der: Box<[u8]>, public_key_der: Box<[u8]>,
/// For session server requests. /// For session server requests.
http_client: HttpClient, http_client: HttpClient,
new_clients_tx: Sender<NewClientMessage>,
} }
/// Contains information about a new client. /// Contains information about a new client.
@ -114,7 +89,7 @@ pub struct NewClientData {
struct NewClientMessage { struct NewClientMessage {
ncd: NewClientData, ncd: NewClientData,
reply: oneshot::Sender<anyhow::Result<ClientPacketChannels>>, reply: oneshot::Sender<ClientPacketChannels>,
} }
/// The result type returned from [`ServerConfig::start`] after the server is /// The result type returned from [`ServerConfig::start`] after the server is
@ -125,24 +100,7 @@ pub type ShutdownError = Box<dyn Error + Send + Sync + 'static>;
pub(crate) type ClientPacketChannels = (Sender<ServerPlayPacket>, Receiver<ClientPlayPacket>); pub(crate) type ClientPacketChannels = (Sender<ServerPlayPacket>, Receiver<ClientPlayPacket>);
pub(crate) type ServerPacketChannels = (Sender<ClientPlayPacket>, Receiver<ServerPlayPacket>); pub(crate) type ServerPacketChannels = (Sender<ClientPlayPacket>, Receiver<ServerPlayPacket>);
impl Other { impl Server {
/// Returns a reference to a [`SharedServer`].
pub fn shared(&self) -> &SharedServer {
&self.shared
}
/// Returns the number of ticks that have elapsed since the server began.
pub fn current_tick(&self) -> Ticks {
self.tick_counter
}
/// Returns the instant the current tick began.
pub fn tick_start(&self) -> Instant {
self.tick_start
}
}
impl SharedServer {
pub fn config(&self) -> &(impl Config + ?Sized) { pub fn config(&self) -> &(impl Config + ?Sized) {
self.0.cfg.as_ref() self.0.cfg.as_ref()
} }
@ -203,8 +161,11 @@ impl SharedServer {
} }
/// Returns an iterator over all added biomes and their associated /// Returns an iterator over all added biomes and their associated
/// [`BiomeId`]. /// [`BiomeId`] in ascending order.
pub fn biomes(&self) -> impl FusedIterator<Item = (BiomeId, &Biome)> + Clone { pub fn biomes(
&self,
) -> impl ExactSizeIterator<Item = (BiomeId, &Biome)> + DoubleEndedIterator + FusedIterator + Clone
{
self.0 self.0
.biomes .biomes
.iter() .iter()
@ -217,6 +178,11 @@ impl SharedServer {
self.0.start_instant self.0.start_instant
} }
/// Returns the number of ticks that have elapsed since the server began.
pub fn current_tick(&self) -> Ticks {
self.0.tick_counter.load(Ordering::SeqCst)
}
/// Immediately stops new connections to the server and initiates server /// Immediately stops new connections to the server and initiates server
/// shutdown. The given result is returned through [`ServerConfig::start`]. /// shutdown. The given result is returned through [`ServerConfig::start`].
/// ///
@ -232,44 +198,23 @@ impl SharedServer {
} }
} }
impl Deref for Server {
type Target = Other;
fn deref(&self) -> &Self::Target {
&self.other
}
}
impl DerefMut for Server {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.other
}
}
impl Deref for Other {
type Target = SharedServer;
fn deref(&self) -> &Self::Target {
&self.shared
}
}
/// Consumes the configuration and starts the server. /// Consumes the configuration and starts the server.
/// ///
/// The function returns when the server has shut down, a runtime error /// The function returns when the server has shut down, a runtime error
/// occurs, or the configuration is invalid. /// occurs, or the configuration is invalid.
pub fn start_server(config: impl Config) -> ShutdownResult { pub fn start_server(config: impl Config) -> ShutdownResult {
let mut server = setup_server(config).map_err(ShutdownError::from)?; let server = setup_server(config).map_err(ShutdownError::from)?;
let shared = server.shared().clone(); let _guard = server.tokio_handle().enter();
let _guard = shared.tokio_handle().enter(); let mut worlds = Worlds::new(server.clone());
let mut worlds_mut = WorldsMut::new(&mut worlds);
shared.config().init(&mut server); server.config().init(&server, worlds_mut.reborrow());
tokio::spawn(do_accept_loop(shared)); tokio::spawn(do_accept_loop(server.clone()));
do_update_loop(&mut server) do_update_loop(server, worlds_mut)
} }
fn setup_server(cfg: impl Config) -> anyhow::Result<Server> { fn setup_server(cfg: impl Config) -> anyhow::Result<Server> {
@ -359,7 +304,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<Server> {
rsa_der::public_key_to_der(&rsa_key.n().to_bytes_be(), &rsa_key.e().to_bytes_be()) rsa_der::public_key_to_der(&rsa_key.n().to_bytes_be(), &rsa_key.e().to_bytes_be())
.into_boxed_slice(); .into_boxed_slice();
let (new_players_tx, new_players_rx) = flume::bounded(1); let (new_clients_tx, new_clients_rx) = flume::bounded(1);
let runtime = if tokio_handle.is_none() { let runtime = if tokio_handle.is_none() {
Some(Runtime::new()?) Some(Runtime::new()?)
@ -372,123 +317,125 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<Server> {
None => tokio_handle.unwrap(), None => tokio_handle.unwrap(),
}; };
let shared = SharedServer(Arc::new(SharedServerInner { let server = ServerInner {
cfg: Box::new(cfg), cfg: Box::new(cfg),
address, address,
tick_rate, tick_rate,
online_mode, online_mode,
max_connections, max_connections,
outgoing_packet_capacity,
incoming_packet_capacity, incoming_packet_capacity,
outgoing_packet_capacity,
tokio_handle, tokio_handle,
_tokio_runtime: runtime, _tokio_runtime: runtime,
dimensions, dimensions,
biomes, biomes,
start_instant: Instant::now(), start_instant: Instant::now(),
new_clients_rx,
new_clients_tx,
tick_counter: AtomicI64::new(0),
connection_sema: Arc::new(Semaphore::new(max_connections)), connection_sema: Arc::new(Semaphore::new(max_connections)),
shutdown_result: Mutex::new(None), shutdown_result: Mutex::new(None),
rsa_key, rsa_key,
public_key_der, public_key_der,
http_client: HttpClient::new(), http_client: HttpClient::new(),
new_clients_tx: new_players_tx, };
}));
Ok(Server { Ok(Server(Arc::new(server)))
entities: EntityStore::new(),
clients: ClientStore::new(),
worlds: WorldStore::new(),
chunks: ChunkStore::new(),
other: Other {
shared: shared.clone(),
tick_counter: 0,
tick_start: Instant::now(),
new_players_rx,
},
})
} }
fn do_update_loop(server: &mut Server) -> ShutdownResult { fn do_update_loop(server: Server, mut worlds: WorldsMut) -> ShutdownResult {
server.tick_start = Instant::now(); let mut tick_start = Instant::now();
let shared = server.shared().clone();
loop { loop {
if let Some(res) = server.0.shutdown_result.lock().take() { if let Some(res) = server.0.shutdown_result.lock().take() {
return res; return res;
} }
while let Ok(msg) = server.new_players_rx.try_recv() { while let Ok(msg) = server.0.new_clients_rx.try_recv() {
join_player(server, msg); join_player(&server, worlds.reborrow(), msg);
} }
server.clients.par_iter_mut().for_each(|(_, client)| { server.config().update(&server, worlds.reborrow());
client.update(
&server.entities,
&server.worlds,
&server.chunks,
&server.other,
)
});
server.entities.update(); worlds.par_iter_mut().for_each(|(_, mut world)| {
world.chunks.par_iter_mut().for_each(|(_, mut chunk)| {
if chunk.created_tick() == server.current_tick() {
// Chunks created this tick can have their changes applied immediately because
// they have not been observed by clients yet. Clients will not have to be sent
// the block change packet in this case.
chunk.apply_modifications();
}
});
server world.clients.par_iter_mut().for_each(|(_, mut client)| {
.chunks client.update(&server, &world.entities, &world.chunks, world.dimension);
.par_iter_mut() });
.for_each(|(_, chunk)| chunk.apply_modifications());
shared.config().update(server); world.entities.update();
// Chunks created this tick can have their changes applied immediately because world.chunks.par_iter_mut().for_each(|(_, mut chunk)| {
// they have not been observed by clients yet.
server.chunks.par_iter_mut().for_each(|(_, chunk)| {
if chunk.created_this_tick() {
chunk.clear_created_this_tick();
chunk.apply_modifications(); chunk.apply_modifications();
} });
}); });
// Sleep for the remainder of the tick. // Sleep for the remainder of the tick.
let tick_duration = Duration::from_secs_f64((server.0.tick_rate as f64).recip()); let tick_duration = Duration::from_secs_f64((server.0.tick_rate as f64).recip());
thread::sleep(tick_duration.saturating_sub(server.tick_start.elapsed())); thread::sleep(tick_duration.saturating_sub(tick_start.elapsed()));
server.tick_start = Instant::now(); tick_start = Instant::now();
server.tick_counter += 1; server.0.tick_counter.fetch_add(1, Ordering::SeqCst);
} }
} }
fn join_player(server: &mut Server, msg: NewClientMessage) { fn join_player(server: &Server, mut worlds: WorldsMut, msg: NewClientMessage) {
let (clientbound_tx, clientbound_rx) = flume::bounded(server.0.outgoing_packet_capacity); let (clientbound_tx, clientbound_rx) = flume::bounded(server.0.outgoing_packet_capacity);
let (serverbound_tx, serverbound_rx) = flume::bounded(server.0.incoming_packet_capacity); let (serverbound_tx, serverbound_rx) = flume::bounded(server.0.incoming_packet_capacity);
let client_packet_channels: ClientPacketChannels = (serverbound_tx, clientbound_rx); let client_packet_channels: ClientPacketChannels = (serverbound_tx, clientbound_rx);
let server_packet_channels: ServerPacketChannels = (clientbound_tx, serverbound_rx); let server_packet_channels: ServerPacketChannels = (clientbound_tx, serverbound_rx);
let _ = msg.reply.send(Ok(client_packet_channels)); let _ = msg.reply.send(client_packet_channels);
let client_backed_entity = match server.entities.create_with_uuid(msg.ncd.uuid) { let mut client = Client::new(
Some(id) => id,
None => {
log::warn!(
"player '{}' cannot join the server because their UUID ({}) conflicts with an \
existing entity",
msg.ncd.username,
msg.ncd.uuid
);
return;
}
};
server.clients.create(Client::new(
server_packet_channels, server_packet_channels,
client_backed_entity,
msg.ncd.username, msg.ncd.username,
msg.ncd.uuid,
server, server,
)); );
let mut client_mut = ClientMut::new(&mut client);
match server
.0
.cfg
.join(server, client_mut.reborrow(), worlds.reborrow())
{
Ok(world_id) => {
if let Some(mut world) = worlds.get_mut(world_id) {
if world.entities.get_with_uuid(client.uuid()).is_none() {
world.clients.create(client);
} else {
log::warn!(
"client '{}' cannot join the server because their UUID ({}) conflicts \
with an existing entity",
client.username(),
client.uuid()
);
}
} else {
log::warn!(
"client '{}' cannot join the server because the WorldId returned by \
Config::join is invalid.",
client.username()
);
}
}
Err(errmsg) => client_mut.disconnect(errmsg),
}
} }
type Codec = (Encoder<OwnedWriteHalf>, Decoder<OwnedReadHalf>); type Codec = (Encoder<OwnedWriteHalf>, Decoder<OwnedReadHalf>);
async fn do_accept_loop(server: SharedServer) { async fn do_accept_loop(server: Server) {
log::trace!("entering accept loop"); log::trace!("entering accept loop");
let listener = match TcpListener::bind(server.0.address).await { let listener = match TcpListener::bind(server.0.address).await {
@ -528,7 +475,7 @@ async fn do_accept_loop(server: SharedServer) {
} }
async fn handle_connection( async fn handle_connection(
server: SharedServer, server: Server,
stream: TcpStream, stream: TcpStream,
remote_addr: SocketAddr, remote_addr: SocketAddr,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@ -556,7 +503,7 @@ async fn handle_connection(
} }
async fn handle_status( async fn handle_status(
server: SharedServer, server: Server,
c: &mut Codec, c: &mut Codec,
remote_addr: SocketAddr, remote_addr: SocketAddr,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@ -607,7 +554,7 @@ async fn handle_status(
/// Handle the login process and return the new player's data if successful. /// Handle the login process and return the new player's data if successful.
async fn handle_login( async fn handle_login(
server: &SharedServer, server: &Server,
c: &mut Codec, c: &mut Codec,
remote_addr: SocketAddr, remote_addr: SocketAddr,
) -> anyhow::Result<Option<NewClientData>> { ) -> anyhow::Result<Option<NewClientData>> {
@ -720,7 +667,7 @@ async fn handle_login(
remote_addr, remote_addr,
}; };
if let Login::Disconnect(reason) = server.0.cfg.login(server, &npd).await { if let Err(reason) = server.0.cfg.login(server, &npd).await {
log::info!("Disconnect at login: \"{reason}\""); log::info!("Disconnect at login: \"{reason}\"");
c.0.write_packet(&login::Disconnect { reason }).await?; c.0.write_packet(&login::Disconnect { reason }).await?;
return Ok(None); return Ok(None);
@ -735,7 +682,7 @@ async fn handle_login(
Ok(Some(npd)) Ok(Some(npd))
} }
async fn handle_play(server: &SharedServer, c: Codec, ncd: NewClientData) -> anyhow::Result<()> { async fn handle_play(server: &Server, c: Codec, ncd: NewClientData) -> anyhow::Result<()> {
let (reply_tx, reply_rx) = oneshot::channel(); let (reply_tx, reply_rx) = oneshot::channel();
server server
@ -748,7 +695,7 @@ async fn handle_play(server: &SharedServer, c: Codec, ncd: NewClientData) -> any
.await?; .await?;
let (packet_tx, packet_rx) = match reply_rx.await { let (packet_tx, packet_rx) = match reply_rx.await {
Ok(res) => res?, Ok(res) => res,
Err(_) => return Ok(()), // Server closed Err(_) => return Ok(()), // Server closed
}; };

View file

@ -159,7 +159,7 @@ impl<T> SlotMap<T> {
} }
pub fn retain(&mut self, mut f: impl FnMut(Key, &mut T) -> bool) { pub fn retain(&mut self, mut f: impl FnMut(Key, &mut T) -> bool) {
for (i, slot) in self.slots.iter_mut().enumerate() { for (i, mut slot) in self.slots.iter_mut().enumerate() {
if let Slot::Occupied { value, version } = &mut slot { if let Slot::Occupied { value, version } = &mut slot {
let key = Key::new(i as u32, *version); let key = Key::new(i as u32, *version);

View file

@ -1,38 +1,72 @@
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::ops::Deref;
use rayon::iter::ParallelIterator; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use crate::chunk::{ChunkId, ChunkPos}; use crate::chunk::ChunkPos;
use crate::config::DimensionId; use crate::config::DimensionId;
use crate::slotmap::{Key, SlotMap}; use crate::slotmap::{Key, SlotMap};
use crate::Id; use crate::{
Chunks, ChunksMut, Clients, ClientsMut, Entities, EntitiesMut, Entity, EntityId, Server,
};
pub struct WorldStore { pub struct Worlds {
sm: SlotMap<World>, sm: SlotMap<World>,
server: Server,
}
pub struct WorldsMut<'a>(&'a mut Worlds);
impl<'a> Deref for WorldsMut<'a> {
type Target = Worlds;
fn deref(&self) -> &Self::Target {
&self.0
}
} }
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct WorldId(Key); pub struct WorldId(Key);
impl Id for WorldId { impl Worlds {
fn idx(self) -> usize { pub(crate) fn new(server: Server) -> Self {
self.0.index() as usize Self {
} sm: SlotMap::new(),
} server,
}
impl WorldStore {
pub(crate) fn new() -> Self {
Self { sm: SlotMap::new() }
} }
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
self.sm.count() self.sm.count()
} }
pub fn get(&self, world: WorldId) -> Option<WorldRef> {
self.sm.get(world.0).map(WorldRef::new)
}
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, WorldRef)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (WorldId(k), WorldRef::new(v)))
}
}
impl<'a> WorldsMut<'a> {
pub(crate) fn new(worlds: &'a mut Worlds) -> Self {
Self(worlds)
}
pub fn reborrow(&mut self) -> WorldsMut {
WorldsMut(self.0)
}
pub fn create(&mut self, dim: DimensionId) -> WorldId { pub fn create(&mut self, dim: DimensionId) -> WorldId {
WorldId(self.sm.insert(World { WorldId(self.0.sm.insert(World {
chunks: HashMap::new(), clients: Clients::new(),
entities: Entities::new(),
chunks: Chunks::new(
self.server.clone(),
(self.server.dimension(dim).height / 16) as u32,
),
dimension: dim, dimension: dim,
})) }))
} }
@ -43,53 +77,98 @@ impl WorldStore {
/// Note that any entities with positions inside the deleted world will not /// Note that any entities with positions inside the deleted world will not
/// be deleted themselves. /// be deleted themselves.
pub fn delete(&mut self, world: WorldId) -> bool { pub fn delete(&mut self, world: WorldId) -> bool {
self.sm.remove(world.0).is_some() self.0.sm.remove(world.0).is_some()
} }
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) { pub fn retain(&mut self, mut f: impl FnMut(WorldId, WorldMut) -> bool) {
self.sm.retain(|k, v| f(WorldId(k), v)) self.0.sm.retain(|k, v| f(WorldId(k), WorldMut::new(v)))
} }
pub fn get(&self, world: WorldId) -> Option<&World> { pub fn get_mut(&mut self, world: WorldId) -> Option<WorldMut> {
self.sm.get(world.0) self.0.sm.get_mut(world.0).map(WorldMut::new)
} }
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, WorldMut)> + '_ {
self.sm.get_mut(world.0) self.0
.sm
.iter_mut()
.map(|(k, v)| (WorldId(k), WorldMut::new(v)))
} }
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, WorldRef)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (WorldId(k), v)) self.0
.sm
.par_iter()
.map(|(k, v)| (WorldId(k), WorldRef::new(v)))
} }
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ { pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, WorldMut)> + '_ {
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v)) self.0
} .sm
.par_iter_mut()
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ { .map(|(k, v)| (WorldId(k), WorldMut::new(v)))
self.sm.par_iter().map(|(k, v)| (WorldId(k), v))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
} }
} }
pub struct World { /// A world on the server is a space for chunks, entities, and clients to
chunks: HashMap<ChunkPos, ChunkId>, /// inhabit.
///
/// Worlds maintain a collection of chunks and entities that are a part of it.
/// For a chunk or entity to appear, it must be inserted into the world. Chunks
/// and entities can be in multiple worlds at the same time.
///
/// Deleted chunks and entities are automatically removed from worlds at the end
/// of each tick.
pub(crate) struct World {
clients: Clients,
entities: Entities,
chunks: Chunks,
dimension: DimensionId, dimension: DimensionId,
} }
impl World { /// A bag of immutable references to the components of a world.
pub fn dimension(&self) -> DimensionId { pub struct WorldRef<'a> {
self.dimension pub clients: &'a Clients,
} pub entities: &'a Entities,
pub chunks: &'a Chunks,
pub dimension: DimensionId,
}
pub fn chunks(&self) -> &HashMap<ChunkPos, ChunkId> { impl<'a> WorldRef<'a> {
&self.chunks pub(crate) fn new(w: &'a World) -> Self {
} Self {
clients: &w.clients,
pub fn chunks_mut(&mut self) -> &mut HashMap<ChunkPos, ChunkId> { entities: &w.entities,
&mut self.chunks chunks: &w.chunks,
dimension: w.dimension,
}
}
}
/// A bag of mutable references to the components of a world.
pub struct WorldMut<'a> {
pub clients: ClientsMut<'a>,
pub entities: EntitiesMut<'a>,
pub chunks: ChunksMut<'a>,
pub dimension: DimensionId,
}
impl<'a> WorldMut<'a> {
pub(crate) fn new(w: &'a mut World) -> Self {
WorldMut {
clients: ClientsMut::new(&mut w.clients),
entities: EntitiesMut::new(&mut w.entities),
chunks: ChunksMut::new(&mut w.chunks),
dimension: w.dimension,
}
}
pub fn immutable(&'a self) -> WorldRef<'a> {
WorldRef {
clients: &self.clients,
entities: &self.entities,
chunks: &self.chunks,
dimension: self.dimension,
}
} }
} }