mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-16 13:32:33 +00:00
Add a brigadier example for use inside a bevy App (#146)
This commit is contained in:
parent
afcf497989
commit
8138d388e7
3 changed files with 199 additions and 1 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -274,6 +274,8 @@ version = "0.10.0"
|
|||
dependencies = [
|
||||
"azalea-buf",
|
||||
"azalea-chat",
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
192
azalea-brigadier/tests/bevy_app_usage.rs
Normal file
192
azalea-brigadier/tests/bevy_app_usage.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue