move from private repo

This commit is contained in:
Ryan 2022-04-14 14:55:45 -07:00
parent db0eae3a6c
commit 5bbbb258d1
30 changed files with 8283 additions and 9 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
Cargo.lock

7
Cargo.lock generated
View file

@ -1,7 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "valence"
version = "0.0.1"

View file

@ -1,9 +1,50 @@
[package]
name = "valence"
version = "0.0.1"
version = "0.1.0"
edition = "2021"
description = "Coming soon to a package manager near you!"
description = "A framework for building Minecraft servers in Rust."
repository = "https://github.com/rj00a/valence"
license = "MIT"
[workspace]
members = ["valence-data"]
[dependencies]
aes = "0.7"
anyhow = "1"
ascii = "1"
async-trait = "0.1"
base64 = "0.13"
bitvec = "1"
bytes = "1"
byteorder = "1"
cfb8 = "0.7"
flate2 = "1"
flume = "0.10"
futures = "0.3"
hematite-nbt = "0.5"
log = "0.4"
nalgebra-glm = "0.16"
num = "0.4"
parking_lot = "0.12"
paste = "1"
rand = "0.8"
rayon = "1"
rsa = "0.5"
rsa-der = "0.3"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha1 = "0.10"
sha2 = "0.10"
thiserror = "1"
tokio = { version = "1", features = ["full"] }
uuid = "0.8"
[dependencies.reqwest]
version = "0.11"
default-features = false
# Avoid OpenSSL dependency on Linux.
features = ["rustls-tls", "json"]
[dev-dependencies]
env_logger = "0.9"

99
examples/basic.rs Normal file
View file

@ -0,0 +1,99 @@
use std::net::SocketAddr;
use std::sync::Arc;
use async_trait::async_trait;
use log::LevelFilter;
use valence::config::{Handler, ServerListPing};
use valence::text::Color;
use valence::{glm, DimensionId, Server, ServerConfig, SharedServer, ShutdownResult, TextFormat};
pub fn main() -> ShutdownResult {
env_logger::Builder::new()
.filter_level(LevelFilter::Trace)
.parse_default_env()
.init();
let game = Game {
favicon: Arc::from(include_bytes!("favicon.png").as_slice()),
};
let mut cfg = ServerConfig::new();
cfg.handler(game);
cfg.online_mode(false);
cfg.start()
}
struct Game {
favicon: Arc<[u8]>,
}
#[async_trait]
impl Handler for Game {
fn init(&self, server: &mut Server) {
let id = server.worlds.create(DimensionId::default());
let mut worlds = server.worlds.worlds_mut().unwrap();
let world = server.worlds.get(&mut worlds, id).unwrap();
let chunk_radius = 5;
for z in -chunk_radius..chunk_radius {
for x in -chunk_radius..chunk_radius {
let id = server.chunks.create(384);
let mut chunks = server.chunks.chunks_mut().unwrap();
let chunk = server.chunks.get(&mut chunks, 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);
}
}
}
world.chunks_mut().insert((x, z).into(), id);
}
}
}
async fn server_list_ping(
&self,
server: &SharedServer,
_remote_addr: SocketAddr,
) -> ServerListPing {
ServerListPing::Respond {
online_players: server.client_count() as i32,
max_players: server.max_clients() as i32,
description: "Hello Valence!".color(Color::AQUA),
favicon_png: Some(self.favicon.clone()),
}
}
fn update(&self, server: &mut Server) {
let mut clients = server.entities.clients_mut().unwrap();
let world_id = server.worlds.ids().nth(0).unwrap();
let mut to_remove = Vec::new();
for (client_id, client) in server.entities.iter(&mut clients) {
if let Some(client) = client.get_mut() {
if client.created_tick() == server.current_tick() {
client.set_world(world_id);
client.teleport(glm::vec3(0.0, 200.0, 0.0), 0.0, 0.0);
}
}
if client.is_disconnected() {
to_remove.push(client_id);
}
}
drop(clients);
for id in to_remove {
server.entities.delete(id);
}
}
}

BIN
examples/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

12
rustfmt.toml Normal file
View file

@ -0,0 +1,12 @@
edition = "2021"
unstable_features = true
wrap_comments = true
imports_granularity = "Module"
reorder_impl_items = true
group_imports = "StdExternalCrate"
format_code_in_doc_comments = true
format_macro_matchers = true
hex_literal_case = "Lower"
format_strings = true
use_field_init_shorthand = true
use_try_shorthand = true

64
src/aabb.rs Normal file
View file

@ -0,0 +1,64 @@
use crate::glm::{self, Number, RealNumber, TVec};
/// An Axis-aligned bounding box in an arbitrary dimension.
#[derive(Clone, Copy, Debug)] // TODO: impl PartialEq, Eq, PartialOrd, Ord, Hash
pub struct Aabb<T, const D: usize> {
min: TVec<T, D>,
max: TVec<T, D>,
}
impl<T: Number, const D: usize> Aabb<T, D> {
pub fn new(p0: TVec<T, D>, p1: TVec<T, D>) -> Self {
Self {
min: glm::min2(&p0, &p1),
max: glm::max2(&p0, &p1),
}
}
pub fn point(pos: TVec<T, D>) -> Self {
Self { min: pos, max: pos }
}
pub fn min(&self) -> TVec<T, D> {
self.min
}
pub fn max(&self) -> TVec<T, D> {
self.max
}
pub fn dimensions(&self) -> TVec<T, D> {
self.max - self.min
}
pub fn collides_with_aabb(&self, other: &Self) -> bool {
let l = glm::less_than_equal(&self.min, &other.max);
let r = glm::greater_than_equal(&self.max, &other.min);
glm::all(&l.zip_map(&r, |l, r| l && r))
}
}
impl<T: RealNumber, const D: usize> Aabb<T, D> {
/// Construct an AABB from a center (centroid) and the dimensions of the box
/// along each axis.
pub fn from_center_and_dimensions(center: TVec<T, D>, dims: TVec<T, D>) -> Self {
let half = dims * T::from_subset(&0.5);
Self {
min: center - half,
max: center + half,
}
}
pub fn center(&self) -> TVec<T, D> {
// TODO: distribute multiplication to avoid intermediate overflow?
(self.min + self.max) * T::from_subset(&0.5)
}
pub fn collides_with_sphere(&self, center: TVec<T, D>, radius: T) -> bool {
self.distance_to_point(center) <= radius
}
pub fn distance_to_point(&self, p: TVec<T, D>) -> T {
glm::distance(&p, &glm::clamp_vec(&p, &self.min, &self.max))
}
}

95
src/block_pos.rs Normal file
View file

@ -0,0 +1,95 @@
// TODO: rename to BlockPos and represent internally as [i32; 3].
use std::io::{Read, Write};
use anyhow::bail;
use glm::{IVec3, Scalar, TVec3};
use num::cast::AsPrimitive;
use crate::glm;
use crate::protocol::{Decode, Encode};
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
pub struct BlockPos(pub IVec3);
impl BlockPos {
pub fn new(x: i32, y: i32, z: i32) -> Self {
Self(glm::vec3(x, y, z))
}
pub fn from_vec3(vec: TVec3<impl Scalar + AsPrimitive<i32>>) -> Self {
Self(vec.map(|n| n.as_()))
}
}
impl<T: Scalar + Into<i32>> From<TVec3<T>> for BlockPos {
fn from(vec: TVec3<T>) -> Self {
Self(vec.map(|n| n.into()))
}
}
impl Encode for BlockPos {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
match (self.0.x, self.0.y, self.0.z) {
(-0x2000000..=0x1ffffff, -0x800..=0x7ff, -0x2000000..=0x1ffffff) => {
let (x, y, z) = (self.0.x as u64, self.0.y as u64, self.0.z as u64);
(x << 38 | z << 38 >> 26 | y & 0xfff).encode(w)
}
_ => bail!("block position {} is out of range", self.0),
}
}
}
impl Decode for BlockPos {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
// Use arithmetic right shift to determine sign.
let val = i64::decode(r)?;
let x = val >> 38;
let z = val << 26 >> 38;
let y = val << 52 >> 52;
Ok(Self(glm::vec3(x as i32, y as i32, z as i32)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn position() {
let xzs = [
(-33554432, true),
(-33554433, false),
(33554431, true),
(33554432, false),
(0, true),
(1, true),
(-1, true),
];
let ys = [
(-2048, true),
(-2049, false),
(2047, true),
(2048, false),
(0, true),
(1, true),
(-1, true),
];
let mut buf = [0; 8];
for (x, x_valid) in xzs {
for (y, y_valid) in ys {
for (z, z_valid) in xzs {
let pos = BlockPos(glm::vec3(x, y, z));
if x_valid && y_valid && z_valid {
pos.encode(&mut &mut buf[..]).unwrap();
assert_eq!(BlockPos::decode(&mut &buf[..]).unwrap(), pos);
} else {
assert!(pos.encode(&mut &mut buf[..]).is_err());
}
}
}
}
}
}

32
src/byte_angle.rs Normal file
View file

@ -0,0 +1,32 @@
// TODO: rename to ByteAngle?
use std::f64::consts::TAU;
use std::io::{Read, Write};
use crate::protocol::{Decode, Encode};
/// Represents an angle in steps of 1/256 of a full turn.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ByteAngle(pub u8);
impl ByteAngle {
pub fn from_radians_f64(f: f64) -> ByteAngle {
ByteAngle((f.rem_euclid(TAU) / TAU * 256.0).round() as u8)
}
pub fn to_radians_f64(self) -> f64 {
self.0 as f64 / 256.0 * TAU
}
}
impl Encode for ByteAngle {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.0.encode(w)
}
}
impl Decode for ByteAngle {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(ByteAngle(u8::decode(r)?))
}
}

359
src/chunk.rs Normal file
View file

@ -0,0 +1,359 @@
// TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil
use std::io::Write;
use bitvec::bitvec;
use bitvec::vec::BitVec;
use num::Integer;
use crate::glm::DVec2;
use crate::packets::play::{
BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange,
};
use crate::protocol::{Encode, Nbt};
use crate::var_int::VarInt;
use crate::BiomeId;
pub struct Chunk {
sections: Box<[ChunkSection]>,
// TODO block_entities: HashMap<u32, BlockEntity>,
heightmap: Vec<i64>,
modified: bool,
created_this_tick: bool,
}
impl Chunk {
pub(crate) fn new(num_sections: usize) -> Self {
let sect = ChunkSection {
blocks: [0; 4096],
biomes: [0; 64],
compact_data: Vec::new(),
modified: true,
};
let mut chunk = Self {
sections: vec![sect; num_sections].into(),
heightmap: Vec::new(),
modified: true,
created_this_tick: true,
};
chunk.apply_modifications();
chunk
}
pub(crate) fn deallocate(&mut self) {
self.sections = Box::new([]);
}
pub fn created_this_tick(&self) -> bool {
self.created_this_tick
}
pub(crate) fn clear_created_this_tick(&mut self) {
self.created_this_tick = false;
}
pub fn height(&self) -> usize {
self.sections.len() * 16
}
pub fn get_block_state(&self, x: usize, y: usize, z: usize) -> u16 {
if x < 16 && y < self.height() && z < 16 {
self.sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16]
} else {
0
}
}
pub fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: u16) {
if x < 16 && y < self.height() && z < 16 {
let sec = &mut self.sections[y / 16];
let idx = x + z * 16 + y % 16 * 16 * 16;
if block != sec.blocks[idx] {
sec.blocks[idx] = block;
// TODO: set the modified bit.
sec.modified = true;
self.modified = true;
// TODO: update block entity if b could have block entity data.
}
}
}
pub fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId {
if x < 4 && y < self.height() / 4 && z < 4 {
BiomeId(self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4])
} else {
BiomeId::default()
}
}
pub fn set_biome(&mut self, x: usize, y: usize, z: usize, b: BiomeId) {
if x < 4 && y < self.height() / 4 && z < 4 {
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = b.0;
}
}
/// Gets the chunk data packet with the given number of sections for this
/// chunk. This does not include unapplied changes.
pub(crate) fn chunk_data_packet(
&self,
pos: ChunkPos,
num_sections: usize,
) -> ChunkDataAndUpdateLight {
let mut blocks_and_biomes = Vec::new();
for i in 0..num_sections {
match self.sections.get(i) {
Some(sect) => {
blocks_and_biomes.extend_from_slice(&sect.compact_data);
}
None => {
// Extra chunk sections are encoded as empty with the default biome.
// non air block count
0i16.encode(&mut blocks_and_biomes).unwrap();
// blocks
encode_paletted_container_single(0, &mut blocks_and_biomes).unwrap();
// biomes
encode_paletted_container_single(0, &mut blocks_and_biomes).unwrap();
}
}
}
let motion_blocking = if num_sections == self.sections.len() {
self.heightmap.clone()
} else {
// This is bad for two reasons:
// - Rebuilding the heightmap from scratch is slow.
// - The new heightmap is subtly wrong because we are building it from blocks
// with the modifications applied already.
// But you shouldn't be using chunks with heights different than the dimensions
// they're residing in anyway, so whatever.
let mut heightmap = Vec::new();
build_heightmap(&self.sections, &mut heightmap);
heightmap
};
ChunkDataAndUpdateLight {
chunk_x: pos.x,
chunk_z: pos.z,
heightmaps: Nbt(ChunkDataHeightmaps { motion_blocking }),
blocks_and_biomes,
block_entities: Vec::new(), // TODO
trust_edges: true,
sky_light_mask: bitvec![u64, _; 1; num_sections + 2],
block_light_mask: BitVec::new(),
empty_sky_light_mask: BitVec::new(),
empty_block_light_mask: BitVec::new(),
sky_light_arrays: vec![[0xff; 2048]; num_sections + 2],
block_light_arrays: Vec::new(),
}
}
/// Gets the unapplied changes to this chunk as a block change packet.
pub(crate) fn block_change_packet(&self, pos: ChunkPos) -> Option<BlockChangePacket> {
if !self.modified {
return None;
}
// TODO
None
}
pub(crate) fn apply_modifications(&mut self) {
if self.modified {
self.modified = false;
for sect in self.sections.iter_mut() {
if sect.modified {
sect.modified = false;
sect.compact_data.clear();
// TODO: consider cave_air and void_air.
let non_air_block_count = sect.blocks.iter().filter(|&&b| b != 0).count();
(non_air_block_count as i16)
.encode(&mut sect.compact_data)
.unwrap();
encode_paletted_container(&sect.blocks, 4, 9, 15, &mut sect.compact_data)
.unwrap();
// TODO: The direct bits per idx changes depending on the number of biomes in
// the biome registry.
encode_paletted_container(&sect.biomes, 0, 4, 6, &mut sect.compact_data)
.unwrap();
}
}
build_heightmap(&self.sections, &mut self.heightmap);
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum BlockChangePacket {
Single(BlockChange),
Multi(MultiBlockChange),
}
impl From<BlockChangePacket> for ClientPlayPacket {
fn from(p: BlockChangePacket) -> Self {
match p {
BlockChangePacket::Single(p) => p.into(),
BlockChangePacket::Multi(p) => p.into(),
}
}
}
/// The X and Z position of a chunk in a world.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ChunkPos {
/// The X position of the chunk.
pub x: i32,
/// The Z position of the chunk.
pub z: i32,
}
impl ChunkPos {
pub const fn new(x: i32, z: i32) -> Self {
Self { x, z }
}
/// Returns the chunk position of the chunk that the given coordinates are
/// contained within.
pub fn from_xz(xz: DVec2) -> Self {
Self {
x: (xz.x / 16.0) as i32,
z: (xz.y / 16.0) as i32,
}
}
}
impl From<(i32, i32)> for ChunkPos {
fn from((x, z): (i32, i32)) -> Self {
ChunkPos { x, z }
}
}
/// A 16x16x16 section of blocks, biomes, and light in a chunk.
#[derive(Clone)]
struct ChunkSection {
/// The blocks in this section, stored in x, z, y order.
blocks: [u16; 4096],
biomes: [u16; 64],
compact_data: Vec<u8>,
/// If the blocks or biomes were modified.
modified: bool,
}
/// Builds the MOTION_BLOCKING heightmap.
fn build_heightmap(sections: &[ChunkSection], heightmap: &mut Vec<i64>) {
let height = sections.len() * 16;
let bits_per_val = log2_ceil(height);
let vals_per_u64 = 64 / bits_per_val;
let num_u64s = Integer::div_ceil(&256, &vals_per_u64);
heightmap.clear();
heightmap.resize(num_u64s, 0);
for x in 0..16 {
for z in 0..16 {
for y in (0..height).rev() {
// TODO: is_solid || is_fluid heuristic for motion blocking.
if sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16] != 0 {
let column_height = y as u64;
let i = x * 16 + z; // TODO: X or Z major?
heightmap[i / vals_per_u64] |=
(column_height << (i % vals_per_u64 * bits_per_val)) as i64;
break;
}
}
}
}
}
fn encode_paletted_container(
entries: &[u16],
min_bits_per_idx: usize,
direct_threshold: usize,
direct_bits_per_idx: usize,
w: &mut impl Write,
) -> anyhow::Result<()> {
let mut palette = Vec::new();
for &entry in entries {
if !palette.contains(&entry) {
palette.push(entry);
}
}
let bits_per_idx = log2_ceil(palette.len());
(bits_per_idx as u8).encode(w)?;
if bits_per_idx == 0 {
// Single value case
debug_assert_eq!(palette.len(), 1);
VarInt(palette[0] as i32).encode(w)?;
VarInt(0).encode(w)?; // data array length
} else if bits_per_idx >= direct_threshold {
// Direct case
// Skip the palette
let idxs_per_u64 = 64 / direct_bits_per_idx;
let num_u64s = Integer::div_ceil(&entries.len(), &idxs_per_u64);
VarInt(num_u64s as i32).encode(w)?;
for &entry in entries {
let mut val = 0u64;
for i in 0..idxs_per_u64 {
val |= (entry as u64) << (i * direct_bits_per_idx);
}
val.encode(w)?;
}
} else {
// Indirect case
VarInt(palette.len() as i32).encode(w)?;
for &val in &palette {
VarInt(val as i32).encode(w)?;
}
let bits_per_idx = bits_per_idx.max(min_bits_per_idx);
let idxs_per_u64 = 64 / bits_per_idx;
let num_u64s = Integer::div_ceil(&entries.len(), &idxs_per_u64);
VarInt(num_u64s as i32).encode(w)?;
for &entry in entries {
let palette_idx = palette
.iter()
.position(|&e| e == entry)
.expect("entry should be in the palette") as u64;
let mut val = 0u64;
for i in 0..idxs_per_u64 {
val |= palette_idx << (i * bits_per_idx);
}
val.encode(w)?;
}
}
Ok(())
}
/// Encode a paletted container where all values are the same.
fn encode_paletted_container_single(entry: u16, w: &mut impl Write) -> anyhow::Result<()> {
0u8.encode(w)?; // bits per idx
VarInt(entry as i32).encode(w)?; // single value
VarInt(0).encode(w)?; // data array length
Ok(())
}
/// Calculates the log base 2 rounded up.
fn log2_ceil(n: usize) -> usize {
n.next_power_of_two().trailing_zeros() as usize
}

220
src/chunk_store.rs Normal file
View file

@ -0,0 +1,220 @@
use std::iter::FusedIterator;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use crate::component::{
ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents,
ZippedComponentsRaw,
};
use crate::Chunk;
pub struct ChunkStore {
comps: ComponentStore<ChunkId>,
chunks: RwLock<Vec<Chunk>>,
}
impl ChunkStore {
pub(crate) fn new() -> Self {
Self {
comps: ComponentStore::new(),
chunks: RwLock::new(Vec::new()),
}
}
pub fn create(&mut self, height: usize) -> ChunkId {
assert!(height % 16 == 0, "chunk height must be a multiple of 16");
let id = self.comps.create_item();
let chunk = Chunk::new(height / 16);
let idx = id.0.idx as usize;
if idx >= self.chunks.get_mut().len() {
self.chunks.get_mut().push(chunk);
} else {
self.chunks.get_mut()[idx] = chunk;
}
id
}
pub fn delete(&mut self, chunk: ChunkId) -> bool {
if self.comps.delete_item(chunk) {
let idx = chunk.0.idx as usize;
self.chunks.get_mut()[idx].deallocate();
true
} else {
false
}
}
pub fn count(&self) -> usize {
self.comps.count()
}
pub fn is_valid(&self, chunk: ChunkId) -> bool {
self.comps.is_valid(chunk)
}
pub fn get<Z>(&self, z: Z, chunk: ChunkId) -> Option<Z::Item>
where
Z: ZippedComponents<Id = ChunkId>,
{
self.comps.get(z, chunk)
}
pub fn iter<'a, Z>(&'a self, z: Z) -> impl FusedIterator<Item = (ChunkId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = ChunkId> + 'a,
{
self.comps.iter(z)
}
pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator<Item = (ChunkId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = ChunkId> + 'a,
{
self.comps.par_iter(z)
}
pub fn ids(&self) -> impl FusedIterator<Item = ChunkId> + Clone + '_ {
self.comps.ids()
}
pub fn par_ids(&self) -> impl ParallelIterator<Item = ChunkId> + Clone + '_ {
self.comps.par_ids()
}
pub fn chunks(&self) -> Result<Chunks, Error> {
Ok(Chunks {
chunks: self.chunks.try_read().ok_or(Error::NoReadAccess)?,
})
}
pub fn chunks_mut(&self) -> Result<ChunksMut, Error> {
Ok(ChunksMut {
chunks: self.chunks.try_write().ok_or(Error::NoWriteAccess)?,
})
}
pub fn register_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.register_component::<C>();
}
pub fn unregister_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.unregister_component::<C>()
}
pub fn is_registered<C: 'static + Send + Sync + Default>(&self) -> bool {
self.comps.is_registered::<C>()
}
pub fn components<C: 'static + Send + Sync + Default>(
&self,
) -> Result<Components<C, ChunkId>, Error> {
self.comps.components::<C>()
}
pub fn components_mut<C: 'static + Send + Sync + Default>(
&self,
) -> Result<ComponentsMut<C, ChunkId>, Error> {
self.comps.components_mut::<C>()
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
pub struct ChunkId(pub(crate) IdData);
impl ChunkId {
/// The value of the default [`ChunkId`] which is always invalid.
pub const NULL: Self = Self(IdData::NULL);
}
impl IdRaw for ChunkId {
fn to_data(self) -> IdData {
self.0
}
fn from_data(id: IdData) -> Self {
Self(id)
}
}
impl Id for ChunkId {}
pub struct Chunks<'a> {
chunks: RwLockReadGuard<'a, Vec<Chunk>>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b Chunks<'a> {
type RawItem = &'b Chunk;
type RawIter = std::slice::Iter<'b, Chunk>;
type RawParIter = rayon::slice::Iter<'b, Chunk>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&self.chunks[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.chunks.iter()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.chunks.par_iter()
}
}
impl<'a, 'b> ZippedComponents for &'b Chunks<'a> {
type Id = ChunkId;
type Item = &'b Chunk;
}
pub struct ChunksMut<'a> {
chunks: RwLockWriteGuard<'a, Vec<Chunk>>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b ChunksMut<'a> {
type RawItem = &'b Chunk;
type RawIter = std::slice::Iter<'b, Chunk>;
type RawParIter = rayon::slice::Iter<'b, Chunk>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&self.chunks[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.chunks.iter()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.chunks.par_iter()
}
}
impl<'a, 'b> ZippedComponents for &'b ChunksMut<'a> {
type Id = ChunkId;
type Item = &'b Chunk;
}
impl<'a, 'b> ZippedComponentsRaw for &'b mut ChunksMut<'a> {
type RawItem = &'b mut Chunk;
type RawIter = std::slice::IterMut<'b, Chunk>;
type RawParIter = rayon::slice::IterMut<'b, Chunk>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&mut self.chunks[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.chunks.iter_mut()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.chunks.par_iter_mut()
}
}
impl<'a, 'b> ZippedComponents for &'b mut ChunksMut<'a> {
type Id = ChunkId;
type Item = &'b mut Chunk;
}

755
src/client.rs Normal file
View file

@ -0,0 +1,755 @@
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use flume::{Receiver, Sender, TrySendError};
use glm::DVec3;
use crate::block_pos::BlockPos;
use crate::chunk_store::ChunkId;
use crate::config::{
Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId,
};
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,
};
use crate::protocol::{BoundedInt, Nbt};
use crate::server::ServerPacketChannels;
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
use crate::var_int::VarInt;
use crate::world::WorldId;
use crate::{glm, ident, ChunkPos, EntityId, Server, SharedServer, Text, Ticks, LIBRARY_NAMESPACE};
pub struct MaybeClient(pub(crate) Option<Box<Client>>);
impl MaybeClient {
pub fn get(&self) -> Option<&Client> {
self.0.as_deref()
}
pub fn get_mut(&mut self) -> Option<&mut Client> {
self.0.as_deref_mut()
}
/// Drops the inner [`Client`]. Future calls to [`get`](MaybeClient::get)
/// and [`get_mut`](MaybeClient::get_mut) will return `None`.
///
/// If the client was still connected prior to calling this function, the
/// client is disconnected from the server without a displayed reason.
///
/// If the inner client was already dropped, this function has no effect.
pub fn clear(&mut self) {
self.0 = None;
}
pub fn is_disconnected(&self) -> bool {
self.get().map_or(true, |c| c.is_disconnected())
}
}
/// 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>,
/// The tick this client was created.
created_tick: Ticks,
username: String,
on_ground: bool,
old_position: DVec3,
new_position: DVec3,
/// Measured in degrees
yaw: f32,
/// Measured in degrees
pitch: f32,
/// If any of position, yaw, or pitch were modified by the
/// user this tick.
teleported_this_tick: bool,
/// Counts up as teleports are made.
teleport_id_counter: u32,
/// The number of pending client teleports that have yet to receive a
/// confirmation. Inbound client position packets are ignored while this
/// is nonzero.
pending_teleports: u32,
spawn_position: BlockPos,
spawn_position_yaw: f32,
/// If spawn_position or spawn_position_yaw were modified this tick.
modified_spawn_position: bool,
/// The world that this client was in at the end of the previous tick.
old_world: WorldId,
new_world: WorldId,
events: Vec<Event>,
/// The ID of the last keepalive sent.
last_keepalive_id: i64,
/// If the last sent keepalive got a response.
got_keepalive: bool,
old_max_view_distance: u8,
new_max_view_distance: u8,
/// Entities that were visible to this client at the end of the last tick.
/// This is used to determine what entity create/destroy packets should be
/// sent.
loaded_entities: HashSet<EntityId>,
hidden_entities: HashSet<EntityId>,
/// Loaded chunks and their positions.
loaded_chunks: HashMap<ChunkPos, ChunkId>,
old_game_mode: GameMode,
new_game_mode: GameMode,
settings: Option<Settings>,
// TODO: latency
// TODO: time, weather
}
impl Client {
pub(crate) fn new(
packet_channels: ServerPacketChannels,
username: String,
server: &Server,
) -> Self {
let (send, recv) = packet_channels;
Self {
shared: server.shared().clone(),
send: Some(send),
recv,
created_tick: server.current_tick(),
username,
on_ground: false,
old_position: DVec3::default(),
new_position: DVec3::default(),
yaw: 0.0,
pitch: 0.0,
teleported_this_tick: false,
teleport_id_counter: 0,
pending_teleports: 0,
spawn_position: BlockPos::default(),
spawn_position_yaw: 0.0,
modified_spawn_position: true,
new_world: WorldId::NULL,
old_world: WorldId::NULL,
events: Vec::new(),
last_keepalive_id: 0,
got_keepalive: true,
old_max_view_distance: 0,
new_max_view_distance: 16,
loaded_entities: HashSet::new(),
hidden_entities: HashSet::new(),
loaded_chunks: HashMap::new(),
old_game_mode: GameMode::Survival,
new_game_mode: GameMode::Survival,
settings: None,
}
}
pub fn created_tick(&self) -> Ticks {
self.created_tick
}
pub fn username(&self) -> &str {
&self.username
}
pub fn position(&self) -> DVec3 {
self.new_position
}
pub fn yaw(&self) -> f32 {
self.yaw
}
pub fn pitch(&self) -> f32 {
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;
if !self.teleported_this_tick {
self.teleported_this_tick = true;
self.pending_teleports = match self.pending_teleports.checked_add(1) {
Some(n) => n,
None => {
self.disconnect("Too many pending teleports");
return;
}
};
self.teleport_id_counter = self.teleport_id_counter.wrapping_add(1);
}
}
pub fn on_ground(&self) -> bool {
self.on_ground
}
/// Changes the point at which compasses point at.
pub fn set_spawn_position(&mut self, pos: impl Into<BlockPos>, yaw_degrees: f32) {
let pos = pos.into();
if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw {
self.spawn_position = pos;
self.spawn_position_yaw = yaw_degrees;
self.modified_spawn_position = true;
}
}
pub fn world(&self) -> WorldId {
self.new_world
}
pub fn set_world(&mut self, new_world: WorldId) {
self.new_world = new_world;
}
/// Attempts to enqueue a play packet to be sent to this client. The client
/// is disconnected if the clientbound packet buffer is full.
pub(crate) fn send_packet(&mut self, packet: impl Into<ClientPlayPacket>) {
send_packet(&mut self.send, packet);
}
pub fn disconnect(&mut self, reason: impl Into<Text>) {
if self.send.is_some() {
let txt = reason.into();
log::info!("disconnecting client '{}': \"{txt}\"", self.username);
self.send_packet(Disconnect { reason: txt });
self.send = None;
}
}
pub fn disconnect_no_reason(&mut self) {
if self.send.is_some() {
log::info!("disconnecting client '{}' (no reason)", self.username);
self.send = None;
}
}
pub fn is_disconnected(&self) -> bool {
self.send.is_none()
}
pub fn events(&self) -> &[Event] {
&self.events
}
pub fn max_view_distance(&self) -> u8 {
self.new_max_view_distance
}
/// The new view distance is clamped to `2..=32`.
pub fn set_max_view_distance(&mut self, dist: u8) {
self.new_max_view_distance = dist.clamp(2, 32);
}
pub fn settings(&self) -> Option<&Settings> {
self.settings.as_ref()
}
pub fn update(&mut self, client_eid: EntityId, server: &Server) {
self.events.clear();
if self.is_disconnected() {
return;
}
(0..self.recv.len()).for_each(|_| match self.recv.try_recv().unwrap() {
ServerPlayPacket::TeleportConfirm(p) => {
if self.pending_teleports == 0 {
self.disconnect("Unexpected teleport confirmation");
return;
}
let got = p.teleport_id.0 as u32;
let expected = self
.teleport_id_counter
.wrapping_sub(self.pending_teleports);
if got == expected {
self.pending_teleports -= 1;
} else {
self.disconnect(format!(
"Unexpected teleport ID (expected {expected}, got {got})"
));
}
}
ServerPlayPacket::QueryBlockNbt(_) => {}
ServerPlayPacket::SetDifficulty(_) => {}
ServerPlayPacket::ChatMessageServerbound(_) => {}
ServerPlayPacket::ClientStatus(_) => {}
ServerPlayPacket::ClientSettings(p) => {
let old = self.settings.replace(Settings {
locale: p.locale.0,
view_distance: p.view_distance.0,
chat_mode: p.chat_mode,
chat_colors: p.chat_colors,
main_hand: p.main_hand,
displayed_skin_parts: p.displayed_skin_parts,
allow_server_listings: p.allow_server_listings,
});
self.events.push(Event::SettingsChanged(old));
}
ServerPlayPacket::TabCompleteServerbound(_) => {}
ServerPlayPacket::ClickWindowButton(_) => {}
ServerPlayPacket::ClickWindow(_) => {}
ServerPlayPacket::CloseWindow(_) => {}
ServerPlayPacket::PluginMessageServerbound(_) => {}
ServerPlayPacket::EditBook(_) => {}
ServerPlayPacket::QueryEntityNbt(_) => {}
ServerPlayPacket::InteractEntity(_) => {}
ServerPlayPacket::GenerateStructure(_) => {}
ServerPlayPacket::KeepAliveServerbound(p) => {
if self.got_keepalive {
self.disconnect("Unexpected keepalive");
} else if p.id != self.last_keepalive_id {
self.disconnect(format!(
"Keepalive ids don't match (expected {}, got {})",
self.last_keepalive_id, p.id
));
} else {
self.got_keepalive = true;
}
}
ServerPlayPacket::LockDifficulty(_) => {}
ServerPlayPacket::PlayerPosition(p) => handle_movement_packet(
self,
glm::vec3(p.x, p.feet_y, p.z),
self.yaw,
self.pitch,
p.on_ground,
),
ServerPlayPacket::PlayerPositionAndRotation(p) => handle_movement_packet(
self,
glm::vec3(p.x, p.feet_y, p.z),
p.yaw,
p.pitch,
p.on_ground,
),
ServerPlayPacket::PlayerRotation(p) => {
handle_movement_packet(self, self.new_position, p.yaw, p.pitch, p.on_ground)
}
ServerPlayPacket::PlayerMovement(p) => {
handle_movement_packet(self, self.new_position, self.yaw, self.pitch, p.on_ground)
}
ServerPlayPacket::VehicleMoveServerbound(_) => {}
ServerPlayPacket::SteerBoat(_) => {}
ServerPlayPacket::PickItem(_) => {}
ServerPlayPacket::CraftRecipeRequest(_) => {}
ServerPlayPacket::PlayerAbilitiesServerbound(_) => {}
ServerPlayPacket::PlayerDigging(_) => {}
ServerPlayPacket::EntityAction(_) => {}
ServerPlayPacket::SteerVehicle(_) => {}
ServerPlayPacket::Pong(_) => {}
ServerPlayPacket::SetRecipeBookState(_) => {}
ServerPlayPacket::SetDisplayedRecipe(_) => {}
ServerPlayPacket::NameItem(_) => {}
ServerPlayPacket::ResourcePackStatus(_) => {}
ServerPlayPacket::AdvancementTab(_) => {}
ServerPlayPacket::SelectTrade(_) => {}
ServerPlayPacket::SetBeaconEffect(_) => {}
ServerPlayPacket::HeldItemChangeServerbound(_) => {}
ServerPlayPacket::UpdateCommandBlock(_) => {}
ServerPlayPacket::UpdateCommandBlockMinecart(_) => {}
ServerPlayPacket::CreativeInventoryAction(_) => {}
ServerPlayPacket::UpdateJigsawBlock(_) => {}
ServerPlayPacket::UpdateStructureBlock(_) => {}
ServerPlayPacket::UpdateSign(_) => {}
ServerPlayPacket::PlayerArmSwing(_) => {}
ServerPlayPacket::Spectate(_) => {}
ServerPlayPacket::PlayerBlockPlacement(_) => {}
ServerPlayPacket::UseItem(_) => {}
});
fn handle_movement_packet(
client: &mut Client,
new_position: DVec3,
new_yaw: f32,
new_pitch: f32,
new_on_ground: bool,
) {
if client.pending_teleports == 0 {
let event = Event::Movement {
position: client.new_position,
yaw_degrees: client.yaw,
pitch_degrees: client.pitch,
on_ground: client.on_ground,
};
client.new_position = new_position;
client.yaw = new_yaw;
client.pitch = new_pitch;
client.on_ground = new_on_ground;
client.events.push(event);
}
}
if let Some(send) = &self.send {
if send.is_disconnected() || self.recv.is_disconnected() {
self.send = None;
return;
}
}
let worlds = server.worlds.worlds().unwrap();
let world = server.worlds.get(&worlds, self.new_world);
let dim_id = world.map_or(DimensionId::default(), |w| w.dimension());
let dim = server.dimension(dim_id);
if self.created_tick == server.current_tick() {
// Send the join game packet and other initial packets. We defer this until now
// so that the user can set the client's location, game mode, etc.
self.send_packet(JoinGame {
entity_id: client_eid.to_network_id(),
is_hardcore: false,
gamemode: self.new_game_mode,
previous_gamemode: self.old_game_mode,
dimension_names: server
.dimensions()
.map(|(_, id)| ident!("{LIBRARY_NAMESPACE}:dimension_{}", id.0))
.collect(),
dimension_codec: Nbt(make_dimension_codec(server)),
dimension: Nbt(to_dimension_registry_item(dim)),
dimension_name: ident!("{LIBRARY_NAMESPACE}:dimension_{}", dim_id.0),
hashed_seed: 0,
max_players: VarInt(0),
view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)),
simulation_distance: VarInt(16),
reduced_debug_info: false,
enable_respawn_screen: false, // TODO
is_debug: false,
is_flat: false, // TODO
});
self.teleport(self.position(), self.yaw(), self.pitch());
}
if self.modified_spawn_position {
self.modified_spawn_position = false;
self.send_packet(SpawnPosition {
location: self.spawn_position,
angle: self.spawn_position_yaw,
})
}
// Update view distance fog on the client if necessary.
if self.old_max_view_distance != self.new_max_view_distance {
self.old_max_view_distance = self.new_max_view_distance;
if self.created_tick != server.current_tick() {
self.send_packet(UpdateViewDistance {
view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)),
})
}
}
// Check if it's time to send another keepalive.
if server.last_keepalive == server.tick_start() {
if self.got_keepalive {
let id = rand::random();
self.send_packet(KeepAliveClientbound { id });
self.last_keepalive_id = id;
self.got_keepalive = false;
} else {
self.disconnect("Timed out (no keepalive response)");
}
}
// Load, update, and unload chunks.
if self.old_world != self.new_world {
let old_dim = server
.worlds
.get(&worlds, self.old_world)
.map_or(DimensionId::default(), |w| w.dimension());
let new_dim = dim_id;
if old_dim != new_dim {
// Changing dimensions automatically unloads all chunks and
// entities.
self.loaded_chunks.clear();
self.loaded_entities.clear();
todo!("need to send respawn packet for new dimension");
}
self.old_world = self.new_world;
}
let view_dist = self
.settings
.as_ref()
.map_or(2, |s| s.view_distance)
.min(self.new_max_view_distance);
let chunks = server.chunks.chunks().unwrap();
let center = ChunkPos::from_xz(self.new_position.xz());
// Send the update view position packet if the client changes the chunk section
// they're in.
{
let old_section = self.old_position.map(|n| (n / 16.0) as i32);
let new_section = self.new_position.map(|n| (n / 16.0) as i32);
if old_section != new_section {
self.send_packet(UpdateViewPosition {
chunk_x: VarInt(new_section.x),
chunk_z: VarInt(new_section.z),
})
}
}
// Unload deleted chunks and those outside the view distance. Also update
// existing chunks.
self.loaded_chunks.retain(|&pos, &mut chunk_id| {
if let Some(chunk) = server.chunks.get(&chunks, chunk_id) {
// The cache stops chunk data packets from needing to be sent when a player is
// jumping between adjacent chunks.
let cache = 2;
if is_chunk_in_view_distance(center, pos, view_dist + cache) {
if let Some(pkt) = chunk.block_change_packet(pos) {
send_packet(&mut self.send, pkt);
}
true
} else {
send_packet(
&mut self.send,
UnloadChunk {
chunk_x: pos.x,
chunk_z: pos.z,
},
);
false
}
} else {
send_packet(
&mut self.send,
UnloadChunk {
chunk_x: pos.x,
chunk_z: pos.z,
},
);
false
}
});
// Load new chunks within the view distance
for pos in chunks_in_view_distance(center, view_dist) {
if let Entry::Vacant(ve) = self.loaded_chunks.entry(pos) {
if let Some(&chunk_id) = world.and_then(|w| w.chunks().get(&pos)) {
if let Some(chunk) = server.chunks.get(&chunks, chunk_id) {
ve.insert(chunk_id);
self.send_packet(chunk.chunk_data_packet(pos, (dim.height / 16) as usize));
if let Some(pkt) = chunk.block_change_packet(pos) {
self.send_packet(pkt);
}
}
}
}
}
// This is done after the chunks are loaded so that the "downloading terrain"
// screen is closed at the appropriate time.
if self.teleported_this_tick {
self.teleported_this_tick = false;
self.send_packet(PlayerPositionAndLook {
x: self.new_position.x,
y: self.new_position.y,
z: self.new_position.z,
yaw: self.yaw,
pitch: self.pitch,
flags: PlayerPositionAndLookFlags::new(false, false, false, false, false),
teleport_id: VarInt((self.teleport_id_counter - 1) as i32),
dismount_vehicle: false,
});
}
self.old_position = self.new_position;
}
}
impl Drop for Client {
fn drop(&mut self) {
log::trace!("Dropping client '{}'", self.username);
self.shared.dec_client_count();
}
}
#[derive(Debug)]
pub enum Event {
/// Settings were changed. The value in this variant is the previous client
/// settings.
SettingsChanged(Option<Settings>),
/// The client has moved. The values in this variant are the previous
/// position and look.
Movement {
position: DVec3,
yaw_degrees: f32,
pitch_degrees: f32,
on_ground: bool,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct Settings {
/// e.g. en_US
pub locale: String,
/// The client side render distance, in chunks.
///
/// The value is always in `2..=32`.
pub view_distance: u8,
pub chat_mode: ChatMode,
/// `true` if the client has chat colors enabled, `false` otherwise.
pub chat_colors: bool,
pub main_hand: MainHand,
pub displayed_skin_parts: DisplayedSkinParts,
pub allow_server_listings: bool,
}
pub use crate::packets::play::{ChatMode, DisplayedSkinParts, MainHand};
fn send_packet(send_opt: &mut Option<Sender<ClientPlayPacket>>, pkt: impl Into<ClientPlayPacket>) {
if let Some(send) = send_opt {
match send.try_send(pkt.into()) {
Err(TrySendError::Full(_)) => {
log::warn!("max outbound packet capacity reached for client");
*send_opt = None;
}
Err(TrySendError::Disconnected(_)) => {
*send_opt = None;
}
Ok(_) => {}
}
}
}
fn make_dimension_codec(server: &Server) -> DimensionCodec {
let mut dims = Vec::new();
for (dim, id) in server.dimensions() {
let id = id.0 as i32;
dims.push(DimensionTypeRegistryEntry {
name: ident!("{LIBRARY_NAMESPACE}:dimension_type_{id}"),
id,
element: to_dimension_registry_item(dim),
})
}
let mut biomes = Vec::new();
for (biome, id) in server.biomes() {
biomes.push(to_biome_registry_item(biome, id.0 as i32));
}
// The client needs a biome named "minecraft:plains" in the registry to
// connect. This is probably a bug.
//
// If the issue is resolved, just delete this block.
if !biomes.iter().any(|b| b.name == ident!("plains")) {
let biome = Biome::default();
assert_eq!(biome.name, ident!("plains"));
biomes.push(to_biome_registry_item(&biome, 0));
}
DimensionCodec {
dimension_type_registry: DimensionTypeRegistry {
typ: ident!("dimension_type"),
value: dims,
},
biome_registry: BiomeRegistry {
typ: ident!("worldgen/biome"),
value: biomes,
},
}
}
fn to_dimension_registry_item(dim: &Dimension) -> DimensionType {
DimensionType {
piglin_safe: true,
natural: dim.natural,
ambient_light: dim.ambient_light,
fixed_time: dim.fixed_time.map(|t| t as i64),
infiniburn: "#minecraft:infiniburn_overworld".into(),
respawn_anchor_works: true,
has_skylight: true,
bed_works: true,
effects: match dim.effects {
DimensionEffects::Overworld => ident!("overworld"),
DimensionEffects::TheNether => ident!("the_nether"),
DimensionEffects::TheEnd => ident!("the_end"),
},
has_raids: true,
min_y: dim.min_y,
height: dim.height,
logical_height: dim.height,
coordinate_scale: 1.0,
ultrawarm: false,
has_ceiling: false,
}
}
fn to_biome_registry_item(biome: &Biome, id: i32) -> BiomeRegistryBiome {
BiomeRegistryBiome {
name: biome.name.clone(),
id,
element: BiomeProperty {
precipitation: match biome.precipitation {
BiomePrecipitation::Rain => "rain",
BiomePrecipitation::Snow => "snow",
BiomePrecipitation::None => "none",
}
.into(),
depth: 0.125,
temperature: 0.8,
scale: 0.05,
downfall: 0.4,
category: "none".into(),
temperature_modifier: None,
effects: BiomeEffects {
sky_color: biome.sky_color as i32,
water_fog_color: biome.water_fog_color as i32,
fog_color: biome.fog_color as i32,
water_color: biome.water_color as i32,
foliage_color: biome.foliage_color.map(|x| x as i32),
grass_color: None,
grass_color_modifier: match biome.grass_color_modifier {
BiomeGrassColorModifier::Swamp => Some("swamp".into()),
BiomeGrassColorModifier::DarkForest => Some("dark_forest".into()),
BiomeGrassColorModifier::None => None,
},
music: biome.music.as_ref().map(|bm| BiomeMusic {
replace_current_music: bm.replace_current_music,
sound: bm.sound.clone(),
max_delay: bm.max_delay,
min_delay: bm.min_delay,
}),
ambient_sound: biome.ambient_sound.clone(),
additions_sound: biome.additions_sound.as_ref().map(|a| BiomeAdditionsSound {
sound: a.sound.clone(),
tick_chance: a.tick_chance,
}),
mood_sound: biome.mood_sound.as_ref().map(|m| BiomeMoodSound {
sound: m.sound.clone(),
tick_delay: m.tick_delay,
offset: m.offset,
block_search_extent: m.block_search_extent,
}),
},
particle: biome.particle.as_ref().map(|p| BiomeParticle {
probability: p.probability,
options: BiomeParticleOptions { typ: p.typ.clone() },
}),
},
}
}

307
src/codec.rs Normal file
View file

@ -0,0 +1,307 @@
use std::io::Read;
use std::time::Duration;
use aes::Aes128;
use anyhow::{bail, ensure, Context};
use cfb8::cipher::{AsyncStreamCipher, NewCipher};
use cfb8::Cfb8;
use flate2::bufread::{ZlibDecoder, ZlibEncoder};
use flate2::Compression;
use log::{log_enabled, Level};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::time::timeout;
use super::packets::{DecodePacket, EncodePacket};
use crate::protocol::{Decode, Encode, MAX_PACKET_SIZE};
use crate::var_int::VarInt;
pub struct Encoder<W> {
write: W,
buf: Vec<u8>,
compress_buf: Vec<u8>,
compression_threshold: Option<u32>,
cipher: Option<Cipher>,
timeout: Duration,
}
impl<W: AsyncWrite + Unpin> Encoder<W> {
pub fn new(write: W, timeout: Duration) -> Self {
Self {
write,
buf: Vec::new(),
compress_buf: Vec::new(),
compression_threshold: None,
cipher: None,
timeout,
}
}
pub async fn write_packet(&mut self, packet: &impl EncodePacket) -> anyhow::Result<()> {
timeout(self.timeout, self.write_packet_impl(packet)).await?
}
async fn write_packet_impl(&mut self, packet: &impl EncodePacket) -> anyhow::Result<()> {
self.buf.clear();
packet.encode_packet(&mut self.buf)?;
let data_len = self.buf.len();
ensure!(data_len <= i32::MAX as usize, "bad packet data length");
if let Some(threshold) = self.compression_threshold {
if data_len >= threshold as usize {
let mut z = ZlibEncoder::new(self.buf.as_slice(), Compression::best());
self.compress_buf.clear();
z.read_to_end(&mut self.compress_buf)?;
let data_len_len = VarInt(data_len as i32).written_size();
let packet_len = data_len_len + self.compress_buf.len();
ensure!(packet_len <= MAX_PACKET_SIZE as usize, "bad packet length");
self.buf.clear();
VarInt(packet_len as i32).encode(&mut self.buf)?;
VarInt(data_len as i32).encode(&mut self.buf)?;
self.buf.extend_from_slice(&self.compress_buf);
} else {
let packet_len = VarInt(0).written_size() + data_len;
ensure!(packet_len <= MAX_PACKET_SIZE as usize, "bad packet length");
self.buf.clear();
VarInt(packet_len as i32).encode(&mut self.buf)?;
VarInt(0).encode(&mut self.buf)?;
packet.encode_packet(&mut self.buf)?;
}
} else {
let packet_len = data_len;
ensure!(packet_len <= MAX_PACKET_SIZE as usize, "bad packet length");
self.buf.clear();
VarInt(packet_len as i32).encode(&mut self.buf)?;
packet.encode_packet(&mut self.buf)?;
}
if let Some(cipher) = &mut self.cipher {
cipher.encrypt(&mut self.buf);
}
self.write.write_all(&self.buf).await?;
Ok(())
}
pub fn enable_encryption(&mut self, key: &[u8; 16]) {
self.cipher = Some(NewCipher::new(key.into(), key.into()));
}
pub fn enable_compression(&mut self, threshold: u32) {
self.compression_threshold = Some(threshold);
}
}
pub struct Decoder<R> {
read: R,
buf: Vec<u8>,
decompress_buf: Vec<u8>,
compression_threshold: Option<u32>,
cipher: Option<Cipher>,
timeout: Duration,
}
impl<R: AsyncRead + Unpin> Decoder<R> {
pub fn new(read: R, timeout: Duration) -> Self {
Self {
read,
buf: Vec::new(),
decompress_buf: Vec::new(),
compression_threshold: None,
cipher: None,
timeout,
}
}
pub async fn read_packet<P: DecodePacket>(&mut self) -> anyhow::Result<P> {
timeout(self.timeout, self.read_packet_impl()).await?
}
async fn read_packet_impl<P: DecodePacket>(&mut self) -> anyhow::Result<P> {
let packet_len = self
.read_var_int_async()
.await
.context("reading packet length")?;
ensure!(
(0..=MAX_PACKET_SIZE).contains(&packet_len),
"invalid packet length of {packet_len}."
);
self.buf.resize(packet_len as usize, 0);
self.read
.read_exact(&mut self.buf)
.await
.context("reading packet body")?;
if let Some(cipher) = &mut self.cipher {
cipher.decrypt(&mut self.buf);
}
let mut packet_contents = self.buf.as_slice();
// Compression enabled?
let packet = if self.compression_threshold.is_some() {
// The length of the packet data once uncompressed (zero indicates no
// compression).
let data_len = VarInt::decode(&mut packet_contents)
.context("reading data length (once uncompressed)")?
.0;
ensure!(
(0..=MAX_PACKET_SIZE).contains(&data_len),
"invalid packet data length of {data_len}."
);
if data_len != 0 {
let mut z = ZlibDecoder::new(&mut packet_contents);
self.decompress_buf.resize(data_len as usize, 0);
z.read_exact(&mut self.decompress_buf)
.context("uncompressing packet body")?;
let mut uncompressed = self.decompress_buf.as_slice();
let packet = P::decode_packet(&mut uncompressed)
.context("decoding packet after uncompressing")?;
ensure!(
uncompressed.is_empty(),
"packet contents were not read completely"
);
packet
} else {
P::decode_packet(&mut packet_contents).context("decoding packet")?
}
} else {
P::decode_packet(&mut packet_contents).context("decoding packet")?
};
if !packet_contents.is_empty() {
if log_enabled!(Level::Debug) {
log::debug!("complete packet after partial decode: {packet:?}");
}
bail!(
"packet contents were not decoded completely ({} bytes remaining)",
packet_contents.len()
);
}
Ok(packet)
}
async fn read_var_int_async(&mut self) -> anyhow::Result<i32> {
let mut val = 0;
for i in 0..VarInt::MAX_SIZE {
let array = &mut [self.read.read_u8().await?];
if let Some(cipher) = &mut self.cipher {
cipher.decrypt(array);
}
let [byte] = *array;
val |= (byte as i32 & 0b01111111) << (i * 7);
if byte & 0b10000000 == 0 {
return Ok(val);
}
}
bail!("var int is too large")
}
pub fn enable_encryption(&mut self, key: &[u8; 16]) {
self.cipher = Some(NewCipher::new(key.into(), key.into()));
}
pub fn enable_compression(&mut self, threshold: u32) {
self.compression_threshold = Some(threshold);
}
}
/// The AES block cipher with a 128 bit key, using the CFB-8 mode of
/// operation.
type Cipher = Cfb8<Aes128>;
#[cfg(test)]
mod tests {
use std::net::SocketAddr;
use std::time::Duration;
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::oneshot;
use super::*;
use crate::packets::test::TestPacket;
#[tokio::test]
async fn encode_decode() {
encode_decode_impl().await
}
const CRYPT_KEY: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
const TIMEOUT: Duration = Duration::from_secs(3);
async fn encode_decode_impl() {
let (tx, rx) = oneshot::channel();
let t = tokio::spawn(listen(tx));
let stream = TcpStream::connect(rx.await.unwrap()).await.unwrap();
let mut encoder = Encoder::new(stream, TIMEOUT);
send_test_packet(&mut encoder).await;
encoder.enable_compression(10);
send_test_packet(&mut encoder).await;
encoder.enable_encryption(&CRYPT_KEY);
send_test_packet(&mut encoder).await;
send_test_packet(&mut encoder).await;
send_test_packet(&mut encoder).await;
t.await.unwrap()
}
async fn listen(local_addr: oneshot::Sender<SocketAddr>) {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
local_addr.send(listener.local_addr().unwrap()).unwrap();
let stream = listener.accept().await.unwrap().0;
let mut decoder = Decoder::new(stream, TIMEOUT);
recv_test_packet(&mut decoder).await;
decoder.enable_compression(10);
recv_test_packet(&mut decoder).await;
decoder.enable_encryption(&CRYPT_KEY);
recv_test_packet(&mut decoder).await;
recv_test_packet(&mut decoder).await;
recv_test_packet(&mut decoder).await;
}
async fn send_test_packet(w: &mut Encoder<TcpStream>) {
w.write_packet(&TestPacket {
first: "abcdefghijklmnopqrstuvwxyz".to_string().into(),
second: vec![0x1234, 0xabcd],
third: 0x1122334455667788,
})
.await
.unwrap();
}
async fn recv_test_packet(r: &mut Decoder<TcpStream>) {
let TestPacket {
first,
second,
third,
} = r.read_packet().await.unwrap();
assert_eq!(&first, "abcdefghijklmnopqrstuvwxyz");
assert_eq!(&second, &[0x1234, 0xabcd]);
assert_eq!(third, 0x1122334455667788);
}
}

550
src/component.rs Normal file
View file

@ -0,0 +1,550 @@
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 + Default>(&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);
ve.insert(Box::new(RwLock::new(vec)));
}
}
pub fn unregister_component<C: 'static + Send + Sync + Default>(&mut self) {
self.components.remove(&TypeId::of::<C>());
}
pub fn is_registered<C: 'static + Send + Sync + Default>(&self) -> bool {
self.components.contains_key(&TypeId::of::<C>())
}
pub fn components<C: 'static + Send + Sync + Default>(
&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 + Default>(
&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 + Default> ComponentVec for RwLock<Vec<T>> {
fn push_default(&mut self) {
self.get_mut().push(T::default());
}
fn clear_at(&mut self, idx: usize) {
self.get_mut()[idx] = T::default();
}
fn as_any(&self) -> &dyn Any {
self as _
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self as _
}
}
pub struct Components<'a, C: 'static + Send + Sync + Default, I: Id> {
handle: RwLockReadGuard<'a, Vec<C>>,
_marker: PhantomData<fn(I) -> I>,
}
impl<'a, 'b, C: 'static + Send + Sync + Default, 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 + Default, I: Id> ZippedComponents
for &'b Components<'a, C, I>
{
type Id = I;
type Item = &'b C;
}
pub struct ComponentsMut<'a, C: 'static + Send + Sync + Default, I: Id> {
handle: RwLockWriteGuard<'a, Vec<C>>,
_marker: PhantomData<fn(I) -> I>,
}
impl<'a, 'b, C: 'static + Send + Sync + Default, 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 + Default, I: Id> ZippedComponents
for &'b ComponentsMut<'a, C, I>
{
type Id = I;
type Item = &'b C;
}
impl<'a, 'b, C: 'static + Send + Sync + Default, 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 + Default, I: Id> ZippedComponents
for &'b mut ComponentsMut<'a, C, I>
{
type Id = I;
type Item = &'b mut C;
}
#[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::*;
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);

522
src/config.rs Normal file
View file

@ -0,0 +1,522 @@
// TODO: rate limit, view distance?
use std::any::Any;
use std::collections::HashSet;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
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};
/// 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.
///
/// The default is `127.0.0.1:25565`.
pub fn address(&mut self, addr: impl Into<SocketAddr>) {
self.address = addr.into();
}
/// Sets 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;
}
/// Sets the state of "online mode", which determines if client
/// authentication and encryption should occur.
///
/// When online mode is disabled, malicious clients can give themselves any
/// username and UUID they want, potentially gaining privileges they
/// might not otherwise have. Additionally, encryption is only enabled in
/// online mode. For these reasons online mode should only be disabled
/// 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;
}
/// 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.
///
/// 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.
///
/// The capacity must be nonzero.
pub fn clientbound_packet_capacity(&mut self, cap: usize) {
self.clientbound_packet_capacity = cap;
}
/// Sets the capacity of the buffer used to hold serverbound 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.
///
/// The capacity must be nonzero.
pub fn serverbound_packet_capacity(&mut self, cap: usize) {
self.serverbound_packet_capacity = cap;
}
/// Sets the 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);
}
/// 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`]
/// 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)
}
/// Adds a new biome to the server which is identified by the returned
/// [`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.
///
/// 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.
///
/// # Default Implementation
/// The default implementation does nothing.
fn init(&self, server: &mut Server) {}
/// Called once at the beginning of every server update (also known as
/// a "tick").
///
/// The frequency of server updates can be configured by `update_duration`
/// in [`ServerConfig`].
///
/// # 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.
///
/// # Default Implementation
/// A placeholder response is returned.
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()
}
/// Called asynchronously for each client after successful authentication
/// (if online mode is enabled) to determine if they are allowed to join the
/// server. On success, a client-backed entity is spawned.
///
/// This function is the appropriate place to perform
/// whitelist checks, database queries, etc.
///
/// # Default Implementation
/// The client is allowed to join unconditionally.
async fn login(&self, server: &SharedServer, ncd: &NewClientData) -> Login {
Login::Join
}
}
/// The result of the [`server_list_ping`](Handler::server_list_ping) callback.
pub enum ServerListPing {
/// Responds to the server list ping with the given information.
Respond {
online_players: i32,
max_players: i32,
description: Text,
/// The server's icon as the bytes of a PNG image.
/// The image must be 64x64 pixels.
///
/// No icon is used if the value is `None`.
favicon_png: Option<Arc<[u8]>>,
},
/// Ignores the query and disconnects from the client.
Ignore,
}
/// The result of the [`login`](Handler::login) callback.
#[derive(Debug)]
pub enum Login {
/// The client may join the server.
Join,
/// The client may not join the server and will be disconnected with the
/// provided reason.
Disconnect(Text),
}
/// Identifies a particular [`Dimension`].
///
/// Dimension IDs are always valid and are cheap to copy and store.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
pub struct DimensionId(pub(crate) u16);
/// Contains the configuration for a custom 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
/// dimension type.
#[derive(Clone, Debug)]
pub struct Dimension {
/// When false, compases will spin randomly.
pub natural: bool,
/// Must be between 0.0 and 1.0.
pub ambient_light: f32,
/// Must be between 0 and 24000.
pub fixed_time: Option<u16>,
/// Determines what skybox/fog effects to use.
pub effects: DimensionEffects,
/// The minimum height in which blocks can exist in this dimension.
///
/// `min_y` must meet the following conditions:
/// * `min_y % 16 == 0`
/// * `-2032 <= min_y <= 2016`
pub min_y: i32,
/// The total height in which blocks can exist in this dimension.
///
/// `height` must meet the following conditions:
/// * `height % 16 == 0`
/// * `0 <= height <= 4064`
/// * `min_y + height <= 2032`
pub height: i32,
// TODO: The following fields should be added if they can affect the
// appearance of the dimension to clients.
// * infiniburn
// * respawn_anchor_works
// * has_skylight
// * bed_works
// * has_raids
// * logical_height
// * coordinate_scale
// * ultrawarm
// * has_ceiling
}
impl Default for Dimension {
fn default() -> Self {
Self {
natural: true,
ambient_light: 0.0,
fixed_time: None,
effects: DimensionEffects::Overworld,
min_y: -64,
height: 384,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DimensionEffects {
Overworld,
TheNether,
TheEnd,
}
/// Identifies a particular [`Biome`].
///
/// Biome IDs are always valid and are cheap to copy and store.
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct BiomeId(pub(crate) u16);
/// Contains the configuration for a custom biome.
#[derive(Clone, Debug)]
pub struct Biome {
/// The unique name for this biome. The name can be
/// seen in the F3 debug menu.
pub name: Identifier,
pub precipitation: BiomePrecipitation,
pub sky_color: u32,
pub water_fog_color: u32,
pub fog_color: u32,
pub water_color: u32,
pub foliage_color: Option<u32>,
pub grass_color_modifier: BiomeGrassColorModifier,
pub music: Option<BiomeMusic>,
pub ambient_sound: Option<Identifier>,
pub additions_sound: Option<BiomeAdditionsSound>,
pub mood_sound: Option<BiomeMoodSound>,
pub particle: Option<BiomeParticle>,
// TODO: The following fields should be added if they can affect the appearance of the biome to
// clients.
// * depth: f32
// * temperature: f32
// * scale: f32
// * downfall: f32
// * category
// * temperature_modifier
// * grass_color (misleading name?)
}
impl Default for Biome {
fn default() -> Self {
Self {
name: ident!("plains"),
precipitation: BiomePrecipitation::Rain,
sky_color: 7907327,
water_fog_color: 329011,
fog_color: 12638463,
water_color: 4159204,
foliage_color: None,
grass_color_modifier: BiomeGrassColorModifier::None,
music: None,
ambient_sound: None,
additions_sound: None,
mood_sound: None,
particle: None,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BiomePrecipitation {
Rain,
Snow,
None,
}
impl Default for BiomePrecipitation {
fn default() -> Self {
Self::Rain
}
}
/// Minecraft handles grass colors for swamps and dark oak forests in a special
/// way.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BiomeGrassColorModifier {
Swamp,
DarkForest,
None,
}
impl Default for BiomeGrassColorModifier {
fn default() -> Self {
Self::None
}
}
#[derive(Clone, Debug)]
pub struct BiomeMusic {
pub replace_current_music: bool,
pub sound: Identifier,
pub min_delay: i32,
pub max_delay: i32,
}
#[derive(Clone, Debug)]
pub struct BiomeAdditionsSound {
pub sound: Identifier,
pub tick_chance: f64,
}
#[derive(Clone, Debug)]
pub struct BiomeMoodSound {
pub sound: Identifier,
pub tick_delay: i32,
pub offset: f64,
pub block_search_extent: i32,
}
#[derive(Clone, Debug)]
pub struct BiomeParticle {
pub probability: f32,
pub typ: Identifier,
}

641
src/entity.rs Normal file
View file

@ -0,0 +1,641 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::iter::FusedIterator;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use uuid::Uuid;
use crate::chunk::ChunkPos;
use crate::client::MaybeClient;
use crate::component::{
ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents,
ZippedComponentsRaw,
};
use crate::{Aabb, WorldId};
pub mod appearance;
pub use appearance::Appearance;
pub struct EntityStore {
comps: ComponentStore<EntityId>,
uuids: Vec<Uuid>,
clients: RwLock<Vec<MaybeClient>>,
appearances: RwLock<Vec<Appearance>>,
old_appearances: Vec<Appearance>,
uuid_to_entity: HashMap<Uuid, EntityId>,
/// Maps chunk positions to the set of all entities with bounding volumes
/// intersecting that chunk.
partition: HashMap<(WorldId, ChunkPos), Vec<EntityId>>,
}
impl EntityStore {
pub(crate) fn new() -> Self {
Self {
comps: ComponentStore::new(),
uuids: Vec::new(),
clients: RwLock::new(Vec::new()),
appearances: RwLock::new(Vec::new()),
old_appearances: Vec::new(),
uuid_to_entity: HashMap::new(),
partition: HashMap::new(),
}
}
/// Gets the [`EntityId`] of the entity with the given UUID in an efficient
/// manner.
///
/// Returns `None` if there is no entity with the provided UUID. Returns
/// `Some` otherwise.
pub fn with_uuid(&self, uuid: Uuid) -> Option<EntityId> {
self.uuid_to_entity.get(&uuid).cloned()
}
/// Spawns a new entity with the provided appearance. The new entity's
/// [`EntityId`] is returned.
pub fn create(&mut self, appearance: impl Into<Appearance>) -> EntityId {
let app = appearance.into();
loop {
let uuid = Uuid::from_bytes(rand::random());
if let Some(e) = self.create_with_uuid(app.clone(), uuid) {
return e;
}
}
}
/// Like [`create`](Entities::create), but requires specifying the new
/// entity's UUID. This is useful for deserialization.
///
/// The provided UUID must not conflict with an existing entity UUID. If it
/// does, `None` is returned and the entity is not spawned.
pub fn create_with_uuid(
&mut self,
appearance: impl Into<Appearance>,
uuid: Uuid,
) -> Option<EntityId> {
match self.uuid_to_entity.entry(uuid) {
Entry::Occupied(_) => None,
Entry::Vacant(ve) => {
let app = appearance.into();
let entity = self.comps.create_item();
ve.insert(entity);
if let (Some(aabb), Some(world)) = (app.aabb(), app.world()) {
self.partition_insert(entity, world, aabb);
}
let idx = entity.0.idx as usize;
if idx >= self.uuids.len() {
self.uuids.push(uuid);
self.clients.get_mut().push(MaybeClient(None));
self.appearances.get_mut().push(app.clone());
self.old_appearances.push(app);
} else {
self.uuids[idx] = uuid;
self.clients.get_mut()[idx].0 = None;
self.appearances.get_mut()[idx] = app.clone();
self.old_appearances[idx] = app;
}
Some(entity)
}
}
}
pub fn delete(&mut self, entity: EntityId) -> bool {
if self.comps.delete_item(entity) {
let idx = entity.0.idx as usize;
self.uuid_to_entity
.remove(&self.uuids[idx])
.expect("UUID should have been in UUID map");
self.clients.get_mut()[idx].0 = None;
let app = &self.appearances.get_mut()[idx];
if let (Some(aabb), Some(world)) = (app.aabb(), app.world()) {
self.partition_remove(entity, world, aabb);
}
true
} else {
false
}
}
/// Returns the number of live entities.
pub fn count(&self) -> usize {
self.comps.count()
}
pub fn is_valid(&self, entity: EntityId) -> bool {
self.comps.is_valid(entity)
}
pub fn get<Z>(&self, z: Z, entity: EntityId) -> Option<Z::Item>
where
Z: ZippedComponents<Id = EntityId>,
{
self.comps.get(z, entity)
}
pub fn iter<'a, Z>(&'a self, z: Z) -> impl FusedIterator<Item = (EntityId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = EntityId> + 'a,
{
self.comps.iter(z)
}
pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator<Item = (EntityId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = EntityId> + 'a,
{
self.comps.par_iter(z)
}
pub fn ids(&self) -> impl FusedIterator<Item = EntityId> + Clone + '_ {
self.comps.ids()
}
pub fn par_ids(&self) -> impl ParallelIterator<Item = EntityId> + Clone + '_ {
self.comps.par_ids()
}
pub fn uuids(&self) -> Uuids {
Uuids { uuids: &self.uuids }
}
pub fn clients(&self) -> Result<Clients, Error> {
Ok(Clients {
clients: self.clients.try_read().ok_or(Error::NoReadAccess)?,
})
}
pub fn clients_mut(&self) -> Result<ClientsMut, Error> {
Ok(ClientsMut {
clients: self.clients.try_write().ok_or(Error::NoWriteAccess)?,
})
}
pub fn appearances(&self) -> Result<Appearances, Error> {
Ok(Appearances {
appearances: self.appearances.try_read().ok_or(Error::NoReadAccess)?,
})
}
pub fn appearances_mut(&self) -> Result<AppearancesMut, Error> {
Ok(AppearancesMut {
appearances: self.appearances.try_write().ok_or(Error::NoWriteAccess)?,
})
}
pub fn old_appearances(&self) -> OldAppearances {
OldAppearances {
old_appearances: &self.old_appearances,
}
}
pub fn register_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.register_component::<C>();
}
pub fn unregister_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.unregister_component::<C>()
}
pub fn is_registered<C: 'static + Send + Sync + Default>(&self) -> bool {
self.comps.is_registered::<C>()
}
pub fn components<C: 'static + Send + Sync + Default>(
&self,
) -> Result<Components<C, EntityId>, Error> {
self.comps.components::<C>()
}
pub fn components_mut<C: 'static + Send + Sync + Default>(
&self,
) -> Result<ComponentsMut<C, EntityId>, Error> {
self.comps.components_mut::<C>()
}
fn partition_insert(&mut self, entity: EntityId, world: WorldId, aabb: Aabb<f64, 3>) {
let min_corner = ChunkPos::from_xz(aabb.min().xz());
let max_corner = ChunkPos::from_xz(aabb.max().xz());
for z in min_corner.z..=max_corner.z {
for x in min_corner.x..=max_corner.x {
self.partition_insert_at(entity, world, ChunkPos { x, z })
}
}
}
fn partition_insert_at(&mut self, entity: EntityId, world: WorldId, pos: ChunkPos) {
match self.partition.entry((world, pos)) {
Entry::Occupied(mut oe) => {
debug_assert!(
!oe.get_mut().contains(&entity),
"spatial partition: entity already present"
);
oe.get_mut().push(entity);
}
Entry::Vacant(ve) => {
ve.insert(vec![entity]);
}
}
}
fn partition_remove(&mut self, entity: EntityId, world: WorldId, aabb: Aabb<f64, 3>) {
let min_corner = ChunkPos::from_xz(aabb.min().xz());
let max_corner = ChunkPos::from_xz(aabb.max().xz());
for z in min_corner.z..=max_corner.z {
for x in min_corner.x..=max_corner.x {
self.partition_remove_at(entity, world, ChunkPos::new(x, z));
}
}
}
fn partition_remove_at(&mut self, entity: EntityId, world: WorldId, pos: ChunkPos) {
let errmsg = "spatial partition: entity removal failed";
match self.partition.entry((world, pos)) {
Entry::Occupied(mut oe) => {
let v = oe.get_mut();
let idx = v.iter().position(|e| *e == entity).expect(errmsg);
v.swap_remove(idx);
if v.is_empty() {
oe.remove();
}
}
Entry::Vacant(_) => panic!("{errmsg}"),
}
}
fn partition_modify(
&mut self,
entity: EntityId,
old_world: WorldId,
old_aabb: Aabb<f64, 3>,
new_world: WorldId,
new_aabb: Aabb<f64, 3>,
) {
if old_world != new_world {
self.partition_remove(entity, old_world, old_aabb);
self.partition_insert(entity, new_world, new_aabb);
} else {
let old_min_corner = ChunkPos::from_xz(old_aabb.min().xz());
let old_max_corner = ChunkPos::from_xz(old_aabb.max().xz());
let new_min_corner = ChunkPos::from_xz(new_aabb.min().xz());
let new_max_corner = ChunkPos::from_xz(new_aabb.max().xz());
for z in new_min_corner.z..=new_max_corner.z {
for x in new_min_corner.x..=new_max_corner.x {
if x < old_min_corner.x
|| x > old_max_corner.x
|| z < old_min_corner.z
|| z > old_max_corner.z
{
self.partition_insert_at(entity, old_world, ChunkPos::new(x, z));
}
}
}
for z in old_min_corner.z..=old_max_corner.z {
for x in old_min_corner.x..=old_max_corner.x {
if x < new_min_corner.x
|| x > new_max_corner.x
|| z < new_min_corner.z
|| z > new_max_corner.z
{
self.partition_remove_at(entity, old_world, ChunkPos::new(x, z))
}
}
}
}
}
/// Returns an iterator over all entities with bounding volumes intersecting
/// the given AABB in an arbitrary order.
pub fn intersecting_aabb(
&self,
world: WorldId,
aabb: Aabb<f64, 3>,
) -> impl FusedIterator<Item = EntityId> + '_ {
let min_corner = ChunkPos::from_xz(aabb.min().xz());
let max_corner = ChunkPos::from_xz(aabb.max().xz());
(min_corner.z..=max_corner.z).flat_map(move |z| {
(min_corner.x..=max_corner.x).flat_map(move |x| {
self.partition
.get(&(world, ChunkPos::new(x, z)))
.into_iter()
.flat_map(move |v| {
v.iter().cloned().filter(move |&e| {
self.get(&self.old_appearances(), e)
.expect("spatial partition contains expired entity")
.aabb()
.expect("spatial partition contains entity without AABB")
.collides_with_aabb(&aabb)
})
})
})
})
}
pub(crate) fn update_old_appearances(&mut self) {
for (old, new) in self
.old_appearances
.iter_mut()
.zip(self.appearances.get_mut().iter())
{
old.clone_from(new);
}
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
pub struct EntityId(IdData);
impl IdRaw for EntityId {
fn from_data(data: IdData) -> Self {
Self(data)
}
fn to_data(self) -> IdData {
self.0
}
}
impl EntityId {
/// The vaule of the default `EntityId` which always refers to an expired
/// entity.
pub const NULL: Self = Self(IdData::NULL);
pub(crate) fn to_network_id(self) -> i32 {
self.0.idx as i32
}
}
impl Id for EntityId {}
/// A built-in component collection containing the UUID of the entities.
///
/// The default value for this component is a random unassigned UUID. UUIDs
/// cannot be modified after an entity is created.
///
/// TODO: describe the UUID for players.
pub struct Uuids<'a> {
uuids: &'a Vec<Uuid>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b Uuids<'a> {
type RawItem = Uuid;
type RawIter = std::iter::Cloned<std::slice::Iter<'b, Uuid>>;
type RawParIter = rayon::iter::Cloned<rayon::slice::Iter<'b, Uuid>>;
fn raw_get(self, idx: usize) -> Self::RawItem {
self.uuids[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.uuids.iter().cloned()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.uuids.par_iter().cloned()
}
}
impl<'a, 'b> ZippedComponents for &'b Uuids<'a> {
type Id = EntityId;
type Item = Uuid;
}
/// A built-in component collection containing the clients that entites are
/// backed by, if any.
///
/// When a client joins the server, a new entity is created which is backed by
/// the new client. However, when a client is disconnected, the entity which
/// they inhabited is _not_ automatically deleted.
///
/// Deleting the associated entity while the client is still connected will
/// immediately disconnect the client.
///
/// The default value of this component will not contain a client and all calls
/// to [`get`](Self::get) and [`get_mut`](Self::get_mut) will return `None`.
pub struct Clients<'a> {
// TODO: box the clients
clients: RwLockReadGuard<'a, Vec<MaybeClient>>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b Clients<'a> {
type RawItem = &'b MaybeClient;
type RawIter = std::slice::Iter<'b, MaybeClient>;
type RawParIter = rayon::slice::Iter<'b, MaybeClient>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&self.clients[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.clients.iter()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.clients.par_iter()
}
}
impl<'a, 'b> ZippedComponents for &'b Clients<'a> {
type Id = EntityId;
type Item = &'b MaybeClient;
}
pub struct ClientsMut<'a> {
clients: RwLockWriteGuard<'a, Vec<MaybeClient>>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b ClientsMut<'a> {
type RawItem = &'b MaybeClient;
type RawIter = std::slice::Iter<'b, MaybeClient>;
type RawParIter = rayon::slice::Iter<'b, MaybeClient>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&self.clients[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.clients.iter()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.clients.par_iter()
}
}
impl<'a, 'b> ZippedComponents for &'b ClientsMut<'a> {
type Id = EntityId;
type Item = &'b MaybeClient;
}
impl<'a, 'b> ZippedComponentsRaw for &'b mut ClientsMut<'a> {
type RawItem = &'b mut MaybeClient;
type RawIter = std::slice::IterMut<'b, MaybeClient>;
type RawParIter = rayon::slice::IterMut<'b, MaybeClient>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&mut self.clients[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.clients.iter_mut()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.clients.par_iter_mut()
}
}
impl<'a, 'b> ZippedComponents for &'b mut ClientsMut<'a> {
type Id = EntityId;
type Item = &'b mut MaybeClient;
}
pub struct Appearances<'a> {
appearances: RwLockReadGuard<'a, Vec<Appearance>>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b Appearances<'a> {
type RawItem = &'b Appearance;
type RawIter = std::slice::Iter<'b, Appearance>;
type RawParIter = rayon::slice::Iter<'b, Appearance>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&self.appearances[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.appearances.iter()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.appearances.par_iter()
}
}
impl<'a, 'b> ZippedComponents for &'b Appearances<'a> {
type Id = EntityId;
type Item = &'b Appearance;
}
pub struct AppearancesMut<'a> {
appearances: RwLockWriteGuard<'a, Vec<Appearance>>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b AppearancesMut<'a> {
type RawItem = &'b Appearance;
type RawIter = std::slice::Iter<'b, Appearance>;
type RawParIter = rayon::slice::Iter<'b, Appearance>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&self.appearances[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.appearances.iter()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.appearances.par_iter()
}
}
impl<'a, 'b> ZippedComponents for &'b AppearancesMut<'a> {
type Id = EntityId;
type Item = &'b Appearance;
}
impl<'a, 'b> ZippedComponentsRaw for &'b mut AppearancesMut<'a> {
type RawItem = &'b mut Appearance;
type RawIter = std::slice::IterMut<'b, Appearance>;
type RawParIter = rayon::slice::IterMut<'b, Appearance>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&mut self.appearances[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.appearances.iter_mut()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.appearances.par_iter_mut()
}
}
impl<'a, 'b> ZippedComponents for &'b mut AppearancesMut<'a> {
type Id = EntityId;
type Item = &'b mut Appearance;
}
/// Contains a snapshot of an entity's [`Appearance`] as it existed at the end
/// of the previous tick.
pub struct OldAppearances<'a> {
old_appearances: &'a Vec<Appearance>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b OldAppearances<'a> {
type RawItem = &'b Appearance;
type RawIter = std::slice::Iter<'b, Appearance>;
type RawParIter = rayon::slice::Iter<'b, Appearance>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&self.old_appearances[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.old_appearances.iter()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.old_appearances.par_iter()
}
}
impl<'a, 'b> ZippedComponents for &'b OldAppearances<'a> {
type Id = EntityId;
type Item = &'b Appearance;
}
#[cfg(test)]
mod tests {
use appearance::Player;
use super::*;
use crate::glm;
// TODO: better test: spawn a bunch of random entities, spawn a random AABB,
// assert collides_with_aabb consistency.
#[test]
fn space_partition() {
let mut entities = EntityStore::new();
let ids = [(16.0, 16.0, 16.0), (8.0, 8.0, 8.0), (10.0, 50.0, 10.0)]
.into_iter()
.map(|(x, y, z)| entities.create(Player::new(glm::vec3(x, y, z), WorldId::NULL)))
.collect::<Vec<_>>();
let outside = *ids.last().unwrap();
assert!(entities
.intersecting_aabb(
WorldId::NULL,
Aabb::new(glm::vec3(8.0, 8.0, 8.0), glm::vec3(16.0, 16.0, 16.0)),
)
.all(|id| ids.contains(&id) && id != outside));
}
}

81
src/entity/appearance.rs Normal file
View file

@ -0,0 +1,81 @@
use glm::DVec3;
use crate::{glm, Aabb, WorldId};
// TODO: override default clone_from impl for Appearance.
/// Encodes the type of an entity (pig, player, item frame, etc.) along with any
/// state that would influence its appearance to clients.
#[derive(Clone, Debug)]
pub enum Appearance {
/// The default appearance.
///
/// Entities with an appearance of `None` will not be visible to clients.
None,
Player(Player),
}
impl Appearance {
pub fn position(&self) -> Option<DVec3> {
match self {
Appearance::None => None,
Appearance::Player(p) => Some(p.position),
}
}
pub fn position_mut(&mut self) -> Option<&mut DVec3> {
match self {
Appearance::None => None,
Appearance::Player(p) => Some(&mut p.position),
}
}
pub fn world(&self) -> Option<WorldId> {
match self {
Appearance::None => None,
Appearance::Player(p) => Some(p.world),
}
}
pub fn world_mut(&mut self) -> Option<&mut WorldId> {
match self {
Appearance::None => None,
Appearance::Player(p) => Some(&mut p.world),
}
}
pub fn aabb(&self) -> Option<Aabb<f64, 3>> {
match self {
Appearance::None => None,
Appearance::Player(p) => Some(p.aabb()),
}
}
}
impl Default for Appearance {
fn default() -> Self {
Self::None
}
}
#[derive(Clone, Debug)]
pub struct Player {
pub position: DVec3,
pub world: WorldId,
}
impl Player {
pub fn new(position: DVec3, world: WorldId) -> Self {
Self { position, world }
}
pub fn aabb(&self) -> Aabb<f64, 3> {
// TODO: player hitbox dimensions change depending on pose
Aabb::from_center_and_dimensions(self.position, glm::vec3(0.6, 1.8, 0.6))
}
}
impl From<Player> for Appearance {
fn from(p: Player) -> Self {
Self::Player(p)
}
}

288
src/identifier.rs Normal file
View file

@ -0,0 +1,288 @@
use std::borrow::Cow;
use std::io::{Read, Write};
use std::str::FromStr;
use ascii::{AsAsciiStr, AsciiChar, AsciiStr, IntoAsciiString};
use serde::de::Visitor;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::protocol::{encode_string_bounded, BoundedString, Decode, Encode};
/// An identifier is a string split into a "namespace" part and a "name" part.
/// For instance `minecraft:apple` and `apple` are both valid identifiers.
///
/// If the namespace part is left off (the part before and including the colon)
/// the namespace is considered to be "minecraft".
///
/// The entire identifier must match the regex `([a-z0-9_-]+:)?[a-z0-9_\/.-]+`.
#[derive(Clone, Eq)]
pub struct Identifier {
ident: Cow<'static, AsciiStr>,
/// The index of the ':' character in the string.
/// If there is no namespace then it is `usize::MAX`.
///
/// Since the string only contains ASCII characters, we can slice it
/// in O(1) time.
colon_idx: usize,
}
#[derive(Clone, Error, PartialEq, Eq, Debug)]
#[error("invalid identifier \"{src}\"")]
pub struct ParseError {
src: Cow<'static, str>,
}
impl Identifier {
/// Parses a new identifier from a string.
///
/// The string must match the regex `([a-z0-9_-]+:)?[a-z0-9_\/.-]+`.
/// If not, an error is returned.
pub fn new(str: impl Into<Cow<'static, str>>) -> Result<Identifier, ParseError> {
#![allow(bindings_with_variant_name)]
let cow = match str.into() {
Cow::Borrowed(s) => {
Cow::Borrowed(s.as_ascii_str().map_err(|_| ParseError { src: s.into() })?)
}
Cow::Owned(s) => Cow::Owned(s.into_ascii_string().map_err(|e| ParseError {
src: e.into_source().into(),
})?),
};
let s = cow.as_ref();
let check_namespace = |s: &AsciiStr| {
!s.is_empty()
&& s.chars()
.all(|c| matches!(c.as_char(), 'a'..='z' | '0'..='9' | '_' | '-'))
};
let check_name = |s: &AsciiStr| {
!s.is_empty()
&& s.chars()
.all(|c| matches!(c.as_char(), 'a'..='z' | '0'..='9' | '_' | '/' | '.' | '-'))
};
if let Some(colon_idx) = s.chars().position(|c| c == AsciiChar::Colon) {
if check_namespace(&s[..colon_idx]) && check_name(&s[colon_idx + 1..]) {
Ok(Self {
ident: cow,
colon_idx,
})
} else {
Err(ParseError {
src: ascii_cow_to_str_cow(cow),
})
}
} else if check_name(s) {
Ok(Self {
ident: cow,
colon_idx: usize::MAX,
})
} else {
Err(ParseError {
src: ascii_cow_to_str_cow(cow),
})
}
}
/// Returns the namespace part of this namespaced identifier.
/// If this identifier was constructed from a string without a namespace,
/// then `None` is returned.
pub fn namespace(&self) -> Option<&str> {
if self.colon_idx == usize::MAX {
None
} else {
Some(self.ident[..self.colon_idx].as_str())
}
}
/// Returns the name part of this namespaced identifier.
pub fn name(&self) -> &str {
if self.colon_idx == usize::MAX {
self.ident.as_str()
} else {
self.ident[self.colon_idx + 1..].as_str()
}
}
/// Returns the identifier as a `str`.
pub fn as_str(&self) -> &str {
self.ident.as_str()
}
}
fn ascii_cow_to_str_cow(cow: Cow<AsciiStr>) -> Cow<str> {
match cow {
Cow::Borrowed(s) => Cow::Borrowed(s.as_str()),
Cow::Owned(s) => Cow::Owned(s.into()),
}
}
impl ParseError {
pub fn into_source(self) -> Cow<'static, str> {
self.src
}
}
impl std::fmt::Debug for Identifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Identifier").field(&self.as_str()).finish()
}
}
impl FromStr for Identifier {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Identifier::new(s.to_string())
}
}
impl From<Identifier> for String {
fn from(id: Identifier) -> Self {
id.ident.into_owned().into()
}
}
impl From<Identifier> for Cow<'static, str> {
fn from(id: Identifier) -> Self {
ascii_cow_to_str_cow(id.ident)
}
}
impl AsRef<str> for Identifier {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl TryFrom<String> for Identifier {
type Error = ParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Identifier::new(value)
}
}
impl TryFrom<&'static str> for Identifier {
type Error = ParseError;
fn try_from(value: &'static str) -> Result<Self, Self::Error> {
Identifier::new(value)
}
}
impl std::fmt::Display for Identifier {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
/// Equality for identifiers respects the fact that "minecraft:apple" and
/// "apple" have the same meaning.
impl PartialEq for Identifier {
fn eq(&self, other: &Self) -> bool {
self.namespace().unwrap_or("minecraft") == other.namespace().unwrap_or("minecraft")
&& self.name() == other.name()
}
}
impl std::hash::Hash for Identifier {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.namespace().unwrap_or("minecraft").hash(state);
self.name().hash(state);
}
}
impl Encode for Identifier {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_string_bounded(self.as_str(), 0, 32767, w)
}
}
impl Decode for Identifier {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let string = BoundedString::<0, 32767>::decode(r)?.0;
Ok(Identifier::new(string)?)
}
}
impl Serialize for Identifier {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_str().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Identifier {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(IdentifierVisitor)
}
}
/// An implementation of `serde::de::Visitor` for Minecraft identifiers.
struct IdentifierVisitor;
impl<'de> Visitor<'de> for IdentifierVisitor {
type Value = Identifier;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "a valid Minecraft identifier")
}
fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
Identifier::from_str(s).map_err(E::custom)
}
fn visit_string<E: serde::de::Error>(self, s: String) -> Result<Self::Value, E> {
Identifier::new(s).map_err(E::custom)
}
}
/// Convenience macro for constructing an identifier from a format string.
///
/// The macro will panic if the formatted string is not a valid
/// identifier.
#[macro_export]
macro_rules! ident {
($($arg:tt)*) => {{
let errmsg = "invalid identifier in `ident` macro";
#[allow(clippy::redundant_closure_call)]
(|args: ::std::fmt::Arguments| match args.as_str() {
Some(s) => $crate::Identifier::new(s).expect(errmsg),
None => $crate::Identifier::new(args.to_string()).expect(errmsg),
})(format_args!($($arg)*))
}}
}
#[cfg(test)]
mod tests {
#[test]
fn parse_valid() {
ident!("minecraft:whatever");
ident!("_what-ever55_:.whatever/whatever123456789_");
}
#[test]
#[should_panic]
fn parse_invalid_0() {
ident!("");
}
#[test]
#[should_panic]
fn parse_invalid_1() {
ident!(":");
}
#[test]
#[should_panic]
fn parse_invalid_2() {
ident!("foo:bar:baz");
}
#[test]
fn equality() {
assert_eq!(ident!("minecraft:my.identifier"), ident!("my.identifier"));
}
}

View file

@ -0,0 +1,49 @@
#![forbid(unsafe_code)]
mod aabb;
mod block_pos;
mod byte_angle;
mod chunk;
mod chunk_store;
mod client;
mod codec;
pub mod component;
pub mod config;
pub mod entity;
pub mod identifier;
mod packets;
mod protocol;
mod server;
pub mod text;
pub mod util;
mod var_int;
mod var_long;
mod world;
pub use aabb::Aabb;
pub use chunk::{Chunk, ChunkPos};
pub use client::Client;
pub use config::{BiomeId, DimensionId, ServerConfig};
pub use entity::{EntityId, EntityStore};
pub use identifier::Identifier;
pub use text::{Text, TextFormat};
pub use uuid::Uuid;
pub use world::{World, WorldId};
pub use {nalgebra_glm as glm, nbt};
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.
pub const VERSION_NAME: &str = "1.18.2";
/// The namespace for this library used internally for namespaced identifiers.
const LIBRARY_NAMESPACE: &str = "valence";
/// A discrete unit of time where 1 tick is the duration of a
/// single game update.
///
/// The duration of a game update depends on the current configuration, which
/// may or may not be the same as Minecraft's standard 20 ticks/second.
pub type Ticks = i64;

1815
src/packets.rs Normal file

File diff suppressed because it is too large Load diff

548
src/protocol.rs Normal file
View file

@ -0,0 +1,548 @@
use std::io::{Read, Write};
use std::mem;
use anyhow::{anyhow, ensure};
use bitvec::prelude::*;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::var_int::VarInt;
/// Trait for types that can be written to the Minecraft protocol.
pub trait Encode {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()>;
}
/// Trait for types that can be constructed from the Minecraft protocol.
pub trait Decode: Sized {
fn decode(r: &mut impl Read) -> anyhow::Result<Self>;
}
/// The maximum number of bytes in a single packet.
pub const MAX_PACKET_SIZE: i32 = 2097151;
impl Encode for () {
fn encode(&self, _w: &mut impl Write) -> anyhow::Result<()> {
Ok(())
}
}
impl Decode for () {
fn decode(_r: &mut impl Read) -> anyhow::Result<Self> {
Ok(())
}
}
impl<T: Encode> Encode for &T {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
(*self).encode(w)
}
}
impl Encode for bool {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_u8(*self as u8)?;
Ok(())
}
}
impl Decode for bool {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let n = r.read_u8()?;
ensure!(n < 2, "boolean is not 0 or 1");
Ok(n == 1)
}
}
impl Encode for u8 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_u8(*self)?;
Ok(())
}
}
impl Decode for u8 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(r.read_u8()?)
}
}
impl Encode for i8 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_i8(*self)?;
Ok(())
}
}
impl Decode for i8 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(r.read_i8()?)
}
}
impl Encode for u16 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_u16::<BigEndian>(*self)?;
Ok(())
}
}
impl Decode for u16 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(r.read_u16::<BigEndian>()?)
}
}
impl Encode for i16 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_i16::<BigEndian>(*self)?;
Ok(())
}
}
impl Decode for i16 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(r.read_i16::<BigEndian>()?)
}
}
impl Encode for u32 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_u32::<BigEndian>(*self)?;
Ok(())
}
}
impl Decode for u32 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(r.read_u32::<BigEndian>()?)
}
}
impl Encode for i32 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_i32::<BigEndian>(*self)?;
Ok(())
}
}
impl Decode for i32 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(r.read_i32::<BigEndian>()?)
}
}
impl Encode for u64 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_u64::<BigEndian>(*self)?;
Ok(())
}
}
impl Decode for u64 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(r.read_u64::<BigEndian>()?)
}
}
impl Encode for i64 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_i64::<BigEndian>(*self)?;
Ok(())
}
}
impl Decode for i64 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(r.read_i64::<BigEndian>()?)
}
}
impl Encode for f32 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
ensure!(
self.is_finite(),
"attempt to encode non-finite f32 ({})",
self
);
w.write_f32::<BigEndian>(*self)?;
Ok(())
}
}
impl Decode for f32 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let f = r.read_f32::<BigEndian>()?;
ensure!(f.is_finite(), "attempt to decode non-finite f32 ({f})");
Ok(f)
}
}
impl Encode for f64 {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
ensure!(
self.is_finite(),
"attempt to encode non-finite f64 ({})",
self
);
w.write_f64::<BigEndian>(*self)?;
Ok(())
}
}
impl Decode for f64 {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let f = r.read_f64::<BigEndian>()?;
ensure!(f.is_finite(), "attempt to decode non-finite f64 ({f})");
Ok(f)
}
}
impl<T: Encode> Encode for Option<T> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
match self {
Some(t) => true.encode(w).and_then(|_| t.encode(w)),
None => false.encode(w),
}
}
}
impl<T: Decode> Decode for Option<T> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
if bool::decode(r)? {
Ok(Some(T::decode(r)?))
} else {
Ok(None)
}
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct BoundedInt<T, const MIN: i64, const MAX: i64>(pub T);
impl<T, const MIN: i64, const MAX: i64> From<T> for BoundedInt<T, MIN, MAX> {
fn from(t: T) -> Self {
Self(t)
}
}
impl<T, const MIN: i64, const MAX: i64> Encode for BoundedInt<T, MIN, MAX>
where
T: Encode + Copy + Into<i64>,
{
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
let val = self.0.into();
ensure!(
(MIN..=MAX).contains(&val),
"Integer is not in bounds while encoding (got {val}, expected {MIN}..={MAX})"
);
self.0.encode(w)
}
}
impl<T, const MIN: i64, const MAX: i64> Decode for BoundedInt<T, MIN, MAX>
where
T: Decode + Copy + Into<i64>,
{
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let res = T::decode(r)?;
let val = res.into();
ensure!(
(MIN..=MAX).contains(&val),
"Integer is not in bounds while decoding (got {val}, expected {MIN}..={MAX})"
);
Ok(Self(res))
}
}
// TODO: bounded float?
impl Encode for String {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_string_bounded(self, 0, 32767, w)
}
}
impl Decode for String {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
decode_string_bounded(0, 32767, r)
}
}
/// A string with a minimum and maximum character length known at compile time.
/// If the string is not in bounds, an error is generated while
/// encoding/decoding.
///
/// Note that the length is a count of the characters in the string, not bytes.
///
/// When encoded and decoded, the string is VarInt prefixed.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
pub struct BoundedString<const MIN: usize, const MAX: usize>(pub String);
impl<const MIN: usize, const MAX: usize> Encode for BoundedString<MIN, MAX> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_string_bounded(&self.0, MIN, MAX, w)
}
}
impl<const MIN: usize, const MAX: usize> Decode for BoundedString<MIN, MAX> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
decode_string_bounded(MIN, MAX, r).map(Self)
}
}
impl<const MIN: usize, const MAX: usize> From<String> for BoundedString<MIN, MAX> {
fn from(s: String) -> Self {
Self(s)
}
}
impl<T: Encode> Encode for Vec<T> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_array_bounded(self, 0, usize::MAX, w)
}
}
impl<T: Decode> Decode for Vec<T> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
decode_array_bounded(0, usize::MAX, r)
}
}
impl<T: Encode> Encode for Box<[T]> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_array_bounded(self, 0, usize::MAX, w)
}
}
impl<T: Decode> Decode for Box<[T]> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
decode_array_bounded(0, usize::MAX, r).map(|v| v.into_boxed_slice())
}
}
impl<T: Encode, const N: usize> Encode for [T; N] {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_array_bounded(self, N, N, w)
}
}
impl<T: Decode, const N: usize> Decode for [T; N] {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let vec = decode_array_bounded(N, N, r)?;
match vec.try_into() {
Ok(arr) => Ok(arr),
Err(_) => unreachable!("array size does not match"),
}
}
}
/// An array with a minimum and maximum character length known at compile time.
/// If the array is not in bounds, an error is generated while
/// encoding/decoding.
///
/// When encoding/decoding, the array is VarInt prefixed.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
pub struct BoundedArray<T, const MIN: usize = 0, const MAX: usize = { usize::MAX }>(pub Vec<T>);
impl<T: Encode, const MIN: usize, const MAX: usize> Encode for BoundedArray<T, MIN, MAX> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_array_bounded(&self.0, MIN, MAX, w)
}
}
impl<T: Decode, const MIN: usize, const MAX: usize> Decode for BoundedArray<T, MIN, MAX> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
decode_array_bounded(MIN, MAX, r).map(Self)
}
}
impl<T, const MIN: usize, const MAX: usize> From<Vec<T>> for BoundedArray<T, MIN, MAX> {
fn from(v: Vec<T>) -> Self {
Self(v)
}
}
impl Encode for Uuid {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_u128::<BigEndian>(self.as_u128())?;
Ok(())
}
}
impl Decode for Uuid {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(Uuid::from_u128(r.read_u128::<BigEndian>()?))
}
}
impl Encode for nbt::Blob {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
Ok(nbt::to_writer(w, self, None)?)
}
}
impl Decode for nbt::Blob {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(nbt::from_reader(r)?)
}
}
/// Wrapper type acting as a bridge between Serde and [Encode]/[Decode] through
/// the NBT format.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
pub struct Nbt<T>(pub T);
impl<T: Serialize> Encode for Nbt<T> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
let mut enc = nbt::ser::Encoder::new(w, None);
self.0.serialize(&mut enc)?;
Ok(())
}
}
impl<'a, T: Deserialize<'a>> Decode for Nbt<T> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let mut dec = nbt::de::Decoder::new(r);
Ok(Nbt(Deserialize::deserialize(&mut dec)?))
}
}
impl Encode for BitVec<u64> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_array_bounded(self.as_raw_slice(), 0, usize::MAX, w)
}
}
impl Decode for BitVec<u64> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
BitVec::try_from_vec(Vec::decode(r)?)
.map_err(|_| anyhow!("Array is too long for bit vector"))
}
}
impl Encode for BitBox<u64> {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
encode_array_bounded(self.as_raw_slice(), 0, usize::MAX, w)
}
}
impl Decode for BitBox<u64> {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
BitVec::decode(r).map(|v| v.into_boxed_bitslice())
}
}
/// When decoding, reads the rest of the data in a packet and stuffs it into a
/// `Vec<u8>`. When encoding, the data is inserted into the packet with no
/// length prefix.
#[derive(Clone, Debug)]
pub struct ReadToEnd(pub Vec<u8>);
impl Decode for ReadToEnd {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let mut buf = Vec::new();
r.read_to_end(&mut buf)?;
Ok(ReadToEnd(buf))
}
}
impl Encode for ReadToEnd {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_all(&self.0).map_err(|e| e.into())
}
}
fn encode_array_bounded<T: Encode>(
s: &[T],
min: usize,
max: usize,
w: &mut impl Write,
) -> anyhow::Result<()> {
assert!(min <= max);
let len = s.len();
ensure!(
(min..=max).contains(&len),
"Length of array is out of bounds while encoding (got {len}, expected {min}..={max})"
);
ensure!(
len <= i32::MAX as usize,
"Length of array ({len}) exceeds i32::MAX"
);
VarInt(len as i32).encode(w)?;
for t in s {
t.encode(w)?;
}
Ok(())
}
pub(crate) fn encode_string_bounded(
s: &str,
min: usize,
max: usize,
w: &mut impl Write,
) -> anyhow::Result<()> {
assert!(min <= max, "Bad min and max");
let char_count = s.chars().count();
ensure!(
(min..=max).contains(&char_count),
"Char count of string is out of bounds while encoding (got {char_count}, expected \
{min}..={max})"
);
encode_array_bounded(s.as_bytes(), 0, usize::MAX, w)
}
pub(crate) fn decode_string_bounded(
min: usize,
max: usize,
r: &mut impl Read,
) -> anyhow::Result<String> {
assert!(min <= max);
let bytes = decode_array_bounded(min, max.saturating_mul(4), r)?;
let string = String::from_utf8(bytes)?;
let char_count = string.chars().count();
ensure!(
(min..=max).contains(&char_count),
"Char count of string is out of bounds while decoding (got {char_count}, expected \
{min}..={max}"
);
Ok(string)
}
pub(crate) fn decode_array_bounded<T: Decode>(
min: usize,
max: usize,
r: &mut impl Read,
) -> anyhow::Result<Vec<T>> {
assert!(min <= max);
let len = VarInt::decode(r)?.0;
ensure!(
len >= 0 && (min..=max).contains(&(len as usize)),
"Length of array is out of bounds while decoding (got {len}, needed {min}..={max})",
);
// Don't allocate more than what would roughly fit in a single packet in case we
// get a malicious array length.
let cap = (MAX_PACKET_SIZE as usize / mem::size_of::<T>().max(1)).min(len as usize);
let mut res = Vec::with_capacity(cap);
for _ in 0..len {
res.push(T::decode(r)?);
}
Ok(res)
}

785
src/server.rs Normal file
View file

@ -0,0 +1,785 @@
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};
use anyhow::{bail, ensure, Context};
use flume::{Receiver, Sender};
use num::BigInt;
use parking_lot::Mutex;
use rand::rngs::OsRng;
use rayon::iter::ParallelIterator;
use reqwest::Client as HttpClient;
use rsa::{PaddingScheme, PublicKeyParts, RsaPrivateKey};
use serde::Deserialize;
use serde_json::{json, Value};
use sha1::digest::Update;
use sha1::Sha1;
use sha2::{Digest, Sha256};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::net::{TcpListener, TcpStream};
use tokio::runtime::{Handle, Runtime};
use tokio::sync::{oneshot, Semaphore};
use uuid::Uuid;
use crate::chunk_store::ChunkStore;
use crate::client::MaybeClient;
use crate::codec::{Decoder, Encoder};
use crate::config::{Biome, BiomeId, Dimension, DimensionId, Handler, Login, ServerListPing};
use crate::entity::Appearance;
use crate::packets::handshake::{Handshake, HandshakeNextState};
use crate::packets::login::{
self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression,
};
use crate::packets::play::{ClientPlayPacket, ServerPlayPacket};
use crate::packets::status::{Ping, Pong, Request, Response};
use crate::protocol::{BoundedArray, BoundedString};
use crate::util::valid_username;
use crate::var_int::VarInt;
use crate::world::WorldStore;
use crate::{Client, EntityStore, ServerConfig, Ticks, 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.
///
/// Note the `Deref` and `DerefMut` impls on `Server` are (ab)used to
/// allow convenient access to the `other` field.
#[non_exhaustive]
pub struct Server {
pub entities: EntityStore,
pub worlds: WorldStore,
pub chunks: ChunkStore,
pub other: Other,
}
pub struct Other {
/// The shared portion of the server.
shared: SharedServer,
new_players_rx: Receiver<NewClientMessage>,
/// Incremented on every game tick.
tick_counter: Ticks,
/// The instant the current game tick began.
tick_start: Instant,
/// The time the last keep alive packet was sent to all players.
pub(crate) last_keepalive: Instant,
}
/// A server handle providing the subset of functionality which can be performed
/// outside the update loop. `SharedServer`s are interally refcounted and can be
/// freely cloned and shared between threads.
#[derive(Clone)]
pub struct SharedServer(Arc<SharedServerInner>);
struct SharedServerInner {
handler: Box<dyn Handler>,
address: SocketAddr,
update_duration: Duration,
online_mode: bool,
max_clients: usize,
clientbound_packet_capacity: usize,
serverbound_packet_capacity: usize,
tokio_handle: Handle,
dimensions: Vec<Dimension>,
biomes: Vec<Biome>,
/// The instant the server was started.
start_instant: Instant,
/// A semaphore used to limit the number of simultaneous connections to the
/// server. Closing this semaphore stops new connections.
connection_sema: Arc<Semaphore>,
/// The result that will be returned when the server is shut down.
shutdown_result: Mutex<Option<ShutdownResult>>,
/// The RSA keypair used for encryption with clients.
rsa_key: RsaPrivateKey,
/// The public part of `rsa_key` encoded in DER, which is an ASN.1 format.
/// This is sent to clients during the authentication process.
public_key_der: Box<[u8]>,
/// For session server requests.
http_client: HttpClient,
new_clients_tx: Sender<NewClientMessage>,
client_count: AtomicUsize,
}
/// Contains information about a new player.
pub struct NewClientData {
pub uuid: Uuid,
pub username: String,
pub remote_addr: SocketAddr,
}
struct NewClientMessage {
ncd: NewClientData,
reply: oneshot::Sender<anyhow::Result<ClientPacketChannels>>,
}
/// The result type returned from [`ServerConfig::start`] after the server is shut
/// down.
pub type ShutdownResult = Result<(), ShutdownError>;
pub type ShutdownError = Box<dyn Error + Send + Sync + 'static>;
pub(crate) type ClientPacketChannels = (Sender<ServerPlayPacket>, Receiver<ClientPlayPacket>);
pub(crate) type ServerPacketChannels = (Sender<ClientPlayPacket>, Receiver<ServerPlayPacket>);
impl Other {
/// Returns a reference to a [`SharedServer`].
pub fn shared(&self) -> &SharedServer {
&self.shared
}
/// Returns the number of ticks that have elapsed since the server began.
pub fn current_tick(&self) -> Ticks {
self.tick_counter
}
/// Returns the instant the current tick began.
pub fn tick_start(&self) -> Instant {
self.tick_start
}
}
impl SharedServer {
pub fn handler(&self) -> &(impl Handler + ?Sized) {
self.0.handler.as_ref()
}
pub fn address(&self) -> SocketAddr {
self.0.address
}
pub fn update_duration(&self) -> Duration {
self.0.update_duration
}
pub fn online_mode(&self) -> bool {
self.0.online_mode
}
pub fn max_clients(&self) -> usize {
self.0.max_clients
}
pub fn clientbound_packet_capacity(&self) -> usize {
self.0.clientbound_packet_capacity
}
pub fn serverbound_packet_capacity(&self) -> usize {
self.0.serverbound_packet_capacity
}
pub fn tokio_handle(&self) -> &Handle {
&self.0.tokio_handle
}
/// Obtains a [`Dimension`] by using its corresponding [`DimensionId`].
///
/// It is safe but unspecified behavior to call this function using a
/// [`DimensionId`] not originating from the configuration used to construct
/// the server.
pub fn dimension(&self, id: DimensionId) -> &Dimension {
self.0
.dimensions
.get(id.0 as usize)
.expect("invalid dimension ID")
}
/// Returns an iterator over all added dimensions and their associated
/// [`DimensionId`].
pub fn dimensions(&self) -> impl FusedIterator<Item = (&Dimension, DimensionId)> + Clone {
self.0
.dimensions
.iter()
.enumerate()
.map(|(i, d)| (d, DimensionId(i as u16)))
}
/// 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 {
self.0
.biomes
.iter()
.enumerate()
.map(|(i, b)| (b, BiomeId(i as u16)))
}
/// Returns the instant the server was started.
pub fn start_instant(&self) -> Instant {
self.0.start_instant
}
/// Immediately stops new connections to the server and initiates server
/// shutdown. The given result is returned through [`ServerConfig::start`].
///
/// You may want to disconnect all players with a message prior to calling
/// this function.
pub fn shutdown<R, E>(&self, res: R)
where
R: Into<Result<(), E>>,
E: Into<Box<dyn Error + Send + Sync + 'static>>,
{
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 {
type Target = Other;
fn deref(&self) -> &Self::Target {
&self.other
}
}
impl DerefMut for Server {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.other
}
}
impl Deref for Other {
type Target = SharedServer;
fn deref(&self) -> &Self::Target {
&self.shared
}
}
pub(crate) fn start_server(config: ServerConfig) -> ShutdownResult {
let rsa_key = RsaPrivateKey::new(&mut OsRng, 1024)?;
let public_key_der =
rsa_der::public_key_to_der(&rsa_key.n().to_bytes_be(), &rsa_key.e().to_bytes_be())
.into_boxed_slice();
let (new_players_tx, new_players_rx) = flume::bounded(1);
let rt = if config.tokio_handle.is_none() {
Some(Runtime::new()?)
} else {
None
};
let handle = match &rt {
Some(rt) => rt.handle().clone(),
None => config.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,
start_instant: Instant::now(),
connection_sema,
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 {
entities: EntityStore::new(),
worlds: WorldStore::new(shared.clone()),
chunks: ChunkStore::new(),
other: Other {
shared: shared.clone(),
tick_counter: 0,
tick_start: Instant::now(),
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 {
server.tick_start = Instant::now();
let shared = server.shared().clone();
loop {
if let Some(res) = server.0.shutdown_result.lock().take() {
return res;
}
while let Ok(msg) = server.new_players_rx.try_recv() {
join_player(server, msg);
}
const KEEPALIVE_FREQ: Duration = Duration::from_secs(8);
if server.tick_start().duration_since(server.last_keepalive) >= KEEPALIVE_FREQ {
server.last_keepalive = server.tick_start();
}
{
let mut clients = server.entities.clients_mut().unwrap();
server
.entities
.par_iter(&mut clients)
.for_each(|(e, client)| {
if let Some(client) = client.get_mut() {
client.update(e, server);
}
});
}
server.entities.update_old_appearances();
{
let mut chunks = server.chunks.chunks_mut().unwrap();
server.chunks.par_iter(&mut chunks).for_each(|(_, chunk)| {
chunk.apply_modifications();
});
}
shared.handler().update(server);
{
let mut chunks = server.chunks.chunks_mut().unwrap();
// Chunks modified this tick can have their changes applied immediately because
// they have not been observed by clients yet.
server.chunks.par_iter(&mut chunks).for_each(|(_, chunk)| {
if chunk.created_this_tick() {
chunk.clear_created_this_tick();
chunk.apply_modifications();
}
});
}
thread::sleep(
server
.0
.update_duration
.saturating_sub(server.tick_start.elapsed()),
);
server.tick_start = Instant::now();
server.tick_counter += 1;
}
}
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 client_packet_channels: ClientPacketChannels = (serverbound_tx, clientbound_rx);
let server_packet_channels: ServerPacketChannels = (clientbound_tx, serverbound_rx);
let _ = msg.reply.send(Ok(client_packet_channels));
let mut client = Client::new(server_packet_channels, msg.ncd.username, server);
let client_eid = match server
.entities
.create_with_uuid(Appearance::None, msg.ncd.uuid)
{
Some(eid) => eid,
None => {
log::error!(
"player '{}' cannot join the server because their UUID ({}) conflicts with an \
existing entity",
client.username(),
msg.ncd.uuid
);
client.disconnect("Cannot join server: Your UUID conflicts with an existing entity.");
return;
}
};
let mut clients = server.entities.clients_mut().unwrap();
*server.entities.get(&mut clients, client_eid).unwrap() = MaybeClient(Some(Box::new(client)));
}
type Codec = (Encoder<OwnedWriteHalf>, Decoder<OwnedReadHalf>);
/// The duration of time between each sent keep alive packet.
// TODO: remove this.
async fn do_accept_loop(server: SharedServer) {
log::trace!("entering accept loop");
let listener = match TcpListener::bind(server.0.address).await {
Ok(listener) => listener,
Err(e) => {
server.shutdown(Err(e).context("failed to start TCP listener"));
return;
}
};
loop {
match server.0.connection_sema.clone().acquire_owned().await {
Ok(permit) => match listener.accept().await {
Ok((stream, remote_addr)) => {
let server = server.clone();
tokio::spawn(async move {
// 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}")
}
if let Err(e) = handle_connection(server, stream, remote_addr).await {
log::debug!("connection to {remote_addr} ended: {e:#}");
}
drop(permit);
});
}
Err(e) => {
log::error!("failed to accept incoming connection: {e}");
}
},
// Closed semaphore indicates server shutdown.
Err(_) => return,
}
}
}
async fn handle_connection(
server: SharedServer,
stream: TcpStream,
remote_addr: SocketAddr,
) -> anyhow::Result<()> {
let timeout = Duration::from_secs(10);
let (read, write) = stream.into_split();
let mut c: Codec = (Encoder::new(write, timeout), Decoder::new(read, timeout));
// TODO: peek stream for 0xFE legacy ping
match c.1.read_packet::<Handshake>().await?.next_state {
HandshakeNextState::Status => handle_status(server, &mut c, remote_addr)
.await
.context("error during status"),
HandshakeNextState::Login => match handle_login(&server, &mut c, remote_addr)
.await
.context("error during login")?
{
Some(npd) => handle_play(&server, c, npd)
.await
.context("error during play"),
None => Ok(()),
},
}
}
async fn handle_status(
server: SharedServer,
c: &mut Codec,
remote_addr: SocketAddr,
) -> anyhow::Result<()> {
c.1.read_packet::<Request>().await?;
match server
.0
.handler
.server_list_ping(&server, remote_addr)
.await
{
ServerListPing::Respond {
online_players,
max_players,
description,
favicon_png,
} => {
let mut json = json!({
"version": {
"name": VERSION_NAME,
"protocol": PROTOCOL_VERSION
},
"players": {
"online": online_players,
"max": max_players,
// TODO: player sample?
},
"description": description,
});
if let Some(data) = favicon_png {
let mut buf = "data:image/png;base64,".to_string();
base64::encode_config_buf(data, base64::STANDARD, &mut buf);
json.as_object_mut()
.unwrap()
.insert("favicon".to_string(), Value::String(buf));
}
c.0.write_packet(&Response {
json_response: json.to_string(),
})
.await?;
}
ServerListPing::Ignore => return Ok(()),
}
let Ping { payload } = c.1.read_packet().await?;
c.0.write_packet(&Pong { payload }).await?;
Ok(())
}
/// Handle the login process and return the new player's data if successful.
async fn handle_login(
server: &SharedServer,
c: &mut Codec,
remote_addr: SocketAddr,
) -> anyhow::Result<Option<NewClientData>> {
let LoginStart {
username: BoundedString(username),
} = c.1.read_packet().await?;
ensure!(valid_username(&username), "invalid username '{username}'");
let (uuid, skin_blob) = if server.0.online_mode {
let verify_token: [u8; 16] = rand::random();
c.0.write_packet(&EncryptionRequest {
server_id: Default::default(), // Always empty
public_key: server.0.public_key_der.to_vec(),
verify_token: verify_token.to_vec().into(),
})
.await?;
let EncryptionResponse {
shared_secret: BoundedArray(encrypted_shared_secret),
verify_token: BoundedArray(encrypted_verify_token),
} = c.1.read_packet().await?;
let shared_secret = server
.0
.rsa_key
.decrypt(PaddingScheme::PKCS1v15Encrypt, &encrypted_shared_secret)
.context("Failed to decrypt shared secret")?;
let new_verify_token = server
.0
.rsa_key
.decrypt(PaddingScheme::PKCS1v15Encrypt, &encrypted_verify_token)
.context("Failed to decrypt verify token")?;
ensure!(
verify_token.as_slice() == new_verify_token,
"Verify tokens do not match"
);
let crypt_key: [u8; 16] = shared_secret
.as_slice()
.try_into()
.context("Shared secret has the wrong length")?;
c.0.enable_encryption(&crypt_key);
c.1.enable_encryption(&crypt_key);
#[derive(Debug, Deserialize)]
struct AuthResponse {
id: String,
name: String,
properties: Vec<Property>,
}
#[derive(Debug, Deserialize)]
struct Property {
name: String,
value: String,
}
let hash = Sha1::new()
.chain(&shared_secret)
.chain(&server.0.public_key_der)
.finalize();
let hex_hash = weird_hex_encoding(&hash);
let url = format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={hex_hash}&ip={}", remote_addr.ip());
let resp = server.0.http_client.get(url).send().await?;
let status = resp.status();
ensure!(
status.is_success(),
"session server GET request failed: {status}"
);
let data: AuthResponse = resp.json().await?;
ensure!(data.name == username, "usernames do not match");
let uuid = Uuid::parse_str(&data.id).context("failed to parse player's UUID")?;
let skin_blob = match data.properties.iter().find(|p| p.name == "textures") {
Some(p) => base64::decode(&p.value).context("failed to parse skin blob")?,
None => bail!("failed to find skin blob in auth response"),
};
(uuid, Some(skin_blob))
} else {
// Derive the player's UUID from a hash of their username.
let uuid = Uuid::from_slice(&Sha256::digest(&username)[..16]).unwrap();
(uuid, None)
};
let compression_threshold = 256;
c.0.write_packet(&SetCompression {
threshold: VarInt(compression_threshold as i32),
})
.await?;
c.0.enable_compression(compression_threshold);
c.1.enable_compression(compression_threshold);
let npd = NewClientData {
uuid,
username,
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 {
log::info!("Disconnect at login: \"{reason}\"");
c.0.write_packet(&login::Disconnect { reason }).await?;
return Ok(None);
}
c.0.write_packet(&LoginSuccess {
uuid: npd.uuid,
username: npd.username.clone().into(),
})
.await?;
Ok(Some(npd))
}
async fn handle_play(server: &SharedServer, c: Codec, ncd: NewClientData) -> anyhow::Result<()> {
let (reply_tx, reply_rx) = oneshot::channel();
server
.0
.new_clients_tx
.send_async(NewClientMessage {
ncd,
reply: reply_tx,
})
.await?;
let (packet_tx, packet_rx) = match reply_rx.await {
Ok(res) => res?,
Err(_) => return Ok(()), // Server closed
};
let (mut encoder, mut decoder) = c;
tokio::spawn(async move {
while let Ok(pkt) = packet_rx.recv_async().await {
if let Err(e) = encoder.write_packet(&pkt).await {
log::debug!("error while sending play packet: {e:#}");
break;
}
}
});
loop {
let pkt = decoder.read_packet().await?;
if packet_tx.send_async(pkt).await.is_err() {
break;
}
}
Ok(())
}
fn weird_hex_encoding(bytes: &[u8]) -> String {
BigInt::from_signed_bytes_be(bytes).to_str_radix(16)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn weird_hex_encoding_correct() {
assert_eq!(
weird_hex_encoding(&Sha1::digest("Notch")),
"4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48"
);
assert_eq!(
weird_hex_encoding(&Sha1::digest("jeb_")),
"-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1"
);
assert_eq!(
weird_hex_encoding(&Sha1::digest("simon")),
"88e16a1019277b15d58faf0541e11910eb756f6"
);
}
}

0
src/slot.rs Normal file
View file

527
src/text.rs Normal file
View file

@ -0,0 +1,527 @@
// TODO: Documentation.
// TODO: make fields of Text public?
use std::borrow::Cow;
use std::fmt;
use std::io::{Read, Write};
use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::protocol::{BoundedString, Decode, Encode};
use crate::Identifier;
/// Represents formatted text in Minecraft's JSON text format.
///
/// Text is used in various places such as chat, window titles,
/// disconnect messages, written books, signs, and more.
///
/// For more information, see the relevant [Minecraft wiki article](https://minecraft.fandom.com/wiki/Raw_JSON_text_format).
///
/// Note that the current `Deserialize` implementation on this type recognizes
/// only a subset of the full JSON chat component format.
///
/// ## Example
/// With [`TextFormat`] in scope, you can write the following:
/// ```
/// use server::text::{Color, Text, TextFormat};
///
/// let txt = "The text is ".into_text()
/// + "Red".color(Color::RED)
/// + ", "
/// + "Green".color(Color::GREEN)
/// + ", and also "
/// + "Blue".color(Color::BLUE)
/// + "!\nAnd maybe even "
/// + "Italic".italic()
/// + ".";
///
/// assert_eq!(
/// txt.to_plain(),
/// "The text is Red, Green, and also Blue!\nAnd maybe even Italic."
/// );
/// ```
#[derive(Clone, PartialEq, Default, Debug, Serialize, Deserialize)]
pub struct Text {
#[serde(flatten)]
content: TextContent,
#[serde(default, skip_serializing_if = "Option::is_none")]
color: Option<Color>,
#[serde(default, skip_serializing_if = "Option::is_none")]
font: Option<Cow<'static, str>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
bold: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
italic: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
underlined: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
strikethrough: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
obfuscated: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
insertion: Option<Cow<'static, str>>,
#[serde(rename = "clickEvent")]
#[serde(default, skip_serializing_if = "Option::is_none")]
click_event: Option<ClickEvent>,
#[serde(rename = "hoverEvent")]
#[serde(default, skip_serializing_if = "Option::is_none")]
hover_event: Option<HoverEvent>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
extra: Vec<Text>,
}
/// Provides the methods necessary for working with [`Text`] objects.
///
/// This trait exists to allow using `Into<Text>` impls without having to first
/// convert `Self` into [`Text`]. It is automatically implemented for all
/// `Into<Text>` types, including [`Text`] itself.
pub trait TextFormat: Into<Text> {
fn into_text(self) -> Text {
self.into()
}
fn color(self, color: Color) -> Text {
let mut t = self.into();
t.color = Some(color);
t
}
fn clear_color(self) -> Text {
let mut t = self.into();
t.color = None;
t
}
fn font(self, font: impl Into<Cow<'static, str>>) -> Text {
let mut t = self.into();
t.font = Some(font.into());
t
}
fn clear_font(self) -> Text {
let mut t = self.into();
t.font = None;
t
}
fn bold(self) -> Text {
let mut t = self.into();
t.bold = Some(true);
t
}
fn not_bold(self) -> Text {
let mut t = self.into();
t.bold = Some(false);
t
}
fn clear_bold(self) -> Text {
let mut t = self.into();
t.bold = None;
t
}
fn italic(self) -> Text {
let mut t = self.into();
t.italic = Some(true);
t
}
fn not_italic(self) -> Text {
let mut t = self.into();
t.italic = Some(false);
t
}
fn clear_italic(self) -> Text {
let mut t = self.into();
t.italic = None;
t
}
fn underlined(self) -> Text {
let mut t = self.into();
t.underlined = Some(true);
t
}
fn not_underlined(self) -> Text {
let mut t = self.into();
t.underlined = Some(false);
t
}
fn clear_underlined(self) -> Text {
let mut t = self.into();
t.underlined = None;
t
}
fn strikethrough(self) -> Text {
let mut t = self.into();
t.strikethrough = Some(true);
t
}
fn not_strikethrough(self) -> Text {
let mut t = self.into();
t.strikethrough = Some(false);
t
}
fn clear_strikethrough(self) -> Text {
let mut t = self.into();
t.strikethrough = None;
t
}
fn obfuscated(self) -> Text {
let mut t = self.into();
t.obfuscated = Some(true);
t
}
fn not_obfuscated(self) -> Text {
let mut t = self.into();
t.obfuscated = Some(false);
t
}
fn clear_obfuscated(self) -> Text {
let mut t = self.into();
t.obfuscated = None;
t
}
fn insertion(self, insertion: impl Into<Cow<'static, str>>) -> Text {
let mut t = self.into();
t.insertion = Some(insertion.into());
t
}
fn clear_insertion(self) -> Text {
let mut t = self.into();
t.insertion = None;
t
}
fn on_click_open_url(self, url: impl Into<Cow<'static, str>>) -> Text {
let mut t = self.into();
t.click_event = Some(ClickEvent::OpenUrl(url.into()));
t
}
fn on_click_run_command(self, command: impl Into<Cow<'static, str>>) -> Text {
let mut t = self.into();
t.click_event = Some(ClickEvent::RunCommand(command.into()));
t
}
fn on_click_suggest_command(self, command: impl Into<Cow<'static, str>>) -> Text {
let mut t = self.into();
t.click_event = Some(ClickEvent::SuggestCommand(command.into()));
t
}
fn on_click_change_page(self, page: impl Into<i32>) -> Text {
let mut t = self.into();
t.click_event = Some(ClickEvent::ChangePage(page.into()));
t
}
fn on_click_copy_to_clipboard(self, text: impl Into<Cow<'static, str>>) -> Text {
let mut t = self.into();
t.click_event = Some(ClickEvent::CopyToClipboard(text.into()));
t
}
fn clear_click_event(self) -> Text {
let mut t = self.into();
t.click_event = None;
t
}
fn on_hover_show_text(self, text: impl Into<Text>) -> Text {
let mut t = self.into();
t.hover_event = Some(HoverEvent::ShowText(Box::new(text.into())));
t
}
fn clear_hover_event(self) -> Text {
let mut t = self.into();
t.hover_event = None;
t
}
fn add_child(self, text: impl Into<Text>) -> Text {
let mut t = self.into();
t.extra.push(text.into());
t
}
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum TextContent {
Text { text: Cow<'static, str> },
// TODO: translate
// TODO: score
// TODO: entity names
// TODO: keybind
// TODO: nbt
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(tag = "action", content = "value", rename_all = "snake_case")]
enum ClickEvent {
OpenUrl(Cow<'static, str>),
/// Only usable by internal servers for security reasons.
OpenFile(Cow<'static, str>),
RunCommand(Cow<'static, str>),
SuggestCommand(Cow<'static, str>),
ChangePage(i32),
CopyToClipboard(Cow<'static, str>),
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(tag = "action", content = "contents", rename_all = "snake_case")]
#[allow(clippy::enum_variant_names)]
enum HoverEvent {
ShowText(Box<Text>),
ShowItem {
id: Identifier,
count: Option<i32>,
// TODO: tag
},
ShowEntity {
name: Box<Text>,
#[serde(rename = "type")]
typ: Identifier,
// TODO: id (hyphenated entity UUID as a string)
},
}
impl Text {
pub fn text(plain: impl Into<Cow<'static, str>>) -> Self {
#![allow(clippy::self_named_constructors)]
Self {
content: TextContent::Text { text: plain.into() },
..Self::default()
}
}
pub fn to_plain(&self) -> String {
let mut res = String::new();
self.write_plain(&mut res)
.expect("failed to write plain text");
res
}
pub fn write_plain(&self, w: &mut impl fmt::Write) -> fmt::Result {
if let TextContent::Text { text } = &self.content {
w.write_str(text.as_ref())?;
}
for child in &self.extra {
child.write_plain(w)?;
}
Ok(())
}
}
impl<T: Into<Text>> TextFormat for T {}
impl<T: Into<Text>> std::ops::Add<T> for Text {
type Output = Self;
fn add(self, rhs: T) -> Self::Output {
self.add_child(rhs)
}
}
impl<T: Into<Text>> std::ops::AddAssign<T> for Text {
fn add_assign(&mut self, rhs: T) {
self.extra.push(rhs.into());
}
}
impl From<String> for Text {
fn from(s: String) -> Self {
Text::text(s)
}
}
impl From<&'static str> for Text {
fn from(s: &'static str) -> Self {
Text::text(s)
}
}
impl From<Cow<'static, str>> for Text {
fn from(s: Cow<'static, str>) -> Self {
Text::text(s)
}
}
impl<'a> From<&'a Text> for String {
fn from(t: &'a Text) -> Self {
t.to_plain()
}
}
impl<'a, 'b> From<&'a Text> for Cow<'b, str> {
fn from(t: &'a Text) -> Self {
String::from(t).into()
}
}
impl fmt::Display for Text {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", String::from(self))
}
}
impl Encode for Text {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
BoundedString::<0, 262144>(serde_json::to_string(self)?).encode(w)
}
}
impl Decode for Text {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let string = BoundedString::<0, 262144>::decode(r)?;
Ok(serde_json::from_str(&string.0)?)
}
}
impl Default for TextContent {
fn default() -> Self {
Self::Text { text: "".into() }
}
}
impl Color {
pub const AQUA: Color = Color::new(85, 255, 255);
pub const BLACK: Color = Color::new(0, 0, 0);
pub const BLUE: Color = Color::new(85, 85, 255);
pub const DARK_AQUA: Color = Color::new(0, 170, 170);
pub const DARK_BLUE: Color = Color::new(0, 0, 170);
pub const DARK_GRAY: Color = Color::new(85, 85, 85);
pub const DARK_GREEN: Color = Color::new(0, 170, 0);
pub const DARK_PURPLE: Color = Color::new(170, 0, 170);
pub const DARK_RED: Color = Color::new(170, 0, 0);
pub const GOLD: Color = Color::new(255, 170, 0);
pub const GRAY: Color = Color::new(170, 170, 170);
pub const GREEN: Color = Color::new(85, 255, 85);
pub const LIGHT_PURPLE: Color = Color::new(255, 85, 255);
pub const RED: Color = Color::new(255, 85, 85);
pub const WHITE: Color = Color::new(255, 255, 255);
pub const YELLOW: Color = Color::new(255, 255, 85);
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
}
impl Serialize for Color {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(ColorVisitor)
}
}
struct ColorVisitor;
impl<'de> Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a hex color of the form #rrggbb")
}
fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
color_from_str(s).ok_or_else(|| E::custom("invalid hex color"))
}
}
fn color_from_str(s: &str) -> Option<Color> {
let to_num = |d| match d {
b'0'..=b'9' => Some(d - b'0'),
b'a'..=b'f' => Some(d - b'a' + 0xa),
b'A'..=b'F' => Some(d - b'A' + 0xa),
_ => None,
};
match s.as_bytes() {
[b'#', r0, r1, g0, g1, b0, b1] => Some(Color {
r: to_num(*r0)? << 4 | to_num(*r1)?,
g: to_num(*g0)? << 4 | to_num(*g1)?,
b: to_num(*b0)? << 4 | to_num(*b1)?,
}),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_deserialize() {
let before = "foo".color(Color::RED).bold()
+ ("bar".obfuscated().color(Color::YELLOW)
+ "baz".underlined().not_bold().italic().color(Color::BLACK));
assert_eq!(before.to_plain(), "foobarbaz");
let json = serde_json::to_string_pretty(&before).unwrap();
let after: Text = serde_json::from_str(&json).unwrap();
println!("==== Before ====\n");
println!("{before:#?}");
println!("==== After ====\n");
println!("{after:#?}");
assert_eq!(before, after);
assert_eq!(before.to_plain(), after.to_plain());
}
#[test]
fn color() {
assert_eq!(
color_from_str("#aBcDeF"),
Some(Color::new(0xab, 0xcd, 0xef))
);
assert_eq!(color_from_str("#fFfFfF"), Some(Color::new(255, 255, 255)));
assert_eq!(color_from_str("#00000000"), None);
assert_eq!(color_from_str("#000000"), Some(Color::BLACK));
assert_eq!(color_from_str("#"), None);
}
}

28
src/util.rs Normal file
View file

@ -0,0 +1,28 @@
use std::iter::FusedIterator;
use crate::ChunkPos;
/// Returns true if the given string meets the criteria for a valid Minecraft
/// username.
pub fn valid_username(s: &str) -> bool {
(3..=16).contains(&s.len())
&& s.chars()
.all(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_'))
}
const EXTRA_RADIUS: i32 = 3;
pub fn chunks_in_view_distance(
center: ChunkPos,
distance: u8,
) -> impl FusedIterator<Item = ChunkPos> {
let dist = distance as i32 + EXTRA_RADIUS;
(center.z - dist..=center.z + dist)
.flat_map(move |z| (center.x - dist..=center.x + dist).map(move |x| ChunkPos { x, z }))
.filter(move |&p| is_chunk_in_view_distance(center, p, distance))
}
pub fn is_chunk_in_view_distance(center: ChunkPos, other: ChunkPos, distance: u8) -> bool {
(center.x as f64 - other.x as f64).powi(2) + (center.z as f64 - other.z as f64).powi(2)
<= (distance as f64 + EXTRA_RADIUS as f64).powi(2)
}

124
src/var_int.rs Normal file
View file

@ -0,0 +1,124 @@
// TODO: use derive_more?
use std::io::{Read, Write};
use anyhow::bail;
use byteorder::{ReadBytesExt, WriteBytesExt};
use crate::protocol::{Decode, Encode};
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VarInt(pub i32);
impl VarInt {
/// The maximum number of bytes a VarInt could occupy when read from and
/// written to the Minecraft protocol.
pub const MAX_SIZE: usize = 5;
/// The number of bytes this `VarInt` will occupy when written to the
/// Minecraft protocol.
pub const fn written_size(self) -> usize {
let val = self.0 as u32;
if val & 0b11110000_00000000_00000000_00000000 != 0 {
5
} else if val & 0b11111111_11100000_00000000_00000000 != 0 {
4
} else if val & 0b11111111_11111111_11000000_00000000 != 0 {
3
} else if val & 0b11111111_11111111_11111111_10000000 != 0 {
2
} else {
1
}
}
}
impl Encode for VarInt {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
let mut val = self.0 as u32;
loop {
if val & 0b11111111111111111111111110000000 == 0 {
w.write_u8(val as u8)?;
return Ok(());
}
w.write_u8(val as u8 & 0b01111111 | 0b10000000)?;
val >>= 7;
}
}
}
impl Decode for VarInt {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let mut val = 0;
for i in 0..Self::MAX_SIZE {
let byte = r.read_u8()?;
val |= (byte as i32 & 0b01111111) << (i * 7);
if byte & 0b10000000 == 0 {
return Ok(VarInt(val));
}
}
bail!("VarInt is too large")
}
}
impl From<VarInt> for i32 {
fn from(i: VarInt) -> Self {
i.0
}
}
impl From<VarInt> for i64 {
fn from(i: VarInt) -> Self {
i.0 as i64
}
}
impl From<i32> for VarInt {
fn from(i: i32) -> Self {
VarInt(i)
}
}
#[cfg(test)]
mod tests {
use rand::{thread_rng, Rng};
use super::*;
#[test]
fn written_size_correct() {
let mut rng = thread_rng();
let mut buf = Vec::new();
for n in (0..100_000)
.map(|_| rng.gen())
.chain([0, i32::MIN, i32::MAX])
.map(VarInt)
{
buf.clear();
n.encode(&mut buf).unwrap();
assert!(buf.len() == n.written_size());
}
}
#[test]
fn encode_decode() {
let mut rng = thread_rng();
let mut buf = Vec::new();
for n in (0..1_000_000)
.map(|_| rng.gen())
.chain([0, i32::MIN, i32::MAX])
{
VarInt(n).encode(&mut buf).unwrap();
let mut slice = buf.as_slice();
assert!(slice.len() <= VarInt::MAX_SIZE);
assert_eq!(n, VarInt::decode(&mut slice).unwrap().0);
assert!(slice.is_empty());
buf.clear();
}
}
}

70
src/var_long.rs Normal file
View file

@ -0,0 +1,70 @@
use std::io::{Read, Write};
use anyhow::bail;
use byteorder::{ReadBytesExt, WriteBytesExt};
use crate::protocol::{Decode, Encode};
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VarLong(i64);
impl VarLong {
/// The maximum number of bytes a `VarLong` can occupy when read from and
/// written to the Minecraft protocol.
pub const MAX_SIZE: usize = 10;
}
impl Encode for VarLong {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
let mut val = self.0 as u64;
loop {
if val & 0b1111111111111111111111111111111111111111111111111111111110000000 == 0 {
w.write_u8(val as u8)?;
return Ok(());
}
w.write_u8(val as u8 & 0b01111111 | 0b10000000)?;
val >>= 7;
}
}
}
impl Decode for VarLong {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
let mut val = 0;
for i in 0..Self::MAX_SIZE {
let byte = r.read_u8()?;
val |= (byte as i64 & 0b01111111) << (i * 7);
if byte & 0b10000000 == 0 {
return Ok(VarLong(val));
}
}
bail!("VarInt is too large")
}
}
#[cfg(test)]
mod tests {
use rand::{thread_rng, Rng};
use super::*;
#[test]
fn encode_decode() {
let mut rng = thread_rng();
let mut buf = Vec::new();
for n in (0..1_000_000)
.map(|_| rng.gen())
.chain([0, i64::MIN, i64::MAX])
{
VarLong(n).encode(&mut buf).unwrap();
let mut slice = buf.as_slice();
assert!(slice.len() <= VarLong::MAX_SIZE);
assert_eq!(n, VarLong::decode(&mut slice).unwrap().0);
assert!(slice.is_empty());
buf.clear();
}
}
}

251
src/world.rs Normal file
View file

@ -0,0 +1,251 @@
use std::collections::HashMap;
use std::iter::FusedIterator;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
use crate::chunk::ChunkPos;
use crate::chunk_store::ChunkId;
use crate::component::{
ComponentStore, Components, ComponentsMut, Error, Id, IdData, IdRaw, ZippedComponents,
ZippedComponentsRaw,
};
use crate::config::DimensionId;
use crate::SharedServer;
pub struct WorldStore {
comps: ComponentStore<WorldId>,
worlds: RwLock<Vec<World>>,
shared: SharedServer,
}
impl WorldStore {
pub(crate) fn new(shared: SharedServer) -> Self {
Self {
comps: ComponentStore::new(),
worlds: RwLock::new(Vec::new()),
shared,
}
}
pub fn create(&mut self, dim: DimensionId) -> WorldId {
let height = self.shared.dimension(dim).height;
let id = self.comps.create_item();
let world = World {
chunks: HashMap::new(),
dimension: dim,
};
let idx = id.0.idx as usize;
if idx >= self.worlds.get_mut().len() {
self.worlds.get_mut().push(world);
} else {
self.worlds.get_mut()[idx] = world;
}
id
}
/// Deletes a world from the server. Any [`WorldId`] referring to the
/// deleted world will be invalidated.
///
/// Note that any entities with positions inside the deleted world will not
/// be deleted themselves. These entities should be deleted or moved
/// elsewhere, preferably before calling this function.
pub fn delete(&mut self, world: WorldId) -> bool {
if self.comps.delete_item(world) {
let idx = world.0.idx as usize;
self.worlds.get_mut()[idx].chunks = HashMap::new();
true
} else {
false
}
}
pub fn count(&self) -> usize {
self.comps.count()
}
pub fn get<Z>(&self, z: Z, world: WorldId) -> Option<Z::Item>
where
Z: ZippedComponents<Id = WorldId>,
{
self.comps.get(z, world)
}
pub fn iter<'a, Z>(&'a self, z: Z) -> impl FusedIterator<Item = (WorldId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = WorldId> + 'a,
{
self.comps.iter(z)
}
pub fn par_iter<'a, Z>(&'a self, z: Z) -> impl ParallelIterator<Item = (WorldId, Z::Item)> + 'a
where
Z: ZippedComponents<Id = WorldId> + 'a,
{
self.comps.par_iter(z)
}
pub fn ids(&self) -> impl FusedIterator<Item = WorldId> + Clone + '_ {
self.comps.ids()
}
pub fn par_ids(&self) -> impl ParallelIterator<Item = WorldId> + Clone + '_ {
self.comps.par_ids()
}
pub fn worlds(&self) -> Result<Worlds, Error> {
Ok(Worlds {
worlds: self.worlds.try_read().ok_or(Error::NoReadAccess)?,
})
}
pub fn worlds_mut(&self) -> Result<WorldsMut, Error> {
Ok(WorldsMut {
worlds: self.worlds.try_write().ok_or(Error::NoWriteAccess)?,
})
}
pub fn register_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.register_component::<C>();
}
pub fn unregister_component<C: 'static + Send + Sync + Default>(&mut self) {
self.comps.unregister_component::<C>()
}
pub fn is_registered<C: 'static + Send + Sync + Default>(&self) -> bool {
self.comps.is_registered::<C>()
}
pub fn components<C: 'static + Send + Sync + Default>(
&self,
) -> Result<Components<C, WorldId>, Error> {
self.comps.components::<C>()
}
pub fn components_mut<C: 'static + Send + Sync + Default>(
&self,
) -> Result<ComponentsMut<C, WorldId>, Error> {
self.comps.components_mut::<C>()
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
pub struct WorldId(pub(crate) IdData);
impl WorldId {
/// The value of the default [`WorldId`] which always refers to an invalid
/// world.
pub const NULL: Self = Self(IdData::NULL);
}
impl IdRaw for WorldId {
fn to_data(self) -> IdData {
self.0
}
fn from_data(id: IdData) -> Self {
Self(id)
}
}
impl Id for WorldId {}
pub struct Worlds<'a> {
worlds: RwLockReadGuard<'a, Vec<World>>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b Worlds<'a> {
type RawItem = &'b World;
type RawIter = std::slice::Iter<'b, World>;
type RawParIter = rayon::slice::Iter<'b, World>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&self.worlds[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.worlds.iter()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.worlds.par_iter()
}
}
impl<'a, 'b> ZippedComponents for &'b Worlds<'a> {
type Id = WorldId;
type Item = &'b World;
}
pub struct WorldsMut<'a> {
worlds: RwLockWriteGuard<'a, Vec<World>>,
}
impl<'a, 'b> ZippedComponentsRaw for &'b WorldsMut<'a> {
type RawItem = &'b World;
type RawIter = std::slice::Iter<'b, World>;
type RawParIter = rayon::slice::Iter<'b, World>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&self.worlds[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.worlds.iter()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.worlds.par_iter()
}
}
impl<'a, 'b> ZippedComponents for &'b WorldsMut<'a> {
type Id = WorldId;
type Item = &'b World;
}
impl<'a, 'b> ZippedComponentsRaw for &'b mut WorldsMut<'a> {
type RawItem = &'b mut World;
type RawIter = std::slice::IterMut<'b, World>;
type RawParIter = rayon::slice::IterMut<'b, World>;
fn raw_get(self, idx: usize) -> Self::RawItem {
&mut self.worlds[idx]
}
fn raw_iter(self) -> Self::RawIter {
self.worlds.iter_mut()
}
fn raw_par_iter(self) -> Self::RawParIter {
self.worlds.par_iter_mut()
}
}
impl<'a, 'b> ZippedComponents for &'b mut WorldsMut<'a> {
type Id = WorldId;
type Item = &'b mut World;
}
pub struct World {
chunks: HashMap<ChunkPos, ChunkId>,
dimension: DimensionId,
}
impl World {
pub fn dimension(&self) -> DimensionId {
self.dimension
}
pub fn chunks(&self) -> &HashMap<ChunkPos, ChunkId> {
&self.chunks
}
pub fn chunks_mut(&mut self) -> &mut HashMap<ChunkPos, ChunkId> {
&mut self.chunks
}
}

9
valence-data/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "valence-data"
version = "0.1.0"
edition = "2021"
description = "Generated code for the Valence project."
repository = "https://github.com/rj00a/valence"
license = "MIT"
[dependencies]

8
valence-data/src/lib.rs Normal file
View file

@ -0,0 +1,8 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}