From 690bec273c981e7c3fc159ad5a8de94c8c795579 Mon Sep 17 00:00:00 2001 From: Andreas Pfeil Date: Fri, 19 Jul 2024 19:11:53 +0200 Subject: [PATCH] new pattern: event bridge --- Cargo.lock | 7 +++ Cargo.toml | 1 + patterns/event_bridge/Cargo.toml | 9 ++++ patterns/event_bridge/README.md | 25 ++++++++++ patterns/event_bridge/src/main.rs | 76 +++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 patterns/event_bridge/Cargo.toml create mode 100644 patterns/event_bridge/README.md create mode 100644 patterns/event_bridge/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index d0b314c..1e661e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1672,6 +1672,13 @@ dependencies = [ "num-traits", ] +[[package]] +name = "event-bridge" +version = "0.1.0" +dependencies = [ + "bevy", +] + [[package]] name = "event-listener" version = "2.5.3" diff --git a/Cargo.toml b/Cargo.toml index edea607..9589a81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ members = [ "patterns/component_installer", "patterns/deferred_spawn", "patterns/dependency_hook", + "patterns/event_bridge", ] diff --git a/patterns/event_bridge/Cargo.toml b/patterns/event_bridge/Cargo.toml new file mode 100644 index 0000000..0aa9e60 --- /dev/null +++ b/patterns/event_bridge/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "event-bridge" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bevy = "0.14" diff --git a/patterns/event_bridge/README.md b/patterns/event_bridge/README.md new file mode 100644 index 0000000..08a0177 --- /dev/null +++ b/patterns/event_bridge/README.md @@ -0,0 +1,25 @@ +# Pattern Name: Event bridge + +## Description + +Send events between your plugins/systems without a shared event type (or other imports). This can make your plugins fully independent and exchangeable. The small amount of boilerplate per system will be dependent on the plugin, though. Making your plugins send/receive generic events allows a project to use an actual custom event type to be used in the ECS. This is done by translating before sending and after receiving an event. + +## Implementation + +[Implementation on both sides (event emitter and receiver plugins)](./src/main.rs) + +## Use cases + +This pattern is applied to plugins/systems making use of events. Such plugins can then be connected using a small amount of code within your project. + +You can use it to connect plugins in a very loose way without any of them knowing anything about the other. It does not introduce additional game cycles for the event transfer, but add some conversion overhead (usually very small). The connecting event can be fully customized. + +## Alternatives + +- Translation systems (a system function which receives all events of a given type and for each emits another type of event) +- Accept dependencies (e.g. shared types) between (internal-only) plugins + +## Credit + +This makes use of [bevy's support for generics](https://bevy-cheatbook.github.io/patterns/generic-systems.html) and the [From-trait from the rust standard library](https://doc.rust-lang.org/std/convert/trait.From.html). + diff --git a/patterns/event_bridge/src/main.rs b/patterns/event_bridge/src/main.rs new file mode 100644 index 0000000..dfd80b3 --- /dev/null +++ b/patterns/event_bridge/src/main.rs @@ -0,0 +1,76 @@ +use bevy::prelude::*; +use boilerplate::*; + +mod boilerplate { + use bevy::ecs::event::Event; + + use super::event_actor::ActorEvent; + use super::event_emitter::EmitterEvent; + + #[derive(Event, Clone)] + /// Glue for the different event types of the plugins + pub struct GameEvent(pub &'static str); + + impl From for GameEvent { + fn from(other: EmitterEvent) -> Self { + Self(other.0) + } + } + + impl From for ActorEvent { + fn from(other: GameEvent) -> Self { + Self(other.0.len()) + } + } +} + +fn main() { + App::new() + .add_event::() + .add_plugins(DefaultPlugins) + + // Note you can comment out each plugin without breaking code + .add_plugins(event_emitter::plugin::) + .add_plugins(event_actor::plugin::) + + .run(); +} + +mod event_actor { + // Note there is no import from the emitter + use bevy::ecs::event::Event; + use bevy::prelude::*; + + #[derive(Event)] + pub struct ActorEvent(pub usize); + + pub fn plugin>(app: &mut App) { + app.add_systems(Update, act::); + } + + fn act>(mut events_in: EventReader) { + for event in events_in.read() { + // with this transformation we can act on all of the data of the event. + let event: ActorEvent = (*event).clone().into(); + info!("Event: {}", event.0); + } + } +} + +mod event_emitter { + // Note there is no import from the actor + use bevy::ecs::event::Event; + use bevy::prelude::*; + + #[derive(Event)] + pub struct EmitterEvent(pub &'static str); + + pub fn plugin>(app: &mut App) { + app.add_event::() + .add_systems(PreUpdate, send::); + } + + fn send>(mut events_out: EventWriter) { + events_out.send(E::from(EmitterEvent("my event"))); + } +}