Add custom data

This commit is contained in:
Ryan 2022-07-15 20:40:39 -07:00
parent 0ef05bb0d0
commit 865ab76699
16 changed files with 466 additions and 422 deletions

View file

@ -2470,11 +2470,11 @@ pub fn build() -> anyhow::Result<()> {
#(#entity_structs)* #(#entity_structs)*
/// An enum encoding the type of an entity along with any data specific to that entity type. /// An enum encoding the type of an entity along with any data specific to that entity type.
pub enum EntityData { pub enum EntityState {
#(#entity_kind_variants(#entity_kind_variants),)* #(#entity_kind_variants(#entity_kind_variants),)*
} }
impl EntityData { impl EntityState {
pub(super) fn new(kind: EntityKind) -> Self { pub(super) fn new(kind: EntityKind) -> Self {
match kind { match kind {
#(EntityKind::#entity_kind_variants => Self::#entity_kind_variants(#entity_kind_variants::new()),)* #(EntityKind::#entity_kind_variants => Self::#entity_kind_variants(#entity_kind_variants::new()),)*

View file

@ -1,19 +1,17 @@
use std::collections::HashMap;
use std::mem; use std::mem;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use log::LevelFilter; use log::LevelFilter;
use num::Integer; use num::Integer;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
use valence::biome::Biome; use valence::biome::Biome;
use valence::block::BlockState; use valence::block::BlockState;
use valence::client::{ClientId, Event, Hand}; use valence::client::{Event, Hand};
use valence::config::{Config, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::dimension::{Dimension, DimensionId}; use valence::dimension::{Dimension, DimensionId};
use valence::entity::data::Pose; use valence::entity::state::Pose;
use valence::entity::{EntityData, EntityId, EntityKind}; use valence::entity::{EntityId, EntityKind, EntityState};
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::{async_trait, ident}; use valence::{async_trait, ident};
@ -24,27 +22,32 @@ pub fn main() -> ShutdownResult {
.parse_default_env() .parse_default_env()
.init(); .init();
valence::start_server(Game { valence::start_server(
player_count: AtomicUsize::new(0), Game {
state: Mutex::new(State { player_count: AtomicUsize::new(0),
player_entities: HashMap::new(), },
ServerData {
board: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(), board: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(),
board_buf: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(), board_buf: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(),
}), },
}) )
} }
struct Game { struct Game {
player_count: AtomicUsize, player_count: AtomicUsize,
state: Mutex<State>,
} }
struct State { struct ServerData {
player_entities: HashMap<ClientId, EntityId>,
board: Box<[bool]>, board: Box<[bool]>,
board_buf: Box<[bool]>, board_buf: Box<[bool]>,
} }
#[derive(Default)]
struct ClientData {
/// The client's player entity.
player: EntityId,
}
const MAX_PLAYERS: usize = 10; const MAX_PLAYERS: usize = 10;
const SIZE_X: usize = 100; const SIZE_X: usize = 100;
@ -53,6 +56,12 @@ const BOARD_Y: i32 = 50;
#[async_trait] #[async_trait]
impl Config for Game { impl Config for Game {
type ChunkData = ();
type ClientData = ClientData;
type EntityData = ();
type ServerData = ServerData;
type WorldData = ();
fn max_connections(&self) -> usize { fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full. // We want status pings to be successful even if the server is full.
MAX_PLAYERS + 64 MAX_PLAYERS + 64
@ -80,7 +89,7 @@ impl Config for Game {
async fn server_list_ping( async fn server_list_ping(
&self, &self,
_server: &SharedServer, _server: &SharedServer<Self>,
_remote_addr: SocketAddr, _remote_addr: SocketAddr,
) -> ServerListPing { ) -> ServerListPing {
ServerListPing::Respond { ServerListPing::Respond {
@ -91,18 +100,18 @@ impl Config for Game {
} }
} }
fn init(&self, server: &mut Server) { fn init(&self, server: &mut Server<Self>) {
let world = server.worlds.create(DimensionId::default()).1; let world = server.worlds.create(DimensionId::default(), ()).1;
world.meta.set_flat(true); world.meta.set_flat(true);
for chunk_z in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 { for chunk_z in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 {
for chunk_x in -2..Integer::div_ceil(&(SIZE_Z as i32), &16) + 2 { for chunk_x in -2..Integer::div_ceil(&(SIZE_Z as i32), &16) + 2 {
world.chunks.create((chunk_x as i32, chunk_z as i32)); world.chunks.create((chunk_x as i32, chunk_z as i32), ());
} }
} }
} }
fn update(&self, server: &mut Server) { fn update(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.iter_mut().next().unwrap(); let (world_id, world) = server.worlds.iter_mut().next().unwrap();
let spawn_pos = [ let spawn_pos = [
@ -111,13 +120,7 @@ impl Config for Game {
SIZE_Z as f64 / 2.0, SIZE_Z as f64 / 2.0,
]; ];
let State { server.clients.retain(|_, client| {
player_entities,
board,
board_buf,
} = &mut *self.state.lock().unwrap();
server.clients.retain(|client_id, client| {
if client.created_tick() == server.shared.current_tick() { if client.created_tick() == server.shared.current_tick() {
if self if self
.player_count .player_count
@ -142,14 +145,13 @@ impl Config for Game {
None, None,
); );
player_entities.insert( let player_id = server
client_id, .entities
server .create_with_uuid(EntityKind::Player, client.uuid(), ())
.entities .unwrap()
.create_with_uuid(EntityKind::Player, client.uuid()) .0;
.unwrap()
.0, client.data = ClientData { player: player_id };
);
client.send_message("Welcome to Conway's game of life in Minecraft!".italic()); client.send_message("Welcome to Conway's game of life in Minecraft!".italic());
client.send_message("Hold the left mouse button to bring blocks to life.".italic()); client.send_message("Hold the left mouse button to bring blocks to life.".italic());
@ -157,8 +159,7 @@ impl Config for Game {
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
let id = player_entities.remove(&client_id).unwrap(); server.entities.delete(client.data.player);
server.entities.delete(id);
world.meta.player_list_mut().remove(client.uuid()); world.meta.player_list_mut().remove(client.uuid());
return false; return false;
} }
@ -166,11 +167,8 @@ impl Config for Game {
true true
}); });
for (client_id, client) in server.clients.iter_mut() { for (_, client) in server.clients.iter_mut() {
let player = server let player = server.entities.get_mut(client.data.player).unwrap();
.entities
.get_mut(player_entities[&client_id])
.unwrap();
while let Some(event) = client.pop_event() { while let Some(event) = client.pop_event() {
match event { match event {
@ -179,7 +177,8 @@ impl Config for Game {
&& (0..SIZE_Z as i32).contains(&position.z) && (0..SIZE_Z as i32).contains(&position.z)
&& position.y == BOARD_Y && position.y == BOARD_Y
{ {
board[position.x as usize + position.z as usize * SIZE_X] = true; server.data.board[position.x as usize + position.z as usize * SIZE_X] =
true;
} }
} }
Event::Movement { .. } => { Event::Movement { .. } => {
@ -195,29 +194,29 @@ impl Config for Game {
player.set_on_ground(client.on_ground()); player.set_on_ground(client.on_ground());
} }
Event::StartSneaking => { Event::StartSneaking => {
if let EntityData::Player(e) = player.data_mut() { if let EntityState::Player(e) = &mut player.state {
e.set_crouching(true); e.set_crouching(true);
e.set_pose(Pose::Sneaking); e.set_pose(Pose::Sneaking);
} }
} }
Event::StopSneaking => { Event::StopSneaking => {
if let EntityData::Player(e) = player.data_mut() { if let EntityState::Player(e) = &mut player.state {
e.set_pose(Pose::Standing); e.set_pose(Pose::Standing);
e.set_crouching(false); e.set_crouching(false);
} }
} }
Event::StartSprinting => { Event::StartSprinting => {
if let EntityData::Player(e) = player.data_mut() { if let EntityState::Player(e) = &mut player.state {
e.set_sprinting(true); e.set_sprinting(true);
} }
} }
Event::StopSprinting => { Event::StopSprinting => {
if let EntityData::Player(e) = player.data_mut() { if let EntityState::Player(e) = &mut player.state {
e.set_sprinting(false); e.set_sprinting(false);
} }
} }
Event::ArmSwing(hand) => { Event::ArmSwing(hand) => {
if let EntityData::Player(e) = player.data_mut() { if let EntityState::Player(e) = &mut player.state {
match hand { match hand {
Hand::Main => e.trigger_swing_main_arm(), Hand::Main => e.trigger_swing_main_arm(),
Hand::Off => e.trigger_swing_offhand(), Hand::Off => e.trigger_swing_offhand(),
@ -233,31 +232,36 @@ impl Config for Game {
return; return;
} }
board_buf.par_iter_mut().enumerate().for_each(|(i, cell)| { server
let cx = (i % SIZE_X) as i32; .data
let cz = (i / SIZE_Z) as i32; .board_buf
.par_iter_mut()
.enumerate()
.for_each(|(i, cell)| {
let cx = (i % SIZE_X) as i32;
let cz = (i / SIZE_Z) as i32;
let mut live_count = 0; let mut live_count = 0;
for z in cz - 1..=cz + 1 { for z in cz - 1..=cz + 1 {
for x in cx - 1..=cx + 1 { for x in cx - 1..=cx + 1 {
if !(x == cx && z == cz) { if !(x == cx && z == cz) {
let i = x.rem_euclid(SIZE_X as i32) as usize let i = x.rem_euclid(SIZE_X as i32) as usize
+ z.rem_euclid(SIZE_Z as i32) as usize * SIZE_X; + z.rem_euclid(SIZE_Z as i32) as usize * SIZE_X;
if board[i] { if server.data.board[i] {
live_count += 1; live_count += 1;
}
} }
} }
} }
}
if board[cx as usize + cz as usize * SIZE_X] { if server.data.board[cx as usize + cz as usize * SIZE_X] {
*cell = (2..=3).contains(&live_count); *cell = (2..=3).contains(&live_count);
} else { } else {
*cell = live_count == 3; *cell = live_count == 3;
} }
}); });
mem::swap(board, board_buf); mem::swap(&mut server.data.board, &mut server.data.board_buf);
let min_y = server.shared.dimensions().next().unwrap().1.min_y; let min_y = server.shared.dimensions().next().unwrap().1.min_y;
@ -273,7 +277,7 @@ impl Config for Game {
let cell_z = chunk_z * 16 + z; let cell_z = chunk_z * 16 + z;
if cell_x < SIZE_X && cell_z < SIZE_Z { if cell_x < SIZE_X && cell_z < SIZE_Z {
let b = if board[cell_x + cell_z * SIZE_X] { let b = if server.data.board[cell_x + cell_z * SIZE_X] {
BlockState::GRASS_BLOCK BlockState::GRASS_BLOCK
} else { } else {
BlockState::DIRT BlockState::DIRT

View file

@ -1,7 +1,6 @@
use std::f64::consts::TAU; use std::f64::consts::TAU;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use log::LevelFilter; use log::LevelFilter;
use valence::async_trait; use valence::async_trait;
@ -21,23 +20,34 @@ pub fn main() -> ShutdownResult {
.parse_default_env() .parse_default_env()
.init(); .init();
valence::start_server(Game { valence::start_server(
player_count: AtomicUsize::new(0), Game {
cows: Mutex::new(Vec::new()), player_count: AtomicUsize::new(0),
}) },
ServerData { cows: Vec::new() },
)
} }
struct Game { struct Game {
player_count: AtomicUsize, player_count: AtomicUsize,
cows: Mutex<Vec<EntityId>>, }
struct ServerData {
cows: Vec<EntityId>,
} }
const MAX_PLAYERS: usize = 10; const MAX_PLAYERS: usize = 10;
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -35); const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -25);
#[async_trait] #[async_trait]
impl Config for Game { impl Config for Game {
type ChunkData = ();
type ClientData = ();
type EntityData = ();
type ServerData = ServerData;
type WorldData = ();
fn max_connections(&self) -> usize { fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full. // We want status pings to be successful even if the server is full.
MAX_PLAYERS + 64 MAX_PLAYERS + 64
@ -50,7 +60,7 @@ impl Config for Game {
async fn server_list_ping( async fn server_list_ping(
&self, &self,
_server: &SharedServer, _server: &SharedServer<Self>,
_remote_addr: SocketAddr, _remote_addr: SocketAddr,
) -> ServerListPing { ) -> ServerListPing {
ServerListPing::Respond { ServerListPing::Respond {
@ -61,27 +71,27 @@ impl Config for Game {
} }
} }
fn init(&self, server: &mut Server) { fn init(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.create(DimensionId::default()); let (world_id, world) = server.worlds.create(DimensionId::default(), ());
world.meta.set_flat(true); world.meta.set_flat(true);
let size = 5; let size = 5;
for z in -size..size { for z in -size..size {
for x in -size..size { for x in -size..size {
world.chunks.create([x, z]); world.chunks.create([x, z], ());
} }
} }
world.chunks.set_block_state(SPAWN_POS, BlockState::BEDROCK); world.chunks.set_block_state(SPAWN_POS, BlockState::BEDROCK);
self.cows.lock().unwrap().extend((0..200).map(|_| { server.data.cows.extend((0..200).map(|_| {
let (id, e) = server.entities.create(EntityKind::Cow); let (id, e) = server.entities.create(EntityKind::Cow, ());
e.set_world(world_id); e.set_world(world_id);
id id
})); }));
} }
fn update(&self, server: &mut Server) { fn update(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.iter_mut().next().unwrap(); let (world_id, world) = server.worlds.iter_mut().next().unwrap();
server.clients.retain(|_, client| { server.clients.retain(|_, client| {
@ -122,10 +132,10 @@ impl Config for Game {
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid()); world.meta.player_list_mut().remove(client.uuid());
false return false;
} else {
true
} }
true
}); });
let time = server.shared.current_tick() as f64 / server.shared.tick_rate() as f64; let time = server.shared.current_tick() as f64 / server.shared.tick_rate() as f64;
@ -134,9 +144,6 @@ impl Config for Game {
.rotated_y(time * TAU * 0.2) .rotated_y(time * TAU * 0.2)
.rotated_z(time * TAU * 0.3); .rotated_z(time * TAU * 0.3);
let cows = self.cows.lock().unwrap();
let cow_count = cows.len();
let radius = 6.0 + ((time * TAU / 2.5).sin() + 1.0) / 2.0 * 10.0; let radius = 6.0 + ((time * TAU / 2.5).sin() + 1.0) / 2.0 * 10.0;
let player_pos = server let player_pos = server
@ -149,11 +156,16 @@ impl Config for Game {
// TODO: hardcoded eye pos. // TODO: hardcoded eye pos.
let eye_pos = Vec3::new(player_pos.x, player_pos.y + 1.6, player_pos.z); let eye_pos = Vec3::new(player_pos.x, player_pos.y + 1.6, player_pos.z);
#[allow(clippy::significant_drop_in_scrutinee)] for (cow_id, p) in server
for (cow_id, p) in cows.iter().cloned().zip(fibonacci_spiral(cow_count)) { .data
.cows
.iter()
.cloned()
.zip(fibonacci_spiral(server.data.cows.len()))
{
let cow = server.entities.get_mut(cow_id).expect("missing cow"); let cow = server.entities.get_mut(cow_id).expect("missing cow");
let rotated = p * rot; let rotated = p * rot;
let transformed = rotated * radius + [0.5, SPAWN_POS.y as f64 + 1.0, 0.5]; let transformed = rotated * radius + [0.5, SPAWN_POS.y as f64 + 2.0, 0.5];
let yaw = f32::atan2(rotated.z as f32, rotated.x as f32).to_degrees() - 90.0; let yaw = f32::atan2(rotated.z as f32, rotated.x as f32).to_degrees() - 90.0;
let (looking_yaw, looking_pitch) = let (looking_yaw, looking_pitch) =

View file

@ -1,4 +1,3 @@
use std::collections::HashSet;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
@ -8,8 +7,9 @@ use valence::block::{BlockPos, BlockState};
use valence::client::GameMode; use valence::client::GameMode;
use valence::config::{Config, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId; use valence::dimension::DimensionId;
use valence::entity::{EntityData, EntityKind}; use valence::entity::{EntityKind, EntityState};
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::spatial_index::RaycastHit;
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::util::from_yaw_and_pitch; use valence::util::from_yaw_and_pitch;
use vek::Vec3; use vek::Vec3;
@ -20,9 +20,12 @@ pub fn main() -> ShutdownResult {
.parse_default_env() .parse_default_env()
.init(); .init();
valence::start_server(Game { valence::start_server(
player_count: AtomicUsize::new(0), Game {
}) player_count: AtomicUsize::new(0),
},
(),
)
} }
struct Game { struct Game {
@ -31,12 +34,19 @@ struct Game {
const MAX_PLAYERS: usize = 10; const MAX_PLAYERS: usize = 10;
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -8); const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -5);
const PLAYER_EYE_HEIGHT: f64 = 1.6; const PLAYER_EYE_HEIGHT: f64 = 1.6;
#[async_trait] #[async_trait]
impl Config for Game { impl Config for Game {
type ChunkData = ();
type ClientData = ();
/// `true` for entities that have been intersected with.
type EntityData = bool;
type ServerData = ();
type WorldData = ();
fn max_connections(&self) -> usize { fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full. // We want status pings to be successful even if the server is full.
MAX_PLAYERS + 64 MAX_PLAYERS + 64
@ -49,7 +59,7 @@ impl Config for Game {
async fn server_list_ping( async fn server_list_ping(
&self, &self,
_server: &SharedServer, _server: &SharedServer<Self>,
_remote_addr: SocketAddr, _remote_addr: SocketAddr,
) -> ServerListPing { ) -> ServerListPing {
ServerListPing::Respond { ServerListPing::Respond {
@ -60,14 +70,14 @@ impl Config for Game {
} }
} }
fn init(&self, server: &mut Server) { fn init(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.create(DimensionId::default()); let (world_id, world) = server.worlds.create(DimensionId::default(), ());
world.meta.set_flat(true); world.meta.set_flat(true);
let size = 5; let size = 5;
for z in -size..size { for z in -size..size {
for x in -size..size { for x in -size..size {
world.chunks.create([x, z]); world.chunks.create([x, z], ());
} }
} }
@ -77,24 +87,17 @@ impl Config for Game {
for i in 0..SHEEP_COUNT { for i in 0..SHEEP_COUNT {
let offset = (i as f64 - (SHEEP_COUNT - 1) as f64 / 2.0) * 1.25; let offset = (i as f64 - (SHEEP_COUNT - 1) as f64 / 2.0) * 1.25;
let (_, sheep) = server.entities.create(EntityKind::Sheep); let (_, sheep) = server.entities.create(EntityKind::Sheep, false);
sheep.set_world(world_id); sheep.set_world(world_id);
sheep.set_position([offset + 0.5, SPAWN_POS.y as f64 + 1.0, 0.0]); sheep.set_position([offset + 0.5, SPAWN_POS.y as f64 + 1.0, 0.0]);
sheep.set_yaw(180.0); sheep.set_yaw(180.0);
sheep.set_head_yaw(180.0); sheep.set_head_yaw(180.0);
if let EntityData::Sheep(sheep) = sheep.data_mut() {
// Shear the sheep.
sheep.set_sheep_state(0b1_0000);
}
} }
} }
fn update(&self, server: &mut Server) { fn update(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.iter_mut().next().unwrap(); let (world_id, world) = server.worlds.iter_mut().next().unwrap();
let mut hit_entities = HashSet::new();
server.clients.retain(|_, client| { server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() { if client.created_tick() == server.shared.current_tick() {
if self if self
@ -133,7 +136,7 @@ impl Config for Game {
"Look at a sheep to change its ".italic() "Look at a sheep to change its ".italic()
+ "color".italic().color(Color::GREEN) + "color".italic().color(Color::GREEN)
+ ".", + ".",
) );
} }
if client.is_disconnected() { if client.is_disconnected() {
@ -146,27 +149,31 @@ impl Config for Game {
let origin = Vec3::new(client_pos.x, client_pos.y + PLAYER_EYE_HEIGHT, client_pos.z); let origin = Vec3::new(client_pos.x, client_pos.y + PLAYER_EYE_HEIGHT, client_pos.z);
let direction = from_yaw_and_pitch(client.yaw() as f64, client.pitch() as f64); let direction = from_yaw_and_pitch(client.yaw() as f64, client.pitch() as f64);
let only_sheep = |hit: &RaycastHit| {
if let Some(hit) = world.spatial_index.raycast(origin, direction, |hit| {
server server
.entities .entities
.get(hit.entity) .get(hit.entity)
.map_or(false, |e| e.kind() == EntityKind::Sheep) .map_or(false, |e| e.kind() == EntityKind::Sheep)
}) { };
hit_entities.insert(hit.entity);
if let Some(hit) = world.spatial_index.raycast(origin, direction, only_sheep) {
if let Some(e) = server.entities.get_mut(hit.entity) {
e.data = true;
}
} }
true true
}); });
for (id, e) in server.entities.iter_mut() { for (_, e) in server.entities.iter_mut() {
if let EntityData::Sheep(sheep) = e.data_mut() { if let EntityState::Sheep(sheep) = &mut e.state {
if hit_entities.contains(&id) { if e.data {
sheep.set_sheep_state(5); sheep.set_sheep_state(5);
} else { } else {
sheep.set_sheep_state(0); sheep.set_sheep_state(0);
} }
} }
e.data = false;
} }
} }
} }

View file

@ -24,14 +24,17 @@ pub fn main() -> ShutdownResult {
let seed = rand::random(); let seed = rand::random();
valence::start_server(Game { valence::start_server(
player_count: AtomicUsize::new(0), Game {
density_noise: SuperSimplex::new().set_seed(seed), player_count: AtomicUsize::new(0),
hilly_noise: SuperSimplex::new().set_seed(seed.wrapping_add(1)), density_noise: SuperSimplex::new().set_seed(seed),
stone_noise: SuperSimplex::new().set_seed(seed.wrapping_add(2)), hilly_noise: SuperSimplex::new().set_seed(seed.wrapping_add(1)),
gravel_noise: SuperSimplex::new().set_seed(seed.wrapping_add(3)), stone_noise: SuperSimplex::new().set_seed(seed.wrapping_add(2)),
grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)), gravel_noise: SuperSimplex::new().set_seed(seed.wrapping_add(3)),
}) grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)),
},
(),
)
} }
struct Game { struct Game {
@ -47,6 +50,12 @@ const MAX_PLAYERS: usize = 10;
#[async_trait] #[async_trait]
impl Config for Game { impl Config for Game {
type ChunkData = ();
type ClientData = ();
type EntityData = ();
type ServerData = ();
type WorldData = ();
fn max_connections(&self) -> usize { fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full. // We want status pings to be successful even if the server is full.
MAX_PLAYERS + 64 MAX_PLAYERS + 64
@ -59,7 +68,7 @@ impl Config for Game {
async fn server_list_ping( async fn server_list_ping(
&self, &self,
_server: &SharedServer, _server: &SharedServer<Self>,
_remote_addr: SocketAddr, _remote_addr: SocketAddr,
) -> ServerListPing { ) -> ServerListPing {
ServerListPing::Respond { ServerListPing::Respond {
@ -70,12 +79,12 @@ impl Config for Game {
} }
} }
fn init(&self, server: &mut Server) { fn init(&self, server: &mut Server<Self>) {
let (_, world) = server.worlds.create(DimensionId::default()); let (_, world) = server.worlds.create(DimensionId::default(), ());
world.meta.set_flat(true); world.meta.set_flat(true);
} }
fn update(&self, server: &mut Server) { fn update(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.iter_mut().next().unwrap(); let (world_id, world) = server.worlds.iter_mut().next().unwrap();
let mut chunks_to_unload = HashSet::<_>::from_iter(world.chunks.iter().map(|t| t.0)); let mut chunks_to_unload = HashSet::<_>::from_iter(world.chunks.iter().map(|t| t.0));
@ -126,7 +135,7 @@ impl Config for Game {
for pos in chunks_in_view_distance(ChunkPos::at(p.x, p.z), dist) { for pos in chunks_in_view_distance(ChunkPos::at(p.x, p.z), dist) {
chunks_to_unload.remove(&pos); chunks_to_unload.remove(&pos);
if world.chunks.get(pos).is_none() { if world.chunks.get(pos).is_none() {
world.chunks.create(pos); world.chunks.create(pos, ());
} }
} }

View file

@ -15,6 +15,7 @@ use crate::biome::BiomeId;
use crate::block::BlockState; use crate::block::BlockState;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
pub use crate::chunk_pos::ChunkPos; pub use crate::chunk_pos::ChunkPos;
use crate::config::Config;
use crate::dimension::DimensionId; use crate::dimension::DimensionId;
use crate::protocol_inner::packets::play::s2c::{ use crate::protocol_inner::packets::play::s2c::{
BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate, BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate,
@ -24,14 +25,14 @@ use crate::server::SharedServer;
use crate::Ticks; use crate::Ticks;
/// A container for all [`Chunks`]s in a [`World`](crate::world::World). /// A container for all [`Chunks`]s in a [`World`](crate::world::World).
pub struct Chunks { pub struct Chunks<C: Config> {
chunks: HashMap<ChunkPos, Chunk>, chunks: HashMap<ChunkPos, Chunk<C>>,
server: SharedServer, server: SharedServer<C>,
dimension: DimensionId, dimension: DimensionId,
} }
impl Chunks { impl<C: Config> Chunks<C> {
pub(crate) fn new(server: SharedServer, dimension: DimensionId) -> Self { pub(crate) fn new(server: SharedServer<C>, dimension: DimensionId) -> Self {
Self { Self {
chunks: HashMap::new(), chunks: HashMap::new(),
server, server,
@ -49,9 +50,9 @@ impl Chunks {
/// adjacent to it must also be loaded. It is also important that clients /// adjacent to it must also be loaded. It is also important that clients
/// are not spawned within unloaded chunks via /// are not spawned within unloaded chunks via
/// [`spawn`](crate::client::Client::spawn). /// [`spawn`](crate::client::Client::spawn).
pub fn create(&mut self, pos: impl Into<ChunkPos>) -> &mut Chunk { pub fn create(&mut self, pos: impl Into<ChunkPos>, data: C::ChunkData) -> &mut Chunk<C> {
let section_count = (self.server.dimension(self.dimension).height / 16) as u32; let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
let chunk = Chunk::new(section_count, self.server.current_tick()); let chunk = Chunk::new(section_count, self.server.current_tick(), data);
match self.chunks.entry(pos.into()) { match self.chunks.entry(pos.into()) {
Entry::Occupied(mut oe) => { Entry::Occupied(mut oe) => {
@ -78,14 +79,14 @@ impl Chunks {
/// Gets a shared reference to the chunk at the provided position. /// Gets a shared reference to the chunk at the provided position.
/// ///
/// If there is no chunk at the position, then `None` is returned. /// If there is no chunk at the position, then `None` is returned.
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk> { pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk<C>> {
self.chunks.get(&pos.into()) self.chunks.get(&pos.into())
} }
/// Gets an exclusive reference to the chunk at the provided position. /// Gets an exclusive reference to the chunk at the provided position.
/// ///
/// If there is no chunk at the position, then `None` is returned. /// If there is no chunk at the position, then `None` is returned.
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk> { pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk<C>> {
self.chunks.get_mut(&pos.into()) self.chunks.get_mut(&pos.into())
} }
@ -96,25 +97,25 @@ impl Chunks {
/// Returns an immutable iterator over all chunks in the world in an /// Returns an immutable iterator over all chunks in the world in an
/// unspecified order. /// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk<C>)> + Clone + '_ {
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk)) self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
} }
/// Returns a mutable iterator over all chunks in the world in an /// Returns a mutable iterator over all chunks in the world in an
/// unspecified order. /// unspecified order.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk)> + '_ { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk<C>)> + '_ {
self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk)) self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk))
} }
/// Returns a parallel immutable iterator over all chunks in the world in an /// Returns a parallel immutable iterator over all chunks in the world in an
/// unspecified order. /// unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk<C>)> + Clone + '_ {
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk)) self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
} }
/// Returns a parallel mutable iterator over all chunks in the world in an /// Returns a parallel mutable iterator over all chunks in the world in an
/// unspecified order. /// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk)> + '_ { pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk<C>)> + '_ {
self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk)) self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk))
} }
@ -183,7 +184,9 @@ impl Chunks {
/// ///
/// In addition to blocks, chunks also contain [biomes](crate::biome::Biome). /// In addition to blocks, chunks also contain [biomes](crate::biome::Biome).
/// Every 4x4x4 segment of blocks in a chunk corresponds to a biome. /// Every 4x4x4 segment of blocks in a chunk corresponds to a biome.
pub struct Chunk { pub struct Chunk<C: Config> {
/// Custom data.
pub data: C::ChunkData,
sections: Box<[ChunkSection]>, sections: Box<[ChunkSection]>,
// TODO block_entities: HashMap<u32, BlockEntity>, // TODO block_entities: HashMap<u32, BlockEntity>,
/// The MOTION_BLOCKING heightmap /// The MOTION_BLOCKING heightmap
@ -191,8 +194,8 @@ pub struct Chunk {
created_tick: Ticks, created_tick: Ticks,
} }
impl Chunk { impl<C: Config> Chunk<C> {
pub(crate) fn new(section_count: u32, current_tick: Ticks) -> Self { pub(crate) fn new(section_count: u32, current_tick: Ticks, data: C::ChunkData) -> Self {
let sect = ChunkSection { let sect = ChunkSection {
blocks: [BlockState::AIR.to_raw(); 4096], blocks: [BlockState::AIR.to_raw(); 4096],
modified_count: 1, // Must be >0 so the chunk is initialized. modified_count: 1, // Must be >0 so the chunk is initialized.
@ -201,6 +204,7 @@ impl Chunk {
}; };
let mut chunk = Self { let mut chunk = Self {
data,
sections: vec![sect; section_count as usize].into(), sections: vec![sect; section_count as usize].into(),
heightmap: Vec::new(), heightmap: Vec::new(),
created_tick: current_tick, created_tick: current_tick,
@ -547,35 +551,3 @@ fn encode_paletted_container(
fn log2_ceil(n: usize) -> usize { fn log2_ceil(n: usize) -> usize {
n.next_power_of_two().trailing_zeros() as usize n.next_power_of_two().trailing_zeros() as usize
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_get() {
let mut chunk = Chunk::new(16, 0);
chunk.set_block_state(1, 2, 3, BlockState::CAKE);
assert_eq!(chunk.get_block_state(1, 2, 3), BlockState::CAKE);
chunk.set_biome(1, 2, 3, BiomeId(7));
assert_eq!(chunk.get_biome(1, 2, 3), BiomeId(7));
}
#[test]
#[should_panic]
fn block_state_oob() {
let mut chunk = Chunk::new(16, 0);
chunk.set_block_state(16, 0, 0, BlockState::CAKE);
}
#[test]
#[should_panic]
fn biome_oob() {
let mut chunk = Chunk::new(16, 0);
chunk.set_biome(4, 0, 0, BiomeId(0));
}
}

View file

@ -16,6 +16,7 @@ use vek::Vec3;
use crate::biome::Biome; use crate::biome::Biome;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::chunk_pos::ChunkPos; use crate::chunk_pos::ChunkPos;
use crate::config::Config;
use crate::dimension::DimensionId; use crate::dimension::DimensionId;
use crate::entity::types::Player; use crate::entity::types::Player;
use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind}; use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind};
@ -47,16 +48,16 @@ use crate::{ident, Ticks, LIBRARY_NAMESPACE};
/// New clients are automatically inserted into this container but /// New clients are automatically inserted into this container but
/// are not automatically deleted. It is your responsibility to delete them once /// are not automatically deleted. It is your responsibility to delete them once
/// they disconnect. This can be checked with [`Client::is_disconnected`]. /// they disconnect. This can be checked with [`Client::is_disconnected`].
pub struct Clients { pub struct Clients<C: Config> {
sm: SlotMap<Client>, sm: SlotMap<Client<C>>,
} }
impl Clients { impl<C: Config> Clients<C> {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { sm: SlotMap::new() } Self { sm: SlotMap::new() }
} }
pub(crate) fn insert(&mut self, client: Client) -> (ClientId, &mut Client) { pub(crate) fn insert(&mut self, client: Client<C>) -> (ClientId, &mut Client<C>) {
let (id, client) = self.sm.insert(client); let (id, client) = self.sm.insert(client);
(ClientId(id), client) (ClientId(id), client)
} }
@ -73,7 +74,7 @@ impl Clients {
/// which `f` returns `true`. /// which `f` returns `true`.
/// ///
/// All clients are visited in an unspecified order. /// All clients are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client) -> bool) { pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client<C>) -> bool) {
self.sm.retain(|k, v| f(ClientId(k), v)) self.sm.retain(|k, v| f(ClientId(k), v))
} }
@ -85,37 +86,39 @@ impl Clients {
/// Returns a shared reference to the client with the given ID. If /// Returns a shared reference to the client with the given ID. If
/// the ID is invalid, then `None` is returned. /// the ID is invalid, then `None` is returned.
pub fn get(&self, client: ClientId) -> Option<&Client> { pub fn get(&self, client: ClientId) -> Option<&Client<C>> {
self.sm.get(client.0) self.sm.get(client.0)
} }
/// Returns an exclusive reference to the client with the given ID. If the /// Returns an exclusive reference to the client with the given ID. If the
/// ID is invalid, then `None` is returned. /// ID is invalid, then `None` is returned.
pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client> { pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client<C>> {
self.sm.get_mut(client.0) self.sm.get_mut(client.0)
} }
/// Returns an immutable iterator over all clients on the server in an /// Returns an immutable iterator over all clients on the server in an
/// unspecified order. /// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client<C>)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (ClientId(k), v)) self.sm.iter().map(|(k, v)| (ClientId(k), v))
} }
/// Returns a mutable iterator over all clients on the server in an /// Returns a mutable iterator over all clients on the server in an
/// unspecified order. /// unspecified order.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client)> + '_ { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client<C>)> + '_ {
self.sm.iter_mut().map(|(k, v)| (ClientId(k), v)) self.sm.iter_mut().map(|(k, v)| (ClientId(k), v))
} }
/// Returns a parallel immutable iterator over all clients on the server in /// Returns a parallel immutable iterator over all clients on the server in
/// an unspecified order. /// an unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client<C>)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (ClientId(k), v)) self.sm.par_iter().map(|(k, v)| (ClientId(k), v))
} }
/// Returns a parallel mutable iterator over all clients on the server in an /// Returns a parallel mutable iterator over all clients on the server in an
/// unspecified order. /// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, &mut Client)> + '_ { pub fn par_iter_mut(
&mut self,
) -> impl ParallelIterator<Item = (ClientId, &mut Client<C>)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v)) self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v))
} }
} }
@ -158,7 +161,9 @@ impl ClientId {
/// ///
/// In Valence however, clients and players have been decoupled. This separation /// In Valence however, clients and players have been decoupled. This separation
/// was done primarily to enable multithreaded client updates. /// was done primarily to enable multithreaded client updates.
pub struct Client { pub struct Client<C: Config> {
/// Custom data.
pub data: C::ClientData,
/// Setting this to `None` disconnects the client. /// Setting this to `None` disconnects the client.
send: SendOpt, send: SendOpt,
recv: Receiver<C2sPlayPacket>, recv: Receiver<C2sPlayPacket>,
@ -227,15 +232,17 @@ pub(crate) struct ClientFlags {
_pad: u8, _pad: u8,
} }
impl Client { impl<C: Config> Client<C> {
pub(crate) fn new( pub(crate) fn new(
packet_channels: C2sPacketChannels, packet_channels: C2sPacketChannels,
server: &SharedServer, server: &SharedServer<C>,
ncd: NewClientData, ncd: NewClientData,
data: C::ClientData,
) -> Self { ) -> Self {
let (send, recv) = packet_channels; let (send, recv) = packet_channels;
Self { Self {
data,
send: Some(send), send: Some(send),
recv, recv,
created_tick: server.current_tick(), created_tick: server.current_tick(),
@ -584,16 +591,16 @@ impl Client {
send_packet(&mut self.send, packet); send_packet(&mut self.send, packet);
} }
pub(crate) fn handle_serverbound_packets(&mut self, entities: &Entities) { pub(crate) fn handle_serverbound_packets(&mut self, entities: &Entities<C>) {
self.events.clear(); self.events.clear();
for _ in 0..self.recv.len() { for _ in 0..self.recv.len() {
self.handle_serverbound_packet(entities, self.recv.try_recv().unwrap()); self.handle_serverbound_packet(entities, self.recv.try_recv().unwrap());
} }
} }
fn handle_serverbound_packet(&mut self, entities: &Entities, pkt: C2sPlayPacket) { fn handle_serverbound_packet(&mut self, entities: &Entities<C>, pkt: C2sPlayPacket) {
fn handle_movement_packet( fn handle_movement_packet<C: Config>(
client: &mut Client, client: &mut Client<C>,
_vehicle: bool, _vehicle: bool,
new_position: Vec3<f64>, new_position: Vec3<f64>,
new_yaw: f32, new_yaw: f32,
@ -835,7 +842,12 @@ impl Client {
} }
} }
pub(crate) fn update(&mut self, shared: &SharedServer, entities: &Entities, worlds: &Worlds) { pub(crate) fn update(
&mut self,
shared: &SharedServer<C>,
entities: &Entities<C>,
worlds: &Worlds<C>,
) {
// Mark the client as disconnected when appropriate. // Mark the client as disconnected when appropriate.
if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) { if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) {
self.send = None; self.send = None;
@ -1298,8 +1310,8 @@ fn send_packet(send_opt: &mut SendOpt, pkt: impl Into<S2cPlayPacket>) {
} }
} }
fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) { fn send_entity_events<C: Config>(send_opt: &mut SendOpt, id: EntityId, entity: &Entity<C>) {
for &code in entity.data().event_codes() { for &code in entity.state.event_codes() {
if code <= ENTITY_EVENT_MAX_BOUND as u8 { if code <= ENTITY_EVENT_MAX_BOUND as u8 {
send_packet( send_packet(
send_opt, send_opt,
@ -1320,7 +1332,7 @@ fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) {
} }
} }
fn make_registry_codec(shared: &SharedServer) -> RegistryCodec { fn make_registry_codec<C: Config>(shared: &SharedServer<C>) -> RegistryCodec {
let mut dims = Vec::new(); let mut dims = Vec::new();
for (id, dim) in shared.dimensions() { for (id, dim) in shared.dimensions() {
let id = id.0 as i32; let id = id.0 as i32;

View file

@ -1,6 +1,5 @@
//! Configuration for the server. //! Configuration for the server.
use std::any::Any;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::panic::{RefUnwindSafe, UnwindSafe}; use std::panic::{RefUnwindSafe, UnwindSafe};
@ -13,14 +12,7 @@ use crate::server::{NewClientData, Server, SharedServer};
use crate::text::Text; use crate::text::Text;
use crate::Ticks; use crate::Ticks;
/// A trait containing callbacks which are invoked by the running Minecraft /// A trait for the configuration of a server.
/// server.
///
/// The config is used from multiple threads and must therefore implement
/// [`Send`] and [`Sync`]. From within a single thread, methods are never
/// invoked recursively by the library. In other words, a mutex can be aquired
/// at the beginning of a method and released at the end without risk of
/// deadlocking.
/// ///
/// This trait uses the [async_trait] attribute macro. It is exported at the /// This trait uses the [async_trait] attribute macro. It is exported at the
/// root of this crate. /// root of this crate.
@ -28,10 +20,21 @@ use crate::Ticks;
/// [async_trait]: https://docs.rs/async-trait/latest/async_trait/ /// [async_trait]: https://docs.rs/async-trait/latest/async_trait/
#[async_trait] #[async_trait]
#[allow(unused_variables)] #[allow(unused_variables)]
pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe { pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe {
/// Called once at startup to get the maximum number of connections allowed /// Custom data to store with the [`Server`].
/// to the server. Note that this includes all connections, not just those type ServerData: Send + Sync;
/// past the login stage. /// Custom data to store with every [`Client`](crate::client::Client).
type ClientData: Default + Send + Sync;
/// Custom data to store with every [`Entity`](crate::entity::Entity).
type EntityData: Send + Sync;
/// Custom data to store with every [`World`](crate::world::World).
type WorldData: Send + Sync;
/// Custom data to store with every [`Chunk`](crate::chunk::Chunk).
type ChunkData: Send + Sync;
/// Called once at startup to get the maximum number of simultaneous
/// connections allowed to the server. This includes all
/// connections, not just those past the login stage.
/// ///
/// You will want this value to be somewhere above the maximum number of /// You will want this value to be somewhere above the maximum number of
/// players, since status pings should still succeed even when the server is /// players, since status pings should still succeed even when the server is
@ -169,7 +172,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// The query is ignored. /// The query is ignored.
async fn server_list_ping( async fn server_list_ping(
&self, &self,
shared: &SharedServer, shared: &SharedServer<Self>,
remote_addr: SocketAddr, remote_addr: SocketAddr,
) -> ServerListPing { ) -> ServerListPing {
ServerListPing::Ignore ServerListPing::Ignore
@ -192,7 +195,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// The client is allowed to join unconditionally. /// The client is allowed to join unconditionally.
/// ///
/// [`Clients`]: crate::client::Clients /// [`Clients`]: crate::client::Clients
async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> { async fn login(&self, shared: &SharedServer<Self>, ncd: &NewClientData) -> Result<(), Text> {
Ok(()) Ok(())
} }
@ -203,7 +206,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// no connections to the server will be made until this function returns. /// no connections to the server will be made until this function returns.
/// ///
/// This method is called from within a tokio runtime. /// This method is called from within a tokio runtime.
fn init(&self, server: &mut Server) {} fn init(&self, server: &mut Server<Self>) {}
/// Called once at the beginning of every server update (also known as /// Called once at the beginning of every server update (also known as
/// "tick"). This is likely where the majority of your code will be. /// "tick"). This is likely where the majority of your code will be.
@ -215,7 +218,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// # Default Implementation /// # Default Implementation
/// ///
/// The default implementation does nothing. /// The default implementation does nothing.
fn update(&self, server: &mut Server); fn update(&self, server: &mut Server<Self>);
} }
/// The result of the [`server_list_ping`](Config::server_list_ping) callback. /// The result of the [`server_list_ping`](Config::server_list_ping) callback.

View file

@ -1,6 +1,6 @@
//! Dynamic actors in a world. //! Dynamic actors in a world.
pub mod data; pub mod state;
pub mod types; pub mod types;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
@ -10,10 +10,11 @@ use std::num::NonZeroU32;
use bitfield_struct::bitfield; use bitfield_struct::bitfield;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
pub use types::{EntityData, EntityKind}; pub use types::{EntityKind, EntityState};
use uuid::Uuid; use uuid::Uuid;
use vek::{Aabb, Vec3}; use vek::{Aabb, Vec3};
use crate::config::Config;
use crate::protocol_inner::packets::play::s2c::{ use crate::protocol_inner::packets::play::s2c::{
AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata, AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata,
}; };
@ -32,13 +33,13 @@ use crate::world::WorldId;
/// ///
/// [`Player`]: crate::entity::types::Player /// [`Player`]: crate::entity::types::Player
/// [`PlayerList`]: crate::player_list::PlayerList /// [`PlayerList`]: crate::player_list::PlayerList
pub struct Entities { pub struct Entities<C: Config> {
sm: SlotMap<Entity>, sm: SlotMap<Entity<C>>,
uuid_to_entity: HashMap<Uuid, EntityId>, uuid_to_entity: HashMap<Uuid, EntityId>,
network_id_to_entity: HashMap<NonZeroU32, u32>, network_id_to_entity: HashMap<NonZeroU32, u32>,
} }
impl Entities { impl<C: Config> Entities<C> {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
sm: SlotMap::new(), sm: SlotMap::new(),
@ -49,8 +50,8 @@ impl Entities {
/// Spawns a new entity with a random UUID. A reference to the entity along /// Spawns a new entity with a random UUID. A reference to the entity along
/// with its ID is returned. /// with its ID is returned.
pub fn create(&mut self, kind: EntityKind) -> (EntityId, &mut Entity) { pub fn create(&mut self, kind: EntityKind, data: C::EntityData) -> (EntityId, &mut Entity<C>) {
self.create_with_uuid(kind, Uuid::from_bytes(rand::random())) self.create_with_uuid(kind, Uuid::from_bytes(rand::random()), data)
.expect("UUID collision") .expect("UUID collision")
} }
@ -63,13 +64,15 @@ impl Entities {
&mut self, &mut self,
kind: EntityKind, kind: EntityKind,
uuid: Uuid, uuid: Uuid,
) -> Option<(EntityId, &mut Entity)> { data: C::EntityData,
) -> Option<(EntityId, &mut Entity<C>)> {
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 (k, e) = self.sm.insert(Entity { let (k, e) = self.sm.insert(Entity {
data,
state: EntityState::new(kind),
flags: EntityFlags(0), flags: EntityFlags(0),
data: EntityData::new(kind),
world: WorldId::NULL, world: WorldId::NULL,
new_position: Vec3::default(), new_position: Vec3::default(),
old_position: Vec3::default(), old_position: Vec3::default(),
@ -113,7 +116,7 @@ impl Entities {
/// Removes all entities from the server for which `f` returns `true`. /// Removes all entities from the server for which `f` returns `true`.
/// ///
/// All entities are visited in an unspecified order. /// All entities are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) { pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity<C>) -> bool) {
self.sm.retain(|k, v| { self.sm.retain(|k, v| {
if f(EntityId(k), v) { if f(EntityId(k), v) {
true true
@ -147,14 +150,14 @@ impl Entities {
/// Gets a shared reference to the entity with the given [`EntityId`]. /// Gets a shared reference to the entity with the given [`EntityId`].
/// ///
/// If the ID is invalid, `None` is returned. /// If the ID is invalid, `None` is returned.
pub fn get(&self, entity: EntityId) -> Option<&Entity> { pub fn get(&self, entity: EntityId) -> Option<&Entity<C>> {
self.sm.get(entity.0) self.sm.get(entity.0)
} }
/// Gets an exclusive reference to the entity with the given [`EntityId`]. /// Gets an exclusive reference to the entity with the given [`EntityId`].
/// ///
/// If the ID is invalid, `None` is returned. /// If the ID is invalid, `None` is returned.
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> { pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity<C>> {
self.sm.get_mut(entity.0) self.sm.get_mut(entity.0)
} }
@ -166,32 +169,34 @@ impl Entities {
/// Returns an immutable iterator over all entities on the server in an /// Returns an immutable iterator over all entities on the server in an
/// unspecified order. /// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity<C>)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (EntityId(k), v)) self.sm.iter().map(|(k, v)| (EntityId(k), v))
} }
/// Returns a mutable iterator over all entities on the server in an /// Returns a mutable iterator over all entities on the server in an
/// unspecified order. /// unspecified order.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity<C>)> + '_ {
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v)) self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
} }
/// Returns a parallel immutable iterator over all entities on the server in /// Returns a parallel immutable iterator over all entities on the server in
/// an unspecified order. /// an unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity<C>)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (EntityId(k), v)) self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
} }
/// Returns a parallel mutable iterator over all clients on the server in an /// Returns a parallel mutable iterator over all clients on the server in an
/// unspecified order. /// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ { pub fn par_iter_mut(
&mut self,
) -> impl ParallelIterator<Item = (EntityId, &mut Entity<C>)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v)) self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
} }
pub(crate) fn update(&mut self) { pub(crate) fn update(&mut self) {
for (_, e) in self.iter_mut() { for (_, e) in self.iter_mut() {
e.old_position = e.new_position; e.old_position = e.new_position;
e.data.clear_modifications(); e.state.clear_modifications();
e.flags.set_yaw_or_pitch_modified(false); e.flags.set_yaw_or_pitch_modified(false);
e.flags.set_head_yaw_modified(false); e.flags.set_head_yaw_modified(false);
@ -230,9 +235,12 @@ impl EntityId {
/// this struct. This includes position, rotation, velocity, UUID, and hitbox. /// this struct. This includes position, rotation, velocity, UUID, and hitbox.
/// To access data that is not common to every kind of entity, see /// To access data that is not common to every kind of entity, see
/// [`Self::data`]. /// [`Self::data`].
pub struct Entity { pub struct Entity<C: Config> {
/// Custom data.
pub data: C::EntityData,
/// Kind-specific state for this entity.
pub state: EntityState,
flags: EntityFlags, flags: EntityFlags,
data: EntityData,
world: WorldId, world: WorldId,
new_position: Vec3<f64>, new_position: Vec3<f64>,
old_position: Vec3<f64>, old_position: Vec3<f64>,
@ -253,25 +261,14 @@ pub(crate) struct EntityFlags {
_pad: u8, _pad: u8,
} }
impl Entity { impl<C: Config> Entity<C> {
pub(crate) fn flags(&self) -> EntityFlags { pub(crate) fn flags(&self) -> EntityFlags {
self.flags self.flags
} }
/// Gets a reference to this entity's [`EntityData`].
pub fn data(&self) -> &EntityData {
&self.data
}
/// Gets a mutable reference to this entity's
/// [`EntityData`].
pub fn data_mut(&mut self) -> &mut EntityData {
&mut self.data
}
/// Gets the [`EntityKind`] of this entity. /// Gets the [`EntityKind`] of this entity.
pub fn kind(&self) -> EntityKind { pub fn kind(&self) -> EntityKind {
self.data.kind() self.state.kind()
} }
/// Gets the [`WorldId`](crate::world::WorldId) of the world this entity is /// Gets the [`WorldId`](crate::world::WorldId) of the world this entity is
@ -389,18 +386,18 @@ impl Entity {
/// ///
/// [interact event]: crate::client::Event::InteractWithEntity /// [interact event]: crate::client::Event::InteractWithEntity
pub fn hitbox(&self) -> Aabb<f64> { pub fn hitbox(&self) -> Aabb<f64> {
let dims = match &self.data { let dims = match &self.state {
EntityData::Allay(_) => [0.6, 0.35, 0.6], EntityState::Allay(_) => [0.6, 0.35, 0.6],
EntityData::ChestBoat(_) => [1.375, 0.5625, 1.375], EntityState::ChestBoat(_) => [1.375, 0.5625, 1.375],
EntityData::Frog(_) => [0.5, 0.5, 0.5], EntityState::Frog(_) => [0.5, 0.5, 0.5],
EntityData::Tadpole(_) => [0.4, 0.3, 0.4], EntityState::Tadpole(_) => [0.4, 0.3, 0.4],
EntityData::Warden(_) => [0.9, 2.9, 0.9], EntityState::Warden(_) => [0.9, 2.9, 0.9],
EntityData::AreaEffectCloud(e) => [ EntityState::AreaEffectCloud(e) => [
e.get_radius() as f64 * 2.0, e.get_radius() as f64 * 2.0,
0.5, 0.5,
e.get_radius() as f64 * 2.0, e.get_radius() as f64 * 2.0,
], ],
EntityData::ArmorStand(e) => { EntityState::ArmorStand(e) => {
if e.get_marker() { if e.get_marker() {
[0.0, 0.0, 0.0] [0.0, 0.0, 0.0]
} else if e.get_small() { } else if e.get_small() {
@ -409,123 +406,123 @@ impl Entity {
[0.5, 1.975, 0.5] [0.5, 1.975, 0.5]
} }
} }
EntityData::Arrow(_) => [0.5, 0.5, 0.5], EntityState::Arrow(_) => [0.5, 0.5, 0.5],
EntityData::Axolotl(_) => [1.3, 0.6, 1.3], EntityState::Axolotl(_) => [1.3, 0.6, 1.3],
EntityData::Bat(_) => [0.5, 0.9, 0.5], EntityState::Bat(_) => [0.5, 0.9, 0.5],
EntityData::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size? EntityState::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size?
EntityData::Blaze(_) => [0.6, 1.8, 0.6], EntityState::Blaze(_) => [0.6, 1.8, 0.6],
EntityData::Boat(_) => [1.375, 0.5625, 1.375], EntityState::Boat(_) => [1.375, 0.5625, 1.375],
EntityData::Cat(_) => [0.6, 0.7, 0.6], EntityState::Cat(_) => [0.6, 0.7, 0.6],
EntityData::CaveSpider(_) => [0.7, 0.5, 0.7], EntityState::CaveSpider(_) => [0.7, 0.5, 0.7],
EntityData::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size? EntityState::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size?
EntityData::Cod(_) => [0.5, 0.3, 0.5], EntityState::Cod(_) => [0.5, 0.3, 0.5],
EntityData::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size? EntityState::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityData::Creeper(_) => [0.6, 1.7, 0.6], EntityState::Creeper(_) => [0.6, 1.7, 0.6],
EntityData::Dolphin(_) => [0.9, 0.6, 0.9], EntityState::Dolphin(_) => [0.9, 0.6, 0.9],
EntityData::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size? EntityState::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size?
EntityData::DragonFireball(_) => [1.0, 1.0, 1.0], EntityState::DragonFireball(_) => [1.0, 1.0, 1.0],
EntityData::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size? EntityState::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::ElderGuardian(_) => [1.9975, 1.9975, 1.9975], EntityState::ElderGuardian(_) => [1.9975, 1.9975, 1.9975],
EntityData::EndCrystal(_) => [2.0, 2.0, 2.0], EntityState::EndCrystal(_) => [2.0, 2.0, 2.0],
EntityData::EnderDragon(_) => [16.0, 8.0, 16.0], EntityState::EnderDragon(_) => [16.0, 8.0, 16.0],
EntityData::Enderman(_) => [0.6, 2.9, 0.6], EntityState::Enderman(_) => [0.6, 2.9, 0.6],
EntityData::Endermite(_) => [0.4, 0.3, 0.4], EntityState::Endermite(_) => [0.4, 0.3, 0.4],
EntityData::Evoker(_) => [0.6, 1.95, 0.6], EntityState::Evoker(_) => [0.6, 1.95, 0.6],
EntityData::EvokerFangs(_) => [0.5, 0.8, 0.5], EntityState::EvokerFangs(_) => [0.5, 0.8, 0.5],
EntityData::ExperienceOrb(_) => [0.5, 0.5, 0.5], EntityState::ExperienceOrb(_) => [0.5, 0.5, 0.5],
EntityData::EyeOfEnder(_) => [0.25, 0.25, 0.25], EntityState::EyeOfEnder(_) => [0.25, 0.25, 0.25],
EntityData::FallingBlock(_) => [0.98, 0.98, 0.98], EntityState::FallingBlock(_) => [0.98, 0.98, 0.98],
EntityData::FireworkRocket(_) => [0.25, 0.25, 0.25], EntityState::FireworkRocket(_) => [0.25, 0.25, 0.25],
EntityData::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size? EntityState::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityData::Ghast(_) => [4.0, 4.0, 4.0], EntityState::Ghast(_) => [4.0, 4.0, 4.0],
EntityData::Giant(_) => [3.6, 12.0, 3.6], EntityState::Giant(_) => [3.6, 12.0, 3.6],
EntityData::GlowItemFrame(_) => todo!("account for rotation"), EntityState::GlowItemFrame(_) => todo!("account for rotation"),
EntityData::GlowSquid(_) => [0.8, 0.8, 0.8], EntityState::GlowSquid(_) => [0.8, 0.8, 0.8],
EntityData::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size? EntityState::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size?
EntityData::Guardian(_) => [0.85, 0.85, 0.85], EntityState::Guardian(_) => [0.85, 0.85, 0.85],
EntityData::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size? EntityState::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityData::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? EntityState::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityData::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size? EntityState::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::Illusioner(_) => [0.6, 1.95, 0.6], EntityState::Illusioner(_) => [0.6, 1.95, 0.6],
EntityData::IronGolem(_) => [1.4, 2.7, 1.4], EntityState::IronGolem(_) => [1.4, 2.7, 1.4],
EntityData::Item(_) => [0.25, 0.25, 0.25], EntityState::Item(_) => [0.25, 0.25, 0.25],
EntityData::ItemFrame(_) => todo!("account for rotation"), EntityState::ItemFrame(_) => todo!("account for rotation"),
EntityData::Fireball(_) => [1.0, 1.0, 1.0], EntityState::Fireball(_) => [1.0, 1.0, 1.0],
EntityData::LeashKnot(_) => [0.375, 0.5, 0.375], EntityState::LeashKnot(_) => [0.375, 0.5, 0.375],
EntityData::LightningBolt(_) => [0.0, 0.0, 0.0], EntityState::LightningBolt(_) => [0.0, 0.0, 0.0],
EntityData::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size? EntityState::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size?
EntityData::LlamaSpit(_) => [0.25, 0.25, 0.25], EntityState::LlamaSpit(_) => [0.25, 0.25, 0.25],
EntityData::MagmaCube(e) => { EntityState::MagmaCube(e) => {
let s = e.get_size() as f64 * 0.51000005; let s = e.get_size() as f64 * 0.51000005;
[s, s, s] [s, s, s]
} }
EntityData::Marker(_) => [0.0, 0.0, 0.0], EntityState::Marker(_) => [0.0, 0.0, 0.0],
EntityData::Minecart(_) => [0.98, 0.7, 0.98], EntityState::Minecart(_) => [0.98, 0.7, 0.98],
EntityData::ChestMinecart(_) => [0.98, 0.7, 0.98], EntityState::ChestMinecart(_) => [0.98, 0.7, 0.98],
EntityData::CommandBlockMinecart(_) => [0.98, 0.7, 0.98], EntityState::CommandBlockMinecart(_) => [0.98, 0.7, 0.98],
EntityData::FurnaceMinecart(_) => [0.98, 0.7, 0.98], EntityState::FurnaceMinecart(_) => [0.98, 0.7, 0.98],
EntityData::HopperMinecart(_) => [0.98, 0.7, 0.98], EntityState::HopperMinecart(_) => [0.98, 0.7, 0.98],
EntityData::SpawnerMinecart(_) => [0.98, 0.7, 0.98], EntityState::SpawnerMinecart(_) => [0.98, 0.7, 0.98],
EntityData::TntMinecart(_) => [0.98, 0.7, 0.98], EntityState::TntMinecart(_) => [0.98, 0.7, 0.98],
EntityData::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? EntityState::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityData::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size? EntityState::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityData::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size? EntityState::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityData::Painting(_) => todo!("account for rotation and type"), EntityState::Painting(_) => todo!("account for rotation and type"),
EntityData::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size? EntityState::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityData::Parrot(_) => [0.5, 0.9, 0.5], EntityState::Parrot(_) => [0.5, 0.9, 0.5],
EntityData::Phantom(_) => [0.9, 0.5, 0.9], EntityState::Phantom(_) => [0.9, 0.5, 0.9],
EntityData::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size? EntityState::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size?
EntityData::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size? EntityState::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::PiglinBrute(_) => [0.6, 1.95, 0.6], EntityState::PiglinBrute(_) => [0.6, 1.95, 0.6],
EntityData::Pillager(_) => [0.6, 1.95, 0.6], EntityState::Pillager(_) => [0.6, 1.95, 0.6],
EntityData::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size? EntityState::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size?
EntityData::Tnt(_) => [0.98, 0.98, 0.98], EntityState::Tnt(_) => [0.98, 0.98, 0.98],
EntityData::Pufferfish(_) => [0.7, 0.7, 0.7], EntityState::Pufferfish(_) => [0.7, 0.7, 0.7],
EntityData::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size? EntityState::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size?
EntityData::Ravager(_) => [1.95, 2.2, 1.95], EntityState::Ravager(_) => [1.95, 2.2, 1.95],
EntityData::Salmon(_) => [0.7, 0.4, 0.7], EntityState::Salmon(_) => [0.7, 0.4, 0.7],
EntityData::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size? EntityState::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size?
EntityData::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated? EntityState::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated?
EntityData::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125], EntityState::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125],
EntityData::Silverfish(_) => [0.4, 0.3, 0.4], EntityState::Silverfish(_) => [0.4, 0.3, 0.4],
EntityData::Skeleton(_) => [0.6, 1.99, 0.6], EntityState::Skeleton(_) => [0.6, 1.99, 0.6],
EntityData::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? EntityState::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityData::Slime(e) => { EntityState::Slime(e) => {
let s = 0.51000005 * e.get_size() as f64; let s = 0.51000005 * e.get_size() as f64;
[s, s, s] [s, s, s]
} }
EntityData::SmallFireball(_) => [0.3125, 0.3125, 0.3125], EntityState::SmallFireball(_) => [0.3125, 0.3125, 0.3125],
EntityData::SnowGolem(_) => [0.7, 1.9, 0.7], EntityState::SnowGolem(_) => [0.7, 1.9, 0.7],
EntityData::Snowball(_) => [0.25, 0.25, 0.25], EntityState::Snowball(_) => [0.25, 0.25, 0.25],
EntityData::SpectralArrow(_) => [0.5, 0.5, 0.5], EntityState::SpectralArrow(_) => [0.5, 0.5, 0.5],
EntityData::Spider(_) => [1.4, 0.9, 1.4], EntityState::Spider(_) => [1.4, 0.9, 1.4],
EntityData::Squid(_) => [0.8, 0.8, 0.8], EntityState::Squid(_) => [0.8, 0.8, 0.8],
EntityData::Stray(_) => [0.6, 1.99, 0.6], EntityState::Stray(_) => [0.6, 1.99, 0.6],
EntityData::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size? EntityState::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size?
EntityData::Egg(_) => [0.25, 0.25, 0.25], EntityState::Egg(_) => [0.25, 0.25, 0.25],
EntityData::EnderPearl(_) => [0.25, 0.25, 0.25], EntityState::EnderPearl(_) => [0.25, 0.25, 0.25],
EntityData::ExperienceBottle(_) => [0.25, 0.25, 0.25], EntityState::ExperienceBottle(_) => [0.25, 0.25, 0.25],
EntityData::Potion(_) => [0.25, 0.25, 0.25], EntityState::Potion(_) => [0.25, 0.25, 0.25],
EntityData::Trident(_) => [0.5, 0.5, 0.5], EntityState::Trident(_) => [0.5, 0.5, 0.5],
EntityData::TraderLlama(_) => [0.9, 1.87, 0.9], EntityState::TraderLlama(_) => [0.9, 1.87, 0.9],
EntityData::TropicalFish(_) => [0.5, 0.4, 0.5], EntityState::TropicalFish(_) => [0.5, 0.4, 0.5],
EntityData::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size? EntityState::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size?
EntityData::Vex(_) => [0.4, 0.8, 0.4], EntityState::Vex(_) => [0.4, 0.8, 0.4],
EntityData::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size? EntityState::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::Vindicator(_) => [0.6, 1.95, 0.6], EntityState::Vindicator(_) => [0.6, 1.95, 0.6],
EntityData::WanderingTrader(_) => [0.6, 1.95, 0.6], EntityState::WanderingTrader(_) => [0.6, 1.95, 0.6],
EntityData::Witch(_) => [0.6, 1.95, 0.6], EntityState::Witch(_) => [0.6, 1.95, 0.6],
EntityData::Wither(_) => [0.9, 3.5, 0.9], EntityState::Wither(_) => [0.9, 3.5, 0.9],
EntityData::WitherSkeleton(_) => [0.7, 2.4, 0.7], EntityState::WitherSkeleton(_) => [0.7, 2.4, 0.7],
EntityData::WitherSkull(_) => [0.3125, 0.3125, 0.3125], EntityState::WitherSkull(_) => [0.3125, 0.3125, 0.3125],
EntityData::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size? EntityState::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size?
EntityData::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size? EntityState::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityData::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size? EntityState::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? EntityState::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityData::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size? EntityState::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size? EntityState::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose. EntityState::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose.
EntityData::FishingBobber(_) => [0.25, 0.25, 0.25], EntityState::FishingBobber(_) => [0.25, 0.25, 0.25],
}; };
aabb_from_bottom_and_size(self.new_position, dims.into()) aabb_from_bottom_and_size(self.new_position, dims.into())
@ -536,7 +533,7 @@ impl Entity {
/// ///
/// Is `None` if there is no initial metadata. /// Is `None` if there is no initial metadata.
pub(crate) fn initial_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> { pub(crate) fn initial_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> {
self.data.initial_metadata().map(|meta| SetEntityMetadata { self.state.initial_metadata().map(|meta| SetEntityMetadata {
entity_id: VarInt(this_id.to_network_id()), entity_id: VarInt(this_id.to_network_id()),
metadata: RawBytes(meta), metadata: RawBytes(meta),
}) })
@ -546,23 +543,23 @@ impl Entity {
/// ///
/// Is `None` if this entity's metadata has not been modified. /// Is `None` if this entity's metadata has not been modified.
pub(crate) fn updated_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> { pub(crate) fn updated_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> {
self.data.updated_metadata().map(|meta| SetEntityMetadata { self.state.updated_metadata().map(|meta| SetEntityMetadata {
entity_id: VarInt(this_id.to_network_id()), entity_id: VarInt(this_id.to_network_id()),
metadata: RawBytes(meta), metadata: RawBytes(meta),
}) })
} }
pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option<EntitySpawnPacket> { pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option<EntitySpawnPacket> {
match &self.data { match &self.state {
EntityData::Marker(_) => None, EntityState::Marker(_) => None,
EntityData::ExperienceOrb(_) => { EntityState::ExperienceOrb(_) => {
Some(EntitySpawnPacket::ExperienceOrb(AddExperienceOrb { Some(EntitySpawnPacket::ExperienceOrb(AddExperienceOrb {
entity_id: VarInt(this_id.to_network_id()), entity_id: VarInt(this_id.to_network_id()),
position: self.new_position, position: self.new_position,
count: 0, // TODO count: 0, // TODO
})) }))
} }
EntityData::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer { EntityState::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer {
entity_id: VarInt(this_id.to_network_id()), entity_id: VarInt(this_id.to_network_id()),
player_uuid: self.uuid, player_uuid: self.uuid,
position: self.new_position, position: self.new_position,

View file

@ -3,7 +3,7 @@
#![allow(clippy::all, missing_docs)] #![allow(clippy::all, missing_docs)]
use crate::block::{BlockPos, BlockState}; use crate::block::{BlockPos, BlockState};
use crate::entity::data::*; use crate::entity::state::*;
use crate::entity::EntityId; use crate::entity::EntityId;
use crate::protocol_inner::{Encode, VarInt}; use crate::protocol_inner::{Encode, VarInt};
use crate::text::Text; use crate::text::Text;

View file

@ -52,17 +52,23 @@
//! use valence::server::{Server, ShutdownResult}; //! use valence::server::{Server, ShutdownResult};
//! //!
//! pub fn main() -> ShutdownResult { //! pub fn main() -> ShutdownResult {
//! valence::start_server(Game) //! valence::start_server(Game, ())
//! } //! }
//! //!
//! struct Game; //! struct Game;
//! //!
//! impl Config for Game { //! impl Config for Game {
//! type ChunkData = ();
//! type ClientData = ();
//! type EntityData = ();
//! type ServerData = ();
//! type WorldData = ();
//!
//! fn max_connections(&self) -> usize { //! fn max_connections(&self) -> usize {
//! 256 //! 256
//! } //! }
//! //!
//! fn update(&self, server: &mut Server) { //! fn update(&self, server: &mut Server<Self>) {
//! server.clients.retain(|_, client| { //! server.clients.retain(|_, client| {
//! if client.created_tick() == server.shared.current_tick() { //! if client.created_tick() == server.shared.current_tick() {
//! println!("{} joined!", client.username()); //! println!("{} joined!", client.username());

View file

@ -1,5 +1,4 @@
/// Reading and writing whole packets. /// Reading and writing whole packets.
use std::io::Read; use std::io::Read;
use std::time::Duration; use std::time::Duration;

View file

@ -51,15 +51,17 @@ use crate::{Ticks, PROTOCOL_VERSION, VERSION_NAME};
/// Contains the entire state of a running Minecraft server, accessible from /// Contains the entire state of a running Minecraft server, accessible from
/// within the [update](crate::config::Config::update) loop. /// within the [update](crate::config::Config::update) loop.
pub struct Server { pub struct Server<C: Config> {
/// Custom data.
pub data: C::ServerData,
/// A handle to this server's [`SharedServer`]. /// A handle to this server's [`SharedServer`].
pub shared: SharedServer, pub shared: SharedServer<C>,
/// All of the clients in the server. /// All of the clients in the server.
pub clients: Clients, pub clients: Clients<C>,
/// All of entities in the server. /// All of entities in the server.
pub entities: Entities, pub entities: Entities<C>,
/// All of the worlds in the server. /// All of the worlds in the server.
pub worlds: Worlds, pub worlds: Worlds<C>,
} }
/// A handle to a Minecraft server containing the subset of functionality which /// A handle to a Minecraft server containing the subset of functionality which
@ -69,11 +71,17 @@ pub struct Server {
/// be shared between threads. /// be shared between threads.
/// ///
/// [update]: crate::config::Config::update /// [update]: crate::config::Config::update
#[derive(Clone)]
pub struct SharedServer(Arc<SharedServerInner>);
struct SharedServerInner { pub struct SharedServer<C: Config>(Arc<SharedServerInner<C>>);
cfg: Box<dyn Config>,
impl<C: Config> Clone for SharedServer<C> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
struct SharedServerInner<C: Config> {
cfg: C,
address: SocketAddr, address: SocketAddr,
tick_rate: Ticks, tick_rate: Ticks,
online_mode: bool, online_mode: bool,
@ -130,10 +138,10 @@ pub type ShutdownResult = Result<(), Box<dyn Error + Send + Sync + 'static>>;
pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>); pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>);
pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>); pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>);
impl SharedServer { impl<C: Config> SharedServer<C> {
/// Gets a reference to the config object used to start the server. /// Gets a reference to the config object used to start the server.
pub fn config(&self) -> &(impl Config + ?Sized) { pub fn config(&self) -> &C {
self.0.cfg.as_ref() &self.0.cfg
} }
/// Gets the socket address this server is bound to. /// Gets the socket address this server is bound to.
@ -240,12 +248,13 @@ impl SharedServer {
/// ///
/// The function returns once the server has shut down, a runtime error /// The function returns once the server has shut down, a runtime error
/// occurs, or the configuration is found to be invalid. /// occurs, or the configuration is found to be invalid.
pub fn start_server(config: impl Config) -> ShutdownResult { pub fn start_server<C: Config>(config: C, data: C::ServerData) -> ShutdownResult {
let shared = setup_server(config).map_err(Box::<dyn Error + Send + Sync + 'static>::from)?; let shared = setup_server(config).map_err(Box::<dyn Error + Send + Sync + 'static>::from)?;
let _guard = shared.tokio_handle().enter(); let _guard = shared.tokio_handle().enter();
let mut server = Server { let mut server = Server {
data,
shared: shared.clone(), shared: shared.clone(),
clients: Clients::new(), clients: Clients::new(),
entities: Entities::new(), entities: Entities::new(),
@ -259,7 +268,7 @@ pub fn start_server(config: impl Config) -> ShutdownResult {
do_update_loop(&mut server) do_update_loop(&mut server)
} }
fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> { fn setup_server<C: Config>(cfg: C) -> anyhow::Result<SharedServer<C>> {
let max_connections = cfg.max_connections(); let max_connections = cfg.max_connections();
let address = cfg.address(); let address = cfg.address();
let tick_rate = cfg.tick_rate(); let tick_rate = cfg.tick_rate();
@ -360,7 +369,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> {
}; };
let server = SharedServerInner { let server = SharedServerInner {
cfg: Box::new(cfg), cfg,
address, address,
tick_rate, tick_rate,
online_mode, online_mode,
@ -385,7 +394,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> {
Ok(SharedServer(Arc::new(server))) Ok(SharedServer(Arc::new(server)))
} }
fn do_update_loop(server: &mut Server) -> ShutdownResult { fn do_update_loop<C: Config>(server: &mut Server<C>) -> ShutdownResult {
let mut tick_start = Instant::now(); let mut tick_start = Instant::now();
let shared = server.shared.clone(); let shared = server.shared.clone();
@ -441,7 +450,7 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult {
} }
} }
fn join_player(server: &mut Server, msg: NewClientMessage) { fn join_player<C: Config>(server: &mut Server<C>, msg: NewClientMessage) {
let (clientbound_tx, clientbound_rx) = flume::bounded(server.shared.0.outgoing_packet_capacity); let (clientbound_tx, clientbound_rx) = flume::bounded(server.shared.0.outgoing_packet_capacity);
let (serverbound_tx, serverbound_rx) = flume::bounded(server.shared.0.incoming_packet_capacity); let (serverbound_tx, serverbound_rx) = flume::bounded(server.shared.0.incoming_packet_capacity);
@ -450,7 +459,12 @@ fn join_player(server: &mut Server, msg: NewClientMessage) {
let _ = msg.reply.send(s2c_packet_channels); let _ = msg.reply.send(s2c_packet_channels);
let client = Client::new(c2s_packet_channels, &server.shared, msg.ncd); let client = Client::new(
c2s_packet_channels,
&server.shared,
msg.ncd,
C::ClientData::default(),
);
server.clients.insert(client); server.clients.insert(client);
} }
@ -460,7 +474,7 @@ struct Codec {
dec: Decoder<OwnedReadHalf>, dec: Decoder<OwnedReadHalf>,
} }
async fn do_accept_loop(server: SharedServer) { async fn do_accept_loop<C: Config>(server: SharedServer<C>) {
log::trace!("entering accept loop"); log::trace!("entering accept loop");
let listener = match TcpListener::bind(server.0.address).await { let listener = match TcpListener::bind(server.0.address).await {
@ -502,8 +516,8 @@ async fn do_accept_loop(server: SharedServer) {
} }
} }
async fn handle_connection( async fn handle_connection<C: Config>(
server: SharedServer, server: SharedServer<C>,
stream: TcpStream, stream: TcpStream,
remote_addr: SocketAddr, remote_addr: SocketAddr,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@ -533,8 +547,8 @@ async fn handle_connection(
} }
} }
async fn handle_status( async fn handle_status<C: Config>(
server: SharedServer, server: SharedServer<C>,
c: &mut Codec, c: &mut Codec,
remote_addr: SocketAddr, remote_addr: SocketAddr,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@ -585,8 +599,8 @@ async fn handle_status(
} }
/// Handle the login process and return the new player's data if successful. /// Handle the login process and return the new player's data if successful.
async fn handle_login( async fn handle_login<C: Config>(
server: &SharedServer, server: &SharedServer<C>,
c: &mut Codec, c: &mut Codec,
remote_addr: SocketAddr, remote_addr: SocketAddr,
) -> anyhow::Result<Option<NewClientData>> { ) -> anyhow::Result<Option<NewClientData>> {
@ -725,7 +739,11 @@ async fn handle_login(
Ok(Some(npd)) Ok(Some(npd))
} }
async fn handle_play(server: &SharedServer, c: Codec, ncd: NewClientData) -> anyhow::Result<()> { async fn handle_play<C: Config>(
server: &SharedServer<C>,
c: Codec,
ncd: NewClientData,
) -> anyhow::Result<()> {
let (reply_tx, reply_rx) = oneshot::channel(); let (reply_tx, reply_rx) = oneshot::channel();
server server

View file

@ -6,6 +6,7 @@ use rayon::iter::{IndexedParallelIterator, ParallelIterator};
use vek::{Aabb, Vec3}; use vek::{Aabb, Vec3};
use crate::bvh::{Bvh, Node}; use crate::bvh::{Bvh, Node};
use crate::config::Config;
use crate::entity::{Entities, EntityId}; use crate::entity::{Entities, EntityId};
use crate::util::ray_box_intersect; use crate::util::ray_box_intersect;
use crate::world::WorldId; use crate::world::WorldId;
@ -225,7 +226,7 @@ impl SpatialIndex {
self.bvh.par_iter().map(|(&id, bb)| (id, bb)) self.bvh.par_iter().map(|(&id, bb)| (id, bb))
} }
pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) { pub(crate) fn update<C: Config>(&mut self, entities: &Entities<C>, id: WorldId) {
self.bvh.build( self.bvh.build(
entities entities
.iter() .iter()

View file

@ -5,6 +5,7 @@ use std::iter::FusedIterator;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use crate::chunk::Chunks; use crate::chunk::Chunks;
use crate::config::Config;
use crate::dimension::DimensionId; use crate::dimension::DimensionId;
use crate::player_list::PlayerList; use crate::player_list::PlayerList;
use crate::server::SharedServer; use crate::server::SharedServer;
@ -12,9 +13,9 @@ use crate::slotmap::{Key, SlotMap};
use crate::spatial_index::SpatialIndex; use crate::spatial_index::SpatialIndex;
/// A container for all [`World`]s on a [`Server`](crate::server::Server). /// A container for all [`World`]s on a [`Server`](crate::server::Server).
pub struct Worlds { pub struct Worlds<C: Config> {
sm: SlotMap<World>, sm: SlotMap<World<C>>,
server: SharedServer, server: SharedServer<C>,
} }
/// An identifier for a [`World`] on the server. /// An identifier for a [`World`] on the server.
@ -34,8 +35,8 @@ impl WorldId {
pub const NULL: Self = Self(Key::NULL); pub const NULL: Self = Self(Key::NULL);
} }
impl Worlds { impl<C: Config> Worlds<C> {
pub(crate) fn new(server: SharedServer) -> Self { pub(crate) fn new(server: SharedServer<C>) -> Self {
Self { Self {
sm: SlotMap::new(), sm: SlotMap::new(),
server, server,
@ -44,8 +45,9 @@ impl Worlds {
/// Creates a new world on the server with the provided dimension. A /// Creates a new world on the server with the provided dimension. A
/// reference to the world along with its ID is returned. /// reference to the world along with its ID is returned.
pub fn create(&mut self, dim: DimensionId) -> (WorldId, &mut World) { pub fn create(&mut self, dim: DimensionId, data: C::WorldData) -> (WorldId, &mut World<C>) {
let (id, world) = self.sm.insert(World { let (id, world) = self.sm.insert(World {
data,
spatial_index: SpatialIndex::new(), spatial_index: SpatialIndex::new(),
chunks: Chunks::new(self.server.clone(), dim), chunks: Chunks::new(self.server.clone(), dim),
meta: WorldMeta { meta: WorldMeta {
@ -71,7 +73,7 @@ impl Worlds {
/// `f` returns `true`. /// `f` returns `true`.
/// ///
/// All worlds are visited in an unspecified order. /// All worlds are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) { pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World<C>) -> bool) {
self.sm.retain(|k, v| f(WorldId(k), v)) self.sm.retain(|k, v| f(WorldId(k), v))
} }
@ -82,47 +84,49 @@ impl Worlds {
/// Returns a shared reference to the world with the given ID. If /// Returns a shared reference to the world with the given ID. If
/// the ID is invalid, then `None` is returned. /// the ID is invalid, then `None` is returned.
pub fn get(&self, world: WorldId) -> Option<&World> { pub fn get(&self, world: WorldId) -> Option<&World<C>> {
self.sm.get(world.0) self.sm.get(world.0)
} }
/// Returns an exclusive reference to the world with the given ID. If the /// Returns an exclusive reference to the world with the given ID. If the
/// ID is invalid, then `None` is returned. /// ID is invalid, then `None` is returned.
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> { pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World<C>> {
self.sm.get_mut(world.0) self.sm.get_mut(world.0)
} }
/// Returns an immutable iterator over all worlds on the server in an /// Returns an immutable iterator over all worlds on the server in an
/// unspecified order. /// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World<C>)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (WorldId(k), v)) self.sm.iter().map(|(k, v)| (WorldId(k), v))
} }
/// Returns a mutable iterator over all worlds on the server in an /// Returns a mutable iterator over all worlds on the server in an
/// unspecified ordder. /// unspecified ordder.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ { pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World<C>)> + '_ {
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v)) self.sm.iter_mut().map(|(k, v)| (WorldId(k), v))
} }
/// Returns a parallel immutable iterator over all worlds on the server in /// Returns a parallel immutable iterator over all worlds on the server in
/// an unspecified order. /// an unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ { pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World<C>)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (WorldId(k), v)) self.sm.par_iter().map(|(k, v)| (WorldId(k), v))
} }
/// Returns a parallel mutable iterator over all worlds on the server in an /// Returns a parallel mutable iterator over all worlds on the server in an
/// unspecified order. /// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ { pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World<C>)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v)) self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
} }
} }
/// A space for chunks, entities, and clients to occupy. /// A space for chunks, entities, and clients to occupy.
pub struct World { pub struct World<C: Config> {
/// Custom data.
pub data: C::WorldData,
/// Contains all of the entities in this world. /// Contains all of the entities in this world.
pub spatial_index: SpatialIndex, pub spatial_index: SpatialIndex,
/// All of the chunks in this world. /// All of the chunks in this world.
pub chunks: Chunks, pub chunks: Chunks<C>,
/// This world's metadata. /// This world's metadata.
pub meta: WorldMeta, pub meta: WorldMeta,
} }