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::(); + + // 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, + /// 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::(|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>(); + + // 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>(); + + // 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) -> 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) -> i32 { + let num = context.argument("entities").unwrap(); + let num = *num.downcast_ref::().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>) { + 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>, +} + +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>; + fn deref(&self) -> &Self::Target { + &self.world + } +}