Put all config options in a single trait

This commit is contained in:
Ryan 2022-04-30 05:06:20 -07:00
parent 732183dd62
commit a0892faa72
9 changed files with 389 additions and 946 deletions

View file

@ -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,6 +52,13 @@ impl Handler for Game {
let chunk_id = server.chunks.create(384);
let chunk = server.chunks.get_mut(chunk_id).unwrap();
// 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 {
@ -48,40 +66,57 @@ impl Handler for Game {
}
}
}
}
world.chunks_mut().insert((x, z).into(), chunk_id);
}
}
}
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())
}
}
}

View file

@ -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(),
}
}

View file

@ -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<Sender<ClientPlayPacket>>,
recv: Receiver<ServerPlayPacket>,
@ -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<DVec3>, 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<Sender<ClientPlayPacket>>, pkt: impl Into<C
fn make_dimension_codec(other: &Other) -> 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));
}

View file

@ -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<I: Id> {
ids: Vec<Slot>,
next_free_head: u32,
count: u32,
components: HashMap<TypeId, Box<dyn ComponentVec>>,
_marker: PhantomData<fn(I) -> I>,
}
impl<I: Id> ComponentStore<I> {
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<Z: ZippedComponents<Id = I>>(&self, z: Z, id: I) -> Option<Z::Item> {
if self.is_valid(id) {
Some(z.raw_get(id.to_data().idx as usize))
} else {
None
}
}
pub fn iter<'a, Z: ZippedComponents<Id = I> + 'a>(
&'a self,
z: Z,
) -> impl FusedIterator<Item = (I, Z::Item)> + '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<Id = I> + 'a>(
&'a self,
z: Z,
) -> impl ParallelIterator<Item = (I, Z::Item)> + '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<Item = I> + 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<Item = I> + 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<C: 'static + Send + Sync + DefaultPrivate>(&mut self) {
if let Entry::Vacant(ve) = self.components.entry(TypeId::of::<C>()) {
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<C: 'static + Send + Sync + DefaultPrivate>(&mut self) {
self.components.remove(&TypeId::of::<C>());
}
pub fn is_registered<C: 'static + Send + Sync + DefaultPrivate>(&self) -> bool {
self.components.contains_key(&TypeId::of::<C>())
}
pub fn components<C: 'static + Send + Sync + DefaultPrivate>(
&self,
) -> Result<Components<C, I>, Error> {
let handle = self
.components
.get(&TypeId::of::<C>())
.ok_or(Error::UnknownComponent)?
.as_any()
.downcast_ref::<RwLock<Vec<C>>>()
.unwrap()
.try_read()
.ok_or(Error::NoReadAccess)?;
Ok(Components {
handle,
_marker: PhantomData,
})
}
pub fn components_mut<C: 'static + Send + Sync + DefaultPrivate>(
&self,
) -> Result<ComponentsMut<C, I>, Error> {
let handle = self
.components
.get(&TypeId::of::<C>())
.ok_or(Error::UnknownComponent)?
.as_any()
.downcast_ref::<RwLock<Vec<C>>>()
.unwrap()
.try_write()
.ok_or(Error::NoWriteAccess)?;
Ok(ComponentsMut {
handle,
_marker: PhantomData,
})
}
}
#[derive(Clone, Copy, Debug)]
struct Slot {
gen: NonZeroU32,
next_free: Option<u32>,
}
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<T: 'static + Send + Sync + DefaultPrivate> ComponentVec for RwLock<Vec<T>> {
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<C>>,
_marker: PhantomData<fn(I) -> 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<C>>,
_marker: PhantomData<fn(I) -> 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<Item = Self::RawItem>;
type RawParIter: IndexedParallelIterator<Item = Self::RawItem>;
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<T> {
pub tuple: T,
}
impl<T> MultiZip<T> {
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<T: Default> DefaultPrivate for T {
fn default_private() -> Self {
T::default()
}
}
pub trait ZippedComponents: ZippedComponentsRaw<RawItem = Self::Item> {
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<Self::Item> {
let ($($T,)*) = &mut self.tuple;
Some(($($T.next()?,)*))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let lower = usize::MAX;
let upper: Option<usize> = 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<Self::Item> {
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);

View file

@ -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<Box<dyn Handler>>,
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<TokioHandle>,
pub(crate) dimensions: Vec<Dimension>,
pub(crate) biomes: Vec<Biome>,
}
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<SocketAddr>) {
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<TokioHandle> {
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<Dimension> {
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());
/// # Default Implementation
/// Returns `vec![Dimension::default()]`.
fn biomes(&self) -> Vec<Biome> {
vec![Biome::default()]
}
if self.dimensions.is_empty() {
self.dimensions.push(Dimension::default());
}
self.validate().map_err(ShutdownError::from)?;
start_server(self)
}
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

View file

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

View file

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

View file

@ -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.[<set_ $bit:snake>]($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,

View file

@ -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<SharedServerInner>);
struct SharedServerInner {
handler: Box<dyn Handler>,
cfg: Box<dyn Config>,
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<Runtime>,
dimensions: Vec<Dimension>,
biomes: Vec<Biome>,
/// The instant the server was started.
@ -104,10 +105,9 @@ struct SharedServerInner {
/// For session server requests.
http_client: HttpClient,
new_clients_tx: Sender<NewClientMessage>,
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<Item = (&Dimension, DimensionId)> + Clone {
pub fn dimensions(&self) -> impl FusedIterator<Item = (DimensionId, &Dimension)> + 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<Item = (&Biome, BiomeId)> + Clone {
pub fn biomes(&self) -> impl FusedIterator<Item = (BiomeId, &Biome)> + 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<Server> {
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::<Request>().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);