diff --git a/examples/combat.rs b/examples/combat.rs index b8c9ba8..2ead62b 100644 --- a/examples/combat.rs +++ b/examples/combat.rs @@ -8,6 +8,7 @@ use valence::config::{Config, ServerListPing}; use valence::dimension::DimensionId; use valence::entity::types::Pose; use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; +use valence::player_list::PlayerListId; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; use valence::{async_trait, Ticks}; @@ -23,7 +24,7 @@ pub fn main() -> ShutdownResult { Game { player_count: AtomicUsize::new(0), }, - (), + None, ) } @@ -57,7 +58,8 @@ impl Config for Game { type ChunkState = (); type ClientState = ClientState; type EntityState = EntityState; - type ServerState = (); + type PlayerListState = (); + type ServerState = Option; type WorldState = (); fn max_connections(&self) -> usize { @@ -84,6 +86,7 @@ impl Config for Game { fn init(&self, server: &mut Server) { let (_, world) = server.worlds.insert(DimensionId::default(), ()); + server.state = Some(server.player_lists.insert(()).0); world.meta.set_flat(true); let min_y = server.shared.dimension(DimensionId::default()).min_y; @@ -159,15 +162,18 @@ impl Config for Game { 0.0, 0.0, ); + client.set_player_list(server.state.clone()); - world.meta.player_list_mut().insert( - client.uuid(), - client.username().to_owned(), - client.textures().cloned(), - client.game_mode(), - 0, - None, - ); + if let Some(id) = &server.state { + server.player_lists.get_mut(id).insert( + client.uuid(), + client.username(), + client.textures().cloned(), + client.game_mode(), + 0, + None, + ); + } client.send_message("Welcome to the arena.".italic()); if self.player_count.load(Ordering::SeqCst) <= 1 { @@ -178,7 +184,9 @@ impl Config for Game { if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); server.entities.remove(client.state.player); - world.meta.player_list_mut().remove(client.uuid()); + if let Some(id) = &server.state { + server.player_lists.get_mut(id).remove(client.uuid()); + } return false; } diff --git a/examples/conway.rs b/examples/conway.rs index 8cc0f43..f1402f1 100644 --- a/examples/conway.rs +++ b/examples/conway.rs @@ -15,6 +15,7 @@ use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; use valence::{async_trait, ident}; +use valence::player_list::PlayerListId; pub fn main() -> ShutdownResult { env_logger::Builder::new() @@ -27,6 +28,7 @@ pub fn main() -> ShutdownResult { player_count: AtomicUsize::new(0), }, ServerState { + player_list: None, board: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(), board_buf: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(), }, @@ -38,6 +40,7 @@ struct Game { } struct ServerState { + player_list: Option, board: Box<[bool]>, board_buf: Box<[bool]>, } @@ -55,6 +58,7 @@ impl Config for Game { type EntityState = (); type ServerState = ServerState; type WorldState = (); + type PlayerListState = (); fn max_connections(&self) -> usize { // We want status pings to be successful even if the server is full. @@ -96,6 +100,7 @@ impl Config for Game { fn init(&self, server: &mut Server) { let world = server.worlds.insert(DimensionId::default(), ()).1; + server.state.player_list = Some(server.player_lists.insert(()).0); world.meta.set_flat(true); for chunk_z in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 { @@ -140,15 +145,18 @@ impl Config for Game { client.spawn(world_id); client.teleport(spawn_pos, 0.0, 0.0); + client.set_player_list(server.state.player_list.clone()); - world.meta.player_list_mut().insert( - client.uuid(), - client.username().to_owned(), - client.textures().cloned(), - client.game_mode(), - 0, - None, - ); + if let Some(id) = &server.state.player_list { + server.player_lists.get_mut(id).insert( + client.uuid(), + client.username(), + client.textures().cloned(), + client.game_mode(), + 0, + None, + ); + } 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,7 +165,9 @@ impl Config for Game { if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); server.entities.remove(client.state); - world.meta.player_list_mut().remove(client.uuid()); + if let Some(id) = &server.state.player_list { + server.player_lists.get_mut(id).remove(client.uuid()); + } return false; } diff --git a/examples/cow_sphere.rs b/examples/cow_sphere.rs index 8549979..09c70ee 100644 --- a/examples/cow_sphere.rs +++ b/examples/cow_sphere.rs @@ -10,6 +10,7 @@ use valence::config::{Config, ServerListPing}; use valence::dimension::DimensionId; use valence::entity::types::Pose; use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; +use valence::player_list::PlayerListId; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; use valence::util::to_yaw_and_pitch; @@ -25,7 +26,10 @@ pub fn main() -> ShutdownResult { Game { player_count: AtomicUsize::new(0), }, - ServerState { cows: Vec::new() }, + ServerState { + player_list: None, + cows: Vec::new(), + }, ) } @@ -34,6 +38,7 @@ struct Game { } struct ServerState { + player_list: Option, cows: Vec, } @@ -46,6 +51,7 @@ impl Config for Game { type ChunkState = (); type ClientState = EntityId; type EntityState = (); + type PlayerListState = (); type ServerState = ServerState; type WorldState = (); @@ -74,6 +80,7 @@ impl Config for Game { fn init(&self, server: &mut Server) { let (world_id, world) = server.worlds.insert(DimensionId::default(), ()); + server.state.player_list = Some(server.player_lists.insert(()).0); world.meta.set_flat(true); let size = 5; @@ -93,7 +100,7 @@ impl Config for Game { } fn update(&self, server: &mut Server) { - let (world_id, world) = server.worlds.iter_mut().next().expect("missing world"); + let (world_id, _) = server.worlds.iter_mut().next().expect("missing world"); server.clients.retain(|_, client| { if client.created_this_tick() { @@ -130,20 +137,25 @@ impl Config for Game { 0.0, 0.0, ); + client.set_player_list(server.state.player_list.clone()); - world.meta.player_list_mut().insert( - client.uuid(), - client.username().to_owned(), - client.textures().cloned(), - client.game_mode(), - 0, - None, - ); + if let Some(id) = &server.state.player_list { + server.player_lists.get_mut(id).insert( + client.uuid(), + client.username(), + client.textures().cloned(), + client.game_mode(), + 0, + None, + ); + } } if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); - world.meta.player_list_mut().remove(client.uuid()); + if let Some(id) = &server.state.player_list { + server.player_lists.get_mut(id).remove(client.uuid()); + } server.entities.remove(client.state); return false; diff --git a/examples/raycast.rs b/examples/raycast.rs index 27e6033..c56621f 100644 --- a/examples/raycast.rs +++ b/examples/raycast.rs @@ -14,6 +14,7 @@ use valence::spatial_index::RaycastHit; use valence::text::{Color, TextFormat}; use valence::util::from_yaw_and_pitch; use vek::Vec3; +use valence::player_list::PlayerListId; pub fn main() -> ShutdownResult { env_logger::Builder::new() @@ -25,7 +26,7 @@ pub fn main() -> ShutdownResult { Game { player_count: AtomicUsize::new(0), }, - (), + None, ) } @@ -45,8 +46,9 @@ impl Config for Game { type ClientState = EntityId; /// `true` for entities that have been intersected with. type EntityState = bool; - type ServerState = (); + type ServerState = Option; type WorldState = (); + type PlayerListState = (); fn max_connections(&self) -> usize { // We want status pings to be successful even if the server is full. @@ -73,6 +75,7 @@ impl Config for Game { fn init(&self, server: &mut Server) { let (world_id, world) = server.worlds.insert(DimensionId::default(), ()); + server.state = Some(server.player_lists.insert(()).0); world.meta.set_flat(true); let size = 5; @@ -134,15 +137,18 @@ impl Config for Game { 0.0, 0.0, ); + client.set_player_list(server.state.clone()); - world.meta.player_list_mut().insert( - client.uuid(), - client.username().to_owned(), - client.textures().cloned(), - client.game_mode(), - 0, - None, - ); + if let Some(id) = &server.state { + server.player_lists.get_mut(id).insert( + client.uuid(), + client.username(), + client.textures().cloned(), + client.game_mode(), + 0, + None, + ); + } client.send_message( "Look at a sheep to change its ".italic() @@ -153,7 +159,9 @@ impl Config for Game { if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); - world.meta.player_list_mut().remove(client.uuid()); + if let Some(id) = &server.state { + server.player_lists.get_mut(id).remove(client.uuid()); + } server.entities.remove(client.state); return false; diff --git a/examples/terrain.rs b/examples/terrain.rs index 38f39b4..7cd0365 100644 --- a/examples/terrain.rs +++ b/examples/terrain.rs @@ -13,6 +13,7 @@ use valence::config::{Config, ServerListPing}; use valence::dimension::DimensionId; use valence::entity::types::Pose; use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; +use valence::player_list::PlayerListId; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; use valence::util::chunks_in_view_distance; @@ -35,7 +36,7 @@ pub fn main() -> ShutdownResult { gravel_noise: SuperSimplex::new().set_seed(seed.wrapping_add(3)), grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)), }, - (), + None, ) } @@ -55,7 +56,8 @@ impl Config for Game { type ChunkState = (); type ClientState = EntityId; type EntityState = (); - type ServerState = (); + type PlayerListState = (); + type ServerState = Option; type WorldState = (); fn max_connections(&self) -> usize { @@ -83,6 +85,7 @@ impl Config for Game { fn init(&self, server: &mut Server) { let (_, world) = server.worlds.insert(DimensionId::default(), ()); + server.state = Some(server.player_lists.insert(()).0); world.meta.set_flat(true); } @@ -118,22 +121,27 @@ impl Config for Game { client.spawn(world_id); client.set_game_mode(GameMode::Creative); client.teleport([0.0, 200.0, 0.0], 0.0, 0.0); + client.set_player_list(server.state.clone()); - world.meta.player_list_mut().insert( - client.uuid(), - client.username().to_owned(), - client.textures().cloned(), - client.game_mode(), - 0, - None, - ); + if let Some(id) = &server.state { + server.player_lists.get_mut(id).insert( + client.uuid(), + client.username(), + client.textures().cloned(), + client.game_mode(), + 0, + None, + ); + } client.send_message("Welcome to the terrain example!".italic()); } if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); - world.meta.player_list_mut().remove(client.uuid()); + if let Some(id) = &server.state { + server.player_lists.get_mut(id).remove(client.uuid()); + } server.entities.remove(client.state); return false; diff --git a/src/client.rs b/src/client.rs index 1c684fe..409c714 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,6 +2,7 @@ use std::collections::{HashSet, VecDeque}; use std::iter::FusedIterator; +use std::mem; use std::time::Duration; pub use bitfield_struct::bitfield; @@ -20,6 +21,7 @@ use crate::entity::data::Player; use crate::entity::{ velocity_to_packet_units, Entities, EntityEvent, EntityId, EntityKind, StatusOrAnimation, }; +use crate::player_list::{PlayerListId, PlayerLists}; use crate::player_textures::SignedPlayerTextures; use crate::protocol_inner::packets::c2s::play::{ C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId, @@ -180,6 +182,8 @@ pub struct Client { username: String, textures: Option, world: WorldId, + old_player_list: Option, + new_player_list: Option, position: Vec3, old_position: Vec3, /// Measured in m/s. @@ -254,6 +258,8 @@ impl Client { username: ncd.username, textures: ncd.textures, world: WorldId::default(), + old_player_list: None, + new_player_list: None, position: Vec3::default(), old_position: Vec3::default(), velocity: Vec3::default(), @@ -311,6 +317,14 @@ impl Client { self.world } + pub fn player_list(&self) -> Option<&PlayerListId> { + self.new_player_list.as_ref() + } + + pub fn set_player_list(&mut self, id: Option) -> Option { + mem::replace(&mut self.new_player_list, id) + } + /// Changes the world this client is located in and respawns the client. /// This can be used to respawn the client after death. /// @@ -825,6 +839,7 @@ impl Client { shared: &SharedServer, entities: &Entities, worlds: &Worlds, + player_lists: &PlayerLists, ) { // Mark the client as disconnected when appropriate. if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) { @@ -849,10 +864,11 @@ impl Client { // Send the join game packet and other initial packets. We defer this until now // so that the user can set the client's location, game mode, etc. if self.created_this_tick() { - world - .meta - .player_list() - .initial_packets(|pkt| self.send_packet(pkt)); + if let Some(id) = &self.new_player_list { + player_lists + .get(id) + .initial_packets(|p| send_packet(&mut self.send, p)); + } let mut dimension_names: Vec<_> = shared .dimensions() @@ -896,8 +912,6 @@ impl Client { self.loaded_entities.clear(); self.loaded_chunks.clear(); - // TODO: clear player list. - // Client bug workaround: send the client to a dummy dimension first. // TODO: is there actually a bug? self.send_packet(PlayerRespawn { @@ -935,6 +949,7 @@ impl Client { self.teleport(self.position(), self.yaw(), self.pitch()); } + // Update game mode if self.old_game_mode != self.new_game_mode { self.old_game_mode = self.new_game_mode; self.send_packet(GameStateChange { @@ -943,10 +958,29 @@ impl Client { }); } - world - .meta - .player_list() - .diff_packets(|pkt| self.send_packet(pkt)); + // If the player list was changed... + if self.old_player_list != self.new_player_list { + // Delete existing entries from old player list. + if let Some(id) = &self.old_player_list { + player_lists + .get(id) + .clear_packets(|p| send_packet(&mut self.send, p)); + } + + // Get initial packets for new player list. + if let Some(id) = &self.new_player_list { + player_lists + .get(id) + .initial_packets(|p| send_packet(&mut self.send, p)); + } + + self.old_player_list = self.new_player_list.clone(); + } else if let Some(id) = &self.new_player_list { + // Update current player list. + player_lists + .get(id) + .update_packets(|p| send_packet(&mut self.send, p)); + } } // Set player attributes diff --git a/src/config.rs b/src/config.rs index 670adba..b86011f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,6 +31,8 @@ pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe { type WorldState: Send + Sync; /// Custom state to store with every [`Chunk`](crate::chunk::Chunk). type ChunkState: Send + Sync; + /// Custom state to store with every [`PlayerList`](crate::player_list::PlayerList). + type PlayerListState: Send + Sync; /// Called once at startup to get the maximum number of simultaneous /// connections allowed to the server. This includes all diff --git a/src/lib.rs b/src/lib.rs index e36fec0..c374616 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,6 +147,7 @@ pub mod player_textures; mod protocol_inner; pub mod server; mod slab; +mod slab_rc; mod slab_versioned; pub mod spatial_index; pub mod text; diff --git a/src/player_list.rs b/src/player_list.rs index 8618a15..1caf66a 100644 --- a/src/player_list.rs +++ b/src/player_list.rs @@ -7,14 +7,67 @@ use bitfield_struct::bitfield; use uuid::Uuid; use crate::client::GameMode; +use crate::config::Config; use crate::player_textures::SignedPlayerTextures; use crate::protocol_inner::packets::s2c::play::{ PlayerListAddPlayer, PlayerListHeaderFooter, S2cPlayPacket, UpdatePlayerList, }; use crate::protocol_inner::packets::Property; use crate::protocol_inner::VarInt; +use crate::slab_rc::{Key, SlabRc}; use crate::text::Text; +pub struct PlayerLists { + slab: SlabRc>, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct PlayerListId(Key); + +impl PlayerLists { + pub(crate) fn new() -> Self { + Self { + slab: SlabRc::new(), + } + } + + pub fn insert(&mut self, state: C::PlayerListState) -> (PlayerListId, &mut PlayerList) { + let (key, pl) = self.slab.insert(PlayerList { + state, + entries: HashMap::new(), + removed: HashSet::new(), + header: Text::default(), + footer: Text::default(), + modified_header_or_footer: false, + }); + + (PlayerListId(key), pl) + } + + pub fn len(&self) -> usize { + self.slab.len() + } + + pub fn get(&self, id: &PlayerListId) -> &PlayerList { + self.slab.get(&id.0) + } + + pub fn get_mut(&mut self, id: &PlayerListId) -> &mut PlayerList { + self.slab.get_mut(&id.0) + } + + pub(crate) fn update(&mut self) { + self.slab.collect_garbage(); + for (_, pl) in self.slab.iter_mut() { + for entry in pl.entries.values_mut() { + entry.bits = EntryBits::new(); + } + pl.removed.clear(); + pl.modified_header_or_footer = false; + } + } +} + /// The list of players on a server visible by pressing the tab key by default. /// /// Each entry in the player list is intended to represent a connected client to @@ -22,7 +75,9 @@ use crate::text::Text; /// /// In addition to a list of players, the player list has a header and a footer /// which can contain arbitrary text. -pub struct PlayerList { +pub struct PlayerList { + /// Custom state + pub state: C::PlayerListState, entries: HashMap, removed: HashSet, header: Text, @@ -30,17 +85,7 @@ pub struct PlayerList { modified_header_or_footer: bool, } -impl PlayerList { - pub(crate) fn new() -> Self { - Self { - entries: HashMap::new(), - removed: HashSet::new(), - header: Text::default(), - footer: Text::default(), - modified_header_or_footer: false, - } - } - +impl PlayerList { /// Inserts a player into the player list. /// /// If the given UUID conflicts with an existing entry, the entry is @@ -160,7 +205,7 @@ impl PlayerList { self.entries.iter_mut().map(|(k, v)| (*k, v)) } - pub(crate) fn initial_packets(&self, mut packet: impl FnMut(S2cPlayPacket)) { + pub(crate) fn initial_packets(&self, mut push_packet: impl FnMut(S2cPlayPacket)) { let add_player: Vec<_> = self .entries .iter() @@ -186,11 +231,11 @@ impl PlayerList { .collect(); if !add_player.is_empty() { - packet(UpdatePlayerList::AddPlayer(add_player).into()); + push_packet(UpdatePlayerList::AddPlayer(add_player).into()); } if self.header != Text::default() || self.footer != Text::default() { - packet( + push_packet( PlayerListHeaderFooter { header: self.header.clone(), footer: self.footer.clone(), @@ -200,9 +245,11 @@ impl PlayerList { } } - pub(crate) fn diff_packets(&self, mut packet: impl FnMut(S2cPlayPacket)) { + pub(crate) fn update_packets(&self, mut push_packet: impl FnMut(S2cPlayPacket)) { if !self.removed.is_empty() { - packet(UpdatePlayerList::RemovePlayer(self.removed.iter().cloned().collect()).into()); + push_packet( + UpdatePlayerList::RemovePlayer(self.removed.iter().cloned().collect()).into(), + ); } let mut add_player = Vec::new(); @@ -248,23 +295,23 @@ impl PlayerList { } if !add_player.is_empty() { - packet(UpdatePlayerList::AddPlayer(add_player).into()); + push_packet(UpdatePlayerList::AddPlayer(add_player).into()); } if !game_mode.is_empty() { - packet(UpdatePlayerList::UpdateGameMode(game_mode).into()); + push_packet(UpdatePlayerList::UpdateGameMode(game_mode).into()); } if !ping.is_empty() { - packet(UpdatePlayerList::UpdateLatency(ping).into()); + push_packet(UpdatePlayerList::UpdateLatency(ping).into()); } if !display_name.is_empty() { - packet(UpdatePlayerList::UpdateDisplayName(display_name).into()); + push_packet(UpdatePlayerList::UpdateDisplayName(display_name).into()); } if self.modified_header_or_footer { - packet( + push_packet( PlayerListHeaderFooter { header: self.header.clone(), footer: self.footer.clone(), @@ -274,12 +321,8 @@ impl PlayerList { } } - pub(crate) fn update(&mut self) { - for e in self.entries.values_mut() { - e.bits = EntryBits::new(); - } - self.removed.clear(); - self.modified_header_or_footer = false; + pub(crate) fn clear_packets(&self, mut push_packet: impl FnMut(S2cPlayPacket)) { + push_packet(UpdatePlayerList::RemovePlayer(self.entries.keys().cloned().collect()).into()); } } diff --git a/src/server.rs b/src/server.rs index e190a9f..ab5d037 100644 --- a/src/server.rs +++ b/src/server.rs @@ -32,6 +32,7 @@ use crate::client::{Client, Clients}; use crate::config::{Config, ServerListPing}; use crate::dimension::{Dimension, DimensionId}; use crate::entity::Entities; +use crate::player_list::PlayerLists; use crate::player_textures::SignedPlayerTextures; use crate::protocol_inner::codec::{Decoder, Encoder}; use crate::protocol_inner::packets::c2s::handshake::{Handshake, HandshakeNextState}; @@ -64,6 +65,8 @@ pub struct Server { pub entities: Entities, /// All of the worlds in the server. pub worlds: Worlds, + /// All of the player lists in the server. + pub player_lists: PlayerLists, } /// A handle to a Minecraft server containing the subset of functionality which @@ -276,6 +279,7 @@ pub fn start_server(config: C, data: C::ServerState) -> ShutdownResul clients: Clients::new(), entities: Entities::new(), worlds: Worlds::new(shared.clone()), + player_lists: PlayerLists::new(), }; shared.config().init(&mut server); @@ -445,7 +449,12 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult { }); server.clients.par_iter_mut().for_each(|(_, client)| { - client.update(&shared, &server.entities, &server.worlds); + client.update( + &shared, + &server.entities, + &server.worlds, + &server.player_lists, + ); }); server.entities.update(); @@ -454,10 +463,10 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult { world.chunks.par_iter_mut().for_each(|(_, chunk)| { chunk.apply_modifications(); }); - - world.meta.update(); }); + server.player_lists.update(); + // Sleep for the remainder of the tick. let tick_duration = Duration::from_secs_f64((shared.0.tick_rate as f64).recip()); thread::sleep(tick_duration.saturating_sub(tick_start.elapsed())); diff --git a/src/slab.rs b/src/slab.rs index c1a44c3..aacb3ab 100644 --- a/src/slab.rs +++ b/src/slab.rs @@ -166,7 +166,7 @@ impl<'a, T> IntoIterator for &'a mut Slab { } } -impl<'a, T: Send + Sync> IntoParallelIterator for &'a Slab { +impl<'a, T: Sync> IntoParallelIterator for &'a Slab { type Item = (usize, &'a T); type Iter = ParIter<'a, T>; @@ -301,7 +301,7 @@ impl Clone for ParIter<'_, T> { } } -impl<'a, T: Send + Sync> ParallelIterator for ParIter<'a, T> { +impl<'a, T: Sync> ParallelIterator for ParIter<'a, T> { type Item = (usize, &'a T); fn drive_unindexed(self, consumer: C) -> C::Result diff --git a/src/slab_rc.rs b/src/slab_rc.rs new file mode 100644 index 0000000..b996569 --- /dev/null +++ b/src/slab_rc.rs @@ -0,0 +1,119 @@ +#![allow(dead_code)] + +use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; +use std::iter::FusedIterator; +use std::sync::Arc; + +use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; + +use crate::slab::Slab; + +#[derive(Clone, Debug)] +pub struct SlabRc { + slab: Slab>, +} + +#[derive(Debug)] +struct Slot { + value: T, + key: Key, +} + +impl Clone for Slot { + fn clone(&self) -> Self { + Self { + value: self.value.clone(), + key: Key(Arc::new(*self.key.0)), + } + } +} + +#[derive(Clone, Eq, Ord, Debug)] +pub struct Key(Arc); + +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0) + } +} + +impl PartialOrd for Key { + fn partial_cmp(&self, other: &Self) -> Option { + Arc::as_ptr(&self.0).partial_cmp(&Arc::as_ptr(&other.0)) + } +} + +impl Hash for Key { + fn hash(&self, state: &mut H) { + Arc::as_ptr(&self.0).hash(state) + } +} + +impl SlabRc { + pub const fn new() -> Self { + Self { slab: Slab::new() } + } + + pub fn get(&self, key: &Key) -> &T { + let slot = self.slab.get(*key.0).expect("invalid key"); + debug_assert_eq!(&slot.key, key, "invalid key"); + + &slot.value + } + + pub fn get_mut(&mut self, key: &Key) -> &mut T { + let slot = self.slab.get_mut(*key.0).expect("invalid key"); + debug_assert_eq!(&slot.key, key, "invalid key"); + + &mut slot.value + } + + pub fn len(&self) -> usize { + self.slab.len() + } + + pub fn insert(&mut self, value: T) -> (Key, &mut T) { + let (_, slot) = self.slab.insert_with(|idx| Slot { + value, + key: Key(Arc::new(idx)), + }); + + (slot.key.clone(), &mut slot.value) + } + + pub fn iter(&self) -> impl ExactSizeIterator + FusedIterator + Clone + '_ { + self.slab.iter().map(|(_, slot)| (&slot.key, &slot.value)) + } + + pub fn iter_mut( + &mut self, + ) -> impl ExactSizeIterator + FusedIterator + '_ { + self.slab + .iter_mut() + .map(|(_, slot)| (&slot.key, &mut slot.value)) + } + + pub fn par_iter(&self) -> impl ParallelIterator + Clone + '_ + where + T: Sync, + { + self.slab + .par_iter() + .map(|(_, slot)| (&slot.key, &slot.value)) + } + + pub fn par_iter_mut(&mut self) -> impl ParallelIterator + '_ + where + T: Send + Sync, + { + self.slab + .par_iter_mut() + .map(|(_, slot)| (&slot.key, &mut slot.value)) + } + + pub fn collect_garbage(&mut self) { + self.slab + .retain(|_, slot| Arc::strong_count(&slot.key.0) > 1); + } +} diff --git a/src/text.rs b/src/text.rs index 35483bd..5db2a28 100644 --- a/src/text.rs +++ b/src/text.rs @@ -83,7 +83,48 @@ pub struct Text { extra: Vec, } +#[allow(clippy::self_named_constructors)] impl Text { + /// Constructs a new plain text object. + pub fn text(plain: impl Into>) -> Self { + Self { + content: TextContent::Text { text: plain.into() }, + ..Self::default() + } + } + + /// Create translated text based on the given translation key. + pub fn translate(key: impl Into>) -> Self { + Self { + content: TextContent::Translate { + translate: key.into(), + }, + ..Self::default() + } + } + + /// Gets this text object as plain text without any formatting. + pub fn to_plain(&self) -> String { + let mut res = String::new(); + self.write_plain(&mut res) + .expect("failed to write plain text"); + res + } + + /// Writes this text object as plain text to the provided writer. + pub fn write_plain(&self, w: &mut impl fmt::Write) -> fmt::Result { + match &self.content { + TextContent::Text { text } => w.write_str(text.as_ref())?, + TextContent::Translate { translate } => w.write_str(translate.as_ref())?, + } + + for child in &self.extra { + child.write_plain(w)?; + } + + Ok(()) + } + /// Returns `true` if the text contains no characters. Returns `false` /// otherwise. pub fn is_empty(&self) -> bool { @@ -349,49 +390,6 @@ enum HoverEvent { }, } -#[allow(clippy::self_named_constructors)] -impl Text { - /// Constructs a new plain text object. - pub fn text(plain: impl Into>) -> Self { - Self { - content: TextContent::Text { text: plain.into() }, - ..Self::default() - } - } - - /// Create translated text based on the given translation key. - pub fn translate(key: impl Into>) -> Self { - Self { - content: TextContent::Translate { - translate: key.into(), - }, - ..Self::default() - } - } - - /// Gets this text object as plain text without any formatting. - pub fn to_plain(&self) -> String { - let mut res = String::new(); - self.write_plain(&mut res) - .expect("failed to write plain text"); - res - } - - /// Writes this text object as plain text to the provided writer. - pub fn write_plain(&self, w: &mut impl fmt::Write) -> fmt::Result { - match &self.content { - TextContent::Text { text } => w.write_str(text.as_ref())?, - TextContent::Translate { translate } => w.write_str(translate.as_ref())?, - } - - for child in &self.extra { - child.write_plain(w)?; - } - - Ok(()) - } -} - impl> TextFormat for T {} impl> std::ops::Add for Text { diff --git a/src/world.rs b/src/world.rs index 5a0bdb9..4aef41f 100644 --- a/src/world.rs +++ b/src/world.rs @@ -7,7 +7,6 @@ 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; use crate::slab_versioned::{Key, VersionedSlab}; use crate::spatial_index::SpatialIndex; @@ -53,7 +52,6 @@ impl Worlds { meta: WorldMeta { dimension: dim, is_flat: false, - player_list: PlayerList::new(), }, }); @@ -139,7 +137,6 @@ pub struct World { pub struct WorldMeta { dimension: DimensionId, is_flat: bool, - player_list: PlayerList, // TODO: time, weather } @@ -162,20 +159,4 @@ impl WorldMeta { pub fn set_flat(&mut self, flat: bool) { self.is_flat = flat; } - - /// Returns a shared reference to the world's - /// [`PlayerList`](crate::player_list::PlayerList). - pub fn player_list(&self) -> &PlayerList { - &self.player_list - } - - /// Returns an exclusive reference to the world's - /// [`PlayerList`](crate::player_list::PlayerList). - pub fn player_list_mut(&mut self) -> &mut PlayerList { - &mut self.player_list - } - - pub(crate) fn update(&mut self) { - self.player_list.update(); - } }