From a0892faa7293de5c6e208a181b2ca377f878c645 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 30 Apr 2022 05:06:20 -0700 Subject: [PATCH] Put all config options in a single trait --- examples/basic.rs | 95 +++++--- src/chunk.rs | 6 +- src/client.rs | 42 ++-- src/component.rs | 555 --------------------------------------------- src/config.rs | 357 +++++++++++------------------ src/entity/meta.rs | 2 +- src/lib.rs | 8 +- src/packets.rs | 11 +- src/server.rs | 259 ++++++++++++--------- 9 files changed, 389 insertions(+), 946 deletions(-) delete mode 100644 src/component.rs diff --git a/examples/basic.rs b/examples/basic.rs index 5077814..58e797e 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,11 +1,14 @@ use std::net::SocketAddr; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use async_trait::async_trait; use log::LevelFilter; -use valence::config::{Handler, ServerListPing}; +use valence::client::GameMode; +use valence::config::{Config, Login, ServerListPing}; use valence::text::Color; -use valence::{glm, DimensionId, Server, ServerConfig, SharedServer, ShutdownResult, TextFormat}; +use valence::{ + async_trait, DimensionId, NewClientData, Server, SharedServer, ShutdownResult, TextFormat, +}; pub fn main() -> ShutdownResult { env_logger::Builder::new() @@ -13,23 +16,31 @@ pub fn main() -> ShutdownResult { .parse_default_env() .init(); - let game = Game { + valence::start_server(Game { favicon: Arc::from(include_bytes!("favicon.png").as_slice()), - }; - - let mut cfg = ServerConfig::new(); - - cfg.handler(game); - cfg.online_mode(false); - cfg.start() + player_count: AtomicUsize::new(0), + }) } struct Game { favicon: Arc<[u8]>, + player_count: AtomicUsize, } +const MAX_PLAYERS: usize = 10; + #[async_trait] -impl Handler for Game { +impl Config for Game { + fn max_connections(&self) -> usize { + // We want status pings to be successful even if the server is full. + MAX_PLAYERS + 64 + } + + fn online_mode(&self) -> bool { + // You'll want this to be true on real servers. + false + } + fn init(&self, server: &mut Server) { let world_id = server.worlds.create(DimensionId::default()); let world = server.worlds.get_mut(world_id).unwrap(); @@ -41,10 +52,18 @@ impl Handler for Game { let chunk_id = server.chunks.create(384); let chunk = server.chunks.get_mut(chunk_id).unwrap(); - for z in 0..16 { - for x in 0..16 { - for y in 0..50 { - chunk.set_block_state(x, y, z, 1); + // Chunks are only visible to clients if all adjacent chunks are loaded. + // This will make the perimiter chunks contain only air. + if x != -chunk_radius + && x != chunk_radius - 1 + && z != -chunk_radius + && z != chunk_radius - 1 + { + for z in 0..16 { + for x in 0..16 { + for y in 0..50 { + chunk.set_block_state(x, y, z, 1); + } } } } @@ -54,34 +73,50 @@ impl Handler for Game { } } - async fn server_list_ping( - &self, - server: &SharedServer, - _remote_addr: SocketAddr, - ) -> ServerListPing { - ServerListPing::Respond { - online_players: server.client_count() as i32, - max_players: server.max_clients() as i32, - description: "Hello Valence!".color(Color::AQUA), - favicon_png: Some(self.favicon.clone()), - } - } - fn update(&self, server: &mut Server) { let world_id = server.worlds.iter().next().unwrap().0; server.clients.retain(|_, client| { if client.created_tick() == server.other.current_tick() { client.set_world(Some(world_id)); - client.teleport(glm::vec3(0.0, 200.0, 0.0), 0.0, 0.0); + client.set_game_mode(GameMode::Creative); + client.teleport([0.0, 200.0, 0.0], 0.0, 0.0); } if client.is_disconnected() { server.entities.delete(client.entity()); + self.player_count.fetch_sub(1, Ordering::SeqCst); false } else { true } }); } + + async fn server_list_ping( + &self, + _server: &SharedServer, + _remote_addr: SocketAddr, + ) -> ServerListPing { + ServerListPing::Respond { + online_players: self.player_count.load(Ordering::SeqCst) as i32, + max_players: MAX_PLAYERS as i32, + description: "Hello Valence!".color(Color::AQUA), + favicon_png: Some(self.favicon.clone()), + } + } + + async fn login(&self, _server: &SharedServer, _ncd: &NewClientData) -> Login { + let res = self + .player_count + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { + (count < MAX_PLAYERS).then(|| count + 1) + }); + + if res.is_ok() { + Login::Join + } else { + Login::Disconnect("The server is full!".into()) + } + } } diff --git a/src/chunk.rs b/src/chunk.rs index 5f5554f..cca250a 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -194,11 +194,13 @@ impl Chunk { blocks_and_biomes, block_entities: Vec::new(), // TODO trust_edges: true, - sky_light_mask: bitvec![u64, _; 1; section_count + 2], + // sky_light_mask: bitvec![u64, _; 1; section_count + 2], + sky_light_mask: BitVec::new(), block_light_mask: BitVec::new(), empty_sky_light_mask: BitVec::new(), empty_block_light_mask: BitVec::new(), - sky_light_arrays: vec![[0xff; 2048]; section_count + 2], + // sky_light_arrays: vec![[0xff; 2048]; section_count + 2], + sky_light_arrays: Vec::new(), block_light_arrays: Vec::new(), } } diff --git a/src/client.rs b/src/client.rs index ef75016..90bdbe4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -14,10 +14,11 @@ use crate::config::{ pub use crate::packets::play::GameMode; use crate::packets::play::{ Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, - BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ClientPlayPacket, - DimensionCodec, DimensionType, DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, - JoinGame, KeepAliveClientbound, PlayerPositionAndLook, PlayerPositionAndLookFlags, - ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition, + BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState, + ChangeGameStateReason, ClientPlayPacket, DimensionCodec, DimensionType, DimensionTypeRegistry, + DimensionTypeRegistryEntry, Disconnect, JoinGame, KeepAliveClientbound, PlayerPositionAndLook, + PlayerPositionAndLookFlags, ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, + UpdateViewPosition, }; use crate::protocol::{BoundedInt, Nbt}; use crate::server::{Other, ServerPacketChannels}; @@ -84,7 +85,6 @@ pub struct ClientId(Key); /// Represents a client connected to the server after logging in. pub struct Client { - shared: SharedServer, /// Setting this to `None` disconnects the client. send: Option>, recv: Receiver, @@ -147,7 +147,6 @@ impl Client { let (send, recv) = packet_channels; Self { - shared: server.shared().clone(), send: Some(send), recv, entity, @@ -200,10 +199,10 @@ impl Client { self.pitch } - pub fn teleport(&mut self, pos: DVec3, yaw_degrees: f32, pitch_degrees: f32) { - self.new_position = pos; - self.yaw = yaw_degrees; - self.pitch = pitch_degrees; + pub fn teleport(&mut self, pos: impl Into, yaw: f32, pitch: f32) { + self.new_position = pos.into(); + self.yaw = yaw; + self.pitch = pitch; if !self.teleported_this_tick { self.teleported_this_tick = true; @@ -220,6 +219,14 @@ impl Client { } } + pub fn game_mode(&self) -> GameMode { + self.new_game_mode + } + + pub fn set_game_mode(&mut self, new_game_mode: GameMode) { + self.new_game_mode = new_game_mode; + } + pub fn on_ground(&self) -> bool { self.on_ground } @@ -329,12 +336,12 @@ impl Client { if self.created_tick == other.current_tick() { self.send_packet(JoinGame { entity_id: self.entity.to_network_id(), - is_hardcore: false, + is_hardcore: false, // TODO gamemode: self.new_game_mode, previous_gamemode: self.old_game_mode, dimension_names: other .dimensions() - .map(|(_, id)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0)) + .map(|(id, _)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0)) .collect(), dimension_codec: Nbt(make_dimension_codec(other)), dimension: Nbt(to_dimension_registry_item(dim)), @@ -350,6 +357,12 @@ impl Client { }); self.teleport(self.position(), self.yaw(), self.pitch()); + } else if self.old_game_mode != self.new_game_mode { + self.old_game_mode = self.new_game_mode; + self.send_packet(ChangeGameState { + reason: ChangeGameStateReason::ChangeGameMode, + value: self.new_game_mode as i32 as f32, + }); } // Update the players spawn position (compass position) @@ -635,7 +648,6 @@ impl Client { impl Drop for Client { fn drop(&mut self) { log::trace!("Dropping client '{}'", self.username); - self.shared.dec_client_count(); } } @@ -690,7 +702,7 @@ fn send_packet(send_opt: &mut Option>, pkt: impl Into DimensionCodec { let mut dims = Vec::new(); - for (dim, id) in other.dimensions() { + for (id, dim) in other.dimensions() { let id = id.0 as i32; dims.push(DimensionTypeRegistryEntry { name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"), @@ -700,7 +712,7 @@ fn make_dimension_codec(other: &Other) -> DimensionCodec { } let mut biomes = Vec::new(); - for (biome, id) in other.biomes() { + for (id, biome) in other.biomes() { biomes.push(to_biome_registry_item(biome, id.0 as i32)); } diff --git a/src/component.rs b/src/component.rs deleted file mode 100644 index c7cfa7a..0000000 --- a/src/component.rs +++ /dev/null @@ -1,555 +0,0 @@ -use std::any::{Any, TypeId}; -use std::collections::hash_map::Entry; -use std::collections::HashMap; -use std::iter::FusedIterator; -use std::marker::PhantomData; -use std::num::NonZeroU32; - -use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use rayon::iter::{ - IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, - IntoParallelRefMutIterator, ParallelIterator, -}; -use thiserror::Error; - -/// Contains custom components -pub(crate) struct ComponentStore { - ids: Vec, - next_free_head: u32, - count: u32, - components: HashMap>, - _marker: PhantomData I>, -} - -impl ComponentStore { - pub fn new() -> Self { - Self { - ids: Vec::new(), - next_free_head: 0, - count: 0, - components: HashMap::new(), - _marker: PhantomData, - } - } - - pub fn count(&self) -> usize { - self.count as usize - } - - pub fn create_item(&mut self) -> I { - assert!(self.count < u32::MAX - 1, "too many items"); - - if self.next_free_head == self.ids.len() as u32 { - self.ids.push(Slot { - gen: ONE, - next_free: None, - }); - self.count += 1; - self.next_free_head += 1; - for v in self.components.values_mut() { - v.push_default(); - } - - I::from_data(IdData { - idx: self.next_free_head - 1, - gen: ONE, - }) - } else { - let s = &mut self.ids[self.next_free_head as usize]; - s.gen = match NonZeroU32::new(s.gen.get().wrapping_add(1)) { - Some(n) => n, - None => { - log::warn!("generation overflow at idx = {}", self.next_free_head); - ONE - } - }; - let next_free = s.next_free.expect("corrupt free list"); - let id = I::from_data(IdData { - idx: self.next_free_head, - gen: s.gen, - }); - - self.next_free_head = next_free; - self.count += 1; - s.next_free = None; - id - } - } - - pub fn delete_item(&mut self, id: I) -> bool { - let id = id.to_data(); - match self.ids.get_mut(id.idx as usize) { - Some(Slot { - gen, - next_free: nf @ None, - }) if *gen == id.gen => { - *nf = Some(self.next_free_head); - self.next_free_head = id.idx; - self.count -= 1; - - for vec in self.components.values_mut() { - vec.clear_at(id.idx as usize); - } - - true - } - _ => false, - } - } - - pub fn is_valid(&self, id: I) -> bool { - let id = id.to_data(); - match self.ids.get(id.idx as usize) { - Some(Slot { - gen, - next_free: None, - }) => *gen == id.gen, - _ => false, - } - } - - pub fn get>(&self, z: Z, id: I) -> Option { - if self.is_valid(id) { - Some(z.raw_get(id.to_data().idx as usize)) - } else { - None - } - } - - pub fn iter<'a, Z: ZippedComponents + 'a>( - &'a self, - z: Z, - ) -> impl FusedIterator + 'a { - self.ids - .iter() - .zip(z.raw_iter()) - .enumerate() - .filter_map(|(i, (s, c))| { - if s.next_free.is_none() { - Some(( - I::from_data(IdData { - idx: i as u32, - gen: s.gen, - }), - c, - )) - } else { - None - } - }) - } - - pub fn par_iter<'a, Z: ZippedComponents + 'a>( - &'a self, - z: Z, - ) -> impl ParallelIterator + 'a { - self.ids - .par_iter() - .zip(z.raw_par_iter()) - .enumerate() - .filter_map(|(i, (s, c))| { - if s.next_free.is_none() { - Some(( - I::from_data(IdData { - idx: i as u32, - gen: s.gen, - }), - c, - )) - } else { - None - } - }) - } - - pub fn ids(&self) -> impl FusedIterator + Clone + '_ { - self.ids.iter().enumerate().filter_map(|(i, s)| { - if s.next_free.is_none() { - Some(I::from_data(IdData { - idx: i as u32, - gen: s.gen, - })) - } else { - None - } - }) - } - - pub fn par_ids(&self) -> impl ParallelIterator + Clone + '_ { - self.ids.par_iter().enumerate().filter_map(|(i, s)| { - if s.next_free.is_none() { - Some(I::from_data(IdData { - idx: i as u32, - gen: s.gen, - })) - } else { - None - } - }) - } - - pub fn register_component(&mut self) { - if let Entry::Vacant(ve) = self.components.entry(TypeId::of::()) { - let mut vec = Vec::new(); - vec.resize_with(self.ids.len(), C::default_private); - ve.insert(Box::new(RwLock::new(vec))); - } - } - - pub fn unregister_component(&mut self) { - self.components.remove(&TypeId::of::()); - } - - pub fn is_registered(&self) -> bool { - self.components.contains_key(&TypeId::of::()) - } - - pub fn components( - &self, - ) -> Result, Error> { - let handle = self - .components - .get(&TypeId::of::()) - .ok_or(Error::UnknownComponent)? - .as_any() - .downcast_ref::>>() - .unwrap() - .try_read() - .ok_or(Error::NoReadAccess)?; - - Ok(Components { - handle, - _marker: PhantomData, - }) - } - - pub fn components_mut( - &self, - ) -> Result, Error> { - let handle = self - .components - .get(&TypeId::of::()) - .ok_or(Error::UnknownComponent)? - .as_any() - .downcast_ref::>>() - .unwrap() - .try_write() - .ok_or(Error::NoWriteAccess)?; - - Ok(ComponentsMut { - handle, - _marker: PhantomData, - }) - } -} - -#[derive(Clone, Copy, Debug)] -struct Slot { - gen: NonZeroU32, - next_free: Option, -} - -pub trait Id: IdRaw + Copy + Send + Sync {} - -const ONE: NonZeroU32 = match NonZeroU32::new(1) { - Some(n) => n, - None => unreachable!(), -}; - -trait ComponentVec: Any + Send + Sync { - fn push_default(&mut self); - - fn clear_at(&mut self, idx: usize); - - fn as_any(&self) -> &dyn Any; - - fn as_any_mut(&mut self) -> &mut dyn Any; -} - -impl ComponentVec for RwLock> { - fn push_default(&mut self) { - self.get_mut().push(T::default_private()); - } - - fn clear_at(&mut self, idx: usize) { - self.get_mut()[idx] = T::default_private(); - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - -pub struct Components<'a, C: 'static + Send + Sync, I: Id> { - handle: RwLockReadGuard<'a, Vec>, - _marker: PhantomData I>, -} - -impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponentsRaw for &'b Components<'a, C, I> { - type RawItem = &'b C; - type RawIter = std::slice::Iter<'b, C>; - type RawParIter = rayon::slice::Iter<'b, C>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.handle[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.handle.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.handle.par_iter() - } -} - -impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponents for &'b Components<'a, C, I> { - type Id = I; - type Item = &'b C; -} - -pub struct ComponentsMut<'a, C: 'static + Send + Sync, I: Id> { - handle: RwLockWriteGuard<'a, Vec>, - _marker: PhantomData I>, -} - -impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponentsRaw for &'b ComponentsMut<'a, C, I> { - type RawItem = &'b C; - type RawIter = std::slice::Iter<'b, C>; - type RawParIter = rayon::slice::Iter<'b, C>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &self.handle[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.handle.iter() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.handle.par_iter() - } -} - -impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponents for &'b ComponentsMut<'a, C, I> { - type Id = I; - type Item = &'b C; -} - -impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponentsRaw - for &'b mut ComponentsMut<'a, C, I> -{ - type RawItem = &'b mut C; - type RawIter = std::slice::IterMut<'b, C>; - type RawParIter = rayon::slice::IterMut<'b, C>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - &mut self.handle[idx] - } - - fn raw_iter(self) -> Self::RawIter { - self.handle.iter_mut() - } - - fn raw_par_iter(self) -> Self::RawParIter { - self.handle.par_iter_mut() - } -} - -impl<'a, 'b, C: 'static + Send + Sync, I: Id> ZippedComponents for &'b mut ComponentsMut<'a, C, I> { - type Id = I; - type Item = &'b mut C; -} - -/// The possible errors when requesting a component. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)] -pub enum Error { - #[error("an unknown component type was requested")] - UnknownComponent, - #[error("shared access to a component was requested while exclusive access was already held")] - NoReadAccess, - #[error( - "exclusive access to a component was requested while shared or exclusive access was \ - already held" - )] - NoWriteAccess, -} - -pub(crate) mod private { - use super::*; - - pub trait ZippedComponentsRaw { - type RawItem: Send + Sync; - type RawIter: FusedIterator; - type RawParIter: IndexedParallelIterator; - - fn raw_get(self, idx: usize) -> Self::RawItem; - fn raw_iter(self) -> Self::RawIter; - fn raw_par_iter(self) -> Self::RawParIter; - } - - pub trait IdRaw { - fn to_data(self) -> IdData; - fn from_data(id: IdData) -> Self; - } - - #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] - pub struct IdData { - pub idx: u32, - pub gen: NonZeroU32, - } - - impl IdData { - pub const NULL: IdData = IdData { - idx: u32::MAX, - gen: match NonZeroU32::new(u32::MAX) { - Some(n) => n, - None => unreachable!(), - }, - }; - } - - impl Default for IdData { - fn default() -> Self { - Self::NULL - } - } - - #[derive(Clone, Debug)] - pub struct MultiZip { - pub tuple: T, - } - - impl MultiZip { - pub fn new(tuple: T) -> Self { - Self { tuple } - } - } -} - -pub(crate) use private::*; - -/// Like `Default`, but only usable internally by this crate. -/// -/// This prevents invariants regarding built-in components from being broken -/// by library users. -pub(crate) trait DefaultPrivate { - fn default_private() -> Self; -} - -impl DefaultPrivate for T { - fn default_private() -> Self { - T::default() - } -} - -pub trait ZippedComponents: ZippedComponentsRaw { - type Id: Copy; - type Item: Send + Sync; -} - -macro_rules! tuple_impl { - ($($T:ident),*) => { - #[allow(non_snake_case)] - impl<$($T: ZippedComponentsRaw,)*> ZippedComponentsRaw for ($($T,)*) { - type RawItem = ($($T::RawItem,)*); - type RawIter = MultiZip<($($T::RawIter,)*)>; - type RawParIter = rayon::iter::MultiZip<($($T::RawParIter,)*)>; - - fn raw_get(self, idx: usize) -> Self::RawItem { - let ($($T,)*) = self; - ($($T.raw_get(idx),)*) - } - - fn raw_iter(self) -> Self::RawIter { - let ($($T,)*) = self; - MultiZip::new(($($T.raw_iter(),)*)) - } - - fn raw_par_iter(self) -> Self::RawParIter { - let ($($T,)*) = self; - ($($T.raw_par_iter(),)*).into_par_iter() - } - } - - - #[allow(non_snake_case)] - impl<$($T: Iterator,)*> Iterator for MultiZip<($($T,)*)> { - type Item = ($($T::Item,)*); - - fn next(&mut self) -> Option { - let ($($T,)*) = &mut self.tuple; - Some(($($T.next()?,)*)) - } - - fn size_hint(&self) -> (usize, Option) { - let lower = usize::MAX; - let upper: Option = None; - let ($($T,)*) = &self.tuple; - $( - let (l, u) = $T.size_hint(); - let lower = lower.min(l); - let upper = match (upper, u) { - (Some(l), Some(r)) => Some(l.min(r)), - (Some(l), None) => Some(l), - (None, Some(r)) => Some(r), - (None, None) => None - }; - )* - (lower, upper) - } - } - - #[allow(non_snake_case)] - impl<$($T: ExactSizeIterator,)*> ExactSizeIterator for MultiZip<($($T,)*)> { - fn len(&self) -> usize { - let len = usize::MAX; - let ($($T,)*) = &self.tuple; - $( - let len = len.min($T.len()); - )* - - debug_assert_eq!(self.size_hint(), (len, Some(len))); - len - } - } - - #[allow(non_snake_case)] - impl<$($T: DoubleEndedIterator + ExactSizeIterator,)*> DoubleEndedIterator for MultiZip<($($T,)*)> { - fn next_back(&mut self) -> Option { - let len = self.len(); - let ($($T,)*) = &mut self.tuple; - - $( - let this_len = $T.len(); - debug_assert!(this_len >= len); - for _ in 0..this_len - len { - $T.next_back(); - } - let $T = $T.next_back(); - )* - - Some(($($T?,)*)) - } - } - - impl<$($T: FusedIterator,)*> FusedIterator for MultiZip<($($T,)*)> {} - } -} - -tuple_impl!(A); -tuple_impl!(A, B); -tuple_impl!(A, B, C); -tuple_impl!(A, B, C, D); -tuple_impl!(A, B, C, D, E); -tuple_impl!(A, B, C, D, E, F); -tuple_impl!(A, B, C, D, E, F, G); -tuple_impl!(A, B, C, D, E, F, G, H); -tuple_impl!(A, B, C, D, E, F, G, H, I); -tuple_impl!(A, B, C, D, E, F, G, H, I, J); -tuple_impl!(A, B, C, D, E, F, G, H, I, J, K); -tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L); diff --git a/src/config.rs b/src/config.rs index 7856f9a..0d85645 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,83 +1,64 @@ -// TODO: rate limit, view distance? - use std::any::Any; -use std::collections::HashSet; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::panic::{RefUnwindSafe, UnwindSafe}; use std::sync::Arc; use std::time::Duration; -use anyhow::ensure; use async_trait::async_trait; use tokio::runtime::Handle as TokioHandle; -use crate::server::{start_server, ShutdownError}; -use crate::{ident, Identifier, NewClientData, Server, SharedServer, ShutdownResult, Text}; +use crate::{ident, Id, Identifier, NewClientData, Server, SharedServer, Text}; -/// A builder type used to configure and start the server. -pub struct ServerConfig { - pub(crate) handler: Option>, - pub(crate) address: SocketAddr, - pub(crate) update_duration: Duration, - pub(crate) online_mode: bool, - pub(crate) max_clients: usize, - pub(crate) clientbound_packet_capacity: usize, - pub(crate) serverbound_packet_capacity: usize, - pub(crate) tokio_handle: Option, - pub(crate) dimensions: Vec, - pub(crate) biomes: Vec, -} - -impl ServerConfig { - /// Constructs a new server configuration with the provided handler. - pub fn new() -> Self { - Self { - handler: None, - address: SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 25565).into(), - update_duration: Duration::from_secs_f64(1.0 / 20.0), - online_mode: false, - max_clients: 32, - clientbound_packet_capacity: 128, - serverbound_packet_capacity: 32, - tokio_handle: None, - dimensions: Vec::new(), - biomes: Vec::new(), - } - } - - /// Sets the [`Handler`] to use for this server. - pub fn handler(&mut self, handler: impl Handler) { - self.handler = Some(Box::new(handler)); - } - - /// Sets the socket address that the server will be bound to. +/// A trait containing callbacks which are invoked by the running Minecraft +/// server. +/// +/// The config is used from multiple threads and must therefore implement +/// `Send` and `Sync`. From within a single thread, methods are never invoked +/// recursively. In other words, a mutex can always be aquired at the beginning +/// of a method and released at the end without risk of deadlocking. +/// +/// This trait uses the [async_trait](https://docs.rs/async-trait/latest/async_trait/) attribute macro. +/// This will be removed once `impl Trait` in return position in traits is +/// available in stable rust. +#[async_trait] +#[allow(unused_variables)] +pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe { + /// Called once at startup to get the maximum number of connections allowed + /// to the server. Note that this includes all connections, not just those + /// past the login stage. /// - /// The default is `127.0.0.1:25565`. - pub fn address(&mut self, addr: impl Into) { - self.address = addr.into(); + /// You will want this value to be somewhere above the maximum number of + /// players, since status pings should still succeed even when the server is + /// full. + fn max_connections(&self) -> usize; + + /// Called once at startup to get the socket address the server will + /// be bound to. + /// + /// # Default Implementation + /// Returns `127.0.0.1:25565`. + fn address(&self) -> SocketAddr { + SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 25565).into() } - /// Sets the duration of each game update. + /// Called once at startup to get the duration of each game update. /// /// On each game update (a.k.a. tick), the server is expected to update game /// logic and respond to packets from clients. Once this is complete, /// the server will sleep for any remaining time until the full update /// duration has passed. /// - /// If the server is running behind schedule due to heavy load or some other - /// reason, the actual duration of a game update will exceed what has been - /// specified. - /// /// The duration must be nonzero. /// - /// The default value is the same as Minecraft's official server (20 ticks - /// per second). You may want to use a shorter duration if you can afford to - /// do so. - pub fn update_duration(&mut self, duration: Duration) { - self.update_duration = duration; + /// # Default Implementation + /// Returns 1/20th of a second, which is the same as Minecraft's official + /// server. + fn update_duration(&self) -> Duration { + Duration::from_secs_f64(1.0 / 20.0) } - /// Sets the state of "online mode", which determines if client - /// authentication and encryption should occur. + /// Called once at startup to get the "online mode" option, which determines + /// if client authentication and encryption should take place. /// /// When online mode is disabled, malicious clients can give themselves any /// username and UUID they want, potentially gaining privileges they @@ -86,178 +67,90 @@ impl ServerConfig { /// for development purposes and enabled on servers exposed to the /// internet. /// - /// By default, online mode is enabled. - pub fn online_mode(&mut self, online_mode: bool) { - self.online_mode = online_mode; + /// # Default Implementation + /// Returns `true`. + fn online_mode(&self) -> bool { + true } - /// Sets the maximum number of clients (past the login stage) allowed on the - /// server simultaneously. - /// - /// The default is 32. - pub fn max_clients(&mut self, clients: usize) { - self.max_clients = clients; - } - - /// The capacity of the buffer used to hold clientbound packets. + /// Called once at startup to get the capacity of the buffer used to + /// hold incoming packets. /// /// A larger capcity reduces the chance of packet loss but increases - /// potential memory usage. The default value is unspecified but should be - /// adequate for most situations. + /// potential memory usage. /// - /// The capacity must be nonzero. - pub fn clientbound_packet_capacity(&mut self, cap: usize) { - self.clientbound_packet_capacity = cap; + /// # Default Implementation + /// An unspecified value is returned that should be adequate in most + /// situations. + fn incoming_packet_capacity(&self) -> usize { + 32 } - /// Sets the capacity of the buffer used to hold serverbound packets. + /// Called once at startup to get the capacity of the buffer used to + /// hold outgoing packets. /// - /// A larger capcity reduces the chance of packet loss but increases - /// potential memory usage. The default value is unspecified but should be - /// adequate for most situations. + /// A larger capcity reduces the chance of packet loss due to a full buffer + /// but increases potential memory usage. /// - /// The capacity must be nonzero. - pub fn serverbound_packet_capacity(&mut self, cap: usize) { - self.serverbound_packet_capacity = cap; + /// # Default Implementation + /// An unspecified value is returned that should be adequate in most + /// situations. + fn outgoing_packet_capacity(&self) -> usize { + 128 } - /// Sets the handle to the tokio runtime the server will use. + /// Called once at startup to get a handle to the tokio runtime the server + /// will use. /// /// If a handle is not provided, the server will create its own tokio /// runtime. - pub fn tokio_handle(&mut self, handle: TokioHandle) { - self.tokio_handle = Some(handle); + /// + /// # Default Implementation + /// Returns `None`. + fn tokio_handle(&self) -> Option { + None } - /// Adds a new dimension to the server which is identified by the returned - /// [`DimensionId`]. The default dimension is added if none are provided. + /// Called once at startup to get the list of [`Dimension`]s usable on the + /// server. /// + /// The dimensions traversed by [`Server::dimensions`] will be in the same + /// order as the `Vec` returned by this function. + /// + /// The number of elements in the returned `Vec` must be in \[1, u16::MAX]. /// Additionally, the documented requirements on the fields of [`Dimension`] - /// must be met. No more than `u16::MAX` dimensions may be added. - pub fn push_dimension(&mut self, dimension: Dimension) -> DimensionId { - let id = self.biomes.len(); - self.dimensions.push(dimension); - DimensionId(id as u16) + /// must be met. + /// + /// # Default Implementation + /// Returns `vec![Dimension::default()]`. + fn dimensions(&self) -> Vec { + vec![Dimension::default()] } - /// Adds a new biome to the server which is identified by the returned - /// [`BiomeId`]. The default biome is added if none are provided. + /// Called once at startup to get the list of [`Biome`]s usable on the + /// server. /// + /// The biomes traversed by [`Server::biomes`] will be in the same + /// order as the `Vec` returned by this function. + /// + /// The number of elements in the returned `Vec` must be in \[1, u16::MAX]. /// Additionally, the documented requirements on the fields of [`Biome`] - /// must be met. No more than `u16::MAX` biomes may be added. - pub fn push_biome(&mut self, biome: Biome) -> BiomeId { - let id = self.biomes.len(); - self.biomes.push(biome); - BiomeId(id as u16) - } - - /// Consumes the configuration and starts the server. + /// must be met. /// - /// The function returns once the server has been shut down, a runtime error - /// occurs, or the configuration is invalid. - pub fn start(mut self) -> ShutdownResult { - if self.biomes.is_empty() { - self.biomes.push(Biome::default()); - } - - if self.dimensions.is_empty() { - self.dimensions.push(Dimension::default()); - } - - self.validate().map_err(ShutdownError::from)?; - start_server(self) + /// # Default Implementation + /// Returns `vec![Dimension::default()]`. + fn biomes(&self) -> Vec { + vec![Biome::default()] } - fn validate(&self) -> anyhow::Result<()> { - ensure!( - self.dimensions.len() <= u16::MAX as usize, - "more than u16::MAX dimensions added" - ); - - ensure!( - self.biomes.len() <= u16::MAX as usize, - "more than u16::MAX biomes added" - ); - - ensure!( - self.update_duration != Duration::ZERO, - "update duration must be nonzero" - ); - - ensure!( - self.clientbound_packet_capacity > 0, - "clientbound packet capacity must be nonzero" - ); - - ensure!( - self.serverbound_packet_capacity > 0, - "serverbound packet capacity must be nonzero" - ); - - for (i, dim) in self.dimensions.iter().enumerate() { - ensure!( - dim.min_y % 16 == 0 && (-2032..=2016).contains(&dim.min_y), - "invalid min_y in dimension #{i}", - ); - - ensure!( - dim.height % 16 == 0 - && (0..=4064).contains(&dim.height) - && dim.min_y.saturating_add(dim.height) <= 2032, - "invalid height in dimension #{i}", - ); - - ensure!( - (0.0..=1.0).contains(&dim.ambient_light), - "ambient_light is out of range in dimension #{i}", - ); - - if let Some(fixed_time) = dim.fixed_time { - assert!( - (0..=24_000).contains(&fixed_time), - "fixed_time is out of range in dimension #{i}", - ); - } - } - - let mut names = HashSet::new(); - - for biome in self.biomes.iter() { - ensure!( - names.insert(biome.name.clone()), - "biome \"{}\" already added", - biome.name - ); - } - - Ok(()) - } -} - -impl Default for ServerConfig { - fn default() -> Self { - Self::new() - } -} - -/// A trait containing callbacks which are invoked by the running Minecraft -/// server. -/// -/// The handler is used from multiple threads and must therefore implement -/// `Send` and `Sync`. From within a single thread, callbacks are never invoked -/// recursively. In other words, a mutex can be aquired at the beginning of a -/// callback and released at the end without risk of deadlocking. -/// -/// All methods are called from within a tokio context. -#[async_trait] -#[allow(unused_variables)] -pub trait Handler: Any + Send + Sync { /// Called after the server is created, but prior to accepting connections /// and entering the update loop. /// /// This is useful for performing initialization work with a guarantee that /// no connections to the server will be made until this function returns. /// + /// This method is called from within a tokio runtime. + /// /// # Default Implementation /// The default implementation does nothing. fn init(&self, server: &mut Server) {} @@ -268,36 +161,25 @@ pub trait Handler: Any + Send + Sync { /// The frequency of server updates can be configured by `update_duration` /// in [`ServerConfig`]. /// + /// This method is called from within a tokio runtime. + /// /// # Default Implementation /// The default implementation does nothing. fn update(&self, server: &mut Server) {} /// Called when the server receives a Server List Ping query. - /// Data for the query can be provided or the query can be ignored. + /// Data for the response can be provided or the query can be ignored. + /// + /// This method is called from within a tokio runtime. /// /// # Default Implementation - /// A placeholder response is returned. + /// The query is ignored. async fn server_list_ping( &self, server: &SharedServer, remote_addr: SocketAddr, ) -> ServerListPing { - ServerListPing::Respond { - online_players: server.client_count() as i32, - max_players: server.max_clients() as i32, - description: "A Minecraft Server".into(), - favicon_png: None, - } - } - - /// Called when a client is disconnected due to the server being full. - /// The return value is the disconnect message to use. - /// - /// # Default Implementation - /// A placeholder message is returned. - async fn max_client_message(&self, server: &SharedServer, npd: &NewClientData) -> Text { - // TODO: Standard translated text for this purpose? - "The server is full!".into() + ServerListPing::Ignore } /// Called asynchronously for each client after successful authentication @@ -305,7 +187,9 @@ pub trait Handler: Any + Send + Sync { /// server. On success, a client-backed entity is spawned. /// /// This function is the appropriate place to perform - /// whitelist checks, database queries, etc. + /// player count checks, whitelist checks, database queries, etc. + /// + /// This method is called from within a tokio runtime. /// /// # Default Implementation /// The client is allowed to join unconditionally. @@ -315,6 +199,7 @@ pub trait Handler: Any + Send + Sync { } /// The result of the [`server_list_ping`](Handler::server_list_ping) callback. +#[derive(Debug)] pub enum ServerListPing { /// Responds to the server list ping with the given information. Respond { @@ -341,18 +226,33 @@ pub enum Login { Disconnect(Text), } -/// Identifies a particular [`Dimension`]. +/// A handle to a particular [`Dimension`] on the server. /// -/// Dimension IDs are always valid and are cheap to copy and store. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)] +/// Dimension IDs must only be used on servers from which they originate. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct DimensionId(pub(crate) u16); -/// Contains the configuration for a custom dimension type. +/// All dimension IDs are valid. +impl Id for DimensionId { + fn idx(self) -> usize { + self.0 as usize + } +} + +/// The default dimension ID corresponds to the first element in the `Vec` +/// returned by [`Config::dimensions`]. +impl Default for DimensionId { + fn default() -> Self { + Self(0) + } +} + +/// Contains the configuration for a dimension type. /// /// In Minecraft, "dimension" and "dimension type" are two different concepts. /// For instance, the Overworld and Nether are dimensions, each with /// their own dimension type. A dimension in this library is analogous to a -/// [`World`](crate::World) while the [`Dimension`] struct represents a +/// [`World`](crate::World) while [`Dimension`] represents a /// dimension type. #[derive(Clone, Debug)] pub struct Dimension { @@ -394,7 +294,7 @@ impl Default for Dimension { fn default() -> Self { Self { natural: true, - ambient_light: 0.0, + ambient_light: 1.0, fixed_time: None, effects: DimensionEffects::Overworld, min_y: -64, @@ -416,7 +316,14 @@ pub enum DimensionEffects { #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct BiomeId(pub(crate) u16); -/// Contains the configuration for a custom biome. +/// All Biome IDs are valid. +impl Id for BiomeId { + fn idx(self) -> usize { + self.0 as usize + } +} + +/// Contains the configuration for a biome. #[derive(Clone, Debug)] pub struct Biome { /// The unique name for this biome. The name can be diff --git a/src/entity/meta.rs b/src/entity/meta.rs index eb40590..f5f6463 100644 --- a/src/entity/meta.rs +++ b/src/entity/meta.rs @@ -6,7 +6,7 @@ use uuid::Uuid; use crate::block_pos::BlockPos; use crate::protocol::Encode; use crate::var_int::VarInt; -use crate::{def_bitfield, Text}; +use crate::Text; #[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)] pub struct ArmorStandRotations { diff --git a/src/lib.rs b/src/lib.rs index 6995410..20f01ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ #![forbid(unsafe_code)] #![warn( - missing_debug_implementations, trivial_casts, trivial_numeric_casts, unused_lifetimes, @@ -15,7 +14,6 @@ mod byte_angle; pub mod chunk; pub mod client; mod codec; -pub mod component; pub mod config; pub mod entity; pub mod identifier; @@ -30,19 +28,19 @@ mod var_long; pub mod world; pub use aabb::Aabb; +pub use async_trait::async_trait; pub use block_pos::BlockPos; pub use chunk::{Chunk, ChunkPos, ChunkStore}; pub use client::{Client, ClientStore}; -pub use config::{BiomeId, DimensionId, ServerConfig}; +pub use config::{Biome, BiomeId, Config, Dimension, DimensionId}; pub use entity::{Entity, EntityId, EntityStore}; pub use identifier::Identifier; +pub use server::{start_server, NewClientData, Server, SharedServer, ShutdownResult}; pub use text::{Text, TextFormat}; pub use uuid::Uuid; pub use world::{World, WorldId, WorldStore}; pub use {nalgebra_glm as glm, nbt, uuid}; -pub use crate::server::{NewClientData, Server, SharedServer, ShutdownResult}; - /// The Minecraft protocol version that this library targets. pub const PROTOCOL_VERSION: i32 = 758; /// The name of the Minecraft version that this library targets. diff --git a/src/packets.rs b/src/packets.rs index e674b10..6a27c89 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -258,8 +258,6 @@ macro_rules! if_typ_is_empty_pat { }; } -#[doc(hidden)] -#[macro_export] macro_rules! def_bitfield { ( $(#[$struct_attrs:meta])* @@ -282,7 +280,7 @@ macro_rules! def_bitfield { )* ) -> Self { let mut res = Self(Default::default()); - paste::paste! { + paste! { $( res = res.[]($bit); )* @@ -290,7 +288,7 @@ macro_rules! def_bitfield { res } - paste::paste! { + paste! { $( #[doc = "Gets the " $bit " bit on this bitfield.\n"] $(#[$bit_attrs])* @@ -743,7 +741,7 @@ pub mod play { NoRespawnBlockAvailable = 0, EndRaining = 1, BeginRaining = 2, - ChangeGamemode = 3, + ChangeGameMode = 3, WinGame = 4, DemoEvent = 5, ArrowHitPlayer = 6, @@ -965,7 +963,7 @@ pub mod play { } def_enum! { - #[derive(Copy, PartialEq, Eq)] + #[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] GameMode: u8 { Survival = 0, Creative = 1, @@ -1116,6 +1114,7 @@ pub mod play { Disconnect, EntityStatus, UnloadChunk, + ChangeGameState, KeepAliveClientbound, ChunkDataAndUpdateLight, JoinGame, diff --git a/src/server.rs b/src/server.rs index 88a1348..4f52e05 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,8 +1,8 @@ +use std::collections::HashSet; use std::error::Error; use std::iter::FusedIterator; use std::net::SocketAddr; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::thread; use std::time::{Duration, Instant}; @@ -27,7 +27,7 @@ use tokio::sync::{oneshot, Semaphore}; use uuid::Uuid; use crate::codec::{Decoder, Encoder}; -use crate::config::{Biome, BiomeId, Dimension, DimensionId, Handler, Login, ServerListPing}; +use crate::config::{Biome, BiomeId, Config, Dimension, DimensionId, Login, ServerListPing}; use crate::packets::handshake::{Handshake, HandshakeNextState}; use crate::packets::login::{ self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression, @@ -38,18 +38,17 @@ use crate::protocol::{BoundedArray, BoundedString}; use crate::util::valid_username; use crate::var_int::VarInt; use crate::{ - ChunkStore, Client, ClientStore, EntityStore, ServerConfig, Ticks, WorldStore, - PROTOCOL_VERSION, VERSION_NAME, + ChunkStore, Client, ClientStore, EntityStore, Ticks, WorldStore, PROTOCOL_VERSION, VERSION_NAME, }; /// Holds the state of a running Minecraft server which is accessible inside the /// update loop. To start a server, see [`ServerConfig`]. /// /// Fields of this struct are made public to enable disjoint borrows. For -/// instance, it is possible to create and delete entities while -/// having read-only access to world data. +/// instance, it is possible to mutate the list of entities while simultaneously +/// reading world data. /// -/// Note the `Deref` and `DerefMut` impls on `Server` are (ab)used to +/// Note the `Deref` and `DerefMut` impls on this struct are (ab)used to /// allow convenient access to the `other` field. #[non_exhaustive] pub struct Server { @@ -79,14 +78,16 @@ pub struct Other { pub struct SharedServer(Arc); struct SharedServerInner { - handler: Box, + cfg: Box, address: SocketAddr, update_duration: Duration, online_mode: bool, - max_clients: usize, - clientbound_packet_capacity: usize, - serverbound_packet_capacity: usize, + max_connections: usize, + incoming_packet_capacity: usize, + outgoing_packet_capacity: usize, tokio_handle: Handle, + /// Store this here so we don't drop it. + _tokio_runtime: Option, dimensions: Vec, biomes: Vec, /// The instant the server was started. @@ -104,10 +105,9 @@ struct SharedServerInner { /// For session server requests. http_client: HttpClient, new_clients_tx: Sender, - client_count: AtomicUsize, } -/// Contains information about a new player. +/// Contains information about a new client. pub struct NewClientData { pub uuid: Uuid, pub username: String, @@ -145,8 +145,8 @@ impl Other { } impl SharedServer { - pub fn handler(&self) -> &(impl Handler + ?Sized) { - self.0.handler.as_ref() + pub fn config(&self) -> &(impl Config + ?Sized) { + self.0.cfg.as_ref() } pub fn address(&self) -> SocketAddr { @@ -161,16 +161,16 @@ impl SharedServer { self.0.online_mode } - pub fn max_clients(&self) -> usize { - self.0.max_clients + pub fn max_connections(&self) -> usize { + self.0.max_connections } - pub fn clientbound_packet_capacity(&self) -> usize { - self.0.clientbound_packet_capacity + pub fn incoming_packet_capacity(&self) -> usize { + self.0.incoming_packet_capacity } - pub fn serverbound_packet_capacity(&self) -> usize { - self.0.serverbound_packet_capacity + pub fn outgoing_packet_capacity(&self) -> usize { + self.0.outgoing_packet_capacity } pub fn tokio_handle(&self) -> &Handle { @@ -191,31 +191,27 @@ impl SharedServer { /// Returns an iterator over all added dimensions and their associated /// [`DimensionId`]. - pub fn dimensions(&self) -> impl FusedIterator + Clone { + pub fn dimensions(&self) -> impl FusedIterator + Clone { self.0 .dimensions .iter() .enumerate() - .map(|(i, d)| (d, DimensionId(i as u16))) + .map(|(i, d)| (DimensionId(i as u16), d)) } /// Obtains a [`Biome`] by using its corresponding [`BiomeId`]. - /// - /// It is safe but unspecified behavior to call this function using a - /// [`BiomeId`] not originating from the configuration used to construct the - /// server. pub fn biome(&self, id: BiomeId) -> &Biome { self.0.biomes.get(id.0 as usize).expect("invalid biome ID") } /// Returns an iterator over all added biomes and their associated /// [`BiomeId`]. - pub fn biomes(&self) -> impl FusedIterator + Clone { + pub fn biomes(&self) -> impl FusedIterator + Clone { self.0 .biomes .iter() .enumerate() - .map(|(i, b)| (b, BiomeId(i as u16))) + .map(|(i, b)| (BiomeId(i as u16), b)) } /// Returns the instant the server was started. @@ -236,33 +232,6 @@ impl SharedServer { self.0.connection_sema.close(); *self.0.shutdown_result.lock() = Some(res.into().map_err(|e| e.into())); } - - /// Returns the number of clients past the login stage that are currently - /// connected to the server. - pub fn client_count(&self) -> usize { - self.0.client_count.load(Ordering::SeqCst) - } - - /// Increment the client count iff it is below the maximum number of - /// clients. Returns true if the client count was incremented, false - /// otherwise. - fn try_inc_player_count(&self) -> bool { - self.0 - .client_count - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { - if count < self.0.max_clients { - count.checked_add(1) - } else { - None - } - }) - .is_ok() - } - - pub(crate) fn dec_client_count(&self) { - let prev = self.0.client_count.fetch_sub(1, Ordering::SeqCst); - assert!(prev != 0); - } } impl Deref for Server { @@ -287,7 +256,108 @@ impl Deref for Other { } } -pub(crate) fn start_server(config: ServerConfig) -> ShutdownResult { +/// Consumes the configuration and starts the server. +/// +/// The function returns when the server has shut down, a runtime error +/// occurs, or the configuration is invalid. +pub fn start_server(config: impl Config) -> ShutdownResult { + let mut server = setup_server(config).map_err(ShutdownError::from)?; + + let shared = server.shared().clone(); + + let _guard = shared.tokio_handle().enter(); + + shared.config().init(&mut server); + + tokio::spawn(do_accept_loop(shared)); + + do_update_loop(&mut server) +} + +fn setup_server(cfg: impl Config) -> anyhow::Result { + let max_connections = cfg.max_connections(); + let address = cfg.address(); + let update_duration = cfg.update_duration(); + + ensure!( + update_duration != Duration::ZERO, + "update duration must be nonzero" + ); + + let online_mode = cfg.online_mode(); + + let incoming_packet_capacity = cfg.incoming_packet_capacity(); + + ensure!( + incoming_packet_capacity > 0, + "serverbound packet capacity must be nonzero" + ); + + let outgoing_packet_capacity = cfg.outgoing_packet_capacity(); + + ensure!( + outgoing_packet_capacity > 0, + "outgoing packet capacity must be nonzero" + ); + + let tokio_handle = cfg.tokio_handle(); + let dimensions = cfg.dimensions(); + + ensure!( + !dimensions.is_empty(), + "at least one dimension must be added" + ); + + ensure!( + dimensions.len() <= u16::MAX as usize, + "more than u16::MAX dimensions added" + ); + + for (i, dim) in dimensions.iter().enumerate() { + ensure!( + dim.min_y % 16 == 0 && (-2032..=2016).contains(&dim.min_y), + "invalid min_y in dimension #{i}", + ); + + ensure!( + dim.height % 16 == 0 + && (0..=4064).contains(&dim.height) + && dim.min_y.saturating_add(dim.height) <= 2032, + "invalid height in dimension #{i}", + ); + + ensure!( + (0.0..=1.0).contains(&dim.ambient_light), + "ambient_light is out of range in dimension #{i}", + ); + + if let Some(fixed_time) = dim.fixed_time { + assert!( + (0..=24_000).contains(&fixed_time), + "fixed_time is out of range in dimension #{i}", + ); + } + } + + let biomes = cfg.biomes(); + + ensure!(!biomes.is_empty(), "at least one biome must be added"); + + ensure!( + biomes.len() <= u16::MAX as usize, + "more than u16::MAX biomes added" + ); + + let mut names = HashSet::new(); + + for biome in biomes.iter() { + ensure!( + names.insert(biome.name.clone()), + "biome \"{}\" already added", + biome.name + ); + } + let rsa_key = RsaPrivateKey::new(&mut OsRng, 1024)?; let public_key_der = @@ -296,46 +366,39 @@ pub(crate) fn start_server(config: ServerConfig) -> ShutdownResult { let (new_players_tx, new_players_rx) = flume::bounded(1); - let rt = if config.tokio_handle.is_none() { + let runtime = if tokio_handle.is_none() { Some(Runtime::new()?) } else { None }; - let handle = match &rt { + let tokio_handle = match &runtime { Some(rt) => rt.handle().clone(), - None => config.tokio_handle.unwrap(), + None => tokio_handle.unwrap(), }; - let _guard = handle.enter(); - - let connection_sema = Arc::new(Semaphore::new(config.max_clients.saturating_add(64))); - - struct DummyHandler; - impl Handler for DummyHandler {} - let shared = SharedServer(Arc::new(SharedServerInner { - handler: config.handler.unwrap_or_else(|| Box::new(DummyHandler)), - address: config.address, - update_duration: config.update_duration, - online_mode: config.online_mode, - max_clients: config.max_clients, - clientbound_packet_capacity: config.clientbound_packet_capacity, - serverbound_packet_capacity: config.serverbound_packet_capacity, - tokio_handle: handle.clone(), - dimensions: config.dimensions, - biomes: config.biomes, + cfg: Box::new(cfg), + address, + update_duration, + online_mode, + max_connections, + outgoing_packet_capacity, + incoming_packet_capacity, + tokio_handle, + _tokio_runtime: runtime, + dimensions, + biomes, start_instant: Instant::now(), - connection_sema, + connection_sema: Arc::new(Semaphore::new(max_connections)), shutdown_result: Mutex::new(None), rsa_key, public_key_der, http_client: HttpClient::new(), new_clients_tx: new_players_tx, - client_count: AtomicUsize::new(0), })); - let mut server = Server { + Ok(Server { entities: EntityStore::new(), clients: ClientStore::new(), worlds: WorldStore::new(), @@ -347,13 +410,7 @@ pub(crate) fn start_server(config: ServerConfig) -> ShutdownResult { new_players_rx, last_keepalive: Instant::now(), }, - }; - - shared.handler().init(&mut server); - - tokio::spawn(do_accept_loop(shared)); - - do_update_loop(&mut server) + }) } fn do_update_loop(server: &mut Server) -> ShutdownResult { @@ -392,7 +449,7 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult { .par_iter_mut() .for_each(|(_, chunk)| chunk.apply_modifications()); - shared.handler().update(server); + shared.config().update(server); // Chunks modified this tick can have their changes applied immediately because // they have not been observed by clients yet. @@ -417,8 +474,8 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult { } fn join_player(server: &mut Server, msg: NewClientMessage) { - let (clientbound_tx, clientbound_rx) = flume::bounded(server.0.clientbound_packet_capacity); - let (serverbound_tx, serverbound_rx) = flume::bounded(server.0.serverbound_packet_capacity); + let (clientbound_tx, clientbound_rx) = flume::bounded(server.0.outgoing_packet_capacity); + let (serverbound_tx, serverbound_rx) = flume::bounded(server.0.incoming_packet_capacity); let client_packet_channels: ClientPacketChannels = (serverbound_tx, clientbound_rx); let server_packet_channels: ServerPacketChannels = (clientbound_tx, serverbound_rx); @@ -428,7 +485,7 @@ fn join_player(server: &mut Server, msg: NewClientMessage) { let client_backed_entity = match server.entities.create_with_uuid(msg.ncd.uuid) { Some(id) => id, None => { - log::error!( + log::warn!( "player '{}' cannot join the server because their UUID ({}) conflicts with an \ existing entity", msg.ncd.username, @@ -438,7 +495,7 @@ fn join_player(server: &mut Server, msg: NewClientMessage) { } }; - let client_id = server.clients.create(Client::new( + server.clients.create(Client::new( server_packet_channels, client_backed_entity, msg.ncd.username, @@ -468,7 +525,7 @@ async fn do_accept_loop(server: SharedServer) { // Setting TCP_NODELAY to true appears to trade some throughput for improved // latency. Testing is required to determine if this is worth keeping. if let Err(e) = stream.set_nodelay(true) { - log::error!("failed to set TCP nodelay: {e}") + log::error!("failed to set TCP nodelay: {e}"); } if let Err(e) = handle_connection(server, stream, remote_addr).await { @@ -522,12 +579,7 @@ async fn handle_status( ) -> anyhow::Result<()> { c.1.read_packet::().await?; - match server - .0 - .handler - .server_list_ping(&server, remote_addr) - .await - { + match server.0.cfg.server_list_ping(&server, remote_addr).await { ServerListPing::Respond { online_players, max_players, @@ -685,14 +737,7 @@ async fn handle_login( remote_addr, }; - if !server.try_inc_player_count() { - let reason = server.0.handler.max_client_message(server, &npd).await; - log::info!("Disconnect at login: \"{reason}\""); - c.0.write_packet(&login::Disconnect { reason }).await?; - return Ok(None); - } - - if let Login::Disconnect(reason) = server.0.handler.login(server, &npd).await { + if let Login::Disconnect(reason) = server.0.cfg.login(server, &npd).await { log::info!("Disconnect at login: \"{reason}\""); c.0.write_packet(&login::Disconnect { reason }).await?; return Ok(None);