mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 14:31:30 +11:00
Rip out the ECS.
This commit is contained in:
parent
5f2389f0e7
commit
732183dd62
|
@ -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 {
|
||||
|
|
547
build/entity.rs
547
build/entity.rs
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
1019
data/entities.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
50
src/aabb.rs
50
src/aabb.rs
|
@ -1,14 +1,19 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::glm::{self, Number, RealNumber, TVec};
|
||||
|
||||
/// An Axis-aligned bounding box in an arbitrary dimension.
|
||||
#[derive(Clone, Copy, Debug)] // TODO: impl PartialEq, Eq, PartialOrd, Ord, Hash
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Aabb<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,
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
21
src/block.rs
21
src/block.rs
|
@ -1,6 +1,12 @@
|
|||
#![allow(clippy::all)]
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::protocol::{Decode, Encode};
|
||||
use crate::var_int::VarInt;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/block.rs"));
|
||||
|
||||
|
@ -32,6 +38,21 @@ impl fmt::Debug for BlockState {
|
|||
}
|
||||
}
|
||||
|
||||
impl Encode for BlockState {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(self.0 as i32).encode(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for BlockState {
|
||||
fn decode(r: &mut impl Read) -> anyhow::Result<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::*;
|
||||
|
|
|
@ -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);
|
||||
|
|
86
src/chunk.rs
86
src/chunk.rs
|
@ -1,19 +1,75 @@
|
|||
// TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil
|
||||
|
||||
use std::io::Write;
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use bitvec::bitvec;
|
||||
use bitvec::vec::BitVec;
|
||||
use num::Integer;
|
||||
use rayon::iter::ParallelIterator;
|
||||
|
||||
use crate::glm::DVec2;
|
||||
use crate::packets::play::{
|
||||
BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange,
|
||||
};
|
||||
use crate::protocol::{Encode, Nbt};
|
||||
use crate::slotmap::{Key, SlotMap};
|
||||
use crate::var_int::VarInt;
|
||||
use crate::BiomeId;
|
||||
|
||||
pub struct ChunkStore {
|
||||
sm: SlotMap<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(§.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
|
||||
|
|
|
@ -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;
|
||||
}
|
548
src/client.rs
548
src/client.rs
|
@ -1,11 +1,13 @@
|
|||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use flume::{Receiver, Sender, TrySendError};
|
||||
use glm::DVec3;
|
||||
use rayon::iter::ParallelIterator;
|
||||
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::chunk_store::ChunkId;
|
||||
use crate::chunk::ChunkId;
|
||||
use crate::config::{
|
||||
Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId,
|
||||
};
|
||||
|
@ -18,51 +20,82 @@ use crate::packets::play::{
|
|||
ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition,
|
||||
};
|
||||
use crate::protocol::{BoundedInt, Nbt};
|
||||
use crate::server::ServerPacketChannels;
|
||||
use crate::server::{Other, ServerPacketChannels};
|
||||
use crate::slotmap::{Key, SlotMap};
|
||||
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
|
||||
use crate::var_int::VarInt;
|
||||
use crate::world::WorldId;
|
||||
use crate::{glm, ident, ChunkPos, EntityId, Server, SharedServer, Text, Ticks, LIBRARY_NAMESPACE};
|
||||
use crate::{
|
||||
glm, ident, ChunkPos, ChunkStore, EntityId, EntityStore, Server, SharedServer, Text, Ticks,
|
||||
WorldStore, LIBRARY_NAMESPACE,
|
||||
};
|
||||
|
||||
pub struct MaybeClient(pub(crate) Option<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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
590
src/entity.rs
590
src/entity.rs
|
@ -1,65 +1,98 @@
|
|||
pub mod meta;
|
||||
pub mod types;
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||
use rayon::iter::ParallelIterator;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::chunk::ChunkPos;
|
||||
use crate::client::MaybeClient;
|
||||
use crate::component::{
|
||||
ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents,
|
||||
ZippedComponentsRaw,
|
||||
};
|
||||
use crate::{Aabb, WorldId};
|
||||
|
||||
pub mod appearance;
|
||||
|
||||
pub use appearance::Appearance;
|
||||
use crate::glm::DVec3;
|
||||
use crate::slotmap::{Key, SlotMap};
|
||||
use crate::{Aabb, Id, WorldId};
|
||||
|
||||
pub struct EntityStore {
|
||||
comps: ComponentStore<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));
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -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
207
src/entity/meta.rs
Normal 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
5
src/entity/types.rs
Normal 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"));
|
44
src/lib.rs
44
src/lib.rs
|
@ -1,12 +1,19 @@
|
|||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_lifetimes,
|
||||
unused_import_braces,
|
||||
missing_docs
|
||||
)]
|
||||
|
||||
mod aabb;
|
||||
pub mod block;
|
||||
mod block_pos;
|
||||
mod byte_angle;
|
||||
mod chunk;
|
||||
mod chunk_store;
|
||||
mod client;
|
||||
pub mod chunk;
|
||||
pub mod client;
|
||||
mod codec;
|
||||
pub mod component;
|
||||
pub mod config;
|
||||
|
@ -14,23 +21,25 @@ pub mod entity;
|
|||
pub mod identifier;
|
||||
mod packets;
|
||||
mod protocol;
|
||||
mod server;
|
||||
pub mod server;
|
||||
mod slotmap;
|
||||
pub mod text;
|
||||
pub mod util;
|
||||
mod var_int;
|
||||
mod var_long;
|
||||
mod world;
|
||||
pub mod world;
|
||||
|
||||
pub use aabb::Aabb;
|
||||
pub use chunk::{Chunk, ChunkPos};
|
||||
pub use client::Client;
|
||||
pub use block_pos::BlockPos;
|
||||
pub use chunk::{Chunk, ChunkPos, ChunkStore};
|
||||
pub use client::{Client, ClientStore};
|
||||
pub use config::{BiomeId, DimensionId, ServerConfig};
|
||||
pub use entity::{EntityId, EntityStore};
|
||||
pub use entity::{Entity, EntityId, EntityStore};
|
||||
pub use identifier::Identifier;
|
||||
pub use text::{Text, TextFormat};
|
||||
pub use uuid::Uuid;
|
||||
pub use world::{World, WorldId};
|
||||
pub use {nalgebra_glm as glm, nbt};
|
||||
pub use world::{World, WorldId, WorldStore};
|
||||
pub use {nalgebra_glm as glm, nbt, uuid};
|
||||
|
||||
pub use crate::server::{NewClientData, Server, SharedServer, ShutdownResult};
|
||||
|
||||
|
@ -48,3 +57,18 @@ const LIBRARY_NAMESPACE: &str = "valence";
|
|||
/// The duration of a game update depends on the current configuration, which
|
||||
/// may or may not be the same as Minecraft's standard 20 ticks/second.
|
||||
pub type Ticks = i64;
|
||||
|
||||
/// Types such as [`EntityId`], [`WorldId`], and [`ChunkId`] which can be used
|
||||
/// as indices into an array.
|
||||
///
|
||||
/// Every ID is either valid or invalid. Valid IDs point to living values. For
|
||||
/// instance, a valid [`EntityId`] points to a living entity on the server. When
|
||||
/// that entity is deleted, the corresponding [`EntityId`] becomes invalid.
|
||||
pub trait Id: Copy + Send + Sync + PartialEq + Eq {
|
||||
/// Returns the index of this ID.
|
||||
///
|
||||
/// For all IDs `a` and `b`, `a == b` implies `a.idx() == b.idx()`. If
|
||||
/// both `a` and `b` are currently valid, then `a != b` implies `a.idx() !=
|
||||
/// b.idx()`.
|
||||
fn idx(self) -> usize;
|
||||
}
|
||||
|
|
|
@ -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])*
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
301
src/slotmap.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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 {}
|
||||
|
|
232
src/world.rs
232
src/world.rs
|
@ -1,234 +1,78 @@
|
|||
use std::collections::HashMap;
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||
use rayon::iter::ParallelIterator;
|
||||
|
||||
use crate::chunk::ChunkPos;
|
||||
use crate::chunk_store::ChunkId;
|
||||
use crate::component::{
|
||||
ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents,
|
||||
ZippedComponentsRaw,
|
||||
};
|
||||
use crate::chunk::{ChunkId, ChunkPos};
|
||||
use crate::config::DimensionId;
|
||||
use crate::SharedServer;
|
||||
use crate::slotmap::{Key, SlotMap};
|
||||
use crate::Id;
|
||||
|
||||
pub struct WorldStore {
|
||||
comps: ComponentStore<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 {
|
||||
|
|
Loading…
Reference in a new issue