From 865ab766998ebfab23d453587b9414cbfe01bc91 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 15 Jul 2022 20:40:39 -0700 Subject: [PATCH] Add custom data --- build/entity.rs | 4 +- examples/conway.rs | 138 +++++++------- examples/cow_sphere.rs | 58 +++--- examples/raycast.rs | 61 +++--- examples/terrain.rs | 35 ++-- src/chunk.rs | 68 ++----- src/client.rs | 56 +++--- src/config.rs | 37 ++-- src/entity.rs | 311 +++++++++++++++---------------- src/entity/{data.rs => state.rs} | 0 src/entity/types.rs | 2 +- src/lib.rs | 10 +- src/protocol_inner/codec.rs | 1 - src/server.rs | 70 ++++--- src/spatial_index.rs | 3 +- src/world.rs | 34 ++-- 16 files changed, 466 insertions(+), 422 deletions(-) rename src/entity/{data.rs => state.rs} (100%) diff --git a/build/entity.rs b/build/entity.rs index 7852500..8e2f0e7 100644 --- a/build/entity.rs +++ b/build/entity.rs @@ -2470,11 +2470,11 @@ pub fn build() -> anyhow::Result<()> { #(#entity_structs)* /// 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),)* } - impl EntityData { + impl EntityState { pub(super) fn new(kind: EntityKind) -> Self { match kind { #(EntityKind::#entity_kind_variants => Self::#entity_kind_variants(#entity_kind_variants::new()),)* diff --git a/examples/conway.rs b/examples/conway.rs index c21e3bd..d17c0bd 100644 --- a/examples/conway.rs +++ b/examples/conway.rs @@ -1,19 +1,17 @@ -use std::collections::HashMap; use std::mem; use std::net::SocketAddr; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Mutex; use log::LevelFilter; use num::Integer; use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; use valence::biome::Biome; use valence::block::BlockState; -use valence::client::{ClientId, Event, Hand}; +use valence::client::{Event, Hand}; use valence::config::{Config, ServerListPing}; use valence::dimension::{Dimension, DimensionId}; -use valence::entity::data::Pose; -use valence::entity::{EntityData, EntityId, EntityKind}; +use valence::entity::state::Pose; +use valence::entity::{EntityId, EntityKind, EntityState}; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; use valence::{async_trait, ident}; @@ -24,27 +22,32 @@ pub fn main() -> ShutdownResult { .parse_default_env() .init(); - valence::start_server(Game { - player_count: AtomicUsize::new(0), - state: Mutex::new(State { - player_entities: HashMap::new(), + valence::start_server( + Game { + player_count: AtomicUsize::new(0), + }, + ServerData { board: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(), board_buf: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(), - }), - }) + }, + ) } struct Game { player_count: AtomicUsize, - state: Mutex, } -struct State { - player_entities: HashMap, +struct ServerData { board: Box<[bool]>, board_buf: Box<[bool]>, } +#[derive(Default)] +struct ClientData { + /// The client's player entity. + player: EntityId, +} + const MAX_PLAYERS: usize = 10; const SIZE_X: usize = 100; @@ -53,6 +56,12 @@ const BOARD_Y: i32 = 50; #[async_trait] impl Config for Game { + type ChunkData = (); + type ClientData = ClientData; + type EntityData = (); + type ServerData = ServerData; + type WorldData = (); + fn max_connections(&self) -> usize { // We want status pings to be successful even if the server is full. MAX_PLAYERS + 64 @@ -80,7 +89,7 @@ impl Config for Game { async fn server_list_ping( &self, - _server: &SharedServer, + _server: &SharedServer, _remote_addr: SocketAddr, ) -> ServerListPing { ServerListPing::Respond { @@ -91,18 +100,18 @@ impl Config for Game { } } - fn init(&self, server: &mut Server) { - let world = server.worlds.create(DimensionId::default()).1; + fn init(&self, server: &mut Server) { + let world = server.worlds.create(DimensionId::default(), ()).1; world.meta.set_flat(true); 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 { - 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) { let (world_id, world) = server.worlds.iter_mut().next().unwrap(); let spawn_pos = [ @@ -111,13 +120,7 @@ impl Config for Game { SIZE_Z as f64 / 2.0, ]; - let State { - player_entities, - board, - board_buf, - } = &mut *self.state.lock().unwrap(); - - server.clients.retain(|client_id, client| { + server.clients.retain(|_, client| { if client.created_tick() == server.shared.current_tick() { if self .player_count @@ -142,14 +145,13 @@ impl Config for Game { None, ); - player_entities.insert( - client_id, - server - .entities - .create_with_uuid(EntityKind::Player, client.uuid()) - .unwrap() - .0, - ); + let player_id = server + .entities + .create_with_uuid(EntityKind::Player, client.uuid(), ()) + .unwrap() + .0; + + client.data = ClientData { player: player_id }; 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()); @@ -157,8 +159,7 @@ impl Config for Game { if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); - let id = player_entities.remove(&client_id).unwrap(); - server.entities.delete(id); + server.entities.delete(client.data.player); world.meta.player_list_mut().remove(client.uuid()); return false; } @@ -166,11 +167,8 @@ impl Config for Game { true }); - for (client_id, client) in server.clients.iter_mut() { - let player = server - .entities - .get_mut(player_entities[&client_id]) - .unwrap(); + for (_, client) in server.clients.iter_mut() { + let player = server.entities.get_mut(client.data.player).unwrap(); while let Some(event) = client.pop_event() { match event { @@ -179,7 +177,8 @@ impl Config for Game { && (0..SIZE_Z as i32).contains(&position.z) && 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 { .. } => { @@ -195,29 +194,29 @@ impl Config for Game { player.set_on_ground(client.on_ground()); } Event::StartSneaking => { - if let EntityData::Player(e) = player.data_mut() { + if let EntityState::Player(e) = &mut player.state { e.set_crouching(true); e.set_pose(Pose::Sneaking); } } 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_crouching(false); } } Event::StartSprinting => { - if let EntityData::Player(e) = player.data_mut() { + if let EntityState::Player(e) = &mut player.state { e.set_sprinting(true); } } Event::StopSprinting => { - if let EntityData::Player(e) = player.data_mut() { + if let EntityState::Player(e) = &mut player.state { e.set_sprinting(false); } } Event::ArmSwing(hand) => { - if let EntityData::Player(e) = player.data_mut() { + if let EntityState::Player(e) = &mut player.state { match hand { Hand::Main => e.trigger_swing_main_arm(), Hand::Off => e.trigger_swing_offhand(), @@ -233,31 +232,36 @@ impl Config for Game { return; } - board_buf.par_iter_mut().enumerate().for_each(|(i, cell)| { - let cx = (i % SIZE_X) as i32; - let cz = (i / SIZE_Z) as i32; + server + .data + .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; - for z in cz - 1..=cz + 1 { - for x in cx - 1..=cx + 1 { - if !(x == cx && z == cz) { - let i = x.rem_euclid(SIZE_X as i32) as usize - + z.rem_euclid(SIZE_Z as i32) as usize * SIZE_X; - if board[i] { - live_count += 1; + let mut live_count = 0; + for z in cz - 1..=cz + 1 { + for x in cx - 1..=cx + 1 { + if !(x == cx && z == cz) { + let i = x.rem_euclid(SIZE_X as i32) as usize + + z.rem_euclid(SIZE_Z as i32) as usize * SIZE_X; + if server.data.board[i] { + live_count += 1; + } } } } - } - if board[cx as usize + cz as usize * SIZE_X] { - *cell = (2..=3).contains(&live_count); - } else { - *cell = live_count == 3; - } - }); + if server.data.board[cx as usize + cz as usize * SIZE_X] { + *cell = (2..=3).contains(&live_count); + } else { + *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; @@ -273,7 +277,7 @@ impl Config for Game { let cell_z = chunk_z * 16 + 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 } else { BlockState::DIRT diff --git a/examples/cow_sphere.rs b/examples/cow_sphere.rs index 3eb3ff4..abc114e 100644 --- a/examples/cow_sphere.rs +++ b/examples/cow_sphere.rs @@ -1,7 +1,6 @@ use std::f64::consts::TAU; use std::net::SocketAddr; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Mutex; use log::LevelFilter; use valence::async_trait; @@ -21,23 +20,34 @@ pub fn main() -> ShutdownResult { .parse_default_env() .init(); - valence::start_server(Game { - player_count: AtomicUsize::new(0), - cows: Mutex::new(Vec::new()), - }) + valence::start_server( + Game { + player_count: AtomicUsize::new(0), + }, + ServerData { cows: Vec::new() }, + ) } struct Game { player_count: AtomicUsize, - cows: Mutex>, +} + +struct ServerData { + cows: Vec, } 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] impl Config for Game { + type ChunkData = (); + type ClientData = (); + type EntityData = (); + type ServerData = ServerData; + type WorldData = (); + fn max_connections(&self) -> usize { // We want status pings to be successful even if the server is full. MAX_PLAYERS + 64 @@ -50,7 +60,7 @@ impl Config for Game { async fn server_list_ping( &self, - _server: &SharedServer, + _server: &SharedServer, _remote_addr: SocketAddr, ) -> ServerListPing { ServerListPing::Respond { @@ -61,27 +71,27 @@ impl Config for Game { } } - fn init(&self, server: &mut Server) { - let (world_id, world) = server.worlds.create(DimensionId::default()); + fn init(&self, server: &mut Server) { + let (world_id, world) = server.worlds.create(DimensionId::default(), ()); world.meta.set_flat(true); let size = 5; for z 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); - self.cows.lock().unwrap().extend((0..200).map(|_| { - let (id, e) = server.entities.create(EntityKind::Cow); + server.data.cows.extend((0..200).map(|_| { + let (id, e) = server.entities.create(EntityKind::Cow, ()); e.set_world(world_id); id })); } - fn update(&self, server: &mut Server) { + fn update(&self, server: &mut Server) { let (world_id, world) = server.worlds.iter_mut().next().unwrap(); server.clients.retain(|_, client| { @@ -122,10 +132,10 @@ impl Config for Game { if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); world.meta.player_list_mut().remove(client.uuid()); - false - } else { - true + return false; } + + true }); 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_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 player_pos = server @@ -149,11 +156,16 @@ impl Config for Game { // TODO: hardcoded eye pos. 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 cows.iter().cloned().zip(fibonacci_spiral(cow_count)) { + for (cow_id, p) in server + .data + .cows + .iter() + .cloned() + .zip(fibonacci_spiral(server.data.cows.len())) + { let cow = server.entities.get_mut(cow_id).expect("missing cow"); 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 (looking_yaw, looking_pitch) = diff --git a/examples/raycast.rs b/examples/raycast.rs index 853d2ec..8e18dbb 100644 --- a/examples/raycast.rs +++ b/examples/raycast.rs @@ -1,4 +1,3 @@ -use std::collections::HashSet; use std::net::SocketAddr; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -8,8 +7,9 @@ use valence::block::{BlockPos, BlockState}; use valence::client::GameMode; use valence::config::{Config, ServerListPing}; use valence::dimension::DimensionId; -use valence::entity::{EntityData, EntityKind}; +use valence::entity::{EntityKind, EntityState}; use valence::server::{Server, SharedServer, ShutdownResult}; +use valence::spatial_index::RaycastHit; use valence::text::{Color, TextFormat}; use valence::util::from_yaw_and_pitch; use vek::Vec3; @@ -20,9 +20,12 @@ pub fn main() -> ShutdownResult { .parse_default_env() .init(); - valence::start_server(Game { - player_count: AtomicUsize::new(0), - }) + valence::start_server( + Game { + player_count: AtomicUsize::new(0), + }, + (), + ) } struct Game { @@ -31,12 +34,19 @@ struct Game { 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; #[async_trait] 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 { // We want status pings to be successful even if the server is full. MAX_PLAYERS + 64 @@ -49,7 +59,7 @@ impl Config for Game { async fn server_list_ping( &self, - _server: &SharedServer, + _server: &SharedServer, _remote_addr: SocketAddr, ) -> ServerListPing { ServerListPing::Respond { @@ -60,14 +70,14 @@ impl Config for Game { } } - fn init(&self, server: &mut Server) { - let (world_id, world) = server.worlds.create(DimensionId::default()); + fn init(&self, server: &mut Server) { + let (world_id, world) = server.worlds.create(DimensionId::default(), ()); world.meta.set_flat(true); let size = 5; for z 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 { 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_position([offset + 0.5, SPAWN_POS.y as f64 + 1.0, 0.0]); sheep.set_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) { let (world_id, world) = server.worlds.iter_mut().next().unwrap(); - let mut hit_entities = HashSet::new(); - server.clients.retain(|_, client| { if client.created_tick() == server.shared.current_tick() { if self @@ -133,7 +136,7 @@ impl Config for Game { "Look at a sheep to change its ".italic() + "color".italic().color(Color::GREEN) + ".", - ) + ); } 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 direction = from_yaw_and_pitch(client.yaw() as f64, client.pitch() as f64); - - if let Some(hit) = world.spatial_index.raycast(origin, direction, |hit| { + let only_sheep = |hit: &RaycastHit| { server .entities .get(hit.entity) .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 }); - for (id, e) in server.entities.iter_mut() { - if let EntityData::Sheep(sheep) = e.data_mut() { - if hit_entities.contains(&id) { + for (_, e) in server.entities.iter_mut() { + if let EntityState::Sheep(sheep) = &mut e.state { + if e.data { sheep.set_sheep_state(5); } else { sheep.set_sheep_state(0); } } + e.data = false; } } } diff --git a/examples/terrain.rs b/examples/terrain.rs index 7cdcc68..94d01e6 100644 --- a/examples/terrain.rs +++ b/examples/terrain.rs @@ -24,14 +24,17 @@ pub fn main() -> ShutdownResult { let seed = rand::random(); - valence::start_server(Game { - player_count: AtomicUsize::new(0), - density_noise: SuperSimplex::new().set_seed(seed), - hilly_noise: SuperSimplex::new().set_seed(seed.wrapping_add(1)), - stone_noise: SuperSimplex::new().set_seed(seed.wrapping_add(2)), - gravel_noise: SuperSimplex::new().set_seed(seed.wrapping_add(3)), - grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)), - }) + valence::start_server( + Game { + player_count: AtomicUsize::new(0), + density_noise: SuperSimplex::new().set_seed(seed), + hilly_noise: SuperSimplex::new().set_seed(seed.wrapping_add(1)), + stone_noise: SuperSimplex::new().set_seed(seed.wrapping_add(2)), + gravel_noise: SuperSimplex::new().set_seed(seed.wrapping_add(3)), + grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)), + }, + (), + ) } struct Game { @@ -47,6 +50,12 @@ const MAX_PLAYERS: usize = 10; #[async_trait] impl Config for Game { + type ChunkData = (); + type ClientData = (); + type EntityData = (); + type ServerData = (); + type WorldData = (); + fn max_connections(&self) -> usize { // We want status pings to be successful even if the server is full. MAX_PLAYERS + 64 @@ -59,7 +68,7 @@ impl Config for Game { async fn server_list_ping( &self, - _server: &SharedServer, + _server: &SharedServer, _remote_addr: SocketAddr, ) -> ServerListPing { ServerListPing::Respond { @@ -70,12 +79,12 @@ impl Config for Game { } } - fn init(&self, server: &mut Server) { - let (_, world) = server.worlds.create(DimensionId::default()); + fn init(&self, server: &mut Server) { + let (_, world) = server.worlds.create(DimensionId::default(), ()); world.meta.set_flat(true); } - fn update(&self, server: &mut Server) { + fn update(&self, server: &mut Server) { 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)); @@ -126,7 +135,7 @@ impl Config for Game { for pos in chunks_in_view_distance(ChunkPos::at(p.x, p.z), dist) { chunks_to_unload.remove(&pos); if world.chunks.get(pos).is_none() { - world.chunks.create(pos); + world.chunks.create(pos, ()); } } diff --git a/src/chunk.rs b/src/chunk.rs index f6a3348..b2aa7ea 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -15,6 +15,7 @@ use crate::biome::BiomeId; use crate::block::BlockState; use crate::block_pos::BlockPos; pub use crate::chunk_pos::ChunkPos; +use crate::config::Config; use crate::dimension::DimensionId; use crate::protocol_inner::packets::play::s2c::{ BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate, @@ -24,14 +25,14 @@ use crate::server::SharedServer; use crate::Ticks; /// A container for all [`Chunks`]s in a [`World`](crate::world::World). -pub struct Chunks { - chunks: HashMap, - server: SharedServer, +pub struct Chunks { + chunks: HashMap>, + server: SharedServer, dimension: DimensionId, } -impl Chunks { - pub(crate) fn new(server: SharedServer, dimension: DimensionId) -> Self { +impl Chunks { + pub(crate) fn new(server: SharedServer, dimension: DimensionId) -> Self { Self { chunks: HashMap::new(), server, @@ -49,9 +50,9 @@ impl Chunks { /// adjacent to it must also be loaded. It is also important that clients /// are not spawned within unloaded chunks via /// [`spawn`](crate::client::Client::spawn). - pub fn create(&mut self, pos: impl Into) -> &mut Chunk { + pub fn create(&mut self, pos: impl Into, data: C::ChunkData) -> &mut Chunk { 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()) { Entry::Occupied(mut oe) => { @@ -78,14 +79,14 @@ impl Chunks { /// Gets a shared reference to the chunk at the provided position. /// /// If there is no chunk at the position, then `None` is returned. - pub fn get(&self, pos: impl Into) -> Option<&Chunk> { + pub fn get(&self, pos: impl Into) -> Option<&Chunk> { self.chunks.get(&pos.into()) } /// Gets an exclusive reference to the chunk at the provided position. /// /// If there is no chunk at the position, then `None` is returned. - pub fn get_mut(&mut self, pos: impl Into) -> Option<&mut Chunk> { + pub fn get_mut(&mut self, pos: impl Into) -> Option<&mut Chunk> { self.chunks.get_mut(&pos.into()) } @@ -96,25 +97,25 @@ impl Chunks { /// Returns an immutable iterator over all chunks in the world in an /// unspecified order. - pub fn iter(&self) -> impl FusedIterator + Clone + '_ { + pub fn iter(&self) -> impl FusedIterator)> + Clone + '_ { self.chunks.iter().map(|(&pos, chunk)| (pos, chunk)) } /// Returns a mutable iterator over all chunks in the world in an /// unspecified order. - pub fn iter_mut(&mut self) -> impl FusedIterator + '_ { + pub fn iter_mut(&mut self) -> impl FusedIterator)> + '_ { self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk)) } /// Returns a parallel immutable iterator over all chunks in the world in an /// unspecified order. - pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ { + pub fn par_iter(&self) -> impl ParallelIterator)> + Clone + '_ { self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk)) } /// Returns a parallel mutable iterator over all chunks in the world in an /// unspecified order. - pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ { + pub fn par_iter_mut(&mut self) -> impl ParallelIterator)> + '_ { 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). /// Every 4x4x4 segment of blocks in a chunk corresponds to a biome. -pub struct Chunk { +pub struct Chunk { + /// Custom data. + pub data: C::ChunkData, sections: Box<[ChunkSection]>, // TODO block_entities: HashMap, /// The MOTION_BLOCKING heightmap @@ -191,8 +194,8 @@ pub struct Chunk { created_tick: Ticks, } -impl Chunk { - pub(crate) fn new(section_count: u32, current_tick: Ticks) -> Self { +impl Chunk { + pub(crate) fn new(section_count: u32, current_tick: Ticks, data: C::ChunkData) -> Self { let sect = ChunkSection { blocks: [BlockState::AIR.to_raw(); 4096], modified_count: 1, // Must be >0 so the chunk is initialized. @@ -201,6 +204,7 @@ impl Chunk { }; let mut chunk = Self { + data, sections: vec![sect; section_count as usize].into(), heightmap: Vec::new(), created_tick: current_tick, @@ -547,35 +551,3 @@ fn encode_paletted_container( fn log2_ceil(n: usize) -> 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)); - } -} diff --git a/src/client.rs b/src/client.rs index f27e95f..c0a1f77 100644 --- a/src/client.rs +++ b/src/client.rs @@ -16,6 +16,7 @@ use vek::Vec3; use crate::biome::Biome; use crate::block_pos::BlockPos; use crate::chunk_pos::ChunkPos; +use crate::config::Config; use crate::dimension::DimensionId; use crate::entity::types::Player; 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 /// are not automatically deleted. It is your responsibility to delete them once /// they disconnect. This can be checked with [`Client::is_disconnected`]. -pub struct Clients { - sm: SlotMap, +pub struct Clients { + sm: SlotMap>, } -impl Clients { +impl Clients { pub(crate) fn new() -> Self { Self { sm: SlotMap::new() } } - pub(crate) fn insert(&mut self, client: Client) -> (ClientId, &mut Client) { + pub(crate) fn insert(&mut self, client: Client) -> (ClientId, &mut Client) { let (id, client) = self.sm.insert(client); (ClientId(id), client) } @@ -73,7 +74,7 @@ impl Clients { /// which `f` returns `true`. /// /// 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) -> bool) { 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 /// the ID is invalid, then `None` is returned. - pub fn get(&self, client: ClientId) -> Option<&Client> { + pub fn get(&self, client: ClientId) -> Option<&Client> { self.sm.get(client.0) } /// Returns an exclusive reference to the client with the given ID. If the /// 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> { self.sm.get_mut(client.0) } /// Returns an immutable iterator over all clients on the server in an /// unspecified order. - pub fn iter(&self) -> impl FusedIterator + Clone + '_ { + pub fn iter(&self) -> impl FusedIterator)> + Clone + '_ { self.sm.iter().map(|(k, v)| (ClientId(k), v)) } /// Returns a mutable iterator over all clients on the server in an /// unspecified order. - pub fn iter_mut(&mut self) -> impl FusedIterator + '_ { + pub fn iter_mut(&mut self) -> impl FusedIterator)> + '_ { self.sm.iter_mut().map(|(k, v)| (ClientId(k), v)) } /// Returns a parallel immutable iterator over all clients on the server in /// an unspecified order. - pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ { + pub fn par_iter(&self) -> impl ParallelIterator)> + Clone + '_ { self.sm.par_iter().map(|(k, v)| (ClientId(k), v)) } /// Returns a parallel mutable iterator over all clients on the server in an /// unspecified order. - pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ { + pub fn par_iter_mut( + &mut self, + ) -> impl ParallelIterator)> + '_ { 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 /// was done primarily to enable multithreaded client updates. -pub struct Client { +pub struct Client { + /// Custom data. + pub data: C::ClientData, /// Setting this to `None` disconnects the client. send: SendOpt, recv: Receiver, @@ -227,15 +232,17 @@ pub(crate) struct ClientFlags { _pad: u8, } -impl Client { +impl Client { pub(crate) fn new( packet_channels: C2sPacketChannels, - server: &SharedServer, + server: &SharedServer, ncd: NewClientData, + data: C::ClientData, ) -> Self { let (send, recv) = packet_channels; Self { + data, send: Some(send), recv, created_tick: server.current_tick(), @@ -584,16 +591,16 @@ impl Client { 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) { self.events.clear(); for _ in 0..self.recv.len() { self.handle_serverbound_packet(entities, self.recv.try_recv().unwrap()); } } - fn handle_serverbound_packet(&mut self, entities: &Entities, pkt: C2sPlayPacket) { - fn handle_movement_packet( - client: &mut Client, + fn handle_serverbound_packet(&mut self, entities: &Entities, pkt: C2sPlayPacket) { + fn handle_movement_packet( + client: &mut Client, _vehicle: bool, new_position: Vec3, 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, + entities: &Entities, + worlds: &Worlds, + ) { // Mark the client as disconnected when appropriate. if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) { self.send = None; @@ -1298,8 +1310,8 @@ fn send_packet(send_opt: &mut SendOpt, pkt: impl Into) { } } -fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) { - for &code in entity.data().event_codes() { +fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) { + for &code in entity.state.event_codes() { if code <= ENTITY_EVENT_MAX_BOUND as u8 { send_packet( 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(shared: &SharedServer) -> RegistryCodec { let mut dims = Vec::new(); for (id, dim) in shared.dimensions() { let id = id.0 as i32; diff --git a/src/config.rs b/src/config.rs index 00ae041..57f8cd6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,5 @@ //! Configuration for the server. -use std::any::Any; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::panic::{RefUnwindSafe, UnwindSafe}; @@ -13,14 +12,7 @@ use crate::server::{NewClientData, Server, SharedServer}; use crate::text::Text; use crate::Ticks; -/// A trait containing callbacks which are invoked by the running Minecraft -/// 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. +/// A trait for the configuration of a server. /// /// This trait uses the [async_trait] attribute macro. It is exported at the /// root of this crate. @@ -28,10 +20,21 @@ use crate::Ticks; /// [async_trait]: https://docs.rs/async-trait/latest/async_trait/ #[async_trait] #[allow(unused_variables)] -pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe { - /// Called once at startup to get the maximum number of connections allowed - /// to the server. Note that this includes all connections, not just those - /// past the login stage. +pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe { + /// Custom data to store with the [`Server`]. + type ServerData: Send + Sync; + /// 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 /// 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. async fn server_list_ping( &self, - shared: &SharedServer, + shared: &SharedServer, remote_addr: SocketAddr, ) -> ServerListPing { ServerListPing::Ignore @@ -192,7 +195,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe { /// The client is allowed to join unconditionally. /// /// [`Clients`]: crate::client::Clients - async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> { + async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> { 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. /// /// This method is called from within a tokio runtime. - fn init(&self, server: &mut Server) {} + fn init(&self, server: &mut Server) {} /// Called once at the beginning of every server update (also known as /// "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 /// /// The default implementation does nothing. - fn update(&self, server: &mut Server); + fn update(&self, server: &mut Server); } /// The result of the [`server_list_ping`](Config::server_list_ping) callback. diff --git a/src/entity.rs b/src/entity.rs index 14a30f0..60c57d6 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -1,6 +1,6 @@ //! Dynamic actors in a world. -pub mod data; +pub mod state; pub mod types; use std::collections::hash_map::Entry; @@ -10,10 +10,11 @@ use std::num::NonZeroU32; use bitfield_struct::bitfield; use rayon::iter::ParallelIterator; -pub use types::{EntityData, EntityKind}; +pub use types::{EntityKind, EntityState}; use uuid::Uuid; use vek::{Aabb, Vec3}; +use crate::config::Config; use crate::protocol_inner::packets::play::s2c::{ AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata, }; @@ -32,13 +33,13 @@ use crate::world::WorldId; /// /// [`Player`]: crate::entity::types::Player /// [`PlayerList`]: crate::player_list::PlayerList -pub struct Entities { - sm: SlotMap, +pub struct Entities { + sm: SlotMap>, uuid_to_entity: HashMap, network_id_to_entity: HashMap, } -impl Entities { +impl Entities { pub(crate) fn new() -> Self { Self { sm: SlotMap::new(), @@ -49,8 +50,8 @@ impl Entities { /// Spawns a new entity with a random UUID. A reference to the entity along /// with its ID is returned. - pub fn create(&mut self, kind: EntityKind) -> (EntityId, &mut Entity) { - self.create_with_uuid(kind, Uuid::from_bytes(rand::random())) + pub fn create(&mut self, kind: EntityKind, data: C::EntityData) -> (EntityId, &mut Entity) { + self.create_with_uuid(kind, Uuid::from_bytes(rand::random()), data) .expect("UUID collision") } @@ -63,13 +64,15 @@ impl Entities { &mut self, kind: EntityKind, uuid: Uuid, - ) -> Option<(EntityId, &mut Entity)> { + data: C::EntityData, + ) -> Option<(EntityId, &mut Entity)> { match self.uuid_to_entity.entry(uuid) { Entry::Occupied(_) => None, Entry::Vacant(ve) => { let (k, e) = self.sm.insert(Entity { + data, + state: EntityState::new(kind), flags: EntityFlags(0), - data: EntityData::new(kind), world: WorldId::NULL, new_position: Vec3::default(), old_position: Vec3::default(), @@ -113,7 +116,7 @@ impl Entities { /// Removes all entities from the server for which `f` returns `true`. /// /// 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) -> bool) { self.sm.retain(|k, v| { if f(EntityId(k), v) { true @@ -147,14 +150,14 @@ impl Entities { /// Gets a shared reference to the entity with the given [`EntityId`]. /// /// If the ID is invalid, `None` is returned. - pub fn get(&self, entity: EntityId) -> Option<&Entity> { + pub fn get(&self, entity: EntityId) -> Option<&Entity> { self.sm.get(entity.0) } /// Gets an exclusive reference to the entity with the given [`EntityId`]. /// /// 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> { self.sm.get_mut(entity.0) } @@ -166,32 +169,34 @@ impl Entities { /// Returns an immutable iterator over all entities on the server in an /// unspecified order. - pub fn iter(&self) -> impl FusedIterator + Clone + '_ { + pub fn iter(&self) -> impl FusedIterator)> + Clone + '_ { self.sm.iter().map(|(k, v)| (EntityId(k), v)) } /// Returns a mutable iterator over all entities on the server in an /// unspecified order. - pub fn iter_mut(&mut self) -> impl FusedIterator + '_ { + pub fn iter_mut(&mut self) -> impl FusedIterator)> + '_ { self.sm.iter_mut().map(|(k, v)| (EntityId(k), v)) } /// Returns a parallel immutable iterator over all entities on the server in /// an unspecified order. - pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ { + pub fn par_iter(&self) -> impl ParallelIterator)> + Clone + '_ { self.sm.par_iter().map(|(k, v)| (EntityId(k), v)) } /// Returns a parallel mutable iterator over all clients on the server in an /// unspecified order. - pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ { + pub fn par_iter_mut( + &mut self, + ) -> impl ParallelIterator)> + '_ { self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v)) } pub(crate) fn update(&mut self) { for (_, e) in self.iter_mut() { 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_head_yaw_modified(false); @@ -230,9 +235,12 @@ impl EntityId { /// this struct. This includes position, rotation, velocity, UUID, and hitbox. /// To access data that is not common to every kind of entity, see /// [`Self::data`]. -pub struct Entity { +pub struct Entity { + /// Custom data. + pub data: C::EntityData, + /// Kind-specific state for this entity. + pub state: EntityState, flags: EntityFlags, - data: EntityData, world: WorldId, new_position: Vec3, old_position: Vec3, @@ -253,25 +261,14 @@ pub(crate) struct EntityFlags { _pad: u8, } -impl Entity { +impl Entity { pub(crate) fn flags(&self) -> EntityFlags { 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. pub fn kind(&self) -> EntityKind { - self.data.kind() + self.state.kind() } /// Gets the [`WorldId`](crate::world::WorldId) of the world this entity is @@ -389,18 +386,18 @@ impl Entity { /// /// [interact event]: crate::client::Event::InteractWithEntity pub fn hitbox(&self) -> Aabb { - let dims = match &self.data { - EntityData::Allay(_) => [0.6, 0.35, 0.6], - EntityData::ChestBoat(_) => [1.375, 0.5625, 1.375], - EntityData::Frog(_) => [0.5, 0.5, 0.5], - EntityData::Tadpole(_) => [0.4, 0.3, 0.4], - EntityData::Warden(_) => [0.9, 2.9, 0.9], - EntityData::AreaEffectCloud(e) => [ + let dims = match &self.state { + EntityState::Allay(_) => [0.6, 0.35, 0.6], + EntityState::ChestBoat(_) => [1.375, 0.5625, 1.375], + EntityState::Frog(_) => [0.5, 0.5, 0.5], + EntityState::Tadpole(_) => [0.4, 0.3, 0.4], + EntityState::Warden(_) => [0.9, 2.9, 0.9], + EntityState::AreaEffectCloud(e) => [ e.get_radius() as f64 * 2.0, 0.5, e.get_radius() as f64 * 2.0, ], - EntityData::ArmorStand(e) => { + EntityState::ArmorStand(e) => { if e.get_marker() { [0.0, 0.0, 0.0] } else if e.get_small() { @@ -409,123 +406,123 @@ impl Entity { [0.5, 1.975, 0.5] } } - EntityData::Arrow(_) => [0.5, 0.5, 0.5], - EntityData::Axolotl(_) => [1.3, 0.6, 1.3], - EntityData::Bat(_) => [0.5, 0.9, 0.5], - EntityData::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size? - EntityData::Blaze(_) => [0.6, 1.8, 0.6], - EntityData::Boat(_) => [1.375, 0.5625, 1.375], - EntityData::Cat(_) => [0.6, 0.7, 0.6], - EntityData::CaveSpider(_) => [0.7, 0.5, 0.7], - EntityData::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size? - EntityData::Cod(_) => [0.5, 0.3, 0.5], - EntityData::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size? - EntityData::Creeper(_) => [0.6, 1.7, 0.6], - EntityData::Dolphin(_) => [0.9, 0.6, 0.9], - EntityData::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size? - EntityData::DragonFireball(_) => [1.0, 1.0, 1.0], - EntityData::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size? - EntityData::ElderGuardian(_) => [1.9975, 1.9975, 1.9975], - EntityData::EndCrystal(_) => [2.0, 2.0, 2.0], - EntityData::EnderDragon(_) => [16.0, 8.0, 16.0], - EntityData::Enderman(_) => [0.6, 2.9, 0.6], - EntityData::Endermite(_) => [0.4, 0.3, 0.4], - EntityData::Evoker(_) => [0.6, 1.95, 0.6], - EntityData::EvokerFangs(_) => [0.5, 0.8, 0.5], - EntityData::ExperienceOrb(_) => [0.5, 0.5, 0.5], - EntityData::EyeOfEnder(_) => [0.25, 0.25, 0.25], - EntityData::FallingBlock(_) => [0.98, 0.98, 0.98], - EntityData::FireworkRocket(_) => [0.25, 0.25, 0.25], - EntityData::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size? - EntityData::Ghast(_) => [4.0, 4.0, 4.0], - EntityData::Giant(_) => [3.6, 12.0, 3.6], - EntityData::GlowItemFrame(_) => todo!("account for rotation"), - EntityData::GlowSquid(_) => [0.8, 0.8, 0.8], - EntityData::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size? - EntityData::Guardian(_) => [0.85, 0.85, 0.85], - EntityData::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size? - EntityData::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? - EntityData::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size? - EntityData::Illusioner(_) => [0.6, 1.95, 0.6], - EntityData::IronGolem(_) => [1.4, 2.7, 1.4], - EntityData::Item(_) => [0.25, 0.25, 0.25], - EntityData::ItemFrame(_) => todo!("account for rotation"), - EntityData::Fireball(_) => [1.0, 1.0, 1.0], - EntityData::LeashKnot(_) => [0.375, 0.5, 0.375], - EntityData::LightningBolt(_) => [0.0, 0.0, 0.0], - EntityData::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size? - EntityData::LlamaSpit(_) => [0.25, 0.25, 0.25], - EntityData::MagmaCube(e) => { + EntityState::Arrow(_) => [0.5, 0.5, 0.5], + EntityState::Axolotl(_) => [1.3, 0.6, 1.3], + EntityState::Bat(_) => [0.5, 0.9, 0.5], + EntityState::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size? + EntityState::Blaze(_) => [0.6, 1.8, 0.6], + EntityState::Boat(_) => [1.375, 0.5625, 1.375], + EntityState::Cat(_) => [0.6, 0.7, 0.6], + EntityState::CaveSpider(_) => [0.7, 0.5, 0.7], + EntityState::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size? + EntityState::Cod(_) => [0.5, 0.3, 0.5], + EntityState::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size? + EntityState::Creeper(_) => [0.6, 1.7, 0.6], + EntityState::Dolphin(_) => [0.9, 0.6, 0.9], + EntityState::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size? + EntityState::DragonFireball(_) => [1.0, 1.0, 1.0], + EntityState::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size? + EntityState::ElderGuardian(_) => [1.9975, 1.9975, 1.9975], + EntityState::EndCrystal(_) => [2.0, 2.0, 2.0], + EntityState::EnderDragon(_) => [16.0, 8.0, 16.0], + EntityState::Enderman(_) => [0.6, 2.9, 0.6], + EntityState::Endermite(_) => [0.4, 0.3, 0.4], + EntityState::Evoker(_) => [0.6, 1.95, 0.6], + EntityState::EvokerFangs(_) => [0.5, 0.8, 0.5], + EntityState::ExperienceOrb(_) => [0.5, 0.5, 0.5], + EntityState::EyeOfEnder(_) => [0.25, 0.25, 0.25], + EntityState::FallingBlock(_) => [0.98, 0.98, 0.98], + EntityState::FireworkRocket(_) => [0.25, 0.25, 0.25], + EntityState::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size? + EntityState::Ghast(_) => [4.0, 4.0, 4.0], + EntityState::Giant(_) => [3.6, 12.0, 3.6], + EntityState::GlowItemFrame(_) => todo!("account for rotation"), + EntityState::GlowSquid(_) => [0.8, 0.8, 0.8], + EntityState::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size? + EntityState::Guardian(_) => [0.85, 0.85, 0.85], + EntityState::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size? + EntityState::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? + EntityState::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size? + EntityState::Illusioner(_) => [0.6, 1.95, 0.6], + EntityState::IronGolem(_) => [1.4, 2.7, 1.4], + EntityState::Item(_) => [0.25, 0.25, 0.25], + EntityState::ItemFrame(_) => todo!("account for rotation"), + EntityState::Fireball(_) => [1.0, 1.0, 1.0], + EntityState::LeashKnot(_) => [0.375, 0.5, 0.375], + EntityState::LightningBolt(_) => [0.0, 0.0, 0.0], + EntityState::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size? + EntityState::LlamaSpit(_) => [0.25, 0.25, 0.25], + EntityState::MagmaCube(e) => { let s = e.get_size() as f64 * 0.51000005; [s, s, s] } - EntityData::Marker(_) => [0.0, 0.0, 0.0], - EntityData::Minecart(_) => [0.98, 0.7, 0.98], - EntityData::ChestMinecart(_) => [0.98, 0.7, 0.98], - EntityData::CommandBlockMinecart(_) => [0.98, 0.7, 0.98], - EntityData::FurnaceMinecart(_) => [0.98, 0.7, 0.98], - EntityData::HopperMinecart(_) => [0.98, 0.7, 0.98], - EntityData::SpawnerMinecart(_) => [0.98, 0.7, 0.98], - EntityData::TntMinecart(_) => [0.98, 0.7, 0.98], - EntityData::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? - EntityData::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size? - EntityData::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size? - EntityData::Painting(_) => todo!("account for rotation and type"), - EntityData::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size? - EntityData::Parrot(_) => [0.5, 0.9, 0.5], - EntityData::Phantom(_) => [0.9, 0.5, 0.9], - EntityData::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size? - EntityData::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size? - EntityData::PiglinBrute(_) => [0.6, 1.95, 0.6], - EntityData::Pillager(_) => [0.6, 1.95, 0.6], - EntityData::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size? - EntityData::Tnt(_) => [0.98, 0.98, 0.98], - EntityData::Pufferfish(_) => [0.7, 0.7, 0.7], - EntityData::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size? - EntityData::Ravager(_) => [1.95, 2.2, 1.95], - EntityData::Salmon(_) => [0.7, 0.4, 0.7], - EntityData::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size? - EntityData::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated? - EntityData::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125], - EntityData::Silverfish(_) => [0.4, 0.3, 0.4], - EntityData::Skeleton(_) => [0.6, 1.99, 0.6], - EntityData::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? - EntityData::Slime(e) => { + EntityState::Marker(_) => [0.0, 0.0, 0.0], + EntityState::Minecart(_) => [0.98, 0.7, 0.98], + EntityState::ChestMinecart(_) => [0.98, 0.7, 0.98], + EntityState::CommandBlockMinecart(_) => [0.98, 0.7, 0.98], + EntityState::FurnaceMinecart(_) => [0.98, 0.7, 0.98], + EntityState::HopperMinecart(_) => [0.98, 0.7, 0.98], + EntityState::SpawnerMinecart(_) => [0.98, 0.7, 0.98], + EntityState::TntMinecart(_) => [0.98, 0.7, 0.98], + EntityState::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? + EntityState::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size? + EntityState::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size? + EntityState::Painting(_) => todo!("account for rotation and type"), + EntityState::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size? + EntityState::Parrot(_) => [0.5, 0.9, 0.5], + EntityState::Phantom(_) => [0.9, 0.5, 0.9], + EntityState::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size? + EntityState::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size? + EntityState::PiglinBrute(_) => [0.6, 1.95, 0.6], + EntityState::Pillager(_) => [0.6, 1.95, 0.6], + EntityState::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size? + EntityState::Tnt(_) => [0.98, 0.98, 0.98], + EntityState::Pufferfish(_) => [0.7, 0.7, 0.7], + EntityState::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size? + EntityState::Ravager(_) => [1.95, 2.2, 1.95], + EntityState::Salmon(_) => [0.7, 0.4, 0.7], + EntityState::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size? + EntityState::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated? + EntityState::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125], + EntityState::Silverfish(_) => [0.4, 0.3, 0.4], + EntityState::Skeleton(_) => [0.6, 1.99, 0.6], + EntityState::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? + EntityState::Slime(e) => { let s = 0.51000005 * e.get_size() as f64; [s, s, s] } - EntityData::SmallFireball(_) => [0.3125, 0.3125, 0.3125], - EntityData::SnowGolem(_) => [0.7, 1.9, 0.7], - EntityData::Snowball(_) => [0.25, 0.25, 0.25], - EntityData::SpectralArrow(_) => [0.5, 0.5, 0.5], - EntityData::Spider(_) => [1.4, 0.9, 1.4], - EntityData::Squid(_) => [0.8, 0.8, 0.8], - EntityData::Stray(_) => [0.6, 1.99, 0.6], - EntityData::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size? - EntityData::Egg(_) => [0.25, 0.25, 0.25], - EntityData::EnderPearl(_) => [0.25, 0.25, 0.25], - EntityData::ExperienceBottle(_) => [0.25, 0.25, 0.25], - EntityData::Potion(_) => [0.25, 0.25, 0.25], - EntityData::Trident(_) => [0.5, 0.5, 0.5], - EntityData::TraderLlama(_) => [0.9, 1.87, 0.9], - EntityData::TropicalFish(_) => [0.5, 0.4, 0.5], - EntityData::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size? - EntityData::Vex(_) => [0.4, 0.8, 0.4], - EntityData::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size? - EntityData::Vindicator(_) => [0.6, 1.95, 0.6], - EntityData::WanderingTrader(_) => [0.6, 1.95, 0.6], - EntityData::Witch(_) => [0.6, 1.95, 0.6], - EntityData::Wither(_) => [0.9, 3.5, 0.9], - EntityData::WitherSkeleton(_) => [0.7, 2.4, 0.7], - EntityData::WitherSkull(_) => [0.3125, 0.3125, 0.3125], - EntityData::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size? - EntityData::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size? - EntityData::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size? - EntityData::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? - EntityData::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size? - EntityData::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size? - EntityData::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose. - EntityData::FishingBobber(_) => [0.25, 0.25, 0.25], + EntityState::SmallFireball(_) => [0.3125, 0.3125, 0.3125], + EntityState::SnowGolem(_) => [0.7, 1.9, 0.7], + EntityState::Snowball(_) => [0.25, 0.25, 0.25], + EntityState::SpectralArrow(_) => [0.5, 0.5, 0.5], + EntityState::Spider(_) => [1.4, 0.9, 1.4], + EntityState::Squid(_) => [0.8, 0.8, 0.8], + EntityState::Stray(_) => [0.6, 1.99, 0.6], + EntityState::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size? + EntityState::Egg(_) => [0.25, 0.25, 0.25], + EntityState::EnderPearl(_) => [0.25, 0.25, 0.25], + EntityState::ExperienceBottle(_) => [0.25, 0.25, 0.25], + EntityState::Potion(_) => [0.25, 0.25, 0.25], + EntityState::Trident(_) => [0.5, 0.5, 0.5], + EntityState::TraderLlama(_) => [0.9, 1.87, 0.9], + EntityState::TropicalFish(_) => [0.5, 0.4, 0.5], + EntityState::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size? + EntityState::Vex(_) => [0.4, 0.8, 0.4], + EntityState::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size? + EntityState::Vindicator(_) => [0.6, 1.95, 0.6], + EntityState::WanderingTrader(_) => [0.6, 1.95, 0.6], + EntityState::Witch(_) => [0.6, 1.95, 0.6], + EntityState::Wither(_) => [0.9, 3.5, 0.9], + EntityState::WitherSkeleton(_) => [0.7, 2.4, 0.7], + EntityState::WitherSkull(_) => [0.3125, 0.3125, 0.3125], + EntityState::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size? + EntityState::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size? + EntityState::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size? + EntityState::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? + EntityState::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size? + EntityState::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size? + EntityState::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose. + EntityState::FishingBobber(_) => [0.25, 0.25, 0.25], }; aabb_from_bottom_and_size(self.new_position, dims.into()) @@ -536,7 +533,7 @@ impl Entity { /// /// Is `None` if there is no initial metadata. pub(crate) fn initial_metadata_packet(&self, this_id: EntityId) -> Option { - self.data.initial_metadata().map(|meta| SetEntityMetadata { + self.state.initial_metadata().map(|meta| SetEntityMetadata { entity_id: VarInt(this_id.to_network_id()), metadata: RawBytes(meta), }) @@ -546,23 +543,23 @@ impl Entity { /// /// Is `None` if this entity's metadata has not been modified. pub(crate) fn updated_metadata_packet(&self, this_id: EntityId) -> Option { - self.data.updated_metadata().map(|meta| SetEntityMetadata { + self.state.updated_metadata().map(|meta| SetEntityMetadata { entity_id: VarInt(this_id.to_network_id()), metadata: RawBytes(meta), }) } pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option { - match &self.data { - EntityData::Marker(_) => None, - EntityData::ExperienceOrb(_) => { + match &self.state { + EntityState::Marker(_) => None, + EntityState::ExperienceOrb(_) => { Some(EntitySpawnPacket::ExperienceOrb(AddExperienceOrb { entity_id: VarInt(this_id.to_network_id()), position: self.new_position, count: 0, // TODO })) } - EntityData::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer { + EntityState::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer { entity_id: VarInt(this_id.to_network_id()), player_uuid: self.uuid, position: self.new_position, diff --git a/src/entity/data.rs b/src/entity/state.rs similarity index 100% rename from src/entity/data.rs rename to src/entity/state.rs diff --git a/src/entity/types.rs b/src/entity/types.rs index 2712ac1..0c9eaa7 100644 --- a/src/entity/types.rs +++ b/src/entity/types.rs @@ -3,7 +3,7 @@ #![allow(clippy::all, missing_docs)] use crate::block::{BlockPos, BlockState}; -use crate::entity::data::*; +use crate::entity::state::*; use crate::entity::EntityId; use crate::protocol_inner::{Encode, VarInt}; use crate::text::Text; diff --git a/src/lib.rs b/src/lib.rs index 3e4fe13..451978b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,17 +52,23 @@ //! use valence::server::{Server, ShutdownResult}; //! //! pub fn main() -> ShutdownResult { -//! valence::start_server(Game) +//! valence::start_server(Game, ()) //! } //! //! struct Game; //! //! impl Config for Game { +//! type ChunkData = (); +//! type ClientData = (); +//! type EntityData = (); +//! type ServerData = (); +//! type WorldData = (); +//! //! fn max_connections(&self) -> usize { //! 256 //! } //! -//! fn update(&self, server: &mut Server) { +//! fn update(&self, server: &mut Server) { //! server.clients.retain(|_, client| { //! if client.created_tick() == server.shared.current_tick() { //! println!("{} joined!", client.username()); diff --git a/src/protocol_inner/codec.rs b/src/protocol_inner/codec.rs index 822b400..62cfb6b 100644 --- a/src/protocol_inner/codec.rs +++ b/src/protocol_inner/codec.rs @@ -1,5 +1,4 @@ /// Reading and writing whole packets. - use std::io::Read; use std::time::Duration; diff --git a/src/server.rs b/src/server.rs index 99d2cd2..3757362 100644 --- a/src/server.rs +++ b/src/server.rs @@ -51,15 +51,17 @@ use crate::{Ticks, PROTOCOL_VERSION, VERSION_NAME}; /// Contains the entire state of a running Minecraft server, accessible from /// within the [update](crate::config::Config::update) loop. -pub struct Server { +pub struct Server { + /// Custom data. + pub data: C::ServerData, /// A handle to this server's [`SharedServer`]. - pub shared: SharedServer, + pub shared: SharedServer, /// All of the clients in the server. - pub clients: Clients, + pub clients: Clients, /// All of entities in the server. - pub entities: Entities, + pub entities: Entities, /// All of the worlds in the server. - pub worlds: Worlds, + pub worlds: Worlds, } /// A handle to a Minecraft server containing the subset of functionality which @@ -69,11 +71,17 @@ pub struct Server { /// be shared between threads. /// /// [update]: crate::config::Config::update -#[derive(Clone)] -pub struct SharedServer(Arc); -struct SharedServerInner { - cfg: Box, +pub struct SharedServer(Arc>); + +impl Clone for SharedServer { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +struct SharedServerInner { + cfg: C, address: SocketAddr, tick_rate: Ticks, online_mode: bool, @@ -130,10 +138,10 @@ pub type ShutdownResult = Result<(), Box>; pub(crate) type S2cPacketChannels = (Sender, Receiver); pub(crate) type C2sPacketChannels = (Sender, Receiver); -impl SharedServer { +impl SharedServer { /// Gets a reference to the config object used to start the server. - pub fn config(&self) -> &(impl Config + ?Sized) { - self.0.cfg.as_ref() + pub fn config(&self) -> &C { + &self.0.cfg } /// 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 /// occurs, or the configuration is found to be invalid. -pub fn start_server(config: impl Config) -> ShutdownResult { +pub fn start_server(config: C, data: C::ServerData) -> ShutdownResult { let shared = setup_server(config).map_err(Box::::from)?; let _guard = shared.tokio_handle().enter(); let mut server = Server { + data, shared: shared.clone(), clients: Clients::new(), entities: Entities::new(), @@ -259,7 +268,7 @@ pub fn start_server(config: impl Config) -> ShutdownResult { do_update_loop(&mut server) } -fn setup_server(cfg: impl Config) -> anyhow::Result { +fn setup_server(cfg: C) -> anyhow::Result> { let max_connections = cfg.max_connections(); let address = cfg.address(); let tick_rate = cfg.tick_rate(); @@ -360,7 +369,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result { }; let server = SharedServerInner { - cfg: Box::new(cfg), + cfg, address, tick_rate, online_mode, @@ -385,7 +394,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result { Ok(SharedServer(Arc::new(server))) } -fn do_update_loop(server: &mut Server) -> ShutdownResult { +fn do_update_loop(server: &mut Server) -> ShutdownResult { let mut tick_start = Instant::now(); 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(server: &mut Server, msg: NewClientMessage) { 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); @@ -450,7 +459,12 @@ fn join_player(server: &mut Server, msg: NewClientMessage) { 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); } @@ -460,7 +474,7 @@ struct Codec { dec: Decoder, } -async fn do_accept_loop(server: SharedServer) { +async fn do_accept_loop(server: SharedServer) { log::trace!("entering accept loop"); let listener = match TcpListener::bind(server.0.address).await { @@ -502,8 +516,8 @@ async fn do_accept_loop(server: SharedServer) { } } -async fn handle_connection( - server: SharedServer, +async fn handle_connection( + server: SharedServer, stream: TcpStream, remote_addr: SocketAddr, ) -> anyhow::Result<()> { @@ -533,8 +547,8 @@ async fn handle_connection( } } -async fn handle_status( - server: SharedServer, +async fn handle_status( + server: SharedServer, c: &mut Codec, remote_addr: SocketAddr, ) -> anyhow::Result<()> { @@ -585,8 +599,8 @@ async fn handle_status( } /// Handle the login process and return the new player's data if successful. -async fn handle_login( - server: &SharedServer, +async fn handle_login( + server: &SharedServer, c: &mut Codec, remote_addr: SocketAddr, ) -> anyhow::Result> { @@ -725,7 +739,11 @@ async fn handle_login( Ok(Some(npd)) } -async fn handle_play(server: &SharedServer, c: Codec, ncd: NewClientData) -> anyhow::Result<()> { +async fn handle_play( + server: &SharedServer, + c: Codec, + ncd: NewClientData, +) -> anyhow::Result<()> { let (reply_tx, reply_rx) = oneshot::channel(); server diff --git a/src/spatial_index.rs b/src/spatial_index.rs index 8889574..1d6cf17 100644 --- a/src/spatial_index.rs +++ b/src/spatial_index.rs @@ -6,6 +6,7 @@ use rayon::iter::{IndexedParallelIterator, ParallelIterator}; use vek::{Aabb, Vec3}; use crate::bvh::{Bvh, Node}; +use crate::config::Config; use crate::entity::{Entities, EntityId}; use crate::util::ray_box_intersect; use crate::world::WorldId; @@ -225,7 +226,7 @@ impl SpatialIndex { self.bvh.par_iter().map(|(&id, bb)| (id, bb)) } - pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) { + pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) { self.bvh.build( entities .iter() diff --git a/src/world.rs b/src/world.rs index 1fb5530..91faece 100644 --- a/src/world.rs +++ b/src/world.rs @@ -5,6 +5,7 @@ use std::iter::FusedIterator; use rayon::iter::ParallelIterator; use crate::chunk::Chunks; +use crate::config::Config; use crate::dimension::DimensionId; use crate::player_list::PlayerList; use crate::server::SharedServer; @@ -12,9 +13,9 @@ use crate::slotmap::{Key, SlotMap}; use crate::spatial_index::SpatialIndex; /// A container for all [`World`]s on a [`Server`](crate::server::Server). -pub struct Worlds { - sm: SlotMap, - server: SharedServer, +pub struct Worlds { + sm: SlotMap>, + server: SharedServer, } /// An identifier for a [`World`] on the server. @@ -34,8 +35,8 @@ impl WorldId { pub const NULL: Self = Self(Key::NULL); } -impl Worlds { - pub(crate) fn new(server: SharedServer) -> Self { +impl Worlds { + pub(crate) fn new(server: SharedServer) -> Self { Self { sm: SlotMap::new(), server, @@ -44,8 +45,9 @@ impl Worlds { /// Creates a new world on the server with the provided dimension. A /// 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) { let (id, world) = self.sm.insert(World { + data, spatial_index: SpatialIndex::new(), chunks: Chunks::new(self.server.clone(), dim), meta: WorldMeta { @@ -71,7 +73,7 @@ impl Worlds { /// `f` returns `true`. /// /// 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) -> bool) { 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 /// the ID is invalid, then `None` is returned. - pub fn get(&self, world: WorldId) -> Option<&World> { + pub fn get(&self, world: WorldId) -> Option<&World> { self.sm.get(world.0) } /// Returns an exclusive reference to the world with the given ID. If the /// 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> { self.sm.get_mut(world.0) } /// Returns an immutable iterator over all worlds on the server in an /// unspecified order. - pub fn iter(&self) -> impl FusedIterator + Clone + '_ { + pub fn iter(&self) -> impl FusedIterator)> + Clone + '_ { self.sm.iter().map(|(k, v)| (WorldId(k), v)) } /// Returns a mutable iterator over all worlds on the server in an /// unspecified ordder. - pub fn iter_mut(&mut self) -> impl FusedIterator + '_ { + pub fn iter_mut(&mut self) -> impl FusedIterator)> + '_ { self.sm.iter_mut().map(|(k, v)| (WorldId(k), v)) } /// Returns a parallel immutable iterator over all worlds on the server in /// an unspecified order. - pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ { + pub fn par_iter(&self) -> impl ParallelIterator)> + Clone + '_ { self.sm.par_iter().map(|(k, v)| (WorldId(k), v)) } /// Returns a parallel mutable iterator over all worlds on the server in an /// unspecified order. - pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ { + pub fn par_iter_mut(&mut self) -> impl ParallelIterator)> + '_ { self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v)) } } /// A space for chunks, entities, and clients to occupy. -pub struct World { +pub struct World { + /// Custom data. + pub data: C::WorldData, /// Contains all of the entities in this world. pub spatial_index: SpatialIndex, /// All of the chunks in this world. - pub chunks: Chunks, + pub chunks: Chunks, /// This world's metadata. pub meta: WorldMeta, }