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]
aes = "0.7"
anyhow = "1"
arrayvec = "0.7"
ascii = "1"
async-trait = "0.1"
base64 = "0.13"
bitfield-struct = "0.1"
bitvec = "1"
bytes = "1"
byteorder = "1"
bytes = "1"
cfb8 = "0.7"
flate2 = "1"
flume = "0.10"
futures = "0.3"
hematite-nbt = "0.5"
log = "0.4"
nalgebra-glm = "0.16"
nalgebra-glm = "0.17"
num = "0.4"
parking_lot = "0.12"
paste = "1"
@ -36,7 +38,7 @@ sha1 = "0.10"
sha2 = "0.10"
thiserror = "1"
tokio = { version = "1", features = ["full"] }
uuid = "0.8"
uuid = "1"
[dependencies.reqwest]
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 {
if b {
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> {
match self {
Self::True => Some(true),

View file

@ -41,7 +41,6 @@ enum Type {
Nbt,
Particle,
VillagerData,
OptVarInt,
Pose,
// ==== Specialized ==== //
OptEntityId,
@ -49,6 +48,76 @@ enum Type {
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 {
name: &'static str,
offset: u8,
@ -60,7 +129,7 @@ const BASE_ENTITY: Class = Class {
inherit: None,
fields: &[
Field {
name: "base_entity_bits",
name: "base_entity_flags",
typ: Type::BitFields(&[
BitField {
name: "on_fire",
@ -135,7 +204,7 @@ const ABSTRACT_ARROW: Class = Class {
inherit: Some(&BASE_ENTITY),
fields: &[
Field {
name: "abstract_arrow_bits",
name: "abstract_arrow_flags",
typ: Type::BitFields(&[
BitField {
name: "critical",
@ -161,7 +230,7 @@ const LIVING_ENTITY: Class = Class {
inherit: Some(&BASE_ENTITY),
fields: &[
Field {
name: "living_entity_bits",
name: "living_entity_flags",
typ: Type::BitFields(&[
BitField {
name: "hand_active",
@ -211,7 +280,7 @@ const MOB: Class = Class {
name: "mob",
inherit: Some(&LIVING_ENTITY),
fields: &[Field {
name: "mob_bits",
name: "mob_flags",
typ: Type::BitFields(&[
BitField {
name: "ai_disabled",
@ -279,7 +348,7 @@ const ABSTRACT_HORSE: Class = Class {
inherit: Some(&ANIMAL),
fields: &[
Field {
name: "horse_bits",
name: "horse_flags",
typ: Type::BitFields(&[
BitField {
name: "tame",
@ -339,7 +408,7 @@ const TAMEABLE_ANIMAL: Class = Class {
name: "tameable_animal",
inherit: Some(&ANIMAL),
fields: &[Field {
name: "tameable_animal_bits",
name: "tameable_animal_flags",
typ: Type::BitFields(&[
BitField {
name: "sitting",
@ -435,7 +504,7 @@ const SPIDER: Class = Class {
name: "spider",
inherit: Some(&MONSTER),
fields: &[Field {
name: "spider_bits",
name: "spider_flags",
typ: Type::BitFields(&[BitField {
name: "climbing",
offset: 0,
@ -843,7 +912,7 @@ const ENTITIES: &[Class] = &[
inherit: Some(&LIVING_ENTITY),
fields: &[
Field {
name: "armor_stand_bits",
name: "armor_stand_flags",
typ: Type::BitFields(&[
BitField {
name: "small",
@ -851,7 +920,7 @@ const ENTITIES: &[Class] = &[
default: false,
},
BitField {
name: "has_arms",
name: "arms",
offset: 1,
default: false,
},
@ -861,7 +930,7 @@ const ENTITIES: &[Class] = &[
default: false,
},
BitField {
name: "is_marker",
name: "marker",
offset: 3,
default: false,
},
@ -897,7 +966,7 @@ const ENTITIES: &[Class] = &[
name: "bat",
inherit: Some(&AMBIENT_CREATURE),
fields: &[Field {
name: "bat_bits",
name: "bat_flags",
typ: Type::BitFields(&[BitField {
name: "hanging",
offset: 0,
@ -1034,7 +1103,7 @@ const ENTITIES: &[Class] = &[
inherit: Some(&ANIMAL),
fields: &[
Field {
name: "bee_bits",
name: "bee_flags",
typ: Type::BitFields(&[
BitField {
name: "angry",
@ -1068,7 +1137,7 @@ const ENTITIES: &[Class] = &[
typ: Type::VarInt(0), // TODO: 0 for red, 1 for snow
},
Field {
name: "fox_bits",
name: "fox_flags",
typ: Type::BitFields(&[
BitField {
name: "sitting",
@ -1151,7 +1220,7 @@ const ENTITIES: &[Class] = &[
typ: Type::Byte(0),
},
Field {
name: "panda_bits",
name: "panda_flags",
typ: Type::BitFields(&[
BitField {
name: "sneezing",
@ -1355,7 +1424,7 @@ const ENTITIES: &[Class] = &[
name: "iron_golem",
inherit: Some(&ABSTRACT_GOLEM),
fields: &[Field {
name: "iron_golem_bits",
name: "iron_golem_flags",
typ: Type::BitFields(&[BitField {
name: "player_created",
offset: 0,
@ -1367,7 +1436,7 @@ const ENTITIES: &[Class] = &[
name: "snow_golem",
inherit: Some(&ABSTRACT_GOLEM),
fields: &[Field {
name: "snow_golem_bits",
name: "snow_golem_flags",
typ: Type::BitFields(&[BitField {
name: "pumpkin_hat",
offset: 4,
@ -1430,7 +1499,7 @@ const ENTITIES: &[Class] = &[
name: "blaze",
inherit: Some(&MONSTER),
fields: &[Field {
name: "blaze_bits",
name: "blaze_flags",
typ: Type::BitFields(&[BitField {
name: "blaze_on_fire", // TODO: better name for this?
offset: 0,
@ -1522,7 +1591,7 @@ const ENTITIES: &[Class] = &[
name: "vex",
inherit: Some(&MONSTER),
fields: &[Field {
name: "vex_bits",
name: "vex_flags",
typ: Type::BitFields(&[BitField {
name: "attacking",
offset: 0,
@ -1665,7 +1734,10 @@ const ENTITIES: &[Class] = &[
Class {
name: "magma_cube",
inherit: Some(&MOB),
fields: &[], // TODO: what are the fields?
fields: &[Field {
name: "size",
typ: Type::VarInt(1),
}],
},
Class {
name: "llama_spit",
@ -1777,6 +1849,49 @@ pub fn build() -> anyhow::Result<()> {
.map(|c| ident(c.name.to_pascal_case()))
.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 mut fields = Vec::new();
collect_class_fields(class, &mut fields);
@ -1787,7 +1902,7 @@ pub fn build() -> anyhow::Result<()> {
let typ = match f.typ {
Type::BitFields(_) => quote! { u8 },
Type::Byte(_) => quote! { u8 },
Type::VarInt(_) => quote! { i32 },
Type::VarInt(_) => quote! { VarInt },
Type::Float(_) => quote! { f32 },
Type::String(_) => quote! { Box<str> },
Type::Text => quote! { Box<Text> },
@ -1803,7 +1918,6 @@ pub fn build() -> anyhow::Result<()> {
Type::Nbt => quote! { nbt::Blob },
Type::Particle => quote! { () }, // TODO
Type::VillagerData => quote! { VillagerData },
Type::OptVarInt => quote! { OptVarInt },
Type::Pose => quote! { Pose },
Type::OptEntityId => quote! { Option<EntityId> },
Type::BoatVariant => quote! { BoatVariant },
@ -1816,46 +1930,7 @@ pub fn build() -> anyhow::Result<()> {
let constructor_fields = fields.iter().map(|field| {
let name = ident(field.name.to_snake_case());
let val = match field.typ {
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() },
};
let val = field.typ.default_expr();
quote! {
#name: #val,
}
@ -1881,7 +1956,7 @@ pub fn build() -> anyhow::Result<()> {
pub fn #setter_name(&mut self, #name: #type_name) {
if self.#name != #name {
self.modified_bits |= 1 << #field_offset;
self.modified_flags |= 1 << #field_offset;
}
self.#name = #name;
@ -1892,9 +1967,6 @@ pub fn build() -> anyhow::Result<()> {
Type::BitFields(bfs) => bfs
.iter()
.map(|bf| {
if bf.name.to_snake_case().is_empty() {
eprintln!("{}", field.name);
}
let bit_name = ident(bf.name.to_snake_case());
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) {
let orig = self.#getter_name();
self.#name = (self.#name & !(1 << #offset)) | ((#bit_name as u8) << #offset);
if orig != self.#getter_name() {
self.modified_bits |= 1 << #field_offset;
if self.#getter_name() != #bit_name {
self.#name = (self.#name & !(1 << #offset)) | ((#bit_name as u8) << #offset);
self.modified_flags |= 1 << #field_offset;
}
}
}
})
.collect(),
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::String(_) => quote! {
pub fn #getter_name(&self) -> &str {
@ -1931,7 +2012,7 @@ pub fn build() -> anyhow::Result<()> {
let #name = #name.into();
if self.#name != #name {
self.modified_bits |= 1 << #field_offset;
self.modified_flags |= 1 << #field_offset;
}
self.#name = #name;
@ -1946,7 +2027,7 @@ pub fn build() -> anyhow::Result<()> {
let #name = Box::new(#name.into());
if self.#name != #name {
self.modified_bits |= 1 << #field_offset;
self.modified_flags |= 1 << #field_offset;
}
self.#name = #name;
@ -1961,7 +2042,7 @@ pub fn build() -> anyhow::Result<()> {
let #name = #name.map(|x| Box::new(x.into()));
if self.#name != #name {
self.modified_bits |= 1 << #field_offset;
self.modified_flags |= 1 << #field_offset;
}
self.#name = #name;
@ -1982,7 +2063,7 @@ pub fn build() -> anyhow::Result<()> {
pub fn #setter_name(&mut self, #name: nbt::Blob) {
if self.#name != #name {
self.modified_bits |= 1 << #field_offset;
self.modified_flags |= 1 << #field_offset;
}
self.#name = #name;
@ -1990,21 +2071,8 @@ pub fn build() -> anyhow::Result<()> {
},
Type::Particle => quote! {}, // TODO
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::OptEntityId => quote! {}, // TODO
Type::OptEntityId => standard_getter_setter(quote!(Option<EntityId>)),
Type::BoatVariant => standard_getter_setter(quote!(BoatVariant)),
Type::MainHand => standard_getter_setter(quote!(MainHand)),
}
@ -2014,14 +2082,14 @@ pub fn build() -> anyhow::Result<()> {
quote! {
pub struct #name {
/// Contains a set bit for each modified field.
modified_bits: u32,
modified_flags: u32,
#(#struct_fields)*
}
impl #name {
pub(super) fn new() -> Self {
Self {
modified_bits: 0,
modified_flags: 0,
#(#constructor_fields)*
}
}
@ -2031,23 +2099,64 @@ pub fn build() -> anyhow::Result<()> {
}
});
let finished = quote! {
pub enum EntityData {
#(#entity_type_variants(#entity_type_variants),)*
}
let initial_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);
impl EntityData {
pub(super) fn new() -> Self {
Self::Marker(Marker::new())
}
pub fn typ(&self) -> EntityType {
match self {
#(Self::#entity_type_variants(_) => EntityType::#entity_type_variants,)*
let check_fields = fields.into_iter().enumerate().map(|(idx, f)| {
let name = ident(f.name.to_snake_case());
let default = f.typ.default_expr();
let index: u8 = idx.try_into().unwrap();
let type_id = f.typ.type_id();
quote! {
if m.#name != #default {
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)]
pub enum EntityType {
#(#entity_type_variants,)*
@ -2060,6 +2169,64 @@ pub fn build() -> anyhow::Result<()> {
}
#(#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())

View file

@ -4,15 +4,16 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use log::LevelFilter;
use valence::block::BlockState;
use valence::client::GameMode;
use valence::config::{Config, Login, ServerListPing};
use valence::config::{Config, ServerListPing};
use valence::text::Color;
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 {
env_logger::Builder::new()
.filter_level(LevelFilter::Trace)
.filter_module("valence", LevelFilter::Trace)
.parse_default_env()
.init();
@ -39,63 +40,7 @@ impl Config for Game {
false
}
fn init(&self, server: &mut Server) {
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 {
async fn server_list_ping(&self, _server: &Server, _remote_addr: SocketAddr) -> ServerListPing {
ServerListPing::Respond {
online_players: self.player_count.load(Ordering::SeqCst) 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 {
let res = self
fn join(
&self,
_server: &Server,
_client: ClientMut,
worlds: WorldsMut,
) -> Result<WorldId, Text> {
if let Ok(_) = self
.player_count
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
(count < MAX_PLAYERS).then(|| count + 1)
});
if res.is_ok() {
Login::Join
})
{
Ok(worlds.iter().next().unwrap().0)
} 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::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)]
pub struct Aabb<T, const D: usize> {
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 }
}
@ -36,6 +44,24 @@ impl<T: Number, const D: usize> Aabb<T, D> {
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 {
let l = glm::less_than_equal(&self.min, &other.max);
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> {
/// Construct an AABB from a center (centroid) and the dimensions of the box
/// along each axis.
pub fn from_center_and_dimensions(center: TVec<T, D>, dims: TVec<T, D>) -> Self {
let half = dims * T::from_subset(&0.5);
pub fn from_center_and_dimensions(
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 {
min: center - half,
max: center + half,
}
}
pub fn center(&self) -> TVec<T, D> {
// TODO: distribute multiplication to avoid intermediate overflow?
(self.min + self.max) * T::from_subset(&0.5)
pub fn collides_with_sphere(
&self,
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 {
self.distance_to_point(center) <= radius
}
pub fn distance_to_point(&self, p: TVec<T, D>) -> T {
pub fn distance_to_point(&self, p: impl Into<TVec<T, D>>) -> T {
let p = p.into();
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> {
fn default() -> Self {
let d = T::default();

View file

@ -1,4 +1,4 @@
#![allow(clippy::all)]
#![allow(clippy::all, missing_docs)]
use std::fmt;
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)]
mod tests {
use super::*;

View file

@ -1,6 +1,3 @@
// TODO: rename to ByteAngle?
use std::f64::consts::TAU;
use std::io::{Read, Write};
use crate::protocol::{Decode, Encode};
@ -10,12 +7,12 @@ use crate::protocol::{Decode, Encode};
pub struct ByteAngle(pub u8);
impl ByteAngle {
pub fn from_radians_f64(f: f64) -> ByteAngle {
ByteAngle((f.rem_euclid(TAU) / TAU * 256.0).round() as u8)
pub fn from_degrees(f: f32) -> ByteAngle {
ByteAngle((f.rem_euclid(360.0) / 360.0 * 256.0).round() as u8)
}
pub fn to_radians_f64(self) -> f64 {
self.0 as f64 / 256.0 * TAU
pub fn to_degrees(self) -> f32 {
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
use std::collections::HashMap;
use std::io::Write;
use std::iter::FusedIterator;
use std::ops::Deref;
use bitvec::bitvec;
use bitvec::vec::BitVec;
use num::Integer;
use rayon::iter::ParallelIterator;
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use crate::block::BlockState;
use crate::glm::DVec2;
@ -14,73 +16,99 @@ use crate::packets::play::{
BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange,
};
use crate::protocol::{Encode, Nbt};
use crate::slotmap::{Key, SlotMap};
use crate::var_int::VarInt;
use crate::BiomeId;
use crate::{BiomeId, Server, Ticks};
pub struct ChunkStore {
sm: SlotMap<Chunk>,
pub struct Chunks {
chunks: HashMap<ChunkPos, Chunk>,
server: Server,
section_count: u32,
}
impl ChunkStore {
pub(crate) fn new() -> Self {
Self { sm: SlotMap::new() }
impl Chunks {
pub(crate) fn new(server: Server, section_count: u32) -> Self {
Self {
chunks: HashMap::new(),
server,
section_count,
}
}
pub fn count(&self) -> usize {
self.sm.count()
self.chunks.len()
}
pub fn create(&mut self, section_count: usize) -> ChunkId {
ChunkId(self.sm.insert(Chunk::new(section_count)))
}
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 get(&self, pos: ChunkPos) -> Option<&Chunk> {
self.chunks.get(&pos)
}
pub fn clear(&mut self) {
self.sm.clear();
self.chunks.clear();
}
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkId, &Chunk)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (ChunkId(k), v))
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
}
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkId, &mut Chunk)> + '_ {
self.sm.iter_mut().map(|(k, v)| (ChunkId(k), v))
}
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))
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ChunkId(Key);
impl<'a> ChunksMut<'a> {
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 {
sections: Box<[ChunkSection]>,
// TODO block_entities: HashMap<u32, BlockEntity>,
/// The MOTION_BLOCKING heightmap
heightmap: Vec<i64>,
modified: bool,
created_this_tick: bool,
created_tick: Ticks,
}
impl Chunk {
pub(crate) fn new(section_count: usize) -> Self {
pub(crate) fn new(section_count: u32, current_tick: Ticks) -> Self {
let sect = ChunkSection {
blocks: [BlockState::default(); 4096],
biomes: [BiomeId::default(); 64],
@ -89,22 +117,18 @@ impl Chunk {
};
let mut chunk = Self {
sections: vec![sect; section_count].into(),
sections: vec![sect; section_count as usize].into(),
heightmap: Vec::new(),
modified: true,
created_this_tick: true,
created_tick: current_tick,
};
chunk.apply_modifications();
ChunkMut(&mut chunk).apply_modifications();
chunk
}
pub fn created_this_tick(&self) -> bool {
self.created_this_tick
}
pub(crate) fn clear_created_this_tick(&mut self) {
self.created_this_tick = false;
pub fn created_tick(&self) -> Ticks {
self.created_tick
}
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 {
if x < 4 && y < self.height() / 4 && z < 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) {
if x < 4 && y < self.height() / 4 && z < 4 {
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = b;
}
}
/// 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 {
/// Gets the chunk data packet for this chunk with the given position. This
/// does not include unapplied changes.
pub(crate) fn chunk_data_packet(&self, pos: ChunkPos) -> ChunkDataAndUpdateLight {
let mut blocks_and_biomes = Vec::new();
for i in 0..section_count {
match self.sections.get(i) {
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();
}
}
for sect in self.sections.iter() {
blocks_and_biomes.extend_from_slice(&sect.compact_data);
}
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 {
chunk_x: pos.x,
chunk_z: pos.z,
heightmaps: Nbt(ChunkDataHeightmaps { motion_blocking }),
heightmaps: Nbt(ChunkDataHeightmaps {
motion_blocking: self.heightmap.clone(),
}),
blocks_and_biomes,
block_entities: Vec::new(), // TODO
trust_edges: true,
@ -215,12 +189,44 @@ impl Chunk {
// TODO
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) {
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 {
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.
#[derive(Clone)]
struct ChunkSection {

View file

@ -1,41 +1,50 @@
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use std::iter::FusedIterator;
use std::ops::Deref;
use flume::{Receiver, Sender, TrySendError};
use glm::DVec3;
use rayon::iter::ParallelIterator;
use uuid::Uuid;
use crate::block_pos::BlockPos;
use crate::chunk::ChunkId;
use crate::config::{
Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId,
};
use crate::entity::EntityType;
pub use crate::packets::play::GameMode;
use crate::packets::play::{
Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic,
BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState,
ChangeGameStateReason, ClientPlayPacket, DimensionCodec, DimensionType, DimensionTypeRegistry,
DimensionTypeRegistryEntry, Disconnect, JoinGame, KeepAliveClientbound, PlayerPositionAndLook,
PlayerPositionAndLookFlags, ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance,
UpdateViewPosition,
ChangeGameStateReason, ClientPlayPacket, DestroyEntities, DimensionCodec, DimensionType,
DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, JoinGame, KeepAliveClientbound,
PlayerPositionAndLook, PlayerPositionAndLookFlags, ServerPlayPacket, SpawnPosition,
UnloadChunk, UpdateViewDistance, UpdateViewPosition,
};
use crate::protocol::{BoundedInt, Nbt};
use crate::server::{Other, ServerPacketChannels};
use crate::server::ServerPacketChannels;
use crate::slotmap::{Key, SlotMap};
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
use crate::var_int::VarInt;
use crate::world::WorldId;
use crate::{
glm, ident, ChunkPos, ChunkStore, EntityId, EntityStore, Server, Text, Ticks, WorldStore,
LIBRARY_NAMESPACE,
glm, ident, ChunkPos, Chunks, Entities, EntityId, Server, Text, Ticks, LIBRARY_NAMESPACE,
};
pub struct ClientStore {
pub struct Clients {
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 {
Self { sm: SlotMap::new() }
}
@ -44,43 +53,58 @@ impl ClientStore {
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> {
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 + '_ {
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 + '_ {
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);
/// Represents a client connected to the server after logging in.
@ -88,11 +112,10 @@ pub struct Client {
/// Setting this to `None` disconnects the client.
send: Option<Sender<ClientPlayPacket>>,
recv: Receiver<ServerPlayPacket>,
/// The entity this client is associated with.
entity: EntityId,
/// The tick this client was created.
created_tick: Ticks,
username: String,
uuid: Uuid,
on_ground: bool,
new_position: DVec3,
old_position: DVec3,
@ -113,9 +136,6 @@ pub struct Client {
spawn_position_yaw: f32,
/// If spawn_position or spawn_position_yaw were modified this tick.
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>,
/// The ID of the last keepalive sent.
last_keepalive_id: i64,
@ -127,9 +147,7 @@ pub struct Client {
/// This is used to determine what entity create/destroy packets should be
/// sent.
loaded_entities: HashSet<EntityId>,
hidden_entities: HashSet<EntityId>,
/// Loaded chunks and their positions.
loaded_chunks: HashMap<ChunkPos, ChunkId>,
loaded_chunks: HashSet<ChunkPos>,
new_game_mode: GameMode,
old_game_mode: GameMode,
settings: Option<Settings>,
@ -137,11 +155,21 @@ pub struct Client {
// 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 {
pub(crate) fn new(
packet_channels: ServerPacketChannels,
entity: EntityId,
username: String,
uuid: Uuid,
server: &Server,
) -> Self {
let (send, recv) = packet_channels;
@ -149,9 +177,9 @@ impl Client {
Self {
send: Some(send),
recv,
entity,
created_tick: server.current_tick(),
username,
uuid,
on_ground: false,
new_position: DVec3::default(),
old_position: DVec3::default(),
@ -163,16 +191,13 @@ impl Client {
spawn_position: BlockPos::default(),
spawn_position_yaw: 0.0,
modified_spawn_position: true,
old_world: None,
new_world: None,
events: Vec::new(),
last_keepalive_id: 0,
got_keepalive: true,
new_max_view_distance: 16,
old_max_view_distance: 0,
loaded_entities: HashSet::new(),
hidden_entities: HashSet::new(),
loaded_chunks: HashMap::new(),
loaded_chunks: HashSet::new(),
new_game_mode: GameMode::Survival,
old_game_mode: GameMode::Survival,
settings: None,
@ -187,6 +212,10 @@ impl Client {
&self.username
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn position(&self) -> DVec3 {
self.new_position
}
@ -199,80 +228,14 @@ impl Client {
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 {
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 {
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 {
self.send.is_none()
}
@ -285,28 +248,91 @@ impl Client {
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> {
self.settings.as_ref()
}
}
/// Returns the entity this client is backing.
pub fn entity(&self) -> EntityId {
self.entity
impl<'a> ClientMut<'a> {
pub(crate) fn new(client: &'a mut Client) -> Self {
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(
&mut self,
entities: &EntityStore,
worlds: &WorldStore,
chunks: &ChunkStore,
other: &Other,
server: &Server,
entities: &Entities,
chunks: &Chunks,
dimension_id: DimensionId,
) {
self.events.clear();
self.0.events.clear();
if self.is_disconnected() {
return;
@ -318,34 +344,28 @@ impl Client {
// Mark the client as disconnected when appropriate.
// We do this check after handling serverbound packets so that none are lost.
if self.recv.is_disconnected()
|| self.send.as_ref().map_or(true, |s| s.is_disconnected())
|| entities.get(self.entity).is_none()
{
self.send = None;
if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) {
self.0.send = None;
return;
}
let world = self.new_world.and_then(|w| worlds.get(w));
let dim_id = world.map_or(DimensionId::default(), |w| w.dimension());
let dim = other.dimension(dim_id);
let dimension = server.dimension(dimension_id);
// 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.
if self.created_tick == other.current_tick() {
if self.created_tick == server.current_tick() {
self.send_packet(JoinGame {
entity_id: self.entity.to_network_id(),
entity_id: 0, // EntityId 0 is reserved for clients.
is_hardcore: false, // TODO
gamemode: self.new_game_mode,
previous_gamemode: self.old_game_mode,
dimension_names: other
dimension_names: server
.dimensions()
.map(|(id, _)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0))
.collect(),
dimension_codec: Nbt(make_dimension_codec(other)),
dimension: Nbt(to_dimension_registry_item(dim)),
dimension_name: ident!("{LIBRARY_NAMESPACE}:dimension_{}", dim_id.0),
dimension_codec: Nbt(make_dimension_codec(server)),
dimension: Nbt(to_dimension_registry_item(dimension)),
dimension_name: ident!("{LIBRARY_NAMESPACE}:dimension_{}", dimension_id.0),
hashed_seed: 0,
max_players: VarInt(0),
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());
} else if self.old_game_mode != self.new_game_mode {
self.old_game_mode = self.new_game_mode;
} else if self.0.old_game_mode != self.0.new_game_mode {
self.0.old_game_mode = self.0.new_game_mode;
self.send_packet(ChangeGameState {
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)
if self.modified_spawn_position {
self.modified_spawn_position = false;
if self.0.modified_spawn_position {
self.0.modified_spawn_position = false;
self.send_packet(SpawnPosition {
location: self.spawn_position,
@ -376,61 +396,42 @@ impl Client {
}
// Update view distance fog on the client if necessary.
if self.old_max_view_distance != self.new_max_view_distance {
self.old_max_view_distance = self.new_max_view_distance;
if self.created_tick != other.current_tick() {
if self.0.old_max_view_distance != self.0.new_max_view_distance {
self.0.old_max_view_distance = self.0.new_max_view_distance;
if self.0.created_tick != server.current_tick() {
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.
if other.current_tick() % (other.tick_rate() * 8) == 0 {
if self.got_keepalive {
if server.current_tick() % (server.tick_rate() * 8) == 0 {
if self.0.got_keepalive {
let id = rand::random();
self.send_packet(KeepAliveClientbound { id });
self.last_keepalive_id = id;
self.got_keepalive = false;
self.0.last_keepalive_id = id;
self.0.got_keepalive = false;
} else {
self.disconnect("Timed out (no keepalive response)");
}
}
// Load, update, and unload chunks.
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;
}
// The actual view distance.
let view_dist = self
.0
.settings
.as_ref()
.map_or(2, |s| s.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
// they're in.
{
let old_section = self.old_position.map(|n| (n / 16.0) as i32);
let new_section = self.new_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.0.new_position.map(|n| (n / 16.0) as i32);
if old_section != new_section {
self.send_packet(UpdateViewPosition {
@ -440,50 +441,41 @@ impl Client {
}
}
// Unload deleted chunks and those outside the view distance. Also update
// existing chunks.
self.loaded_chunks.retain(|&pos, &mut chunk_id| {
if let Some(chunk) = chunks.get(chunk_id) {
// The cache stops chunk data packets from needing to be sent when a player is
// jumping between adjacent chunks.
let cache = 2;
if is_chunk_in_view_distance(center, pos, view_dist + cache) {
// Update existing chunks and unload those outside the view distance. Chunks
// that have been overwritten also need to be unloaded.
self.0.loaded_chunks.retain(|&pos| {
// The cache stops chunk data packets from needing to be sent when a player
// moves to an adjacent chunk and back to the original.
let cache = 2;
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) {
send_packet(&mut self.send, pkt);
send_packet(&mut self.0.send, pkt);
}
true
} else {
send_packet(
&mut self.send,
UnloadChunk {
chunk_x: pos.x,
chunk_z: pos.z,
},
);
false
return true;
}
} 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
for pos in chunks_in_view_distance(center, view_dist) {
if let Entry::Vacant(ve) = self.loaded_chunks.entry(pos) {
if let Some(&chunk_id) = world.and_then(|w| w.chunks().get(&pos)) {
if let Some(chunk) = chunks.get(chunk_id) {
ve.insert(chunk_id);
self.send_packet(chunk.chunk_data_packet(pos, (dim.height / 16) as usize));
if let Some(pkt) = chunk.block_change_packet(pos) {
self.send_packet(pkt);
}
if let Some(chunk) = chunks.get(pos) {
if self.0.loaded_chunks.insert(pos) {
self.send_packet(chunk.chunk_data_packet(pos));
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"
// 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 {
x: self.new_position.x,
y: self.new_position.y,
z: self.new_position.z,
yaw: self.yaw,
pitch: self.pitch,
flags: PlayerPositionAndLookFlags::new(false, false, false, false, false),
teleport_id: VarInt((self.teleport_id_counter - 1) as i32),
dismount_vehicle: false,
// self.send_packet(dbg!(PlayerPositionAndLook {
// position: self.new_position,
// yaw: self.yaw,
// pitch: self.pitch,
// flags: PlayerPositionAndLookFlags::new(false, false, false, false, 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) {
let client = &mut self.0;
fn handle_movement_packet(
client: &mut Client,
new_position: DVec3,
@ -536,18 +572,18 @@ impl Client {
match pkt {
ServerPlayPacket::TeleportConfirm(p) => {
if self.pending_teleports == 0 {
if client.pending_teleports == 0 {
self.disconnect("Unexpected teleport confirmation");
return;
}
let got = p.teleport_id.0 as u32;
let expected = self
let expected = client
.teleport_id_counter
.wrapping_sub(self.pending_teleports);
.wrapping_sub(client.pending_teleports);
if got == expected {
self.pending_teleports -= 1;
client.pending_teleports -= 1;
} else {
self.disconnect(format!(
"Unexpected teleport ID (expected {expected}, got {got})"
@ -559,7 +595,7 @@ impl Client {
ServerPlayPacket::ChatMessageServerbound(_) => {}
ServerPlayPacket::ClientStatus(_) => {}
ServerPlayPacket::ClientSettings(p) => {
let old = self.settings.replace(Settings {
let old = client.settings.replace(Settings {
locale: p.locale.0,
view_distance: p.view_distance.0,
chat_mode: p.chat_mode,
@ -569,7 +605,7 @@ impl Client {
allow_server_listings: p.allow_server_listings,
});
self.events.push(Event::SettingsChanged(old));
client.events.push(Event::SettingsChanged(old));
}
ServerPlayPacket::TabCompleteServerbound(_) => {}
ServerPlayPacket::ClickWindowButton(_) => {}
@ -581,39 +617,36 @@ impl Client {
ServerPlayPacket::InteractEntity(_) => {}
ServerPlayPacket::GenerateStructure(_) => {}
ServerPlayPacket::KeepAliveServerbound(p) => {
if self.got_keepalive {
let last_keepalive_id = client.last_keepalive_id;
if client.got_keepalive {
self.disconnect("Unexpected keepalive");
} else if p.id != self.last_keepalive_id {
} else if p.id != last_keepalive_id {
self.disconnect(format!(
"Keepalive ids don't match (expected {}, got {})",
self.last_keepalive_id, p.id
last_keepalive_id, p.id
));
} else {
self.got_keepalive = true;
client.got_keepalive = true;
}
}
ServerPlayPacket::LockDifficulty(_) => {}
ServerPlayPacket::PlayerPosition(p) => handle_movement_packet(
self,
glm::vec3(p.x, p.feet_y, p.z),
self.yaw,
self.pitch,
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::PlayerPosition(p) => {
handle_movement_packet(client, p.position, client.yaw, client.pitch, p.on_ground)
}
ServerPlayPacket::PlayerPositionAndRotation(p) => {
handle_movement_packet(client, p.position, p.yaw, p.pitch, p.on_ground)
}
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) => {
handle_movement_packet(self, self.new_position, self.yaw, self.pitch, p.on_ground)
}
ServerPlayPacket::PlayerMovement(p) => handle_movement_packet(
client,
client.new_position,
client.yaw,
client.pitch,
p.on_ground,
),
ServerPlayPacket::VehicleMoveServerbound(_) => {}
ServerPlayPacket::SteerBoat(_) => {}
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();
for (id, dim) in other.dimensions() {
for (id, dim) in server.dimensions() {
let id = id.0 as i32;
dims.push(DimensionTypeRegistryEntry {
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
@ -712,7 +745,7 @@ fn make_dimension_codec(other: &Other) -> DimensionCodec {
}
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));
}

View file

@ -5,15 +5,17 @@ use std::panic::{RefUnwindSafe, UnwindSafe};
use async_trait::async_trait;
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
/// server.
///
/// The config is used from multiple threads and must therefore implement
/// `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
/// of a method and released at the end without risk of deadlocking.
/// recursively by the library. In other words, a mutex can be aquired at
/// 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 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.
///
/// 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
/// Returns `20`, which is the same as Minecraft's official server.
fn tick_rate(&self) -> Ticks {
@ -140,6 +145,45 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
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
/// 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.
///
/// This method is called from within a tokio runtime.
///
/// # Default Implementation
/// The default implementation does nothing.
fn init(&self, server: &mut Server) {}
fn init(&self, server: &Server, worlds: WorldsMut) {}
/// Called once at the beginning of every server update (also known as
/// a "tick").
@ -162,37 +203,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
///
/// # Default Implementation
/// The default implementation does nothing.
fn update(&self, server: &mut Server) {}
/// 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
}
fn update(&self, server: &Server, worlds: WorldsMut);
}
/// The result of the [`server_list_ping`](Handler::server_list_ping) callback.
@ -213,25 +224,14 @@ pub enum ServerListPing<'a> {
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.
///
/// Dimension IDs must only be used on servers from which they originate.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct DimensionId(pub(crate) u16);
/// All dimension IDs are valid.
impl Id for DimensionId {
fn idx(self) -> usize {
impl DimensionId {
pub fn to_index(self) -> usize {
self.0 as usize
}
}
@ -313,9 +313,8 @@ pub enum DimensionEffects {
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct BiomeId(pub(crate) u16);
/// All Biome IDs are valid.
impl Id for BiomeId {
fn idx(self) -> usize {
impl BiomeId {
pub fn to_index(self) -> usize {
self.0 as usize
}
}

View file

@ -4,77 +4,44 @@ pub mod types;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::iter::FusedIterator;
use std::ops::Deref;
use bitfield_struct::bitfield;
use rayon::iter::ParallelIterator;
use uuid::Uuid;
use crate::byte_angle::ByteAngle;
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::{Aabb, Id, WorldId};
use crate::var_int::VarInt;
use crate::{Aabb, WorldId};
pub struct EntityStore {
pub struct Entities {
sm: SlotMap<Entity>,
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 EntityId(Key);
pub struct EntitiesMut<'a>(&'a mut Entities);
impl Id for EntityId {
fn idx(self) -> usize {
self.0.index() as usize
impl<'a> Deref for EntitiesMut<'a> {
type Target = Entities;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl EntityId {
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 {
impl Entities {
pub(crate) fn new() -> Self {
Self {
sm: SlotMap::new(),
uuid_to_entity: HashMap::new(),
partition: HashMap::new(),
}
}
@ -83,11 +50,38 @@ impl EntityStore {
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.
///
/// 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 {
loop {
let uuid = Uuid::from_bytes(rand::random());
@ -100,19 +94,22 @@ impl EntityStore {
/// Like [`create`](Entities::create), but requires specifying the new
/// entity's UUID. This is useful for deserialization.
///
/// The provided UUID must not conflict with an existing entity UUID. If it
/// does, `None` is returned and the entity is not spawned.
/// The provided UUID must not conflict with an existing entity UUID in this
/// world. If it does, `None` is returned and the entity is not spawned.
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::Vacant(ve) => {
let entity = EntityId(self.sm.insert(Entity {
data: EntityData::Marker(types::Marker::new()),
old_type: EntityType::Marker,
let entity = EntityId(self.0.sm.insert(Entity {
flags: EntityFlags(0),
meta: EntityMeta::new(EntityType::Marker),
new_position: DVec3::default(),
old_position: DVec3::default(),
new_world: None,
old_world: None,
yaw: 0.0,
pitch: 0.0,
head_yaw: 0.0,
head_pitch: 0.0,
velocity: Vec3::default(),
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 {
if let Some(e) = self.sm.remove(entity.0) {
self.uuid_to_entity
if let Some(e) = self.0.sm.remove(entity.0) {
self.0
.uuid_to_entity
.remove(&e.uuid)
.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) {
self.sm.retain(|k, v| f(EntityId(k), v))
pub fn retain(&mut self, mut f: impl FnMut(EntityId, EntityMut) -> bool) {
// TODO
self.0.sm.retain(|k, v| f(EntityId(k), EntityMut(v)))
}
pub fn get(&self, entity: EntityId) -> Option<&Entity> {
self.sm.get(entity.0)
pub fn get_mut(&mut self, entity: EntityId) -> Option<EntityMut> {
self.0.sm.get_mut(entity.0).map(EntityMut)
}
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> {
self.sm.get_mut(entity.0)
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, EntityMut)> + '_ {
self.0
.sm
.iter_mut()
.map(|(k, v)| (EntityId(k), EntityMut(v)))
}
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (EntityId(k), v))
}
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ {
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 fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, EntityMut)> + '_ {
self.0
.sm
.par_iter_mut()
.map(|(k, v)| (EntityId(k), EntityMut(v)))
}
pub(crate) fn update(&mut self) {
for (_, e) in self.iter_mut() {
e.old_position = e.new_position;
e.old_world = e.new_world;
// TODO: update entity old_type.
// TODO: clear changed bits in metadata.
e.0.old_position = e.new_position;
e.0.meta.clear_modifications();
e.0.flags = EntityFlags(0);
}
}
}
//#[cfg(test)]
//mod tests {
// use appearance::Player;
//
// use super::*;
// use crate::glm;
//
// // TODO: better test: spawn a bunch of random entities, spawn a random
// AABB, // assert collides_with_aabb consistency.
//
// #[test]
// fn space_partition() {
// let mut entities = EntityStore::new();
//
// let ids = [(16.0, 16.0, 16.0), (8.0, 8.0, 8.0), (10.0, 50.0, 10.0)]
// .into_iter()
// .map(|(x, y, z)| entities.create(Player::new(glm::vec3(x, y, z),
// WorldId::NULL))) .collect::<Vec<_>>();
//
// let outside = *ids.last().unwrap();
//
// assert!(entities
// .intersecting_aabb(
// WorldId::NULL,
// Aabb::new(glm::vec3(8.0, 8.0, 8.0), glm::vec3(16.0, 16.0,
// 16.0)), )
// .all(|id| ids.contains(&id) && id != outside));
// }
//}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct EntityId(Key);
impl EntityId {
pub(crate) fn to_network_id(self) -> i32 {
// TODO: is ID 0 reserved?
self.0.index() as i32
}
}
pub struct Entity {
flags: EntityFlags,
meta: EntityMeta,
new_position: DVec3,
old_position: DVec3,
yaw: f32,
pitch: f32,
head_yaw: f32,
head_pitch: f32,
velocity: Vec3,
uuid: Uuid,
}
pub struct EntityMut<'a>(&'a mut Entity);
impl<'a> Deref for EntityMut<'a> {
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 anyhow::Context;
@ -10,26 +12,25 @@ use crate::Text;
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
pub struct ArmorStandRotations {
pub x_degrees: f32,
pub y_degrees: f32,
pub z_degrees: f32,
/// Rotation on the X axis in degrees.
pub x: f32,
/// Rotation on the Y axis in degrees.
pub y: f32,
/// Rotation on the Z axis in degrees.
pub z: f32,
}
impl ArmorStandRotations {
pub fn new(x_degrees: f32, y_degrees: f32, z_degrees: f32) -> Self {
Self {
x_degrees,
y_degrees,
z_degrees,
}
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
}
impl Encode for ArmorStandRotations {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.x_degrees.encode(w)?;
self.y_degrees.encode(w)?;
self.z_degrees.encode(w)
self.x.encode(w)?;
self.y.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)]
pub enum Pose {
Standing,
@ -166,6 +151,7 @@ impl Encode for Pose {
}
}
/// The main hand of a player.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum MainHand {
Left,

View file

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

View file

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

View file

@ -1,13 +1,16 @@
use std::io::{Read, Write};
use std::mem;
use anyhow::{anyhow, ensure};
use anyhow::{anyhow, ensure, Context};
use arrayvec::ArrayVec;
use bitvec::prelude::*;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use nalgebra_glm::{Number, TVec};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::var_int::VarInt;
use crate::EntityId;
/// Trait for types that can be written to the Minecraft protocol.
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)]
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] {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let vec = decode_array_bounded(N, N, r)?;
match vec.try_into() {
Ok(arr) => Ok(arr),
Err(_) => unreachable!("array size does not match"),
let mut elems = ArrayVec::new();
for _ in 0..N {
elems.push(T::decode(r)?);
}
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>(
s: &[T],
min: usize,

View file

@ -2,7 +2,7 @@ use std::collections::HashSet;
use std::error::Error;
use std::iter::FusedIterator;
use std::net::SocketAddr;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
@ -27,7 +27,7 @@ use tokio::sync::{oneshot, Semaphore};
use uuid::Uuid;
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::login::{
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::util::valid_username;
use crate::var_int::VarInt;
use crate::{
ChunkStore, Client, ClientStore, EntityStore, Ticks, WorldStore, PROTOCOL_VERSION, VERSION_NAME,
};
use crate::world::Worlds;
use crate::{Client, ClientMut, Ticks, WorldsMut, PROTOCOL_VERSION, VERSION_NAME};
/// Holds the state of a running Minecraft server which is accessible inside the
/// update loop. To start a server, see [`ServerConfig`].
///
/// 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.
/// A handle to a running Minecraft server containing state which is accessible
/// outside the update loop. Servers are internally refcounted and can be shared
/// between threads.
#[derive(Clone)]
pub struct SharedServer(Arc<SharedServerInner>);
pub struct Server(Arc<ServerInner>);
struct SharedServerInner {
struct ServerInner {
cfg: Box<dyn Config>,
address: SocketAddr,
tick_rate: Ticks,
@ -90,6 +61,11 @@ struct SharedServerInner {
biomes: Vec<Biome>,
/// The instant the server was started.
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
/// server. Closing this semaphore stops new connections.
connection_sema: Arc<Semaphore>,
@ -102,7 +78,6 @@ struct SharedServerInner {
public_key_der: Box<[u8]>,
/// For session server requests.
http_client: HttpClient,
new_clients_tx: Sender<NewClientMessage>,
}
/// Contains information about a new client.
@ -114,7 +89,7 @@ pub struct NewClientData {
struct NewClientMessage {
ncd: NewClientData,
reply: oneshot::Sender<anyhow::Result<ClientPacketChannels>>,
reply: oneshot::Sender<ClientPacketChannels>,
}
/// 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 ServerPacketChannels = (Sender<ClientPlayPacket>, Receiver<ServerPlayPacket>);
impl Other {
/// 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 {
impl Server {
pub fn config(&self) -> &(impl Config + ?Sized) {
self.0.cfg.as_ref()
}
@ -203,8 +161,11 @@ impl SharedServer {
}
/// Returns an iterator over all added biomes and their associated
/// [`BiomeId`].
pub fn biomes(&self) -> impl FusedIterator<Item = (BiomeId, &Biome)> + Clone {
/// [`BiomeId`] in ascending order.
pub fn biomes(
&self,
) -> impl ExactSizeIterator<Item = (BiomeId, &Biome)> + DoubleEndedIterator + FusedIterator + Clone
{
self.0
.biomes
.iter()
@ -217,6 +178,11 @@ impl SharedServer {
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
/// 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.
///
/// The function returns when the server has shut down, a runtime error
/// occurs, or the configuration is invalid.
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> {
@ -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())
.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() {
Some(Runtime::new()?)
@ -372,123 +317,125 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<Server> {
None => tokio_handle.unwrap(),
};
let shared = SharedServer(Arc::new(SharedServerInner {
let server = ServerInner {
cfg: Box::new(cfg),
address,
tick_rate,
online_mode,
max_connections,
outgoing_packet_capacity,
incoming_packet_capacity,
outgoing_packet_capacity,
tokio_handle,
_tokio_runtime: runtime,
dimensions,
biomes,
start_instant: Instant::now(),
new_clients_rx,
new_clients_tx,
tick_counter: AtomicI64::new(0),
connection_sema: Arc::new(Semaphore::new(max_connections)),
shutdown_result: Mutex::new(None),
rsa_key,
public_key_der,
http_client: HttpClient::new(),
new_clients_tx: new_players_tx,
}));
};
Ok(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,
},
})
Ok(Server(Arc::new(server)))
}
fn do_update_loop(server: &mut Server) -> ShutdownResult {
server.tick_start = Instant::now();
let shared = server.shared().clone();
fn do_update_loop(server: Server, mut worlds: WorldsMut) -> ShutdownResult {
let mut tick_start = Instant::now();
loop {
if let Some(res) = server.0.shutdown_result.lock().take() {
return res;
}
while let Ok(msg) = server.new_players_rx.try_recv() {
join_player(server, msg);
while let Ok(msg) = server.0.new_clients_rx.try_recv() {
join_player(&server, worlds.reborrow(), msg);
}
server.clients.par_iter_mut().for_each(|(_, client)| {
client.update(
&server.entities,
&server.worlds,
&server.chunks,
&server.other,
)
});
server.config().update(&server, worlds.reborrow());
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
.chunks
.par_iter_mut()
.for_each(|(_, chunk)| chunk.apply_modifications());
world.clients.par_iter_mut().for_each(|(_, mut client)| {
client.update(&server, &world.entities, &world.chunks, world.dimension);
});
shared.config().update(server);
world.entities.update();
// Chunks created this tick can have their changes applied immediately because
// 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();
world.chunks.par_iter_mut().for_each(|(_, mut chunk)| {
chunk.apply_modifications();
}
});
});
// Sleep for the remainder of the tick.
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();
server.tick_counter += 1;
tick_start = Instant::now();
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 (serverbound_tx, serverbound_rx) = flume::bounded(server.0.incoming_packet_capacity);
let client_packet_channels: ClientPacketChannels = (serverbound_tx, clientbound_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) {
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(
let mut client = Client::new(
server_packet_channels,
client_backed_entity,
msg.ncd.username,
msg.ncd.uuid,
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>);
async fn do_accept_loop(server: SharedServer) {
async fn do_accept_loop(server: Server) {
log::trace!("entering accept loop");
let listener = match TcpListener::bind(server.0.address).await {
@ -528,7 +475,7 @@ async fn do_accept_loop(server: SharedServer) {
}
async fn handle_connection(
server: SharedServer,
server: Server,
stream: TcpStream,
remote_addr: SocketAddr,
) -> anyhow::Result<()> {
@ -556,7 +503,7 @@ async fn handle_connection(
}
async fn handle_status(
server: SharedServer,
server: Server,
c: &mut Codec,
remote_addr: SocketAddr,
) -> anyhow::Result<()> {
@ -607,7 +554,7 @@ async fn handle_status(
/// Handle the login process and return the new player's data if successful.
async fn handle_login(
server: &SharedServer,
server: &Server,
c: &mut Codec,
remote_addr: SocketAddr,
) -> anyhow::Result<Option<NewClientData>> {
@ -720,7 +667,7 @@ async fn handle_login(
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}\"");
c.0.write_packet(&login::Disconnect { reason }).await?;
return Ok(None);
@ -735,7 +682,7 @@ async fn handle_login(
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();
server
@ -748,7 +695,7 @@ async fn handle_play(server: &SharedServer, c: Codec, ncd: NewClientData) -> any
.await?;
let (packet_tx, packet_rx) = match reply_rx.await {
Ok(res) => res?,
Ok(res) => res,
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) {
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 {
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::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::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>,
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)]
pub struct WorldId(Key);
impl Id for WorldId {
fn idx(self) -> usize {
self.0.index() as usize
}
}
impl WorldStore {
pub(crate) fn new() -> Self {
Self { sm: SlotMap::new() }
impl Worlds {
pub(crate) fn new(server: Server) -> Self {
Self {
sm: SlotMap::new(),
server,
}
}
pub fn count(&self) -> usize {
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 {
WorldId(self.sm.insert(World {
chunks: HashMap::new(),
WorldId(self.0.sm.insert(World {
clients: Clients::new(),
entities: Entities::new(),
chunks: Chunks::new(
self.server.clone(),
(self.server.dimension(dim).height / 16) as u32,
),
dimension: dim,
}))
}
@ -43,53 +77,98 @@ impl WorldStore {
/// Note that any entities with positions inside the deleted world will not
/// be deleted themselves.
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) {
self.sm.retain(|k, v| f(WorldId(k), v))
pub fn retain(&mut self, mut f: impl FnMut(WorldId, WorldMut) -> bool) {
self.0.sm.retain(|k, v| f(WorldId(k), WorldMut::new(v)))
}
pub fn get(&self, world: WorldId) -> Option<&World> {
self.sm.get(world.0)
pub fn get_mut(&mut self, world: WorldId) -> Option<WorldMut> {
self.0.sm.get_mut(world.0).map(WorldMut::new)
}
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> {
self.sm.get_mut(world.0)
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, WorldMut)> + '_ {
self.0
.sm
.iter_mut()
.map(|(k, v)| (WorldId(k), WorldMut::new(v)))
}
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (WorldId(k), v))
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, WorldRef)> + Clone + '_ {
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)> + '_ {
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v))
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ {
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 fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, WorldMut)> + '_ {
self.0
.sm
.par_iter_mut()
.map(|(k, v)| (WorldId(k), WorldMut::new(v)))
}
}
pub struct World {
chunks: HashMap<ChunkPos, ChunkId>,
/// A world on the server is a space for chunks, entities, and clients to
/// 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,
}
impl World {
pub fn dimension(&self) -> DimensionId {
self.dimension
}
/// A bag of immutable references to the components of a world.
pub struct WorldRef<'a> {
pub clients: &'a Clients,
pub entities: &'a Entities,
pub chunks: &'a Chunks,
pub dimension: DimensionId,
}
pub fn chunks(&self) -> &HashMap<ChunkPos, ChunkId> {
&self.chunks
}
pub fn chunks_mut(&mut self) -> &mut HashMap<ChunkPos, ChunkId> {
&mut self.chunks
impl<'a> WorldRef<'a> {
pub(crate) fn new(w: &'a World) -> Self {
Self {
clients: &w.clients,
entities: &w.entities,
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,
}
}
}