mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 14:31:30 +11:00
Put all config options in a single trait
This commit is contained in:
parent
732183dd62
commit
a0892faa72
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
555
src/component.rs
555
src/component.rs
|
@ -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);
|
357
src/config.rs
357
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<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());
|
||||
}
|
||||
|
||||
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<Biome> {
|
||||
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
259
src/server.rs
259
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<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);
|
||||
|
|
Loading…
Reference in a new issue