Rip out the ECS.

This commit is contained in:
Ryan 2022-04-29 00:48:41 -07:00
parent 5f2389f0e7
commit 732183dd62
23 changed files with 2841 additions and 1432 deletions

View file

@ -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::<TokenStream>();
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::<TokenStream>();
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::<TokenStream>();
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::<TokenStream>();
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<bool> 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 {

View file

@ -1,6 +1,14 @@
//! See: <https://wiki.vg/Entity_metadata>
#![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<JsonEntity> =
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::<Vec<_>>();
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<str> },
Type::Text => quote! { Box<Text> },
Type::OptText(_) => quote! { Option<Box<Text>> },
Type::Slot => quote! { () }, // TODO
Type::Bool(_) => quote! { bool },
Type::ArmorStandRotations(_, _, _) => quote! { ArmorStandRotations },
Type::BlockPos(_, _, _) => quote! { BlockPos },
Type::OptBlockPos(_) => quote! { Option<BlockPos> },
Type::Direction => quote! { Direction },
Type::OptUuid => quote! { Option<Uuid> },
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<EntityId> },
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<Box<str>>) {
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<Text>) {
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<impl Into<Text>>) {
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<BlockPos>)),
Type::Direction => standard_getter_setter(quote!(Direction)),
Type::OptUuid => standard_getter_setter(quote!(Option<Uuid>)),
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::<TokenStream>();
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);
}

View file

@ -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<str>) -> Ident {
Ident::new(s, Span::call_site())
}
}
fn write_to_out_path(file_name: impl AsRef<str>, content: impl AsRef<str>) -> 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(())
}

1019
data/entities.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -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);
}
});
}
}

View file

@ -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<T, const D: usize> {
min: TVec<T, D>,
max: TVec<T, D>,
}
impl<T: Number, const D: usize> Aabb<T, D> {
pub fn new(p0: TVec<T, D>, p1: TVec<T, D>) -> Self {
pub fn new(p0: impl Into<TVec<T, D>>, p1: impl Into<TVec<T, D>>) -> 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<T: RealNumber, const D: usize> Aabb<T, D> {
glm::distance(&p, &glm::clamp_vec(&p, &self.min, &self.max))
}
}
impl<T: Number + Default, const D: usize> Default for Aabb<T, D> {
fn default() -> Self {
let d = T::default();
Self::new([d; D], [d; D])
}
}
impl<T: Number, const D: usize> PartialEq for Aabb<T, D> {
fn eq(&self, other: &Self) -> bool {
self.min == other.min && self.max == other.max
}
}
impl<T: Number + Eq, const D: usize> Eq for Aabb<T, D> {}
impl<T: Number, const D: usize> PartialOrd for Aabb<T, D> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.min.partial_cmp(&other.min) {
Some(Ordering::Equal) => self.max.partial_cmp(&other.max),
ord => return ord,
}
}
}
impl<T: Number + Hash, const D: usize> Hash for Aabb<T, D> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.min.hash(state);
self.max.hash(state);
}
}
// TODO: impl Ord for Aabb
//impl<T: Number + Ord, const D: usize> Ord for Aabb<T, D> {
// fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// match self.min.cmp(&other.min) {
// Ordering::Equal => self.max.cmp(&other.max),
// ord => ord,
// }
// }
//}

View file

@ -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<Self> {
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::*;

View file

@ -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<impl Scalar + AsPrimitive<i32>>) -> Self {
Self(vec.map(|n| n.as_()))
}
pub struct BlockPos {
pub x: i32,
pub y: i32,
pub z: i32,
}
impl<T: Scalar + Into<i32>> From<TVec3<T>> for BlockPos {
fn from(vec: TVec3<T>) -> 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);

View file

@ -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<Chunk>,
}
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<Item = (ChunkId, &Chunk)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (ChunkId(k), v))
}
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkId, &mut Chunk)> + '_ {
self.sm.iter_mut().map(|(k, v)| (ChunkId(k), v))
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkId, &Chunk)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (ChunkId(k), v))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkId, &mut Chunk)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (ChunkId(k), v))
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ChunkId(Key);
pub struct Chunk {
sections: Box<[ChunkSection]>,
// TODO block_entities: HashMap<u32, BlockEntity>,
@ -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(&sect.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<i64>) {
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

View file

@ -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<ChunkId>,
chunks: RwLock<Vec<Chunk>>,
}
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<Z>(&self, z: Z, chunk: ChunkId) -> Option<Z::Item>
where
Z: ZippedComponents<Id = ChunkId>,
{
self.comps.get(z, chunk)
}
pub fn iter<'a, Z>(&'a self, z: Z) -> impl FusedIterator<Item = (ChunkId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = ChunkId> + 'a,
{
self.comps.iter(z)
}
pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator<Item = (ChunkId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = ChunkId> + 'a,
{
self.comps.par_iter(z)
}
pub fn ids(&self) -> impl FusedIterator<Item = ChunkId> + Clone + '_ {
self.comps.ids()
}
pub fn par_ids(&self) -> impl ParallelIterator<Item = ChunkId> + Clone + '_ {
self.comps.par_ids()
}
pub fn chunks(&self) -> Result<Chunks, Error> {
Ok(Chunks {
chunks: self.chunks.try_read().ok_or(Error::NoReadAccess)?,
})
}
pub fn chunks_mut(&self) -> Result<ChunksMut, Error> {
Ok(ChunksMut {
chunks: self.chunks.try_write().ok_or(Error::NoWriteAccess)?,
})
}
pub fn register_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.register_component::<C>();
}
pub fn unregister_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.unregister_component::<C>()
}
pub fn is_registered<C: 'static + Send + Sync + Default>(&self) -> bool {
self.comps.is_registered::<C>()
}
pub fn components<C: 'static + Send + Sync + Default>(
&self,
) -> Result<Components<C, ChunkId>, Error> {
self.comps.components::<C>()
}
pub fn components_mut<C: 'static + Send + Sync + Default>(
&self,
) -> Result<ComponentsMut<C, ChunkId>, Error> {
self.comps.components_mut::<C>()
}
}
#[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<Chunk>>,
}
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<Chunk>>,
}
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;
}

View file

@ -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<Box<Client>>);
pub struct ClientStore {
sm: SlotMap<Client>,
}
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<Item = (ClientId, &Client)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (ClientId(k), v))
}
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client)> + '_ {
self.sm.iter_mut().map(|(k, v)| (ClientId(k), v))
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (ClientId(k), v))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, &mut Client)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v))
}
}
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<Sender<ClientPlayPacket>>,
recv: Receiver<ServerPlayPacket>,
/// The entity this client is associated with.
entity: EntityId,
/// The tick this client was created.
created_tick: Ticks,
username: String,
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<WorldId>,
old_world: Option<WorldId>,
events: Vec<Event>,
/// 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<EntityId>,
/// Loaded chunks and their positions.
loaded_chunks: HashMap<ChunkPos, ChunkId>,
old_game_mode: GameMode,
new_game_mode: GameMode,
old_game_mode: GameMode,
settings: Option<Settings>,
// 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<WorldId> {
self.new_world
}
pub fn set_world(&mut self, new_world: WorldId) {
pub fn set_world(&mut self, new_world: Option<WorldId>) {
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<Sender<ClientPlayPacket>>, pkt: impl Into<C
}
}
fn make_dimension_codec(server: &Server) -> 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));
}

View file

@ -188,23 +188,23 @@ impl<I: Id> ComponentStore<I> {
})
}
pub fn register_component<C: 'static + Send + Sync + Default>(&mut self) {
pub fn register_component<C: 'static + Send + Sync + DefaultPrivate>(&mut self) {
if let Entry::Vacant(ve) = self.components.entry(TypeId::of::<C>()) {
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<C: 'static + Send + Sync + Default>(&mut self) {
pub fn unregister_component<C: 'static + Send + Sync + DefaultPrivate>(&mut self) {
self.components.remove(&TypeId::of::<C>());
}
pub fn is_registered<C: 'static + Send + Sync + Default>(&self) -> bool {
pub fn is_registered<C: 'static + Send + Sync + DefaultPrivate>(&self) -> bool {
self.components.contains_key(&TypeId::of::<C>())
}
pub fn components<C: 'static + Send + Sync + Default>(
pub fn components<C: 'static + Send + Sync + DefaultPrivate>(
&self,
) -> Result<Components<C, I>, Error> {
let handle = self
@ -223,7 +223,7 @@ impl<I: Id> ComponentStore<I> {
})
}
pub fn components_mut<C: 'static + Send + Sync + Default>(
pub fn components_mut<C: 'static + Send + Sync + DefaultPrivate>(
&self,
) -> Result<ComponentsMut<C, I>, Error> {
let handle = self
@ -266,32 +266,30 @@ trait ComponentVec: Any + Send + Sync {
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T: 'static + Send + Sync + Default> ComponentVec for RwLock<Vec<T>> {
impl<T: 'static + Send + Sync + DefaultPrivate> ComponentVec for RwLock<Vec<T>> {
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<C>>,
_marker: PhantomData<fn(I) -> 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<C>>,
_marker: PhantomData<fn(I) -> 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<T: Default> DefaultPrivate for T {
fn default_private() -> Self {
T::default()
}
}
pub trait ZippedComponents: ZippedComponentsRaw<RawItem = Self::Item> {
type Id: Copy;
type Item: Send + Sync;

View file

@ -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<EntityId>,
uuids: Vec<Uuid>,
clients: RwLock<Vec<MaybeClient>>,
appearances: RwLock<Vec<Appearance>>,
old_appearances: Vec<Appearance>,
sm: SlotMap<Entity>,
uuid_to_entity: HashMap<Uuid, EntityId>,
/// Maps chunk positions to the set of all entities with bounding volumes
/// intersecting that chunk.
partition: HashMap<(WorldId, ChunkPos), Vec<EntityId>>,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct EntityId(Key);
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<WorldId>,
old_world: Option<WorldId>,
uuid: Uuid,
}
impl Entity {
pub fn data(&self) -> &EntityData {
&self.data
}
pub fn typ(&self) -> EntityType {
self.data.typ()
}
/// Changes the type of this entity.
pub fn change_type(&mut self, new_type: EntityType) {
todo!(); // TODO
}
fn hitbox(&self) -> Aabb<f64, 3> {
// TODO
Aabb::default()
}
}
pub use types::{EntityData, EntityType};
impl EntityStore {
pub(crate) fn new() -> Self {
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<EntityId> {
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<Appearance>) -> 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<Appearance>,
uuid: Uuid,
) -> Option<EntityId> {
pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option<EntityId> {
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<EntityId> {
self.uuid_to_entity.get(&uuid).cloned()
}
pub fn delete(&mut self, entity: EntityId) -> bool {
if let Some(e) = self.sm.remove(entity.0) {
self.uuid_to_entity
.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<Z>(&self, z: Z, entity: EntityId) -> Option<Z::Item>
where
Z: ZippedComponents<Id = EntityId>,
{
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<Item = (EntityId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = EntityId> + 'a,
{
self.comps.iter(z)
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (EntityId(k), v))
}
pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator<Item = (EntityId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = EntityId> + 'a,
{
self.comps.par_iter(z)
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ {
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
}
pub fn ids(&self) -> impl FusedIterator<Item = EntityId> + Clone + '_ {
self.comps.ids()
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
}
pub fn par_ids(&self) -> impl ParallelIterator<Item = EntityId> + Clone + '_ {
self.comps.par_ids()
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
}
pub fn uuids(&self) -> Uuids {
Uuids { uuids: &self.uuids }
}
pub fn clients(&self) -> Result<Clients, Error> {
Ok(Clients {
clients: self.clients.try_read().ok_or(Error::NoReadAccess)?,
})
}
pub fn clients_mut(&self) -> Result<ClientsMut, Error> {
Ok(ClientsMut {
clients: self.clients.try_write().ok_or(Error::NoWriteAccess)?,
})
}
pub fn appearances(&self) -> Result<Appearances, Error> {
Ok(Appearances {
appearances: self.appearances.try_read().ok_or(Error::NoReadAccess)?,
})
}
pub fn appearances_mut(&self) -> Result<AppearancesMut, Error> {
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<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.register_component::<C>();
}
pub fn unregister_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.unregister_component::<C>()
}
pub fn is_registered<C: 'static + Send + Sync + Default>(&self) -> bool {
self.comps.is_registered::<C>()
}
pub fn components<C: 'static + Send + Sync + Default>(
&self,
) -> Result<Components<C, EntityId>, Error> {
self.comps.components::<C>()
}
pub fn components_mut<C: 'static + Send + Sync + Default>(
&self,
) -> Result<ComponentsMut<C, EntityId>, Error> {
self.comps.components_mut::<C>()
pub(crate) fn from_network_id(&self, network_id: i32) -> Option<EntityId> {
self.sm.key_at_index(network_id as usize).map(EntityId)
}
fn partition_insert(&mut self, entity: EntityId, world: WorldId, aabb: Aabb<f64, 3>) {
@ -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<Uuid>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b Uuids<'a> {
type RawItem = Uuid;
type RawIter = std::iter::Cloned<std::slice::Iter<'b, Uuid>>;
type RawParIter = rayon::iter::Cloned<rayon::slice::Iter<'b, Uuid>>;
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<MaybeClient>>,
}
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<MaybeClient>>,
}
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<Appearance>>,
}
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<Appearance>>,
}
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<Appearance>,
}
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::<Vec<_>>();
let outside = *ids.last().unwrap();
assert!(entities
.intersecting_aabb(
WorldId::NULL,
Aabb::new(glm::vec3(8.0, 8.0, 8.0), glm::vec3(16.0, 16.0, 16.0)),
)
.all(|id| ids.contains(&id) && id != outside));
}
}
//#[cfg(test)]
//mod tests {
// use appearance::Player;
//
// use super::*;
// use crate::glm;
//
// // TODO: better test: spawn a bunch of random entities, spawn a random
// AABB, // assert collides_with_aabb consistency.
//
// #[test]
// fn space_partition() {
// let mut entities = EntityStore::new();
//
// let ids = [(16.0, 16.0, 16.0), (8.0, 8.0, 8.0), (10.0, 50.0, 10.0)]
// .into_iter()
// .map(|(x, y, z)| entities.create(Player::new(glm::vec3(x, y, z),
// WorldId::NULL))) .collect::<Vec<_>>();
//
// let outside = *ids.last().unwrap();
//
// assert!(entities
// .intersecting_aabb(
// WorldId::NULL,
// Aabb::new(glm::vec3(8.0, 8.0, 8.0), glm::vec3(16.0, 16.0,
// 16.0)), )
// .all(|id| ids.contains(&id) && id != outside));
// }
//}

View file

@ -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<DVec3> {
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<WorldId> {
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<Aabb<f64, 3>> {
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<f64, 3> {
// 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<Player> for Appearance {
fn from(p: Player) -> Self {
Self::Player(p)
}
}

207
src/entity/meta.rs Normal file
View file

@ -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<i32>);
impl Encode for OptVarInt {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
match self.0 {
Some(n) => VarInt(
n.checked_add(1)
.context("i32::MAX is unrepresentable as an optional VarInt")?,
)
.encode(w),
None => VarInt(0).encode(w),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Pose {
Standing,
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)
}
}

5
src/entity/types.rs Normal file
View file

@ -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"));

View file

@ -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;
}

View file

@ -1,4 +1,4 @@
//! Contains packet definitions and the types contained within them.
//! Contains packet definitions and some types contained within them.
//!
//! See <https://wiki.vg/Protocol> 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.[<set_ $bit:snake>]($bit);
)*
@ -288,7 +290,7 @@ macro_rules! def_bitfield {
res
}
paste! {
paste::paste! {
$(
#[doc = "Gets the " $bit " bit on this bitfield.\n"]
$(#[$bit_attrs])*

View file

@ -201,7 +201,10 @@ impl Decode for f64 {
impl<T: Encode> Encode for Option<T> {
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),
}
}

View file

@ -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<Dimension>,
biomes: Vec<Biome>,
/// 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<OwnedWriteHalf>, Decoder<OwnedReadHalf>);
/// 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");

301
src/slotmap.rs Normal file
View file

@ -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<T> {
slots: Vec<Slot<T>>,
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<T> {
version: NonZeroU32,
item: Item<T>,
}
#[derive(Clone, Debug)]
enum Item<T> {
Occupied(T),
Vacant { next_free: u32 },
}
impl<T> SlotMap<T> {
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<T> {
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<Key> {
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<Item = (Key, &T)> + 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<Item = (Key, &mut T)> + '_ {
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<T: Sync> SlotMap<T> {
pub fn par_iter(&self) -> impl ParallelIterator<Item = (Key, &T)> + 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<T: Send + Sync> SlotMap<T> {
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (Key, &mut T)> + '_ {
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<T> Default for SlotMap<T> {
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);
}
}

View file

@ -83,8 +83,8 @@ pub struct Text {
/// Provides the methods necessary for working with [`Text`] objects.
///
/// This trait exists to allow using `Into<Text>` impls without having to first
/// convert `Self` into [`Text`]. It is automatically implemented for all
/// This trait exists to allow using `Into<Text>` types without having to first
/// convert the type into [`Text`]. It is automatically implemented for all
/// `Into<Text>` types, including [`Text`] itself.
pub trait TextFormat: Into<Text> {
fn into_text(self) -> Text {
@ -347,6 +347,8 @@ impl Text {
Ok(())
}
// TODO: getters
}
impl<T: Into<Text>> TextFormat for T {}

View file

@ -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<WorldId>,
worlds: RwLock<Vec<World>>,
shared: SharedServer,
sm: SlotMap<World>,
}
#[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<Z>(&self, z: Z, world: WorldId) -> Option<Z::Item>
where
Z: ZippedComponents<Id = WorldId>,
{
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<Item = (WorldId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = WorldId> + '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<Item = (WorldId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = WorldId> + 'a,
{
self.comps.par_iter(z)
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (WorldId(k), v))
}
pub fn ids(&self) -> impl FusedIterator<Item = WorldId> + Clone + '_ {
self.comps.ids()
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ {
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v))
}
pub fn par_ids(&self) -> impl ParallelIterator<Item = WorldId> + Clone + '_ {
self.comps.par_ids()
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (WorldId(k), v))
}
pub fn worlds(&self) -> Result<Worlds, Error> {
Ok(Worlds {
worlds: self.worlds.try_read().ok_or(Error::NoReadAccess)?,
})
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
}
pub fn worlds_mut(&self) -> Result<WorldsMut, Error> {
Ok(WorldsMut {
worlds: self.worlds.try_write().ok_or(Error::NoWriteAccess)?,
})
}
pub fn register_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.register_component::<C>();
}
pub fn unregister_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.unregister_component::<C>()
}
pub fn is_registered<C: 'static + Send + Sync + Default>(&self) -> bool {
self.comps.is_registered::<C>()
}
pub fn components<C: 'static + Send + Sync + Default>(
&self,
) -> Result<Components<C, WorldId>, Error> {
self.comps.components::<C>()
}
pub fn components_mut<C: 'static + Send + Sync + Default>(
&self,
) -> Result<ComponentsMut<C, WorldId>, Error> {
self.comps.components_mut::<C>()
}
}
#[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<World>>,
}
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<World>>,
}
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 {