Skip to content

Commit

Permalink
Implement remote triggers
Browse files Browse the repository at this point in the history
I decided to implement remote triggers on top of events to reuse existing logic.

Unlike events, we can't simply observe and re-trigger, as we need
access to all targets. To address this, we provide a special `RemoteTrigger` and
extension traits for sending these events internally. Both global and targeted
triggers are supported. But we don't support component-based targets yet, only entity-based.

To allow users to override serialization without manually writing it for
for event targets, I replaced the bare serialization functions with an abstraction
similar to component rules. Additionally, I updated how `ClientEvent` is created
internally to cleanly reuse this logic for triggers.

For API examples see documentation changes in `lib.rs`.
  • Loading branch information
Shatur committed Jan 25, 2025
1 parent a1cd29a commit fc00071
Show file tree
Hide file tree
Showing 14 changed files with 1,386 additions and 282 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ all-features = true

[dependencies]
bevy = { version = "0.15", default-features = false, features = ["serialize"] }
typeid = "1.0"
bytes = "1.5"
bincode = "1.3"
serde = "1.0"
Expand Down
73 changes: 51 additions & 22 deletions src/client/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ impl Plugin for ClientEventPlugin {
.build_state(app.world_mut())
.build_system(Self::receive);

let trigger = (
FilteredResourcesMutParamBuilder::new(|builder| {
for trigger in event_registry.iter_server_triggers() {
builder.add_write_by_id(trigger.event().events_id());
}
}),
ParamBuilder,
ParamBuilder,
)
.build_state(app.world_mut())
.build_system(Self::trigger);

let resend_locally = (
FilteredResourcesMutParamBuilder::new(|builder| {
for event in event_registry.iter_client_events() {
Expand Down Expand Up @@ -106,10 +118,14 @@ impl Plugin for ClientEventPlugin {
PreUpdate,
(
reset.in_set(ClientSet::ResetEvents),
receive
.after(ClientPlugin::receive_replication)
.in_set(ClientSet::Receive)
.run_if(client_connected),
(
receive
.after(ClientPlugin::receive_replication)
.run_if(client_connected),
trigger.run_if(not(server_running)),
)
.chain()
.in_set(ClientSet::Receive),
),
)
.add_systems(
Expand Down Expand Up @@ -138,17 +154,17 @@ impl ClientEventPlugin {
registry: &registry.read(),
};

for event_data in event_registry.iter_client_events() {
for event in event_registry.iter_client_events() {
let events = events
.get_by_id(event_data.events_id())
.get_by_id(event.events_id())
.expect("events resource should be accessible");
let reader = readers
.get_mut_by_id(event_data.reader_id())
.get_mut_by_id(event.reader_id())
.expect("event reader resource should be accessible");

// SAFETY: passed pointers were obtained using this event data.
unsafe {
event_data.send(&mut ctx, &events, reader.into_inner(), &mut client);
event.send(&mut ctx, &events, reader.into_inner(), &mut client);
}
}
}
Expand All @@ -168,17 +184,17 @@ impl ClientEventPlugin {
invalid_entities: Vec::new(),
};

for event_data in event_registry.iter_server_events() {
for event in event_registry.iter_server_events() {
let events = events
.get_mut_by_id(event_data.events_id())
.get_mut_by_id(event.events_id())
.expect("events resource should be accessible");
let queue = queues
.get_mut_by_id(event_data.queue_id())
.get_mut_by_id(event.queue_id())
.expect("queue resource should be accessible");

// SAFETY: passed pointers were obtained using this event data.
unsafe {
event_data.receive(
event.receive(
&mut ctx,
events.into_inner(),
queue.into_inner(),
Expand All @@ -189,21 +205,34 @@ impl ClientEventPlugin {
}
}

fn trigger(
mut events: FilteredResourcesMut,
mut commands: Commands,
event_registry: Res<EventRegistry>,
) {
for trigger in event_registry.iter_server_triggers() {
let events = events
.get_mut_by_id(trigger.event().events_id())
.expect("events resource should be accessible");
trigger.trigger(&mut commands, events.into_inner());
}
}

fn resend_locally(
mut client_events: FilteredResourcesMut,
mut events: FilteredResourcesMut,
event_registry: Res<EventRegistry>,
) {
for event_data in event_registry.iter_client_events() {
for event in event_registry.iter_client_events() {
let client_events = client_events
.get_mut_by_id(event_data.client_events_id())
.get_mut_by_id(event.client_events_id())
.expect("client events resource should be accessible");
let events = events
.get_mut_by_id(event_data.events_id())
.get_mut_by_id(event.events_id())
.expect("events resource should be accessible");

// SAFETY: passed pointers were obtained using this event data.
unsafe { event_data.resend_locally(client_events.into_inner(), events.into_inner()) };
unsafe { event.resend_locally(client_events.into_inner(), events.into_inner()) };
}
}

Expand All @@ -212,22 +241,22 @@ impl ClientEventPlugin {
mut queues: FilteredResourcesMut,
event_registry: Res<EventRegistry>,
) {
for event_data in event_registry.iter_client_events() {
for event in event_registry.iter_client_events() {
let events = events
.get_mut_by_id(event_data.events_id())
.get_mut_by_id(event.events_id())
.expect("events resource should be accessible");

// SAFETY: passed pointer was obtained using this event data.
unsafe { event_data.reset(events.into_inner()) };
unsafe { event.reset(events.into_inner()) };
}

for event_data in event_registry.iter_server_events() {
for event in event_registry.iter_server_events() {
let queue = queues
.get_mut_by_id(event_data.queue_id())
.get_mut_by_id(event.queue_id())
.expect("event queue resource should be accessible");

// SAFETY: passed pointer was obtained using this event data.
unsafe { event_data.reset(queue.into_inner()) };
unsafe { event.reset(queue.into_inner()) };
}
}
}
19 changes: 19 additions & 0 deletions src/core/event.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
pub mod client_event;
pub mod client_trigger;
pub mod ctx;
pub mod event_fns;
pub(crate) mod event_registry;
pub mod server_event;
pub mod server_trigger;

use bevy::prelude::*;

/// An event that used under the hood for client and server triggers.
///
/// We can't just observe for triggers like we do for events since we need access to all its targets
/// and we need to buffer them. This is why we just emit this event instead and after receive drain it
/// to trigger regular events.
///
/// Traditional trigger interface is provided by [`ClientTriggerExt`](client_trigger::ClientTriggerExt)
/// and [`ServerTriggerExt`](server_trigger::ServerTriggerExt).
#[derive(Event)]
struct RemoteTrigger<E> {
event: E,
targets: Vec<Entity>,
}
Loading

0 comments on commit fc00071

Please sign in to comment.