mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41: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::net::SocketAddr;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use valence::config::{Handler, ServerListPing};
|
use valence::client::GameMode;
|
||||||
|
use valence::config::{Config, Login, ServerListPing};
|
||||||
use valence::text::Color;
|
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 {
|
pub fn main() -> ShutdownResult {
|
||||||
env_logger::Builder::new()
|
env_logger::Builder::new()
|
||||||
|
@ -13,23 +16,31 @@ pub fn main() -> ShutdownResult {
|
||||||
.parse_default_env()
|
.parse_default_env()
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let game = Game {
|
valence::start_server(Game {
|
||||||
favicon: Arc::from(include_bytes!("favicon.png").as_slice()),
|
favicon: Arc::from(include_bytes!("favicon.png").as_slice()),
|
||||||
};
|
player_count: AtomicUsize::new(0),
|
||||||
|
})
|
||||||
let mut cfg = ServerConfig::new();
|
|
||||||
|
|
||||||
cfg.handler(game);
|
|
||||||
cfg.online_mode(false);
|
|
||||||
cfg.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Game {
|
struct Game {
|
||||||
favicon: Arc<[u8]>,
|
favicon: Arc<[u8]>,
|
||||||
|
player_count: AtomicUsize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_PLAYERS: usize = 10;
|
||||||
|
|
||||||
#[async_trait]
|
#[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) {
|
fn init(&self, server: &mut Server) {
|
||||||
let world_id = server.worlds.create(DimensionId::default());
|
let world_id = server.worlds.create(DimensionId::default());
|
||||||
let world = server.worlds.get_mut(world_id).unwrap();
|
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_id = server.chunks.create(384);
|
||||||
let chunk = server.chunks.get_mut(chunk_id).unwrap();
|
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 z in 0..16 {
|
||||||
for x in 0..16 {
|
for x in 0..16 {
|
||||||
for y in 0..50 {
|
for y in 0..50 {
|
||||||
|
@ -48,40 +66,57 @@ impl Handler for Game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
world.chunks_mut().insert((x, z).into(), chunk_id);
|
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) {
|
fn update(&self, server: &mut Server) {
|
||||||
let world_id = server.worlds.iter().next().unwrap().0;
|
let world_id = server.worlds.iter().next().unwrap().0;
|
||||||
|
|
||||||
server.clients.retain(|_, client| {
|
server.clients.retain(|_, client| {
|
||||||
if client.created_tick() == server.other.current_tick() {
|
if client.created_tick() == server.other.current_tick() {
|
||||||
client.set_world(Some(world_id));
|
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() {
|
if client.is_disconnected() {
|
||||||
server.entities.delete(client.entity());
|
server.entities.delete(client.entity());
|
||||||
|
self.player_count.fetch_sub(1, Ordering::SeqCst);
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
true
|
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,
|
blocks_and_biomes,
|
||||||
block_entities: Vec::new(), // TODO
|
block_entities: Vec::new(), // TODO
|
||||||
trust_edges: true,
|
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(),
|
block_light_mask: BitVec::new(),
|
||||||
empty_sky_light_mask: BitVec::new(),
|
empty_sky_light_mask: BitVec::new(),
|
||||||
empty_block_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(),
|
block_light_arrays: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,11 @@ use crate::config::{
|
||||||
pub use crate::packets::play::GameMode;
|
pub use crate::packets::play::GameMode;
|
||||||
use crate::packets::play::{
|
use crate::packets::play::{
|
||||||
Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic,
|
Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic,
|
||||||
BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ClientPlayPacket,
|
BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState,
|
||||||
DimensionCodec, DimensionType, DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect,
|
ChangeGameStateReason, ClientPlayPacket, DimensionCodec, DimensionType, DimensionTypeRegistry,
|
||||||
JoinGame, KeepAliveClientbound, PlayerPositionAndLook, PlayerPositionAndLookFlags,
|
DimensionTypeRegistryEntry, Disconnect, JoinGame, KeepAliveClientbound, PlayerPositionAndLook,
|
||||||
ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition,
|
PlayerPositionAndLookFlags, ServerPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance,
|
||||||
|
UpdateViewPosition,
|
||||||
};
|
};
|
||||||
use crate::protocol::{BoundedInt, Nbt};
|
use crate::protocol::{BoundedInt, Nbt};
|
||||||
use crate::server::{Other, ServerPacketChannels};
|
use crate::server::{Other, ServerPacketChannels};
|
||||||
|
@ -84,7 +85,6 @@ pub struct ClientId(Key);
|
||||||
|
|
||||||
/// Represents a client connected to the server after logging in.
|
/// Represents a client connected to the server after logging in.
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
shared: SharedServer,
|
|
||||||
/// Setting this to `None` disconnects the client.
|
/// Setting this to `None` disconnects the client.
|
||||||
send: Option<Sender<ClientPlayPacket>>,
|
send: Option<Sender<ClientPlayPacket>>,
|
||||||
recv: Receiver<ServerPlayPacket>,
|
recv: Receiver<ServerPlayPacket>,
|
||||||
|
@ -147,7 +147,6 @@ impl Client {
|
||||||
let (send, recv) = packet_channels;
|
let (send, recv) = packet_channels;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
shared: server.shared().clone(),
|
|
||||||
send: Some(send),
|
send: Some(send),
|
||||||
recv,
|
recv,
|
||||||
entity,
|
entity,
|
||||||
|
@ -200,10 +199,10 @@ impl Client {
|
||||||
self.pitch
|
self.pitch
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn teleport(&mut self, pos: DVec3, yaw_degrees: f32, pitch_degrees: f32) {
|
pub fn teleport(&mut self, pos: impl Into<DVec3>, yaw: f32, pitch: f32) {
|
||||||
self.new_position = pos;
|
self.new_position = pos.into();
|
||||||
self.yaw = yaw_degrees;
|
self.yaw = yaw;
|
||||||
self.pitch = pitch_degrees;
|
self.pitch = pitch;
|
||||||
|
|
||||||
if !self.teleported_this_tick {
|
if !self.teleported_this_tick {
|
||||||
self.teleported_this_tick = true;
|
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 {
|
pub fn on_ground(&self) -> bool {
|
||||||
self.on_ground
|
self.on_ground
|
||||||
}
|
}
|
||||||
|
@ -329,12 +336,12 @@ impl Client {
|
||||||
if self.created_tick == other.current_tick() {
|
if self.created_tick == other.current_tick() {
|
||||||
self.send_packet(JoinGame {
|
self.send_packet(JoinGame {
|
||||||
entity_id: self.entity.to_network_id(),
|
entity_id: self.entity.to_network_id(),
|
||||||
is_hardcore: false,
|
is_hardcore: false, // TODO
|
||||||
gamemode: self.new_game_mode,
|
gamemode: self.new_game_mode,
|
||||||
previous_gamemode: self.old_game_mode,
|
previous_gamemode: self.old_game_mode,
|
||||||
dimension_names: other
|
dimension_names: other
|
||||||
.dimensions()
|
.dimensions()
|
||||||
.map(|(_, id)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0))
|
.map(|(id, _)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0))
|
||||||
.collect(),
|
.collect(),
|
||||||
dimension_codec: Nbt(make_dimension_codec(other)),
|
dimension_codec: Nbt(make_dimension_codec(other)),
|
||||||
dimension: Nbt(to_dimension_registry_item(dim)),
|
dimension: Nbt(to_dimension_registry_item(dim)),
|
||||||
|
@ -350,6 +357,12 @@ impl Client {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.teleport(self.position(), self.yaw(), self.pitch());
|
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)
|
// Update the players spawn position (compass position)
|
||||||
|
@ -635,7 +648,6 @@ impl Client {
|
||||||
impl Drop for Client {
|
impl Drop for Client {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
log::trace!("Dropping client '{}'", self.username);
|
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 {
|
fn make_dimension_codec(other: &Other) -> DimensionCodec {
|
||||||
let mut dims = Vec::new();
|
let mut dims = Vec::new();
|
||||||
for (dim, id) in other.dimensions() {
|
for (id, dim) in other.dimensions() {
|
||||||
let id = id.0 as i32;
|
let id = id.0 as i32;
|
||||||
dims.push(DimensionTypeRegistryEntry {
|
dims.push(DimensionTypeRegistryEntry {
|
||||||
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
|
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
|
||||||
|
@ -700,7 +712,7 @@ fn make_dimension_codec(other: &Other) -> DimensionCodec {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut biomes = Vec::new();
|
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));
|
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);
|
|
367
src/config.rs
367
src/config.rs
|
@ -1,83 +1,64 @@
|
||||||
// TODO: rate limit, view distance?
|
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||||
|
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::ensure;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use tokio::runtime::Handle as TokioHandle;
|
use tokio::runtime::Handle as TokioHandle;
|
||||||
|
|
||||||
use crate::server::{start_server, ShutdownError};
|
use crate::{ident, Id, Identifier, NewClientData, Server, SharedServer, Text};
|
||||||
use crate::{ident, Identifier, NewClientData, Server, SharedServer, ShutdownResult, Text};
|
|
||||||
|
|
||||||
/// A builder type used to configure and start the server.
|
/// A trait containing callbacks which are invoked by the running Minecraft
|
||||||
pub struct ServerConfig {
|
/// server.
|
||||||
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.
|
|
||||||
///
|
///
|
||||||
/// The default is `127.0.0.1:25565`.
|
/// The config is used from multiple threads and must therefore implement
|
||||||
pub fn address(&mut self, addr: impl Into<SocketAddr>) {
|
/// `Send` and `Sync`. From within a single thread, methods are never invoked
|
||||||
self.address = addr.into();
|
/// 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.
|
||||||
|
///
|
||||||
|
/// 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
|
/// 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,
|
/// logic and respond to packets from clients. Once this is complete,
|
||||||
/// the server will sleep for any remaining time until the full update
|
/// the server will sleep for any remaining time until the full update
|
||||||
/// duration has passed.
|
/// 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 duration must be nonzero.
|
||||||
///
|
///
|
||||||
/// The default value is the same as Minecraft's official server (20 ticks
|
/// # Default Implementation
|
||||||
/// per second). You may want to use a shorter duration if you can afford to
|
/// Returns 1/20th of a second, which is the same as Minecraft's official
|
||||||
/// do so.
|
/// server.
|
||||||
pub fn update_duration(&mut self, duration: Duration) {
|
fn update_duration(&self) -> Duration {
|
||||||
self.update_duration = duration;
|
Duration::from_secs_f64(1.0 / 20.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the state of "online mode", which determines if client
|
/// Called once at startup to get the "online mode" option, which determines
|
||||||
/// authentication and encryption should occur.
|
/// if client authentication and encryption should take place.
|
||||||
///
|
///
|
||||||
/// When online mode is disabled, malicious clients can give themselves any
|
/// When online mode is disabled, malicious clients can give themselves any
|
||||||
/// username and UUID they want, potentially gaining privileges they
|
/// 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
|
/// for development purposes and enabled on servers exposed to the
|
||||||
/// internet.
|
/// internet.
|
||||||
///
|
///
|
||||||
/// By default, online mode is enabled.
|
/// # Default Implementation
|
||||||
pub fn online_mode(&mut self, online_mode: bool) {
|
/// Returns `true`.
|
||||||
self.online_mode = online_mode;
|
fn online_mode(&self) -> bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the maximum number of clients (past the login stage) allowed on the
|
/// Called once at startup to get the capacity of the buffer used to
|
||||||
/// server simultaneously.
|
/// hold incoming packets.
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
///
|
///
|
||||||
/// A larger capcity reduces the chance of packet loss but increases
|
/// A larger capcity reduces the chance of packet loss but increases
|
||||||
/// potential memory usage. The default value is unspecified but should be
|
/// potential memory usage.
|
||||||
/// adequate for most situations.
|
|
||||||
///
|
///
|
||||||
/// The capacity must be nonzero.
|
/// # Default Implementation
|
||||||
pub fn clientbound_packet_capacity(&mut self, cap: usize) {
|
/// An unspecified value is returned that should be adequate in most
|
||||||
self.clientbound_packet_capacity = cap;
|
/// 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
|
/// A larger capcity reduces the chance of packet loss due to a full buffer
|
||||||
/// potential memory usage. The default value is unspecified but should be
|
/// but increases potential memory usage.
|
||||||
/// adequate for most situations.
|
|
||||||
///
|
///
|
||||||
/// The capacity must be nonzero.
|
/// # Default Implementation
|
||||||
pub fn serverbound_packet_capacity(&mut self, cap: usize) {
|
/// An unspecified value is returned that should be adequate in most
|
||||||
self.serverbound_packet_capacity = cap;
|
/// 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
|
/// If a handle is not provided, the server will create its own tokio
|
||||||
/// runtime.
|
/// runtime.
|
||||||
pub fn tokio_handle(&mut self, handle: TokioHandle) {
|
|
||||||
self.tokio_handle = Some(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new dimension to the server which is identified by the returned
|
|
||||||
/// [`DimensionId`]. The default dimension is added if none are provided.
|
|
||||||
///
|
///
|
||||||
/// Additionally, the documented requirements on the fields of [`Dimension`]
|
/// # Default Implementation
|
||||||
/// must be met. No more than `u16::MAX` dimensions may be added.
|
/// Returns `None`.
|
||||||
pub fn push_dimension(&mut self, dimension: Dimension) -> DimensionId {
|
fn tokio_handle(&self) -> Option<TokioHandle> {
|
||||||
let id = self.biomes.len();
|
None
|
||||||
self.dimensions.push(dimension);
|
|
||||||
DimensionId(id as u16)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new biome to the server which is identified by the returned
|
/// Called once at startup to get the list of [`Dimension`]s usable on the
|
||||||
/// [`BiomeId`]. The default biome is added if none are provided.
|
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
///
|
|
||||||
/// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
/// server.
|
||||||
///
|
///
|
||||||
/// The handler is used from multiple threads and must therefore implement
|
/// The dimensions traversed by [`Server::dimensions`] will be in the same
|
||||||
/// `Send` and `Sync`. From within a single thread, callbacks are never invoked
|
/// order as the `Vec` returned by this function.
|
||||||
/// 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.
|
/// The number of elements in the returned `Vec` must be in \[1, u16::MAX].
|
||||||
#[async_trait]
|
/// Additionally, the documented requirements on the fields of [`Dimension`]
|
||||||
#[allow(unused_variables)]
|
/// must be met.
|
||||||
pub trait Handler: Any + Send + Sync {
|
///
|
||||||
|
/// # Default Implementation
|
||||||
|
/// Returns `vec![Dimension::default()]`.
|
||||||
|
fn dimensions(&self) -> Vec<Dimension> {
|
||||||
|
vec![Dimension::default()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// # Default Implementation
|
||||||
|
/// Returns `vec![Dimension::default()]`.
|
||||||
|
fn biomes(&self) -> Vec<Biome> {
|
||||||
|
vec![Biome::default()]
|
||||||
|
}
|
||||||
|
|
||||||
/// Called after the server is created, but prior to accepting connections
|
/// Called after the server is created, but prior to accepting connections
|
||||||
/// and entering the update loop.
|
/// and entering the update loop.
|
||||||
///
|
///
|
||||||
/// This is useful for performing initialization work with a guarantee that
|
/// This is useful for performing initialization work with a guarantee that
|
||||||
/// no connections to the server will be made until this function returns.
|
/// no connections to the server will be made until this function returns.
|
||||||
///
|
///
|
||||||
|
/// This method is called from within a tokio runtime.
|
||||||
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
/// The default implementation does nothing.
|
/// The default implementation does nothing.
|
||||||
fn init(&self, server: &mut Server) {}
|
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`
|
/// The frequency of server updates can be configured by `update_duration`
|
||||||
/// in [`ServerConfig`].
|
/// in [`ServerConfig`].
|
||||||
///
|
///
|
||||||
|
/// This method is called from within a tokio runtime.
|
||||||
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
/// The default implementation does nothing.
|
/// The default implementation does nothing.
|
||||||
fn update(&self, server: &mut Server) {}
|
fn update(&self, server: &mut Server) {}
|
||||||
|
|
||||||
/// Called when the server receives a Server List Ping query.
|
/// 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
|
/// # Default Implementation
|
||||||
/// A placeholder response is returned.
|
/// The query is ignored.
|
||||||
async fn server_list_ping(
|
async fn server_list_ping(
|
||||||
&self,
|
&self,
|
||||||
server: &SharedServer,
|
server: &SharedServer,
|
||||||
remote_addr: SocketAddr,
|
remote_addr: SocketAddr,
|
||||||
) -> ServerListPing {
|
) -> ServerListPing {
|
||||||
ServerListPing::Respond {
|
ServerListPing::Ignore
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called asynchronously for each client after successful authentication
|
/// 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.
|
/// server. On success, a client-backed entity is spawned.
|
||||||
///
|
///
|
||||||
/// This function is the appropriate place to perform
|
/// 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
|
/// # Default Implementation
|
||||||
/// The client is allowed to join unconditionally.
|
/// 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.
|
/// The result of the [`server_list_ping`](Handler::server_list_ping) callback.
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ServerListPing {
|
pub enum ServerListPing {
|
||||||
/// Responds to the server list ping with the given information.
|
/// Responds to the server list ping with the given information.
|
||||||
Respond {
|
Respond {
|
||||||
|
@ -341,18 +226,33 @@ pub enum Login {
|
||||||
Disconnect(Text),
|
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.
|
/// Dimension IDs must only be used on servers from which they originate.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
pub struct DimensionId(pub(crate) u16);
|
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.
|
/// In Minecraft, "dimension" and "dimension type" are two different concepts.
|
||||||
/// For instance, the Overworld and Nether are dimensions, each with
|
/// For instance, the Overworld and Nether are dimensions, each with
|
||||||
/// their own dimension type. A dimension in this library is analogous to a
|
/// 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.
|
/// dimension type.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Dimension {
|
pub struct Dimension {
|
||||||
|
@ -394,7 +294,7 @@ impl Default for Dimension {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
natural: true,
|
natural: true,
|
||||||
ambient_light: 0.0,
|
ambient_light: 1.0,
|
||||||
fixed_time: None,
|
fixed_time: None,
|
||||||
effects: DimensionEffects::Overworld,
|
effects: DimensionEffects::Overworld,
|
||||||
min_y: -64,
|
min_y: -64,
|
||||||
|
@ -416,7 +316,14 @@ pub enum DimensionEffects {
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
pub struct BiomeId(pub(crate) u16);
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Biome {
|
pub struct Biome {
|
||||||
/// The unique name for this biome. The name can be
|
/// The unique name for this biome. The name can be
|
||||||
|
|
|
@ -6,7 +6,7 @@ use uuid::Uuid;
|
||||||
use crate::block_pos::BlockPos;
|
use crate::block_pos::BlockPos;
|
||||||
use crate::protocol::Encode;
|
use crate::protocol::Encode;
|
||||||
use crate::var_int::VarInt;
|
use crate::var_int::VarInt;
|
||||||
use crate::{def_bitfield, Text};
|
use crate::Text;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
|
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
|
||||||
pub struct ArmorStandRotations {
|
pub struct ArmorStandRotations {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![warn(
|
#![warn(
|
||||||
missing_debug_implementations,
|
|
||||||
trivial_casts,
|
trivial_casts,
|
||||||
trivial_numeric_casts,
|
trivial_numeric_casts,
|
||||||
unused_lifetimes,
|
unused_lifetimes,
|
||||||
|
@ -15,7 +14,6 @@ mod byte_angle;
|
||||||
pub mod chunk;
|
pub mod chunk;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
mod codec;
|
mod codec;
|
||||||
pub mod component;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod entity;
|
pub mod entity;
|
||||||
pub mod identifier;
|
pub mod identifier;
|
||||||
|
@ -30,19 +28,19 @@ mod var_long;
|
||||||
pub mod world;
|
pub mod world;
|
||||||
|
|
||||||
pub use aabb::Aabb;
|
pub use aabb::Aabb;
|
||||||
|
pub use async_trait::async_trait;
|
||||||
pub use block_pos::BlockPos;
|
pub use block_pos::BlockPos;
|
||||||
pub use chunk::{Chunk, ChunkPos, ChunkStore};
|
pub use chunk::{Chunk, ChunkPos, ChunkStore};
|
||||||
pub use client::{Client, ClientStore};
|
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 entity::{Entity, EntityId, EntityStore};
|
||||||
pub use identifier::Identifier;
|
pub use identifier::Identifier;
|
||||||
|
pub use server::{start_server, NewClientData, Server, SharedServer, ShutdownResult};
|
||||||
pub use text::{Text, TextFormat};
|
pub use text::{Text, TextFormat};
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
pub use world::{World, WorldId, WorldStore};
|
pub use world::{World, WorldId, WorldStore};
|
||||||
pub use {nalgebra_glm as glm, nbt, uuid};
|
pub use {nalgebra_glm as glm, nbt, uuid};
|
||||||
|
|
||||||
pub use crate::server::{NewClientData, Server, SharedServer, ShutdownResult};
|
|
||||||
|
|
||||||
/// The Minecraft protocol version that this library targets.
|
/// The Minecraft protocol version that this library targets.
|
||||||
pub const PROTOCOL_VERSION: i32 = 758;
|
pub const PROTOCOL_VERSION: i32 = 758;
|
||||||
/// The name of the Minecraft version that this library targets.
|
/// 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 {
|
macro_rules! def_bitfield {
|
||||||
(
|
(
|
||||||
$(#[$struct_attrs:meta])*
|
$(#[$struct_attrs:meta])*
|
||||||
|
@ -282,7 +280,7 @@ macro_rules! def_bitfield {
|
||||||
)*
|
)*
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut res = Self(Default::default());
|
let mut res = Self(Default::default());
|
||||||
paste::paste! {
|
paste! {
|
||||||
$(
|
$(
|
||||||
res = res.[<set_ $bit:snake>]($bit);
|
res = res.[<set_ $bit:snake>]($bit);
|
||||||
)*
|
)*
|
||||||
|
@ -290,7 +288,7 @@ macro_rules! def_bitfield {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
paste::paste! {
|
paste! {
|
||||||
$(
|
$(
|
||||||
#[doc = "Gets the " $bit " bit on this bitfield.\n"]
|
#[doc = "Gets the " $bit " bit on this bitfield.\n"]
|
||||||
$(#[$bit_attrs])*
|
$(#[$bit_attrs])*
|
||||||
|
@ -743,7 +741,7 @@ pub mod play {
|
||||||
NoRespawnBlockAvailable = 0,
|
NoRespawnBlockAvailable = 0,
|
||||||
EndRaining = 1,
|
EndRaining = 1,
|
||||||
BeginRaining = 2,
|
BeginRaining = 2,
|
||||||
ChangeGamemode = 3,
|
ChangeGameMode = 3,
|
||||||
WinGame = 4,
|
WinGame = 4,
|
||||||
DemoEvent = 5,
|
DemoEvent = 5,
|
||||||
ArrowHitPlayer = 6,
|
ArrowHitPlayer = 6,
|
||||||
|
@ -965,7 +963,7 @@ pub mod play {
|
||||||
}
|
}
|
||||||
|
|
||||||
def_enum! {
|
def_enum! {
|
||||||
#[derive(Copy, PartialEq, Eq)]
|
#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
GameMode: u8 {
|
GameMode: u8 {
|
||||||
Survival = 0,
|
Survival = 0,
|
||||||
Creative = 1,
|
Creative = 1,
|
||||||
|
@ -1116,6 +1114,7 @@ pub mod play {
|
||||||
Disconnect,
|
Disconnect,
|
||||||
EntityStatus,
|
EntityStatus,
|
||||||
UnloadChunk,
|
UnloadChunk,
|
||||||
|
ChangeGameState,
|
||||||
KeepAliveClientbound,
|
KeepAliveClientbound,
|
||||||
ChunkDataAndUpdateLight,
|
ChunkDataAndUpdateLight,
|
||||||
JoinGame,
|
JoinGame,
|
||||||
|
|
259
src/server.rs
259
src/server.rs
|
@ -1,8 +1,8 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
@ -27,7 +27,7 @@ use tokio::sync::{oneshot, Semaphore};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::codec::{Decoder, Encoder};
|
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::handshake::{Handshake, HandshakeNextState};
|
||||||
use crate::packets::login::{
|
use crate::packets::login::{
|
||||||
self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression,
|
self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression,
|
||||||
|
@ -38,18 +38,17 @@ use crate::protocol::{BoundedArray, BoundedString};
|
||||||
use crate::util::valid_username;
|
use crate::util::valid_username;
|
||||||
use crate::var_int::VarInt;
|
use crate::var_int::VarInt;
|
||||||
use crate::{
|
use crate::{
|
||||||
ChunkStore, Client, ClientStore, EntityStore, ServerConfig, Ticks, WorldStore,
|
ChunkStore, Client, ClientStore, EntityStore, Ticks, WorldStore, PROTOCOL_VERSION, VERSION_NAME,
|
||||||
PROTOCOL_VERSION, VERSION_NAME,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Holds the state of a running Minecraft server which is accessible inside the
|
/// Holds the state of a running Minecraft server which is accessible inside the
|
||||||
/// update loop. To start a server, see [`ServerConfig`].
|
/// update loop. To start a server, see [`ServerConfig`].
|
||||||
///
|
///
|
||||||
/// Fields of this struct are made public to enable disjoint borrows. For
|
/// Fields of this struct are made public to enable disjoint borrows. For
|
||||||
/// instance, it is possible to create and delete entities while
|
/// instance, it is possible to mutate the list of entities while simultaneously
|
||||||
/// having read-only access to world data.
|
/// 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.
|
/// allow convenient access to the `other` field.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
|
@ -79,14 +78,16 @@ pub struct Other {
|
||||||
pub struct SharedServer(Arc<SharedServerInner>);
|
pub struct SharedServer(Arc<SharedServerInner>);
|
||||||
|
|
||||||
struct SharedServerInner {
|
struct SharedServerInner {
|
||||||
handler: Box<dyn Handler>,
|
cfg: Box<dyn Config>,
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
update_duration: Duration,
|
update_duration: Duration,
|
||||||
online_mode: bool,
|
online_mode: bool,
|
||||||
max_clients: usize,
|
max_connections: usize,
|
||||||
clientbound_packet_capacity: usize,
|
incoming_packet_capacity: usize,
|
||||||
serverbound_packet_capacity: usize,
|
outgoing_packet_capacity: usize,
|
||||||
tokio_handle: Handle,
|
tokio_handle: Handle,
|
||||||
|
/// Store this here so we don't drop it.
|
||||||
|
_tokio_runtime: Option<Runtime>,
|
||||||
dimensions: Vec<Dimension>,
|
dimensions: Vec<Dimension>,
|
||||||
biomes: Vec<Biome>,
|
biomes: Vec<Biome>,
|
||||||
/// The instant the server was started.
|
/// The instant the server was started.
|
||||||
|
@ -104,10 +105,9 @@ struct SharedServerInner {
|
||||||
/// For session server requests.
|
/// For session server requests.
|
||||||
http_client: HttpClient,
|
http_client: HttpClient,
|
||||||
new_clients_tx: Sender<NewClientMessage>,
|
new_clients_tx: Sender<NewClientMessage>,
|
||||||
client_count: AtomicUsize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains information about a new player.
|
/// Contains information about a new client.
|
||||||
pub struct NewClientData {
|
pub struct NewClientData {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
@ -145,8 +145,8 @@ impl Other {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedServer {
|
impl SharedServer {
|
||||||
pub fn handler(&self) -> &(impl Handler + ?Sized) {
|
pub fn config(&self) -> &(impl Config + ?Sized) {
|
||||||
self.0.handler.as_ref()
|
self.0.cfg.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn address(&self) -> SocketAddr {
|
pub fn address(&self) -> SocketAddr {
|
||||||
|
@ -161,16 +161,16 @@ impl SharedServer {
|
||||||
self.0.online_mode
|
self.0.online_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_clients(&self) -> usize {
|
pub fn max_connections(&self) -> usize {
|
||||||
self.0.max_clients
|
self.0.max_connections
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clientbound_packet_capacity(&self) -> usize {
|
pub fn incoming_packet_capacity(&self) -> usize {
|
||||||
self.0.clientbound_packet_capacity
|
self.0.incoming_packet_capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serverbound_packet_capacity(&self) -> usize {
|
pub fn outgoing_packet_capacity(&self) -> usize {
|
||||||
self.0.serverbound_packet_capacity
|
self.0.outgoing_packet_capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tokio_handle(&self) -> &Handle {
|
pub fn tokio_handle(&self) -> &Handle {
|
||||||
|
@ -191,31 +191,27 @@ impl SharedServer {
|
||||||
|
|
||||||
/// Returns an iterator over all added dimensions and their associated
|
/// Returns an iterator over all added dimensions and their associated
|
||||||
/// [`DimensionId`].
|
/// [`DimensionId`].
|
||||||
pub fn dimensions(&self) -> impl FusedIterator<Item = (&Dimension, DimensionId)> + Clone {
|
pub fn dimensions(&self) -> impl FusedIterator<Item = (DimensionId, &Dimension)> + Clone {
|
||||||
self.0
|
self.0
|
||||||
.dimensions
|
.dimensions
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.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`].
|
/// 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 {
|
pub fn biome(&self, id: BiomeId) -> &Biome {
|
||||||
self.0.biomes.get(id.0 as usize).expect("invalid biome ID")
|
self.0.biomes.get(id.0 as usize).expect("invalid biome ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over all added biomes and their associated
|
/// Returns an iterator over all added biomes and their associated
|
||||||
/// [`BiomeId`].
|
/// [`BiomeId`].
|
||||||
pub fn biomes(&self) -> impl FusedIterator<Item = (&Biome, BiomeId)> + Clone {
|
pub fn biomes(&self) -> impl FusedIterator<Item = (BiomeId, &Biome)> + Clone {
|
||||||
self.0
|
self.0
|
||||||
.biomes
|
.biomes
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, b)| (b, BiomeId(i as u16)))
|
.map(|(i, b)| (BiomeId(i as u16), b))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the instant the server was started.
|
/// Returns the instant the server was started.
|
||||||
|
@ -236,33 +232,6 @@ impl SharedServer {
|
||||||
self.0.connection_sema.close();
|
self.0.connection_sema.close();
|
||||||
*self.0.shutdown_result.lock() = Some(res.into().map_err(|e| e.into()));
|
*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 {
|
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 rsa_key = RsaPrivateKey::new(&mut OsRng, 1024)?;
|
||||||
|
|
||||||
let public_key_der =
|
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 (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()?)
|
Some(Runtime::new()?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle = match &rt {
|
let tokio_handle = match &runtime {
|
||||||
Some(rt) => rt.handle().clone(),
|
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 {
|
let shared = SharedServer(Arc::new(SharedServerInner {
|
||||||
handler: config.handler.unwrap_or_else(|| Box::new(DummyHandler)),
|
cfg: Box::new(cfg),
|
||||||
address: config.address,
|
address,
|
||||||
update_duration: config.update_duration,
|
update_duration,
|
||||||
online_mode: config.online_mode,
|
online_mode,
|
||||||
max_clients: config.max_clients,
|
max_connections,
|
||||||
clientbound_packet_capacity: config.clientbound_packet_capacity,
|
outgoing_packet_capacity,
|
||||||
serverbound_packet_capacity: config.serverbound_packet_capacity,
|
incoming_packet_capacity,
|
||||||
tokio_handle: handle.clone(),
|
tokio_handle,
|
||||||
dimensions: config.dimensions,
|
_tokio_runtime: runtime,
|
||||||
biomes: config.biomes,
|
dimensions,
|
||||||
|
biomes,
|
||||||
start_instant: Instant::now(),
|
start_instant: Instant::now(),
|
||||||
connection_sema,
|
connection_sema: Arc::new(Semaphore::new(max_connections)),
|
||||||
shutdown_result: Mutex::new(None),
|
shutdown_result: Mutex::new(None),
|
||||||
rsa_key,
|
rsa_key,
|
||||||
public_key_der,
|
public_key_der,
|
||||||
http_client: HttpClient::new(),
|
http_client: HttpClient::new(),
|
||||||
new_clients_tx: new_players_tx,
|
new_clients_tx: new_players_tx,
|
||||||
client_count: AtomicUsize::new(0),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut server = Server {
|
Ok(Server {
|
||||||
entities: EntityStore::new(),
|
entities: EntityStore::new(),
|
||||||
clients: ClientStore::new(),
|
clients: ClientStore::new(),
|
||||||
worlds: WorldStore::new(),
|
worlds: WorldStore::new(),
|
||||||
|
@ -347,13 +410,7 @@ pub(crate) fn start_server(config: ServerConfig) -> ShutdownResult {
|
||||||
new_players_rx,
|
new_players_rx,
|
||||||
last_keepalive: Instant::now(),
|
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 {
|
fn do_update_loop(server: &mut Server) -> ShutdownResult {
|
||||||
|
@ -392,7 +449,7 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult {
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.for_each(|(_, chunk)| chunk.apply_modifications());
|
.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
|
// Chunks modified this tick can have their changes applied immediately because
|
||||||
// they have not been observed by clients yet.
|
// 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) {
|
fn join_player(server: &mut Server, msg: NewClientMessage) {
|
||||||
let (clientbound_tx, clientbound_rx) = flume::bounded(server.0.clientbound_packet_capacity);
|
let (clientbound_tx, clientbound_rx) = flume::bounded(server.0.outgoing_packet_capacity);
|
||||||
let (serverbound_tx, serverbound_rx) = flume::bounded(server.0.serverbound_packet_capacity);
|
let (serverbound_tx, serverbound_rx) = flume::bounded(server.0.incoming_packet_capacity);
|
||||||
|
|
||||||
let client_packet_channels: ClientPacketChannels = (serverbound_tx, clientbound_rx);
|
let client_packet_channels: ClientPacketChannels = (serverbound_tx, clientbound_rx);
|
||||||
let server_packet_channels: ServerPacketChannels = (clientbound_tx, serverbound_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) {
|
let client_backed_entity = match server.entities.create_with_uuid(msg.ncd.uuid) {
|
||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => {
|
None => {
|
||||||
log::error!(
|
log::warn!(
|
||||||
"player '{}' cannot join the server because their UUID ({}) conflicts with an \
|
"player '{}' cannot join the server because their UUID ({}) conflicts with an \
|
||||||
existing entity",
|
existing entity",
|
||||||
msg.ncd.username,
|
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,
|
server_packet_channels,
|
||||||
client_backed_entity,
|
client_backed_entity,
|
||||||
msg.ncd.username,
|
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
|
// Setting TCP_NODELAY to true appears to trade some throughput for improved
|
||||||
// latency. Testing is required to determine if this is worth keeping.
|
// latency. Testing is required to determine if this is worth keeping.
|
||||||
if let Err(e) = stream.set_nodelay(true) {
|
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 {
|
if let Err(e) = handle_connection(server, stream, remote_addr).await {
|
||||||
|
@ -522,12 +579,7 @@ async fn handle_status(
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
c.1.read_packet::<Request>().await?;
|
c.1.read_packet::<Request>().await?;
|
||||||
|
|
||||||
match server
|
match server.0.cfg.server_list_ping(&server, remote_addr).await {
|
||||||
.0
|
|
||||||
.handler
|
|
||||||
.server_list_ping(&server, remote_addr)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
ServerListPing::Respond {
|
ServerListPing::Respond {
|
||||||
online_players,
|
online_players,
|
||||||
max_players,
|
max_players,
|
||||||
|
@ -685,14 +737,7 @@ async fn handle_login(
|
||||||
remote_addr,
|
remote_addr,
|
||||||
};
|
};
|
||||||
|
|
||||||
if !server.try_inc_player_count() {
|
if let Login::Disconnect(reason) = server.0.cfg.login(server, &npd).await {
|
||||||
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 {
|
|
||||||
log::info!("Disconnect at login: \"{reason}\"");
|
log::info!("Disconnect at login: \"{reason}\"");
|
||||||
c.0.write_packet(&login::Disconnect { reason }).await?;
|
c.0.write_packet(&login::Disconnect { reason }).await?;
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
|
Loading…
Reference in a new issue