1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

Shared worlds work

This commit is contained in:
mat 2022-11-19 22:20:10 -06:00
parent 8397fc8445
commit 141cd227e0
9 changed files with 586 additions and 304 deletions

View file

@ -30,7 +30,7 @@ use azalea_protocol::{
};
use azalea_world::{
entity::{metadata, Entity, EntityData, EntityMetadata},
World,
WeakWorld, WeakWorldContainer, World,
};
use log::{debug, error, info, warn};
use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
@ -71,9 +71,13 @@ pub struct Client {
pub read_conn: Arc<tokio::sync::Mutex<ReadConnection<ClientboundGamePacket>>>,
pub write_conn: Arc<tokio::sync::Mutex<WriteConnection<ServerboundGamePacket>>>,
pub player: Arc<RwLock<Player>>,
/// The world that this client has access to. This supports shared worlds.
pub world: Arc<RwLock<World>>,
/// Whether there's multiple clients sharing the same world.
pub world_shared: Arc<RwLock<bool>>,
/// A container of world names to worlds. If we're not using a shared world
/// (i.e. not a swarm), then this will only contain data about the world
/// we're currently in.
world_container: Arc<RwLock<WeakWorldContainer>>,
pub world_name: Arc<RwLock<Option<ResourceLocation>>>,
pub physics_state: Arc<Mutex<PhysicsState>>,
pub client_information: Arc<RwLock<ClientInformation>>,
/// Plugins are a way for other crates to add custom functionality to the
@ -133,7 +137,7 @@ impl Client {
pub fn new(
game_profile: GameProfile,
conn: Connection<ClientboundGamePacket, ServerboundGamePacket>,
world: Option<Arc<RwLock<World>>>,
world_container: Option<Arc<RwLock<WeakWorldContainer>>>,
) -> Self {
let (read_conn, write_conn) = conn.into_split();
let (read_conn, write_conn) = (
@ -146,8 +150,11 @@ impl Client {
read_conn,
write_conn,
player: Arc::new(RwLock::new(Player::default())),
world_shared: Arc::new(RwLock::new(world.is_some())),
world: world.unwrap_or_else(|| Arc::new(RwLock::new(World::default()))),
// the world will be set later
world: Arc::new(RwLock::new(World::default())),
world_container: world_container
.unwrap_or_else(|| Arc::new(RwLock::new(WeakWorldContainer::new()))),
world_name: Arc::new(RwLock::new(None)),
physics_state: Arc::new(Mutex::new(PhysicsState::default())),
client_information: Arc::new(
RwLock::new(ServerboundClientInformationPacket::default()),
@ -431,32 +438,19 @@ impl Client {
.as_int()
.expect("min_y tag is not an int");
// add this world to the world_container (or don't if it's already there)
let weak_world =
client
.world_container
.write()
.insert(p.dimension.clone(), height, min_y);
// set the loaded_world to an empty world
// (when we add chunks or entities those will be in the world_container)
let mut world_lock = client.world.write();
if *client.world_shared.read() {
// we can't clear the dimension if it's shared, so just
// make sure the height and stuff is correct
if world_lock.height() != height {
error!(
"Shared dimension height mismatch: {} != {}",
world_lock.height(),
height
);
}
if world_lock.min_y() != min_y {
error!(
"Shared world min_y mismatch: {} != {}",
world_lock.min_y(),
min_y
);
}
} else {
*world_lock = World::new(
client.client_information.read().view_distance.into(),
height,
min_y,
);
}
*world_lock = World::new(
client.client_information.read().view_distance.into(),
weak_world,
);
let entity = EntityData::new(
client.game_profile.uuid,
@ -867,7 +861,7 @@ impl Client {
return;
};
let player_chunk_pos: ChunkPos = player_entity.pos().into();
if world_lock[&player_chunk_pos].is_none() {
if world_lock.get_chunk(&player_chunk_pos).is_none() {
return;
}
}
@ -884,6 +878,25 @@ impl Client {
// TODO: minecraft does ambient sounds here
}
/// Get a [`WeakWorld`] from our world container. If it's a normal client,
/// then it'll be the same as the world the client has loaded. If the
/// client using a shared world, then the shared world will be a superset
/// of the client's world.
///
/// # Panics
/// Panics if the client has not received the login packet yet. You can check this with [`Client::logged_in`].
pub fn world(&self) -> Arc<WeakWorld> {
let world_name = self.world_name.read();
let world_name = world_name
.as_ref()
.expect("Client has not received login packet yet");
if let Some(world) = self.world_container.read().get(world_name) {
world
} else {
unreachable!("The world name must be in the world container");
}
}
/// Returns the entity associated to the player.
pub fn entity_mut(&self) -> Entity<RwLockWriteGuard<World>> {
let entity_id = {
@ -895,7 +908,7 @@ impl Client {
let entity_data = world
.entity_storage
.get_mut_by_id(entity_id)
.get_by_id(entity_id)
.expect("Player entity should exist");
let entity_ptr = unsafe { entity_data.as_ptr() };
Entity::new(world, entity_id, entity_ptr)
@ -913,15 +926,14 @@ impl Client {
.entity_storage
.get_by_id(entity_id)
.expect("Player entity should be in the given world");
let entity_ptr = unsafe { entity_data.as_const_ptr() };
let entity_ptr = unsafe { entity_data.as_ptr() };
Entity::new(world, entity_id, entity_ptr)
}
/// Returns whether we have a received the login packet yet.
pub fn logged_in(&self) -> bool {
let world = self.world.read();
let player = self.player.write();
player.entity(&world).is_some()
// the login packet tells us the world name
self.world_name.read().is_some()
}
/// Tell the server we changed our game options (i.e. render distance, main hand).

View file

@ -57,7 +57,7 @@ impl<'a> BlockCollisions<'a> {
}
}
fn get_chunk(&self, block_x: i32, block_z: i32) -> Option<&Arc<Mutex<Chunk>>> {
fn get_chunk(&self, block_x: i32, block_z: i32) -> Option<Arc<Mutex<Chunk>>> {
let chunk_x = ChunkSectionPos::block_to_section_coord(block_x);
let chunk_z = ChunkSectionPos::block_to_section_coord(block_z);
let chunk_pos = ChunkPos::new(chunk_x, chunk_z);
@ -75,7 +75,7 @@ impl<'a> BlockCollisions<'a> {
// return var7;
// }
self.world[&chunk_pos].as_ref()
self.world.get_chunk(&chunk_pos)
}
}

View file

@ -9,8 +9,11 @@ use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
use log::debug;
use log::trace;
use parking_lot::Mutex;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::fmt::Debug;
use std::io::Cursor;
use std::sync::Weak;
use std::{
io::Write,
ops::{Index, IndexMut},
@ -19,16 +22,35 @@ use std::{
const SECTION_HEIGHT: u32 = 16;
pub struct ChunkStorage {
/// An efficient storage for chunks for a client that has a limited render
/// distance. This has support for using a shared [`WeakChunkStorage`]. You
/// should not use this if you have infinite render distance (like a server), and instead of use a [`ChunkStorage`].
pub struct LimitedChunkStorage {
/// Chunk storage that can be shared by clients.
shared: Arc<RwLock<WeakChunkStorage>>,
pub view_center: ChunkPos,
chunk_radius: u32,
view_range: u32,
pub height: u32,
pub min_y: i32,
// chunks is a list of size chunk_radius * chunk_radius
chunks: Vec<Option<Arc<Mutex<Chunk>>>>,
}
/// A storage for chunks where they're only stored weakly, so if they're not
/// actively being used somewhere else they'll be forgotten. This is used for
/// shared worlds.
pub struct WeakChunkStorage {
pub height: u32,
pub min_y: i32,
pub chunks: HashMap<ChunkPos, Weak<Mutex<Chunk>>>,
}
pub struct ChunkStorage {
pub height: u32,
pub min_y: i32,
pub chunks: HashMap<ChunkPos, Arc<Mutex<Chunk>>>,
}
#[derive(Debug)]
pub struct Chunk {
pub sections: Vec<Section>,
@ -59,19 +81,25 @@ impl Default for Chunk {
}
}
impl ChunkStorage {
pub fn new(chunk_radius: u32, height: u32, min_y: i32) -> Self {
impl LimitedChunkStorage {
pub fn new(chunk_radius: u32, shared: Arc<RwLock<WeakChunkStorage>>) -> Self {
let view_range = chunk_radius * 2 + 1;
ChunkStorage {
LimitedChunkStorage {
shared,
view_center: ChunkPos::new(0, 0),
chunk_radius,
view_range,
height,
min_y,
chunks: vec![None; (view_range * view_range) as usize],
}
}
pub fn min_y(&self) -> i32 {
self.shared.read().min_y
}
pub fn height(&self) -> u32 {
self.shared.read().height
}
fn get_index(&self, chunk_pos: &ChunkPos) -> usize {
(floor_mod(chunk_pos.x, self.view_range) * self.view_range
+ floor_mod(chunk_pos.z, self.view_range)) as usize
@ -84,19 +112,19 @@ impl ChunkStorage {
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
let chunk_pos = ChunkPos::from(pos);
let chunk = self[&chunk_pos].as_ref()?;
let chunk = self.get(&chunk_pos)?;
let chunk = chunk.lock();
chunk.get(&ChunkBlockPos::from(pos), self.min_y)
chunk.get(&ChunkBlockPos::from(pos), self.min_y())
}
pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
if pos.y < self.min_y || pos.y >= (self.min_y + self.height as i32) {
if pos.y < self.min_y() || pos.y >= (self.min_y() + self.height() as i32) {
return None;
}
let chunk_pos = ChunkPos::from(pos);
let chunk = self[&chunk_pos].as_ref()?;
let chunk = self.get(&chunk_pos)?;
let mut chunk = chunk.lock();
Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y))
Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y()))
}
pub fn replace_with_packet_data(
@ -116,28 +144,70 @@ impl ChunkStorage {
let chunk = Arc::new(Mutex::new(Chunk::read_with_dimension_height(
data,
self.height,
self.height(),
)?));
trace!("Loaded chunk {:?}", pos);
self[pos] = Some(chunk);
self.set(pos, Some(chunk));
Ok(())
}
}
impl Index<&ChunkPos> for ChunkStorage {
type Output = Option<Arc<Mutex<Chunk>>>;
fn index(&self, pos: &ChunkPos) -> &Self::Output {
&self.chunks[self.get_index(pos)]
/// Get a [`Chunk`] within render distance, or `None` if it's not loaded.
/// Use [`LimitedChunkStorage::get`] to get a chunk from the shared storage.
///
/// # Panics
/// If the chunk is not in the render distance.
pub fn limited_get(&self, pos: &ChunkPos) -> &Option<Arc<Mutex<Chunk>>> {
let index = self.get_index(pos);
&self.chunks[index]
}
}
impl IndexMut<&ChunkPos> for ChunkStorage {
fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output {
/// Get a mutable reference to a [`Chunk`] within render distance, or
/// `None` if it's not loaded. Use [`LimitedChunkStorage::get`] to get
/// a chunk from the shared storage.
///
/// # Panics
/// If the chunk is not in the render distance.
pub fn limited_get_mut(&mut self, pos: &ChunkPos) -> &mut Option<Arc<Mutex<Chunk>>> {
let index = self.get_index(pos);
&mut self.chunks[index]
}
/// Get a chunk,
pub fn get(&self, pos: &ChunkPos) -> Option<Arc<Mutex<Chunk>>> {
self.shared
.read()
.chunks
.get(pos)
.and_then(|chunk| chunk.upgrade())
}
/// Set a chunk in the shared storage and reference it from the limited
/// storage.
///
/// # Panics
/// If the chunk is not in the render distance.
pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Arc<Mutex<Chunk>>>) {
if let Some(chunk) = &chunk {
self.shared
.write()
.chunks
.insert(*pos, Arc::downgrade(chunk));
} else {
// don't remove it from the shared storage, since it'll be removed
// automatically if this was the last reference
}
*self.limited_get_mut(pos) = chunk;
}
}
impl WeakChunkStorage {
pub fn new(height: u32, min_y: i32) -> Self {
WeakChunkStorage {
height,
min_y,
chunks: HashMap::new(),
}
}
}
impl Chunk {
@ -214,14 +284,14 @@ impl McBufWritable for Chunk {
}
}
impl Debug for ChunkStorage {
impl Debug for LimitedChunkStorage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ChunkStorage")
.field("view_center", &self.view_center)
.field("chunk_radius", &self.chunk_radius)
.field("view_range", &self.view_range)
.field("height", &self.height)
.field("min_y", &self.min_y)
.field("height", &self.height())
.field("min_y", &self.min_y())
// .field("chunks", &self.chunks)
.field("chunks", &format_args!("{} items", self.chunks.len()))
.finish()
@ -292,9 +362,14 @@ impl Section {
}
}
impl Default for ChunkStorage {
impl Default for LimitedChunkStorage {
fn default() -> Self {
Self::new(8, 384, -64)
Self::new(8, Arc::new(RwLock::new(WeakChunkStorage::default())))
}
}
impl Default for WeakChunkStorage {
fn default() -> Self {
Self::new(384, -64)
}
}
@ -317,8 +392,11 @@ mod tests {
#[test]
fn test_out_of_bounds_y() {
let mut chunk_storage = ChunkStorage::default();
chunk_storage[&ChunkPos { x: 0, z: 0 }] = Some(Arc::new(Mutex::new(Chunk::default())));
let mut chunk_storage = LimitedChunkStorage::default();
chunk_storage.set(
&ChunkPos { x: 0, z: 0 },
Some(Arc::new(Mutex::new(Chunk::default()))),
);
assert!(chunk_storage
.get_block_state(&BlockPos { x: 0, y: 319, z: 0 })
.is_some());

View file

@ -0,0 +1,54 @@
use crate::{WeakWorld, World};
use azalea_core::ResourceLocation;
use log::error;
use std::{
collections::HashMap,
sync::{Arc, Weak},
};
/// A container of Worlds. Worlds are stored as a Weak pointer here, so if no
/// clients are using a world it will be forgotten.
#[derive(Default)]
pub struct WeakWorldContainer {
pub worlds: HashMap<ResourceLocation, Weak<WeakWorld>>,
}
impl WeakWorldContainer {
pub fn new() -> Self {
WeakWorldContainer {
worlds: HashMap::new(),
}
}
/// Get a world from the container.
pub fn get(&self, name: &ResourceLocation) -> Option<Arc<WeakWorld>> {
self.worlds.get(name).and_then(|world| world.upgrade())
}
/// Add an empty world to the container (or not if it already exists) and
/// returns a strong reference to the world.
#[must_use = "the world will be immediately forgotten if unused"]
pub fn insert(&mut self, name: ResourceLocation, height: u32, min_y: i32) -> Arc<WeakWorld> {
if let Some(existing) = self.worlds.get(&name).and_then(|world| world.upgrade()) {
if existing.height() != height {
error!(
"Shared dimension height mismatch: {} != {}",
existing.height(),
height,
);
}
if existing.min_y() != min_y {
error!(
"Shared world min_y mismatch: {} != {}",
existing.min_y(),
min_y,
);
}
existing
} else {
let world = Arc::new(WeakWorld::new(height, min_y));
self.worlds.insert(name, Arc::downgrade(&world));
world
}
}
}

View file

@ -270,20 +270,11 @@ impl EntityData {
&self.pos
}
/// Convert this &mut self into a (mutable) pointer.
///
/// # Safety
/// The entity MUST exist while this pointer exists.
pub unsafe fn as_ptr(&mut self) -> NonNull<EntityData> {
NonNull::new_unchecked(self as *mut EntityData)
}
/// Convert this &self into a (mutable) pointer.
///
/// # Safety
/// The entity MUST exist while this pointer exists. You also must not
/// modify the data inside the pointer.
pub unsafe fn as_const_ptr(&self) -> NonNull<EntityData> {
/// The entity MUST exist for at least as long as this pointer exists.
pub unsafe fn as_ptr(&self) -> NonNull<EntityData> {
// this is cursed
NonNull::new_unchecked(self as *const EntityData as *mut EntityData)
}

View file

@ -2,101 +2,170 @@ use crate::entity::EntityData;
use azalea_core::ChunkPos;
use log::warn;
use nohash_hasher::{IntMap, IntSet};
use std::collections::HashMap;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use std::{
collections::HashMap,
iter::FilterMap,
sync::{Arc, Weak},
};
use uuid::Uuid;
#[derive(Debug)]
/// Store a map of entities by ID. To get an iterator over all entities, use `storage.shared.read().entities`
/// [`WeakEntityStorage::entities`]
#[derive(Debug, Default)]
pub struct EntityStorage {
data_by_id: IntMap<u32, EntityData>,
id_by_chunk: HashMap<ChunkPos, IntSet<u32>>,
pub shared: Arc<RwLock<WeakEntityStorage>>,
_data_by_id: IntMap<u32, Arc<EntityData>>,
}
/// Weakly store entities in a world. If the entities aren't being referenced
/// by anything else (like an [`EntityStorage`]), they'll be forgotten.
#[derive(Debug, Default)]
pub struct WeakEntityStorage {
data_by_id: IntMap<u32, Weak<EntityData>>,
ids_by_chunk: HashMap<ChunkPos, IntSet<u32>>,
id_by_uuid: HashMap<Uuid, u32>,
}
impl EntityStorage {
pub fn new() -> Self {
pub fn new(shared: Arc<RwLock<WeakEntityStorage>>) -> Self {
Self {
data_by_id: IntMap::default(),
id_by_chunk: HashMap::default(),
id_by_uuid: HashMap::default(),
shared: shared,
_data_by_id: IntMap::default(),
}
}
/// Add an entity to the storage.
#[inline]
pub fn insert(&mut self, id: u32, entity: EntityData) {
self.id_by_chunk
self.shared
.write()
.ids_by_chunk
.entry(ChunkPos::from(entity.pos()))
.or_default()
.insert(id);
self.id_by_uuid.insert(entity.uuid, id);
self.data_by_id.insert(id, entity);
self.shared.write().id_by_uuid.insert(entity.uuid, id);
let entity = Arc::new(entity);
self.shared
.write()
.data_by_id
.insert(id, Arc::downgrade(&entity));
self._data_by_id.insert(id, entity);
}
/// Remove an entity from the storage by its id.
/// Remove an entity from this storage by its id. It will only be removed
/// from the shared storage if there are no other references to it.
#[inline]
pub fn remove_by_id(&mut self, id: u32) {
if let Some(entity) = self.data_by_id.remove(&id) {
let entity_chunk = ChunkPos::from(entity.pos());
let entity_uuid = entity.uuid;
if self.id_by_chunk.remove(&entity_chunk).is_none() {
warn!("Tried to remove entity with id {id} from chunk {entity_chunk:?} but it was not found.");
}
if self.id_by_uuid.remove(&entity_uuid).is_none() {
warn!("Tried to remove entity with id {id} from uuid {entity_uuid:?} but it was not found.");
}
if let Some(entity) = self._data_by_id.remove(&id) {
let chunk = ChunkPos::from(entity.pos());
let uuid = entity.uuid;
drop(entity);
// maybe remove it from the storage
self.shared.write().remove_entity_if_unused(id, uuid, chunk);
} else {
warn!("Tried to remove entity with id {id} but it was not found.")
}
}
/// Check if there is an entity that exists with the given id.
/// Whether the entity with the given id is being loaded by this storage.
/// If you want to check whether the entity is in the shared storage, use
/// [`WeakEntityStorage::contains_id`].
#[inline]
pub fn limited_contains_id(&self, id: &u32) -> bool {
self._data_by_id.contains_key(id)
}
/// Whether the entity with the given id is in the shared storage (i.e.
/// it's possible we don't see the entity but something else in the shared
/// storage does). To check whether the entity is being loaded by this
/// storage, use [`EntityStorage::limited_contains_id`].
#[inline]
pub fn contains_id(&self, id: &u32) -> bool {
self.data_by_id.contains_key(id)
self.shared.read().data_by_id.contains_key(id)
}
/// Get a reference to an entity by its id.
/// Get a reference to an entity by its id, if it's being loaded by this storage.
#[inline]
pub fn get_by_id(&self, id: u32) -> Option<&EntityData> {
self.data_by_id.get(&id)
pub fn limited_get_by_id(&self, id: u32) -> Option<&Arc<EntityData>> {
self._data_by_id.get(&id)
}
/// Get a mutable reference to an entity by its id.
/// Get a mutable reference to an entity by its id, if it's being loaded by
/// this storage.
#[inline]
pub fn get_mut_by_id(&mut self, id: u32) -> Option<&mut EntityData> {
self.data_by_id.get_mut(&id)
pub fn limited_get_mut_by_id(&mut self, id: u32) -> Option<&mut Arc<EntityData>> {
self._data_by_id.get_mut(&id)
}
/// Get a reference to an entity by its uuid.
/// Get an entity in the shared storage by its id, if it exists.
#[inline]
pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> {
self.id_by_uuid
pub fn get_by_id(&self, id: u32) -> Option<Arc<EntityData>> {
self.shared
.read()
.data_by_id
.get(&id)
.and_then(|e| e.upgrade())
}
/// Get a reference to an entity by its UUID, if it's being loaded by this
/// storage.
#[inline]
pub fn limited_get_by_uuid(&self, uuid: &Uuid) -> Option<&Arc<EntityData>> {
self.shared
.read()
.id_by_uuid
.get(uuid)
.and_then(|id| self.data_by_id.get(id))
.and_then(|id| self._data_by_id.get(id))
}
/// Get a mutable reference to an entity by its uuid.
/// Get a mutable reference to an entity by its UUID, if it's being loaded
/// by this storage.
#[inline]
pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut EntityData> {
self.id_by_uuid
pub fn limited_get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut Arc<EntityData>> {
self.shared
.read()
.id_by_uuid
.get(uuid)
.and_then(|id| self.data_by_id.get_mut(id))
.and_then(|id| self._data_by_id.get_mut(id))
}
/// Clear all entities in a chunk.
/// Get an entity in the shared storage by its UUID, if it exists.
#[inline]
pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<Arc<EntityData>> {
self.shared.read().id_by_uuid.get(uuid).and_then(|id| {
self.shared
.read()
.data_by_id
.get(id)
.and_then(|e| e.upgrade().clone())
})
}
/// Clear all entities in a chunk. This will not clear them from the
/// shared storage, unless there are no other references to them.
pub fn clear_chunk(&mut self, chunk: &ChunkPos) {
if let Some(entities) = self.id_by_chunk.remove(chunk) {
for entity_id in entities {
if let Some(entity) = self.data_by_id.remove(&entity_id) {
self.id_by_uuid.remove(&entity.uuid);
} else {
warn!("While clearing chunk {chunk:?}, found an entity that isn't in by_id {entity_id}.");
if let Some(entities) = self.shared.read().ids_by_chunk.get(chunk) {
for id in entities.iter() {
if let Some(entity) = self._data_by_id.remove(id) {
let uuid = entity.uuid;
drop(entity);
// maybe remove it from the storage
self.shared
.write()
.remove_entity_if_unused(*id, uuid, *chunk);
}
}
// for entity_id in entities {
// self.remove_by_id(entity_id);
// }
}
}
/// Updates an entity from its old chunk.
/// Move an entity from its old chunk to a new chunk.
#[inline]
pub fn update_entity_chunk(
&mut self,
@ -104,36 +173,40 @@ impl EntityStorage {
old_chunk: &ChunkPos,
new_chunk: &ChunkPos,
) {
if let Some(entities) = self.id_by_chunk.get_mut(old_chunk) {
if let Some(entities) = self.shared.write().ids_by_chunk.get_mut(old_chunk) {
entities.remove(&entity_id);
}
self.id_by_chunk
self.shared
.write()
.ids_by_chunk
.entry(*new_chunk)
.or_default()
.insert(entity_id);
}
/// Get an iterator over all entities.
#[inline]
pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> {
self.data_by_id.values()
pub fn find_one_entity<F>(&self, mut f: F) -> Option<Arc<EntityData>>
where
F: FnMut(&Arc<EntityData>) -> bool,
{
for entity in self.shared.read().entities().into_iter() {
if let Some(entity) = entity.upgrade() {
if f(&entity) {
return Some(entity);
}
}
}
None
}
pub fn find_one_entity<F>(&self, mut f: F) -> Option<&EntityData>
pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<Arc<EntityData>>
where
F: FnMut(&EntityData) -> bool,
{
self.entities().find(|&entity| f(entity))
}
pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<&EntityData>
where
F: FnMut(&EntityData) -> bool,
{
if let Some(entities) = self.id_by_chunk.get(chunk) {
let shared = self.shared.read();
if let Some(entities) = shared.ids_by_chunk.get(chunk) {
for entity_id in entities {
if let Some(entity) = self.data_by_id.get(entity_id) {
if f(entity) {
if let Some(entity) = shared.data_by_id.get(entity_id).and_then(|e| e.upgrade()) {
if f(&entity) {
return Some(entity);
}
}
@ -143,9 +216,68 @@ impl EntityStorage {
}
}
impl Default for EntityStorage {
fn default() -> Self {
Self::new()
impl WeakEntityStorage {
pub fn new() -> Self {
Self {
data_by_id: IntMap::default(),
ids_by_chunk: HashMap::default(),
id_by_uuid: HashMap::default(),
}
}
/// Remove an entity from the storage if it has no strong references left.
/// Returns whether the entity was removed.
pub fn remove_entity_if_unused(&mut self, id: u32, uuid: Uuid, chunk: ChunkPos) -> bool {
if let Some(_) = self.data_by_id.get(&id).and_then(|e| e.upgrade()) {
// if we could get the entity, that means there are still strong
// references to it
false
} else {
if self.ids_by_chunk.remove(&chunk).is_none() {
warn!("Tried to remove entity with id {id} from chunk {chunk:?} but it was not found.");
}
if self.id_by_uuid.remove(&uuid).is_none() {
warn!(
"Tried to remove entity with id {id} from uuid {uuid:?} but it was not found."
);
}
true
}
}
/// Remove a chunk from the storage if the entities in it have no strong
/// references left.
pub fn remove_chunk_if_unused(&mut self, chunk: &ChunkPos) {
if let Some(entities) = self.ids_by_chunk.get(chunk) {
if entities.is_empty() {
self.ids_by_chunk.remove(chunk);
}
}
}
/// Get an iterator over all entities in the shared storage. The iterator
/// is over `Weak<EntityData>`s, so you'll have to manually try to upgrade.
///
/// # Examples
///
/// ```rust
/// let mut storage = EntityStorage::new();
/// storage.insert(
/// 0,
/// Arc::new(EntityData::new(
/// uuid,
/// Vec3::default(),
/// EntityMetadata::Player(metadata::Player::default()),
/// )),
/// );
/// for entity in storage.shared.read().entities() {
/// if let Some(entity) = entity.upgrade() {
/// println!("Entity: {:?}", entity);
/// }
/// }
/// ```
pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Weak<EntityData>> {
self.data_by_id.values()
}
}
@ -158,7 +290,7 @@ mod tests {
#[test]
fn test_store_entity() {
let mut storage = EntityStorage::new();
let mut storage = EntityStorage::default();
assert!(storage.get_by_id(0).is_none());
let uuid = Uuid::from_u128(100);

View file

@ -2,169 +2,21 @@
mod bit_storage;
mod chunk_storage;
mod container;
pub mod entity;
mod entity_storage;
mod palette;
mod world;
use azalea_block::BlockState;
use azalea_buf::BufReadError;
use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3};
pub use bit_storage::BitStorage;
pub use chunk_storage::{Chunk, ChunkStorage};
use entity::{Entity, EntityData};
pub use entity_storage::EntityStorage;
use parking_lot::Mutex;
use std::{
io::Cursor,
ops::{Index, IndexMut},
sync::Arc,
};
pub use chunk_storage::{Chunk, LimitedChunkStorage, WeakChunkStorage};
pub use container::*;
pub use entity_storage::{EntityStorage, WeakEntityStorage};
use thiserror::Error;
use uuid::Uuid;
/// A world is a collection of chunks and entities. They're called "levels" in Minecraft's source code.
#[derive(Debug, Default)]
pub struct World {
pub chunk_storage: ChunkStorage,
pub entity_storage: EntityStorage,
}
pub use world::*;
#[derive(Error, Debug)]
pub enum MoveEntityError {
#[error("Entity doesn't exist")]
EntityDoesNotExist,
}
impl World {
pub fn new(chunk_radius: u32, height: u32, min_y: i32) -> Self {
World {
chunk_storage: ChunkStorage::new(chunk_radius, height, min_y),
entity_storage: EntityStorage::new(),
}
}
pub fn replace_with_packet_data(
&mut self,
pos: &ChunkPos,
data: &mut Cursor<&[u8]>,
) -> Result<(), BufReadError> {
self.chunk_storage.replace_with_packet_data(pos, data)
}
pub fn set_chunk(&mut self, pos: &ChunkPos, chunk: Option<Chunk>) -> Result<(), BufReadError> {
self[pos] = chunk.map(|c| Arc::new(Mutex::new(c)));
Ok(())
}
pub fn update_view_center(&mut self, pos: &ChunkPos) {
self.chunk_storage.view_center = *pos;
}
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
self.chunk_storage.get_block_state(pos)
}
pub fn set_block_state(&mut self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
self.chunk_storage.set_block_state(pos, state)
}
pub fn set_entity_pos(&mut self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> {
let mut entity = self
.entity_mut(entity_id)
.ok_or(MoveEntityError::EntityDoesNotExist)?;
let old_chunk = ChunkPos::from(entity.pos());
let new_chunk = ChunkPos::from(&new_pos);
// this is fine because we update the chunk below
unsafe { entity.move_unchecked(new_pos) };
if old_chunk != new_chunk {
self.entity_storage
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
}
Ok(())
}
pub fn move_entity_with_delta(
&mut self,
entity_id: u32,
delta: &PositionDelta8,
) -> Result<(), MoveEntityError> {
let mut entity = self
.entity_mut(entity_id)
.ok_or(MoveEntityError::EntityDoesNotExist)?;
let new_pos = entity.pos().with_delta(delta);
let old_chunk = ChunkPos::from(entity.pos());
let new_chunk = ChunkPos::from(&new_pos);
// this is fine because we update the chunk below
unsafe { entity.move_unchecked(new_pos) };
if old_chunk != new_chunk {
self.entity_storage
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
}
Ok(())
}
pub fn add_entity(&mut self, id: u32, entity: EntityData) {
self.entity_storage.insert(id, entity);
}
pub fn height(&self) -> u32 {
self.chunk_storage.height
}
pub fn min_y(&self) -> i32 {
self.chunk_storage.min_y
}
pub fn entity_data_by_id(&self, id: u32) -> Option<&EntityData> {
self.entity_storage.get_by_id(id)
}
pub fn entity_data_mut_by_id(&mut self, id: u32) -> Option<&mut EntityData> {
self.entity_storage.get_mut_by_id(id)
}
pub fn entity(&self, id: u32) -> Option<Entity<&World>> {
let entity_data = self.entity_storage.get_by_id(id)?;
let entity_ptr = unsafe { entity_data.as_const_ptr() };
Some(Entity::new(self, id, entity_ptr))
}
pub fn entity_mut(&mut self, id: u32) -> Option<Entity<'_, &mut World>> {
let entity_data = self.entity_storage.get_mut_by_id(id)?;
let entity_ptr = unsafe { entity_data.as_ptr() };
Some(Entity::new(self, id, entity_ptr))
}
pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> {
self.entity_storage.get_by_uuid(uuid)
}
/// Get an iterator over all entities.
#[inline]
pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> {
self.entity_storage.entities()
}
pub fn find_one_entity<F>(&self, mut f: F) -> Option<&EntityData>
where
F: FnMut(&EntityData) -> bool,
{
self.entity_storage.find_one_entity(|entity| f(entity))
}
}
impl Index<&ChunkPos> for World {
type Output = Option<Arc<Mutex<Chunk>>>;
fn index(&self, pos: &ChunkPos) -> &Self::Output {
&self.chunk_storage[pos]
}
}
impl IndexMut<&ChunkPos> for World {
fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output {
&mut self.chunk_storage[pos]
}
}

158
azalea-world/src/world.rs Normal file
View file

@ -0,0 +1,158 @@
use crate::{
entity::{Entity, EntityData},
Chunk, EntityStorage, LimitedChunkStorage, MoveEntityError, WeakChunkStorage,
WeakEntityStorage,
};
use azalea_block::BlockState;
use azalea_buf::BufReadError;
use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3};
use parking_lot::{Mutex, RwLock};
use std::{io::Cursor, sync::Arc};
use uuid::Uuid;
/// A world is a collection of chunks and entities. They're called "levels" in Minecraft's source code.
#[derive(Debug, Default)]
pub struct World {
pub chunk_storage: LimitedChunkStorage,
pub entity_storage: EntityStorage,
}
/// A world where the chunks are stored as weak pointers. This is used for shared worlds.
#[derive(Default)]
pub struct WeakWorld {
pub chunk_storage: Arc<RwLock<WeakChunkStorage>>,
pub entity_storage: Arc<RwLock<WeakEntityStorage>>,
}
impl World {
pub fn new(chunk_radius: u32, shared: Arc<WeakWorld>) -> Self {
World {
chunk_storage: LimitedChunkStorage::new(chunk_radius, shared.chunk_storage.clone()),
entity_storage: EntityStorage::new(shared.entity_storage.clone()),
}
}
pub fn replace_with_packet_data(
&mut self,
pos: &ChunkPos,
data: &mut Cursor<&[u8]>,
) -> Result<(), BufReadError> {
self.chunk_storage.replace_with_packet_data(pos, data)
}
pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<Mutex<Chunk>>> {
self.chunk_storage.get(pos)
}
pub fn set_chunk(&mut self, pos: &ChunkPos, chunk: Option<Chunk>) -> Result<(), BufReadError> {
self.chunk_storage
.set(pos, chunk.map(|c| Arc::new(Mutex::new(c))));
Ok(())
}
pub fn update_view_center(&mut self, pos: &ChunkPos) {
self.chunk_storage.view_center = *pos;
}
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
self.chunk_storage.get_block_state(pos)
}
pub fn set_block_state(&mut self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
self.chunk_storage.set_block_state(pos, state)
}
pub fn set_entity_pos(&mut self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> {
let mut entity = self
.entity_mut(entity_id)
.ok_or(MoveEntityError::EntityDoesNotExist)?;
let old_chunk = ChunkPos::from(entity.pos());
let new_chunk = ChunkPos::from(&new_pos);
// this is fine because we update the chunk below
unsafe { entity.move_unchecked(new_pos) };
if old_chunk != new_chunk {
self.entity_storage
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
}
Ok(())
}
pub fn move_entity_with_delta(
&mut self,
entity_id: u32,
delta: &PositionDelta8,
) -> Result<(), MoveEntityError> {
let mut entity = self
.entity_mut(entity_id)
.ok_or(MoveEntityError::EntityDoesNotExist)?;
let new_pos = entity.pos().with_delta(delta);
let old_chunk = ChunkPos::from(entity.pos());
let new_chunk = ChunkPos::from(&new_pos);
// this is fine because we update the chunk below
unsafe { entity.move_unchecked(new_pos) };
if old_chunk != new_chunk {
self.entity_storage
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
}
Ok(())
}
pub fn add_entity(&mut self, id: u32, entity: EntityData) {
self.entity_storage.insert(id, entity);
}
pub fn height(&self) -> u32 {
self.chunk_storage.height()
}
pub fn min_y(&self) -> i32 {
self.chunk_storage.min_y()
}
pub fn entity_data_by_id(&self, id: u32) -> Option<Arc<EntityData>> {
self.entity_storage.get_by_id(id)
}
pub fn entity(&self, id: u32) -> Option<Entity<&World>> {
let entity_data = self.entity_storage.get_by_id(id)?;
let entity_ptr = unsafe { entity_data.as_ptr() };
Some(Entity::new(self, id, entity_ptr))
}
pub fn entity_mut(&mut self, id: u32) -> Option<Entity<'_, &mut World>> {
let entity_data = self.entity_storage.get_by_id(id)?;
let entity_ptr = unsafe { entity_data.as_ptr() };
Some(Entity::new(self, id, entity_ptr))
}
pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<Arc<EntityData>> {
self.entity_storage.get_by_uuid(uuid)
}
pub fn find_one_entity<F>(&self, mut f: F) -> Option<Arc<EntityData>>
where
F: FnMut(&EntityData) -> bool,
{
self.entity_storage.find_one_entity(|entity| f(entity))
}
}
impl WeakWorld {
pub fn new(height: u32, min_y: i32) -> Self {
WeakWorld {
chunk_storage: Arc::new(RwLock::new(WeakChunkStorage::new(height, min_y))),
entity_storage: Arc::new(RwLock::new(WeakEntityStorage::new())),
}
}
pub fn height(&self) -> u32 {
self.chunk_storage.read().height
}
pub fn min_y(&self) -> i32 {
self.chunk_storage.read().min_y
}
}

View file

@ -11,6 +11,7 @@ use azalea_protocol::{
resolver::{self, ResolverError},
ServerAddress,
};
use azalea_world::WeakWorldContainer;
use azalea_world::World;
use futures::{
future::{select_all, try_join_all},
@ -121,8 +122,8 @@ pub async fn start_swarm<
let resolved_address = resolver::resolve_address(&address).await?;
let address_borrow = &address;
let shared_world = Arc::new(RwLock::new(World::default()));
let shared_world_borrow = &shared_world;
let shared_world_container = Arc::new(RwLock::new(WeakWorldContainer::default()));
let shared_world_container_borrow = &shared_world_container;
let bots: Vec<(Client, UnboundedReceiver<Event>)> = try_join_all(options.accounts.iter().map(
async move |account| -> Result<(Client, UnboundedReceiver<Event>), JoinError> {
@ -132,7 +133,11 @@ pub async fn start_swarm<
let (tx, rx) = mpsc::unbounded_channel();
let client = Client::new(game_profile, conn, Some(shared_world_borrow.clone()));
let client = Client::new(
game_profile,
conn,
Some(shared_world_container_borrow.clone()),
);
tx.send(Event::Initialize).unwrap();