Skip to content

Commit

Permalink
add Display for Vec3, add SimulationSet, and add EntityChunkPos compo…
Browse files Browse the repository at this point in the history
…nent
  • Loading branch information
mat-1 committed Feb 25, 2024
1 parent c389573 commit 13426b0
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 72 deletions.
6 changes: 6 additions & 0 deletions azalea-core/src/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,12 @@ impl fmt::Display for BlockPos {
write!(f, "{} {} {}", self.x, self.y, self.z)
}
}
impl fmt::Display for Vec3 {
/// Display a position as `x y z`.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.x, self.y, self.z)
}
}

const PACKED_X_LENGTH: u64 = 1 + 25; // minecraft does something a bit more complicated to get this 25
const PACKED_Z_LENGTH: u64 = PACKED_X_LENGTH;
Expand Down
4 changes: 4 additions & 0 deletions azalea-core/src/tick.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use bevy_ecs::schedule::ScheduleLabel;

/// A Bevy schedule that runs every Minecraft game tick, i.e. every 50ms.
///
/// Many client systems run on this schedule, the most important one being
/// physics.
#[derive(ScheduleLabel, Hash, Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct GameTick;
5 changes: 5 additions & 0 deletions azalea-entity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use bevy_ecs::{bundle::Bundle, component::Component};
pub use data::*;
use derive_more::{Deref, DerefMut};
pub use dimensions::EntityDimensions;
use plugin::indexing::EntityChunkPos;
use std::{
fmt::Debug,
hash::{Hash, Hasher},
Expand Down Expand Up @@ -347,6 +348,9 @@ pub struct EntityBundle {
pub world_name: InstanceName,
pub position: Position,
pub last_sent_position: LastSentPosition,

pub chunk_pos: EntityChunkPos,

pub physics: Physics,
pub direction: LookDirection,
pub eye_height: EyeHeight,
Expand Down Expand Up @@ -375,6 +379,7 @@ impl EntityBundle {
uuid: EntityUuid(uuid),
world_name: InstanceName(world_name),
position: Position(pos),
chunk_pos: EntityChunkPos(ChunkPos::from(&pos)),
last_sent_position: LastSentPosition(pos),
physics: Physics::new(dimensions, &pos),
eye_height: EyeHeight(eye_height),
Expand Down
35 changes: 20 additions & 15 deletions azalea-entity/src/plugin/indexing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ use bevy_ecs::{
query::Changed,
system::{Commands, Query, Res, ResMut, Resource},
};
use derive_more::{Deref, DerefMut};
use nohash_hasher::IntMap;
use std::{collections::HashMap, fmt::Debug};
use tracing::{debug, warn};
use uuid::Uuid;

use crate::{EntityUuid, LastSentPosition, Position};
use crate::{EntityUuid, Position};

use super::LoadedBy;

Expand Down Expand Up @@ -83,33 +84,37 @@ impl Debug for EntityUuidIndex {
}
}

// TODO: this should keep track of chunk position changes in a better way
// instead of relying on LastSentPosition
/// The chunk position that an entity is currently in.
#[derive(Component, Debug, Deref, DerefMut)]
pub struct EntityChunkPos(pub ChunkPos);

/// Update the chunk position indexes in [`Instance::entities_by_chunk`].
///
/// [`Instance::entities_by_chunk`]: azalea_world::Instance::entities_by_chunk
pub fn update_entity_chunk_positions(
mut query: Query<(Entity, &Position, &LastSentPosition, &InstanceName), Changed<Position>>,
mut query: Query<(Entity, &Position, &InstanceName, &mut EntityChunkPos), Changed<Position>>,
instance_container: Res<InstanceContainer>,
) {
for (entity, pos, last_pos, world_name) in query.iter_mut() {
for (entity, pos, world_name, mut entity_chunk_pos) in query.iter_mut() {
let instance_lock = instance_container.get(world_name).unwrap();
let mut instance = instance_lock.write();

let old_chunk = ChunkPos::from(*last_pos);
let old_chunk = **entity_chunk_pos;
let new_chunk = ChunkPos::from(*pos);

if old_chunk != new_chunk {
// move the entity from the old chunk to the new one
if let Some(entities) = instance.entities_by_chunk.get_mut(&old_chunk) {
entities.remove(&entity);
**entity_chunk_pos = new_chunk;

if old_chunk != new_chunk {
// move the entity from the old chunk to the new one
if let Some(entities) = instance.entities_by_chunk.get_mut(&old_chunk) {
entities.remove(&entity);
}
instance
.entities_by_chunk
.entry(new_chunk)
.or_default()
.insert(entity);
}
instance
.entities_by_chunk
.entry(new_chunk)
.or_default()
.insert(entity);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions azalea/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub use azalea_core::{
resource_location::ResourceLocation,
};
pub use azalea_entity as entity;
pub use azalea_physics as physics;
pub use azalea_protocol as protocol;
pub use azalea_registry as registry;
pub use azalea_world as world;
Expand Down
163 changes: 106 additions & 57 deletions azalea/src/pathfinder/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use azalea_client::{
};
use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick};
use azalea_entity::{
attributes::AttributeInstance, Attributes, EntityDimensions, Physics, Position,
attributes::AttributeInstance, Attributes, EntityDimensions, LookDirection, Physics, Position,
};
use azalea_world::{ChunkStorage, Instance, InstanceContainer, MinecraftEntityId, PartialInstance};
use bevy_app::App;
Expand All @@ -20,6 +20,7 @@ pub struct SimulatedPlayerBundle {
pub position: Position,
pub physics: Physics,
pub physics_state: PhysicsState,
pub look_direction: LookDirection,
pub attributes: Attributes,
pub inventory: InventoryComponent,
}
Expand All @@ -35,6 +36,7 @@ impl SimulatedPlayerBundle {
position: Position::new(position),
physics: Physics::new(dimensions, &position),
physics_state: PhysicsState::default(),
look_direction: LookDirection::new(0.0, 0.0),
attributes: Attributes {
speed: AttributeInstance::new(0.1),
attack_speed: AttributeInstance::new(4.0),
Expand All @@ -44,6 +46,77 @@ impl SimulatedPlayerBundle {
}
}

fn simulation_instance_name() -> ResourceLocation {
ResourceLocation::new("azalea:simulation")
}

fn create_simulation_instance(chunks: ChunkStorage) -> (App, Arc<RwLock<Instance>>) {
let instance_name = simulation_instance_name();

let instance = Arc::new(RwLock::new(Instance {
chunks,
..Default::default()
}));

let mut app = App::new();
// we don't use all the default azalea plugins because we don't need all of them
app.add_plugins((
azalea_physics::PhysicsPlugin,
azalea_entity::EntityPlugin,
azalea_client::movement::PlayerMovePlugin,
super::PathfinderPlugin,
crate::BotPlugin,
azalea_client::task_pool::TaskPoolPlugin::default(),
// for mining
azalea_client::inventory::InventoryPlugin,
azalea_client::mining::MinePlugin,
azalea_client::interact::InteractPlugin,
))
.insert_resource(InstanceContainer {
instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))]
.iter()
.cloned()
.collect(),
})
.add_event::<SendPacketEvent>();

app.edit_schedule(bevy_app::Main, |schedule| {
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
});

(app, instance)
}

fn create_simulation_player(
ecs: &mut World,
instance: Arc<RwLock<Instance>>,
player: SimulatedPlayerBundle,
) -> Entity {
let instance_name = simulation_instance_name();

let mut entity = ecs.spawn((
MinecraftEntityId(0),
azalea_entity::LocalEntity,
azalea_entity::metadata::PlayerMetadataBundle::default(),
azalea_entity::EntityBundle::new(
Uuid::nil(),
*player.position,
azalea_registry::EntityKind::Player,
instance_name,
),
azalea_client::InstanceHolder {
// partial_instance is never actually used by the pathfinder so
partial_instance: Arc::new(RwLock::new(PartialInstance::default())),
instance: instance.clone(),
},
InventoryComponent::default(),
));
entity.insert(player);

let entity_id = entity.id();
entity_id

Check warning on line 117 in azalea/src/pathfinder/simulation.rs

View workflow job for this annotation

GitHub Actions / clippy

returning the result of a `let` binding from a block

warning: returning the result of a `let` binding from a block --> azalea/src/pathfinder/simulation.rs:117:5 | 116 | let entity_id = entity.id(); | ---------------------------- unnecessary `let` binding 117 | entity_id | ^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return = note: `#[warn(clippy::let_and_return)]` on by default help: return the expression directly | 116 ~ 117 ~ entity.id() |
}

/// Simulate the Minecraft world to see if certain movements would be possible.
pub struct Simulation {
pub app: App,
Expand All @@ -53,71 +126,47 @@ pub struct Simulation {

impl Simulation {
pub fn new(chunks: ChunkStorage, player: SimulatedPlayerBundle) -> Self {
let instance_name = ResourceLocation::new("azalea:simulation");

let instance = Arc::new(RwLock::new(Instance {
chunks,
..Default::default()
}));

let mut app = App::new();
// we don't use all the default azalea plugins because we don't need all of them
app.add_plugins((
azalea_physics::PhysicsPlugin,
azalea_entity::EntityPlugin,
azalea_client::movement::PlayerMovePlugin,
super::PathfinderPlugin,
crate::BotPlugin,
azalea_client::task_pool::TaskPoolPlugin::default(),
// for mining
azalea_client::inventory::InventoryPlugin,
azalea_client::mining::MinePlugin,
azalea_client::interact::InteractPlugin,
))
.insert_resource(InstanceContainer {
instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))]
.iter()
.cloned()
.collect(),
})
.add_event::<SendPacketEvent>();

app.edit_schedule(bevy_app::Main, |schedule| {
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
});

let mut entity = app.world.spawn((
MinecraftEntityId(0),
azalea_entity::LocalEntity,
azalea_entity::metadata::PlayerMetadataBundle::default(),
azalea_entity::EntityBundle::new(
Uuid::nil(),
*player.position,
azalea_registry::EntityKind::Player,
instance_name,
),
azalea_client::InstanceHolder {
// partial_instance is never actually used by the pathfinder so
partial_instance: Arc::new(RwLock::new(PartialInstance::default())),
instance: instance.clone(),
},
InventoryComponent::default(),
));
entity.insert(player);

let entity_id = entity.id();

let (mut app, instance) = create_simulation_instance(chunks);
let entity = create_simulation_player(&mut app.world, instance.clone(), player);
Self {
app,
entity: entity_id,
entity,
_instance: instance,
}
}

pub fn tick(&mut self) {
self.app.world.run_schedule(GameTick);
self.app.update();
self.app.world.run_schedule(GameTick);
}
pub fn position(&self) -> Vec3 {
**self.app.world.get::<Position>(self.entity).unwrap()
}
}

/// A set of simulations, useful for efficiently doing multiple simulations.
pub struct SimulationSet {
pub app: App,
instance: Arc<RwLock<Instance>>,
}
impl SimulationSet {
pub fn new(chunks: ChunkStorage) -> Self {
let (app, instance) = create_simulation_instance(chunks);
Self { app, instance }
}
pub fn tick(&mut self) {
self.app.update();
self.app.world.run_schedule(GameTick);
}

pub fn spawn(&mut self, player: SimulatedPlayerBundle) -> Entity {
create_simulation_player(&mut self.app.world, self.instance.clone(), player)
}
pub fn despawn(&mut self, entity: Entity) {
self.app.world.despawn(entity);
}

pub fn position(&self, entity: Entity) -> Vec3 {
**self.app.world.get::<Position>(entity).unwrap()
}
}

0 comments on commit 13426b0

Please sign in to comment.