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::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<PlayerListId>;
type WorldState = ();
fn max_connections(&self) -> usize {
@ -84,6 +86,7 @@ impl Config for Game {
fn init(&self, server: &mut Server<Self>) {
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;
}

View file

@ -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<PlayerListId>,
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<Self>) {
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;
}

View file

@ -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<PlayerListId>,
cows: Vec<EntityId>,
}
@ -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<Self>) {
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<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| {
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;

View file

@ -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<PlayerListId>;
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<Self>) {
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;

View file

@ -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<PlayerListId>;
type WorldState = ();
fn max_connections(&self) -> usize {
@ -83,6 +85,7 @@ impl Config for Game {
fn init(&self, server: &mut Server<Self>) {
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;

View file

@ -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<C: Config> {
username: String,
textures: Option<SignedPlayerTextures>,
world: WorldId,
old_player_list: Option<PlayerListId>,
new_player_list: Option<PlayerListId>,
position: Vec3<f64>,
old_position: Vec3<f64>,
/// Measured in m/s.
@ -254,6 +258,8 @@ impl<C: Config> Client<C> {
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<C: Config> Client<C> {
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.
/// This can be used to respawn the client after death.
///
@ -825,6 +839,7 @@ impl<C: Config> Client<C> {
shared: &SharedServer<C>,
entities: &Entities<C>,
worlds: &Worlds<C>,
player_lists: &PlayerLists<C>,
) {
// 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<C: Config> Client<C> {
// 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<C: Config> Client<C> {
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<C: Config> Client<C> {
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<C: Config> Client<C> {
});
}
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

View file

@ -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

View file

@ -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;

View file

@ -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<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.
///
/// 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<C: Config> {
/// Custom state
pub state: C::PlayerListState,
entries: HashMap<Uuid, PlayerListEntry>,
removed: HashSet<Uuid>,
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<C: Config> PlayerList<C> {
/// 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());
}
}

View file

@ -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<C: Config> {
pub entities: Entities<C>,
/// All of the worlds in the server.
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
@ -276,6 +279,7 @@ pub fn start_server<C: Config>(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<C: Config>(server: &mut Server<C>) -> 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<C: Config>(server: &mut Server<C>) -> 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()));

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 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);
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>,
}
#[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(())
}
/// 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<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>> std::ops::Add<T> for Text {

View file

@ -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<C: Config> Worlds<C> {
meta: WorldMeta {
dimension: dim,
is_flat: false,
player_list: PlayerList::new(),
},
});
@ -139,7 +137,6 @@ pub struct World<C: Config> {
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();
}
}