aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEightFactorial <29801334+EightFactorial@users.noreply.github.com>2024-05-09 19:55:55 -0700
committerGitHub <noreply@github.com>2024-05-09 21:55:55 -0500
commit8138d388e7126df28cb9e12d102d05f9060aaa31 (patch)
tree9dd54f3cf476e51d1e8b3f5207523927804bdde9
parentafcf497989ad696be2a137cb06990ccfb93a389b (diff)
downloadazalea-drasl-8138d388e7126df28cb9e12d102d05f9060aaa31.tar.xz
Add a brigadier example for use inside a bevy App (#146)
-rw-r--r--Cargo.lock2
-rw-r--r--azalea-brigadier/Cargo.toml6
-rw-r--r--azalea-brigadier/tests/bevy_app_usage.rs192
3 files changed, 199 insertions, 1 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 213c405d..28a49690 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -274,6 +274,8 @@ version = "0.10.0"
dependencies = [
"azalea-buf",
"azalea-chat",
+ "bevy_app",
+ "bevy_ecs",
"parking_lot",
]
diff --git a/azalea-brigadier/Cargo.toml b/azalea-brigadier/Cargo.toml
index 9f3ea7e7..a2636940 100644
--- a/azalea-brigadier/Cargo.toml
+++ b/azalea-brigadier/Cargo.toml
@@ -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"]
diff --git a/azalea-brigadier/tests/bevy_app_usage.rs b/azalea-brigadier/tests/bevy_app_usage.rs
new file mode 100644
index 00000000..1ed1fc99
--- /dev/null
+++ b/azalea-brigadier/tests/bevy_app_usage.rs
@@ -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
+ }
+}