diff --git a/build/block.rs b/build/block.rs index 70e0507..e027f28 100644 --- a/build/block.rs +++ b/build/block.rs @@ -1,17 +1,13 @@ // TODO: can't match on str in const fn. use std::collections::BTreeSet; -use std::path::Path; -use std::process::Command; -use std::{env, fs}; -use anyhow::Context; use heck::{ToPascalCase, ToShoutySnakeCase}; use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; -use crate::ident; +use crate::{ident, write_to_out_path}; pub fn build() -> anyhow::Result<()> { let blocks = parse_blocks_json()?; @@ -49,7 +45,7 @@ pub fn build() -> anyhow::Result<()> { .map(|p| p.vals.len() as u16) .product(); - let num_values = p.vals.len() as u16; + let values_count = p.vals.len() as u16; let arms = p.vals.iter().enumerate().map(|(i, v)| { let value_idx = i as u16; @@ -60,7 +56,7 @@ pub fn build() -> anyhow::Result<()> { }).collect::(); quote! { - PropName::#prop_name => match (self.0 - #min_state_id) / #product % #num_values { + PropName::#prop_name => match (self.0 - #min_state_id) / #product % #values_count { #arms _ => unreachable!(), }, @@ -96,7 +92,7 @@ pub fn build() -> anyhow::Result<()> { .map(|p| p.vals.len() as u16) .product(); - let num_values = p.vals.len() as u16; + let values_count = p.vals.len() as u16; let arms = p .vals @@ -107,7 +103,7 @@ pub fn build() -> anyhow::Result<()> { let val_name = ident(v.to_pascal_case()); quote! { PropValue::#val_name => - Self(self.0 - (self.0 - #min_state_id) / #product % #num_values * #product + Self(self.0 - (self.0 - #min_state_id) / #product % #values_count * #product + #val_idx * #product), } }) @@ -213,7 +209,7 @@ pub fn build() -> anyhow::Result<()> { }) .collect::(); - let num_block_types = blocks.len(); + let block_type_count = blocks.len(); let prop_names = blocks .iter() @@ -245,7 +241,7 @@ pub fn build() -> anyhow::Result<()> { }) .collect::(); - let num_prop_names = prop_names.len(); + let prop_name_count = prop_names.len(); let prop_values = blocks .iter() @@ -299,9 +295,9 @@ pub fn build() -> anyhow::Result<()> { }) .collect::(); - let num_property_names = prop_values.len(); + let property_name_count = prop_values.len(); - let finshed = quote! { + let finished = quote! { /// Represents the state of a block, not including block entity data such as /// the text on a sign, the design on a banner, or the content of a spawner. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] @@ -432,7 +428,7 @@ pub fn build() -> anyhow::Result<()> { } /// An array of all block types. - pub const ALL: [Self; #num_block_types] = [#(Self::#block_type_variants,)*]; + pub const ALL: [Self; #block_type_count] = [#(Self::#block_type_variants,)*]; } /// The default block type is `air`. @@ -469,7 +465,7 @@ pub fn build() -> anyhow::Result<()> { } /// An array of all property names. - pub const ALL: [Self; #num_prop_names] = [#(Self::#prop_name_variants,)*]; + pub const ALL: [Self; #prop_name_count] = [#(Self::#prop_name_variants,)*]; } /// Contains all possible values that a block property might have. @@ -534,7 +530,7 @@ pub fn build() -> anyhow::Result<()> { } /// An array of all property values. - pub const ALL: [Self; #num_property_names] = [#(Self::#prop_value_variants,)*]; + pub const ALL: [Self; #property_name_count] = [#(Self::#prop_value_variants,)*]; } impl From for PropValue { @@ -544,16 +540,7 @@ pub fn build() -> anyhow::Result<()> { } }; - let out_path = - Path::new(&env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?).join("block.rs"); - - fs::write(&out_path, &finshed.to_string())?; - - // Format the output for debugging purposes. - // Doesn't matter if rustfmt is unavailable. - let _ = Command::new("rustfmt").arg(out_path).output(); - - Ok(()) + write_to_out_path("block.rs", &finished.to_string()) } struct Block { diff --git a/build/entity.rs b/build/entity.rs index 0281ff6..405bb27 100644 --- a/build/entity.rs +++ b/build/entity.rs @@ -1,6 +1,14 @@ //! See: -#![allow(unused)] +use std::collections::{BTreeMap, HashMap}; + +use anyhow::Context; +use heck::{ToPascalCase, ToSnakeCase}; +use proc_macro2::TokenStream; +use quote::quote; +use serde::Deserialize; + +use crate::{ident, write_to_out_path}; struct Class { name: &'static str, @@ -24,72 +32,70 @@ enum Type { OptText(Option<&'static str>), Slot, Bool(bool), - Rotations(f32, f32, f32), + ArmorStandRotations(f32, f32, f32), BlockPos(i32, i32, i32), - OptPosition(Option<(i32, i32, i32)>), - Direction(Direction), + OptBlockPos(Option<(i32, i32, i32)>), + Direction, OptUuid, - OptBlockId, + BlockState, Nbt, Particle, VillagerData, OptVarInt, Pose, - OptBlockPos, // TODO: What is this type? // ==== Specialized ==== // OptEntityId, - BoatType, + BoatVariant, MainHand, } -enum Direction { - Down, - Up, - North, - South, - West, - East, -} - struct BitField { name: &'static str, offset: u8, + default: bool, } const BASE_ENTITY: Class = Class { - name: "entity_base", + name: "base_entity", inherit: None, fields: &[ Field { - name: "entity_base_bits", + name: "base_entity_bits", typ: Type::BitFields(&[ BitField { name: "on_fire", offset: 0, + default: false, }, BitField { name: "crouching", offset: 1, + default: false, }, BitField { name: "sprinting", - offset: 3, // Skipping unused field + offset: 3, // Skipping unused + default: false, }, BitField { name: "swimming", offset: 4, + default: false, }, BitField { name: "invisible", offset: 5, + default: false, }, BitField { name: "glowing", offset: 6, + default: false, }, BitField { name: "elytra_flying", offset: 7, + default: false, }, ]), }, @@ -134,10 +140,12 @@ const ABSTRACT_ARROW: Class = Class { BitField { name: "critical", offset: 0, + default: false, }, BitField { name: "noclip", offset: 1, + default: false, }, ]), }, @@ -158,14 +166,17 @@ const LIVING_ENTITY: Class = Class { BitField { name: "hand_active", offset: 0, + default: false, }, BitField { name: "active_hand", offset: 1, + default: false, }, BitField { name: "riptide_spin_attack", offset: 2, + default: false, }, ]), }, @@ -191,7 +202,7 @@ const LIVING_ENTITY: Class = Class { }, Field { name: "bed_sleeping_position", - typ: Type::OptBlockPos, + typ: Type::OptBlockPos(None), }, ], }; @@ -205,14 +216,17 @@ const MOB: Class = Class { BitField { name: "ai_disabled", offset: 0, + default: false, }, BitField { name: "left_handed", offset: 1, + default: false, }, BitField { name: "aggressive", offset: 2, + default: false, }, ]), }], @@ -270,26 +284,32 @@ const ABSTRACT_HORSE: Class = Class { BitField { name: "tame", offset: 1, // Skip unused + default: false, }, BitField { name: "saddled", offset: 2, + default: false, }, BitField { name: "bred", offset: 3, + default: false, }, BitField { name: "eating", offset: 4, + default: false, }, BitField { name: "rearing", offset: 5, + default: false, }, BitField { name: "mouth_open", offset: 6, + default: false, }, ]), }, @@ -324,10 +344,12 @@ const TAMEABLE_ANIMAL: Class = Class { BitField { name: "sitting", offset: 0, + default: false, }, BitField { name: "tamed", offset: 2, // Skip unused. + default: false, }, ]), }], @@ -417,6 +439,7 @@ const SPIDER: Class = Class { typ: Type::BitFields(&[BitField { name: "climbing", offset: 0, + default: false, }]), }], }; @@ -474,6 +497,18 @@ const ABSTRACT_MINECART_CONTAINER: Class = Class { }; const ENTITIES: &[Class] = &[ + Class { + // TODO: how is this defined? + name: "leash_knot", + inherit: None, + fields: &[], + }, + Class { + // TODO: how is this defined? + name: "lightning_bolt", + inherit: None, + fields: &[], + }, Class { name: "experience_orb", inherit: None, @@ -485,7 +520,17 @@ const ENTITIES: &[Class] = &[ fields: &[], }, Class { - name: "thrown_egg", + name: "marker", + inherit: None, + fields: &[], + }, + Class { + name: "item", + inherit: Some(&BASE_ENTITY), + fields: &[], // TODO: what are the fields? + }, + Class { + name: "egg", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", @@ -493,7 +538,7 @@ const ENTITIES: &[Class] = &[ }], }, Class { - name: "thrown_ender_pearl", + name: "ender_pearl", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", @@ -501,7 +546,7 @@ const ENTITIES: &[Class] = &[ }], }, Class { - name: "thrown_experience_bottle", + name: "experience_bottle", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", @@ -509,7 +554,7 @@ const ENTITIES: &[Class] = &[ }], }, Class { - name: "thrown_potion", + name: "potion", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "potion", @@ -563,7 +608,7 @@ const ENTITIES: &[Class] = &[ ], }, Class { - name: "fishing_hook", + name: "fishing_bobber", inherit: Some(&BASE_ENTITY), fields: &[ Field { @@ -590,7 +635,7 @@ const ENTITIES: &[Class] = &[ fields: &[], }, Class { - name: "thrown_trident", + name: "trident", inherit: Some(&ABSTRACT_ARROW), fields: &[ Field { @@ -621,7 +666,7 @@ const ENTITIES: &[Class] = &[ }, Field { name: "typ", - typ: Type::BoatType, + typ: Type::BoatVariant, }, Field { name: "left_paddle_turning", @@ -643,7 +688,7 @@ const ENTITIES: &[Class] = &[ fields: &[ Field { name: "beam_target", - typ: Type::OptBlockPos, + typ: Type::OptBlockPos(None), }, Field { name: "show_bottom", @@ -745,30 +790,37 @@ const ENTITIES: &[Class] = &[ BitField { name: "cape_enabled", offset: 0, + default: false, }, BitField { name: "jacket_enabled", offset: 1, + default: false, }, BitField { name: "left_sleeve_enabled", offset: 2, + default: false, }, BitField { name: "right_sleeve_enabled", offset: 3, + default: false, }, BitField { name: "left_pants_leg_enabled", offset: 4, + default: false, }, BitField { name: "right_pants_leg_enabled", offset: 5, + default: false, }, BitField { name: "hat_enabled", offset: 6, + default: false, }, ]), }, @@ -796,44 +848,48 @@ const ENTITIES: &[Class] = &[ BitField { name: "small", offset: 0, + default: false, }, BitField { name: "has_arms", offset: 1, + default: false, }, BitField { name: "no_baseplate", offset: 2, + default: false, }, BitField { name: "is_marker", offset: 3, + default: false, }, ]), }, Field { name: "head_rotation", - typ: Type::Rotations(0.0, 0.0, 0.0), + typ: Type::ArmorStandRotations(0.0, 0.0, 0.0), }, Field { name: "body_rotation", - typ: Type::Rotations(0.0, 0.0, 0.0), + typ: Type::ArmorStandRotations(0.0, 0.0, 0.0), }, Field { name: "left_arm_rotation", - typ: Type::Rotations(-10.0, 0.0, -10.0), + typ: Type::ArmorStandRotations(-10.0, 0.0, -10.0), }, Field { name: "right_arm_rotation", - typ: Type::Rotations(-15.0, 0.0, -10.0), + typ: Type::ArmorStandRotations(-15.0, 0.0, -10.0), }, Field { name: "left_leg_rotation", - typ: Type::Rotations(-1.0, 0.0, -1.0), + typ: Type::ArmorStandRotations(-1.0, 0.0, -1.0), }, Field { name: "right_leg_rotation", - typ: Type::Rotations(1.0, 0.0, 1.0), + typ: Type::ArmorStandRotations(1.0, 0.0, 1.0), }, ], }, @@ -845,6 +901,7 @@ const ENTITIES: &[Class] = &[ typ: Type::BitFields(&[BitField { name: "hanging", offset: 0, + default: false, }]), }], }, @@ -883,7 +940,7 @@ const ENTITIES: &[Class] = &[ fields: &[], }, Class { - name: "puffer_fish", + name: "pufferfish", inherit: Some(&ABSTRACT_FISH), fields: &[Field { name: "puff_state", @@ -982,14 +1039,17 @@ const ENTITIES: &[Class] = &[ BitField { name: "angry", offset: 1, // Skip unused. + default: false, }, BitField { name: "stung", offset: 2, + default: false, }, BitField { name: "nectar", offset: 3, + default: false, }, ]), }, @@ -1013,10 +1073,37 @@ const ENTITIES: &[Class] = &[ BitField { name: "sitting", offset: 0, + default: false, }, BitField { - name: "", + name: "fox_crouching", offset: 2, // Skip unused + default: false, + }, + BitField { + name: "interested", + offset: 3, + default: false, + }, + BitField { + name: "pouncing", + offset: 4, + default: false, + }, + BitField { + name: "sleeping", + offset: 5, + default: false, + }, + BitField { + name: "faceplanted", + offset: 6, + default: false, + }, + BitField { + name: "defending", + offset: 7, + default: false, }, ]), }, @@ -1069,18 +1156,22 @@ const ENTITIES: &[Class] = &[ BitField { name: "sneezing", offset: 1, // Skip unused. + default: false, }, BitField { name: "rolling", offset: 2, + default: false, }, BitField { name: "sitting", offset: 3, + default: false, }, BitField { name: "on_back", offset: 4, + default: false, }, ]), }, @@ -1176,6 +1267,11 @@ const ENTITIES: &[Class] = &[ typ: Type::Byte(0), // TODO: sheep state type. }], }, + Class { + name: "goat", + inherit: Some(&ANIMAL), + fields: &[], // TODO: What are the goat fields? + }, Class { name: "strider", inherit: Some(&ANIMAL), @@ -1263,6 +1359,7 @@ const ENTITIES: &[Class] = &[ typ: Type::BitFields(&[BitField { name: "player_created", offset: 0, + default: false, }]), }], }, @@ -1274,7 +1371,7 @@ const ENTITIES: &[Class] = &[ typ: Type::BitFields(&[BitField { name: "pumpkin_hat", offset: 4, - // TODO: should default to true. + default: true, }]), }], }, @@ -1284,11 +1381,11 @@ const ENTITIES: &[Class] = &[ fields: &[ Field { name: "attach_face", - typ: Type::Direction(Direction::Down), + typ: Type::Direction, }, Field { name: "attachment_position", - typ: Type::OptPosition(None), + typ: Type::OptBlockPos(None), }, Field { name: "shield_height", @@ -1300,6 +1397,12 @@ const ENTITIES: &[Class] = &[ }, ], }, + Class { + // TODO: how is this defined? + name: "shulker_bullet", + inherit: Some(&BASE_ENTITY), + fields: &[], + }, Class { name: "piglin", inherit: Some(&BASE_PIGLIN), @@ -1331,6 +1434,7 @@ const ENTITIES: &[Class] = &[ typ: Type::BitFields(&[BitField { name: "blaze_on_fire", // TODO: better name for this? offset: 0, + default: false, }]), }], }, @@ -1402,7 +1506,7 @@ const ENTITIES: &[Class] = &[ fields: &[], }, Class { - name: "evoker_fanges", + name: "evoker_fangs", inherit: Some(&BASE_ENTITY), fields: &[], }, @@ -1422,6 +1526,7 @@ const ENTITIES: &[Class] = &[ typ: Type::BitFields(&[BitField { name: "attacking", offset: 0, + default: false, }]), }], }, @@ -1513,7 +1618,7 @@ const ENTITIES: &[Class] = &[ fields: &[ Field { name: "carried_block", - typ: Type::OptBlockId, + typ: Type::BlockState, }, Field { name: "screaming", @@ -1557,6 +1662,11 @@ const ENTITIES: &[Class] = &[ typ: Type::VarInt(1), // TODO: bounds? }], }, + Class { + name: "magma_cube", + inherit: Some(&MOB), + fields: &[], // TODO: what are the fields? + }, Class { name: "llama_spit", inherit: Some(&BASE_ENTITY), @@ -1568,17 +1678,17 @@ const ENTITIES: &[Class] = &[ fields: &[], }, Class { - name: "minecart_hopper", + name: "hopper_minecart", inherit: Some(&ABSTRACT_MINECART_CONTAINER), fields: &[], }, Class { - name: "minecart_chest", + name: "chest_minecart", inherit: Some(&ABSTRACT_MINECART_CONTAINER), fields: &[], }, Class { - name: "minecart_furnace", + name: "furnace_minecart", inherit: Some(&ABSTRACT_MINECART), fields: &[Field { name: "has_fuel", @@ -1586,17 +1696,17 @@ const ENTITIES: &[Class] = &[ }], }, Class { - name: "minecart_tnt", + name: "tnt_minecart", inherit: Some(&ABSTRACT_MINECART), fields: &[], }, Class { - name: "minecart_spawner", + name: "spawner_minecart", inherit: Some(&ABSTRACT_MINECART), fields: &[], }, Class { - name: "minecart_command_block", + name: "command_block_minecart", inherit: Some(&ABSTRACT_MINECART), fields: &[ Field { @@ -1610,7 +1720,7 @@ const ENTITIES: &[Class] = &[ ], }, Class { - name: "primed_tnt", + name: "tnt", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "fuse_timer", @@ -1620,5 +1730,344 @@ const ENTITIES: &[Class] = &[ ]; pub fn build() -> anyhow::Result<()> { - Ok(()) + // Sort the entities in ID order, where the IDs are obtained from entities.json. + let entities = { + let entities: HashMap<_, _> = ENTITIES.iter().map(|c| (c.name, c)).collect(); + + #[derive(Deserialize)] + struct JsonEntity { + id: usize, + name: String, + } + + let json_entities: Vec = + serde_json::from_str(include_str!("../data/entities.json"))?; + + let mut res = Vec::new(); + + for (i, e) in json_entities.iter().enumerate() { + assert_eq!(e.id, i); + + let name = e.name.as_str(); + + res.push( + *entities + .get(name) + .with_context(|| format!("entity \"{name}\" was not defined"))?, + ); + } + + assert_eq!(json_entities.len(), entities.len()); + + res + }; + + let mut all_classes = BTreeMap::new(); + for mut class in entities.iter().cloned() { + while let None = all_classes.insert(class.name, class) { + match class.inherit { + Some(parent) => class = parent, + None => break, + } + } + } + + let entity_type_variants = entities + .iter() + .map(|c| ident(c.name.to_pascal_case())) + .collect::>(); + + let entity_structs = entities.iter().map(|&class| { + let mut fields = Vec::new(); + collect_class_fields(class, &mut fields); + + let name = ident(class.name.to_pascal_case()); + let struct_fields = fields.iter().map(|&f| { + let name = ident(f.name.to_snake_case()); + let typ = match f.typ { + Type::BitFields(_) => quote! { u8 }, + Type::Byte(_) => quote! { u8 }, + Type::VarInt(_) => quote! { i32 }, + Type::Float(_) => quote! { f32 }, + Type::String(_) => quote! { Box }, + Type::Text => quote! { Box }, + Type::OptText(_) => quote! { Option> }, + Type::Slot => quote! { () }, // TODO + Type::Bool(_) => quote! { bool }, + Type::ArmorStandRotations(_, _, _) => quote! { ArmorStandRotations }, + Type::BlockPos(_, _, _) => quote! { BlockPos }, + Type::OptBlockPos(_) => quote! { Option }, + Type::Direction => quote! { Direction }, + Type::OptUuid => quote! { Option }, + Type::BlockState => quote! { BlockState }, + 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 }, + Type::BoatVariant => quote! { BoatVariant }, + Type::MainHand => quote! { MainHand }, + }; + quote! { + #name: #typ, + } + }); + + 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() }, + }; + + quote! { + #name: #val, + } + }); + + let getter_setters = + fields + .iter() + .enumerate() + .map(|(field_offset, field)| { + let name = ident(field.name.to_snake_case()); + let getter_name = ident(format!("get_{}", name.to_string())); + let setter_name = ident(format!("set_{}", name.to_string())); + + let field_offset = field_offset as u32; + + // TODO: documentation on methods. + + let standard_getter_setter = |type_name: TokenStream| quote! { + pub fn #getter_name(&self) -> #type_name { + self.#name + } + + pub fn #setter_name(&mut self, #name: #type_name) { + if self.#name != #name { + self.modified_bits |= 1 << #field_offset; + } + + self.#name = #name; + } + }; + + match field.typ { + 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())); + let setter_name = ident(format!("set_{}", bit_name.to_string())); + + let offset = bf.offset; + + quote! { + pub fn #getter_name(&self) -> bool { + (self.#name >> #offset) & 1 == 1 + } + + 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; + } + } + } + }) + .collect(), + Type::Byte(_) => standard_getter_setter(quote!(u8)), + Type::VarInt(_) => standard_getter_setter(quote!(i32)), + Type::Float(_) => standard_getter_setter(quote!(f32)), + Type::String(_) => quote! { + pub fn #getter_name(&self) -> &str { + &self.#name + } + + pub fn #setter_name(&mut self, #name: impl Into>) { + let #name = #name.into(); + + if self.#name != #name { + self.modified_bits |= 1 << #field_offset; + } + + self.#name = #name; + } + }, + Type::Text => quote! { + pub fn #getter_name(&self) -> &Text { + &self.#name + } + + pub fn #setter_name(&mut self, #name: impl Into) { + let #name = Box::new(#name.into()); + + if self.#name != #name { + self.modified_bits |= 1 << #field_offset; + } + + self.#name = #name; + } + }, + Type::OptText(_) => quote! { + pub fn #getter_name(&self) -> Option<&Text> { + self.#name.as_deref() + } + + pub fn #setter_name(&mut self, #name: Option>) { + let #name = #name.map(|x| Box::new(x.into())); + + if self.#name != #name { + self.modified_bits |= 1 << #field_offset; + } + + self.#name = #name; + } + }, + Type::Slot => quote! {}, // TODO + Type::Bool(_) => standard_getter_setter(quote!(bool)), + Type::ArmorStandRotations(_, _, _) => standard_getter_setter(quote!(ArmorStandRotations)), + Type::BlockPos(_, _, _) => standard_getter_setter(quote!(BlockPos)), + Type::OptBlockPos(_) => standard_getter_setter(quote!(Option)), + Type::Direction => standard_getter_setter(quote!(Direction)), + Type::OptUuid => standard_getter_setter(quote!(Option)), + Type::BlockState => standard_getter_setter(quote!(BlockState)), + Type::Nbt => quote! { + pub fn #getter_name(&self) -> &nbt::Blob { + &self.#name + } + + pub fn #setter_name(&mut self, #name: nbt::Blob) { + if self.#name != #name { + self.modified_bits |= 1 << #field_offset; + } + + self.#name = #name; + } + }, + 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::BoatVariant => standard_getter_setter(quote!(BoatVariant)), + Type::MainHand => standard_getter_setter(quote!(MainHand)), + } + }) + .collect::(); + + quote! { + pub struct #name { + /// Contains a set bit for each modified field. + modified_bits: u32, + #(#struct_fields)* + } + + impl #name { + pub(super) fn new() -> Self { + Self { + modified_bits: 0, + #(#constructor_fields)* + } + } + + #getter_setters + } + } + }); + + let finished = quote! { + pub enum EntityData { + #(#entity_type_variants(#entity_type_variants),)* + } + + 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,)* + } + } + } + + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + pub enum EntityType { + #(#entity_type_variants,)* + } + + impl Default for EntityType { + fn default() -> Self { + Self::Marker + } + } + + #(#entity_structs)* + }; + + write_to_out_path("entity.rs", &finished.to_string()) +} + +fn collect_class_fields(class: &Class, fields: &mut Vec<&'static Field>) { + if let Some(parent) = class.inherit { + collect_class_fields(parent, fields); + } + fields.extend(class.fields); } diff --git a/build/main.rs b/build/main.rs index 9407947..0c06b53 100644 --- a/build/main.rs +++ b/build/main.rs @@ -1,13 +1,20 @@ +use std::path::Path; +use std::process::Command; +use std::{env, fs}; + +use anyhow::Context; use proc_macro2::{Ident, Span}; mod block; +mod entity; pub fn main() -> anyhow::Result<()> { - for file in ["blocks.json"] { + for file in ["blocks.json", "entities.json"] { println!("cargo:rerun-if-changed=data/{file}"); } block::build()?; + entity::build()?; Ok(()) } @@ -20,3 +27,15 @@ fn ident(s: impl AsRef) -> Ident { Ident::new(s, Span::call_site()) } } + +fn write_to_out_path(file_name: impl AsRef, content: impl AsRef) -> anyhow::Result<()> { + let out_dir = env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?; + let path = Path::new(&out_dir).join(file_name.as_ref()); + + fs::write(&path, &content.as_ref())?; + + // Format the output for debugging purposes. + // Doesn't matter if rustfmt is unavailable. + let _ = Command::new("rustfmt").arg(path).output(); + Ok(()) +} diff --git a/data/entities.json b/data/entities.json new file mode 100644 index 0000000..af04f8f --- /dev/null +++ b/data/entities.json @@ -0,0 +1,1019 @@ +[ + { + "id": 0, + "internalId": 0, + "name": "area_effect_cloud", + "displayName": "Area Effect Cloud", + "width": 6.0, + "height": 0.5, + "type": "other" + }, + { + "id": 1, + "internalId": 1, + "name": "armor_stand", + "displayName": "Armor Stand", + "width": 0.5, + "height": 1.975, + "type": "living" + }, + { + "id": 2, + "internalId": 2, + "name": "arrow", + "displayName": "Arrow", + "width": 0.5, + "height": 0.5, + "type": "projectile" + }, + { + "id": 3, + "internalId": 3, + "name": "axolotl", + "displayName": "Axolotl", + "width": 0.75, + "height": 0.42, + "type": "animal" + }, + { + "id": 4, + "internalId": 4, + "name": "bat", + "displayName": "Bat", + "width": 0.5, + "height": 0.9, + "type": "ambient" + }, + { + "id": 5, + "internalId": 5, + "name": "bee", + "displayName": "Bee", + "width": 0.7, + "height": 0.6, + "type": "animal" + }, + { + "id": 6, + "internalId": 6, + "name": "blaze", + "displayName": "Blaze", + "width": 0.6, + "height": 1.8, + "type": "hostile" + }, + { + "id": 7, + "internalId": 7, + "name": "boat", + "displayName": "Boat", + "width": 1.375, + "height": 0.5625, + "type": "other" + }, + { + "id": 8, + "internalId": 8, + "name": "cat", + "displayName": "Cat", + "width": 0.6, + "height": 0.7, + "type": "animal" + }, + { + "id": 9, + "internalId": 9, + "name": "cave_spider", + "displayName": "Cave Spider", + "width": 0.7, + "height": 0.5, + "type": "hostile" + }, + { + "id": 10, + "internalId": 10, + "name": "chicken", + "displayName": "Chicken", + "width": 0.4, + "height": 0.7, + "type": "animal" + }, + { + "id": 11, + "internalId": 11, + "name": "cod", + "displayName": "Cod", + "width": 0.5, + "height": 0.3, + "type": "water_creature" + }, + { + "id": 12, + "internalId": 12, + "name": "cow", + "displayName": "Cow", + "width": 0.9, + "height": 1.4, + "type": "animal" + }, + { + "id": 13, + "internalId": 13, + "name": "creeper", + "displayName": "Creeper", + "width": 0.6, + "height": 1.7, + "type": "hostile" + }, + { + "id": 14, + "internalId": 14, + "name": "dolphin", + "displayName": "Dolphin", + "width": 0.9, + "height": 0.6, + "type": "water_creature" + }, + { + "id": 15, + "internalId": 15, + "name": "donkey", + "displayName": "Donkey", + "width": 1.3964844, + "height": 1.5, + "type": "animal" + }, + { + "id": 16, + "internalId": 16, + "name": "dragon_fireball", + "displayName": "Dragon Fireball", + "width": 1.0, + "height": 1.0, + "type": "projectile" + }, + { + "id": 17, + "internalId": 17, + "name": "drowned", + "displayName": "Drowned", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 18, + "internalId": 18, + "name": "elder_guardian", + "displayName": "Elder Guardian", + "width": 1.9975, + "height": 1.9975, + "type": "hostile" + }, + { + "id": 19, + "internalId": 19, + "name": "end_crystal", + "displayName": "End Crystal", + "width": 2.0, + "height": 2.0, + "type": "other" + }, + { + "id": 20, + "internalId": 20, + "name": "ender_dragon", + "displayName": "Ender Dragon", + "width": 16.0, + "height": 8.0, + "type": "mob" + }, + { + "id": 21, + "internalId": 21, + "name": "enderman", + "displayName": "Enderman", + "width": 0.6, + "height": 2.9, + "type": "hostile" + }, + { + "id": 22, + "internalId": 22, + "name": "endermite", + "displayName": "Endermite", + "width": 0.4, + "height": 0.3, + "type": "hostile" + }, + { + "id": 23, + "internalId": 23, + "name": "evoker", + "displayName": "Evoker", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 24, + "internalId": 24, + "name": "evoker_fangs", + "displayName": "Evoker Fangs", + "width": 0.5, + "height": 0.8, + "type": "other" + }, + { + "id": 25, + "internalId": 25, + "name": "experience_orb", + "displayName": "Experience Orb", + "width": 0.5, + "height": 0.5, + "type": "other" + }, + { + "id": 26, + "internalId": 26, + "name": "eye_of_ender", + "displayName": "Eye of Ender", + "width": 0.25, + "height": 0.25, + "type": "other" + }, + { + "id": 27, + "internalId": 27, + "name": "falling_block", + "displayName": "Falling Block", + "width": 0.98, + "height": 0.98, + "type": "other" + }, + { + "id": 28, + "internalId": 28, + "name": "firework_rocket", + "displayName": "Firework Rocket", + "width": 0.25, + "height": 0.25, + "type": "projectile" + }, + { + "id": 29, + "internalId": 29, + "name": "fox", + "displayName": "Fox", + "width": 0.6, + "height": 0.7, + "type": "animal" + }, + { + "id": 30, + "internalId": 30, + "name": "ghast", + "displayName": "Ghast", + "width": 4.0, + "height": 4.0, + "type": "mob" + }, + { + "id": 31, + "internalId": 31, + "name": "giant", + "displayName": "Giant", + "width": 3.6, + "height": 12.0, + "type": "hostile" + }, + { + "id": 32, + "internalId": 32, + "name": "glow_item_frame", + "displayName": "Glow Item Frame", + "width": 0.5, + "height": 0.5, + "type": "other" + }, + { + "id": 33, + "internalId": 33, + "name": "glow_squid", + "displayName": "Glow Squid", + "width": 0.8, + "height": 0.8, + "type": "water_creature" + }, + { + "id": 34, + "internalId": 34, + "name": "goat", + "displayName": "Goat", + "width": 0.9, + "height": 1.3, + "type": "animal" + }, + { + "id": 35, + "internalId": 35, + "name": "guardian", + "displayName": "Guardian", + "width": 0.85, + "height": 0.85, + "type": "hostile" + }, + { + "id": 36, + "internalId": 36, + "name": "hoglin", + "displayName": "Hoglin", + "width": 1.3964844, + "height": 1.4, + "type": "animal" + }, + { + "id": 37, + "internalId": 37, + "name": "horse", + "displayName": "Horse", + "width": 1.3964844, + "height": 1.6, + "type": "animal" + }, + { + "id": 38, + "internalId": 38, + "name": "husk", + "displayName": "Husk", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 39, + "internalId": 39, + "name": "illusioner", + "displayName": "Illusioner", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 40, + "internalId": 40, + "name": "iron_golem", + "displayName": "Iron Golem", + "width": 1.4, + "height": 2.7, + "type": "mob" + }, + { + "id": 41, + "internalId": 41, + "name": "item", + "displayName": "Item", + "width": 0.25, + "height": 0.25, + "type": "other" + }, + { + "id": 42, + "internalId": 42, + "name": "item_frame", + "displayName": "Item Frame", + "width": 0.5, + "height": 0.5, + "type": "other" + }, + { + "id": 43, + "internalId": 43, + "name": "fireball", + "displayName": "Fireball", + "width": 1.0, + "height": 1.0, + "type": "projectile" + }, + { + "id": 44, + "internalId": 44, + "name": "leash_knot", + "displayName": "Leash Knot", + "width": 0.375, + "height": 0.5, + "type": "other" + }, + { + "id": 45, + "internalId": 45, + "name": "lightning_bolt", + "displayName": "Lightning Bolt", + "width": 0.0, + "height": 0.0, + "type": "other" + }, + { + "id": 46, + "internalId": 46, + "name": "llama", + "displayName": "Llama", + "width": 0.9, + "height": 1.87, + "type": "animal" + }, + { + "id": 47, + "internalId": 47, + "name": "llama_spit", + "displayName": "Llama Spit", + "width": 0.25, + "height": 0.25, + "type": "projectile" + }, + { + "id": 48, + "internalId": 48, + "name": "magma_cube", + "displayName": "Magma Cube", + "width": 2.04, + "height": 2.04, + "type": "mob" + }, + { + "id": 49, + "internalId": 49, + "name": "marker", + "displayName": "Marker", + "width": 0.0, + "height": 0.0, + "type": "other" + }, + { + "id": 50, + "internalId": 50, + "name": "minecart", + "displayName": "Minecart", + "width": 0.98, + "height": 0.7, + "type": "other" + }, + { + "id": 51, + "internalId": 51, + "name": "chest_minecart", + "displayName": "Minecart with Chest", + "width": 0.98, + "height": 0.7, + "type": "other" + }, + { + "id": 52, + "internalId": 52, + "name": "command_block_minecart", + "displayName": "Minecart with Command Block", + "width": 0.98, + "height": 0.7, + "type": "other" + }, + { + "id": 53, + "internalId": 53, + "name": "furnace_minecart", + "displayName": "Minecart with Furnace", + "width": 0.98, + "height": 0.7, + "type": "other" + }, + { + "id": 54, + "internalId": 54, + "name": "hopper_minecart", + "displayName": "Minecart with Hopper", + "width": 0.98, + "height": 0.7, + "type": "other" + }, + { + "id": 55, + "internalId": 55, + "name": "spawner_minecart", + "displayName": "Minecart with Spawner", + "width": 0.98, + "height": 0.7, + "type": "other" + }, + { + "id": 56, + "internalId": 56, + "name": "tnt_minecart", + "displayName": "Minecart with TNT", + "width": 0.98, + "height": 0.7, + "type": "other" + }, + { + "id": 57, + "internalId": 57, + "name": "mule", + "displayName": "Mule", + "width": 1.3964844, + "height": 1.6, + "type": "animal" + }, + { + "id": 58, + "internalId": 58, + "name": "mooshroom", + "displayName": "Mooshroom", + "width": 0.9, + "height": 1.4, + "type": "animal" + }, + { + "id": 59, + "internalId": 59, + "name": "ocelot", + "displayName": "Ocelot", + "width": 0.6, + "height": 0.7, + "type": "animal" + }, + { + "id": 60, + "internalId": 60, + "name": "painting", + "displayName": "Painting", + "width": 0.5, + "height": 0.5, + "type": "other" + }, + { + "id": 61, + "internalId": 61, + "name": "panda", + "displayName": "Panda", + "width": 1.3, + "height": 1.25, + "type": "animal" + }, + { + "id": 62, + "internalId": 62, + "name": "parrot", + "displayName": "Parrot", + "width": 0.5, + "height": 0.9, + "type": "animal" + }, + { + "id": 63, + "internalId": 63, + "name": "phantom", + "displayName": "Phantom", + "width": 0.9, + "height": 0.5, + "type": "mob" + }, + { + "id": 64, + "internalId": 64, + "name": "pig", + "displayName": "Pig", + "width": 0.9, + "height": 0.9, + "type": "animal" + }, + { + "id": 65, + "internalId": 65, + "name": "piglin", + "displayName": "Piglin", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 66, + "internalId": 66, + "name": "piglin_brute", + "displayName": "Piglin Brute", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 67, + "internalId": 67, + "name": "pillager", + "displayName": "Pillager", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 68, + "internalId": 68, + "name": "polar_bear", + "displayName": "Polar Bear", + "width": 1.4, + "height": 1.4, + "type": "animal" + }, + { + "id": 69, + "internalId": 69, + "name": "tnt", + "displayName": "Primed TNT", + "width": 0.98, + "height": 0.98, + "type": "other" + }, + { + "id": 70, + "internalId": 70, + "name": "pufferfish", + "displayName": "Pufferfish", + "width": 0.7, + "height": 0.7, + "type": "water_creature" + }, + { + "id": 71, + "internalId": 71, + "name": "rabbit", + "displayName": "Rabbit", + "width": 0.4, + "height": 0.5, + "type": "animal" + }, + { + "id": 72, + "internalId": 72, + "name": "ravager", + "displayName": "Ravager", + "width": 1.95, + "height": 2.2, + "type": "hostile" + }, + { + "id": 73, + "internalId": 73, + "name": "salmon", + "displayName": "Salmon", + "width": 0.7, + "height": 0.4, + "type": "water_creature" + }, + { + "id": 74, + "internalId": 74, + "name": "sheep", + "displayName": "Sheep", + "width": 0.9, + "height": 1.3, + "type": "animal" + }, + { + "id": 75, + "internalId": 75, + "name": "shulker", + "displayName": "Shulker", + "width": 1.0, + "height": 1.0, + "type": "mob" + }, + { + "id": 76, + "internalId": 76, + "name": "shulker_bullet", + "displayName": "Shulker Bullet", + "width": 0.3125, + "height": 0.3125, + "type": "projectile" + }, + { + "id": 77, + "internalId": 77, + "name": "silverfish", + "displayName": "Silverfish", + "width": 0.4, + "height": 0.3, + "type": "hostile" + }, + { + "id": 78, + "internalId": 78, + "name": "skeleton", + "displayName": "Skeleton", + "width": 0.6, + "height": 1.99, + "type": "hostile" + }, + { + "id": 79, + "internalId": 79, + "name": "skeleton_horse", + "displayName": "Skeleton Horse", + "width": 1.3964844, + "height": 1.6, + "type": "animal" + }, + { + "id": 80, + "internalId": 80, + "name": "slime", + "displayName": "Slime", + "width": 2.04, + "height": 2.04, + "type": "mob" + }, + { + "id": 81, + "internalId": 81, + "name": "small_fireball", + "displayName": "Small Fireball", + "width": 0.3125, + "height": 0.3125, + "type": "projectile" + }, + { + "id": 82, + "internalId": 82, + "name": "snow_golem", + "displayName": "Snow Golem", + "width": 0.7, + "height": 1.9, + "type": "mob" + }, + { + "id": 83, + "internalId": 83, + "name": "snowball", + "displayName": "Snowball", + "width": 0.25, + "height": 0.25, + "type": "projectile" + }, + { + "id": 84, + "internalId": 84, + "name": "spectral_arrow", + "displayName": "Spectral Arrow", + "width": 0.5, + "height": 0.5, + "type": "projectile" + }, + { + "id": 85, + "internalId": 85, + "name": "spider", + "displayName": "Spider", + "width": 1.4, + "height": 0.9, + "type": "hostile" + }, + { + "id": 86, + "internalId": 86, + "name": "squid", + "displayName": "Squid", + "width": 0.8, + "height": 0.8, + "type": "water_creature" + }, + { + "id": 87, + "internalId": 87, + "name": "stray", + "displayName": "Stray", + "width": 0.6, + "height": 1.99, + "type": "hostile" + }, + { + "id": 88, + "internalId": 88, + "name": "strider", + "displayName": "Strider", + "width": 0.9, + "height": 1.7, + "type": "animal" + }, + { + "id": 89, + "internalId": 89, + "name": "egg", + "displayName": "Thrown Egg", + "width": 0.25, + "height": 0.25, + "type": "projectile" + }, + { + "id": 90, + "internalId": 90, + "name": "ender_pearl", + "displayName": "Thrown Ender Pearl", + "width": 0.25, + "height": 0.25, + "type": "projectile" + }, + { + "id": 91, + "internalId": 91, + "name": "experience_bottle", + "displayName": "Thrown Bottle o' Enchanting", + "width": 0.25, + "height": 0.25, + "type": "projectile" + }, + { + "id": 92, + "internalId": 92, + "name": "potion", + "displayName": "Potion", + "width": 0.25, + "height": 0.25, + "type": "projectile" + }, + { + "id": 93, + "internalId": 93, + "name": "trident", + "displayName": "Trident", + "width": 0.5, + "height": 0.5, + "type": "projectile" + }, + { + "id": 94, + "internalId": 94, + "name": "trader_llama", + "displayName": "Trader Llama", + "width": 0.9, + "height": 1.87, + "type": "animal" + }, + { + "id": 95, + "internalId": 95, + "name": "tropical_fish", + "displayName": "Tropical Fish", + "width": 0.5, + "height": 0.4, + "type": "water_creature" + }, + { + "id": 96, + "internalId": 96, + "name": "turtle", + "displayName": "Turtle", + "width": 1.2, + "height": 0.4, + "type": "animal" + }, + { + "id": 97, + "internalId": 97, + "name": "vex", + "displayName": "Vex", + "width": 0.4, + "height": 0.8, + "type": "hostile" + }, + { + "id": 98, + "internalId": 98, + "name": "villager", + "displayName": "Villager", + "width": 0.6, + "height": 1.95, + "type": "passive" + }, + { + "id": 99, + "internalId": 99, + "name": "vindicator", + "displayName": "Vindicator", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 100, + "internalId": 100, + "name": "wandering_trader", + "displayName": "Wandering Trader", + "width": 0.6, + "height": 1.95, + "type": "passive" + }, + { + "id": 101, + "internalId": 101, + "name": "witch", + "displayName": "Witch", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 102, + "internalId": 102, + "name": "wither", + "displayName": "Wither", + "width": 0.9, + "height": 3.5, + "type": "hostile" + }, + { + "id": 103, + "internalId": 103, + "name": "wither_skeleton", + "displayName": "Wither Skeleton", + "width": 0.7, + "height": 2.4, + "type": "hostile" + }, + { + "id": 104, + "internalId": 104, + "name": "wither_skull", + "displayName": "Wither Skull", + "width": 0.3125, + "height": 0.3125, + "type": "projectile" + }, + { + "id": 105, + "internalId": 105, + "name": "wolf", + "displayName": "Wolf", + "width": 0.6, + "height": 0.85, + "type": "animal" + }, + { + "id": 106, + "internalId": 106, + "name": "zoglin", + "displayName": "Zoglin", + "width": 1.3964844, + "height": 1.4, + "type": "hostile" + }, + { + "id": 107, + "internalId": 107, + "name": "zombie", + "displayName": "Zombie", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 108, + "internalId": 108, + "name": "zombie_horse", + "displayName": "Zombie Horse", + "width": 1.3964844, + "height": 1.6, + "type": "animal" + }, + { + "id": 109, + "internalId": 109, + "name": "zombie_villager", + "displayName": "Zombie Villager", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 110, + "internalId": 110, + "name": "zombified_piglin", + "displayName": "Zombified Piglin", + "width": 0.6, + "height": 1.95, + "type": "hostile" + }, + { + "id": 111, + "internalId": 111, + "name": "player", + "displayName": "Player", + "width": 0.6, + "height": 1.8, + "type": "player" + }, + { + "id": 112, + "internalId": 112, + "name": "fishing_bobber", + "displayName": "Fishing Bobber", + "width": 0.25, + "height": 0.25, + "type": "projectile" + } +] \ No newline at end of file diff --git a/examples/basic.rs b/examples/basic.rs index ad97a75..5077814 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -31,18 +31,15 @@ struct Game { #[async_trait] impl Handler for Game { fn init(&self, server: &mut Server) { - let id = server.worlds.create(DimensionId::default()); - let mut worlds = server.worlds.worlds_mut().unwrap(); - let world = server.worlds.get(&mut worlds, id).unwrap(); + 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 id = server.chunks.create(384); - let mut chunks = server.chunks.chunks_mut().unwrap(); - - let chunk = server.chunks.get(&mut chunks, id).unwrap(); + let chunk_id = server.chunks.create(384); + let chunk = server.chunks.get_mut(chunk_id).unwrap(); for z in 0..16 { for x in 0..16 { @@ -52,7 +49,7 @@ impl Handler for Game { } } - world.chunks_mut().insert((x, z).into(), id); + world.chunks_mut().insert((x, z).into(), chunk_id); } } } @@ -71,29 +68,20 @@ impl Handler for Game { } fn update(&self, server: &mut Server) { - let mut clients = server.entities.clients_mut().unwrap(); + let world_id = server.worlds.iter().next().unwrap().0; - let world_id = server.worlds.ids().nth(0).unwrap(); - - let mut to_remove = Vec::new(); - - for (client_id, client) in server.entities.iter(&mut clients) { - if let Some(client) = client.get_mut() { - if client.created_tick() == server.current_tick() { - client.set_world(world_id); - client.teleport(glm::vec3(0.0, 200.0, 0.0), 0.0, 0.0); - } + server.clients.retain(|_, client| { + if client.created_tick() == server.other.current_tick() { + client.set_world(Some(world_id)); + client.teleport(glm::vec3(0.0, 200.0, 0.0), 0.0, 0.0); } if client.is_disconnected() { - to_remove.push(client_id); + server.entities.delete(client.entity()); + false + } else { + true } - } - - drop(clients); - - for id in to_remove { - server.entities.delete(id); - } + }); } } diff --git a/src/aabb.rs b/src/aabb.rs index 8ee0907..9d74218 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -1,14 +1,19 @@ +use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; + use crate::glm::{self, Number, RealNumber, TVec}; /// An Axis-aligned bounding box in an arbitrary dimension. -#[derive(Clone, Copy, Debug)] // TODO: impl PartialEq, Eq, PartialOrd, Ord, Hash +#[derive(Clone, Copy, Debug)] pub struct Aabb { min: TVec, max: TVec, } impl Aabb { - pub fn new(p0: TVec, p1: TVec) -> Self { + pub fn new(p0: impl Into>, p1: impl Into>) -> Self { + let p0 = p0.into(); + let p1 = p1.into(); Self { min: glm::min2(&p0, &p1), max: glm::max2(&p0, &p1), @@ -62,3 +67,44 @@ impl Aabb { glm::distance(&p, &glm::clamp_vec(&p, &self.min, &self.max)) } } + +impl Default for Aabb { + fn default() -> Self { + let d = T::default(); + Self::new([d; D], [d; D]) + } +} + +impl PartialEq for Aabb { + fn eq(&self, other: &Self) -> bool { + self.min == other.min && self.max == other.max + } +} + +impl Eq for Aabb {} + +impl PartialOrd for Aabb { + fn partial_cmp(&self, other: &Self) -> Option { + match self.min.partial_cmp(&other.min) { + Some(Ordering::Equal) => self.max.partial_cmp(&other.max), + ord => return ord, + } + } +} + +impl Hash for Aabb { + fn hash(&self, state: &mut H) { + self.min.hash(state); + self.max.hash(state); + } +} + +// TODO: impl Ord for Aabb +//impl Ord for Aabb { +// fn cmp(&self, other: &Self) -> std::cmp::Ordering { +// match self.min.cmp(&other.min) { +// Ordering::Equal => self.max.cmp(&other.max), +// ord => ord, +// } +// } +//} diff --git a/src/block.rs b/src/block.rs index a7cc560..0920d50 100644 --- a/src/block.rs +++ b/src/block.rs @@ -1,6 +1,12 @@ #![allow(clippy::all)] use std::fmt; +use std::io::{Read, Write}; + +use anyhow::Context; + +use crate::protocol::{Decode, Encode}; +use crate::var_int::VarInt; include!(concat!(env!("OUT_DIR"), "/block.rs")); @@ -32,6 +38,21 @@ impl fmt::Debug for BlockState { } } +impl Encode for BlockState { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(self.0 as i32).encode(w) + } +} + +impl Decode for BlockState { + fn decode(r: &mut impl Read) -> anyhow::Result { + let id = VarInt::decode(r)?.0; + let errmsg = "invalid block state ID"; + + BlockState::from_raw(id.try_into().context(errmsg)?).context(errmsg) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/block_pos.rs b/src/block_pos.rs index 5f95c66..48fb59c 100644 --- a/src/block_pos.rs +++ b/src/block_pos.rs @@ -1,41 +1,33 @@ -// TODO: rename to BlockPos and represent internally as [i32; 3]. - use std::io::{Read, Write}; use anyhow::bail; -use glm::{IVec3, Scalar, TVec3}; -use num::cast::AsPrimitive; -use crate::glm; use crate::protocol::{Decode, Encode}; #[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)] -pub struct BlockPos(pub IVec3); - -impl BlockPos { - pub fn new(x: i32, y: i32, z: i32) -> Self { - Self(glm::vec3(x, y, z)) - } - - pub fn from_vec3(vec: TVec3>) -> Self { - Self(vec.map(|n| n.as_())) - } +pub struct BlockPos { + pub x: i32, + pub y: i32, + pub z: i32, } -impl> From> for BlockPos { - fn from(vec: TVec3) -> Self { - Self(vec.map(|n| n.into())) +impl BlockPos { + pub const fn new(x: i32, y: i32, z: i32) -> Self { + Self { x, y, z } } } impl Encode for BlockPos { fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - match (self.0.x, self.0.y, self.0.z) { + match (self.x, self.y, self.z) { (-0x2000000..=0x1ffffff, -0x800..=0x7ff, -0x2000000..=0x1ffffff) => { - let (x, y, z) = (self.0.x as u64, self.0.y as u64, self.0.z as u64); + let (x, y, z) = (self.x as u64, self.y as u64, self.z as u64); (x << 38 | z << 38 >> 26 | y & 0xfff).encode(w) } - _ => bail!("block position {} is out of range", self.0), + _ => bail!( + "block position {:?} is out of range", + (self.x, self.y, self.z) + ), } } } @@ -47,7 +39,11 @@ impl Decode for BlockPos { let x = val >> 38; let z = val << 26 >> 38; let y = val << 52 >> 52; - Ok(Self(glm::vec3(x as i32, y as i32, z as i32))) + Ok(Self { + x: x as i32, + y: y as i32, + z: z as i32, + }) } } @@ -81,7 +77,7 @@ mod tests { for (x, x_valid) in xzs { for (y, y_valid) in ys { for (z, z_valid) in xzs { - let pos = BlockPos(glm::vec3(x, y, z)); + let pos = BlockPos::new(x, y, z); if x_valid && y_valid && z_valid { pos.encode(&mut &mut buf[..]).unwrap(); assert_eq!(BlockPos::decode(&mut &buf[..]).unwrap(), pos); diff --git a/src/chunk.rs b/src/chunk.rs index efb1514..5f5554f 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,19 +1,75 @@ // TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil use std::io::Write; +use std::iter::FusedIterator; use bitvec::bitvec; use bitvec::vec::BitVec; use num::Integer; +use rayon::iter::ParallelIterator; use crate::glm::DVec2; 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; +pub struct ChunkStore { + sm: SlotMap, +} + +impl ChunkStore { + pub(crate) fn new() -> Self { + Self { sm: SlotMap::new() } + } + + pub fn count(&self) -> usize { + self.sm.count() + } + + 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 clear(&mut self) { + self.sm.clear(); + } + + pub fn iter(&self) -> impl FusedIterator + Clone + '_ { + self.sm.iter().map(|(k, v)| (ChunkId(k), v)) + } + + pub fn iter_mut(&mut self) -> impl FusedIterator + '_ { + self.sm.iter_mut().map(|(k, v)| (ChunkId(k), v)) + } + + pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ { + self.sm.par_iter().map(|(k, v)| (ChunkId(k), v)) + } + + pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ { + self.sm.par_iter_mut().map(|(k, v)| (ChunkId(k), v)) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct ChunkId(Key); + pub struct Chunk { sections: Box<[ChunkSection]>, // TODO block_entities: HashMap, @@ -23,7 +79,7 @@ pub struct Chunk { } impl Chunk { - pub(crate) fn new(num_sections: usize) -> Self { + pub(crate) fn new(section_count: usize) -> Self { let sect = ChunkSection { blocks: [0; 4096], biomes: [0; 64], @@ -32,7 +88,7 @@ impl Chunk { }; let mut chunk = Self { - sections: vec![sect; num_sections].into(), + sections: vec![sect; section_count].into(), heightmap: Vec::new(), modified: true, created_this_tick: true, @@ -42,10 +98,6 @@ impl Chunk { chunk } - pub(crate) fn deallocate(&mut self) { - self.sections = Box::new([]); - } - pub fn created_this_tick(&self) -> bool { self.created_this_tick } @@ -99,11 +151,11 @@ impl Chunk { pub(crate) fn chunk_data_packet( &self, pos: ChunkPos, - num_sections: usize, + section_count: usize, ) -> ChunkDataAndUpdateLight { let mut blocks_and_biomes = Vec::new(); - for i in 0..num_sections { + for i in 0..section_count { match self.sections.get(i) { Some(sect) => { blocks_and_biomes.extend_from_slice(§.compact_data); @@ -121,7 +173,7 @@ impl Chunk { } } - let motion_blocking = if num_sections == self.sections.len() { + let motion_blocking = if section_count == self.sections.len() { self.heightmap.clone() } else { // This is bad for two reasons: @@ -142,11 +194,11 @@ impl Chunk { blocks_and_biomes, block_entities: Vec::new(), // TODO trust_edges: true, - sky_light_mask: bitvec![u64, _; 1; num_sections + 2], + sky_light_mask: bitvec![u64, _; 1; section_count + 2], block_light_mask: BitVec::new(), empty_sky_light_mask: BitVec::new(), empty_block_light_mask: BitVec::new(), - sky_light_arrays: vec![[0xff; 2048]; num_sections + 2], + sky_light_arrays: vec![[0xff; 2048]; section_count + 2], block_light_arrays: Vec::new(), } } @@ -253,10 +305,10 @@ fn build_heightmap(sections: &[ChunkSection], heightmap: &mut Vec) { let height = sections.len() * 16; let bits_per_val = log2_ceil(height); let vals_per_u64 = 64 / bits_per_val; - let num_u64s = Integer::div_ceil(&256, &vals_per_u64); + let u64_count = Integer::div_ceil(&256, &vals_per_u64); heightmap.clear(); - heightmap.resize(num_u64s, 0); + heightmap.resize(u64_count, 0); for x in 0..16 { for z in 0..16 { @@ -304,9 +356,9 @@ fn encode_paletted_container( // Direct case // Skip the palette let idxs_per_u64 = 64 / direct_bits_per_idx; - let num_u64s = Integer::div_ceil(&entries.len(), &idxs_per_u64); + let u64_count = Integer::div_ceil(&entries.len(), &idxs_per_u64); - VarInt(num_u64s as i32).encode(w)?; + VarInt(u64_count as i32).encode(w)?; for &entry in entries { let mut val = 0u64; @@ -324,9 +376,9 @@ fn encode_paletted_container( let bits_per_idx = bits_per_idx.max(min_bits_per_idx); let idxs_per_u64 = 64 / bits_per_idx; - let num_u64s = Integer::div_ceil(&entries.len(), &idxs_per_u64); + let u64_count = Integer::div_ceil(&entries.len(), &idxs_per_u64); - VarInt(num_u64s as i32).encode(w)?; + VarInt(u64_count as i32).encode(w)?; for &entry in entries { let palette_idx = palette diff --git a/src/chunk_store.rs b/src/chunk_store.rs deleted file mode 100644 index fa75bae..0000000 --- a/src/chunk_store.rs +++ /dev/null @@ -1,220 +0,0 @@ -use std::iter::FusedIterator; - -use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; - -use crate::component::{ - ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents, - ZippedComponentsRaw, -}; -use crate::Chunk; - -pub struct ChunkStore { - comps: ComponentStore, - chunks: RwLock>, -} - -impl ChunkStore { - pub(crate) fn new() -> Self { - Self { - comps: ComponentStore::new(), - chunks: RwLock::new(Vec::new()), - } - } - - pub fn create(&mut self, height: usize) -> ChunkId { - assert!(height % 16 == 0, "chunk height must be a multiple of 16"); - - let id = self.comps.create_item(); - let chunk = Chunk::new(height / 16); - - let idx = id.0.idx as usize; - if idx >= self.chunks.get_mut().len() { - self.chunks.get_mut().push(chunk); - } else { - self.chunks.get_mut()[idx] = chunk; - } - - id - } - - pub fn delete(&mut self, chunk: ChunkId) -> bool { - if self.comps.delete_item(chunk) { - let idx = chunk.0.idx as usize; - self.chunks.get_mut()[idx].deallocate(); - true - } else { - false - } - } - - pub fn count(&self) -> usize { - self.comps.count() - } - - pub fn is_valid(&self, chunk: ChunkId) -> bool { - self.comps.is_valid(chunk) - } - - pub fn get(&self, z: Z, chunk: ChunkId) -> Option - where - Z: ZippedComponents, - { - self.comps.get(z, chunk) - } - - pub fn iter<'a, Z>(&'a self, z: Z) -> impl FusedIterator + 'a - where - Z: ZippedComponents + 'a, - { - self.comps.iter(z) - } - - pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator + 'a - where - Z: ZippedComponents + 'a, - { - self.comps.par_iter(z) - } - - pub fn ids(&self) -> impl FusedIterator + Clone + '_ { - self.comps.ids() - } - - pub fn par_ids(&self) -> impl ParallelIterator + Clone + '_ { - self.comps.par_ids() - } - - pub fn chunks(&self) -> Result { - Ok(Chunks { - chunks: self.chunks.try_read().ok_or(Error::NoReadAccess)?, - }) - } - - pub fn chunks_mut(&self) -> Result { - Ok(ChunksMut { - chunks: self.chunks.try_write().ok_or(Error::NoWriteAccess)?, - }) - } - - pub fn register_component(&mut self) { - self.comps.register_component::(); - } - - pub fn unregister_component(&mut self) { - self.comps.unregister_component::() - } - - pub fn is_registered(&self) -> bool { - self.comps.is_registered::() - } - - pub fn components( - &self, - ) -> Result, Error> { - self.comps.components::() - } - - pub fn components_mut( - &self, - ) -> Result, Error> { - self.comps.components_mut::() - } -} - -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)] -pub struct ChunkId(pub(crate) IdData); - -impl ChunkId { - /// The value of the default [`ChunkId`] which is always invalid. - pub const NULL: Self = Self(IdData::NULL); -} - -impl IdRaw for ChunkId { - fn to_data(self) -> IdData { - self.0 - } - - fn from_data(id: IdData) -> Self { - Self(id) - } -} - -impl Id for ChunkId {} - -pub struct Chunks<'a> { - chunks: RwLockReadGuard<'a, Vec>, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b Chunks<'a> { - type RawItem = &'b Chunk; - type RawIter = std::slice::Iter<'b, Chunk>; - type RawParIter = rayon::slice::Iter<'b, Chunk>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.chunks[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.chunks.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.chunks.par_iter() - } -} - -impl<'a, 'b> ZippedComponents for &'b Chunks<'a> { - type Id = ChunkId; - type Item = &'b Chunk; -} - -pub struct ChunksMut<'a> { - chunks: RwLockWriteGuard<'a, Vec>, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b ChunksMut<'a> { - type RawItem = &'b Chunk; - type RawIter = std::slice::Iter<'b, Chunk>; - type RawParIter = rayon::slice::Iter<'b, Chunk>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.chunks[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.chunks.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.chunks.par_iter() - } -} - -impl<'a, 'b> ZippedComponents for &'b ChunksMut<'a> { - type Id = ChunkId; - type Item = &'b Chunk; -} - -impl<'a, 'b> ZippedComponentsRaw for &'b mut ChunksMut<'a> { - type RawItem = &'b mut Chunk; - type RawIter = std::slice::IterMut<'b, Chunk>; - type RawParIter = rayon::slice::IterMut<'b, Chunk>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &mut self.chunks[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.chunks.iter_mut() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.chunks.par_iter_mut() - } -} - -impl<'a, 'b> ZippedComponents for &'b mut ChunksMut<'a> { - type Id = ChunkId; - type Item = &'b mut Chunk; -} diff --git a/src/client.rs b/src/client.rs index 806403a..ef75016 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,11 +1,13 @@ use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; +use std::iter::FusedIterator; use flume::{Receiver, Sender, TrySendError}; use glm::DVec3; +use rayon::iter::ParallelIterator; use crate::block_pos::BlockPos; -use crate::chunk_store::ChunkId; +use crate::chunk::ChunkId; use crate::config::{ Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId, }; @@ -18,51 +20,82 @@ use crate::packets::play::{ ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition, }; use crate::protocol::{BoundedInt, Nbt}; -use crate::server::ServerPacketChannels; +use crate::server::{Other, 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, EntityId, Server, SharedServer, Text, Ticks, LIBRARY_NAMESPACE}; +use crate::{ + glm, ident, ChunkPos, ChunkStore, EntityId, EntityStore, Server, SharedServer, Text, Ticks, + WorldStore, LIBRARY_NAMESPACE, +}; -pub struct MaybeClient(pub(crate) Option>); +pub struct ClientStore { + sm: SlotMap, +} -impl MaybeClient { - pub fn get(&self) -> Option<&Client> { - self.0.as_deref() +impl ClientStore { + pub(crate) fn new() -> Self { + Self { sm: SlotMap::new() } } - pub fn get_mut(&mut self) -> Option<&mut Client> { - self.0.as_deref_mut() + pub fn count(&self) -> usize { + self.sm.count() } - /// Drops the inner [`Client`]. Future calls to [`get`](MaybeClient::get) - /// and [`get_mut`](MaybeClient::get_mut) will return `None`. - /// - /// If the client was still connected prior to calling this function, the - /// client is disconnected from the server without a displayed reason. - /// - /// If the inner client was already dropped, this function has no effect. - pub fn clear(&mut self) { - self.0 = None; + pub(crate) fn create(&mut self, client: Client) -> ClientId { + ClientId(self.sm.insert(client)) } - pub fn is_disconnected(&self) -> bool { - self.get().map_or(true, |c| c.is_disconnected()) + 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 + Clone + '_ { + self.sm.iter().map(|(k, v)| (ClientId(k), v)) + } + + pub fn iter_mut(&mut self) -> impl FusedIterator + '_ { + self.sm.iter_mut().map(|(k, v)| (ClientId(k), v)) + } + + pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ { + self.sm.par_iter().map(|(k, v)| (ClientId(k), v)) + } + + pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ { + self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v)) } } +pub struct ClientId(Key); + /// Represents a client connected to the server after logging in. pub struct Client { shared: SharedServer, /// Setting this to `None` disconnects the client. send: Option>, recv: Receiver, + /// The entity this client is associated with. + entity: EntityId, /// The tick this client was created. created_tick: Ticks, username: String, on_ground: bool, - old_position: DVec3, new_position: DVec3, + old_position: DVec3, /// Measured in degrees yaw: f32, /// Measured in degrees @@ -81,15 +114,15 @@ pub struct Client { /// 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. - old_world: WorldId, - new_world: WorldId, + new_world: Option, + old_world: Option, events: Vec, /// The ID of the last keepalive sent. last_keepalive_id: i64, /// If the last sent keepalive got a response. got_keepalive: bool, - old_max_view_distance: u8, new_max_view_distance: u8, + old_max_view_distance: u8, /// Entities that were visible to this client at the end of the last tick. /// This is used to determine what entity create/destroy packets should be /// sent. @@ -97,8 +130,8 @@ pub struct Client { hidden_entities: HashSet, /// Loaded chunks and their positions. loaded_chunks: HashMap, - old_game_mode: GameMode, new_game_mode: GameMode, + old_game_mode: GameMode, settings: Option, // TODO: latency // TODO: time, weather @@ -107,6 +140,7 @@ pub struct Client { impl Client { pub(crate) fn new( packet_channels: ServerPacketChannels, + entity: EntityId, username: String, server: &Server, ) -> Self { @@ -116,11 +150,12 @@ impl Client { shared: server.shared().clone(), send: Some(send), recv, + entity, created_tick: server.current_tick(), username, on_ground: false, - old_position: DVec3::default(), new_position: DVec3::default(), + old_position: DVec3::default(), yaw: 0.0, pitch: 0.0, teleported_this_tick: false, @@ -129,18 +164,18 @@ impl Client { spawn_position: BlockPos::default(), spawn_position_yaw: 0.0, modified_spawn_position: true, - new_world: WorldId::NULL, - old_world: WorldId::NULL, + old_world: None, + new_world: None, events: Vec::new(), last_keepalive_id: 0, got_keepalive: true, - old_max_view_distance: 0, new_max_view_distance: 16, + old_max_view_distance: 0, loaded_entities: HashSet::new(), hidden_entities: HashSet::new(), loaded_chunks: HashMap::new(), - old_game_mode: GameMode::Survival, new_game_mode: GameMode::Survival, + old_game_mode: GameMode::Survival, settings: None, } } @@ -199,11 +234,11 @@ impl Client { } } - pub fn world(&self) -> WorldId { + pub fn world(&self) -> Option { self.new_world } - pub fn set_world(&mut self, new_world: WorldId) { + pub fn set_world(&mut self, new_world: Option) { self.new_world = new_world; } @@ -252,14 +287,241 @@ impl Client { self.settings.as_ref() } - pub fn update(&mut self, client_eid: EntityId, server: &Server) { + /// Returns the entity this client is backing. + pub fn entity(&self) -> EntityId { + self.entity + } + + pub(crate) fn update( + &mut self, + entities: &EntityStore, + worlds: &WorldStore, + chunks: &ChunkStore, + other: &Other, + ) { self.events.clear(); if self.is_disconnected() { return; } - (0..self.recv.len()).for_each(|_| match self.recv.try_recv().unwrap() { + for _ in 0..self.recv.len() { + self.handle_serverbound_packet(self.recv.try_recv().unwrap()); + } + + // 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; + 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); + + // 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() { + self.send_packet(JoinGame { + entity_id: self.entity.to_network_id(), + is_hardcore: false, + gamemode: self.new_game_mode, + previous_gamemode: self.old_game_mode, + dimension_names: other + .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), + hashed_seed: 0, + max_players: VarInt(0), + view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)), + simulation_distance: VarInt(16), + reduced_debug_info: false, + enable_respawn_screen: false, // TODO + is_debug: false, + is_flat: false, // TODO + }); + + self.teleport(self.position(), self.yaw(), self.pitch()); + } + + // Update the players spawn position (compass position) + if self.modified_spawn_position { + self.modified_spawn_position = false; + + self.send_packet(SpawnPosition { + location: self.spawn_position, + angle: self.spawn_position_yaw, + }) + } + + // 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() { + self.send_packet(UpdateViewDistance { + view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)), + }) + } + } + + // Check if it's time to send another keepalive. + if other.last_keepalive == other.tick_start() { + if self.got_keepalive { + let id = rand::random(); + self.send_packet(KeepAliveClientbound { id }); + self.last_keepalive_id = id; + self.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; + } + + let view_dist = self + .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()); + + // 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); + + if old_section != new_section { + self.send_packet(UpdateViewPosition { + chunk_x: VarInt(new_section.x), + chunk_z: VarInt(new_section.z), + }) + } + } + + // 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) { + if let Some(pkt) = chunk.block_change_packet(pos) { + send_packet(&mut self.send, pkt); + } + true + } else { + send_packet( + &mut self.send, + UnloadChunk { + chunk_x: pos.x, + chunk_z: pos.z, + }, + ); + false + } + } else { + send_packet( + &mut self.send, + UnloadChunk { + chunk_x: pos.x, + chunk_z: pos.z, + }, + ); + false + } + }); + + // 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); + } + } + } + } + } + + // 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; + + 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.old_position = self.new_position; + } + + fn handle_serverbound_packet(&mut self, pkt: ServerPlayPacket) { + fn handle_movement_packet( + client: &mut Client, + new_position: DVec3, + new_yaw: f32, + new_pitch: f32, + new_on_ground: bool, + ) { + if client.pending_teleports == 0 { + let event = Event::Movement { + position: client.new_position, + yaw_degrees: client.yaw, + pitch_degrees: client.pitch, + on_ground: client.on_ground, + }; + + client.new_position = new_position; + client.yaw = new_yaw; + client.pitch = new_pitch; + client.on_ground = new_on_ground; + + client.events.push(event); + } + } + + match pkt { ServerPlayPacket::TeleportConfirm(p) => { if self.pending_teleports == 0 { self.disconnect("Unexpected teleport confirmation"); @@ -366,217 +628,7 @@ impl Client { ServerPlayPacket::Spectate(_) => {} ServerPlayPacket::PlayerBlockPlacement(_) => {} ServerPlayPacket::UseItem(_) => {} - }); - - fn handle_movement_packet( - client: &mut Client, - new_position: DVec3, - new_yaw: f32, - new_pitch: f32, - new_on_ground: bool, - ) { - if client.pending_teleports == 0 { - let event = Event::Movement { - position: client.new_position, - yaw_degrees: client.yaw, - pitch_degrees: client.pitch, - on_ground: client.on_ground, - }; - - client.new_position = new_position; - client.yaw = new_yaw; - client.pitch = new_pitch; - client.on_ground = new_on_ground; - - client.events.push(event); - } } - - if let Some(send) = &self.send { - if send.is_disconnected() || self.recv.is_disconnected() { - self.send = None; - return; - } - } - - let worlds = server.worlds.worlds().unwrap(); - let world = server.worlds.get(&worlds, self.new_world); - - let dim_id = world.map_or(DimensionId::default(), |w| w.dimension()); - let dim = server.dimension(dim_id); - - if self.created_tick == server.current_tick() { - // 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. - - self.send_packet(JoinGame { - entity_id: client_eid.to_network_id(), - is_hardcore: false, - gamemode: self.new_game_mode, - previous_gamemode: self.old_game_mode, - dimension_names: server - .dimensions() - .map(|(_, id)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0)) - .collect(), - dimension_codec: Nbt(make_dimension_codec(server)), - dimension: Nbt(to_dimension_registry_item(dim)), - dimension_name: ident!("{LIBRARY_NAMESPACE}:dimension_{}", dim_id.0), - hashed_seed: 0, - max_players: VarInt(0), - view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)), - simulation_distance: VarInt(16), - reduced_debug_info: false, - enable_respawn_screen: false, // TODO - is_debug: false, - is_flat: false, // TODO - }); - - self.teleport(self.position(), self.yaw(), self.pitch()); - } - - if self.modified_spawn_position { - self.modified_spawn_position = false; - - self.send_packet(SpawnPosition { - location: self.spawn_position, - angle: self.spawn_position_yaw, - }) - } - - // 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 != server.current_tick() { - self.send_packet(UpdateViewDistance { - view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)), - }) - } - } - - // Check if it's time to send another keepalive. - if server.last_keepalive == server.tick_start() { - if self.got_keepalive { - let id = rand::random(); - self.send_packet(KeepAliveClientbound { id }); - self.last_keepalive_id = id; - self.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 = server - .worlds - .get(&worlds, self.old_world) - .map_or(DimensionId::default(), |w| w.dimension()); - - let new_dim = dim_id; - - if old_dim != new_dim { - // Changing dimensions automatically unloads all chunks and - // entities. - self.loaded_chunks.clear(); - self.loaded_entities.clear(); - - todo!("need to send respawn packet for new dimension"); - } - - self.old_world = self.new_world; - } - - let view_dist = self - .settings - .as_ref() - .map_or(2, |s| s.view_distance) - .min(self.new_max_view_distance); - - let chunks = server.chunks.chunks().unwrap(); - - let center = ChunkPos::from_xz(self.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); - - if old_section != new_section { - self.send_packet(UpdateViewPosition { - chunk_x: VarInt(new_section.x), - chunk_z: VarInt(new_section.z), - }) - } - } - - // 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) = server.chunks.get(&chunks, 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) { - if let Some(pkt) = chunk.block_change_packet(pos) { - send_packet(&mut self.send, pkt); - } - true - } else { - send_packet( - &mut self.send, - UnloadChunk { - chunk_x: pos.x, - chunk_z: pos.z, - }, - ); - false - } - } else { - send_packet( - &mut self.send, - UnloadChunk { - chunk_x: pos.x, - chunk_z: pos.z, - }, - ); - false - } - }); - - // 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) = server.chunks.get(&chunks, 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); - } - } - } - } - } - - // 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; - - 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.old_position = self.new_position; } } @@ -603,7 +655,7 @@ pub enum Event { }, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq, Debug)] pub struct Settings { /// e.g. en_US pub locale: String, @@ -636,9 +688,9 @@ fn send_packet(send_opt: &mut Option>, pkt: impl Into DimensionCodec { +fn make_dimension_codec(other: &Other) -> DimensionCodec { let mut dims = Vec::new(); - for (dim, id) in server.dimensions() { + for (dim, id) in other.dimensions() { let id = id.0 as i32; dims.push(DimensionTypeRegistryEntry { name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"), @@ -648,7 +700,7 @@ fn make_dimension_codec(server: &Server) -> DimensionCodec { } let mut biomes = Vec::new(); - for (biome, id) in server.biomes() { + for (biome, id) in other.biomes() { biomes.push(to_biome_registry_item(biome, id.0 as i32)); } diff --git a/src/component.rs b/src/component.rs index e579791..c7cfa7a 100644 --- a/src/component.rs +++ b/src/component.rs @@ -188,23 +188,23 @@ impl ComponentStore { }) } - pub fn register_component(&mut self) { + pub fn register_component(&mut self) { if let Entry::Vacant(ve) = self.components.entry(TypeId::of::()) { let mut vec = Vec::new(); - vec.resize_with(self.ids.len(), C::default); + vec.resize_with(self.ids.len(), C::default_private); ve.insert(Box::new(RwLock::new(vec))); } } - pub fn unregister_component(&mut self) { + pub fn unregister_component(&mut self) { self.components.remove(&TypeId::of::()); } - pub fn is_registered(&self) -> bool { + pub fn is_registered(&self) -> bool { self.components.contains_key(&TypeId::of::()) } - pub fn components( + pub fn components( &self, ) -> Result, Error> { let handle = self @@ -223,7 +223,7 @@ impl ComponentStore { }) } - pub fn components_mut( + pub fn components_mut( &self, ) -> Result, Error> { let handle = self @@ -266,32 +266,30 @@ trait ComponentVec: Any + Send + Sync { fn as_any_mut(&mut self) -> &mut dyn Any; } -impl ComponentVec for RwLock> { +impl ComponentVec for RwLock> { fn push_default(&mut self) { - self.get_mut().push(T::default()); + self.get_mut().push(T::default_private()); } fn clear_at(&mut self, idx: usize) { - self.get_mut()[idx] = T::default(); + self.get_mut()[idx] = T::default_private(); } fn as_any(&self) -> &dyn Any { - self as _ + self } fn as_any_mut(&mut self) -> &mut dyn Any { - self as _ + self } } -pub struct Components<'a, C: 'static + Send + Sync + Default, I: Id> { +pub struct Components<'a, C: 'static + Send + Sync, I: Id> { handle: RwLockReadGuard<'a, Vec>, _marker: PhantomData I>, } -impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponentsRaw - for &'b Components<'a, C, I> -{ +impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponentsRaw for &'b Components<'a, C, I> { type RawItem = &'b C; type RawIter = std::slice::Iter<'b, C>; type RawParIter = rayon::slice::Iter<'b, C>; @@ -309,21 +307,17 @@ impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponentsRaw } } -impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponents - for &'b Components<'a, C, I> -{ +impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponents for &'b Components<'a, C, I> { type Id = I; type Item = &'b C; } -pub struct ComponentsMut<'a, C: 'static + Send + Sync + Default, I: Id> { +pub struct ComponentsMut<'a, C: 'static + Send + Sync, I: Id> { handle: RwLockWriteGuard<'a, Vec>, _marker: PhantomData I>, } -impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponentsRaw - for &'b ComponentsMut<'a, C, I> -{ +impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponentsRaw for &'b ComponentsMut<'a, C, I> { type RawItem = &'b C; type RawIter = std::slice::Iter<'b, C>; type RawParIter = rayon::slice::Iter<'b, C>; @@ -341,14 +335,12 @@ impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponentsRaw } } -impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponents - for &'b ComponentsMut<'a, C, I> -{ +impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponents for &'b ComponentsMut<'a, C, I> { type Id = I; type Item = &'b C; } -impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponentsRaw +impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponentsRaw for &'b mut ComponentsMut<'a, C, I> { type RawItem = &'b mut C; @@ -368,13 +360,12 @@ impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponentsRaw } } -impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponents - for &'b mut ComponentsMut<'a, C, I> -{ +impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponents for &'b mut ComponentsMut<'a, C, I> { type Id = I; type Item = &'b mut C; } +/// The possible errors when requesting a component. #[derive(Clone, Copy, PartialEq, Eq, Debug, Error)] pub enum Error { #[error("an unknown component type was requested")] @@ -442,6 +433,20 @@ pub(crate) mod private { pub(crate) use private::*; +/// Like `Default`, but only usable internally by this crate. +/// +/// This prevents invariants regarding built-in components from being broken +/// by library users. +pub(crate) trait DefaultPrivate { + fn default_private() -> Self; +} + +impl DefaultPrivate for T { + fn default_private() -> Self { + T::default() + } +} + pub trait ZippedComponents: ZippedComponentsRaw { type Id: Copy; type Item: Send + Sync; diff --git a/src/entity.rs b/src/entity.rs index 2e4ef53..a92ea1b 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -1,65 +1,98 @@ +pub mod meta; +pub mod types; + use std::collections::hash_map::Entry; use std::collections::HashMap; use std::iter::FusedIterator; -use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; +use rayon::iter::ParallelIterator; use uuid::Uuid; use crate::chunk::ChunkPos; -use crate::client::MaybeClient; -use crate::component::{ - ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents, - ZippedComponentsRaw, -}; -use crate::{Aabb, WorldId}; - -pub mod appearance; - -pub use appearance::Appearance; +use crate::glm::DVec3; +use crate::slotmap::{Key, SlotMap}; +use crate::{Aabb, Id, WorldId}; pub struct EntityStore { - comps: ComponentStore, - uuids: Vec, - clients: RwLock>, - appearances: RwLock>, - old_appearances: Vec, + sm: SlotMap, uuid_to_entity: HashMap, /// Maps chunk positions to the set of all entities with bounding volumes /// intersecting that chunk. partition: HashMap<(WorldId, ChunkPos), Vec>, } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct EntityId(Key); + +impl Id for EntityId { + fn idx(self) -> usize { + self.0.index() as usize + } +} + +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, + old_world: Option, + 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 { + // TODO + Aabb::default() + } +} + +pub use types::{EntityData, EntityType}; + impl EntityStore { pub(crate) fn new() -> Self { Self { - comps: ComponentStore::new(), - uuids: Vec::new(), - clients: RwLock::new(Vec::new()), - appearances: RwLock::new(Vec::new()), - old_appearances: Vec::new(), + sm: SlotMap::new(), uuid_to_entity: HashMap::new(), partition: HashMap::new(), } } - /// 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 with_uuid(&self, uuid: Uuid) -> Option { - self.uuid_to_entity.get(&uuid).cloned() + /// Returns the number of live entities. + pub fn count(&self) -> usize { + self.sm.count() } - /// Spawns a new entity with the provided appearance. The new entity's - /// [`EntityId`] is returned. - pub fn create(&mut self, appearance: impl Into) -> EntityId { - let app = appearance.into(); + /// Spawns a new entity with the default data. The new entity'd [`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. + pub fn create(&mut self) -> EntityId { loop { let uuid = Uuid::from_bytes(rand::random()); - if let Some(e) = self.create_with_uuid(app.clone(), uuid) { - return e; + if let Some(entity) = self.create_with_uuid(uuid) { + return entity; } } } @@ -69,156 +102,81 @@ impl EntityStore { /// /// The provided UUID must not conflict with an existing entity UUID. If it /// does, `None` is returned and the entity is not spawned. - pub fn create_with_uuid( - &mut self, - appearance: impl Into, - uuid: Uuid, - ) -> Option { + pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option { match self.uuid_to_entity.entry(uuid) { Entry::Occupied(_) => None, Entry::Vacant(ve) => { - let app = appearance.into(); - - let entity = self.comps.create_item(); + let entity = EntityId(self.sm.insert(Entity { + data: EntityData::Marker(types::Marker::new()), + old_type: EntityType::Marker, + new_position: DVec3::default(), + old_position: DVec3::default(), + new_world: None, + old_world: None, + uuid, + })); ve.insert(entity); - if let (Some(aabb), Some(world)) = (app.aabb(), app.world()) { - self.partition_insert(entity, world, aabb); - } - - let idx = entity.0.idx as usize; - if idx >= self.uuids.len() { - self.uuids.push(uuid); - self.clients.get_mut().push(MaybeClient(None)); - self.appearances.get_mut().push(app.clone()); - self.old_appearances.push(app); - } else { - self.uuids[idx] = uuid; - self.clients.get_mut()[idx].0 = None; - self.appearances.get_mut()[idx] = app.clone(); - self.old_appearances[idx] = app; - } + // TODO: insert into partition. Some(entity) } } } - pub fn delete(&mut self, entity: EntityId) -> bool { - if self.comps.delete_item(entity) { - let idx = entity.0.idx as usize; + /// 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 { + 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 - .remove(&self.uuids[idx]) + .remove(&e.uuid) .expect("UUID should have been in UUID map"); - self.clients.get_mut()[idx].0 = None; - - let app = &self.appearances.get_mut()[idx]; - if let (Some(aabb), Some(world)) = (app.aabb(), app.world()) { - self.partition_remove(entity, world, aabb); - } + // TODO: remove entity from partition. true } else { false } } - /// Returns the number of live entities. - pub fn count(&self) -> usize { - self.comps.count() + pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) { + self.sm.retain(|k, v| f(EntityId(k), v)) } - pub fn is_valid(&self, entity: EntityId) -> bool { - self.comps.is_valid(entity) + pub fn get(&self, entity: EntityId) -> Option<&Entity> { + self.sm.get(entity.0) } - pub fn get(&self, z: Z, entity: EntityId) -> Option - where - Z: ZippedComponents, - { - self.comps.get(z, entity) + pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> { + self.sm.get_mut(entity.0) } - pub fn iter<'a, Z>(&'a self, z: Z) -> impl FusedIterator + 'a - where - Z: ZippedComponents + 'a, - { - self.comps.iter(z) + pub fn iter(&self) -> impl FusedIterator + Clone + '_ { + self.sm.iter().map(|(k, v)| (EntityId(k), v)) } - pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator + 'a - where - Z: ZippedComponents + 'a, - { - self.comps.par_iter(z) + pub fn iter_mut(&mut self) -> impl FusedIterator + '_ { + self.sm.iter_mut().map(|(k, v)| (EntityId(k), v)) } - pub fn ids(&self) -> impl FusedIterator + Clone + '_ { - self.comps.ids() + pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ { + self.sm.par_iter().map(|(k, v)| (EntityId(k), v)) } - pub fn par_ids(&self) -> impl ParallelIterator + Clone + '_ { - self.comps.par_ids() + pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ { + self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v)) } - pub fn uuids(&self) -> Uuids { - Uuids { uuids: &self.uuids } - } - - pub fn clients(&self) -> Result { - Ok(Clients { - clients: self.clients.try_read().ok_or(Error::NoReadAccess)?, - }) - } - - pub fn clients_mut(&self) -> Result { - Ok(ClientsMut { - clients: self.clients.try_write().ok_or(Error::NoWriteAccess)?, - }) - } - - pub fn appearances(&self) -> Result { - Ok(Appearances { - appearances: self.appearances.try_read().ok_or(Error::NoReadAccess)?, - }) - } - - pub fn appearances_mut(&self) -> Result { - Ok(AppearancesMut { - appearances: self.appearances.try_write().ok_or(Error::NoWriteAccess)?, - }) - } - - pub fn old_appearances(&self) -> OldAppearances { - OldAppearances { - old_appearances: &self.old_appearances, - } - } - - pub fn register_component(&mut self) { - self.comps.register_component::(); - } - - pub fn unregister_component(&mut self) { - self.comps.unregister_component::() - } - - pub fn is_registered(&self) -> bool { - self.comps.is_registered::() - } - - pub fn components( - &self, - ) -> Result, Error> { - self.comps.components::() - } - - pub fn components_mut( - &self, - ) -> Result, Error> { - self.comps.components_mut::() + pub(crate) fn from_network_id(&self, network_id: i32) -> Option { + self.sm.key_at_index(network_id as usize).map(EntityId) } fn partition_insert(&mut self, entity: EntityId, world: WorldId, aabb: Aabb) { @@ -333,10 +291,9 @@ impl EntityStore { .into_iter() .flat_map(move |v| { v.iter().cloned().filter(move |&e| { - self.get(&self.old_appearances(), e) - .expect("spatial partition contains expired entity") - .aabb() - .expect("spatial partition contains entity without AABB") + self.get(e) + .expect("spatial partition contains deleted entity") + .hitbox() .collides_with_aabb(&aabb) }) }) @@ -344,298 +301,43 @@ impl EntityStore { }) } - pub(crate) fn update_old_appearances(&mut self) { - for (old, new) in self - .old_appearances - .iter_mut() - .zip(self.appearances.get_mut().iter()) - { - old.clone_from(new); + 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. } } } -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)] -pub struct EntityId(IdData); - -impl IdRaw for EntityId { - fn from_data(data: IdData) -> Self { - Self(data) - } - - fn to_data(self) -> IdData { - self.0 - } -} - -impl EntityId { - /// The vaule of the default `EntityId` which always refers to an expired - /// entity. - pub const NULL: Self = Self(IdData::NULL); - - pub(crate) fn to_network_id(self) -> i32 { - self.0.idx as i32 - } -} - -impl Id for EntityId {} - -/// A built-in component collection containing the UUID of the entities. -/// -/// The default value for this component is a random unassigned UUID. UUIDs -/// cannot be modified after an entity is created. -/// -/// TODO: describe the UUID for players. -pub struct Uuids<'a> { - uuids: &'a Vec, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b Uuids<'a> { - type RawItem = Uuid; - type RawIter = std::iter::Cloned>; - type RawParIter = rayon::iter::Cloned>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - self.uuids[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.uuids.iter().cloned() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.uuids.par_iter().cloned() - } -} - -impl<'a, 'b> ZippedComponents for &'b Uuids<'a> { - type Id = EntityId; - type Item = Uuid; -} - -/// A built-in component collection containing the clients that entites are -/// backed by, if any. -/// -/// When a client joins the server, a new entity is created which is backed by -/// the new client. However, when a client is disconnected, the entity which -/// they inhabited is _not_ automatically deleted. -/// -/// Deleting the associated entity while the client is still connected will -/// immediately disconnect the client. -/// -/// The default value of this component will not contain a client and all calls -/// to [`get`](Self::get) and [`get_mut`](Self::get_mut) will return `None`. - -pub struct Clients<'a> { - // TODO: box the clients - clients: RwLockReadGuard<'a, Vec>, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b Clients<'a> { - type RawItem = &'b MaybeClient; - type RawIter = std::slice::Iter<'b, MaybeClient>; - type RawParIter = rayon::slice::Iter<'b, MaybeClient>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.clients[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.clients.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.clients.par_iter() - } -} - -impl<'a, 'b> ZippedComponents for &'b Clients<'a> { - type Id = EntityId; - type Item = &'b MaybeClient; -} - -pub struct ClientsMut<'a> { - clients: RwLockWriteGuard<'a, Vec>, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b ClientsMut<'a> { - type RawItem = &'b MaybeClient; - type RawIter = std::slice::Iter<'b, MaybeClient>; - type RawParIter = rayon::slice::Iter<'b, MaybeClient>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.clients[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.clients.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.clients.par_iter() - } -} - -impl<'a, 'b> ZippedComponents for &'b ClientsMut<'a> { - type Id = EntityId; - type Item = &'b MaybeClient; -} - -impl<'a, 'b> ZippedComponentsRaw for &'b mut ClientsMut<'a> { - type RawItem = &'b mut MaybeClient; - type RawIter = std::slice::IterMut<'b, MaybeClient>; - type RawParIter = rayon::slice::IterMut<'b, MaybeClient>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &mut self.clients[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.clients.iter_mut() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.clients.par_iter_mut() - } -} - -impl<'a, 'b> ZippedComponents for &'b mut ClientsMut<'a> { - type Id = EntityId; - type Item = &'b mut MaybeClient; -} - -pub struct Appearances<'a> { - appearances: RwLockReadGuard<'a, Vec>, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b Appearances<'a> { - type RawItem = &'b Appearance; - type RawIter = std::slice::Iter<'b, Appearance>; - type RawParIter = rayon::slice::Iter<'b, Appearance>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.appearances[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.appearances.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.appearances.par_iter() - } -} - -impl<'a, 'b> ZippedComponents for &'b Appearances<'a> { - type Id = EntityId; - type Item = &'b Appearance; -} - -pub struct AppearancesMut<'a> { - appearances: RwLockWriteGuard<'a, Vec>, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b AppearancesMut<'a> { - type RawItem = &'b Appearance; - type RawIter = std::slice::Iter<'b, Appearance>; - type RawParIter = rayon::slice::Iter<'b, Appearance>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.appearances[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.appearances.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.appearances.par_iter() - } -} - -impl<'a, 'b> ZippedComponents for &'b AppearancesMut<'a> { - type Id = EntityId; - type Item = &'b Appearance; -} - -impl<'a, 'b> ZippedComponentsRaw for &'b mut AppearancesMut<'a> { - type RawItem = &'b mut Appearance; - type RawIter = std::slice::IterMut<'b, Appearance>; - type RawParIter = rayon::slice::IterMut<'b, Appearance>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &mut self.appearances[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.appearances.iter_mut() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.appearances.par_iter_mut() - } -} - -impl<'a, 'b> ZippedComponents for &'b mut AppearancesMut<'a> { - type Id = EntityId; - type Item = &'b mut Appearance; -} - -/// Contains a snapshot of an entity's [`Appearance`] as it existed at the end -/// of the previous tick. -pub struct OldAppearances<'a> { - old_appearances: &'a Vec, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b OldAppearances<'a> { - type RawItem = &'b Appearance; - type RawIter = std::slice::Iter<'b, Appearance>; - type RawParIter = rayon::slice::Iter<'b, Appearance>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.old_appearances[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.old_appearances.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.old_appearances.par_iter() - } -} - -impl<'a, 'b> ZippedComponents for &'b OldAppearances<'a> { - type Id = EntityId; - type Item = &'b Appearance; -} - -#[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::>(); - - 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)); - } -} +//#[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::>(); +// +// 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)); +// } +//} diff --git a/src/entity/appearance.rs b/src/entity/appearance.rs deleted file mode 100644 index 290804e..0000000 --- a/src/entity/appearance.rs +++ /dev/null @@ -1,81 +0,0 @@ -use glm::DVec3; - -use crate::{glm, Aabb, WorldId}; - -// TODO: override default clone_from impl for Appearance. -/// Encodes the type of an entity (pig, player, item frame, etc.) along with any -/// state that would influence its appearance to clients. -#[derive(Clone, Debug)] -pub enum Appearance { - /// The default appearance. - /// - /// Entities with an appearance of `None` will not be visible to clients. - None, - Player(Player), -} - -impl Appearance { - pub fn position(&self) -> Option { - match self { - Appearance::None => None, - Appearance::Player(p) => Some(p.position), - } - } - - pub fn position_mut(&mut self) -> Option<&mut DVec3> { - match self { - Appearance::None => None, - Appearance::Player(p) => Some(&mut p.position), - } - } - - pub fn world(&self) -> Option { - match self { - Appearance::None => None, - Appearance::Player(p) => Some(p.world), - } - } - - pub fn world_mut(&mut self) -> Option<&mut WorldId> { - match self { - Appearance::None => None, - Appearance::Player(p) => Some(&mut p.world), - } - } - - pub fn aabb(&self) -> Option> { - match self { - Appearance::None => None, - Appearance::Player(p) => Some(p.aabb()), - } - } -} - -impl Default for Appearance { - fn default() -> Self { - Self::None - } -} - -#[derive(Clone, Debug)] -pub struct Player { - pub position: DVec3, - pub world: WorldId, -} - -impl Player { - pub fn new(position: DVec3, world: WorldId) -> Self { - Self { position, world } - } - - pub fn aabb(&self) -> Aabb { - // TODO: player hitbox dimensions change depending on pose - Aabb::from_center_and_dimensions(self.position, glm::vec3(0.6, 1.8, 0.6)) - } -} - -impl From for Appearance { - fn from(p: Player) -> Self { - Self::Player(p) - } -} diff --git a/src/entity/meta.rs b/src/entity/meta.rs new file mode 100644 index 0000000..eb40590 --- /dev/null +++ b/src/entity/meta.rs @@ -0,0 +1,207 @@ +use std::io::Write; + +use anyhow::Context; +use uuid::Uuid; + +use crate::block_pos::BlockPos; +use crate::protocol::Encode; +use crate::var_int::VarInt; +use crate::{def_bitfield, Text}; + +#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)] +pub struct ArmorStandRotations { + pub x_degrees: f32, + pub y_degrees: f32, + pub z_degrees: f32, +} + +impl ArmorStandRotations { + pub fn new(x_degrees: f32, y_degrees: f32, z_degrees: f32) -> Self { + Self { + x_degrees, + y_degrees, + z_degrees, + } + } +} + +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) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum Direction { + Down, + Up, + North, + South, + West, + East, +} + +impl Encode for Direction { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct VillagerData { + pub typ: VillagerType, + pub profession: VillagerProfession, + pub level: i32, +} + +impl VillagerData { + pub const fn new(typ: VillagerType, profession: VillagerProfession, level: i32) -> Self { + Self { + typ, + profession, + level, + } + } +} + +impl Default for VillagerData { + fn default() -> Self { + Self { + typ: Default::default(), + profession: Default::default(), + level: 1, + } + } +} + +impl Encode for VillagerData { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(self.typ as i32).encode(w)?; + VarInt(self.profession as i32).encode(w)?; + VarInt(self.level).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum VillagerType { + Desert, + Jungle, + Plains, + Savanna, + Snow, + Swamp, + Taiga, +} + +impl Default for VillagerType { + fn default() -> Self { + Self::Plains + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum VillagerProfession { + None, + Armorer, + Butcher, + Cartographer, + Cleric, + Farmer, + Fisherman, + Fletcher, + Leatherworker, + Librarian, + Mason, + Nitwit, + Shepherd, + Toolsmith, + Weaponsmith, +} + +impl Default for VillagerProfession { + fn default() -> Self { + Self::None + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +struct OptVarInt(Option); + +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, + FallFlying, + Sleeping, + Swimming, + SpinAttack, + Sneaking, + LongJumping, + Dying, +} + +impl Default for Pose { + fn default() -> Self { + Self::Standing + } +} + +impl Encode for Pose { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum MainHand { + Left, + Right, +} + +impl Default for MainHand { + fn default() -> Self { + Self::Right + } +} + +impl Encode for MainHand { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + (*self as u8).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum BoatVariant { + Oak, + Spruce, + Birch, + Jungle, + Acacia, + DarkOak, +} + +impl Default for BoatVariant { + fn default() -> Self { + Self::Oak + } +} + +impl Encode for BoatVariant { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} diff --git a/src/entity/types.rs b/src/entity/types.rs new file mode 100644 index 0000000..f29989b --- /dev/null +++ b/src/entity/types.rs @@ -0,0 +1,5 @@ +use crate::block::BlockState; +use crate::entity::meta::*; +use crate::{BlockPos, EntityId, Text, Uuid}; + +include!(concat!(env!("OUT_DIR"), "/entity.rs")); diff --git a/src/lib.rs b/src/lib.rs index fd7104f..6995410 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,19 @@ #![forbid(unsafe_code)] +#![warn( + missing_debug_implementations, + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + missing_docs +)] mod aabb; pub mod block; mod block_pos; mod byte_angle; -mod chunk; -mod chunk_store; -mod client; +pub mod chunk; +pub mod client; mod codec; pub mod component; pub mod config; @@ -14,23 +21,25 @@ pub mod entity; pub mod identifier; mod packets; mod protocol; -mod server; +pub mod server; +mod slotmap; pub mod text; pub mod util; mod var_int; mod var_long; -mod world; +pub mod world; pub use aabb::Aabb; -pub use chunk::{Chunk, ChunkPos}; -pub use client::Client; +pub use block_pos::BlockPos; +pub use chunk::{Chunk, ChunkPos, ChunkStore}; +pub use client::{Client, ClientStore}; pub use config::{BiomeId, DimensionId, ServerConfig}; -pub use entity::{EntityId, EntityStore}; +pub use entity::{Entity, EntityId, EntityStore}; pub use identifier::Identifier; pub use text::{Text, TextFormat}; pub use uuid::Uuid; -pub use world::{World, WorldId}; -pub use {nalgebra_glm as glm, nbt}; +pub use world::{World, WorldId, WorldStore}; +pub use {nalgebra_glm as glm, nbt, uuid}; pub use crate::server::{NewClientData, Server, SharedServer, ShutdownResult}; @@ -48,3 +57,18 @@ 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; +} diff --git a/src/packets.rs b/src/packets.rs index a53f430..e674b10 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -1,4 +1,4 @@ -//! Contains packet definitions and the types contained within them. +//! Contains packet definitions and some types contained within them. //! //! See for up to date protocol information. @@ -258,6 +258,8 @@ macro_rules! if_typ_is_empty_pat { }; } +#[doc(hidden)] +#[macro_export] macro_rules! def_bitfield { ( $(#[$struct_attrs:meta])* @@ -269,7 +271,7 @@ macro_rules! def_bitfield { } ) => { // TODO: custom Debug impl. - #[derive(Copy, Clone, Default, PartialEq, Eq, Debug)] + #[derive(Clone, Copy, PartialEq, Eq, Debug)] $(#[$struct_attrs])* pub struct $name($inner_ty); @@ -279,8 +281,8 @@ macro_rules! def_bitfield { $bit: bool, )* ) -> Self { - let mut res = Self::default(); - paste! { + let mut res = Self(Default::default()); + paste::paste! { $( res = res.[]($bit); )* @@ -288,7 +290,7 @@ macro_rules! def_bitfield { res } - paste! { + paste::paste! { $( #[doc = "Gets the " $bit " bit on this bitfield.\n"] $(#[$bit_attrs])* diff --git a/src/protocol.rs b/src/protocol.rs index 905f943..5a1e71a 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -201,7 +201,10 @@ impl Decode for f64 { impl Encode for Option { fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { match self { - Some(t) => true.encode(w).and_then(|_| t.encode(w)), + Some(t) => { + true.encode(w)?; + t.encode(w) + } None => false.encode(w), } } diff --git a/src/server.rs b/src/server.rs index 1e3b240..88a1348 100644 --- a/src/server.rs +++ b/src/server.rs @@ -26,11 +26,8 @@ use tokio::runtime::{Handle, Runtime}; use tokio::sync::{oneshot, Semaphore}; use uuid::Uuid; -use crate::chunk_store::ChunkStore; -use crate::client::MaybeClient; use crate::codec::{Decoder, Encoder}; use crate::config::{Biome, BiomeId, Dimension, DimensionId, Handler, Login, ServerListPing}; -use crate::entity::Appearance; use crate::packets::handshake::{Handshake, HandshakeNextState}; use crate::packets::login::{ self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression, @@ -40,8 +37,10 @@ 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::world::WorldStore; -use crate::{Client, EntityStore, ServerConfig, Ticks, PROTOCOL_VERSION, VERSION_NAME}; +use crate::{ + ChunkStore, Client, ClientStore, EntityStore, ServerConfig, Ticks, WorldStore, + 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`]. @@ -55,6 +54,7 @@ use crate::{Client, EntityStore, ServerConfig, Ticks, PROTOCOL_VERSION, VERSION_ #[non_exhaustive] pub struct Server { pub entities: EntityStore, + pub clients: ClientStore, pub worlds: WorldStore, pub chunks: ChunkStore, pub other: Other, @@ -89,7 +89,6 @@ struct SharedServerInner { tokio_handle: Handle, dimensions: Vec, biomes: Vec, - /// The instant the server was started. start_instant: Instant, /// A semaphore used to limit the number of simultaneous connections to the @@ -338,7 +337,8 @@ pub(crate) fn start_server(config: ServerConfig) -> ShutdownResult { let mut server = Server { entities: EntityStore::new(), - worlds: WorldStore::new(shared.clone()), + clients: ClientStore::new(), + worlds: WorldStore::new(), chunks: ChunkStore::new(), other: Other { shared: shared.clone(), @@ -375,43 +375,35 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult { } { - let mut clients = server.entities.clients_mut().unwrap(); - - server - .entities - .par_iter(&mut clients) - .for_each(|(e, client)| { - if let Some(client) = client.get_mut() { - client.update(e, server); - } - }); - } - - server.entities.update_old_appearances(); - - { - let mut chunks = server.chunks.chunks_mut().unwrap(); - - server.chunks.par_iter(&mut chunks).for_each(|(_, chunk)| { - chunk.apply_modifications(); + server.clients.par_iter_mut().for_each(|(_, client)| { + client.update( + &server.entities, + &server.worlds, + &server.chunks, + &server.other, + ) }); } + server.entities.update(); + + server + .chunks + .par_iter_mut() + .for_each(|(_, chunk)| chunk.apply_modifications()); + shared.handler().update(server); - { - let mut chunks = server.chunks.chunks_mut().unwrap(); - - // Chunks modified this tick can have their changes applied immediately because - // they have not been observed by clients yet. - server.chunks.par_iter(&mut chunks).for_each(|(_, chunk)| { - if chunk.created_this_tick() { - chunk.clear_created_this_tick(); - chunk.apply_modifications(); - } - }); - } + // Chunks modified 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(); + chunk.apply_modifications(); + } + }); + // Sleep for the remainder of the tick. thread::sleep( server .0 @@ -433,35 +425,29 @@ fn join_player(server: &mut Server, msg: NewClientMessage) { let _ = msg.reply.send(Ok(client_packet_channels)); - let mut client = Client::new(server_packet_channels, msg.ncd.username, server); - - let client_eid = match server - .entities - .create_with_uuid(Appearance::None, msg.ncd.uuid) - { - Some(eid) => eid, + let client_backed_entity = match server.entities.create_with_uuid(msg.ncd.uuid) { + Some(id) => id, None => { log::error!( "player '{}' cannot join the server because their UUID ({}) conflicts with an \ existing entity", - client.username(), + msg.ncd.username, msg.ncd.uuid ); - - client.disconnect("Cannot join server: Your UUID conflicts with an existing entity."); return; } }; - let mut clients = server.entities.clients_mut().unwrap(); - *server.entities.get(&mut clients, client_eid).unwrap() = MaybeClient(Some(Box::new(client))); + let client_id = server.clients.create(Client::new( + server_packet_channels, + client_backed_entity, + msg.ncd.username, + server, + )); } type Codec = (Encoder, Decoder); -/// The duration of time between each sent keep alive packet. -// TODO: remove this. - async fn do_accept_loop(server: SharedServer) { log::trace!("entering accept loop"); diff --git a/src/slotmap.rs b/src/slotmap.rs new file mode 100644 index 0000000..d0e3a22 --- /dev/null +++ b/src/slotmap.rs @@ -0,0 +1,301 @@ +//! Like the `slotmap` crate, but uses no unsafe code and has rayon support. + +use std::iter::FusedIterator; +use std::mem; +use std::num::NonZeroU32; + +use rayon::iter::{ + IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, +}; + +#[derive(Clone, Debug)] +pub struct SlotMap { + slots: Vec>, + next_free_head: u32, + /// The number of occupied slots. + count: u32, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct Key { + version: NonZeroU32, + index: u32, +} + +impl Key { + pub fn version(self) -> NonZeroU32 { + self.version + } + + pub fn index(self) -> u32 { + self.index + } +} + +#[derive(Clone, Debug)] +struct Slot { + version: NonZeroU32, + item: Item, +} + +#[derive(Clone, Debug)] +enum Item { + Occupied(T), + Vacant { next_free: u32 }, +} + +impl SlotMap { + pub fn new() -> Self { + Self { + slots: Vec::new(), + next_free_head: 0, + count: 0, + } + } + + pub fn count(&self) -> usize { + self.count as usize + } + + pub fn insert(&mut self, val: T) -> Key { + assert!( + // -1 so that NULL is always invalid. + self.count < u32::MAX, + "SlotMap: too many items inserted" + ); + + if self.next_free_head == self.slots.len() as u32 { + self.slots.push(Slot { + version: ONE, + item: Item::Occupied(val), + }); + + self.count += 1; + self.next_free_head += 1; + Key { + version: ONE, + index: self.next_free_head - 1, + } + } else { + let slot = &mut self.slots[self.next_free_head as usize]; + slot.version = match NonZeroU32::new(slot.version.get().wrapping_add(1)) { + Some(n) => n, + None => { + log::debug!("SlotMap: version overflow at idx = {}", self.next_free_head); + ONE + } + }; + + let next_free = match slot.item { + Item::Occupied(_) => unreachable!("corrupt free list"), + Item::Vacant { next_free } => next_free, + }; + + let key = Key { + version: slot.version, + index: self.next_free_head, + }; + + self.next_free_head = next_free; + self.count += 1; + slot.item = Item::Occupied(val); + key + } + } + + pub fn remove(&mut self, key: Key) -> Option { + let Slot { version, item } = self.slots.get_mut(key.index as usize)?; + + match item { + Item::Occupied(_) if *version == key.version => { + let item = mem::replace( + item, + Item::Vacant { + next_free: self.next_free_head, + }, + ); + + self.next_free_head = key.index; + self.count -= 1; + + match item { + Item::Occupied(val) => Some(val), + Item::Vacant { next_free } => unreachable!(), + } + } + _ => None, + } + } + + pub fn get(&self, key: Key) -> Option<&T> { + match self.slots.get(key.index as usize)? { + Slot { + version, + item: Item::Occupied(val), + } if *version == key.version => Some(val), + _ => None, + } + } + + pub fn get_mut(&mut self, key: Key) -> Option<&mut T> { + match self.slots.get_mut(key.index as usize)? { + Slot { + version, + item: Item::Occupied(val), + } if *version == key.version => Some(val), + _ => None, + } + } + + pub fn key_at_index(&self, idx: usize) -> Option { + Some(Key { + version: self.slots.get(idx)?.version, + index: idx as u32, + }) + } + + pub fn clear(&mut self) { + self.slots.clear(); + self.next_free_head = 0; + self.count = 0; + } + + pub fn retain(&mut self, mut f: impl FnMut(Key, &mut T) -> bool) { + for (i, slot) in self.slots.iter_mut().enumerate() { + if let Item::Occupied(val) = &mut slot.item { + let key = Key { + version: slot.version, + index: i as u32, + }; + if !f(key, val) { + slot.item = Item::Vacant { + next_free: self.next_free_head, + }; + + self.next_free_head = key.index; + self.count -= 1; + } + } + } + } + + pub fn iter(&self) -> impl FusedIterator + Clone + '_ { + self.slots + .iter() + .enumerate() + .filter_map(|(i, slot)| match &slot.item { + Item::Occupied(val) => Some(( + Key { + version: slot.version, + index: i as u32, + }, + val, + )), + Item::Vacant { .. } => None, + }) + } + + pub fn iter_mut(&mut self) -> impl FusedIterator + '_ { + self.slots + .iter_mut() + .enumerate() + .filter_map(|(i, slot)| match &mut slot.item { + Item::Occupied(val) => Some(( + Key { + version: slot.version, + index: i as u32, + }, + val, + )), + Item::Vacant { .. } => None, + }) + } +} + +impl SlotMap { + pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ { + self.slots + .par_iter() + .enumerate() + .filter_map(|(i, slot)| match &slot.item { + Item::Occupied(val) => Some(( + Key { + version: slot.version, + index: i as u32, + }, + val, + )), + Item::Vacant { .. } => None, + }) + } +} + +impl SlotMap { + pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ { + self.slots + .par_iter_mut() + .enumerate() + .filter_map(|(i, slot)| match &mut slot.item { + Item::Occupied(val) => Some(( + Key { + version: slot.version, + index: i as u32, + }, + val, + )), + Item::Vacant { .. } => None, + }) + } +} + +impl Default for SlotMap { + fn default() -> Self { + Self::new() + } +} + +const ONE: NonZeroU32 = match NonZeroU32::new(1) { + Some(n) => n, + None => unreachable!(), +}; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn insert_remove() { + let mut sm = SlotMap::new(); + + let k0 = sm.insert(10); + let k1 = sm.insert(20); + let k2 = sm.insert(30); + + assert_eq!(sm.remove(k1), Some(20)); + assert_eq!(sm.get(k1), None); + assert_eq!(sm.get(k2), Some(&30)); + let k3 = sm.insert(40); + assert_eq!(sm.get(k0), Some(&10)); + assert_eq!(sm.get_mut(k3), Some(&mut 40)); + assert_eq!(sm.remove(k0), Some(10)); + + sm.clear(); + assert_eq!(sm.count(), 0); + } + + fn retain() { + let mut sm = SlotMap::new(); + + let k0 = sm.insert(10); + let k1 = sm.insert(20); + let k2 = sm.insert(30); + + sm.retain(|k, _| k == k1); + + assert_eq!(sm.get(k1), Some(&20)); + assert_eq!(sm.count(), 1); + + assert_eq!(sm.get(k0), None); + assert_eq!(sm.get(k2), None); + } +} diff --git a/src/text.rs b/src/text.rs index 9a14949..d784fe8 100644 --- a/src/text.rs +++ b/src/text.rs @@ -83,8 +83,8 @@ pub struct Text { /// Provides the methods necessary for working with [`Text`] objects. /// -/// This trait exists to allow using `Into` impls without having to first -/// convert `Self` into [`Text`]. It is automatically implemented for all +/// This trait exists to allow using `Into` types without having to first +/// convert the type into [`Text`]. It is automatically implemented for all /// `Into` types, including [`Text`] itself. pub trait TextFormat: Into { fn into_text(self) -> Text { @@ -347,6 +347,8 @@ impl Text { Ok(()) } + + // TODO: getters } impl> TextFormat for T {} diff --git a/src/world.rs b/src/world.rs index 716b10a..55b2afe 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,234 +1,78 @@ use std::collections::HashMap; use std::iter::FusedIterator; -use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; +use rayon::iter::ParallelIterator; -use crate::chunk::ChunkPos; -use crate::chunk_store::ChunkId; -use crate::component::{ - ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents, - ZippedComponentsRaw, -}; +use crate::chunk::{ChunkId, ChunkPos}; use crate::config::DimensionId; -use crate::SharedServer; +use crate::slotmap::{Key, SlotMap}; +use crate::Id; pub struct WorldStore { - comps: ComponentStore, - worlds: RwLock>, - shared: SharedServer, + sm: SlotMap, +} + +#[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(shared: SharedServer) -> Self { - Self { - comps: ComponentStore::new(), - worlds: RwLock::new(Vec::new()), - shared, - } + pub(crate) fn new() -> Self { + Self { sm: SlotMap::new() } + } + + pub fn count(&self) -> usize { + self.sm.count() } pub fn create(&mut self, dim: DimensionId) -> WorldId { - let height = self.shared.dimension(dim).height; - - let id = self.comps.create_item(); - let world = World { + WorldId(self.sm.insert(World { chunks: HashMap::new(), dimension: dim, - }; - - let idx = id.0.idx as usize; - if idx >= self.worlds.get_mut().len() { - self.worlds.get_mut().push(world); - } else { - self.worlds.get_mut()[idx] = world; - } - - id + })) } /// Deletes a world from the server. Any [`WorldId`] referring to the /// deleted world will be invalidated. /// /// Note that any entities with positions inside the deleted world will not - /// be deleted themselves. These entities should be deleted or moved - /// elsewhere, preferably before calling this function. + /// be deleted themselves. pub fn delete(&mut self, world: WorldId) -> bool { - if self.comps.delete_item(world) { - let idx = world.0.idx as usize; - self.worlds.get_mut()[idx].chunks = HashMap::new(); - true - } else { - false - } + self.sm.remove(world.0).is_some() } - pub fn count(&self) -> usize { - self.comps.count() + pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) { + self.sm.retain(|k, v| f(WorldId(k), v)) } - pub fn get(&self, z: Z, world: WorldId) -> Option - where - Z: ZippedComponents, - { - self.comps.get(z, world) + pub fn get(&self, world: WorldId) -> Option<&World> { + self.sm.get(world.0) } - pub fn iter<'a, Z>(&'a self, z: Z) -> impl FusedIterator + 'a - where - Z: ZippedComponents + 'a, - { - self.comps.iter(z) + pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> { + self.sm.get_mut(world.0) } - pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator + 'a - where - Z: ZippedComponents + 'a, - { - self.comps.par_iter(z) + pub fn iter(&self) -> impl FusedIterator + Clone + '_ { + self.sm.iter().map(|(k, v)| (WorldId(k), v)) } - pub fn ids(&self) -> impl FusedIterator + Clone + '_ { - self.comps.ids() + pub fn iter_mut(&mut self) -> impl FusedIterator + '_ { + self.sm.iter_mut().map(|(k, v)| (WorldId(k), v)) } - pub fn par_ids(&self) -> impl ParallelIterator + Clone + '_ { - self.comps.par_ids() + pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ { + self.sm.par_iter().map(|(k, v)| (WorldId(k), v)) } - pub fn worlds(&self) -> Result { - Ok(Worlds { - worlds: self.worlds.try_read().ok_or(Error::NoReadAccess)?, - }) + pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ { + self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v)) } - - pub fn worlds_mut(&self) -> Result { - Ok(WorldsMut { - worlds: self.worlds.try_write().ok_or(Error::NoWriteAccess)?, - }) - } - - pub fn register_component(&mut self) { - self.comps.register_component::(); - } - - pub fn unregister_component(&mut self) { - self.comps.unregister_component::() - } - - pub fn is_registered(&self) -> bool { - self.comps.is_registered::() - } - - pub fn components( - &self, - ) -> Result, Error> { - self.comps.components::() - } - - pub fn components_mut( - &self, - ) -> Result, Error> { - self.comps.components_mut::() - } -} - -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)] -pub struct WorldId(pub(crate) IdData); - -impl WorldId { - /// The value of the default [`WorldId`] which always refers to an invalid - /// world. - pub const NULL: Self = Self(IdData::NULL); -} - -impl IdRaw for WorldId { - fn to_data(self) -> IdData { - self.0 - } - - fn from_data(id: IdData) -> Self { - Self(id) - } -} - -impl Id for WorldId {} - -pub struct Worlds<'a> { - worlds: RwLockReadGuard<'a, Vec>, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b Worlds<'a> { - type RawItem = &'b World; - type RawIter = std::slice::Iter<'b, World>; - type RawParIter = rayon::slice::Iter<'b, World>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.worlds[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.worlds.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.worlds.par_iter() - } -} - -impl<'a, 'b> ZippedComponents for &'b Worlds<'a> { - type Id = WorldId; - type Item = &'b World; -} - -pub struct WorldsMut<'a> { - worlds: RwLockWriteGuard<'a, Vec>, -} - -impl<'a, 'b> ZippedComponentsRaw for &'b WorldsMut<'a> { - type RawItem = &'b World; - type RawIter = std::slice::Iter<'b, World>; - type RawParIter = rayon::slice::Iter<'b, World>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.worlds[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.worlds.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.worlds.par_iter() - } -} - -impl<'a, 'b> ZippedComponents for &'b WorldsMut<'a> { - type Id = WorldId; - type Item = &'b World; -} - -impl<'a, 'b> ZippedComponentsRaw for &'b mut WorldsMut<'a> { - type RawItem = &'b mut World; - type RawIter = std::slice::IterMut<'b, World>; - type RawParIter = rayon::slice::IterMut<'b, World>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &mut self.worlds[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.worlds.iter_mut() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.worlds.par_iter_mut() - } -} - -impl<'a, 'b> ZippedComponents for &'b mut WorldsMut<'a> { - type Id = WorldId; - type Item = &'b mut World; } pub struct World {