mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-11 07:11: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.
|
// TODO: can't match on str in const fn.
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::path::Path;
|
|
||||||
use std::process::Command;
|
|
||||||
use std::{env, fs};
|
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use heck::{ToPascalCase, ToShoutySnakeCase};
|
use heck::{ToPascalCase, ToShoutySnakeCase};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::ident;
|
use crate::{ident, write_to_out_path};
|
||||||
|
|
||||||
pub fn build() -> anyhow::Result<()> {
|
pub fn build() -> anyhow::Result<()> {
|
||||||
let blocks = parse_blocks_json()?;
|
let blocks = parse_blocks_json()?;
|
||||||
|
@ -49,7 +45,7 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
.map(|p| p.vals.len() as u16)
|
.map(|p| p.vals.len() as u16)
|
||||||
.product();
|
.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 arms = p.vals.iter().enumerate().map(|(i, v)| {
|
||||||
let value_idx = i as u16;
|
let value_idx = i as u16;
|
||||||
|
@ -60,7 +56,7 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
}).collect::<TokenStream>();
|
}).collect::<TokenStream>();
|
||||||
|
|
||||||
quote! {
|
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
|
#arms
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
|
@ -96,7 +92,7 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
.map(|p| p.vals.len() as u16)
|
.map(|p| p.vals.len() as u16)
|
||||||
.product();
|
.product();
|
||||||
|
|
||||||
let num_values = p.vals.len() as u16;
|
let values_count = p.vals.len() as u16;
|
||||||
|
|
||||||
let arms = p
|
let arms = p
|
||||||
.vals
|
.vals
|
||||||
|
@ -107,7 +103,7 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
let val_name = ident(v.to_pascal_case());
|
let val_name = ident(v.to_pascal_case());
|
||||||
quote! {
|
quote! {
|
||||||
PropValue::#val_name =>
|
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),
|
+ #val_idx * #product),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -213,7 +209,7 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
})
|
})
|
||||||
.collect::<TokenStream>();
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
let num_block_types = blocks.len();
|
let block_type_count = blocks.len();
|
||||||
|
|
||||||
let prop_names = blocks
|
let prop_names = blocks
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -245,7 +241,7 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
})
|
})
|
||||||
.collect::<TokenStream>();
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
let num_prop_names = prop_names.len();
|
let prop_name_count = prop_names.len();
|
||||||
|
|
||||||
let prop_values = blocks
|
let prop_values = blocks
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -299,9 +295,9 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
})
|
})
|
||||||
.collect::<TokenStream>();
|
.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
|
/// 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.
|
/// 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)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||||
|
@ -432,7 +428,7 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An array of all block types.
|
/// 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`.
|
/// The default block type is `air`.
|
||||||
|
@ -469,7 +465,7 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An array of all property names.
|
/// 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.
|
/// 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.
|
/// 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 {
|
impl From<bool> for PropValue {
|
||||||
|
@ -544,16 +540,7 @@ pub fn build() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let out_path =
|
write_to_out_path("block.rs", &finished.to_string())
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Block {
|
struct Block {
|
||||||
|
|
547
build/entity.rs
547
build/entity.rs
|
@ -1,6 +1,14 @@
|
||||||
//! See: <https://wiki.vg/Entity_metadata>
|
//! 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 {
|
struct Class {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
@ -24,72 +32,70 @@ enum Type {
|
||||||
OptText(Option<&'static str>),
|
OptText(Option<&'static str>),
|
||||||
Slot,
|
Slot,
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Rotations(f32, f32, f32),
|
ArmorStandRotations(f32, f32, f32),
|
||||||
BlockPos(i32, i32, i32),
|
BlockPos(i32, i32, i32),
|
||||||
OptPosition(Option<(i32, i32, i32)>),
|
OptBlockPos(Option<(i32, i32, i32)>),
|
||||||
Direction(Direction),
|
Direction,
|
||||||
OptUuid,
|
OptUuid,
|
||||||
OptBlockId,
|
BlockState,
|
||||||
Nbt,
|
Nbt,
|
||||||
Particle,
|
Particle,
|
||||||
VillagerData,
|
VillagerData,
|
||||||
OptVarInt,
|
OptVarInt,
|
||||||
Pose,
|
Pose,
|
||||||
OptBlockPos, // TODO: What is this type?
|
|
||||||
// ==== Specialized ==== //
|
// ==== Specialized ==== //
|
||||||
OptEntityId,
|
OptEntityId,
|
||||||
BoatType,
|
BoatVariant,
|
||||||
MainHand,
|
MainHand,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Direction {
|
|
||||||
Down,
|
|
||||||
Up,
|
|
||||||
North,
|
|
||||||
South,
|
|
||||||
West,
|
|
||||||
East,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BitField {
|
struct BitField {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
offset: u8,
|
offset: u8,
|
||||||
|
default: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const BASE_ENTITY: Class = Class {
|
const BASE_ENTITY: Class = Class {
|
||||||
name: "entity_base",
|
name: "base_entity",
|
||||||
inherit: None,
|
inherit: None,
|
||||||
fields: &[
|
fields: &[
|
||||||
Field {
|
Field {
|
||||||
name: "entity_base_bits",
|
name: "base_entity_bits",
|
||||||
typ: Type::BitFields(&[
|
typ: Type::BitFields(&[
|
||||||
BitField {
|
BitField {
|
||||||
name: "on_fire",
|
name: "on_fire",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "crouching",
|
name: "crouching",
|
||||||
offset: 1,
|
offset: 1,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "sprinting",
|
name: "sprinting",
|
||||||
offset: 3, // Skipping unused field
|
offset: 3, // Skipping unused
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "swimming",
|
name: "swimming",
|
||||||
offset: 4,
|
offset: 4,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "invisible",
|
name: "invisible",
|
||||||
offset: 5,
|
offset: 5,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "glowing",
|
name: "glowing",
|
||||||
offset: 6,
|
offset: 6,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "elytra_flying",
|
name: "elytra_flying",
|
||||||
offset: 7,
|
offset: 7,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
@ -134,10 +140,12 @@ const ABSTRACT_ARROW: Class = Class {
|
||||||
BitField {
|
BitField {
|
||||||
name: "critical",
|
name: "critical",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "noclip",
|
name: "noclip",
|
||||||
offset: 1,
|
offset: 1,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
@ -158,14 +166,17 @@ const LIVING_ENTITY: Class = Class {
|
||||||
BitField {
|
BitField {
|
||||||
name: "hand_active",
|
name: "hand_active",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "active_hand",
|
name: "active_hand",
|
||||||
offset: 1,
|
offset: 1,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "riptide_spin_attack",
|
name: "riptide_spin_attack",
|
||||||
offset: 2,
|
offset: 2,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
@ -191,7 +202,7 @@ const LIVING_ENTITY: Class = Class {
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "bed_sleeping_position",
|
name: "bed_sleeping_position",
|
||||||
typ: Type::OptBlockPos,
|
typ: Type::OptBlockPos(None),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -205,14 +216,17 @@ const MOB: Class = Class {
|
||||||
BitField {
|
BitField {
|
||||||
name: "ai_disabled",
|
name: "ai_disabled",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "left_handed",
|
name: "left_handed",
|
||||||
offset: 1,
|
offset: 1,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "aggressive",
|
name: "aggressive",
|
||||||
offset: 2,
|
offset: 2,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}],
|
}],
|
||||||
|
@ -270,26 +284,32 @@ const ABSTRACT_HORSE: Class = Class {
|
||||||
BitField {
|
BitField {
|
||||||
name: "tame",
|
name: "tame",
|
||||||
offset: 1, // Skip unused
|
offset: 1, // Skip unused
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "saddled",
|
name: "saddled",
|
||||||
offset: 2,
|
offset: 2,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "bred",
|
name: "bred",
|
||||||
offset: 3,
|
offset: 3,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "eating",
|
name: "eating",
|
||||||
offset: 4,
|
offset: 4,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "rearing",
|
name: "rearing",
|
||||||
offset: 5,
|
offset: 5,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "mouth_open",
|
name: "mouth_open",
|
||||||
offset: 6,
|
offset: 6,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
@ -324,10 +344,12 @@ const TAMEABLE_ANIMAL: Class = Class {
|
||||||
BitField {
|
BitField {
|
||||||
name: "sitting",
|
name: "sitting",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "tamed",
|
name: "tamed",
|
||||||
offset: 2, // Skip unused.
|
offset: 2, // Skip unused.
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}],
|
}],
|
||||||
|
@ -417,6 +439,7 @@ const SPIDER: Class = Class {
|
||||||
typ: Type::BitFields(&[BitField {
|
typ: Type::BitFields(&[BitField {
|
||||||
name: "climbing",
|
name: "climbing",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
}]),
|
}]),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
@ -474,6 +497,18 @@ const ABSTRACT_MINECART_CONTAINER: Class = Class {
|
||||||
};
|
};
|
||||||
|
|
||||||
const ENTITIES: &[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 {
|
Class {
|
||||||
name: "experience_orb",
|
name: "experience_orb",
|
||||||
inherit: None,
|
inherit: None,
|
||||||
|
@ -485,7 +520,17 @@ const ENTITIES: &[Class] = &[
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
Class {
|
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),
|
inherit: Some(&BASE_ENTITY),
|
||||||
fields: &[Field {
|
fields: &[Field {
|
||||||
name: "item",
|
name: "item",
|
||||||
|
@ -493,7 +538,7 @@ const ENTITIES: &[Class] = &[
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "thrown_ender_pearl",
|
name: "ender_pearl",
|
||||||
inherit: Some(&BASE_ENTITY),
|
inherit: Some(&BASE_ENTITY),
|
||||||
fields: &[Field {
|
fields: &[Field {
|
||||||
name: "item",
|
name: "item",
|
||||||
|
@ -501,7 +546,7 @@ const ENTITIES: &[Class] = &[
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "thrown_experience_bottle",
|
name: "experience_bottle",
|
||||||
inherit: Some(&BASE_ENTITY),
|
inherit: Some(&BASE_ENTITY),
|
||||||
fields: &[Field {
|
fields: &[Field {
|
||||||
name: "item",
|
name: "item",
|
||||||
|
@ -509,7 +554,7 @@ const ENTITIES: &[Class] = &[
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "thrown_potion",
|
name: "potion",
|
||||||
inherit: Some(&BASE_ENTITY),
|
inherit: Some(&BASE_ENTITY),
|
||||||
fields: &[Field {
|
fields: &[Field {
|
||||||
name: "potion",
|
name: "potion",
|
||||||
|
@ -563,7 +608,7 @@ const ENTITIES: &[Class] = &[
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "fishing_hook",
|
name: "fishing_bobber",
|
||||||
inherit: Some(&BASE_ENTITY),
|
inherit: Some(&BASE_ENTITY),
|
||||||
fields: &[
|
fields: &[
|
||||||
Field {
|
Field {
|
||||||
|
@ -590,7 +635,7 @@ const ENTITIES: &[Class] = &[
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "thrown_trident",
|
name: "trident",
|
||||||
inherit: Some(&ABSTRACT_ARROW),
|
inherit: Some(&ABSTRACT_ARROW),
|
||||||
fields: &[
|
fields: &[
|
||||||
Field {
|
Field {
|
||||||
|
@ -621,7 +666,7 @@ const ENTITIES: &[Class] = &[
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "typ",
|
name: "typ",
|
||||||
typ: Type::BoatType,
|
typ: Type::BoatVariant,
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "left_paddle_turning",
|
name: "left_paddle_turning",
|
||||||
|
@ -643,7 +688,7 @@ const ENTITIES: &[Class] = &[
|
||||||
fields: &[
|
fields: &[
|
||||||
Field {
|
Field {
|
||||||
name: "beam_target",
|
name: "beam_target",
|
||||||
typ: Type::OptBlockPos,
|
typ: Type::OptBlockPos(None),
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "show_bottom",
|
name: "show_bottom",
|
||||||
|
@ -745,30 +790,37 @@ const ENTITIES: &[Class] = &[
|
||||||
BitField {
|
BitField {
|
||||||
name: "cape_enabled",
|
name: "cape_enabled",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "jacket_enabled",
|
name: "jacket_enabled",
|
||||||
offset: 1,
|
offset: 1,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "left_sleeve_enabled",
|
name: "left_sleeve_enabled",
|
||||||
offset: 2,
|
offset: 2,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "right_sleeve_enabled",
|
name: "right_sleeve_enabled",
|
||||||
offset: 3,
|
offset: 3,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "left_pants_leg_enabled",
|
name: "left_pants_leg_enabled",
|
||||||
offset: 4,
|
offset: 4,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "right_pants_leg_enabled",
|
name: "right_pants_leg_enabled",
|
||||||
offset: 5,
|
offset: 5,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "hat_enabled",
|
name: "hat_enabled",
|
||||||
offset: 6,
|
offset: 6,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
@ -796,44 +848,48 @@ const ENTITIES: &[Class] = &[
|
||||||
BitField {
|
BitField {
|
||||||
name: "small",
|
name: "small",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "has_arms",
|
name: "has_arms",
|
||||||
offset: 1,
|
offset: 1,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "no_baseplate",
|
name: "no_baseplate",
|
||||||
offset: 2,
|
offset: 2,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "is_marker",
|
name: "is_marker",
|
||||||
offset: 3,
|
offset: 3,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "head_rotation",
|
name: "head_rotation",
|
||||||
typ: Type::Rotations(0.0, 0.0, 0.0),
|
typ: Type::ArmorStandRotations(0.0, 0.0, 0.0),
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "body_rotation",
|
name: "body_rotation",
|
||||||
typ: Type::Rotations(0.0, 0.0, 0.0),
|
typ: Type::ArmorStandRotations(0.0, 0.0, 0.0),
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "left_arm_rotation",
|
name: "left_arm_rotation",
|
||||||
typ: Type::Rotations(-10.0, 0.0, -10.0),
|
typ: Type::ArmorStandRotations(-10.0, 0.0, -10.0),
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "right_arm_rotation",
|
name: "right_arm_rotation",
|
||||||
typ: Type::Rotations(-15.0, 0.0, -10.0),
|
typ: Type::ArmorStandRotations(-15.0, 0.0, -10.0),
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "left_leg_rotation",
|
name: "left_leg_rotation",
|
||||||
typ: Type::Rotations(-1.0, 0.0, -1.0),
|
typ: Type::ArmorStandRotations(-1.0, 0.0, -1.0),
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "right_leg_rotation",
|
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 {
|
typ: Type::BitFields(&[BitField {
|
||||||
name: "hanging",
|
name: "hanging",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
}]),
|
}]),
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
@ -883,7 +940,7 @@ const ENTITIES: &[Class] = &[
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "puffer_fish",
|
name: "pufferfish",
|
||||||
inherit: Some(&ABSTRACT_FISH),
|
inherit: Some(&ABSTRACT_FISH),
|
||||||
fields: &[Field {
|
fields: &[Field {
|
||||||
name: "puff_state",
|
name: "puff_state",
|
||||||
|
@ -982,14 +1039,17 @@ const ENTITIES: &[Class] = &[
|
||||||
BitField {
|
BitField {
|
||||||
name: "angry",
|
name: "angry",
|
||||||
offset: 1, // Skip unused.
|
offset: 1, // Skip unused.
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "stung",
|
name: "stung",
|
||||||
offset: 2,
|
offset: 2,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "nectar",
|
name: "nectar",
|
||||||
offset: 3,
|
offset: 3,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
@ -1013,10 +1073,37 @@ const ENTITIES: &[Class] = &[
|
||||||
BitField {
|
BitField {
|
||||||
name: "sitting",
|
name: "sitting",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "",
|
name: "fox_crouching",
|
||||||
offset: 2, // Skip unused
|
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 {
|
BitField {
|
||||||
name: "sneezing",
|
name: "sneezing",
|
||||||
offset: 1, // Skip unused.
|
offset: 1, // Skip unused.
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "rolling",
|
name: "rolling",
|
||||||
offset: 2,
|
offset: 2,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "sitting",
|
name: "sitting",
|
||||||
offset: 3,
|
offset: 3,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
BitField {
|
BitField {
|
||||||
name: "on_back",
|
name: "on_back",
|
||||||
offset: 4,
|
offset: 4,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
@ -1176,6 +1267,11 @@ const ENTITIES: &[Class] = &[
|
||||||
typ: Type::Byte(0), // TODO: sheep state type.
|
typ: Type::Byte(0), // TODO: sheep state type.
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
Class {
|
||||||
|
name: "goat",
|
||||||
|
inherit: Some(&ANIMAL),
|
||||||
|
fields: &[], // TODO: What are the goat fields?
|
||||||
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "strider",
|
name: "strider",
|
||||||
inherit: Some(&ANIMAL),
|
inherit: Some(&ANIMAL),
|
||||||
|
@ -1263,6 +1359,7 @@ const ENTITIES: &[Class] = &[
|
||||||
typ: Type::BitFields(&[BitField {
|
typ: Type::BitFields(&[BitField {
|
||||||
name: "player_created",
|
name: "player_created",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
}]),
|
}]),
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
@ -1274,7 +1371,7 @@ const ENTITIES: &[Class] = &[
|
||||||
typ: Type::BitFields(&[BitField {
|
typ: Type::BitFields(&[BitField {
|
||||||
name: "pumpkin_hat",
|
name: "pumpkin_hat",
|
||||||
offset: 4,
|
offset: 4,
|
||||||
// TODO: should default to true.
|
default: true,
|
||||||
}]),
|
}]),
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
@ -1284,11 +1381,11 @@ const ENTITIES: &[Class] = &[
|
||||||
fields: &[
|
fields: &[
|
||||||
Field {
|
Field {
|
||||||
name: "attach_face",
|
name: "attach_face",
|
||||||
typ: Type::Direction(Direction::Down),
|
typ: Type::Direction,
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "attachment_position",
|
name: "attachment_position",
|
||||||
typ: Type::OptPosition(None),
|
typ: Type::OptBlockPos(None),
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "shield_height",
|
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 {
|
Class {
|
||||||
name: "piglin",
|
name: "piglin",
|
||||||
inherit: Some(&BASE_PIGLIN),
|
inherit: Some(&BASE_PIGLIN),
|
||||||
|
@ -1331,6 +1434,7 @@ const ENTITIES: &[Class] = &[
|
||||||
typ: Type::BitFields(&[BitField {
|
typ: Type::BitFields(&[BitField {
|
||||||
name: "blaze_on_fire", // TODO: better name for this?
|
name: "blaze_on_fire", // TODO: better name for this?
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
}]),
|
}]),
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
@ -1402,7 +1506,7 @@ const ENTITIES: &[Class] = &[
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "evoker_fanges",
|
name: "evoker_fangs",
|
||||||
inherit: Some(&BASE_ENTITY),
|
inherit: Some(&BASE_ENTITY),
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
|
@ -1422,6 +1526,7 @@ const ENTITIES: &[Class] = &[
|
||||||
typ: Type::BitFields(&[BitField {
|
typ: Type::BitFields(&[BitField {
|
||||||
name: "attacking",
|
name: "attacking",
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
default: false,
|
||||||
}]),
|
}]),
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
@ -1513,7 +1618,7 @@ const ENTITIES: &[Class] = &[
|
||||||
fields: &[
|
fields: &[
|
||||||
Field {
|
Field {
|
||||||
name: "carried_block",
|
name: "carried_block",
|
||||||
typ: Type::OptBlockId,
|
typ: Type::BlockState,
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
name: "screaming",
|
name: "screaming",
|
||||||
|
@ -1557,6 +1662,11 @@ const ENTITIES: &[Class] = &[
|
||||||
typ: Type::VarInt(1), // TODO: bounds?
|
typ: Type::VarInt(1), // TODO: bounds?
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
Class {
|
||||||
|
name: "magma_cube",
|
||||||
|
inherit: Some(&MOB),
|
||||||
|
fields: &[], // TODO: what are the fields?
|
||||||
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "llama_spit",
|
name: "llama_spit",
|
||||||
inherit: Some(&BASE_ENTITY),
|
inherit: Some(&BASE_ENTITY),
|
||||||
|
@ -1568,17 +1678,17 @@ const ENTITIES: &[Class] = &[
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "minecart_hopper",
|
name: "hopper_minecart",
|
||||||
inherit: Some(&ABSTRACT_MINECART_CONTAINER),
|
inherit: Some(&ABSTRACT_MINECART_CONTAINER),
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "minecart_chest",
|
name: "chest_minecart",
|
||||||
inherit: Some(&ABSTRACT_MINECART_CONTAINER),
|
inherit: Some(&ABSTRACT_MINECART_CONTAINER),
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "minecart_furnace",
|
name: "furnace_minecart",
|
||||||
inherit: Some(&ABSTRACT_MINECART),
|
inherit: Some(&ABSTRACT_MINECART),
|
||||||
fields: &[Field {
|
fields: &[Field {
|
||||||
name: "has_fuel",
|
name: "has_fuel",
|
||||||
|
@ -1586,17 +1696,17 @@ const ENTITIES: &[Class] = &[
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "minecart_tnt",
|
name: "tnt_minecart",
|
||||||
inherit: Some(&ABSTRACT_MINECART),
|
inherit: Some(&ABSTRACT_MINECART),
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "minecart_spawner",
|
name: "spawner_minecart",
|
||||||
inherit: Some(&ABSTRACT_MINECART),
|
inherit: Some(&ABSTRACT_MINECART),
|
||||||
fields: &[],
|
fields: &[],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "minecart_command_block",
|
name: "command_block_minecart",
|
||||||
inherit: Some(&ABSTRACT_MINECART),
|
inherit: Some(&ABSTRACT_MINECART),
|
||||||
fields: &[
|
fields: &[
|
||||||
Field {
|
Field {
|
||||||
|
@ -1610,7 +1720,7 @@ const ENTITIES: &[Class] = &[
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Class {
|
Class {
|
||||||
name: "primed_tnt",
|
name: "tnt",
|
||||||
inherit: Some(&BASE_ENTITY),
|
inherit: Some(&BASE_ENTITY),
|
||||||
fields: &[Field {
|
fields: &[Field {
|
||||||
name: "fuse_timer",
|
name: "fuse_timer",
|
||||||
|
@ -1620,5 +1730,344 @@ const ENTITIES: &[Class] = &[
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn build() -> anyhow::Result<()> {
|
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};
|
use proc_macro2::{Ident, Span};
|
||||||
|
|
||||||
mod block;
|
mod block;
|
||||||
|
mod entity;
|
||||||
|
|
||||||
pub fn main() -> anyhow::Result<()> {
|
pub fn main() -> anyhow::Result<()> {
|
||||||
for file in ["blocks.json"] {
|
for file in ["blocks.json", "entities.json"] {
|
||||||
println!("cargo:rerun-if-changed=data/{file}");
|
println!("cargo:rerun-if-changed=data/{file}");
|
||||||
}
|
}
|
||||||
|
|
||||||
block::build()?;
|
block::build()?;
|
||||||
|
entity::build()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -20,3 +27,15 @@ fn ident(s: impl AsRef<str>) -> Ident {
|
||||||
Ident::new(s, Span::call_site())
|
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]
|
#[async_trait]
|
||||||
impl Handler for Game {
|
impl Handler for Game {
|
||||||
fn init(&self, server: &mut Server) {
|
fn init(&self, server: &mut Server) {
|
||||||
let id = server.worlds.create(DimensionId::default());
|
let world_id = server.worlds.create(DimensionId::default());
|
||||||
let mut worlds = server.worlds.worlds_mut().unwrap();
|
let world = server.worlds.get_mut(world_id).unwrap();
|
||||||
let world = server.worlds.get(&mut worlds, id).unwrap();
|
|
||||||
|
|
||||||
let chunk_radius = 5;
|
let chunk_radius = 5;
|
||||||
|
|
||||||
for z in -chunk_radius..chunk_radius {
|
for z in -chunk_radius..chunk_radius {
|
||||||
for x in -chunk_radius..chunk_radius {
|
for x in -chunk_radius..chunk_radius {
|
||||||
let id = server.chunks.create(384);
|
let chunk_id = server.chunks.create(384);
|
||||||
let mut chunks = server.chunks.chunks_mut().unwrap();
|
let chunk = server.chunks.get_mut(chunk_id).unwrap();
|
||||||
|
|
||||||
let chunk = server.chunks.get(&mut chunks, id).unwrap();
|
|
||||||
|
|
||||||
for z in 0..16 {
|
for z in 0..16 {
|
||||||
for x 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) {
|
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();
|
server.clients.retain(|_, client| {
|
||||||
|
if client.created_tick() == server.other.current_tick() {
|
||||||
let mut to_remove = Vec::new();
|
client.set_world(Some(world_id));
|
||||||
|
|
||||||
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);
|
client.teleport(glm::vec3(0.0, 200.0, 0.0), 0.0, 0.0);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if client.is_disconnected() {
|
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};
|
use crate::glm::{self, Number, RealNumber, TVec};
|
||||||
|
|
||||||
/// An Axis-aligned bounding box in an arbitrary dimension.
|
/// 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> {
|
pub struct Aabb<T, const D: usize> {
|
||||||
min: TVec<T, D>,
|
min: TVec<T, D>,
|
||||||
max: TVec<T, D>,
|
max: TVec<T, D>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Number, const D: usize> Aabb<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 {
|
Self {
|
||||||
min: glm::min2(&p0, &p1),
|
min: glm::min2(&p0, &p1),
|
||||||
max: glm::max2(&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))
|
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)]
|
#![allow(clippy::all)]
|
||||||
|
|
||||||
use std::fmt;
|
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"));
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,41 +1,33 @@
|
||||||
// TODO: rename to BlockPos and represent internally as [i32; 3].
|
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use glm::{IVec3, Scalar, TVec3};
|
|
||||||
use num::cast::AsPrimitive;
|
|
||||||
|
|
||||||
use crate::glm;
|
|
||||||
use crate::protocol::{Decode, Encode};
|
use crate::protocol::{Decode, Encode};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct BlockPos(pub IVec3);
|
pub struct BlockPos {
|
||||||
|
pub x: i32,
|
||||||
impl BlockPos {
|
pub y: i32,
|
||||||
pub fn new(x: i32, y: i32, z: i32) -> Self {
|
pub z: i32,
|
||||||
Self(glm::vec3(x, y, z))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_vec3(vec: TVec3<impl Scalar + AsPrimitive<i32>>) -> Self {
|
|
||||||
Self(vec.map(|n| n.as_()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Scalar + Into<i32>> From<TVec3<T>> for BlockPos {
|
impl BlockPos {
|
||||||
fn from(vec: TVec3<T>) -> Self {
|
pub const fn new(x: i32, y: i32, z: i32) -> Self {
|
||||||
Self(vec.map(|n| n.into()))
|
Self { x, y, z }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encode for BlockPos {
|
impl Encode for BlockPos {
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
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) => {
|
(-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)
|
(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 x = val >> 38;
|
||||||
let z = val << 26 >> 38;
|
let z = val << 26 >> 38;
|
||||||
let y = val << 52 >> 52;
|
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 (x, x_valid) in xzs {
|
||||||
for (y, y_valid) in ys {
|
for (y, y_valid) in ys {
|
||||||
for (z, z_valid) in xzs {
|
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 {
|
if x_valid && y_valid && z_valid {
|
||||||
pos.encode(&mut &mut buf[..]).unwrap();
|
pos.encode(&mut &mut buf[..]).unwrap();
|
||||||
assert_eq!(BlockPos::decode(&mut &buf[..]).unwrap(), pos);
|
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
|
// TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
use bitvec::bitvec;
|
use bitvec::bitvec;
|
||||||
use bitvec::vec::BitVec;
|
use bitvec::vec::BitVec;
|
||||||
use num::Integer;
|
use num::Integer;
|
||||||
|
use rayon::iter::ParallelIterator;
|
||||||
|
|
||||||
use crate::glm::DVec2;
|
use crate::glm::DVec2;
|
||||||
use crate::packets::play::{
|
use crate::packets::play::{
|
||||||
BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange,
|
BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange,
|
||||||
};
|
};
|
||||||
use crate::protocol::{Encode, Nbt};
|
use crate::protocol::{Encode, Nbt};
|
||||||
|
use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::var_int::VarInt;
|
use crate::var_int::VarInt;
|
||||||
use crate::BiomeId;
|
use crate::BiomeId;
|
||||||
|
|
||||||
|
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 {
|
pub struct Chunk {
|
||||||
sections: Box<[ChunkSection]>,
|
sections: Box<[ChunkSection]>,
|
||||||
// TODO block_entities: HashMap<u32, BlockEntity>,
|
// TODO block_entities: HashMap<u32, BlockEntity>,
|
||||||
|
@ -23,7 +79,7 @@ pub struct Chunk {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
pub(crate) fn new(num_sections: usize) -> Self {
|
pub(crate) fn new(section_count: usize) -> Self {
|
||||||
let sect = ChunkSection {
|
let sect = ChunkSection {
|
||||||
blocks: [0; 4096],
|
blocks: [0; 4096],
|
||||||
biomes: [0; 64],
|
biomes: [0; 64],
|
||||||
|
@ -32,7 +88,7 @@ impl Chunk {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut chunk = Self {
|
let mut chunk = Self {
|
||||||
sections: vec![sect; num_sections].into(),
|
sections: vec![sect; section_count].into(),
|
||||||
heightmap: Vec::new(),
|
heightmap: Vec::new(),
|
||||||
modified: true,
|
modified: true,
|
||||||
created_this_tick: true,
|
created_this_tick: true,
|
||||||
|
@ -42,10 +98,6 @@ impl Chunk {
|
||||||
chunk
|
chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn deallocate(&mut self) {
|
|
||||||
self.sections = Box::new([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn created_this_tick(&self) -> bool {
|
pub fn created_this_tick(&self) -> bool {
|
||||||
self.created_this_tick
|
self.created_this_tick
|
||||||
}
|
}
|
||||||
|
@ -99,11 +151,11 @@ impl Chunk {
|
||||||
pub(crate) fn chunk_data_packet(
|
pub(crate) fn chunk_data_packet(
|
||||||
&self,
|
&self,
|
||||||
pos: ChunkPos,
|
pos: ChunkPos,
|
||||||
num_sections: usize,
|
section_count: usize,
|
||||||
) -> ChunkDataAndUpdateLight {
|
) -> ChunkDataAndUpdateLight {
|
||||||
let mut blocks_and_biomes = Vec::new();
|
let mut blocks_and_biomes = Vec::new();
|
||||||
|
|
||||||
for i in 0..num_sections {
|
for i in 0..section_count {
|
||||||
match self.sections.get(i) {
|
match self.sections.get(i) {
|
||||||
Some(sect) => {
|
Some(sect) => {
|
||||||
blocks_and_biomes.extend_from_slice(§.compact_data);
|
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()
|
self.heightmap.clone()
|
||||||
} else {
|
} else {
|
||||||
// This is bad for two reasons:
|
// This is bad for two reasons:
|
||||||
|
@ -142,11 +194,11 @@ impl Chunk {
|
||||||
blocks_and_biomes,
|
blocks_and_biomes,
|
||||||
block_entities: Vec::new(), // TODO
|
block_entities: Vec::new(), // TODO
|
||||||
trust_edges: true,
|
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(),
|
block_light_mask: BitVec::new(),
|
||||||
empty_sky_light_mask: BitVec::new(),
|
empty_sky_light_mask: BitVec::new(),
|
||||||
empty_block_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(),
|
block_light_arrays: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,10 +305,10 @@ fn build_heightmap(sections: &[ChunkSection], heightmap: &mut Vec<i64>) {
|
||||||
let height = sections.len() * 16;
|
let height = sections.len() * 16;
|
||||||
let bits_per_val = log2_ceil(height);
|
let bits_per_val = log2_ceil(height);
|
||||||
let vals_per_u64 = 64 / bits_per_val;
|
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.clear();
|
||||||
heightmap.resize(num_u64s, 0);
|
heightmap.resize(u64_count, 0);
|
||||||
|
|
||||||
for x in 0..16 {
|
for x in 0..16 {
|
||||||
for z in 0..16 {
|
for z in 0..16 {
|
||||||
|
@ -304,9 +356,9 @@ fn encode_paletted_container(
|
||||||
// Direct case
|
// Direct case
|
||||||
// Skip the palette
|
// Skip the palette
|
||||||
let idxs_per_u64 = 64 / direct_bits_per_idx;
|
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 {
|
for &entry in entries {
|
||||||
let mut val = 0u64;
|
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 bits_per_idx = bits_per_idx.max(min_bits_per_idx);
|
||||||
let idxs_per_u64 = 64 / 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 {
|
for &entry in entries {
|
||||||
let palette_idx = palette
|
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::hash_map::Entry;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
use flume::{Receiver, Sender, TrySendError};
|
use flume::{Receiver, Sender, TrySendError};
|
||||||
use glm::DVec3;
|
use glm::DVec3;
|
||||||
|
use rayon::iter::ParallelIterator;
|
||||||
|
|
||||||
use crate::block_pos::BlockPos;
|
use crate::block_pos::BlockPos;
|
||||||
use crate::chunk_store::ChunkId;
|
use crate::chunk::ChunkId;
|
||||||
use crate::config::{
|
use crate::config::{
|
||||||
Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId,
|
Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId,
|
||||||
};
|
};
|
||||||
|
@ -18,51 +20,82 @@ use crate::packets::play::{
|
||||||
ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition,
|
ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition,
|
||||||
};
|
};
|
||||||
use crate::protocol::{BoundedInt, Nbt};
|
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::util::{chunks_in_view_distance, is_chunk_in_view_distance};
|
||||||
use crate::var_int::VarInt;
|
use crate::var_int::VarInt;
|
||||||
use crate::world::WorldId;
|
use crate::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 {
|
impl ClientStore {
|
||||||
pub fn get(&self) -> Option<&Client> {
|
pub(crate) fn new() -> Self {
|
||||||
self.0.as_deref()
|
Self { sm: SlotMap::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self) -> Option<&mut Client> {
|
pub fn count(&self) -> usize {
|
||||||
self.0.as_deref_mut()
|
self.sm.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drops the inner [`Client`]. Future calls to [`get`](MaybeClient::get)
|
pub(crate) fn create(&mut self, client: Client) -> ClientId {
|
||||||
/// and [`get_mut`](MaybeClient::get_mut) will return `None`.
|
ClientId(self.sm.insert(client))
|
||||||
///
|
|
||||||
/// 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 fn is_disconnected(&self) -> bool {
|
pub fn delete(&mut self, client: ClientId) -> bool {
|
||||||
self.get().map_or(true, |c| c.is_disconnected())
|
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.
|
/// Represents a client connected to the server after logging in.
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
shared: SharedServer,
|
shared: SharedServer,
|
||||||
/// Setting this to `None` disconnects the client.
|
/// Setting this to `None` disconnects the client.
|
||||||
send: Option<Sender<ClientPlayPacket>>,
|
send: Option<Sender<ClientPlayPacket>>,
|
||||||
recv: Receiver<ServerPlayPacket>,
|
recv: Receiver<ServerPlayPacket>,
|
||||||
|
/// The entity this client is associated with.
|
||||||
|
entity: EntityId,
|
||||||
/// The tick this client was created.
|
/// The tick this client was created.
|
||||||
created_tick: Ticks,
|
created_tick: Ticks,
|
||||||
username: String,
|
username: String,
|
||||||
on_ground: bool,
|
on_ground: bool,
|
||||||
old_position: DVec3,
|
|
||||||
new_position: DVec3,
|
new_position: DVec3,
|
||||||
|
old_position: DVec3,
|
||||||
/// Measured in degrees
|
/// Measured in degrees
|
||||||
yaw: f32,
|
yaw: f32,
|
||||||
/// Measured in degrees
|
/// Measured in degrees
|
||||||
|
@ -81,15 +114,15 @@ pub struct Client {
|
||||||
/// If spawn_position or spawn_position_yaw were modified this tick.
|
/// If spawn_position or spawn_position_yaw were modified this tick.
|
||||||
modified_spawn_position: bool,
|
modified_spawn_position: bool,
|
||||||
/// The world that this client was in at the end of the previous tick.
|
/// The world that this client was in at the end of the previous tick.
|
||||||
old_world: WorldId,
|
new_world: Option<WorldId>,
|
||||||
new_world: WorldId,
|
old_world: Option<WorldId>,
|
||||||
events: Vec<Event>,
|
events: Vec<Event>,
|
||||||
/// The ID of the last keepalive sent.
|
/// The ID of the last keepalive sent.
|
||||||
last_keepalive_id: i64,
|
last_keepalive_id: i64,
|
||||||
/// If the last sent keepalive got a response.
|
/// If the last sent keepalive got a response.
|
||||||
got_keepalive: bool,
|
got_keepalive: bool,
|
||||||
old_max_view_distance: u8,
|
|
||||||
new_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.
|
/// 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
|
/// This is used to determine what entity create/destroy packets should be
|
||||||
/// sent.
|
/// sent.
|
||||||
|
@ -97,8 +130,8 @@ pub struct Client {
|
||||||
hidden_entities: HashSet<EntityId>,
|
hidden_entities: HashSet<EntityId>,
|
||||||
/// Loaded chunks and their positions.
|
/// Loaded chunks and their positions.
|
||||||
loaded_chunks: HashMap<ChunkPos, ChunkId>,
|
loaded_chunks: HashMap<ChunkPos, ChunkId>,
|
||||||
old_game_mode: GameMode,
|
|
||||||
new_game_mode: GameMode,
|
new_game_mode: GameMode,
|
||||||
|
old_game_mode: GameMode,
|
||||||
settings: Option<Settings>,
|
settings: Option<Settings>,
|
||||||
// TODO: latency
|
// TODO: latency
|
||||||
// TODO: time, weather
|
// TODO: time, weather
|
||||||
|
@ -107,6 +140,7 @@ pub struct Client {
|
||||||
impl Client {
|
impl Client {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
packet_channels: ServerPacketChannels,
|
packet_channels: ServerPacketChannels,
|
||||||
|
entity: EntityId,
|
||||||
username: String,
|
username: String,
|
||||||
server: &Server,
|
server: &Server,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -116,11 +150,12 @@ impl Client {
|
||||||
shared: server.shared().clone(),
|
shared: server.shared().clone(),
|
||||||
send: Some(send),
|
send: Some(send),
|
||||||
recv,
|
recv,
|
||||||
|
entity,
|
||||||
created_tick: server.current_tick(),
|
created_tick: server.current_tick(),
|
||||||
username,
|
username,
|
||||||
on_ground: false,
|
on_ground: false,
|
||||||
old_position: DVec3::default(),
|
|
||||||
new_position: DVec3::default(),
|
new_position: DVec3::default(),
|
||||||
|
old_position: DVec3::default(),
|
||||||
yaw: 0.0,
|
yaw: 0.0,
|
||||||
pitch: 0.0,
|
pitch: 0.0,
|
||||||
teleported_this_tick: false,
|
teleported_this_tick: false,
|
||||||
|
@ -129,18 +164,18 @@ impl Client {
|
||||||
spawn_position: BlockPos::default(),
|
spawn_position: BlockPos::default(),
|
||||||
spawn_position_yaw: 0.0,
|
spawn_position_yaw: 0.0,
|
||||||
modified_spawn_position: true,
|
modified_spawn_position: true,
|
||||||
new_world: WorldId::NULL,
|
old_world: None,
|
||||||
old_world: WorldId::NULL,
|
new_world: None,
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
last_keepalive_id: 0,
|
last_keepalive_id: 0,
|
||||||
got_keepalive: true,
|
got_keepalive: true,
|
||||||
old_max_view_distance: 0,
|
|
||||||
new_max_view_distance: 16,
|
new_max_view_distance: 16,
|
||||||
|
old_max_view_distance: 0,
|
||||||
loaded_entities: HashSet::new(),
|
loaded_entities: HashSet::new(),
|
||||||
hidden_entities: HashSet::new(),
|
hidden_entities: HashSet::new(),
|
||||||
loaded_chunks: HashMap::new(),
|
loaded_chunks: HashMap::new(),
|
||||||
old_game_mode: GameMode::Survival,
|
|
||||||
new_game_mode: GameMode::Survival,
|
new_game_mode: GameMode::Survival,
|
||||||
|
old_game_mode: GameMode::Survival,
|
||||||
settings: None,
|
settings: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,11 +234,11 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn world(&self) -> WorldId {
|
pub fn world(&self) -> Option<WorldId> {
|
||||||
self.new_world
|
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;
|
self.new_world = new_world;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,14 +287,241 @@ impl Client {
|
||||||
self.settings.as_ref()
|
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();
|
self.events.clear();
|
||||||
|
|
||||||
if self.is_disconnected() {
|
if self.is_disconnected() {
|
||||||
return;
|
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) => {
|
ServerPlayPacket::TeleportConfirm(p) => {
|
||||||
if self.pending_teleports == 0 {
|
if self.pending_teleports == 0 {
|
||||||
self.disconnect("Unexpected teleport confirmation");
|
self.disconnect("Unexpected teleport confirmation");
|
||||||
|
@ -366,218 +628,8 @@ impl Client {
|
||||||
ServerPlayPacket::Spectate(_) => {}
|
ServerPlayPacket::Spectate(_) => {}
|
||||||
ServerPlayPacket::PlayerBlockPlacement(_) => {}
|
ServerPlayPacket::PlayerBlockPlacement(_) => {}
|
||||||
ServerPlayPacket::UseItem(_) => {}
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Client {
|
impl Drop for Client {
|
||||||
|
@ -603,7 +655,7 @@ pub enum Event {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
/// e.g. en_US
|
/// e.g. en_US
|
||||||
pub locale: String,
|
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();
|
let mut dims = Vec::new();
|
||||||
for (dim, id) in server.dimensions() {
|
for (dim, id) in other.dimensions() {
|
||||||
let id = id.0 as i32;
|
let id = id.0 as i32;
|
||||||
dims.push(DimensionTypeRegistryEntry {
|
dims.push(DimensionTypeRegistryEntry {
|
||||||
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
|
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
|
||||||
|
@ -648,7 +700,7 @@ fn make_dimension_codec(server: &Server) -> DimensionCodec {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut biomes = Vec::new();
|
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));
|
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>()) {
|
if let Entry::Vacant(ve) = self.components.entry(TypeId::of::<C>()) {
|
||||||
let mut vec = Vec::new();
|
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)));
|
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>());
|
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>())
|
self.components.contains_key(&TypeId::of::<C>())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn components<C: 'static + Send + Sync + Default>(
|
pub fn components<C: 'static + Send + Sync + DefaultPrivate>(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Components<C, I>, Error> {
|
) -> Result<Components<C, I>, Error> {
|
||||||
let handle = self
|
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,
|
&self,
|
||||||
) -> Result<ComponentsMut<C, I>, Error> {
|
) -> Result<ComponentsMut<C, I>, Error> {
|
||||||
let handle = self
|
let handle = self
|
||||||
|
@ -266,32 +266,30 @@ trait ComponentVec: Any + Send + Sync {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
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) {
|
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) {
|
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 {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self as _
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
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>>,
|
handle: RwLockReadGuard<'a, Vec<C>>,
|
||||||
_marker: PhantomData<fn(I) -> I>,
|
_marker: PhantomData<fn(I) -> I>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponentsRaw
|
impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponentsRaw for &'b Components<'a, C, I> {
|
||||||
for &'b Components<'a, C, I>
|
|
||||||
{
|
|
||||||
type RawItem = &'b C;
|
type RawItem = &'b C;
|
||||||
type RawIter = std::slice::Iter<'b, C>;
|
type RawIter = std::slice::Iter<'b, C>;
|
||||||
type RawParIter = rayon::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
|
impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponents for &'b Components<'a, C, I> {
|
||||||
for &'b Components<'a, C, I>
|
|
||||||
{
|
|
||||||
type Id = I;
|
type Id = I;
|
||||||
type Item = &'b C;
|
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>>,
|
handle: RwLockWriteGuard<'a, Vec<C>>,
|
||||||
_marker: PhantomData<fn(I) -> I>,
|
_marker: PhantomData<fn(I) -> I>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b, C: 'static + Send + Sync + Default, I: Id> ZippedComponentsRaw
|
impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponentsRaw for &'b ComponentsMut<'a, C, I> {
|
||||||
for &'b ComponentsMut<'a, C, I>
|
|
||||||
{
|
|
||||||
type RawItem = &'b C;
|
type RawItem = &'b C;
|
||||||
type RawIter = std::slice::Iter<'b, C>;
|
type RawIter = std::slice::Iter<'b, C>;
|
||||||
type RawParIter = rayon::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
|
impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponents for &'b ComponentsMut<'a, C, I> {
|
||||||
for &'b ComponentsMut<'a, C, I>
|
|
||||||
{
|
|
||||||
type Id = I;
|
type Id = I;
|
||||||
type Item = &'b C;
|
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>
|
for &'b mut ComponentsMut<'a, C, I>
|
||||||
{
|
{
|
||||||
type RawItem = &'b mut C;
|
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
|
impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponents for &'b mut ComponentsMut<'a, C, I> {
|
||||||
for &'b mut ComponentsMut<'a, C, I>
|
|
||||||
{
|
|
||||||
type Id = I;
|
type Id = I;
|
||||||
type Item = &'b mut C;
|
type Item = &'b mut C;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The possible errors when requesting a component.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("an unknown component type was requested")]
|
#[error("an unknown component type was requested")]
|
||||||
|
@ -442,6 +433,20 @@ pub(crate) mod private {
|
||||||
|
|
||||||
pub(crate) use 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> {
|
pub trait ZippedComponents: ZippedComponentsRaw<RawItem = Self::Item> {
|
||||||
type Id: Copy;
|
type Id: Copy;
|
||||||
type Item: Send + Sync;
|
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::hash_map::Entry;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use rayon::iter::ParallelIterator;
|
||||||
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::chunk::ChunkPos;
|
use crate::chunk::ChunkPos;
|
||||||
use crate::client::MaybeClient;
|
use crate::glm::DVec3;
|
||||||
use crate::component::{
|
use crate::slotmap::{Key, SlotMap};
|
||||||
ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents,
|
use crate::{Aabb, Id, WorldId};
|
||||||
ZippedComponentsRaw,
|
|
||||||
};
|
|
||||||
use crate::{Aabb, WorldId};
|
|
||||||
|
|
||||||
pub mod appearance;
|
|
||||||
|
|
||||||
pub use appearance::Appearance;
|
|
||||||
|
|
||||||
pub struct EntityStore {
|
pub struct EntityStore {
|
||||||
comps: ComponentStore<EntityId>,
|
sm: SlotMap<Entity>,
|
||||||
uuids: Vec<Uuid>,
|
|
||||||
clients: RwLock<Vec<MaybeClient>>,
|
|
||||||
appearances: RwLock<Vec<Appearance>>,
|
|
||||||
old_appearances: Vec<Appearance>,
|
|
||||||
uuid_to_entity: HashMap<Uuid, EntityId>,
|
uuid_to_entity: HashMap<Uuid, EntityId>,
|
||||||
/// Maps chunk positions to the set of all entities with bounding volumes
|
/// Maps chunk positions to the set of all entities with bounding volumes
|
||||||
/// intersecting that chunk.
|
/// intersecting that chunk.
|
||||||
partition: HashMap<(WorldId, ChunkPos), Vec<EntityId>>,
|
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 {
|
impl EntityStore {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
comps: ComponentStore::new(),
|
sm: SlotMap::new(),
|
||||||
uuids: Vec::new(),
|
|
||||||
clients: RwLock::new(Vec::new()),
|
|
||||||
appearances: RwLock::new(Vec::new()),
|
|
||||||
old_appearances: Vec::new(),
|
|
||||||
uuid_to_entity: HashMap::new(),
|
uuid_to_entity: HashMap::new(),
|
||||||
partition: HashMap::new(),
|
partition: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the [`EntityId`] of the entity with the given UUID in an efficient
|
/// Returns the number of live entities.
|
||||||
/// manner.
|
pub fn count(&self) -> usize {
|
||||||
///
|
self.sm.count()
|
||||||
/// 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a new entity with the provided appearance. The new entity's
|
/// Spawns a new entity with the default data. The new entity'd [`EntityId`]
|
||||||
/// [`EntityId`] is returned.
|
/// is returned.
|
||||||
pub fn create(&mut self, appearance: impl Into<Appearance>) -> EntityId {
|
///
|
||||||
let app = appearance.into();
|
/// 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 {
|
loop {
|
||||||
let uuid = Uuid::from_bytes(rand::random());
|
let uuid = Uuid::from_bytes(rand::random());
|
||||||
if let Some(e) = self.create_with_uuid(app.clone(), uuid) {
|
if let Some(entity) = self.create_with_uuid(uuid) {
|
||||||
return e;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,156 +102,81 @@ impl EntityStore {
|
||||||
///
|
///
|
||||||
/// The provided UUID must not conflict with an existing entity UUID. If it
|
/// The provided UUID must not conflict with an existing entity UUID. If it
|
||||||
/// does, `None` is returned and the entity is not spawned.
|
/// does, `None` is returned and the entity is not spawned.
|
||||||
pub fn create_with_uuid(
|
pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option<EntityId> {
|
||||||
&mut self,
|
|
||||||
appearance: impl Into<Appearance>,
|
|
||||||
uuid: Uuid,
|
|
||||||
) -> Option<EntityId> {
|
|
||||||
match self.uuid_to_entity.entry(uuid) {
|
match self.uuid_to_entity.entry(uuid) {
|
||||||
Entry::Occupied(_) => None,
|
Entry::Occupied(_) => None,
|
||||||
Entry::Vacant(ve) => {
|
Entry::Vacant(ve) => {
|
||||||
let app = appearance.into();
|
let entity = EntityId(self.sm.insert(Entity {
|
||||||
|
data: EntityData::Marker(types::Marker::new()),
|
||||||
let entity = self.comps.create_item();
|
old_type: EntityType::Marker,
|
||||||
|
new_position: DVec3::default(),
|
||||||
|
old_position: DVec3::default(),
|
||||||
|
new_world: None,
|
||||||
|
old_world: None,
|
||||||
|
uuid,
|
||||||
|
}));
|
||||||
|
|
||||||
ve.insert(entity);
|
ve.insert(entity);
|
||||||
|
|
||||||
if let (Some(aabb), Some(world)) = (app.aabb(), app.world()) {
|
// TODO: insert into partition.
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(entity)
|
Some(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, entity: EntityId) -> bool {
|
/// Gets the [`EntityId`] of the entity with the given UUID in an efficient
|
||||||
if self.comps.delete_item(entity) {
|
/// manner.
|
||||||
let idx = entity.0.idx as usize;
|
///
|
||||||
|
/// 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
|
self.uuid_to_entity
|
||||||
.remove(&self.uuids[idx])
|
.remove(&e.uuid)
|
||||||
.expect("UUID should have been in UUID map");
|
.expect("UUID should have been in UUID map");
|
||||||
|
|
||||||
self.clients.get_mut()[idx].0 = None;
|
// TODO: remove entity from partition.
|
||||||
|
|
||||||
let app = &self.appearances.get_mut()[idx];
|
|
||||||
if let (Some(aabb), Some(world)) = (app.aabb(), app.world()) {
|
|
||||||
self.partition_remove(entity, world, aabb);
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of live entities.
|
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) {
|
||||||
pub fn count(&self) -> usize {
|
self.sm.retain(|k, v| f(EntityId(k), v))
|
||||||
self.comps.count()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid(&self, entity: EntityId) -> bool {
|
pub fn get(&self, entity: EntityId) -> Option<&Entity> {
|
||||||
self.comps.is_valid(entity)
|
self.sm.get(entity.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<Z>(&self, z: Z, entity: EntityId) -> Option<Z::Item>
|
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> {
|
||||||
where
|
self.sm.get_mut(entity.0)
|
||||||
Z: ZippedComponents<Id = EntityId>,
|
|
||||||
{
|
|
||||||
self.comps.get(z, entity)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter<'a, Z>(&'a self, z: Z) -> impl FusedIterator<Item = (EntityId, Z::Item)> + 'a
|
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
||||||
where
|
self.sm.iter().map(|(k, v)| (EntityId(k), v))
|
||||||
Z: ZippedComponents<Id = EntityId> + 'a,
|
|
||||||
{
|
|
||||||
self.comps.iter(z)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator<Item = (EntityId, Z::Item)> + 'a
|
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ {
|
||||||
where
|
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
|
||||||
Z: ZippedComponents<Id = EntityId> + 'a,
|
|
||||||
{
|
|
||||||
self.comps.par_iter(z)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ids(&self) -> impl FusedIterator<Item = EntityId> + Clone + '_ {
|
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
|
||||||
self.comps.ids()
|
self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn par_ids(&self) -> impl ParallelIterator<Item = EntityId> + Clone + '_ {
|
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ {
|
||||||
self.comps.par_ids()
|
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uuids(&self) -> Uuids {
|
pub(crate) fn from_network_id(&self, network_id: i32) -> Option<EntityId> {
|
||||||
Uuids { uuids: &self.uuids }
|
self.sm.key_at_index(network_id as usize).map(EntityId)
|
||||||
}
|
|
||||||
|
|
||||||
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>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn partition_insert(&mut self, entity: EntityId, world: WorldId, aabb: Aabb<f64, 3>) {
|
fn partition_insert(&mut self, entity: EntityId, world: WorldId, aabb: Aabb<f64, 3>) {
|
||||||
|
@ -333,10 +291,9 @@ impl EntityStore {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(move |v| {
|
.flat_map(move |v| {
|
||||||
v.iter().cloned().filter(move |&e| {
|
v.iter().cloned().filter(move |&e| {
|
||||||
self.get(&self.old_appearances(), e)
|
self.get(e)
|
||||||
.expect("spatial partition contains expired entity")
|
.expect("spatial partition contains deleted entity")
|
||||||
.aabb()
|
.hitbox()
|
||||||
.expect("spatial partition contains entity without AABB")
|
|
||||||
.collides_with_aabb(&aabb)
|
.collides_with_aabb(&aabb)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -344,298 +301,43 @@ impl EntityStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_old_appearances(&mut self) {
|
pub(crate) fn update(&mut self) {
|
||||||
for (old, new) in self
|
for (_, e) in self.iter_mut() {
|
||||||
.old_appearances
|
e.old_position = e.new_position;
|
||||||
.iter_mut()
|
e.old_world = e.new_world;
|
||||||
.zip(self.appearances.get_mut().iter())
|
|
||||||
{
|
// TODO: update entity old_type.
|
||||||
old.clone_from(new);
|
// TODO: clear changed bits in metadata.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
|
//#[cfg(test)]
|
||||||
pub struct EntityId(IdData);
|
//mod tests {
|
||||||
|
// use appearance::Player;
|
||||||
impl IdRaw for EntityId {
|
//
|
||||||
fn from_data(data: IdData) -> Self {
|
// use super::*;
|
||||||
Self(data)
|
// use crate::glm;
|
||||||
}
|
//
|
||||||
|
// // TODO: better test: spawn a bunch of random entities, spawn a random
|
||||||
fn to_data(self) -> IdData {
|
// AABB, // assert collides_with_aabb consistency.
|
||||||
self.0
|
//
|
||||||
}
|
// #[test]
|
||||||
}
|
// fn space_partition() {
|
||||||
|
// let mut entities = EntityStore::new();
|
||||||
impl EntityId {
|
//
|
||||||
/// The vaule of the default `EntityId` which always refers to an expired
|
// let ids = [(16.0, 16.0, 16.0), (8.0, 8.0, 8.0), (10.0, 50.0, 10.0)]
|
||||||
/// entity.
|
// .into_iter()
|
||||||
pub const NULL: Self = Self(IdData::NULL);
|
// .map(|(x, y, z)| entities.create(Player::new(glm::vec3(x, y, z),
|
||||||
|
// WorldId::NULL))) .collect::<Vec<_>>();
|
||||||
pub(crate) fn to_network_id(self) -> i32 {
|
//
|
||||||
self.0.idx as i32
|
// let outside = *ids.last().unwrap();
|
||||||
}
|
//
|
||||||
}
|
// assert!(entities
|
||||||
|
// .intersecting_aabb(
|
||||||
impl Id for EntityId {}
|
// WorldId::NULL,
|
||||||
|
// Aabb::new(glm::vec3(8.0, 8.0, 8.0), glm::vec3(16.0, 16.0,
|
||||||
/// A built-in component collection containing the UUID of the entities.
|
// 16.0)), )
|
||||||
///
|
// .all(|id| ids.contains(&id) && id != outside));
|
||||||
/// 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)]
|
#![forbid(unsafe_code)]
|
||||||
|
#![warn(
|
||||||
|
missing_debug_implementations,
|
||||||
|
trivial_casts,
|
||||||
|
trivial_numeric_casts,
|
||||||
|
unused_lifetimes,
|
||||||
|
unused_import_braces,
|
||||||
|
missing_docs
|
||||||
|
)]
|
||||||
|
|
||||||
mod aabb;
|
mod aabb;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
mod block_pos;
|
mod block_pos;
|
||||||
mod byte_angle;
|
mod byte_angle;
|
||||||
mod chunk;
|
pub mod chunk;
|
||||||
mod chunk_store;
|
pub mod client;
|
||||||
mod client;
|
|
||||||
mod codec;
|
mod codec;
|
||||||
pub mod component;
|
pub mod component;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
@ -14,23 +21,25 @@ pub mod entity;
|
||||||
pub mod identifier;
|
pub mod identifier;
|
||||||
mod packets;
|
mod packets;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod server;
|
pub mod server;
|
||||||
|
mod slotmap;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
mod var_int;
|
mod var_int;
|
||||||
mod var_long;
|
mod var_long;
|
||||||
mod world;
|
pub mod world;
|
||||||
|
|
||||||
pub use aabb::Aabb;
|
pub use aabb::Aabb;
|
||||||
pub use chunk::{Chunk, ChunkPos};
|
pub use block_pos::BlockPos;
|
||||||
pub use client::Client;
|
pub use chunk::{Chunk, ChunkPos, ChunkStore};
|
||||||
|
pub use client::{Client, ClientStore};
|
||||||
pub use config::{BiomeId, DimensionId, ServerConfig};
|
pub use config::{BiomeId, DimensionId, ServerConfig};
|
||||||
pub use entity::{EntityId, EntityStore};
|
pub use entity::{Entity, EntityId, EntityStore};
|
||||||
pub use identifier::Identifier;
|
pub use identifier::Identifier;
|
||||||
pub use text::{Text, TextFormat};
|
pub use text::{Text, TextFormat};
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
pub use world::{World, WorldId};
|
pub use world::{World, WorldId, WorldStore};
|
||||||
pub use {nalgebra_glm as glm, nbt};
|
pub use {nalgebra_glm as glm, nbt, uuid};
|
||||||
|
|
||||||
pub use crate::server::{NewClientData, Server, SharedServer, ShutdownResult};
|
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
|
/// The duration of a game update depends on the current configuration, which
|
||||||
/// may or may not be the same as Minecraft's standard 20 ticks/second.
|
/// may or may not be the same as Minecraft's standard 20 ticks/second.
|
||||||
pub type Ticks = i64;
|
pub type Ticks = i64;
|
||||||
|
|
||||||
|
/// Types such as [`EntityId`], [`WorldId`], and [`ChunkId`] which can be used
|
||||||
|
/// as indices into an array.
|
||||||
|
///
|
||||||
|
/// Every ID is either valid or invalid. Valid IDs point to living values. For
|
||||||
|
/// instance, a valid [`EntityId`] points to a living entity on the server. When
|
||||||
|
/// that entity is deleted, the corresponding [`EntityId`] becomes invalid.
|
||||||
|
pub trait Id: Copy + Send + Sync + PartialEq + Eq {
|
||||||
|
/// Returns the index of this ID.
|
||||||
|
///
|
||||||
|
/// For all IDs `a` and `b`, `a == b` implies `a.idx() == b.idx()`. If
|
||||||
|
/// both `a` and `b` are currently valid, then `a != b` implies `a.idx() !=
|
||||||
|
/// b.idx()`.
|
||||||
|
fn idx(self) -> usize;
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
//! 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 {
|
macro_rules! def_bitfield {
|
||||||
(
|
(
|
||||||
$(#[$struct_attrs:meta])*
|
$(#[$struct_attrs:meta])*
|
||||||
|
@ -269,7 +271,7 @@ macro_rules! def_bitfield {
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
// TODO: custom Debug impl.
|
// TODO: custom Debug impl.
|
||||||
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
$(#[$struct_attrs])*
|
$(#[$struct_attrs])*
|
||||||
pub struct $name($inner_ty);
|
pub struct $name($inner_ty);
|
||||||
|
|
||||||
|
@ -279,8 +281,8 @@ macro_rules! def_bitfield {
|
||||||
$bit: bool,
|
$bit: bool,
|
||||||
)*
|
)*
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut res = Self::default();
|
let mut res = Self(Default::default());
|
||||||
paste! {
|
paste::paste! {
|
||||||
$(
|
$(
|
||||||
res = res.[<set_ $bit:snake>]($bit);
|
res = res.[<set_ $bit:snake>]($bit);
|
||||||
)*
|
)*
|
||||||
|
@ -288,7 +290,7 @@ macro_rules! def_bitfield {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
paste! {
|
paste::paste! {
|
||||||
$(
|
$(
|
||||||
#[doc = "Gets the " $bit " bit on this bitfield.\n"]
|
#[doc = "Gets the " $bit " bit on this bitfield.\n"]
|
||||||
$(#[$bit_attrs])*
|
$(#[$bit_attrs])*
|
||||||
|
|
|
@ -201,7 +201,10 @@ impl Decode for f64 {
|
||||||
impl<T: Encode> Encode for Option<T> {
|
impl<T: Encode> Encode for Option<T> {
|
||||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Some(t) => true.encode(w).and_then(|_| t.encode(w)),
|
Some(t) => {
|
||||||
|
true.encode(w)?;
|
||||||
|
t.encode(w)
|
||||||
|
}
|
||||||
None => false.encode(w),
|
None => false.encode(w),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,8 @@ use tokio::runtime::{Handle, Runtime};
|
||||||
use tokio::sync::{oneshot, Semaphore};
|
use tokio::sync::{oneshot, Semaphore};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::chunk_store::ChunkStore;
|
|
||||||
use crate::client::MaybeClient;
|
|
||||||
use crate::codec::{Decoder, Encoder};
|
use crate::codec::{Decoder, Encoder};
|
||||||
use crate::config::{Biome, BiomeId, Dimension, DimensionId, Handler, Login, ServerListPing};
|
use crate::config::{Biome, BiomeId, Dimension, DimensionId, Handler, Login, ServerListPing};
|
||||||
use crate::entity::Appearance;
|
|
||||||
use crate::packets::handshake::{Handshake, HandshakeNextState};
|
use crate::packets::handshake::{Handshake, HandshakeNextState};
|
||||||
use crate::packets::login::{
|
use crate::packets::login::{
|
||||||
self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression,
|
self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression,
|
||||||
|
@ -40,8 +37,10 @@ use crate::packets::status::{Ping, Pong, Request, Response};
|
||||||
use crate::protocol::{BoundedArray, BoundedString};
|
use crate::protocol::{BoundedArray, BoundedString};
|
||||||
use crate::util::valid_username;
|
use crate::util::valid_username;
|
||||||
use crate::var_int::VarInt;
|
use crate::var_int::VarInt;
|
||||||
use crate::world::WorldStore;
|
use crate::{
|
||||||
use crate::{Client, EntityStore, ServerConfig, Ticks, PROTOCOL_VERSION, VERSION_NAME};
|
ChunkStore, Client, ClientStore, EntityStore, ServerConfig, Ticks, WorldStore,
|
||||||
|
PROTOCOL_VERSION, VERSION_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
/// Holds the state of a running Minecraft server which is accessible inside the
|
/// Holds the state of a running Minecraft server which is accessible inside the
|
||||||
/// update loop. To start a server, see [`ServerConfig`].
|
/// update loop. To start a server, see [`ServerConfig`].
|
||||||
|
@ -55,6 +54,7 @@ use crate::{Client, EntityStore, ServerConfig, Ticks, PROTOCOL_VERSION, VERSION_
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
pub entities: EntityStore,
|
pub entities: EntityStore,
|
||||||
|
pub clients: ClientStore,
|
||||||
pub worlds: WorldStore,
|
pub worlds: WorldStore,
|
||||||
pub chunks: ChunkStore,
|
pub chunks: ChunkStore,
|
||||||
pub other: Other,
|
pub other: Other,
|
||||||
|
@ -89,7 +89,6 @@ struct SharedServerInner {
|
||||||
tokio_handle: Handle,
|
tokio_handle: Handle,
|
||||||
dimensions: Vec<Dimension>,
|
dimensions: Vec<Dimension>,
|
||||||
biomes: Vec<Biome>,
|
biomes: Vec<Biome>,
|
||||||
|
|
||||||
/// The instant the server was started.
|
/// The instant the server was started.
|
||||||
start_instant: Instant,
|
start_instant: Instant,
|
||||||
/// A semaphore used to limit the number of simultaneous connections to the
|
/// 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 {
|
let mut server = Server {
|
||||||
entities: EntityStore::new(),
|
entities: EntityStore::new(),
|
||||||
worlds: WorldStore::new(shared.clone()),
|
clients: ClientStore::new(),
|
||||||
|
worlds: WorldStore::new(),
|
||||||
chunks: ChunkStore::new(),
|
chunks: ChunkStore::new(),
|
||||||
other: Other {
|
other: Other {
|
||||||
shared: shared.clone(),
|
shared: shared.clone(),
|
||||||
|
@ -375,43 +375,35 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut clients = server.entities.clients_mut().unwrap();
|
server.clients.par_iter_mut().for_each(|(_, client)| {
|
||||||
|
client.update(
|
||||||
|
&server.entities,
|
||||||
|
&server.worlds,
|
||||||
|
&server.chunks,
|
||||||
|
&server.other,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
server.entities.update();
|
||||||
|
|
||||||
server
|
server
|
||||||
.entities
|
.chunks
|
||||||
.par_iter(&mut clients)
|
.par_iter_mut()
|
||||||
.for_each(|(e, client)| {
|
.for_each(|(_, chunk)| chunk.apply_modifications());
|
||||||
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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shared.handler().update(server);
|
shared.handler().update(server);
|
||||||
|
|
||||||
{
|
|
||||||
let mut chunks = server.chunks.chunks_mut().unwrap();
|
|
||||||
|
|
||||||
// Chunks modified this tick can have their changes applied immediately because
|
// Chunks modified this tick can have their changes applied immediately because
|
||||||
// they have not been observed by clients yet.
|
// they have not been observed by clients yet.
|
||||||
server.chunks.par_iter(&mut chunks).for_each(|(_, chunk)| {
|
server.chunks.par_iter_mut().for_each(|(_, chunk)| {
|
||||||
if chunk.created_this_tick() {
|
if chunk.created_this_tick() {
|
||||||
chunk.clear_created_this_tick();
|
chunk.clear_created_this_tick();
|
||||||
chunk.apply_modifications();
|
chunk.apply_modifications();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
|
// Sleep for the remainder of the tick.
|
||||||
thread::sleep(
|
thread::sleep(
|
||||||
server
|
server
|
||||||
.0
|
.0
|
||||||
|
@ -433,35 +425,29 @@ fn join_player(server: &mut Server, msg: NewClientMessage) {
|
||||||
|
|
||||||
let _ = msg.reply.send(Ok(client_packet_channels));
|
let _ = msg.reply.send(Ok(client_packet_channels));
|
||||||
|
|
||||||
let mut client = Client::new(server_packet_channels, msg.ncd.username, server);
|
let client_backed_entity = match server.entities.create_with_uuid(msg.ncd.uuid) {
|
||||||
|
Some(id) => id,
|
||||||
let client_eid = match server
|
|
||||||
.entities
|
|
||||||
.create_with_uuid(Appearance::None, msg.ncd.uuid)
|
|
||||||
{
|
|
||||||
Some(eid) => eid,
|
|
||||||
None => {
|
None => {
|
||||||
log::error!(
|
log::error!(
|
||||||
"player '{}' cannot join the server because their UUID ({}) conflicts with an \
|
"player '{}' cannot join the server because their UUID ({}) conflicts with an \
|
||||||
existing entity",
|
existing entity",
|
||||||
client.username(),
|
msg.ncd.username,
|
||||||
msg.ncd.uuid
|
msg.ncd.uuid
|
||||||
);
|
);
|
||||||
|
|
||||||
client.disconnect("Cannot join server: Your UUID conflicts with an existing entity.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut clients = server.entities.clients_mut().unwrap();
|
let client_id = server.clients.create(Client::new(
|
||||||
*server.entities.get(&mut clients, client_eid).unwrap() = MaybeClient(Some(Box::new(client)));
|
server_packet_channels,
|
||||||
|
client_backed_entity,
|
||||||
|
msg.ncd.username,
|
||||||
|
server,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
type Codec = (Encoder<OwnedWriteHalf>, Decoder<OwnedReadHalf>);
|
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) {
|
async fn do_accept_loop(server: SharedServer) {
|
||||||
log::trace!("entering accept loop");
|
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.
|
/// Provides the methods necessary for working with [`Text`] objects.
|
||||||
///
|
///
|
||||||
/// This trait exists to allow using `Into<Text>` impls without having to first
|
/// This trait exists to allow using `Into<Text>` types without having to first
|
||||||
/// convert `Self` into [`Text`]. It is automatically implemented for all
|
/// convert the type into [`Text`]. It is automatically implemented for all
|
||||||
/// `Into<Text>` types, including [`Text`] itself.
|
/// `Into<Text>` types, including [`Text`] itself.
|
||||||
pub trait TextFormat: Into<Text> {
|
pub trait TextFormat: Into<Text> {
|
||||||
fn into_text(self) -> Text {
|
fn into_text(self) -> Text {
|
||||||
|
@ -347,6 +347,8 @@ impl Text {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: getters
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Into<Text>> TextFormat for T {}
|
impl<T: Into<Text>> TextFormat for T {}
|
||||||
|
|
230
src/world.rs
230
src/world.rs
|
@ -1,234 +1,78 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use rayon::iter::ParallelIterator;
|
||||||
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
|
|
||||||
|
|
||||||
use crate::chunk::ChunkPos;
|
use crate::chunk::{ChunkId, ChunkPos};
|
||||||
use crate::chunk_store::ChunkId;
|
|
||||||
use crate::component::{
|
|
||||||
ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents,
|
|
||||||
ZippedComponentsRaw,
|
|
||||||
};
|
|
||||||
use crate::config::DimensionId;
|
use crate::config::DimensionId;
|
||||||
use crate::SharedServer;
|
use crate::slotmap::{Key, SlotMap};
|
||||||
|
use crate::Id;
|
||||||
|
|
||||||
pub struct WorldStore {
|
pub struct WorldStore {
|
||||||
comps: ComponentStore<WorldId>,
|
sm: SlotMap<World>,
|
||||||
worlds: RwLock<Vec<World>>,
|
}
|
||||||
shared: SharedServer,
|
|
||||||
|
#[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 {
|
impl WorldStore {
|
||||||
pub(crate) fn new(shared: SharedServer) -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self { sm: SlotMap::new() }
|
||||||
comps: ComponentStore::new(),
|
|
||||||
worlds: RwLock::new(Vec::new()),
|
|
||||||
shared,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn count(&self) -> usize {
|
||||||
|
self.sm.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(&mut self, dim: DimensionId) -> WorldId {
|
pub fn create(&mut self, dim: DimensionId) -> WorldId {
|
||||||
let height = self.shared.dimension(dim).height;
|
WorldId(self.sm.insert(World {
|
||||||
|
|
||||||
let id = self.comps.create_item();
|
|
||||||
let world = World {
|
|
||||||
chunks: HashMap::new(),
|
chunks: HashMap::new(),
|
||||||
dimension: dim,
|
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
|
/// Deletes a world from the server. Any [`WorldId`] referring to the
|
||||||
/// deleted world will be invalidated.
|
/// deleted world will be invalidated.
|
||||||
///
|
///
|
||||||
/// Note that any entities with positions inside the deleted world will not
|
/// Note that any entities with positions inside the deleted world will not
|
||||||
/// be deleted themselves. These entities should be deleted or moved
|
/// be deleted themselves.
|
||||||
/// elsewhere, preferably before calling this function.
|
|
||||||
pub fn delete(&mut self, world: WorldId) -> bool {
|
pub fn delete(&mut self, world: WorldId) -> bool {
|
||||||
if self.comps.delete_item(world) {
|
self.sm.remove(world.0).is_some()
|
||||||
let idx = world.0.idx as usize;
|
|
||||||
self.worlds.get_mut()[idx].chunks = HashMap::new();
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count(&self) -> usize {
|
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) {
|
||||||
self.comps.count()
|
self.sm.retain(|k, v| f(WorldId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<Z>(&self, z: Z, world: WorldId) -> Option<Z::Item>
|
pub fn get(&self, world: WorldId) -> Option<&World> {
|
||||||
where
|
self.sm.get(world.0)
|
||||||
Z: ZippedComponents<Id = WorldId>,
|
|
||||||
{
|
|
||||||
self.comps.get(z, world)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter<'a, Z>(&'a self, z: Z) -> impl FusedIterator<Item = (WorldId, Z::Item)> + 'a
|
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> {
|
||||||
where
|
self.sm.get_mut(world.0)
|
||||||
Z: ZippedComponents<Id = WorldId> + 'a,
|
|
||||||
{
|
|
||||||
self.comps.iter(z)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator<Item = (WorldId, Z::Item)> + 'a
|
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ {
|
||||||
where
|
self.sm.iter().map(|(k, v)| (WorldId(k), v))
|
||||||
Z: ZippedComponents<Id = WorldId> + 'a,
|
|
||||||
{
|
|
||||||
self.comps.par_iter(z)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ids(&self) -> impl FusedIterator<Item = WorldId> + Clone + '_ {
|
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ {
|
||||||
self.comps.ids()
|
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn par_ids(&self) -> impl ParallelIterator<Item = WorldId> + Clone + '_ {
|
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ {
|
||||||
self.comps.par_ids()
|
self.sm.par_iter().map(|(k, v)| (WorldId(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn worlds(&self) -> Result<Worlds, Error> {
|
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ {
|
||||||
Ok(Worlds {
|
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
|
||||||
worlds: self.worlds.try_read().ok_or(Error::NoReadAccess)?,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
pub struct World {
|
||||||
|
|
Loading…
Reference in a new issue