New player list implementation

This commit is contained in:
Ryan 2022-08-09 14:44:04 -07:00
parent 1838c290a0
commit a5a560220c
14 changed files with 391 additions and 158 deletions

View file

@ -8,6 +8,7 @@ use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId; use valence::dimension::DimensionId;
use valence::entity::types::Pose; use valence::entity::types::Pose;
use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::player_list::PlayerListId;
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::{async_trait, Ticks}; use valence::{async_trait, Ticks};
@ -23,7 +24,7 @@ pub fn main() -> ShutdownResult {
Game { Game {
player_count: AtomicUsize::new(0), player_count: AtomicUsize::new(0),
}, },
(), None,
) )
} }
@ -57,7 +58,8 @@ impl Config for Game {
type ChunkState = (); type ChunkState = ();
type ClientState = ClientState; type ClientState = ClientState;
type EntityState = EntityState; type EntityState = EntityState;
type ServerState = (); type PlayerListState = ();
type ServerState = Option<PlayerListId>;
type WorldState = (); type WorldState = ();
fn max_connections(&self) -> usize { fn max_connections(&self) -> usize {
@ -84,6 +86,7 @@ impl Config for Game {
fn init(&self, server: &mut Server<Self>) { fn init(&self, server: &mut Server<Self>) {
let (_, world) = server.worlds.insert(DimensionId::default(), ()); let (_, world) = server.worlds.insert(DimensionId::default(), ());
server.state = Some(server.player_lists.insert(()).0);
world.meta.set_flat(true); world.meta.set_flat(true);
let min_y = server.shared.dimension(DimensionId::default()).min_y; let min_y = server.shared.dimension(DimensionId::default()).min_y;
@ -159,15 +162,18 @@ impl Config for Game {
0.0, 0.0,
0.0, 0.0,
); );
client.set_player_list(server.state.clone());
world.meta.player_list_mut().insert( if let Some(id) = &server.state {
client.uuid(), server.player_lists.get_mut(id).insert(
client.username().to_owned(), client.uuid(),
client.textures().cloned(), client.username(),
client.game_mode(), client.textures().cloned(),
0, client.game_mode(),
None, 0,
); None,
);
}
client.send_message("Welcome to the arena.".italic()); client.send_message("Welcome to the arena.".italic());
if self.player_count.load(Ordering::SeqCst) <= 1 { if self.player_count.load(Ordering::SeqCst) <= 1 {
@ -178,7 +184,9 @@ impl Config for Game {
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state.player); 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; return false;
} }

View file

@ -15,6 +15,7 @@ use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::{async_trait, ident}; use valence::{async_trait, ident};
use valence::player_list::PlayerListId;
pub fn main() -> ShutdownResult { pub fn main() -> ShutdownResult {
env_logger::Builder::new() env_logger::Builder::new()
@ -27,6 +28,7 @@ pub fn main() -> ShutdownResult {
player_count: AtomicUsize::new(0), player_count: AtomicUsize::new(0),
}, },
ServerState { ServerState {
player_list: None,
board: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(), board: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(),
board_buf: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(), board_buf: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(),
}, },
@ -38,6 +40,7 @@ struct Game {
} }
struct ServerState { struct ServerState {
player_list: Option<PlayerListId>,
board: Box<[bool]>, board: Box<[bool]>,
board_buf: Box<[bool]>, board_buf: Box<[bool]>,
} }
@ -55,6 +58,7 @@ impl Config for Game {
type EntityState = (); type EntityState = ();
type ServerState = ServerState; type ServerState = ServerState;
type WorldState = (); type WorldState = ();
type PlayerListState = ();
fn max_connections(&self) -> usize { fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full. // We want status pings to be successful even if the server is full.
@ -96,6 +100,7 @@ impl Config for Game {
fn init(&self, server: &mut Server<Self>) { fn init(&self, server: &mut Server<Self>) {
let world = server.worlds.insert(DimensionId::default(), ()).1; let world = server.worlds.insert(DimensionId::default(), ()).1;
server.state.player_list = Some(server.player_lists.insert(()).0);
world.meta.set_flat(true); world.meta.set_flat(true);
for chunk_z in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 { for chunk_z in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 {
@ -140,15 +145,18 @@ impl Config for Game {
client.spawn(world_id); client.spawn(world_id);
client.teleport(spawn_pos, 0.0, 0.0); client.teleport(spawn_pos, 0.0, 0.0);
client.set_player_list(server.state.player_list.clone());
world.meta.player_list_mut().insert( if let Some(id) = &server.state.player_list {
client.uuid(), server.player_lists.get_mut(id).insert(
client.username().to_owned(), client.uuid(),
client.textures().cloned(), client.username(),
client.game_mode(), client.textures().cloned(),
0, client.game_mode(),
None, 0,
); None,
);
}
client.send_message("Welcome to Conway's game of life in Minecraft!".italic()); client.send_message("Welcome to Conway's game of life in Minecraft!".italic());
client.send_message("Hold the left mouse button to bring blocks to life.".italic()); client.send_message("Hold the left mouse button to bring blocks to life.".italic());
@ -157,7 +165,9 @@ impl Config for Game {
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
server.entities.remove(client.state); 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; return false;
} }

View file

@ -10,6 +10,7 @@ use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId; use valence::dimension::DimensionId;
use valence::entity::types::Pose; use valence::entity::types::Pose;
use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::player_list::PlayerListId;
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::util::to_yaw_and_pitch; use valence::util::to_yaw_and_pitch;
@ -25,7 +26,10 @@ pub fn main() -> ShutdownResult {
Game { Game {
player_count: AtomicUsize::new(0), player_count: AtomicUsize::new(0),
}, },
ServerState { cows: Vec::new() }, ServerState {
player_list: None,
cows: Vec::new(),
},
) )
} }
@ -34,6 +38,7 @@ struct Game {
} }
struct ServerState { struct ServerState {
player_list: Option<PlayerListId>,
cows: Vec<EntityId>, cows: Vec<EntityId>,
} }
@ -46,6 +51,7 @@ impl Config for Game {
type ChunkState = (); type ChunkState = ();
type ClientState = EntityId; type ClientState = EntityId;
type EntityState = (); type EntityState = ();
type PlayerListState = ();
type ServerState = ServerState; type ServerState = ServerState;
type WorldState = (); type WorldState = ();
@ -74,6 +80,7 @@ impl Config for Game {
fn init(&self, server: &mut Server<Self>) { fn init(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.insert(DimensionId::default(), ()); let (world_id, world) = server.worlds.insert(DimensionId::default(), ());
server.state.player_list = Some(server.player_lists.insert(()).0);
world.meta.set_flat(true); world.meta.set_flat(true);
let size = 5; let size = 5;
@ -93,7 +100,7 @@ impl Config for Game {
} }
fn update(&self, server: &mut Server<Self>) { fn update(&self, server: &mut Server<Self>) {
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| { server.clients.retain(|_, client| {
if client.created_this_tick() { if client.created_this_tick() {
@ -130,20 +137,25 @@ impl Config for Game {
0.0, 0.0,
0.0, 0.0,
); );
client.set_player_list(server.state.player_list.clone());
world.meta.player_list_mut().insert( if let Some(id) = &server.state.player_list {
client.uuid(), server.player_lists.get_mut(id).insert(
client.username().to_owned(), client.uuid(),
client.textures().cloned(), client.username(),
client.game_mode(), client.textures().cloned(),
0, client.game_mode(),
None, 0,
); None,
);
}
} }
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid()); if let Some(id) = &server.state.player_list {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.entities.remove(client.state); server.entities.remove(client.state);
return false; return false;

View file

@ -14,6 +14,7 @@ use valence::spatial_index::RaycastHit;
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::util::from_yaw_and_pitch; use valence::util::from_yaw_and_pitch;
use vek::Vec3; use vek::Vec3;
use valence::player_list::PlayerListId;
pub fn main() -> ShutdownResult { pub fn main() -> ShutdownResult {
env_logger::Builder::new() env_logger::Builder::new()
@ -25,7 +26,7 @@ pub fn main() -> ShutdownResult {
Game { Game {
player_count: AtomicUsize::new(0), player_count: AtomicUsize::new(0),
}, },
(), None,
) )
} }
@ -45,8 +46,9 @@ impl Config for Game {
type ClientState = EntityId; type ClientState = EntityId;
/// `true` for entities that have been intersected with. /// `true` for entities that have been intersected with.
type EntityState = bool; type EntityState = bool;
type ServerState = (); type ServerState = Option<PlayerListId>;
type WorldState = (); type WorldState = ();
type PlayerListState = ();
fn max_connections(&self) -> usize { fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full. // We want status pings to be successful even if the server is full.
@ -73,6 +75,7 @@ impl Config for Game {
fn init(&self, server: &mut Server<Self>) { fn init(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.insert(DimensionId::default(), ()); let (world_id, world) = server.worlds.insert(DimensionId::default(), ());
server.state = Some(server.player_lists.insert(()).0);
world.meta.set_flat(true); world.meta.set_flat(true);
let size = 5; let size = 5;
@ -134,15 +137,18 @@ impl Config for Game {
0.0, 0.0,
0.0, 0.0,
); );
client.set_player_list(server.state.clone());
world.meta.player_list_mut().insert( if let Some(id) = &server.state {
client.uuid(), server.player_lists.get_mut(id).insert(
client.username().to_owned(), client.uuid(),
client.textures().cloned(), client.username(),
client.game_mode(), client.textures().cloned(),
0, client.game_mode(),
None, 0,
); None,
);
}
client.send_message( client.send_message(
"Look at a sheep to change its ".italic() "Look at a sheep to change its ".italic()
@ -153,7 +159,9 @@ impl Config for Game {
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid()); if let Some(id) = &server.state {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.entities.remove(client.state); server.entities.remove(client.state);
return false; return false;

View file

@ -13,6 +13,7 @@ use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId; use valence::dimension::DimensionId;
use valence::entity::types::Pose; use valence::entity::types::Pose;
use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData};
use valence::player_list::PlayerListId;
use valence::server::{Server, SharedServer, ShutdownResult}; use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat}; use valence::text::{Color, TextFormat};
use valence::util::chunks_in_view_distance; 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)), gravel_noise: SuperSimplex::new().set_seed(seed.wrapping_add(3)),
grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)), grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)),
}, },
(), None,
) )
} }
@ -55,7 +56,8 @@ impl Config for Game {
type ChunkState = (); type ChunkState = ();
type ClientState = EntityId; type ClientState = EntityId;
type EntityState = (); type EntityState = ();
type ServerState = (); type PlayerListState = ();
type ServerState = Option<PlayerListId>;
type WorldState = (); type WorldState = ();
fn max_connections(&self) -> usize { fn max_connections(&self) -> usize {
@ -83,6 +85,7 @@ impl Config for Game {
fn init(&self, server: &mut Server<Self>) { fn init(&self, server: &mut Server<Self>) {
let (_, world) = server.worlds.insert(DimensionId::default(), ()); let (_, world) = server.worlds.insert(DimensionId::default(), ());
server.state = Some(server.player_lists.insert(()).0);
world.meta.set_flat(true); world.meta.set_flat(true);
} }
@ -118,22 +121,27 @@ impl Config for Game {
client.spawn(world_id); client.spawn(world_id);
client.set_game_mode(GameMode::Creative); client.set_game_mode(GameMode::Creative);
client.teleport([0.0, 200.0, 0.0], 0.0, 0.0); 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( if let Some(id) = &server.state {
client.uuid(), server.player_lists.get_mut(id).insert(
client.username().to_owned(), client.uuid(),
client.textures().cloned(), client.username(),
client.game_mode(), client.textures().cloned(),
0, client.game_mode(),
None, 0,
); None,
);
}
client.send_message("Welcome to the terrain example!".italic()); client.send_message("Welcome to the terrain example!".italic());
} }
if client.is_disconnected() { if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst); self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid()); if let Some(id) = &server.state {
server.player_lists.get_mut(id).remove(client.uuid());
}
server.entities.remove(client.state); server.entities.remove(client.state);
return false; return false;

View file

@ -2,6 +2,7 @@
use std::collections::{HashSet, VecDeque}; use std::collections::{HashSet, VecDeque};
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::mem;
use std::time::Duration; use std::time::Duration;
pub use bitfield_struct::bitfield; pub use bitfield_struct::bitfield;
@ -20,6 +21,7 @@ use crate::entity::data::Player;
use crate::entity::{ use crate::entity::{
velocity_to_packet_units, Entities, EntityEvent, EntityId, EntityKind, StatusOrAnimation, velocity_to_packet_units, Entities, EntityEvent, EntityId, EntityKind, StatusOrAnimation,
}; };
use crate::player_list::{PlayerListId, PlayerLists};
use crate::player_textures::SignedPlayerTextures; use crate::player_textures::SignedPlayerTextures;
use crate::protocol_inner::packets::c2s::play::{ use crate::protocol_inner::packets::c2s::play::{
C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId, C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId,
@ -180,6 +182,8 @@ pub struct Client<C: Config> {
username: String, username: String,
textures: Option<SignedPlayerTextures>, textures: Option<SignedPlayerTextures>,
world: WorldId, world: WorldId,
old_player_list: Option<PlayerListId>,
new_player_list: Option<PlayerListId>,
position: Vec3<f64>, position: Vec3<f64>,
old_position: Vec3<f64>, old_position: Vec3<f64>,
/// Measured in m/s. /// Measured in m/s.
@ -254,6 +258,8 @@ impl<C: Config> Client<C> {
username: ncd.username, username: ncd.username,
textures: ncd.textures, textures: ncd.textures,
world: WorldId::default(), world: WorldId::default(),
old_player_list: None,
new_player_list: None,
position: Vec3::default(), position: Vec3::default(),
old_position: Vec3::default(), old_position: Vec3::default(),
velocity: Vec3::default(), velocity: Vec3::default(),
@ -311,6 +317,14 @@ impl<C: Config> Client<C> {
self.world self.world
} }
pub fn player_list(&self) -> Option<&PlayerListId> {
self.new_player_list.as_ref()
}
pub fn set_player_list(&mut self, id: Option<PlayerListId>) -> Option<PlayerListId> {
mem::replace(&mut self.new_player_list, id)
}
/// Changes the world this client is located in and respawns the client. /// Changes the world this client is located in and respawns the client.
/// This can be used to respawn the client after death. /// This can be used to respawn the client after death.
/// ///
@ -825,6 +839,7 @@ impl<C: Config> Client<C> {
shared: &SharedServer<C>, shared: &SharedServer<C>,
entities: &Entities<C>, entities: &Entities<C>,
worlds: &Worlds<C>, worlds: &Worlds<C>,
player_lists: &PlayerLists<C>,
) { ) {
// Mark the client as disconnected when appropriate. // Mark the client as disconnected when appropriate.
if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) { if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) {
@ -849,10 +864,11 @@ impl<C: Config> Client<C> {
// Send the join game packet and other initial packets. We defer this until now // 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. // so that the user can set the client's location, game mode, etc.
if self.created_this_tick() { if self.created_this_tick() {
world if let Some(id) = &self.new_player_list {
.meta player_lists
.player_list() .get(id)
.initial_packets(|pkt| self.send_packet(pkt)); .initial_packets(|p| send_packet(&mut self.send, p));
}
let mut dimension_names: Vec<_> = shared let mut dimension_names: Vec<_> = shared
.dimensions() .dimensions()
@ -896,8 +912,6 @@ impl<C: Config> Client<C> {
self.loaded_entities.clear(); self.loaded_entities.clear();
self.loaded_chunks.clear(); self.loaded_chunks.clear();
// TODO: clear player list.
// Client bug workaround: send the client to a dummy dimension first. // Client bug workaround: send the client to a dummy dimension first.
// TODO: is there actually a bug? // TODO: is there actually a bug?
self.send_packet(PlayerRespawn { self.send_packet(PlayerRespawn {
@ -935,6 +949,7 @@ impl<C: Config> Client<C> {
self.teleport(self.position(), self.yaw(), self.pitch()); self.teleport(self.position(), self.yaw(), self.pitch());
} }
// Update game mode
if self.old_game_mode != self.new_game_mode { if self.old_game_mode != self.new_game_mode {
self.old_game_mode = self.new_game_mode; self.old_game_mode = self.new_game_mode;
self.send_packet(GameStateChange { self.send_packet(GameStateChange {
@ -943,10 +958,29 @@ impl<C: Config> Client<C> {
}); });
} }
world // If the player list was changed...
.meta if self.old_player_list != self.new_player_list {
.player_list() // Delete existing entries from old player list.
.diff_packets(|pkt| self.send_packet(pkt)); 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 // Set player attributes

View file

@ -31,6 +31,8 @@ pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe {
type WorldState: Send + Sync; type WorldState: Send + Sync;
/// Custom state to store with every [`Chunk`](crate::chunk::Chunk). /// Custom state to store with every [`Chunk`](crate::chunk::Chunk).
type ChunkState: Send + Sync; 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 /// Called once at startup to get the maximum number of simultaneous
/// connections allowed to the server. This includes all /// connections allowed to the server. This includes all

View file

@ -147,6 +147,7 @@ pub mod player_textures;
mod protocol_inner; mod protocol_inner;
pub mod server; pub mod server;
mod slab; mod slab;
mod slab_rc;
mod slab_versioned; mod slab_versioned;
pub mod spatial_index; pub mod spatial_index;
pub mod text; pub mod text;

View file

@ -7,14 +7,67 @@ use bitfield_struct::bitfield;
use uuid::Uuid; use uuid::Uuid;
use crate::client::GameMode; use crate::client::GameMode;
use crate::config::Config;
use crate::player_textures::SignedPlayerTextures; use crate::player_textures::SignedPlayerTextures;
use crate::protocol_inner::packets::s2c::play::{ use crate::protocol_inner::packets::s2c::play::{
PlayerListAddPlayer, PlayerListHeaderFooter, S2cPlayPacket, UpdatePlayerList, PlayerListAddPlayer, PlayerListHeaderFooter, S2cPlayPacket, UpdatePlayerList,
}; };
use crate::protocol_inner::packets::Property; use crate::protocol_inner::packets::Property;
use crate::protocol_inner::VarInt; use crate::protocol_inner::VarInt;
use crate::slab_rc::{Key, SlabRc};
use crate::text::Text; use crate::text::Text;
pub struct PlayerLists<C: Config> {
slab: SlabRc<PlayerList<C>>,
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct PlayerListId(Key);
impl<C: Config> PlayerLists<C> {
pub(crate) fn new() -> Self {
Self {
slab: SlabRc::new(),
}
}
pub fn insert(&mut self, state: C::PlayerListState) -> (PlayerListId, &mut PlayerList<C>) {
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<C> {
self.slab.get(&id.0)
}
pub fn get_mut(&mut self, id: &PlayerListId) -> &mut PlayerList<C> {
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. /// 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 /// 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 /// In addition to a list of players, the player list has a header and a footer
/// which can contain arbitrary text. /// which can contain arbitrary text.
pub struct PlayerList { pub struct PlayerList<C: Config> {
/// Custom state
pub state: C::PlayerListState,
entries: HashMap<Uuid, PlayerListEntry>, entries: HashMap<Uuid, PlayerListEntry>,
removed: HashSet<Uuid>, removed: HashSet<Uuid>,
header: Text, header: Text,
@ -30,17 +85,7 @@ pub struct PlayerList {
modified_header_or_footer: bool, modified_header_or_footer: bool,
} }
impl PlayerList { impl<C: Config> PlayerList<C> {
pub(crate) fn new() -> Self {
Self {
entries: HashMap::new(),
removed: HashSet::new(),
header: Text::default(),
footer: Text::default(),
modified_header_or_footer: false,
}
}
/// Inserts a player into the player list. /// Inserts a player into the player list.
/// ///
/// If the given UUID conflicts with an existing entry, the entry is /// 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)) 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 let add_player: Vec<_> = self
.entries .entries
.iter() .iter()
@ -186,11 +231,11 @@ impl PlayerList {
.collect(); .collect();
if !add_player.is_empty() { 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() { if self.header != Text::default() || self.footer != Text::default() {
packet( push_packet(
PlayerListHeaderFooter { PlayerListHeaderFooter {
header: self.header.clone(), header: self.header.clone(),
footer: self.footer.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() { 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(); let mut add_player = Vec::new();
@ -248,23 +295,23 @@ impl PlayerList {
} }
if !add_player.is_empty() { if !add_player.is_empty() {
packet(UpdatePlayerList::AddPlayer(add_player).into()); push_packet(UpdatePlayerList::AddPlayer(add_player).into());
} }
if !game_mode.is_empty() { if !game_mode.is_empty() {
packet(UpdatePlayerList::UpdateGameMode(game_mode).into()); push_packet(UpdatePlayerList::UpdateGameMode(game_mode).into());
} }
if !ping.is_empty() { if !ping.is_empty() {
packet(UpdatePlayerList::UpdateLatency(ping).into()); push_packet(UpdatePlayerList::UpdateLatency(ping).into());
} }
if !display_name.is_empty() { if !display_name.is_empty() {
packet(UpdatePlayerList::UpdateDisplayName(display_name).into()); push_packet(UpdatePlayerList::UpdateDisplayName(display_name).into());
} }
if self.modified_header_or_footer { if self.modified_header_or_footer {
packet( push_packet(
PlayerListHeaderFooter { PlayerListHeaderFooter {
header: self.header.clone(), header: self.header.clone(),
footer: self.footer.clone(), footer: self.footer.clone(),
@ -274,12 +321,8 @@ impl PlayerList {
} }
} }
pub(crate) fn update(&mut self) { pub(crate) fn clear_packets(&self, mut push_packet: impl FnMut(S2cPlayPacket)) {
for e in self.entries.values_mut() { push_packet(UpdatePlayerList::RemovePlayer(self.entries.keys().cloned().collect()).into());
e.bits = EntryBits::new();
}
self.removed.clear();
self.modified_header_or_footer = false;
} }
} }

View file

@ -32,6 +32,7 @@ use crate::client::{Client, Clients};
use crate::config::{Config, ServerListPing}; use crate::config::{Config, ServerListPing};
use crate::dimension::{Dimension, DimensionId}; use crate::dimension::{Dimension, DimensionId};
use crate::entity::Entities; use crate::entity::Entities;
use crate::player_list::PlayerLists;
use crate::player_textures::SignedPlayerTextures; use crate::player_textures::SignedPlayerTextures;
use crate::protocol_inner::codec::{Decoder, Encoder}; use crate::protocol_inner::codec::{Decoder, Encoder};
use crate::protocol_inner::packets::c2s::handshake::{Handshake, HandshakeNextState}; use crate::protocol_inner::packets::c2s::handshake::{Handshake, HandshakeNextState};
@ -64,6 +65,8 @@ pub struct Server<C: Config> {
pub entities: Entities<C>, pub entities: Entities<C>,
/// All of the worlds in the server. /// All of the worlds in the server.
pub worlds: Worlds<C>, pub worlds: Worlds<C>,
/// All of the player lists in the server.
pub player_lists: PlayerLists<C>,
} }
/// A handle to a Minecraft server containing the subset of functionality which /// A handle to a Minecraft server containing the subset of functionality which
@ -276,6 +279,7 @@ pub fn start_server<C: Config>(config: C, data: C::ServerState) -> ShutdownResul
clients: Clients::new(), clients: Clients::new(),
entities: Entities::new(), entities: Entities::new(),
worlds: Worlds::new(shared.clone()), worlds: Worlds::new(shared.clone()),
player_lists: PlayerLists::new(),
}; };
shared.config().init(&mut server); shared.config().init(&mut server);
@ -445,7 +449,12 @@ fn do_update_loop<C: Config>(server: &mut Server<C>) -> ShutdownResult {
}); });
server.clients.par_iter_mut().for_each(|(_, client)| { 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(); server.entities.update();
@ -454,10 +463,10 @@ fn do_update_loop<C: Config>(server: &mut Server<C>) -> ShutdownResult {
world.chunks.par_iter_mut().for_each(|(_, chunk)| { world.chunks.par_iter_mut().for_each(|(_, chunk)| {
chunk.apply_modifications(); chunk.apply_modifications();
}); });
world.meta.update();
}); });
server.player_lists.update();
// Sleep for the remainder of the tick. // Sleep for the remainder of the tick.
let tick_duration = Duration::from_secs_f64((shared.0.tick_rate as f64).recip()); let tick_duration = Duration::from_secs_f64((shared.0.tick_rate as f64).recip());
thread::sleep(tick_duration.saturating_sub(tick_start.elapsed())); thread::sleep(tick_duration.saturating_sub(tick_start.elapsed()));

View file

@ -166,7 +166,7 @@ impl<'a, T> IntoIterator for &'a mut Slab<T> {
} }
} }
impl<'a, T: Send + Sync> IntoParallelIterator for &'a Slab<T> { impl<'a, T: Sync> IntoParallelIterator for &'a Slab<T> {
type Item = (usize, &'a T); type Item = (usize, &'a T);
type Iter = ParIter<'a, T>; type Iter = ParIter<'a, T>;
@ -301,7 +301,7 @@ impl<T> 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); type Item = (usize, &'a T);
fn drive_unindexed<C>(self, consumer: C) -> C::Result fn drive_unindexed<C>(self, consumer: C) -> C::Result

119
src/slab_rc.rs Normal file
View file

@ -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<T> {
slab: Slab<Slot<T>>,
}
#[derive(Debug)]
struct Slot<T> {
value: T,
key: Key,
}
impl<T: Clone> Clone for Slot<T> {
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<usize>);
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<Ordering> {
Arc::as_ptr(&self.0).partial_cmp(&Arc::as_ptr(&other.0))
}
}
impl Hash for Key {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state)
}
}
impl<T> SlabRc<T> {
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<Item = (&Key, &T)> + FusedIterator + Clone + '_ {
self.slab.iter().map(|(_, slot)| (&slot.key, &slot.value))
}
pub fn iter_mut(
&mut self,
) -> impl ExactSizeIterator<Item = (&Key, &mut T)> + FusedIterator + '_ {
self.slab
.iter_mut()
.map(|(_, slot)| (&slot.key, &mut slot.value))
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (&Key, &T)> + Clone + '_
where
T: Sync,
{
self.slab
.par_iter()
.map(|(_, slot)| (&slot.key, &slot.value))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (&Key, &mut T)> + '_
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);
}
}

View file

@ -83,7 +83,48 @@ pub struct Text {
extra: Vec<Text>, extra: Vec<Text>,
} }
#[allow(clippy::self_named_constructors)]
impl Text { impl Text {
/// Constructs a new plain text object.
pub fn text(plain: impl Into<Cow<'static, str>>) -> 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<Cow<'static, str>>) -> 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` /// Returns `true` if the text contains no characters. Returns `false`
/// otherwise. /// otherwise.
pub fn is_empty(&self) -> bool { 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<Cow<'static, str>>) -> 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<Cow<'static, str>>) -> 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<T: Into<Text>> TextFormat for T {} impl<T: Into<Text>> TextFormat for T {}
impl<T: Into<Text>> std::ops::Add<T> for Text { impl<T: Into<Text>> std::ops::Add<T> for Text {

View file

@ -7,7 +7,6 @@ use rayon::iter::ParallelIterator;
use crate::chunk::Chunks; use crate::chunk::Chunks;
use crate::config::Config; use crate::config::Config;
use crate::dimension::DimensionId; use crate::dimension::DimensionId;
use crate::player_list::PlayerList;
use crate::server::SharedServer; use crate::server::SharedServer;
use crate::slab_versioned::{Key, VersionedSlab}; use crate::slab_versioned::{Key, VersionedSlab};
use crate::spatial_index::SpatialIndex; use crate::spatial_index::SpatialIndex;
@ -53,7 +52,6 @@ impl<C: Config> Worlds<C> {
meta: WorldMeta { meta: WorldMeta {
dimension: dim, dimension: dim,
is_flat: false, is_flat: false,
player_list: PlayerList::new(),
}, },
}); });
@ -139,7 +137,6 @@ pub struct World<C: Config> {
pub struct WorldMeta { pub struct WorldMeta {
dimension: DimensionId, dimension: DimensionId,
is_flat: bool, is_flat: bool,
player_list: PlayerList,
// TODO: time, weather // TODO: time, weather
} }
@ -162,20 +159,4 @@ impl WorldMeta {
pub fn set_flat(&mut self, flat: bool) { pub fn set_flat(&mut self, flat: bool) {
self.is_flat = flat; 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();
}
} }