aevyrie / bevy_eventlistener Goto Github PK
View Code? Open in Web Editor NEWEvent listening, bubbling, and callbacks
License: Apache License 2.0
Event listening, bubbling, and callbacks
License: Apache License 2.0
pub fn send_event<F: Event + From<ListenerInput<E>>>() -> Self {
This is the current signature of On::send_event
.
However, I want to be able to quickly do things like "send an AppExit
event when this button is clicked".
I propose three related methods:
send_event
: takes an event of type F
and captures it in the closure.convert_and_send_event
: current behavior: converts E to F and sends it.send_default_event
: sends an event with the Default
value.This crate is awesome! It's solving exactly the problem that was driving me nuts when trying to represent intents & actions in a turn-based roguelike prototype I'm using to play around with bevy
.
An architecture for representing behaviors emerged pretty directly from the way bevy_eventlistener
is set up. I'm sharing it here because 1. maybe it's useful for someone 2. maybe it's generic enough to be supported with a bit of extra library code, either here or in a separate crate.
EntityEvent
sPrototype: abesto/treeffect@8bc7403 (this is a commit that moves from intents being components, and behaviors being regular systems)
Nothing interesting here, just so that the rest makes sense.
#[derive(Clone, Event, EntityEvent)]
pub struct MovementIntent {
#[target]
pub actor: Entity,
pub vector: IVec2,
}
EntityEvents
are registered in a plugin with some macro magic for further DRY:
// short for "event listener plugins"
macro_rules! elp {
($app:ident, $($events:ident),+ $(,)?) => {
$($app.add_plugins(EventListenerPlugin::<$events>::default());)+
};
}
pub struct EventsPlugin;
impl Plugin for EventsPlugin {
fn build(&self, app: &mut App) {
app.add_event::<took_turn::TookTurn>();
elp!(app, MovementIntent, AttackIntent, WaitIntent);
}
}
On reflection, elp
should probably be replaced with an App
extention trait with .add_intents
or something more generic.
Side-track: that might actually be a meaningful separate feature request. app.add_entity_event::<WaitIntent>()
is sensible. It still wouldn't be DRY enough, so I'd probably still have a similar wrapper macro.
A trait so we can hang logic somewhere, implemented for tuples (spoiler: think about bundles):
pub trait Behavior {
fn behavior() -> Self;
}
#[impl_for_tuples(1, 16)]
impl Behavior for BehaviorIdentifier {
fn behavior() -> Self {
for_tuples!( ( #( (BehaviorIdentifier::behavior()) ),* ) )
}
}
fn movement(
mut query: Query<&mut Position, With<Active>>,
map: Res<Map>,
intent: Listener<MovementIntent>,
mut ev_took_turn: EventWriter<TookTurn>,
) {
let entity = intent.listener();
let Ok(mut position) = query.get_mut(entity) else {
return;
};
let new_position = map.iclamp(&(position.xy.as_ivec2() + intent.vector));
if map.is_walkable(&new_position) {
position.xy = new_position;
}
ev_took_turn.send(entity.into());
}
behavior!(movement);
I'm ignoring event propagation here, because I'm not using entity hierarchies. I'm probably picking the wrong one out of (Listener, Target) somewhere.
behavior!
is a simple macro that, if I was an adult, I'd implement as a derive macro. It's coupled very tightly to my naming conventions, unsure how feasible this would be as library code. It generates code like this:
pub type MovementBehavior = On<MovementIntent>;
impl Behavior for MovementBehavior {
fn behavior() -> Self {
On::<MovementIntent>::run(movement)
}
}
pub type ActorBehavior = (MovementBehavior, AttackBehavior, WaitBehavior);
I guess you could call this a behavior bundle.
You can pretty much figure this out from the rest, but for the record:
type Behaviors = ActorBehavior; // Could add more specific behaviors here
#[derive(Bundle)]
pub struct PlayerBundle {
...
pub behaviors: Behaviors,
}
impl Default for PlayerBundle {
fn default() -> Self {
PlayerBundle {
...
behaviors: Behaviors::behavior(),
}
}
}
Some events intrinsically needs to targets multiple entities.
For example collision events will need to be able to report to multiple entities. You can currently simulate this by cloning the event (which can contain lots of data like a vector of manifolds) and sending them to each entity.
I think the ability to natively target multiple entities would help a lot for this kind of use-case.
This feature is necessary because there's currently no way to have more than one of a callback of a single type on an entity.
This is usually just a papercut, but it is required in bevy_mod_picking, in order to merge on_click / on_drag etc into a single callback.
In turn, we want to do that in order to handle the ordering of input events correctly„ using bevyengine/bevy#12100
This would be useful to construct bundle types with On
components that have no effect.
I'm running into this while trying to construct ButtonBundle
equivalents with On<Pointer<Click>>
and On<Pointer<Hover>>
fields.
We currently can traverse up the hierarchy via bubbles, which is great!
I think we can extend this by also allowing for sinking - letting an Event propagate down the tree from the calling node.
While this may prove worse in terms of performance (linearly going up the tree vs recursively going down children) for most situations, there are times when having a parent node and propagating events generated on the parent to all the children is a good design choice.
This feature would work like another trait on EntityEvents, : #[can_sink]
This would not block #[can_bubble]
, and if you define both then your event gets sent both up and down the tree.
Here's a minimal example that illustrates the issue I discussed last week on the Discord. When I sent a custom event "Clicked" (not "Click"), the first event is sometimes not received:
On
handler, but only the first time.On
handler as expected.The problem is intermittent - you may have to run the example multiple times in order to see it. For me, happens about 50% of the time, so it shouldn't take many runs to see it.
When it is not working, you'll see output like this:
--> Sending Clicked id='One' target=4v0
? Reading global clicked: id='One' target=4v0
And when it is working, you'll see output like this:
--> Sending Clicked id='One' target=4v0
<-- Received Clicked Button id='One' target=4v0
? Reading global clicked: id='One' target=4v0
Hi there! I'm using this as a standalone crate in a project and looking to update Bevy to latest. I'm happy to help migrate the code, just wanted to open this issue to verify that you don't already have a branch out for this. Let me know and I'll get in a pull request ASAP.
I ran into this issue updating the editor for my game where I use the bevy_reflect "documentation" feature to show component documentation for easy access. It used to work with bevy 0.10 and in bevy 0.11 it is now failing with the below error, repeated for every type that impl_reflect_value is used on.
error[E0599]: no method named `with_docs` found for struct `type_info::ValueInfo` in the current scope
--> /home/paul/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_reflect-0.11.3/src/impls/uuid.rs:6:1
|
6 | impl_reflect_value!(::bevy_utils::Uuid(
| _-
7 | | Serialize,
8 | | Deserialize,
9 | | Default,
... |
12 | | Hash
13 | | ));
| | ^
| | |
| |__method not found in `ValueInfo`
| in this macro invocation
|
::: /home/paul/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_reflect-0.11.3/src/type_info.rs:177:1
|
177 | pub struct ValueInfo {
| -------------------- method `with_docs` not found for this struct
|
::: /home/paul/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_reflect_derive-0.11.3/src/lib.rs:381:1
|
381 | pub fn impl_reflect_value(input: TokenStream) -> TokenStream {
| ------------------------------------------------------------ in this expansion of `impl_reflect_value!`
Create a new project with this Cargo.toml
[package]
name = "bevy_docs_test"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy_eventlistener = "0.5"
bevy_reflect = {version="0.11", features=["documentation"]}
Run cargo run
Changing bevy_reflect to version 0.11 and bevy_eventlistener to 0.4 makes it work again.
Removing the documentation feature from bevy_reflect also makes it work again.
The motivation is similar to #24: I'd like to have a reusable set of event handlers that I can instantiate.
I was disappointed to discover this won't work:
On::<ScrollWheel>::listener_component_mut::<Scrolling>(move |ev, scrolling| {
// (Do stuff)
ev.stop_propagation(); // ERROR: 'ev' is immutable
}),
The problem is that listener_component_mut
gives me an immutable listener, rather than a mutable one; most of the other convenience methods have the same limitation. So if I want to call stop_propagation()
I have to use run()
.
I generally want to call stop_propagation()
in most handlers, which means that the convenience methods hardly ever get used.
This is a common simple pattern that uses a surprising amount of boilerplate. We could simplify this to a single method call.
Hello, is there a way, to run systems from On::run in fixed update?
My usecase is that i want to simulate physics for separate world assigned to separate entities, meaning each entity has a physics world and I want to process the input with bevy_eventlistener and then simulate the physics from that input in fixed update.
Thanks in advance.
If I understand correctly, ListenerInput
should probably be re-exported with other callbacks
here:
Lines 9 to 13 in 4d8de4c
as it's commonly used like in this bevy_mod_picking
example:
struct DoSomethingComplex(Entity, f32);
impl From<ListenerInput<Pointer<Down>>> for DoSomethingComplex {
fn from(event: ListenerInput<Pointer<Down>>) -> Self {
DoSomethingComplex(event.target, event.hit.depth)
}
}
Bevy systems have very helpful control flow extension methods called run_if
.
Is there a way to use them with this library that is documented? I have tried a simple run_if
and am getting a compiler error as shown below.
error[E0277]: the trait bound `NodeConfigs<std::boxed::Box<dyn bevy::prelude::System<In = (), Out = ()>>>: bevy::prelude::IntoSystem<(), (), _>` is not satisfied
--> src/game/character.rs:99:17
|
98 | on_drag_start: On::<Pointer<DragStart>>::run(
| ----------------------------- required by a bound introduced by this call
99 | drag_start_character_system.distributive_run_if(tool_active(ToolType::Arrow)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `bevy::prelude::IntoSystem<(), (), _>` is not implemented for `NodeConfigs<std::boxed::Box<dyn bevy::prelude::System<In = (), Out = ()>>>`
|
note: required by a bound in `On::<E>::run`
--> /Users/stevenbeshensky/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_eventlistener_core-0.6.1/src/event_listener.rs:41:39
|
41 | pub fn run<Marker>(callback: impl IntoSystem<(), (), Marker>) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `On::<E>::run`
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.