Add custom data

This commit is contained in:
Ryan 2022-07-15 20:40:39 -07:00
parent 0ef05bb0d0
commit 865ab76699
16 changed files with 466 additions and 422 deletions

View file

@ -2470,11 +2470,11 @@ pub fn build() -> anyhow::Result<()> {
#(#entity_structs)*
/// An enum encoding the type of an entity along with any data specific to that entity type.
pub enum EntityData {
pub enum EntityState {
#(#entity_kind_variants(#entity_kind_variants),)*
}
impl EntityData {
impl EntityState {
pub(super) fn new(kind: EntityKind) -> Self {
match kind {
#(EntityKind::#entity_kind_variants => Self::#entity_kind_variants(#entity_kind_variants::new()),)*

View file

@ -1,19 +1,17 @@
use std::collections::HashMap;
use std::mem;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use log::LevelFilter;
use num::Integer;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
use valence::biome::Biome;
use valence::block::BlockState;
use valence::client::{ClientId, Event, Hand};
use valence::client::{Event, Hand};
use valence::config::{Config, ServerListPing};
use valence::dimension::{Dimension, DimensionId};
use valence::entity::data::Pose;
use valence::entity::{EntityData, EntityId, EntityKind};
use valence::entity::state::Pose;
use valence::entity::{EntityId, EntityKind, EntityState};
use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat};
use valence::{async_trait, ident};
@ -24,27 +22,32 @@ pub fn main() -> ShutdownResult {
.parse_default_env()
.init();
valence::start_server(Game {
player_count: AtomicUsize::new(0),
state: Mutex::new(State {
player_entities: HashMap::new(),
valence::start_server(
Game {
player_count: AtomicUsize::new(0),
},
ServerData {
board: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(),
board_buf: vec![false; SIZE_X * SIZE_Z].into_boxed_slice(),
}),
})
},
)
}
struct Game {
player_count: AtomicUsize,
state: Mutex<State>,
}
struct State {
player_entities: HashMap<ClientId, EntityId>,
struct ServerData {
board: Box<[bool]>,
board_buf: Box<[bool]>,
}
#[derive(Default)]
struct ClientData {
/// The client's player entity.
player: EntityId,
}
const MAX_PLAYERS: usize = 10;
const SIZE_X: usize = 100;
@ -53,6 +56,12 @@ const BOARD_Y: i32 = 50;
#[async_trait]
impl Config for Game {
type ChunkData = ();
type ClientData = ClientData;
type EntityData = ();
type ServerData = ServerData;
type WorldData = ();
fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full.
MAX_PLAYERS + 64
@ -80,7 +89,7 @@ impl Config for Game {
async fn server_list_ping(
&self,
_server: &SharedServer,
_server: &SharedServer<Self>,
_remote_addr: SocketAddr,
) -> ServerListPing {
ServerListPing::Respond {
@ -91,18 +100,18 @@ impl Config for Game {
}
}
fn init(&self, server: &mut Server) {
let world = server.worlds.create(DimensionId::default()).1;
fn init(&self, server: &mut Server<Self>) {
let world = server.worlds.create(DimensionId::default(), ()).1;
world.meta.set_flat(true);
for chunk_z in -2..Integer::div_ceil(&(SIZE_X as i32), &16) + 2 {
for chunk_x in -2..Integer::div_ceil(&(SIZE_Z as i32), &16) + 2 {
world.chunks.create((chunk_x as i32, chunk_z as i32));
world.chunks.create((chunk_x as i32, chunk_z as i32), ());
}
}
}
fn update(&self, server: &mut Server) {
fn update(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
let spawn_pos = [
@ -111,13 +120,7 @@ impl Config for Game {
SIZE_Z as f64 / 2.0,
];
let State {
player_entities,
board,
board_buf,
} = &mut *self.state.lock().unwrap();
server.clients.retain(|client_id, client| {
server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() {
if self
.player_count
@ -142,14 +145,13 @@ impl Config for Game {
None,
);
player_entities.insert(
client_id,
server
.entities
.create_with_uuid(EntityKind::Player, client.uuid())
.unwrap()
.0,
);
let player_id = server
.entities
.create_with_uuid(EntityKind::Player, client.uuid(), ())
.unwrap()
.0;
client.data = ClientData { player: player_id };
client.send_message("Welcome to Conway's game of life in Minecraft!".italic());
client.send_message("Hold the left mouse button to bring blocks to life.".italic());
@ -157,8 +159,7 @@ impl Config for Game {
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
let id = player_entities.remove(&client_id).unwrap();
server.entities.delete(id);
server.entities.delete(client.data.player);
world.meta.player_list_mut().remove(client.uuid());
return false;
}
@ -166,11 +167,8 @@ impl Config for Game {
true
});
for (client_id, client) in server.clients.iter_mut() {
let player = server
.entities
.get_mut(player_entities[&client_id])
.unwrap();
for (_, client) in server.clients.iter_mut() {
let player = server.entities.get_mut(client.data.player).unwrap();
while let Some(event) = client.pop_event() {
match event {
@ -179,7 +177,8 @@ impl Config for Game {
&& (0..SIZE_Z as i32).contains(&position.z)
&& position.y == BOARD_Y
{
board[position.x as usize + position.z as usize * SIZE_X] = true;
server.data.board[position.x as usize + position.z as usize * SIZE_X] =
true;
}
}
Event::Movement { .. } => {
@ -195,29 +194,29 @@ impl Config for Game {
player.set_on_ground(client.on_ground());
}
Event::StartSneaking => {
if let EntityData::Player(e) = player.data_mut() {
if let EntityState::Player(e) = &mut player.state {
e.set_crouching(true);
e.set_pose(Pose::Sneaking);
}
}
Event::StopSneaking => {
if let EntityData::Player(e) = player.data_mut() {
if let EntityState::Player(e) = &mut player.state {
e.set_pose(Pose::Standing);
e.set_crouching(false);
}
}
Event::StartSprinting => {
if let EntityData::Player(e) = player.data_mut() {
if let EntityState::Player(e) = &mut player.state {
e.set_sprinting(true);
}
}
Event::StopSprinting => {
if let EntityData::Player(e) = player.data_mut() {
if let EntityState::Player(e) = &mut player.state {
e.set_sprinting(false);
}
}
Event::ArmSwing(hand) => {
if let EntityData::Player(e) = player.data_mut() {
if let EntityState::Player(e) = &mut player.state {
match hand {
Hand::Main => e.trigger_swing_main_arm(),
Hand::Off => e.trigger_swing_offhand(),
@ -233,31 +232,36 @@ impl Config for Game {
return;
}
board_buf.par_iter_mut().enumerate().for_each(|(i, cell)| {
let cx = (i % SIZE_X) as i32;
let cz = (i / SIZE_Z) as i32;
server
.data
.board_buf
.par_iter_mut()
.enumerate()
.for_each(|(i, cell)| {
let cx = (i % SIZE_X) as i32;
let cz = (i / SIZE_Z) as i32;
let mut live_count = 0;
for z in cz - 1..=cz + 1 {
for x in cx - 1..=cx + 1 {
if !(x == cx && z == cz) {
let i = x.rem_euclid(SIZE_X as i32) as usize
+ z.rem_euclid(SIZE_Z as i32) as usize * SIZE_X;
if board[i] {
live_count += 1;
let mut live_count = 0;
for z in cz - 1..=cz + 1 {
for x in cx - 1..=cx + 1 {
if !(x == cx && z == cz) {
let i = x.rem_euclid(SIZE_X as i32) as usize
+ z.rem_euclid(SIZE_Z as i32) as usize * SIZE_X;
if server.data.board[i] {
live_count += 1;
}
}
}
}
}
if board[cx as usize + cz as usize * SIZE_X] {
*cell = (2..=3).contains(&live_count);
} else {
*cell = live_count == 3;
}
});
if server.data.board[cx as usize + cz as usize * SIZE_X] {
*cell = (2..=3).contains(&live_count);
} else {
*cell = live_count == 3;
}
});
mem::swap(board, board_buf);
mem::swap(&mut server.data.board, &mut server.data.board_buf);
let min_y = server.shared.dimensions().next().unwrap().1.min_y;
@ -273,7 +277,7 @@ impl Config for Game {
let cell_z = chunk_z * 16 + z;
if cell_x < SIZE_X && cell_z < SIZE_Z {
let b = if board[cell_x + cell_z * SIZE_X] {
let b = if server.data.board[cell_x + cell_z * SIZE_X] {
BlockState::GRASS_BLOCK
} else {
BlockState::DIRT

View file

@ -1,7 +1,6 @@
use std::f64::consts::TAU;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use log::LevelFilter;
use valence::async_trait;
@ -21,23 +20,34 @@ pub fn main() -> ShutdownResult {
.parse_default_env()
.init();
valence::start_server(Game {
player_count: AtomicUsize::new(0),
cows: Mutex::new(Vec::new()),
})
valence::start_server(
Game {
player_count: AtomicUsize::new(0),
},
ServerData { cows: Vec::new() },
)
}
struct Game {
player_count: AtomicUsize,
cows: Mutex<Vec<EntityId>>,
}
struct ServerData {
cows: Vec<EntityId>,
}
const MAX_PLAYERS: usize = 10;
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -35);
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -25);
#[async_trait]
impl Config for Game {
type ChunkData = ();
type ClientData = ();
type EntityData = ();
type ServerData = ServerData;
type WorldData = ();
fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full.
MAX_PLAYERS + 64
@ -50,7 +60,7 @@ impl Config for Game {
async fn server_list_ping(
&self,
_server: &SharedServer,
_server: &SharedServer<Self>,
_remote_addr: SocketAddr,
) -> ServerListPing {
ServerListPing::Respond {
@ -61,27 +71,27 @@ impl Config for Game {
}
}
fn init(&self, server: &mut Server) {
let (world_id, world) = server.worlds.create(DimensionId::default());
fn init(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.create(DimensionId::default(), ());
world.meta.set_flat(true);
let size = 5;
for z in -size..size {
for x in -size..size {
world.chunks.create([x, z]);
world.chunks.create([x, z], ());
}
}
world.chunks.set_block_state(SPAWN_POS, BlockState::BEDROCK);
self.cows.lock().unwrap().extend((0..200).map(|_| {
let (id, e) = server.entities.create(EntityKind::Cow);
server.data.cows.extend((0..200).map(|_| {
let (id, e) = server.entities.create(EntityKind::Cow, ());
e.set_world(world_id);
id
}));
}
fn update(&self, server: &mut Server) {
fn update(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
server.clients.retain(|_, client| {
@ -122,10 +132,10 @@ impl Config for Game {
if client.is_disconnected() {
self.player_count.fetch_sub(1, Ordering::SeqCst);
world.meta.player_list_mut().remove(client.uuid());
false
} else {
true
return false;
}
true
});
let time = server.shared.current_tick() as f64 / server.shared.tick_rate() as f64;
@ -134,9 +144,6 @@ impl Config for Game {
.rotated_y(time * TAU * 0.2)
.rotated_z(time * TAU * 0.3);
let cows = self.cows.lock().unwrap();
let cow_count = cows.len();
let radius = 6.0 + ((time * TAU / 2.5).sin() + 1.0) / 2.0 * 10.0;
let player_pos = server
@ -149,11 +156,16 @@ impl Config for Game {
// TODO: hardcoded eye pos.
let eye_pos = Vec3::new(player_pos.x, player_pos.y + 1.6, player_pos.z);
#[allow(clippy::significant_drop_in_scrutinee)]
for (cow_id, p) in cows.iter().cloned().zip(fibonacci_spiral(cow_count)) {
for (cow_id, p) in server
.data
.cows
.iter()
.cloned()
.zip(fibonacci_spiral(server.data.cows.len()))
{
let cow = server.entities.get_mut(cow_id).expect("missing cow");
let rotated = p * rot;
let transformed = rotated * radius + [0.5, SPAWN_POS.y as f64 + 1.0, 0.5];
let transformed = rotated * radius + [0.5, SPAWN_POS.y as f64 + 2.0, 0.5];
let yaw = f32::atan2(rotated.z as f32, rotated.x as f32).to_degrees() - 90.0;
let (looking_yaw, looking_pitch) =

View file

@ -1,4 +1,3 @@
use std::collections::HashSet;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
@ -8,8 +7,9 @@ use valence::block::{BlockPos, BlockState};
use valence::client::GameMode;
use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId;
use valence::entity::{EntityData, EntityKind};
use valence::entity::{EntityKind, EntityState};
use valence::server::{Server, SharedServer, ShutdownResult};
use valence::spatial_index::RaycastHit;
use valence::text::{Color, TextFormat};
use valence::util::from_yaw_and_pitch;
use vek::Vec3;
@ -20,9 +20,12 @@ pub fn main() -> ShutdownResult {
.parse_default_env()
.init();
valence::start_server(Game {
player_count: AtomicUsize::new(0),
})
valence::start_server(
Game {
player_count: AtomicUsize::new(0),
},
(),
)
}
struct Game {
@ -31,12 +34,19 @@ struct Game {
const MAX_PLAYERS: usize = 10;
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -8);
const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -5);
const PLAYER_EYE_HEIGHT: f64 = 1.6;
#[async_trait]
impl Config for Game {
type ChunkData = ();
type ClientData = ();
/// `true` for entities that have been intersected with.
type EntityData = bool;
type ServerData = ();
type WorldData = ();
fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full.
MAX_PLAYERS + 64
@ -49,7 +59,7 @@ impl Config for Game {
async fn server_list_ping(
&self,
_server: &SharedServer,
_server: &SharedServer<Self>,
_remote_addr: SocketAddr,
) -> ServerListPing {
ServerListPing::Respond {
@ -60,14 +70,14 @@ impl Config for Game {
}
}
fn init(&self, server: &mut Server) {
let (world_id, world) = server.worlds.create(DimensionId::default());
fn init(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.create(DimensionId::default(), ());
world.meta.set_flat(true);
let size = 5;
for z in -size..size {
for x in -size..size {
world.chunks.create([x, z]);
world.chunks.create([x, z], ());
}
}
@ -77,24 +87,17 @@ impl Config for Game {
for i in 0..SHEEP_COUNT {
let offset = (i as f64 - (SHEEP_COUNT - 1) as f64 / 2.0) * 1.25;
let (_, sheep) = server.entities.create(EntityKind::Sheep);
let (_, sheep) = server.entities.create(EntityKind::Sheep, false);
sheep.set_world(world_id);
sheep.set_position([offset + 0.5, SPAWN_POS.y as f64 + 1.0, 0.0]);
sheep.set_yaw(180.0);
sheep.set_head_yaw(180.0);
if let EntityData::Sheep(sheep) = sheep.data_mut() {
// Shear the sheep.
sheep.set_sheep_state(0b1_0000);
}
}
}
fn update(&self, server: &mut Server) {
fn update(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
let mut hit_entities = HashSet::new();
server.clients.retain(|_, client| {
if client.created_tick() == server.shared.current_tick() {
if self
@ -133,7 +136,7 @@ impl Config for Game {
"Look at a sheep to change its ".italic()
+ "color".italic().color(Color::GREEN)
+ ".",
)
);
}
if client.is_disconnected() {
@ -146,27 +149,31 @@ impl Config for Game {
let origin = Vec3::new(client_pos.x, client_pos.y + PLAYER_EYE_HEIGHT, client_pos.z);
let direction = from_yaw_and_pitch(client.yaw() as f64, client.pitch() as f64);
if let Some(hit) = world.spatial_index.raycast(origin, direction, |hit| {
let only_sheep = |hit: &RaycastHit| {
server
.entities
.get(hit.entity)
.map_or(false, |e| e.kind() == EntityKind::Sheep)
}) {
hit_entities.insert(hit.entity);
};
if let Some(hit) = world.spatial_index.raycast(origin, direction, only_sheep) {
if let Some(e) = server.entities.get_mut(hit.entity) {
e.data = true;
}
}
true
});
for (id, e) in server.entities.iter_mut() {
if let EntityData::Sheep(sheep) = e.data_mut() {
if hit_entities.contains(&id) {
for (_, e) in server.entities.iter_mut() {
if let EntityState::Sheep(sheep) = &mut e.state {
if e.data {
sheep.set_sheep_state(5);
} else {
sheep.set_sheep_state(0);
}
}
e.data = false;
}
}
}

View file

@ -24,14 +24,17 @@ pub fn main() -> ShutdownResult {
let seed = rand::random();
valence::start_server(Game {
player_count: AtomicUsize::new(0),
density_noise: SuperSimplex::new().set_seed(seed),
hilly_noise: SuperSimplex::new().set_seed(seed.wrapping_add(1)),
stone_noise: SuperSimplex::new().set_seed(seed.wrapping_add(2)),
gravel_noise: SuperSimplex::new().set_seed(seed.wrapping_add(3)),
grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)),
})
valence::start_server(
Game {
player_count: AtomicUsize::new(0),
density_noise: SuperSimplex::new().set_seed(seed),
hilly_noise: SuperSimplex::new().set_seed(seed.wrapping_add(1)),
stone_noise: SuperSimplex::new().set_seed(seed.wrapping_add(2)),
gravel_noise: SuperSimplex::new().set_seed(seed.wrapping_add(3)),
grass_noise: SuperSimplex::new().set_seed(seed.wrapping_add(4)),
},
(),
)
}
struct Game {
@ -47,6 +50,12 @@ const MAX_PLAYERS: usize = 10;
#[async_trait]
impl Config for Game {
type ChunkData = ();
type ClientData = ();
type EntityData = ();
type ServerData = ();
type WorldData = ();
fn max_connections(&self) -> usize {
// We want status pings to be successful even if the server is full.
MAX_PLAYERS + 64
@ -59,7 +68,7 @@ impl Config for Game {
async fn server_list_ping(
&self,
_server: &SharedServer,
_server: &SharedServer<Self>,
_remote_addr: SocketAddr,
) -> ServerListPing {
ServerListPing::Respond {
@ -70,12 +79,12 @@ impl Config for Game {
}
}
fn init(&self, server: &mut Server) {
let (_, world) = server.worlds.create(DimensionId::default());
fn init(&self, server: &mut Server<Self>) {
let (_, world) = server.worlds.create(DimensionId::default(), ());
world.meta.set_flat(true);
}
fn update(&self, server: &mut Server) {
fn update(&self, server: &mut Server<Self>) {
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
let mut chunks_to_unload = HashSet::<_>::from_iter(world.chunks.iter().map(|t| t.0));
@ -126,7 +135,7 @@ impl Config for Game {
for pos in chunks_in_view_distance(ChunkPos::at(p.x, p.z), dist) {
chunks_to_unload.remove(&pos);
if world.chunks.get(pos).is_none() {
world.chunks.create(pos);
world.chunks.create(pos, ());
}
}

View file

@ -15,6 +15,7 @@ use crate::biome::BiomeId;
use crate::block::BlockState;
use crate::block_pos::BlockPos;
pub use crate::chunk_pos::ChunkPos;
use crate::config::Config;
use crate::dimension::DimensionId;
use crate::protocol_inner::packets::play::s2c::{
BlockUpdate, LevelChunkHeightmaps, LevelChunkWithLight, S2cPlayPacket, SectionBlocksUpdate,
@ -24,14 +25,14 @@ use crate::server::SharedServer;
use crate::Ticks;
/// A container for all [`Chunks`]s in a [`World`](crate::world::World).
pub struct Chunks {
chunks: HashMap<ChunkPos, Chunk>,
server: SharedServer,
pub struct Chunks<C: Config> {
chunks: HashMap<ChunkPos, Chunk<C>>,
server: SharedServer<C>,
dimension: DimensionId,
}
impl Chunks {
pub(crate) fn new(server: SharedServer, dimension: DimensionId) -> Self {
impl<C: Config> Chunks<C> {
pub(crate) fn new(server: SharedServer<C>, dimension: DimensionId) -> Self {
Self {
chunks: HashMap::new(),
server,
@ -49,9 +50,9 @@ impl Chunks {
/// adjacent to it must also be loaded. It is also important that clients
/// are not spawned within unloaded chunks via
/// [`spawn`](crate::client::Client::spawn).
pub fn create(&mut self, pos: impl Into<ChunkPos>) -> &mut Chunk {
pub fn create(&mut self, pos: impl Into<ChunkPos>, data: C::ChunkData) -> &mut Chunk<C> {
let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
let chunk = Chunk::new(section_count, self.server.current_tick());
let chunk = Chunk::new(section_count, self.server.current_tick(), data);
match self.chunks.entry(pos.into()) {
Entry::Occupied(mut oe) => {
@ -78,14 +79,14 @@ impl Chunks {
/// Gets a shared reference to the chunk at the provided position.
///
/// If there is no chunk at the position, then `None` is returned.
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk> {
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk<C>> {
self.chunks.get(&pos.into())
}
/// Gets an exclusive reference to the chunk at the provided position.
///
/// If there is no chunk at the position, then `None` is returned.
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk> {
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk<C>> {
self.chunks.get_mut(&pos.into())
}
@ -96,25 +97,25 @@ impl Chunks {
/// Returns an immutable iterator over all chunks in the world in an
/// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk<C>)> + Clone + '_ {
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
}
/// Returns a mutable iterator over all chunks in the world in an
/// unspecified order.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk<C>)> + '_ {
self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk))
}
/// Returns a parallel immutable iterator over all chunks in the world in an
/// unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk<C>)> + Clone + '_ {
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
}
/// Returns a parallel mutable iterator over all chunks in the world in an
/// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk)> + '_ {
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk<C>)> + '_ {
self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk))
}
@ -183,7 +184,9 @@ impl Chunks {
///
/// In addition to blocks, chunks also contain [biomes](crate::biome::Biome).
/// Every 4x4x4 segment of blocks in a chunk corresponds to a biome.
pub struct Chunk {
pub struct Chunk<C: Config> {
/// Custom data.
pub data: C::ChunkData,
sections: Box<[ChunkSection]>,
// TODO block_entities: HashMap<u32, BlockEntity>,
/// The MOTION_BLOCKING heightmap
@ -191,8 +194,8 @@ pub struct Chunk {
created_tick: Ticks,
}
impl Chunk {
pub(crate) fn new(section_count: u32, current_tick: Ticks) -> Self {
impl<C: Config> Chunk<C> {
pub(crate) fn new(section_count: u32, current_tick: Ticks, data: C::ChunkData) -> Self {
let sect = ChunkSection {
blocks: [BlockState::AIR.to_raw(); 4096],
modified_count: 1, // Must be >0 so the chunk is initialized.
@ -201,6 +204,7 @@ impl Chunk {
};
let mut chunk = Self {
data,
sections: vec![sect; section_count as usize].into(),
heightmap: Vec::new(),
created_tick: current_tick,
@ -547,35 +551,3 @@ fn encode_paletted_container(
fn log2_ceil(n: usize) -> usize {
n.next_power_of_two().trailing_zeros() as usize
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_get() {
let mut chunk = Chunk::new(16, 0);
chunk.set_block_state(1, 2, 3, BlockState::CAKE);
assert_eq!(chunk.get_block_state(1, 2, 3), BlockState::CAKE);
chunk.set_biome(1, 2, 3, BiomeId(7));
assert_eq!(chunk.get_biome(1, 2, 3), BiomeId(7));
}
#[test]
#[should_panic]
fn block_state_oob() {
let mut chunk = Chunk::new(16, 0);
chunk.set_block_state(16, 0, 0, BlockState::CAKE);
}
#[test]
#[should_panic]
fn biome_oob() {
let mut chunk = Chunk::new(16, 0);
chunk.set_biome(4, 0, 0, BiomeId(0));
}
}

View file

@ -16,6 +16,7 @@ use vek::Vec3;
use crate::biome::Biome;
use crate::block_pos::BlockPos;
use crate::chunk_pos::ChunkPos;
use crate::config::Config;
use crate::dimension::DimensionId;
use crate::entity::types::Player;
use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind};
@ -47,16 +48,16 @@ use crate::{ident, Ticks, LIBRARY_NAMESPACE};
/// New clients are automatically inserted into this container but
/// are not automatically deleted. It is your responsibility to delete them once
/// they disconnect. This can be checked with [`Client::is_disconnected`].
pub struct Clients {
sm: SlotMap<Client>,
pub struct Clients<C: Config> {
sm: SlotMap<Client<C>>,
}
impl Clients {
impl<C: Config> Clients<C> {
pub(crate) fn new() -> Self {
Self { sm: SlotMap::new() }
}
pub(crate) fn insert(&mut self, client: Client) -> (ClientId, &mut Client) {
pub(crate) fn insert(&mut self, client: Client<C>) -> (ClientId, &mut Client<C>) {
let (id, client) = self.sm.insert(client);
(ClientId(id), client)
}
@ -73,7 +74,7 @@ impl Clients {
/// which `f` returns `true`.
///
/// All clients are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client) -> bool) {
pub fn retain(&mut self, mut f: impl FnMut(ClientId, &mut Client<C>) -> bool) {
self.sm.retain(|k, v| f(ClientId(k), v))
}
@ -85,37 +86,39 @@ impl Clients {
/// Returns a shared reference to the client with the given ID. If
/// the ID is invalid, then `None` is returned.
pub fn get(&self, client: ClientId) -> Option<&Client> {
pub fn get(&self, client: ClientId) -> Option<&Client<C>> {
self.sm.get(client.0)
}
/// Returns an exclusive reference to the client with the given ID. If the
/// ID is invalid, then `None` is returned.
pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client> {
pub fn get_mut(&mut self, client: ClientId) -> Option<&mut Client<C>> {
self.sm.get_mut(client.0)
}
/// Returns an immutable iterator over all clients on the server in an
/// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client)> + Clone + '_ {
pub fn iter(&self) -> impl FusedIterator<Item = (ClientId, &Client<C>)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (ClientId(k), v))
}
/// Returns a mutable iterator over all clients on the server in an
/// unspecified order.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client)> + '_ {
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ClientId, &mut Client<C>)> + '_ {
self.sm.iter_mut().map(|(k, v)| (ClientId(k), v))
}
/// Returns a parallel immutable iterator over all clients on the server in
/// an unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client)> + Clone + '_ {
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ClientId, &Client<C>)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (ClientId(k), v))
}
/// Returns a parallel mutable iterator over all clients on the server in an
/// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ClientId, &mut Client)> + '_ {
pub fn par_iter_mut(
&mut self,
) -> impl ParallelIterator<Item = (ClientId, &mut Client<C>)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (ClientId(k), v))
}
}
@ -158,7 +161,9 @@ impl ClientId {
///
/// In Valence however, clients and players have been decoupled. This separation
/// was done primarily to enable multithreaded client updates.
pub struct Client {
pub struct Client<C: Config> {
/// Custom data.
pub data: C::ClientData,
/// Setting this to `None` disconnects the client.
send: SendOpt,
recv: Receiver<C2sPlayPacket>,
@ -227,15 +232,17 @@ pub(crate) struct ClientFlags {
_pad: u8,
}
impl Client {
impl<C: Config> Client<C> {
pub(crate) fn new(
packet_channels: C2sPacketChannels,
server: &SharedServer,
server: &SharedServer<C>,
ncd: NewClientData,
data: C::ClientData,
) -> Self {
let (send, recv) = packet_channels;
Self {
data,
send: Some(send),
recv,
created_tick: server.current_tick(),
@ -584,16 +591,16 @@ impl Client {
send_packet(&mut self.send, packet);
}
pub(crate) fn handle_serverbound_packets(&mut self, entities: &Entities) {
pub(crate) fn handle_serverbound_packets(&mut self, entities: &Entities<C>) {
self.events.clear();
for _ in 0..self.recv.len() {
self.handle_serverbound_packet(entities, self.recv.try_recv().unwrap());
}
}
fn handle_serverbound_packet(&mut self, entities: &Entities, pkt: C2sPlayPacket) {
fn handle_movement_packet(
client: &mut Client,
fn handle_serverbound_packet(&mut self, entities: &Entities<C>, pkt: C2sPlayPacket) {
fn handle_movement_packet<C: Config>(
client: &mut Client<C>,
_vehicle: bool,
new_position: Vec3<f64>,
new_yaw: f32,
@ -835,7 +842,12 @@ impl Client {
}
}
pub(crate) fn update(&mut self, shared: &SharedServer, entities: &Entities, worlds: &Worlds) {
pub(crate) fn update(
&mut self,
shared: &SharedServer<C>,
entities: &Entities<C>,
worlds: &Worlds<C>,
) {
// Mark the client as disconnected when appropriate.
if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) {
self.send = None;
@ -1298,8 +1310,8 @@ fn send_packet(send_opt: &mut SendOpt, pkt: impl Into<S2cPlayPacket>) {
}
}
fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) {
for &code in entity.data().event_codes() {
fn send_entity_events<C: Config>(send_opt: &mut SendOpt, id: EntityId, entity: &Entity<C>) {
for &code in entity.state.event_codes() {
if code <= ENTITY_EVENT_MAX_BOUND as u8 {
send_packet(
send_opt,
@ -1320,7 +1332,7 @@ fn send_entity_events(send_opt: &mut SendOpt, id: EntityId, entity: &Entity) {
}
}
fn make_registry_codec(shared: &SharedServer) -> RegistryCodec {
fn make_registry_codec<C: Config>(shared: &SharedServer<C>) -> RegistryCodec {
let mut dims = Vec::new();
for (id, dim) in shared.dimensions() {
let id = id.0 as i32;

View file

@ -1,6 +1,5 @@
//! Configuration for the server.
use std::any::Any;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::panic::{RefUnwindSafe, UnwindSafe};
@ -13,14 +12,7 @@ use crate::server::{NewClientData, Server, SharedServer};
use crate::text::Text;
use crate::Ticks;
/// A trait containing callbacks which are invoked by the running Minecraft
/// server.
///
/// The config is used from multiple threads and must therefore implement
/// [`Send`] and [`Sync`]. From within a single thread, methods are never
/// invoked recursively by the library. In other words, a mutex can be aquired
/// at the beginning of a method and released at the end without risk of
/// deadlocking.
/// A trait for the configuration of a server.
///
/// This trait uses the [async_trait] attribute macro. It is exported at the
/// root of this crate.
@ -28,10 +20,21 @@ use crate::Ticks;
/// [async_trait]: https://docs.rs/async-trait/latest/async_trait/
#[async_trait]
#[allow(unused_variables)]
pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// Called once at startup to get the maximum number of connections allowed
/// to the server. Note that this includes all connections, not just those
/// past the login stage.
pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe {
/// Custom data to store with the [`Server`].
type ServerData: Send + Sync;
/// Custom data to store with every [`Client`](crate::client::Client).
type ClientData: Default + Send + Sync;
/// Custom data to store with every [`Entity`](crate::entity::Entity).
type EntityData: Send + Sync;
/// Custom data to store with every [`World`](crate::world::World).
type WorldData: Send + Sync;
/// Custom data to store with every [`Chunk`](crate::chunk::Chunk).
type ChunkData: Send + Sync;
/// Called once at startup to get the maximum number of simultaneous
/// connections allowed to the server. This includes all
/// connections, not just those past the login stage.
///
/// You will want this value to be somewhere above the maximum number of
/// players, since status pings should still succeed even when the server is
@ -169,7 +172,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// The query is ignored.
async fn server_list_ping(
&self,
shared: &SharedServer,
shared: &SharedServer<Self>,
remote_addr: SocketAddr,
) -> ServerListPing {
ServerListPing::Ignore
@ -192,7 +195,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// The client is allowed to join unconditionally.
///
/// [`Clients`]: crate::client::Clients
async fn login(&self, shared: &SharedServer, ncd: &NewClientData) -> Result<(), Text> {
async fn login(&self, shared: &SharedServer<Self>, ncd: &NewClientData) -> Result<(), Text> {
Ok(())
}
@ -203,7 +206,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// no connections to the server will be made until this function returns.
///
/// This method is called from within a tokio runtime.
fn init(&self, server: &mut Server) {}
fn init(&self, server: &mut Server<Self>) {}
/// Called once at the beginning of every server update (also known as
/// "tick"). This is likely where the majority of your code will be.
@ -215,7 +218,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// # Default Implementation
///
/// The default implementation does nothing.
fn update(&self, server: &mut Server);
fn update(&self, server: &mut Server<Self>);
}
/// The result of the [`server_list_ping`](Config::server_list_ping) callback.

View file

@ -1,6 +1,6 @@
//! Dynamic actors in a world.
pub mod data;
pub mod state;
pub mod types;
use std::collections::hash_map::Entry;
@ -10,10 +10,11 @@ use std::num::NonZeroU32;
use bitfield_struct::bitfield;
use rayon::iter::ParallelIterator;
pub use types::{EntityData, EntityKind};
pub use types::{EntityKind, EntityState};
use uuid::Uuid;
use vek::{Aabb, Vec3};
use crate::config::Config;
use crate::protocol_inner::packets::play::s2c::{
AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata,
};
@ -32,13 +33,13 @@ use crate::world::WorldId;
///
/// [`Player`]: crate::entity::types::Player
/// [`PlayerList`]: crate::player_list::PlayerList
pub struct Entities {
sm: SlotMap<Entity>,
pub struct Entities<C: Config> {
sm: SlotMap<Entity<C>>,
uuid_to_entity: HashMap<Uuid, EntityId>,
network_id_to_entity: HashMap<NonZeroU32, u32>,
}
impl Entities {
impl<C: Config> Entities<C> {
pub(crate) fn new() -> Self {
Self {
sm: SlotMap::new(),
@ -49,8 +50,8 @@ impl Entities {
/// Spawns a new entity with a random UUID. A reference to the entity along
/// with its ID is returned.
pub fn create(&mut self, kind: EntityKind) -> (EntityId, &mut Entity) {
self.create_with_uuid(kind, Uuid::from_bytes(rand::random()))
pub fn create(&mut self, kind: EntityKind, data: C::EntityData) -> (EntityId, &mut Entity<C>) {
self.create_with_uuid(kind, Uuid::from_bytes(rand::random()), data)
.expect("UUID collision")
}
@ -63,13 +64,15 @@ impl Entities {
&mut self,
kind: EntityKind,
uuid: Uuid,
) -> Option<(EntityId, &mut Entity)> {
data: C::EntityData,
) -> Option<(EntityId, &mut Entity<C>)> {
match self.uuid_to_entity.entry(uuid) {
Entry::Occupied(_) => None,
Entry::Vacant(ve) => {
let (k, e) = self.sm.insert(Entity {
data,
state: EntityState::new(kind),
flags: EntityFlags(0),
data: EntityData::new(kind),
world: WorldId::NULL,
new_position: Vec3::default(),
old_position: Vec3::default(),
@ -113,7 +116,7 @@ impl Entities {
/// Removes all entities from the server for which `f` returns `true`.
///
/// All entities are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity) -> bool) {
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity<C>) -> bool) {
self.sm.retain(|k, v| {
if f(EntityId(k), v) {
true
@ -147,14 +150,14 @@ impl Entities {
/// Gets a shared reference to the entity with the given [`EntityId`].
///
/// If the ID is invalid, `None` is returned.
pub fn get(&self, entity: EntityId) -> Option<&Entity> {
pub fn get(&self, entity: EntityId) -> Option<&Entity<C>> {
self.sm.get(entity.0)
}
/// Gets an exclusive reference to the entity with the given [`EntityId`].
///
/// If the ID is invalid, `None` is returned.
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity> {
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity<C>> {
self.sm.get_mut(entity.0)
}
@ -166,32 +169,34 @@ impl Entities {
/// Returns an immutable iterator over all entities on the server in an
/// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity<C>)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (EntityId(k), v))
}
/// Returns a mutable iterator over all entities on the server in an
/// unspecified order.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity)> + '_ {
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (EntityId, &mut Entity<C>)> + '_ {
self.sm.iter_mut().map(|(k, v)| (EntityId(k), v))
}
/// Returns a parallel immutable iterator over all entities on the server in
/// an unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity)> + Clone + '_ {
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity<C>)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (EntityId(k), v))
}
/// Returns a parallel mutable iterator over all clients on the server in an
/// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (EntityId, &mut Entity)> + '_ {
pub fn par_iter_mut(
&mut self,
) -> impl ParallelIterator<Item = (EntityId, &mut Entity<C>)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (EntityId(k), v))
}
pub(crate) fn update(&mut self) {
for (_, e) in self.iter_mut() {
e.old_position = e.new_position;
e.data.clear_modifications();
e.state.clear_modifications();
e.flags.set_yaw_or_pitch_modified(false);
e.flags.set_head_yaw_modified(false);
@ -230,9 +235,12 @@ impl EntityId {
/// this struct. This includes position, rotation, velocity, UUID, and hitbox.
/// To access data that is not common to every kind of entity, see
/// [`Self::data`].
pub struct Entity {
pub struct Entity<C: Config> {
/// Custom data.
pub data: C::EntityData,
/// Kind-specific state for this entity.
pub state: EntityState,
flags: EntityFlags,
data: EntityData,
world: WorldId,
new_position: Vec3<f64>,
old_position: Vec3<f64>,
@ -253,25 +261,14 @@ pub(crate) struct EntityFlags {
_pad: u8,
}
impl Entity {
impl<C: Config> Entity<C> {
pub(crate) fn flags(&self) -> EntityFlags {
self.flags
}
/// Gets a reference to this entity's [`EntityData`].
pub fn data(&self) -> &EntityData {
&self.data
}
/// Gets a mutable reference to this entity's
/// [`EntityData`].
pub fn data_mut(&mut self) -> &mut EntityData {
&mut self.data
}
/// Gets the [`EntityKind`] of this entity.
pub fn kind(&self) -> EntityKind {
self.data.kind()
self.state.kind()
}
/// Gets the [`WorldId`](crate::world::WorldId) of the world this entity is
@ -389,18 +386,18 @@ impl Entity {
///
/// [interact event]: crate::client::Event::InteractWithEntity
pub fn hitbox(&self) -> Aabb<f64> {
let dims = match &self.data {
EntityData::Allay(_) => [0.6, 0.35, 0.6],
EntityData::ChestBoat(_) => [1.375, 0.5625, 1.375],
EntityData::Frog(_) => [0.5, 0.5, 0.5],
EntityData::Tadpole(_) => [0.4, 0.3, 0.4],
EntityData::Warden(_) => [0.9, 2.9, 0.9],
EntityData::AreaEffectCloud(e) => [
let dims = match &self.state {
EntityState::Allay(_) => [0.6, 0.35, 0.6],
EntityState::ChestBoat(_) => [1.375, 0.5625, 1.375],
EntityState::Frog(_) => [0.5, 0.5, 0.5],
EntityState::Tadpole(_) => [0.4, 0.3, 0.4],
EntityState::Warden(_) => [0.9, 2.9, 0.9],
EntityState::AreaEffectCloud(e) => [
e.get_radius() as f64 * 2.0,
0.5,
e.get_radius() as f64 * 2.0,
],
EntityData::ArmorStand(e) => {
EntityState::ArmorStand(e) => {
if e.get_marker() {
[0.0, 0.0, 0.0]
} else if e.get_small() {
@ -409,123 +406,123 @@ impl Entity {
[0.5, 1.975, 0.5]
}
}
EntityData::Arrow(_) => [0.5, 0.5, 0.5],
EntityData::Axolotl(_) => [1.3, 0.6, 1.3],
EntityData::Bat(_) => [0.5, 0.9, 0.5],
EntityData::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size?
EntityData::Blaze(_) => [0.6, 1.8, 0.6],
EntityData::Boat(_) => [1.375, 0.5625, 1.375],
EntityData::Cat(_) => [0.6, 0.7, 0.6],
EntityData::CaveSpider(_) => [0.7, 0.5, 0.7],
EntityData::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size?
EntityData::Cod(_) => [0.5, 0.3, 0.5],
EntityData::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityData::Creeper(_) => [0.6, 1.7, 0.6],
EntityData::Dolphin(_) => [0.9, 0.6, 0.9],
EntityData::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size?
EntityData::DragonFireball(_) => [1.0, 1.0, 1.0],
EntityData::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::ElderGuardian(_) => [1.9975, 1.9975, 1.9975],
EntityData::EndCrystal(_) => [2.0, 2.0, 2.0],
EntityData::EnderDragon(_) => [16.0, 8.0, 16.0],
EntityData::Enderman(_) => [0.6, 2.9, 0.6],
EntityData::Endermite(_) => [0.4, 0.3, 0.4],
EntityData::Evoker(_) => [0.6, 1.95, 0.6],
EntityData::EvokerFangs(_) => [0.5, 0.8, 0.5],
EntityData::ExperienceOrb(_) => [0.5, 0.5, 0.5],
EntityData::EyeOfEnder(_) => [0.25, 0.25, 0.25],
EntityData::FallingBlock(_) => [0.98, 0.98, 0.98],
EntityData::FireworkRocket(_) => [0.25, 0.25, 0.25],
EntityData::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityData::Ghast(_) => [4.0, 4.0, 4.0],
EntityData::Giant(_) => [3.6, 12.0, 3.6],
EntityData::GlowItemFrame(_) => todo!("account for rotation"),
EntityData::GlowSquid(_) => [0.8, 0.8, 0.8],
EntityData::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size?
EntityData::Guardian(_) => [0.85, 0.85, 0.85],
EntityData::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityData::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityData::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::Illusioner(_) => [0.6, 1.95, 0.6],
EntityData::IronGolem(_) => [1.4, 2.7, 1.4],
EntityData::Item(_) => [0.25, 0.25, 0.25],
EntityData::ItemFrame(_) => todo!("account for rotation"),
EntityData::Fireball(_) => [1.0, 1.0, 1.0],
EntityData::LeashKnot(_) => [0.375, 0.5, 0.375],
EntityData::LightningBolt(_) => [0.0, 0.0, 0.0],
EntityData::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size?
EntityData::LlamaSpit(_) => [0.25, 0.25, 0.25],
EntityData::MagmaCube(e) => {
EntityState::Arrow(_) => [0.5, 0.5, 0.5],
EntityState::Axolotl(_) => [1.3, 0.6, 1.3],
EntityState::Bat(_) => [0.5, 0.9, 0.5],
EntityState::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size?
EntityState::Blaze(_) => [0.6, 1.8, 0.6],
EntityState::Boat(_) => [1.375, 0.5625, 1.375],
EntityState::Cat(_) => [0.6, 0.7, 0.6],
EntityState::CaveSpider(_) => [0.7, 0.5, 0.7],
EntityState::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size?
EntityState::Cod(_) => [0.5, 0.3, 0.5],
EntityState::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityState::Creeper(_) => [0.6, 1.7, 0.6],
EntityState::Dolphin(_) => [0.9, 0.6, 0.9],
EntityState::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size?
EntityState::DragonFireball(_) => [1.0, 1.0, 1.0],
EntityState::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::ElderGuardian(_) => [1.9975, 1.9975, 1.9975],
EntityState::EndCrystal(_) => [2.0, 2.0, 2.0],
EntityState::EnderDragon(_) => [16.0, 8.0, 16.0],
EntityState::Enderman(_) => [0.6, 2.9, 0.6],
EntityState::Endermite(_) => [0.4, 0.3, 0.4],
EntityState::Evoker(_) => [0.6, 1.95, 0.6],
EntityState::EvokerFangs(_) => [0.5, 0.8, 0.5],
EntityState::ExperienceOrb(_) => [0.5, 0.5, 0.5],
EntityState::EyeOfEnder(_) => [0.25, 0.25, 0.25],
EntityState::FallingBlock(_) => [0.98, 0.98, 0.98],
EntityState::FireworkRocket(_) => [0.25, 0.25, 0.25],
EntityState::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityState::Ghast(_) => [4.0, 4.0, 4.0],
EntityState::Giant(_) => [3.6, 12.0, 3.6],
EntityState::GlowItemFrame(_) => todo!("account for rotation"),
EntityState::GlowSquid(_) => [0.8, 0.8, 0.8],
EntityState::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size?
EntityState::Guardian(_) => [0.85, 0.85, 0.85],
EntityState::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityState::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityState::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::Illusioner(_) => [0.6, 1.95, 0.6],
EntityState::IronGolem(_) => [1.4, 2.7, 1.4],
EntityState::Item(_) => [0.25, 0.25, 0.25],
EntityState::ItemFrame(_) => todo!("account for rotation"),
EntityState::Fireball(_) => [1.0, 1.0, 1.0],
EntityState::LeashKnot(_) => [0.375, 0.5, 0.375],
EntityState::LightningBolt(_) => [0.0, 0.0, 0.0],
EntityState::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size?
EntityState::LlamaSpit(_) => [0.25, 0.25, 0.25],
EntityState::MagmaCube(e) => {
let s = e.get_size() as f64 * 0.51000005;
[s, s, s]
}
EntityData::Marker(_) => [0.0, 0.0, 0.0],
EntityData::Minecart(_) => [0.98, 0.7, 0.98],
EntityData::ChestMinecart(_) => [0.98, 0.7, 0.98],
EntityData::CommandBlockMinecart(_) => [0.98, 0.7, 0.98],
EntityData::FurnaceMinecart(_) => [0.98, 0.7, 0.98],
EntityData::HopperMinecart(_) => [0.98, 0.7, 0.98],
EntityData::SpawnerMinecart(_) => [0.98, 0.7, 0.98],
EntityData::TntMinecart(_) => [0.98, 0.7, 0.98],
EntityData::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityData::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityData::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityData::Painting(_) => todo!("account for rotation and type"),
EntityData::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityData::Parrot(_) => [0.5, 0.9, 0.5],
EntityData::Phantom(_) => [0.9, 0.5, 0.9],
EntityData::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size?
EntityData::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::PiglinBrute(_) => [0.6, 1.95, 0.6],
EntityData::Pillager(_) => [0.6, 1.95, 0.6],
EntityData::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size?
EntityData::Tnt(_) => [0.98, 0.98, 0.98],
EntityData::Pufferfish(_) => [0.7, 0.7, 0.7],
EntityData::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size?
EntityData::Ravager(_) => [1.95, 2.2, 1.95],
EntityData::Salmon(_) => [0.7, 0.4, 0.7],
EntityData::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size?
EntityData::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated?
EntityData::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125],
EntityData::Silverfish(_) => [0.4, 0.3, 0.4],
EntityData::Skeleton(_) => [0.6, 1.99, 0.6],
EntityData::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityData::Slime(e) => {
EntityState::Marker(_) => [0.0, 0.0, 0.0],
EntityState::Minecart(_) => [0.98, 0.7, 0.98],
EntityState::ChestMinecart(_) => [0.98, 0.7, 0.98],
EntityState::CommandBlockMinecart(_) => [0.98, 0.7, 0.98],
EntityState::FurnaceMinecart(_) => [0.98, 0.7, 0.98],
EntityState::HopperMinecart(_) => [0.98, 0.7, 0.98],
EntityState::SpawnerMinecart(_) => [0.98, 0.7, 0.98],
EntityState::TntMinecart(_) => [0.98, 0.7, 0.98],
EntityState::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityState::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityState::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityState::Painting(_) => todo!("account for rotation and type"),
EntityState::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityState::Parrot(_) => [0.5, 0.9, 0.5],
EntityState::Phantom(_) => [0.9, 0.5, 0.9],
EntityState::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size?
EntityState::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::PiglinBrute(_) => [0.6, 1.95, 0.6],
EntityState::Pillager(_) => [0.6, 1.95, 0.6],
EntityState::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size?
EntityState::Tnt(_) => [0.98, 0.98, 0.98],
EntityState::Pufferfish(_) => [0.7, 0.7, 0.7],
EntityState::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size?
EntityState::Ravager(_) => [1.95, 2.2, 1.95],
EntityState::Salmon(_) => [0.7, 0.4, 0.7],
EntityState::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size?
EntityState::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated?
EntityState::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125],
EntityState::Silverfish(_) => [0.4, 0.3, 0.4],
EntityState::Skeleton(_) => [0.6, 1.99, 0.6],
EntityState::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityState::Slime(e) => {
let s = 0.51000005 * e.get_size() as f64;
[s, s, s]
}
EntityData::SmallFireball(_) => [0.3125, 0.3125, 0.3125],
EntityData::SnowGolem(_) => [0.7, 1.9, 0.7],
EntityData::Snowball(_) => [0.25, 0.25, 0.25],
EntityData::SpectralArrow(_) => [0.5, 0.5, 0.5],
EntityData::Spider(_) => [1.4, 0.9, 1.4],
EntityData::Squid(_) => [0.8, 0.8, 0.8],
EntityData::Stray(_) => [0.6, 1.99, 0.6],
EntityData::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size?
EntityData::Egg(_) => [0.25, 0.25, 0.25],
EntityData::EnderPearl(_) => [0.25, 0.25, 0.25],
EntityData::ExperienceBottle(_) => [0.25, 0.25, 0.25],
EntityData::Potion(_) => [0.25, 0.25, 0.25],
EntityData::Trident(_) => [0.5, 0.5, 0.5],
EntityData::TraderLlama(_) => [0.9, 1.87, 0.9],
EntityData::TropicalFish(_) => [0.5, 0.4, 0.5],
EntityData::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size?
EntityData::Vex(_) => [0.4, 0.8, 0.4],
EntityData::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::Vindicator(_) => [0.6, 1.95, 0.6],
EntityData::WanderingTrader(_) => [0.6, 1.95, 0.6],
EntityData::Witch(_) => [0.6, 1.95, 0.6],
EntityData::Wither(_) => [0.9, 3.5, 0.9],
EntityData::WitherSkeleton(_) => [0.7, 2.4, 0.7],
EntityData::WitherSkull(_) => [0.3125, 0.3125, 0.3125],
EntityData::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size?
EntityData::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityData::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityData::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityData::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose.
EntityData::FishingBobber(_) => [0.25, 0.25, 0.25],
EntityState::SmallFireball(_) => [0.3125, 0.3125, 0.3125],
EntityState::SnowGolem(_) => [0.7, 1.9, 0.7],
EntityState::Snowball(_) => [0.25, 0.25, 0.25],
EntityState::SpectralArrow(_) => [0.5, 0.5, 0.5],
EntityState::Spider(_) => [1.4, 0.9, 1.4],
EntityState::Squid(_) => [0.8, 0.8, 0.8],
EntityState::Stray(_) => [0.6, 1.99, 0.6],
EntityState::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size?
EntityState::Egg(_) => [0.25, 0.25, 0.25],
EntityState::EnderPearl(_) => [0.25, 0.25, 0.25],
EntityState::ExperienceBottle(_) => [0.25, 0.25, 0.25],
EntityState::Potion(_) => [0.25, 0.25, 0.25],
EntityState::Trident(_) => [0.5, 0.5, 0.5],
EntityState::TraderLlama(_) => [0.9, 1.87, 0.9],
EntityState::TropicalFish(_) => [0.5, 0.4, 0.5],
EntityState::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size?
EntityState::Vex(_) => [0.4, 0.8, 0.4],
EntityState::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::Vindicator(_) => [0.6, 1.95, 0.6],
EntityState::WanderingTrader(_) => [0.6, 1.95, 0.6],
EntityState::Witch(_) => [0.6, 1.95, 0.6],
EntityState::Wither(_) => [0.9, 3.5, 0.9],
EntityState::WitherSkeleton(_) => [0.7, 2.4, 0.7],
EntityState::WitherSkull(_) => [0.3125, 0.3125, 0.3125],
EntityState::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size?
EntityState::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityState::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityState::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose.
EntityState::FishingBobber(_) => [0.25, 0.25, 0.25],
};
aabb_from_bottom_and_size(self.new_position, dims.into())
@ -536,7 +533,7 @@ impl Entity {
///
/// Is `None` if there is no initial metadata.
pub(crate) fn initial_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> {
self.data.initial_metadata().map(|meta| SetEntityMetadata {
self.state.initial_metadata().map(|meta| SetEntityMetadata {
entity_id: VarInt(this_id.to_network_id()),
metadata: RawBytes(meta),
})
@ -546,23 +543,23 @@ impl Entity {
///
/// Is `None` if this entity's metadata has not been modified.
pub(crate) fn updated_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> {
self.data.updated_metadata().map(|meta| SetEntityMetadata {
self.state.updated_metadata().map(|meta| SetEntityMetadata {
entity_id: VarInt(this_id.to_network_id()),
metadata: RawBytes(meta),
})
}
pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option<EntitySpawnPacket> {
match &self.data {
EntityData::Marker(_) => None,
EntityData::ExperienceOrb(_) => {
match &self.state {
EntityState::Marker(_) => None,
EntityState::ExperienceOrb(_) => {
Some(EntitySpawnPacket::ExperienceOrb(AddExperienceOrb {
entity_id: VarInt(this_id.to_network_id()),
position: self.new_position,
count: 0, // TODO
}))
}
EntityData::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer {
EntityState::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer {
entity_id: VarInt(this_id.to_network_id()),
player_uuid: self.uuid,
position: self.new_position,

View file

@ -3,7 +3,7 @@
#![allow(clippy::all, missing_docs)]
use crate::block::{BlockPos, BlockState};
use crate::entity::data::*;
use crate::entity::state::*;
use crate::entity::EntityId;
use crate::protocol_inner::{Encode, VarInt};
use crate::text::Text;

View file

@ -52,17 +52,23 @@
//! use valence::server::{Server, ShutdownResult};
//!
//! pub fn main() -> ShutdownResult {
//! valence::start_server(Game)
//! valence::start_server(Game, ())
//! }
//!
//! struct Game;
//!
//! impl Config for Game {
//! type ChunkData = ();
//! type ClientData = ();
//! type EntityData = ();
//! type ServerData = ();
//! type WorldData = ();
//!
//! fn max_connections(&self) -> usize {
//! 256
//! }
//!
//! fn update(&self, server: &mut Server) {
//! fn update(&self, server: &mut Server<Self>) {
//! server.clients.retain(|_, client| {
//! if client.created_tick() == server.shared.current_tick() {
//! println!("{} joined!", client.username());

View file

@ -1,5 +1,4 @@
/// Reading and writing whole packets.
use std::io::Read;
use std::time::Duration;

View file

@ -51,15 +51,17 @@ use crate::{Ticks, PROTOCOL_VERSION, VERSION_NAME};
/// Contains the entire state of a running Minecraft server, accessible from
/// within the [update](crate::config::Config::update) loop.
pub struct Server {
pub struct Server<C: Config> {
/// Custom data.
pub data: C::ServerData,
/// A handle to this server's [`SharedServer`].
pub shared: SharedServer,
pub shared: SharedServer<C>,
/// All of the clients in the server.
pub clients: Clients,
pub clients: Clients<C>,
/// All of entities in the server.
pub entities: Entities,
pub entities: Entities<C>,
/// All of the worlds in the server.
pub worlds: Worlds,
pub worlds: Worlds<C>,
}
/// A handle to a Minecraft server containing the subset of functionality which
@ -69,11 +71,17 @@ pub struct Server {
/// be shared between threads.
///
/// [update]: crate::config::Config::update
#[derive(Clone)]
pub struct SharedServer(Arc<SharedServerInner>);
struct SharedServerInner {
cfg: Box<dyn Config>,
pub struct SharedServer<C: Config>(Arc<SharedServerInner<C>>);
impl<C: Config> Clone for SharedServer<C> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
struct SharedServerInner<C: Config> {
cfg: C,
address: SocketAddr,
tick_rate: Ticks,
online_mode: bool,
@ -130,10 +138,10 @@ pub type ShutdownResult = Result<(), Box<dyn Error + Send + Sync + 'static>>;
pub(crate) type S2cPacketChannels = (Sender<C2sPlayPacket>, Receiver<S2cPlayPacket>);
pub(crate) type C2sPacketChannels = (Sender<S2cPlayPacket>, Receiver<C2sPlayPacket>);
impl SharedServer {
impl<C: Config> SharedServer<C> {
/// Gets a reference to the config object used to start the server.
pub fn config(&self) -> &(impl Config + ?Sized) {
self.0.cfg.as_ref()
pub fn config(&self) -> &C {
&self.0.cfg
}
/// Gets the socket address this server is bound to.
@ -240,12 +248,13 @@ impl SharedServer {
///
/// The function returns once the server has shut down, a runtime error
/// occurs, or the configuration is found to be invalid.
pub fn start_server(config: impl Config) -> ShutdownResult {
pub fn start_server<C: Config>(config: C, data: C::ServerData) -> ShutdownResult {
let shared = setup_server(config).map_err(Box::<dyn Error + Send + Sync + 'static>::from)?;
let _guard = shared.tokio_handle().enter();
let mut server = Server {
data,
shared: shared.clone(),
clients: Clients::new(),
entities: Entities::new(),
@ -259,7 +268,7 @@ pub fn start_server(config: impl Config) -> ShutdownResult {
do_update_loop(&mut server)
}
fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> {
fn setup_server<C: Config>(cfg: C) -> anyhow::Result<SharedServer<C>> {
let max_connections = cfg.max_connections();
let address = cfg.address();
let tick_rate = cfg.tick_rate();
@ -360,7 +369,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> {
};
let server = SharedServerInner {
cfg: Box::new(cfg),
cfg,
address,
tick_rate,
online_mode,
@ -385,7 +394,7 @@ fn setup_server(cfg: impl Config) -> anyhow::Result<SharedServer> {
Ok(SharedServer(Arc::new(server)))
}
fn do_update_loop(server: &mut Server) -> ShutdownResult {
fn do_update_loop<C: Config>(server: &mut Server<C>) -> ShutdownResult {
let mut tick_start = Instant::now();
let shared = server.shared.clone();
@ -441,7 +450,7 @@ fn do_update_loop(server: &mut Server) -> ShutdownResult {
}
}
fn join_player(server: &mut Server, msg: NewClientMessage) {
fn join_player<C: Config>(server: &mut Server<C>, msg: NewClientMessage) {
let (clientbound_tx, clientbound_rx) = flume::bounded(server.shared.0.outgoing_packet_capacity);
let (serverbound_tx, serverbound_rx) = flume::bounded(server.shared.0.incoming_packet_capacity);
@ -450,7 +459,12 @@ fn join_player(server: &mut Server, msg: NewClientMessage) {
let _ = msg.reply.send(s2c_packet_channels);
let client = Client::new(c2s_packet_channels, &server.shared, msg.ncd);
let client = Client::new(
c2s_packet_channels,
&server.shared,
msg.ncd,
C::ClientData::default(),
);
server.clients.insert(client);
}
@ -460,7 +474,7 @@ struct Codec {
dec: Decoder<OwnedReadHalf>,
}
async fn do_accept_loop(server: SharedServer) {
async fn do_accept_loop<C: Config>(server: SharedServer<C>) {
log::trace!("entering accept loop");
let listener = match TcpListener::bind(server.0.address).await {
@ -502,8 +516,8 @@ async fn do_accept_loop(server: SharedServer) {
}
}
async fn handle_connection(
server: SharedServer,
async fn handle_connection<C: Config>(
server: SharedServer<C>,
stream: TcpStream,
remote_addr: SocketAddr,
) -> anyhow::Result<()> {
@ -533,8 +547,8 @@ async fn handle_connection(
}
}
async fn handle_status(
server: SharedServer,
async fn handle_status<C: Config>(
server: SharedServer<C>,
c: &mut Codec,
remote_addr: SocketAddr,
) -> anyhow::Result<()> {
@ -585,8 +599,8 @@ async fn handle_status(
}
/// Handle the login process and return the new player's data if successful.
async fn handle_login(
server: &SharedServer,
async fn handle_login<C: Config>(
server: &SharedServer<C>,
c: &mut Codec,
remote_addr: SocketAddr,
) -> anyhow::Result<Option<NewClientData>> {
@ -725,7 +739,11 @@ async fn handle_login(
Ok(Some(npd))
}
async fn handle_play(server: &SharedServer, c: Codec, ncd: NewClientData) -> anyhow::Result<()> {
async fn handle_play<C: Config>(
server: &SharedServer<C>,
c: Codec,
ncd: NewClientData,
) -> anyhow::Result<()> {
let (reply_tx, reply_rx) = oneshot::channel();
server

View file

@ -6,6 +6,7 @@ use rayon::iter::{IndexedParallelIterator, ParallelIterator};
use vek::{Aabb, Vec3};
use crate::bvh::{Bvh, Node};
use crate::config::Config;
use crate::entity::{Entities, EntityId};
use crate::util::ray_box_intersect;
use crate::world::WorldId;
@ -225,7 +226,7 @@ impl SpatialIndex {
self.bvh.par_iter().map(|(&id, bb)| (id, bb))
}
pub(crate) fn update(&mut self, entities: &Entities, id: WorldId) {
pub(crate) fn update<C: Config>(&mut self, entities: &Entities<C>, id: WorldId) {
self.bvh.build(
entities
.iter()

View file

@ -5,6 +5,7 @@ use std::iter::FusedIterator;
use rayon::iter::ParallelIterator;
use crate::chunk::Chunks;
use crate::config::Config;
use crate::dimension::DimensionId;
use crate::player_list::PlayerList;
use crate::server::SharedServer;
@ -12,9 +13,9 @@ use crate::slotmap::{Key, SlotMap};
use crate::spatial_index::SpatialIndex;
/// A container for all [`World`]s on a [`Server`](crate::server::Server).
pub struct Worlds {
sm: SlotMap<World>,
server: SharedServer,
pub struct Worlds<C: Config> {
sm: SlotMap<World<C>>,
server: SharedServer<C>,
}
/// An identifier for a [`World`] on the server.
@ -34,8 +35,8 @@ impl WorldId {
pub const NULL: Self = Self(Key::NULL);
}
impl Worlds {
pub(crate) fn new(server: SharedServer) -> Self {
impl<C: Config> Worlds<C> {
pub(crate) fn new(server: SharedServer<C>) -> Self {
Self {
sm: SlotMap::new(),
server,
@ -44,8 +45,9 @@ impl Worlds {
/// Creates a new world on the server with the provided dimension. A
/// reference to the world along with its ID is returned.
pub fn create(&mut self, dim: DimensionId) -> (WorldId, &mut World) {
pub fn create(&mut self, dim: DimensionId, data: C::WorldData) -> (WorldId, &mut World<C>) {
let (id, world) = self.sm.insert(World {
data,
spatial_index: SpatialIndex::new(),
chunks: Chunks::new(self.server.clone(), dim),
meta: WorldMeta {
@ -71,7 +73,7 @@ impl Worlds {
/// `f` returns `true`.
///
/// All worlds are visited in an unspecified order.
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World) -> bool) {
pub fn retain(&mut self, mut f: impl FnMut(WorldId, &mut World<C>) -> bool) {
self.sm.retain(|k, v| f(WorldId(k), v))
}
@ -82,47 +84,49 @@ impl Worlds {
/// Returns a shared reference to the world with the given ID. If
/// the ID is invalid, then `None` is returned.
pub fn get(&self, world: WorldId) -> Option<&World> {
pub fn get(&self, world: WorldId) -> Option<&World<C>> {
self.sm.get(world.0)
}
/// Returns an exclusive reference to the world with the given ID. If the
/// ID is invalid, then `None` is returned.
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World> {
pub fn get_mut(&mut self, world: WorldId) -> Option<&mut World<C>> {
self.sm.get_mut(world.0)
}
/// Returns an immutable iterator over all worlds on the server in an
/// unspecified order.
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World)> + Clone + '_ {
pub fn iter(&self) -> impl FusedIterator<Item = (WorldId, &World<C>)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (WorldId(k), v))
}
/// Returns a mutable iterator over all worlds on the server in an
/// unspecified ordder.
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World)> + '_ {
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (WorldId, &mut World<C>)> + '_ {
self.sm.iter_mut().map(|(k, v)| (WorldId(k), v))
}
/// Returns a parallel immutable iterator over all worlds on the server in
/// an unspecified order.
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World)> + Clone + '_ {
pub fn par_iter(&self) -> impl ParallelIterator<Item = (WorldId, &World<C>)> + Clone + '_ {
self.sm.par_iter().map(|(k, v)| (WorldId(k), v))
}
/// Returns a parallel mutable iterator over all worlds on the server in an
/// unspecified order.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World)> + '_ {
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (WorldId, &mut World<C>)> + '_ {
self.sm.par_iter_mut().map(|(k, v)| (WorldId(k), v))
}
}
/// A space for chunks, entities, and clients to occupy.
pub struct World {
pub struct World<C: Config> {
/// Custom data.
pub data: C::WorldData,
/// Contains all of the entities in this world.
pub spatial_index: SpatialIndex,
/// All of the chunks in this world.
pub chunks: Chunks,
pub chunks: Chunks<C>,
/// This world's metadata.
pub meta: WorldMeta,
}