mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-26 21:46:33 +11:00
New player list implementation
This commit is contained in:
parent
1838c290a0
commit
a5a560220c
14 changed files with 391 additions and 158 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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
119
src/slab_rc.rs
Normal 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);
|
||||
}
|
||||
}
|
84
src/text.rs
84
src/text.rs
|
@ -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 {
|
||||
|
|
19
src/world.rs
19
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<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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue