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

make entities have a reference to WeakWorlds instead

... and other exciting bug fixes
This commit is contained in:
mat 2022-12-11 00:15:37 -06:00
parent 2d6737b247
commit 37b9f10b3b
17 changed files with 531 additions and 335 deletions

View file

@ -292,6 +292,11 @@ impl From<String> for Component {
})
}
}
impl From<&str> for Component {
fn from(s: &str) -> Self {
Self::from(s.to_string())
}
}
impl Display for Component {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View file

@ -78,6 +78,15 @@ impl ChatPacket {
pub fn content(&self) -> String {
self.split_sender_and_content().1
}
/// Create a new ChatPacket from a string. This is meant to be used as a
/// convenience function for testing.
pub fn new(message: &str) -> Self {
ChatPacket::System(Arc::new(ClientboundSystemChatPacket {
content: Component::from(message),
overlay: false,
}))
}
}
impl Client {

View file

@ -30,10 +30,10 @@ use azalea_protocol::{
};
use azalea_world::{
entity::{metadata, Entity, EntityData, EntityMetadata},
WeakWorld, WeakWorldContainer, World,
PartialWorld, WeakWorld, WeakWorldContainer,
};
use log::{debug, error, info, trace, warn};
use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
use parking_lot::{Mutex, RwLock};
use std::{
any,
backtrace::Backtrace,
@ -92,7 +92,7 @@ pub struct Client {
pub write_conn: Arc<tokio::sync::Mutex<WriteConnection<ServerboundGamePacket>>>,
pub entity_id: Arc<RwLock<u32>>,
/// The world that this client has access to. This supports shared worlds.
pub world: Arc<RwLock<World>>,
pub world: Arc<RwLock<PartialWorld>>,
/// 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.
@ -180,7 +180,7 @@ impl Client {
write_conn,
// default our id to 0, it'll be set later
entity_id: Arc::new(RwLock::new(0)),
world: Arc::new(RwLock::new(World::default())),
world: Arc::new(RwLock::new(PartialWorld::default())),
world_container: world_container
.unwrap_or_else(|| Arc::new(RwLock::new(WeakWorldContainer::new()))),
world_name: Arc::new(RwLock::new(None)),
@ -509,16 +509,18 @@ impl Client {
.as_int()
.expect("min_y tag is not an int");
let world_name = p.dimension.clone();
*client.world_name.write() = Some(world_name.clone());
// add this world to the world_container (or don't if it's already there)
let weak_world =
client
let weak_world = client
.world_container
.write()
.insert(p.dimension.clone(), height, min_y);
.insert(world_name, 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();
*world_lock = World::new(
*world_lock = PartialWorld::new(
client.client_information.read().view_distance.into(),
weak_world,
p.player_id,
@ -1011,7 +1013,7 @@ impl Client {
// return if there's no chunk at the player's position
{
let world_lock = client.world.read();
let world_lock = client.world();
let player_entity_id = *client.entity_id.read();
let player_entity = world_lock.entity(player_entity_id);
let Some(player_entity) = player_entity else {
@ -1037,46 +1039,24 @@ 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.
/// Get a reference to our (potentially shared) world.
///
/// # Panics
/// Panics if the client has not received the login packet yet. You can
/// check this with [`Client::logged_in`].
/// This gets the [`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.
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");
}
self.world.read().shared.clone()
}
/// Returns the entity associated to the player.
pub fn entity_mut(&self) -> Entity<RwLockWriteGuard<World>> {
pub fn entity(&self) -> Entity<Arc<WeakWorld>> {
let entity_id = *self.entity_id.read();
let world = self.world.write();
let entity_data = world
.entity_storage
.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)
}
/// Returns the entity associated to the player.
pub fn entity(&self) -> Entity<RwLockReadGuard<World>> {
let entity_id = *self.entity_id.read();
let world = self.world.read();
let world = self.world();
let entity_data = world
.entity_storage
.read()
.get_by_id(entity_id)
.expect("Player entity should be in the given world");
let entity_ptr = unsafe { entity_data.as_ptr() };

View file

@ -107,7 +107,7 @@ impl Client {
};
drop(player_entity);
let mut player_entity = self.entity_mut();
let mut player_entity = self.entity();
if sending_position {
player_entity.last_pos = *player_entity.pos();
@ -192,7 +192,7 @@ impl Client {
// server ai step
{
let mut player_entity = self.entity_mut();
let mut player_entity = self.entity();
let physics_state = self.physics_state.lock();
player_entity.xxa = physics_state.left_impulse;
@ -222,7 +222,7 @@ impl Client {
self.set_sprinting(true);
}
let mut player_entity = self.entity_mut();
let mut player_entity = self.entity();
player_entity.ai_step();
}
@ -318,7 +318,7 @@ impl Client {
/// player. You should use the [`walk`] and [`sprint`] methods instead.
/// Returns if the operation was successful.
fn set_sprinting(&mut self, sprinting: bool) -> bool {
let mut player_entity = self.entity_mut();
let mut player_entity = self.entity();
player_entity.metadata.sprinting = sprinting;
if sprinting {
player_entity
@ -341,14 +341,13 @@ impl Client {
/// If you're making a realistic client, calling this function every tick is
/// recommended.
pub fn set_jumping(&mut self, jumping: bool) {
let mut player_entity = self.entity_mut();
let mut player_entity = self.entity();
player_entity.jumping = jumping;
}
/// Returns whether the player will try to jump next tick.
pub fn jumping(&self) -> bool {
let player_entity = self.entity();
player_entity.jumping
}
@ -357,7 +356,7 @@ impl Client {
/// f3 screen.
/// `y_rot` goes from -180 to 180, and `x_rot` goes from -90 to 90.
pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) {
let mut player_entity = self.entity_mut();
let mut player_entity = self.entity();
player_entity.set_rotation(y_rot, x_rot);
}

View file

@ -1,12 +1,12 @@
use azalea_auth::game_profile::GameProfile;
use azalea_chat::Component;
use azalea_core::GameType;
use azalea_world::World;
use azalea_world::PartialWorld;
use uuid::Uuid;
/// Something that has a world associated to it. this is usually a `Client`.
pub trait WorldHaver {
fn world(&self) -> &World;
fn world(&self) -> &PartialWorld;
}
/// A player in the tab list.

View file

@ -6,11 +6,11 @@ mod world_collisions;
use azalea_core::{Axis, Vec3, AABB, EPSILON};
use azalea_world::entity::{Entity, EntityData};
use azalea_world::{MoveEntityError, World};
use azalea_world::{MoveEntityError, WeakWorld};
pub use blocks::BlockWithShape;
pub use discrete_voxel_shape::*;
pub use shape::*;
use std::ops::DerefMut;
use std::ops::Deref;
use world_collisions::CollisionGetter;
pub enum MoverType {
@ -33,7 +33,7 @@ pub trait MovableEntity {
) -> Result<(), MoveEntityError>;
}
impl HasCollision for World {
impl<D: Deref<Target = WeakWorld>> HasCollision for D {
// private Vec3 collide(Vec3 var1) {
// AABB var2 = this.getBoundingBox();
// List var3 = this.level.getEntityCollisions(this,
@ -87,7 +87,7 @@ impl HasCollision for World {
}
}
impl<D: DerefMut<Target = World>> MovableEntity for Entity<'_, D> {
impl<D: Deref<Target = WeakWorld>> MovableEntity for Entity<'_, D> {
/// Move an entity by a given delta, checking for collisions.
fn move_colliding(
&mut self,
@ -206,7 +206,7 @@ fn collide_bounding_box(
entity: Option<&EntityData>,
movement: &Vec3,
entity_bounding_box: &AABB,
world: &World,
world: &WeakWorld,
entity_collisions: Vec<VoxelShape>,
) -> Vec3 {
let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1);

View file

@ -2,8 +2,8 @@ use crate::collision::{BlockWithShape, VoxelShape, AABB};
use azalea_block::BlockState;
use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON};
use azalea_world::entity::EntityData;
use azalea_world::{Chunk, World};
use parking_lot::Mutex;
use azalea_world::{Chunk, WeakWorld};
use parking_lot::RwLock;
use std::sync::Arc;
use super::Shapes;
@ -16,7 +16,7 @@ pub trait CollisionGetter {
) -> BlockCollisions<'a>;
}
impl CollisionGetter for World {
impl CollisionGetter for WeakWorld {
fn get_block_collisions<'a>(
&'a self,
entity: Option<&EntityData>,
@ -27,7 +27,7 @@ impl CollisionGetter for World {
}
pub struct BlockCollisions<'a> {
pub world: &'a World,
pub world: &'a WeakWorld,
// context: CollisionContext,
pub aabb: AABB,
pub entity_shape: VoxelShape,
@ -37,7 +37,7 @@ pub struct BlockCollisions<'a> {
impl<'a> BlockCollisions<'a> {
// TODO: the entity is stored in the context
pub fn new(world: &'a World, _entity: Option<&EntityData>, aabb: AABB) -> Self {
pub fn new(world: &'a WeakWorld, _entity: Option<&EntityData>, aabb: AABB) -> Self {
let origin_x = (aabb.min_x - EPSILON) as i32 - 1;
let origin_y = (aabb.min_y - EPSILON) as i32 - 1;
let origin_z = (aabb.min_z - EPSILON) as i32 - 1;
@ -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<RwLock<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);
@ -96,7 +96,7 @@ impl<'a> Iterator for BlockCollisions<'a> {
let pos = item.pos;
let block_state: BlockState = chunk
.lock()
.read()
.get(&(&pos).into(), self.world.min_y())
.unwrap_or(BlockState::Air);

View file

@ -2,15 +2,14 @@
pub mod collision;
use std::ops::DerefMut;
use azalea_block::{Block, BlockState};
use azalea_core::{BlockPos, Vec3};
use azalea_world::{
entity::{Entity, EntityData},
World,
WeakWorld,
};
use collision::{MovableEntity, MoverType};
use std::ops::Deref;
pub trait HasPhysics {
fn travel(&mut self, acceleration: &Vec3);
@ -19,7 +18,7 @@ pub trait HasPhysics {
fn jump_from_ground(&mut self);
}
impl<D: DerefMut<Target = World>> HasPhysics for Entity<'_, D> {
impl<D: Deref<Target = WeakWorld>> HasPhysics for Entity<'_, D> {
/// Move the entity with the given acceleration while handling friction,
/// gravity, collisions, and some other stuff.
fn travel(&mut self, acceleration: &Vec3) {
@ -143,7 +142,7 @@ fn get_block_pos_below_that_affects_movement(entity: &EntityData) -> BlockPos {
)
}
fn handle_relative_friction_and_calculate_movement<D: DerefMut<Target = World>>(
fn handle_relative_friction_and_calculate_movement<D: Deref<Target = WeakWorld>>(
entity: &mut Entity<D>,
acceleration: &Vec3,
block_friction: f32,
@ -183,7 +182,7 @@ fn get_friction_influenced_speed(entity: &EntityData, friction: f32) -> f32 {
/// Returns the what the entity's jump should be multiplied by based on the
/// block they're standing on.
fn block_jump_factor<D: DerefMut<Target = World>>(entity: &Entity<D>) -> f32 {
fn block_jump_factor<D: Deref<Target = WeakWorld>>(entity: &Entity<D>) -> f32 {
let block_at_pos = entity.world.get_block_state(&entity.pos().into());
let block_below = entity
.world
@ -211,11 +210,11 @@ fn block_jump_factor<D: DerefMut<Target = World>>(entity: &Entity<D>) -> f32 {
// public double getJumpBoostPower() {
// return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F *
// (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; }
fn jump_power<D: DerefMut<Target = World>>(entity: &Entity<D>) -> f32 {
fn jump_power<D: Deref<Target = WeakWorld>>(entity: &Entity<D>) -> f32 {
0.42 * block_jump_factor(entity)
}
fn jump_boost_power<D: DerefMut<Target = World>>(_entity: &Entity<D>) -> f64 {
fn jump_boost_power<D: Deref<Target = WeakWorld>>(_entity: &Entity<D>) -> f64 {
// TODO: potion effects
// if let Some(effects) = entity.effects() {
// if let Some(jump_effect) = effects.get(&Effect::Jump) {
@ -235,13 +234,13 @@ mod tests {
use azalea_core::ChunkPos;
use azalea_world::{
entity::{metadata, EntityMetadata},
Chunk, World,
Chunk, PartialWorld,
};
use uuid::Uuid;
#[test]
fn test_gravity() {
let mut world = World::default();
let mut world = PartialWorld::default();
world.add_entity(
0,
@ -272,7 +271,7 @@ mod tests {
}
#[test]
fn test_collision() {
let mut world = World::default();
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();
@ -305,7 +304,7 @@ mod tests {
#[test]
fn test_slab_collision() {
let mut world = World::default();
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();
@ -339,7 +338,7 @@ mod tests {
#[test]
fn test_top_slab_collision() {
let mut world = World::default();
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();
@ -373,7 +372,7 @@ mod tests {
#[test]
fn test_weird_wall_collision() {
let mut world = World::default();
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();

View file

@ -1,20 +1,16 @@
use crate::palette::PalettedContainer;
use crate::palette::PalettedContainerType;
use crate::World;
use azalea_block::BlockState;
use azalea_buf::BufReadError;
use azalea_buf::{McBufReadable, McBufWritable};
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
use log::debug;
use log::trace;
use log::warn;
use parking_lot::Mutex;
use log::{debug, trace, warn};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::fmt::Debug;
use std::io::Cursor;
use std::sync::Weak;
use std::{io::Write, sync::Arc};
use std::{
collections::HashMap,
fmt::Debug,
io::{Cursor, Write},
sync::{Arc, Weak},
};
const SECTION_HEIGHT: u32 = 16;
@ -30,7 +26,7 @@ pub struct PartialChunkStorage {
chunk_radius: u32,
view_range: u32,
// chunks is a list of size chunk_radius * chunk_radius
chunks: Vec<Option<Arc<Mutex<Chunk>>>>,
chunks: Vec<Option<Arc<RwLock<Chunk>>>>,
}
/// A storage for chunks where they're only stored weakly, so if they're not
@ -39,7 +35,7 @@ pub struct PartialChunkStorage {
pub struct WeakChunkStorage {
pub height: u32,
pub min_y: i32,
pub chunks: HashMap<ChunkPos, Weak<Mutex<Chunk>>>,
pub chunks: HashMap<ChunkPos, Weak<RwLock<Chunk>>>,
}
/// A storage of potentially infinite chunks in a world. Chunks are stored as
@ -47,7 +43,7 @@ pub struct WeakChunkStorage {
pub struct ChunkStorage {
pub height: u32,
pub min_y: i32,
pub chunks: HashMap<ChunkPos, Arc<Mutex<Chunk>>>,
pub chunks: HashMap<ChunkPos, Arc<RwLock<Chunk>>>,
}
/// A single chunk in a world (16*?*16 blocks). This only contains the blocks
@ -113,20 +109,13 @@ impl PartialChunkStorage {
&& (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius
}
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
let chunk_pos = ChunkPos::from(pos);
let chunk = self.get(&chunk_pos)?;
let chunk = chunk.lock();
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) {
return None;
}
let chunk_pos = ChunkPos::from(pos);
let chunk = self.get(&chunk_pos)?;
let mut chunk = chunk.lock();
let mut chunk = chunk.write();
Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y()))
}
@ -145,7 +134,7 @@ impl PartialChunkStorage {
return Ok(());
}
let chunk = Arc::new(Mutex::new(Chunk::read_with_dimension_height(
let chunk = Arc::new(RwLock::new(Chunk::read_with_dimension_height(
data,
self.height(),
)?));
@ -158,7 +147,7 @@ impl PartialChunkStorage {
/// Get a [`Chunk`] within render distance, or `None` if it's not loaded.
/// Use [`PartialChunkStorage::get`] to get a chunk from the shared storage.
pub fn limited_get(&self, pos: &ChunkPos) -> Option<&Arc<Mutex<Chunk>>> {
pub fn limited_get(&self, pos: &ChunkPos) -> Option<&Arc<RwLock<Chunk>>> {
if !self.in_range(pos) {
warn!(
"Chunk at {:?} is not in the render distance (center: {:?}, {} chunks)",
@ -173,7 +162,7 @@ impl PartialChunkStorage {
/// Get a mutable reference to a [`Chunk`] within render distance, or
/// `None` if it's not loaded. Use [`PartialChunkStorage::get`] to get
/// a chunk from the shared storage.
pub fn limited_get_mut(&mut self, pos: &ChunkPos) -> Option<&mut Option<Arc<Mutex<Chunk>>>> {
pub fn limited_get_mut(&mut self, pos: &ChunkPos) -> Option<&mut Option<Arc<RwLock<Chunk>>>> {
if !self.in_range(pos) {
return None;
}
@ -183,7 +172,7 @@ impl PartialChunkStorage {
}
/// Get a chunk,
pub fn get(&self, pos: &ChunkPos) -> Option<Arc<Mutex<Chunk>>> {
pub fn get(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
self.shared
.read()
.chunks
@ -196,7 +185,7 @@ impl PartialChunkStorage {
///
/// # Panics
/// If the chunk is not in the render distance.
pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Arc<Mutex<Chunk>>>) {
pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Arc<RwLock<Chunk>>>) {
if let Some(chunk) = &chunk {
self.shared
.write()
@ -219,16 +208,30 @@ impl WeakChunkStorage {
chunks: HashMap::new(),
}
}
pub fn get(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
self.chunks.get(pos).and_then(|chunk| chunk.upgrade())
}
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
let chunk_pos = ChunkPos::from(pos);
let chunk = self.get(&chunk_pos)?;
let chunk = chunk.read();
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) {
return None;
}
let chunk_pos = ChunkPos::from(pos);
let chunk = self.get(&chunk_pos)?;
let mut chunk = chunk.write();
Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y))
}
}
impl Chunk {
pub fn read_with_dimension(
buf: &mut Cursor<&[u8]>,
data: &World,
) -> Result<Self, BufReadError> {
Self::read_with_dimension_height(buf, data.height())
}
pub fn read_with_dimension_height(
buf: &mut Cursor<&[u8]>,
dimension_height: u32,
@ -242,18 +245,12 @@ impl Chunk {
Ok(Chunk { sections })
}
pub fn section_index(&self, y: i32, min_y: i32) -> u32 {
assert!(y >= min_y, "y ({y}) must be at least {min_y}");
let min_section_index = min_y.div_floor(16);
(y.div_floor(16) - min_section_index) as u32
}
pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> {
if pos.y < min_y {
// y position is out of bounds
return None;
}
let section_index = self.section_index(pos.y, min_y) as usize;
let section_index = section_index(pos.y, min_y) as usize;
if section_index >= self.sections.len() {
// y position is out of bounds
return None;
@ -270,7 +267,7 @@ impl Chunk {
state: BlockState,
min_y: i32,
) -> BlockState {
let section_index = self.section_index(pos.y, min_y);
let section_index = section_index(pos.y, min_y);
// TODO: make sure the section exists
let section = &mut self.sections[section_index as usize];
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
@ -278,7 +275,7 @@ impl Chunk {
}
pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
let section_index = self.section_index(pos.y, min_y);
let section_index = section_index(pos.y, min_y);
// TODO: make sure the section exists
let section = &mut self.sections[section_index as usize];
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
@ -384,21 +381,28 @@ impl Default for WeakChunkStorage {
}
}
/// Get the index of where a section is in a chunk based on its y coordinate
/// and the minimum y coordinate of the world.
pub fn section_index(y: i32, min_y: i32) -> u32 {
assert!(y >= min_y, "y ({y}) must be at least {min_y}");
let min_section_index = min_y.div_floor(16);
(y.div_floor(16) - min_section_index) as u32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_section_index() {
let chunk = Chunk::default();
assert_eq!(chunk.section_index(0, 0), 0);
assert_eq!(chunk.section_index(128, 0), 8);
assert_eq!(chunk.section_index(127, 0), 7);
assert_eq!(chunk.section_index(0, -64), 4);
assert_eq!(chunk.section_index(-64, -64), 0);
assert_eq!(chunk.section_index(-49, -64), 0);
assert_eq!(chunk.section_index(-48, -64), 1);
assert_eq!(chunk.section_index(128, -64), 12);
assert_eq!(section_index(0, 0), 0);
assert_eq!(section_index(128, 0), 8);
assert_eq!(section_index(127, 0), 7);
assert_eq!(section_index(0, -64), 4);
assert_eq!(section_index(-64, -64), 0);
assert_eq!(section_index(-49, -64), 0);
assert_eq!(section_index(-48, -64), 1);
assert_eq!(section_index(128, -64), 12);
}
#[test]
@ -406,21 +410,31 @@ mod tests {
let mut chunk_storage = PartialChunkStorage::default();
chunk_storage.set(
&ChunkPos { x: 0, z: 0 },
Some(Arc::new(Mutex::new(Chunk::default()))),
Some(Arc::new(RwLock::new(Chunk::default()))),
);
assert!(chunk_storage
.shared
.read()
.get_block_state(&BlockPos { x: 0, y: 319, z: 0 })
.is_some());
assert!(chunk_storage
.shared
.read()
.get_block_state(&BlockPos { x: 0, y: 320, z: 0 })
.is_none());
assert!(chunk_storage
.shared
.read()
.get_block_state(&BlockPos { x: 0, y: 338, z: 0 })
.is_none());
assert!(chunk_storage
.shared
.read()
.get_block_state(&BlockPos { x: 0, y: -64, z: 0 })
.is_some());
assert!(chunk_storage
.shared
.read()
.get_block_state(&BlockPos { x: 0, y: -65, z: 0 })
.is_none());
}

View file

@ -5,7 +5,7 @@ pub mod metadata;
use self::attributes::{AttributeInstance, AttributeModifiers};
pub use self::metadata::EntityMetadata;
use crate::World;
use crate::WeakWorld;
use azalea_block::BlockState;
use azalea_core::{BlockPos, Vec3, AABB};
pub use data::*;
@ -17,7 +17,7 @@ use uuid::Uuid;
/// A reference to an entity in a world.
#[derive(Debug)]
pub struct Entity<'d, D = &'d mut World> {
pub struct Entity<'d, D = &'d WeakWorld> {
/// The world this entity is in.
pub world: D,
/// The incrementing numerical id of the entity.
@ -26,7 +26,7 @@ pub struct Entity<'d, D = &'d mut World> {
_marker: PhantomData<&'d ()>,
}
impl<'d, D: Deref<Target = World>> Entity<'d, D> {
impl<'d, D: Deref<Target = WeakWorld>> Entity<'d, D> {
pub fn new(world: D, id: u32, data: NonNull<EntityData>) -> Self {
// TODO: have this be based on the entity type
Self {
@ -38,7 +38,7 @@ impl<'d, D: Deref<Target = World>> Entity<'d, D> {
}
}
impl<'d, D: DerefMut<Target = World>> Entity<'d, D> {
impl<'d, D: Deref<Target = WeakWorld>> Entity<'d, D> {
/// Sets the position of the entity. This doesn't update the cache in
/// azalea-world, and should only be used within azalea-world!
///
@ -95,7 +95,7 @@ impl<'d, D: DerefMut<Target = World>> Entity<'d, D> {
}
}
impl<'d, D: Deref<Target = World>> Entity<'d, D> {
impl<'d, D: Deref<Target = WeakWorld>> Entity<'d, D> {
#[inline]
pub fn pos(&self) -> &Vec3 {
&self.pos
@ -150,8 +150,8 @@ impl<'d, D: Deref<Target = World>> Entity<'d, D> {
// impl<
// 'd,
// D: DerefMut<Target = World> + Deref<Target = World>,
// D2: Deref<Target = World>,
// D: Deref<Target = WeakWorld> + Deref<Target = WeakWorld>,
// D2: Deref<Target = WeakWorld>,
// > From<Entity<'d, D>> for Entity<'d, D2>
// {
// fn from(entity: Entity<'d, D>) -> Entity<'d, D> {
@ -164,13 +164,13 @@ impl<'d, D: Deref<Target = World>> Entity<'d, D> {
// }
// }
impl<D: DerefMut<Target = World>> DerefMut for Entity<'_, D> {
impl<D: Deref<Target = WeakWorld>> DerefMut for Entity<'_, D> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.data.as_mut() }
}
}
impl<D: Deref<Target = World>> Deref for Entity<'_, D> {
impl<D: Deref<Target = WeakWorld>> Deref for Entity<'_, D> {
type Target = EntityData;
fn deref(&self) -> &Self::Target {
@ -287,10 +287,11 @@ impl EntityData {
#[cfg(test)]
mod tests {
use super::*;
use crate::PartialWorld;
#[test]
fn from_mut_entity_to_ref_entity() {
let mut world = World::default();
let mut world = PartialWorld::default();
let uuid = Uuid::from_u128(100);
world.add_entity(
0,
@ -301,7 +302,6 @@ mod tests {
),
);
let entity: Entity = world.entity_mut(0).unwrap();
let entity_ref = Entity::from(entity);
assert_eq!(entity_ref.uuid, uuid);
assert_eq!(entity.uuid, uuid);
}
}

View file

@ -34,7 +34,11 @@ use uuid::Uuid;
pub struct PartialEntityStorage {
pub shared: Arc<RwLock<WeakEntityStorage>>,
/// The entity id of the player that owns this struct.
/// The entity id of the player that owns this partial world. This will
/// make [`PartialWorld::entity_mut`] pretend the entity doesn't exist so
/// it doesn't get modified from outside sources.
///
/// [`PartialWorld::entity_mut`]: crate::PartialWorld::entity_mut
pub owner_entity_id: u32,
pub updates_received: IntMap<u32, u32>,
/// Strong references to the entities we have loaded.
@ -172,11 +176,7 @@ impl PartialEntityStorage {
/// Get an entity in the shared storage by its id, if it exists.
#[inline]
pub fn get_by_id(&self, id: u32) -> Option<Arc<EntityData>> {
self.shared
.read()
.data_by_id
.get(&id)
.and_then(|e| e.upgrade())
self.shared.read().get_by_id(id)
}
/// Get a reference to an entity by its UUID, if it's being loaded by this
@ -204,13 +204,7 @@ impl PartialEntityStorage {
/// 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())
})
self.shared.read().get_by_uuid(uuid)
}
/// Clear all entities in a chunk. This will not clear them from the
@ -241,46 +235,23 @@ impl PartialEntityStorage {
old_chunk: &ChunkPos,
new_chunk: &ChunkPos,
) {
if let Some(entities) = self.shared.write().ids_by_chunk.get_mut(old_chunk) {
entities.remove(&entity_id);
}
self.shared
.write()
.ids_by_chunk
.entry(*new_chunk)
.or_default()
.insert(entity_id);
.update_entity_chunk(entity_id, old_chunk, new_chunk);
}
pub fn find_one_entity<F>(&self, mut f: F) -> Option<Arc<EntityData>>
pub fn find_entity<F>(&self, mut f: F) -> Option<Arc<EntityData>>
where
F: FnMut(&Arc<EntityData>) -> bool,
{
for entity in self.shared.read().entities() {
if let Some(entity) = entity.upgrade() {
if f(&entity) {
return Some(entity);
}
}
}
None
self.shared.read().find_entity(|e| f(e))
}
pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<Arc<EntityData>>
pub fn find_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<Arc<EntityData>>
where
F: FnMut(&EntityData) -> bool,
{
let shared = self.shared.read();
if let Some(entities) = shared.ids_by_chunk.get(chunk) {
for entity_id in entities {
if let Some(entity) = shared.data_by_id.get(entity_id).and_then(|e| e.upgrade()) {
if f(&entity) {
return Some(entity);
}
}
}
}
None
self.shared.read().find_entity_in_chunk(chunk, |e| f(e))
}
}
@ -366,6 +337,67 @@ impl WeakEntityStorage {
pub fn contains_id(&self, id: &u32) -> bool {
self.data_by_id.contains_key(id)
}
/// Get an entity by its id, if it exists.
#[inline]
pub fn get_by_id(&self, id: u32) -> Option<Arc<EntityData>> {
self.data_by_id.get(&id).and_then(|e| e.upgrade())
}
/// 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.id_by_uuid
.get(uuid)
.and_then(|id| self.data_by_id.get(id).and_then(|e| e.upgrade()))
}
pub fn find_entity<F>(&self, mut f: F) -> Option<Arc<EntityData>>
where
F: FnMut(&Arc<EntityData>) -> bool,
{
for entity in self.entities() {
if let Some(entity) = entity.upgrade() {
if f(&entity) {
return Some(entity);
}
}
}
None
}
pub fn find_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<Arc<EntityData>>
where
F: FnMut(&EntityData) -> bool,
{
if let Some(entities) = self.ids_by_chunk.get(chunk) {
for entity_id in entities {
if let Some(entity) = self.data_by_id.get(entity_id).and_then(|e| e.upgrade()) {
if f(&entity) {
return Some(entity);
}
}
}
}
None
}
/// Move an entity from its old chunk to a new chunk.
#[inline]
pub fn update_entity_chunk(
&mut self,
entity_id: u32,
old_chunk: &ChunkPos,
new_chunk: &ChunkPos,
) {
if let Some(entities) = self.ids_by_chunk.get_mut(old_chunk) {
entities.remove(&entity_id);
}
self.ids_by_chunk
.entry(*new_chunk)
.or_default()
.insert(entity_id);
}
}
#[cfg(test)]

View file

@ -6,18 +6,24 @@ use crate::{
use azalea_block::BlockState;
use azalea_buf::BufReadError;
use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3};
use parking_lot::{Mutex, RwLock};
use parking_lot::RwLock;
use std::{backtrace::Backtrace, fmt::Debug};
use std::{fmt::Formatter, 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.
/// PartialWorlds are usually owned by clients, and hold strong references to
/// chunks and entities in [`WeakWorld`]s.
///
/// Basically, they hold the chunks and entities that are within render
/// distance but can still access chunks and entities owned by other
/// `PartialWorld`s that have the same `WeakWorld`.
///
/// This is primarily useful for having multiple clients in the same world.
#[derive(Default)]
pub struct World {
pub struct PartialWorld {
// we just need to keep a strong reference to `shared` so it doesn't get
// dropped, we don't need to do anything with it
_shared: Arc<WeakWorld>,
pub shared: Arc<WeakWorld>,
pub chunk_storage: PartialChunkStorage,
pub entity_storage: PartialEntityStorage,
@ -31,10 +37,10 @@ pub struct WeakWorld {
pub entity_storage: Arc<RwLock<WeakEntityStorage>>,
}
impl World {
impl PartialWorld {
pub fn new(chunk_radius: u32, shared: Arc<WeakWorld>, owner_entity_id: u32) -> Self {
World {
_shared: shared.clone(),
PartialWorld {
shared: shared.clone(),
chunk_storage: PartialChunkStorage::new(chunk_radius, shared.chunk_storage.clone()),
entity_storage: PartialEntityStorage::new(
shared.entity_storage.clone(),
@ -51,13 +57,13 @@ impl World {
self.chunk_storage.replace_with_packet_data(pos, data)
}
pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<Mutex<Chunk>>> {
pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<RwLock<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))));
.set(pos, chunk.map(|c| Arc::new(RwLock::new(c))));
Ok(())
}
@ -65,14 +71,24 @@ impl World {
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)
}
/// Returns a mutable reference to the entity with the given ID.
pub fn entity_mut(&mut self, id: u32) -> Option<Entity<'_, &WeakWorld>> {
// no entity for you (we're processing this entity somewhere else)
if id != self.entity_storage.owner_entity_id && !self.entity_storage.maybe_update(id) {
return None;
}
self.shared.entity(id)
}
pub fn add_entity(&mut self, id: u32, entity: EntityData) {
self.entity_storage.insert(id, entity);
}
pub fn set_entity_pos(&mut self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> {
let mut entity = self
.entity_mut(entity_id)
@ -109,51 +125,6 @@ impl World {
}
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))
}
/// Returns a mutable reference to the entity with the given ID.
pub fn entity_mut(&mut self, id: u32) -> Option<Entity<'_, &mut World>> {
// no entity for you (we're processing this entity somewhere else)
if id != self.entity_storage.owner_entity_id && !self.entity_storage.maybe_update(id) {
return None;
}
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 {
@ -164,16 +135,68 @@ impl WeakWorld {
}
}
/// Read the total height of the world. You can add this to [`Self::min_y`]
/// to get the highest possible y coordinate a block can be placed at.
pub fn height(&self) -> u32 {
self.chunk_storage.read().height
}
/// Get the lowest possible y coordinate a block can be placed at.
pub fn min_y(&self) -> i32 {
self.chunk_storage.read().min_y
}
pub fn entity_data_by_id(&self, id: u32) -> Option<Arc<EntityData>> {
self.entity_storage.read().get_by_id(id)
}
impl Debug for World {
/// Returns a entity with the given ID.
///
/// The returned Entity can technically be mutated, but you should avoid
/// doing any relative mutations.
pub fn entity(&self, id: u32) -> Option<Entity<&WeakWorld>> {
let entity_data = self.entity_storage.read().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.read().get_by_uuid(uuid)
}
pub fn find_entity<F>(&self, mut f: F) -> Option<Arc<EntityData>>
where
F: FnMut(&EntityData) -> bool,
{
self.entity_storage.read().find_entity(|entity| f(entity))
}
pub fn set_entity_pos(&self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> {
let mut entity = self
.entity(entity_id)
.ok_or_else(|| MoveEntityError::EntityDoesNotExist(Backtrace::capture()))?;
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
.write()
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
}
Ok(())
}
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
self.chunk_storage.read().get_block_state(pos)
}
pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
self.chunk_storage.read().get(pos)
}
}
impl Debug for PartialWorld {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("World")
.field("chunk_storage", &self.chunk_storage)

View file

@ -50,7 +50,7 @@ async fn swarm_handle(
if let Some(target) = swarm
.worlds
.read()
.find_one_entity(|e| e.id == "minecraft:player")
.find_entity(|e| e.id == "minecraft:player")
{
for (bot, bot_state) in swarm {
bot.tick_goto_goal(pathfinder::Goals::Reach(target.bounding_box));

View file

@ -85,6 +85,7 @@ pub mod prelude;
mod start;
mod swarm;
pub use azalea_block::*;
pub use azalea_client::*;
pub use azalea_core::{BlockPos, Vec3};
pub use start::{start, Options};

View file

@ -6,7 +6,7 @@ use crate::{Client, Event};
use async_trait::async_trait;
use azalea_core::{BlockPos, CardinalDirection};
use azalea_world::entity::EntityData;
use log::debug;
use log::{debug, error};
use mtdstarlite::Edge;
pub use mtdstarlite::MTDStarLite;
use parking_lot::Mutex;
@ -80,7 +80,7 @@ impl Trait for azalea_client::Client {
let successors = |node: &Node| {
let mut edges = Vec::new();
let world = self.world.read();
let world = &self.world.read().shared;
for possible_move in possible_moves.iter() {
edges.push(Edge {
target: possible_move.next_node(node),
@ -111,7 +111,11 @@ impl Trait for azalea_client::Client {
.expect("Pathfinder plugin not installed!")
.clone();
// convert the Option<Vec<Node>> to a VecDeque<Node>
*state.path.lock() = p.expect("no path").into_iter().collect();
if let Some(p) = p {
*state.path.lock() = p.into_iter().collect();
} else {
error!("no path found");
}
}
}

View file

@ -1,10 +1,10 @@
use super::{Node, VerticalVel};
use azalea_core::{BlockPos, CardinalDirection};
use azalea_physics::collision::{self, BlockWithShape};
use azalea_world::World;
use azalea_world::WeakWorld;
/// whether this block is passable
fn is_block_passable(pos: &BlockPos, world: &World) -> bool {
fn is_block_passable(pos: &BlockPos, world: &WeakWorld) -> bool {
if let Some(block) = world.get_block_state(pos) {
block.shape() == &collision::empty_shape()
} else {
@ -13,7 +13,7 @@ fn is_block_passable(pos: &BlockPos, world: &World) -> bool {
}
/// whether this block has a solid hitbox (i.e. we can stand on it)
fn is_block_solid(pos: &BlockPos, world: &World) -> bool {
fn is_block_solid(pos: &BlockPos, world: &WeakWorld) -> bool {
if let Some(block) = world.get_block_state(pos) {
block.shape() == &collision::block_shape()
} else {
@ -22,14 +22,14 @@ fn is_block_solid(pos: &BlockPos, world: &World) -> bool {
}
/// Whether this block and the block above are passable
fn is_passable(pos: &BlockPos, world: &World) -> bool {
fn is_passable(pos: &BlockPos, world: &WeakWorld) -> bool {
is_block_passable(pos, world) && is_block_passable(&pos.up(1), world)
}
/// Whether we can stand in this position. Checks if the block below is solid,
/// and that the two blocks above that are passable.
fn is_standable(pos: &BlockPos, world: &World) -> bool {
fn is_standable(pos: &BlockPos, world: &WeakWorld) -> bool {
is_block_solid(&pos.down(1), world) && is_passable(pos, world)
}
@ -37,7 +37,7 @@ const JUMP_COST: f32 = 0.5;
const WALK_ONE_BLOCK_COST: f32 = 1.0;
pub trait Move {
fn cost(&self, world: &World, node: &Node) -> f32;
fn cost(&self, world: &WeakWorld, node: &Node) -> f32;
/// Returns by how much the entity's position should be changed when this
/// move is executed.
fn offset(&self) -> BlockPos;
@ -51,7 +51,7 @@ pub trait Move {
pub struct ForwardMove(pub CardinalDirection);
impl Move for ForwardMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
fn cost(&self, world: &WeakWorld, node: &Node) -> f32 {
if is_standable(&(node.pos + self.offset()), world)
&& node.vertical_vel == VerticalVel::None
{
@ -67,7 +67,7 @@ impl Move for ForwardMove {
pub struct AscendMove(pub CardinalDirection);
impl Move for AscendMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
fn cost(&self, world: &WeakWorld, node: &Node) -> f32 {
if node.vertical_vel == VerticalVel::None
&& is_block_passable(&node.pos.up(2), world)
&& is_standable(&(node.pos + self.offset()), world)
@ -89,7 +89,7 @@ impl Move for AscendMove {
}
pub struct DescendMove(pub CardinalDirection);
impl Move for DescendMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
fn cost(&self, world: &WeakWorld, node: &Node) -> f32 {
// check whether 3 blocks vertically forward are passable
if node.vertical_vel == VerticalVel::None
&& is_standable(&(node.pos + self.offset()), world)
@ -112,7 +112,7 @@ impl Move for DescendMove {
}
pub struct DiagonalMove(pub CardinalDirection);
impl Move for DiagonalMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
fn cost(&self, world: &WeakWorld, node: &Node) -> f32 {
if node.vertical_vel != VerticalVel::None {
return f32::INFINITY;
}
@ -151,37 +151,46 @@ mod tests {
use super::*;
use azalea_block::BlockState;
use azalea_core::ChunkPos;
use azalea_world::Chunk;
use azalea_world::{Chunk, PartialWorld};
#[test]
fn test_is_passable() {
let mut world = World::default();
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();
world.set_block_state(&BlockPos::new(0, 0, 0), BlockState::Stone);
world.set_block_state(&BlockPos::new(0, 1, 0), BlockState::Air);
assert_eq!(is_block_passable(&BlockPos::new(0, 0, 0), &world), false);
assert_eq!(is_block_passable(&BlockPos::new(0, 1, 0), &world), true);
assert_eq!(
is_block_passable(&BlockPos::new(0, 0, 0), &world.shared),
false
);
assert_eq!(
is_block_passable(&BlockPos::new(0, 1, 0), &world.shared),
true
);
}
#[test]
fn test_is_solid() {
let mut world = World::default();
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();
world.set_block_state(&BlockPos::new(0, 0, 0), BlockState::Stone);
world.set_block_state(&BlockPos::new(0, 1, 0), BlockState::Air);
assert_eq!(is_block_solid(&BlockPos::new(0, 0, 0), &world), true);
assert_eq!(is_block_solid(&BlockPos::new(0, 1, 0), &world), false);
assert_eq!(is_block_solid(&BlockPos::new(0, 0, 0), &world.shared), true);
assert_eq!(
is_block_solid(&BlockPos::new(0, 1, 0), &world.shared),
false
);
}
#[test]
fn test_is_standable() {
let mut world = World::default();
let mut world = PartialWorld::default();
world
.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
.unwrap();
@ -190,8 +199,8 @@ mod tests {
world.set_block_state(&BlockPos::new(0, 2, 0), BlockState::Air);
world.set_block_state(&BlockPos::new(0, 3, 0), BlockState::Air);
assert!(is_standable(&BlockPos::new(0, 1, 0), &world));
assert!(!is_standable(&BlockPos::new(0, 0, 0), &world));
assert!(!is_standable(&BlockPos::new(0, 2, 0), &world));
assert!(is_standable(&BlockPos::new(0, 1, 0), &world.shared));
assert!(!is_standable(&BlockPos::new(0, 0, 0), &world.shared));
assert!(!is_standable(&BlockPos::new(0, 2, 0), &world.shared));
}
}

View file

@ -18,12 +18,12 @@ use async_trait::async_trait;
use azalea_client::{ChatPacket, Client, Event};
use parking_lot::Mutex;
use std::{collections::VecDeque, sync::Arc};
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::sync::broadcast::{Receiver, Sender};
#[derive(Clone)]
pub struct Plugin {
pub swarm_state: SwarmState,
pub tx: UnboundedSender<ChatPacket>,
pub tx: Sender<ChatPacket>,
}
impl crate::Plugin for Plugin {
@ -31,7 +31,7 @@ impl crate::Plugin for Plugin {
fn build(&self) -> State {
State {
farthest_chat_index: Arc::new(Mutex::new(0)),
chat_index: Arc::new(Mutex::new(0)),
swarm_state: self.swarm_state.clone(),
tx: self.tx.clone(),
}
@ -40,8 +40,8 @@ impl crate::Plugin for Plugin {
#[derive(Clone)]
pub struct State {
pub farthest_chat_index: Arc<Mutex<usize>>,
pub tx: UnboundedSender<ChatPacket>,
pub chat_index: Arc<Mutex<usize>>,
pub tx: Sender<ChatPacket>,
pub swarm_state: SwarmState,
}
@ -49,14 +49,11 @@ pub struct State {
pub struct SwarmState {
pub chat_queue: Arc<Mutex<VecDeque<ChatPacket>>>,
pub chat_min_index: Arc<Mutex<usize>>,
pub rx: Arc<tokio::sync::Mutex<UnboundedReceiver<ChatPacket>>>,
pub rx: Arc<tokio::sync::Mutex<Receiver<ChatPacket>>>,
}
#[async_trait]
impl crate::PluginState for State {
async fn handle(self: Box<Self>, event: Event, _bot: Client) {
// we're allowed to access Plugin::swarm_state since it's shared for every bot
if let Event::Chat(m) = event {
impl State {
pub fn handle_chat(&self, message: ChatPacket) {
// When a bot receives a chat messages, it looks into the queue to find the
// earliest instance of the message content that's after the bot's chat index.
// If it finds it, then its personal index is simply updated. Otherwise, fire
@ -64,16 +61,23 @@ impl crate::PluginState for State {
let mut chat_queue = self.swarm_state.chat_queue.lock();
let chat_min_index = self.swarm_state.chat_min_index.lock();
let mut farthest_chat_index = self.farthest_chat_index.lock();
let mut chat_index = self.chat_index.lock();
let actual_vec_index = *farthest_chat_index - *chat_min_index;
if *chat_min_index > *chat_index {
// if this happens it's because this bot just logged in, so
// ignore it and let another bot handle it
println!("chat_min_index ({chat_min_index}) > chat_index ({chat_index})");
*chat_index = *chat_min_index;
return;
}
let actual_vec_index = *chat_index - *chat_min_index;
// go through the queue and find the first message that's after the bot's index
let mut found = false;
for (i, msg) in chat_queue.iter().enumerate().skip(actual_vec_index) {
if msg == &m {
for (i, past_message) in chat_queue.iter().enumerate().skip(actual_vec_index) {
if past_message == &message {
// found the message, update the index
*farthest_chat_index = i + *chat_min_index + 1;
*chat_index = i + *chat_min_index + 1;
found = true;
break;
}
@ -82,21 +86,30 @@ impl crate::PluginState for State {
if !found {
// didn't find the message, so fire the swarm event and add to the queue
self.tx
.send(m.clone())
.send(message.clone())
.expect("failed to send chat message to swarm");
chat_queue.push_back(m);
*farthest_chat_index = chat_queue.len() - 1 + *chat_min_index;
chat_queue.push_back(message);
*chat_index = chat_queue.len() + *chat_min_index;
}
}
}
#[async_trait]
impl crate::PluginState for State {
async fn handle(self: Box<Self>, event: Event, _bot: Client) {
// we're allowed to access Plugin::swarm_state since it's shared for every bot
if let Event::Chat(m) = event {
self.handle_chat(m);
}
}
}
impl SwarmState {
pub fn new<S>(swarm: Swarm<S>) -> (Self, UnboundedSender<ChatPacket>)
pub fn new<S>(swarm: Swarm<S>) -> (Self, Sender<ChatPacket>)
where
S: Send + Sync + Clone + 'static,
{
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let (tx, rx) = tokio::sync::broadcast::channel(1);
let swarm_state = SwarmState {
chat_queue: Arc::new(Mutex::new(VecDeque::new())),
@ -114,28 +127,46 @@ impl SwarmState {
// it should never be locked unless we reused the same plugin for two swarms
// (bad)
let mut rx = self.rx.lock().await;
while let Some(m) = rx.recv().await {
while let Ok(m) = rx.recv().await {
swarm.swarm_tx.send(SwarmEvent::Chat(m)).unwrap();
let bot_states = swarm
.bot_datas
.lock()
.iter()
.map(|(bot, _)| {
bot.plugins
.get::<State>()
.expect("Chat plugin not installed")
.clone()
})
.collect::<Vec<_>>();
self.handle_new_chat_message(&bot_states);
}
}
}
impl SwarmState {
pub fn handle_new_chat_message(&self, bot_states: &[State]) {
// To make sure the queue doesn't grow too large, we keep a `chat_min_index`
// in Swarm that's set to the smallest index of all the bots, and we remove all
// messages from the queue that are before that index.
let chat_min_index = *self.chat_min_index.lock();
let mut new_chat_min_index = usize::MAX;
for (bot, _) in swarm.bot_datas.lock().iter() {
let this_farthest_chat_index = *bot
.plugins
.get::<State>()
.expect("Chat plugin not installed")
.farthest_chat_index
.lock();
if this_farthest_chat_index < new_chat_min_index {
new_chat_min_index = this_farthest_chat_index;
for bot_state in bot_states {
let this_chat_index = *bot_state.chat_index.lock();
if this_chat_index < new_chat_min_index {
new_chat_min_index = this_chat_index;
}
}
let mut chat_queue = self.chat_queue.lock();
if chat_min_index > new_chat_min_index {
println!(
"chat_min_index ({chat_min_index}) > new_chat_min_index ({new_chat_min_index})"
);
return;
}
// remove all messages from the queue that are before the min index
for _ in 0..(new_chat_min_index - chat_min_index) {
chat_queue.pop_front();
@ -145,4 +176,94 @@ impl SwarmState {
*self.chat_min_index.lock() = new_chat_min_index;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_swarm_chat() {
let (tx, mut rx) = tokio::sync::broadcast::channel(1);
let swarm_state = SwarmState {
chat_queue: Arc::new(Mutex::new(VecDeque::new())),
chat_min_index: Arc::new(Mutex::new(0)),
rx: Arc::new(tokio::sync::Mutex::new(rx)),
};
let mut bot_states = vec![];
let bot0 = State {
swarm_state: swarm_state.clone(),
chat_index: Arc::new(Mutex::new(0)),
tx: tx.clone(),
};
let bot1 = State {
swarm_state: swarm_state.clone(),
chat_index: Arc::new(Mutex::new(0)),
tx: tx.clone(),
};
bot_states.push(bot0.clone());
bot_states.push(bot1.clone());
bot0.handle_chat(ChatPacket::new("a"));
// the swarm should get the event immediately after the bot gets it
assert_eq!(
swarm_state.rx.lock().await.try_recv(),
Ok(ChatPacket::new("a"))
);
assert_eq!(*bot0.chat_index.lock(), 1);
// and a second bot sending the event shouldn't do anything
bot1.handle_chat(ChatPacket::new("a"));
assert!(swarm_state.rx.lock().await.try_recv().is_err());
assert_eq!(*bot1.chat_index.lock(), 1);
// but if the first one gets it again, it should sent it again
bot0.handle_chat(ChatPacket::new("a"));
assert_eq!(
swarm_state.rx.lock().await.try_recv(),
Ok(ChatPacket::new("a"))
);
// alright and now the second bot got a different chat message and it should be
// sent
bot1.handle_chat(ChatPacket::new("b"));
assert_eq!(
swarm_state.rx.lock().await.try_recv(),
Ok(ChatPacket::new("b"))
);
}
#[tokio::test]
async fn test_new_bot() {
let (tx, mut rx) = tokio::sync::broadcast::channel(1);
let swarm_state = SwarmState {
chat_queue: Arc::new(Mutex::new(VecDeque::new())),
chat_min_index: Arc::new(Mutex::new(0)),
rx: Arc::new(tokio::sync::Mutex::new(rx)),
};
let mut bot_states = vec![];
let bot0 = State {
swarm_state: swarm_state.clone(),
chat_index: Arc::new(Mutex::new(0)),
tx: tx.clone(),
};
bot_states.push(bot0.clone());
// bot0 gets a chat message
bot0.handle_chat(ChatPacket::new("a"));
assert_eq!(
swarm_state.rx.lock().await.try_recv(),
Ok(ChatPacket::new("a"))
);
// now a second bot joined and got a different chat message
let bot1 = State {
swarm_state: swarm_state.clone(),
chat_index: Arc::new(Mutex::new(0)),
tx: tx.clone(),
};
bot_states.push(bot1.clone());
bot1.handle_chat(ChatPacket::new("b"));
assert_eq!(
swarm_state.rx.lock().await.try_recv(),
Ok(ChatPacket::new("b"))
);
}
}