Add a brigadier example for use inside a bevy App (#146)

This commit is contained in:
EightFactorial 2024-05-09 19:55:55 -07:00 committed by GitHub
parent afcf497989
commit 8138d388e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 199 additions and 1 deletions

2
Cargo.lock generated
View file

@ -274,6 +274,8 @@ version = "0.10.0"
dependencies = [
"azalea-buf",
"azalea-chat",
"bevy_app",
"bevy_ecs",
"parking_lot",
]

View file

@ -8,10 +8,14 @@ version = "0.10.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
bevy_app = "0.13.0"
bevy_ecs = "0.13.0"
[dependencies]
azalea-buf = { path = "../azalea-buf", version = "0.10.0", optional = true }
azalea-chat = { path = "../azalea-chat", version = "0.10.0", optional = true }
parking_lot = "0.12.1"
[features]
azalea-buf = ["dep:azalea-buf", "dep:azalea-chat"]
azalea-buf = ["dep:azalea-buf", "dep:azalea-chat", "azalea-chat/azalea-buf"]

View file

@ -0,0 +1,192 @@
use std::sync::Arc;
use azalea_brigadier::{
arguments::integer_argument_type::integer,
builder::{literal_argument_builder::literal, required_argument_builder::argument},
command_dispatcher::CommandDispatcher,
context::CommandContext,
};
use bevy_app::App;
use bevy_ecs::{
component::Component,
query::With,
system::{Query, Resource, RunSystemOnce},
world::{FromWorld, World},
};
use parking_lot::Mutex;
#[test]
fn bevy_app() {
let mut app = App::new();
// Initialize the dispatcher using FromWorld
app.init_resource::<DispatchStorage>();
// Process commands from bevy
app.world
.run_system_once(DispatchStorage::bevy_process_commands);
// Verify spawned entities exist after processing commands
app.world
.run_system_once(DispatchStorage::verify_spawned_entities);
}
#[derive(Resource)]
struct DispatchStorage {
/// The [`CommandDispatcher`].
///
/// Processes incoming commands.
dispatch: CommandDispatcher<WorldAccessor>,
/// The world accessor.
///
/// Allows the dispatcher to query the [`World`].
world: WorldAccessor,
}
/// Implement [`FromWorld`] to initialize the dispatcher.
///
/// Allows the dispatcher to query the [`World`]
/// for generating commands on startup.
impl FromWorld for DispatchStorage {
fn from_world(_: &mut World) -> Self {
let mut dispatch = CommandDispatcher::new();
// Register dispatcher commands
{
// Register the "spawn_entity" command
dispatch
.register(literal("spawn_entity").executes(DispatchStorage::command_spawn_entity));
// Register the "spawn_entity_num" command
dispatch.register(literal("spawn_entity_num").then(
argument("entities", integer()).executes(DispatchStorage::command_spawn_entity_num),
));
}
Self {
dispatch,
world: WorldAccessor::empty(),
}
}
}
impl DispatchStorage {
/// A bevy system called to process commands.
fn bevy_process_commands(world: &mut World) {
world.resource_scope::<Self, _>(|bevy_world, mut storage| {
// NOTE: Initial swap to own bevy's `World`
//
// This is important, otherwise the dispatcher
// will only be able to access it's own empty `World`.
storage.world.swap(bevy_world);
let source = storage.world.clone();
// Test "spawn_entity"
{
println!("Testing 'spawn_entity' command");
let result = storage.dispatch.execute("spawn_entity", source.clone());
// Ensure the command was successful
assert_eq!(result, Ok(0));
// Query the World for the spawned entity
let mut world = source.lock();
let mut query = world.query_filtered::<(), With<SpawnedEntity>>();
// Ensure only one entity was spawned
let count = query.iter(&world).count();
println!("Spawned entities: {count}");
assert_eq!(count, 1);
}
// Test "spawn_entity_num"
{
println!("Testing 'spawn_entity_num' command");
let result = storage
.dispatch
.execute("spawn_entity_num 3", source.clone());
// Ensure the command was successful
assert_eq!(result, Ok(0));
// Query the World for spawned entities
let mut world = source.lock();
let mut query = world.query_filtered::<(), With<SpawnedEntity>>();
// Ensure three additional entities were spawned
let count = query.iter(&world).count();
println!("Spawned entities: {count}");
assert_eq!(count, 4);
}
// NOTE: Second swap to give bevy's `World` back
//
// It's even more important to give the `World` back
// after commands are executed, otherwise your app
// will be stuck with an empty `World`.
storage.world.swap(bevy_world);
});
}
/// A command called from the dispatcher.
///
/// Spawns an entity with the [`SpawnedEntity`] component.
fn command_spawn_entity(context: &CommandContext<WorldAccessor>) -> i32 {
context.source.lock().spawn(SpawnedEntity);
0
}
/// A command called from the dispatcher.
///
/// Spawns a number of entities with the [`SpawnedEntity`] component.
fn command_spawn_entity_num(context: &CommandContext<WorldAccessor>) -> i32 {
let num = context.argument("entities").unwrap();
let num = *num.downcast_ref::<i32>().unwrap();
for _ in 0..num {
context.source.lock().spawn(SpawnedEntity);
}
0
}
/// A bevy system called to verify four total entities was spawned.
fn verify_spawned_entities(query: Query<(), With<SpawnedEntity>>) {
assert_eq!(query.iter().count(), 4);
}
}
/// A wrapper around a [`World`] that allows for
/// access from inside a [`CommandDispatcher`].
#[derive(Clone)]
struct WorldAccessor {
world: Arc<Mutex<World>>,
}
impl WorldAccessor {
/// Create a new empty [`WorldAccessor`].
fn empty() -> Self {
Self {
world: Arc::new(Mutex::new(World::new())),
}
}
/// Swap the internal [`World`] with the given one.
fn swap(&mut self, world: &mut World) {
std::mem::swap(&mut *self.lock(), world);
}
}
/// A marker [`Component`] used to test spawning entities from the dispatcher.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Component)]
struct SpawnedEntity;
/// Implemented for convenience.
impl std::ops::Deref for WorldAccessor {
type Target = Arc<Mutex<World>>;
fn deref(&self) -> &Self::Target {
&self.world
}
}