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:
parent
8397fc8445
commit
141cd227e0
9 changed files with 586 additions and 304 deletions
|
@ -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).
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
54
azalea-world/src/container.rs
Normal file
54
azalea-world/src/container.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
158
azalea-world/src/world.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue