From fc0007184bd949f19ea8a8bb327c35243552e666 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Sat, 25 Jan 2025 02:21:01 +0200 Subject: [PATCH] Implement remote triggers 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`. --- Cargo.toml | 1 + src/client/event.rs | 73 +++++++--- src/core/event.rs | 19 +++ src/core/event/client_event.rs | 174 ++++++++--------------- src/core/event/client_trigger.rs | 217 +++++++++++++++++++++++++++++ src/core/event/event_fns.rs | 198 ++++++++++++++++++++++++++ src/core/event/event_registry.rs | 52 +++++-- src/core/event/server_event.rs | 177 +++++++++--------------- src/core/event/server_trigger.rs | 229 +++++++++++++++++++++++++++++++ src/lib.rs | 58 +++++++- src/server/event.rs | 58 ++++++-- tests/client_event.rs | 10 +- tests/client_trigger.rs | 178 ++++++++++++++++++++++++ tests/server_trigger.rs | 224 ++++++++++++++++++++++++++++++ 14 files changed, 1386 insertions(+), 282 deletions(-) create mode 100644 src/core/event/client_trigger.rs create mode 100644 src/core/event/event_fns.rs create mode 100644 src/core/event/server_trigger.rs create mode 100644 tests/client_trigger.rs create mode 100644 tests/server_trigger.rs diff --git a/Cargo.toml b/Cargo.toml index adf1cfd1..99dcf6bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/client/event.rs b/src/client/event.rs index 88602b34..5ad04f0c 100644 --- a/src/client/event.rs +++ b/src/client/event.rs @@ -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() { @@ -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( @@ -138,17 +154,17 @@ impl ClientEventPlugin { registry: ®istry.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); } } } @@ -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(), @@ -189,21 +205,34 @@ impl ClientEventPlugin { } } + fn trigger( + mut events: FilteredResourcesMut, + mut commands: Commands, + event_registry: Res, + ) { + 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, ) { - 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()) }; } } @@ -212,22 +241,22 @@ impl ClientEventPlugin { mut queues: FilteredResourcesMut, event_registry: Res, ) { - 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()) }; } } } diff --git a/src/core/event.rs b/src/core/event.rs index 9ed49219..385ee94a 100644 --- a/src/core/event.rs +++ b/src/core/event.rs @@ -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 { + event: E, + targets: Vec, +} diff --git a/src/core/event/client_event.rs b/src/core/event/client_event.rs index 4537d957..748e697b 100644 --- a/src/core/event/client_event.rs +++ b/src/core/event/client_event.rs @@ -1,15 +1,7 @@ -use std::{ - any::{self, TypeId}, - io::Cursor, - mem, -}; +use std::{any, io::Cursor}; use bevy::{ - ecs::{ - component::{ComponentId, Components}, - entity::MapEntities, - event::EventCursor, - }, + ecs::{component::ComponentId, entity::MapEntities, event::EventCursor}, prelude::*, ptr::{Ptr, PtrMut}, }; @@ -18,6 +10,7 @@ use serde::{de::DeserializeOwned, Serialize}; use super::{ ctx::{ClientSendCtx, ServerReceiveCtx}, + event_fns::{EventDeserializeFn, EventFns, EventSerializeFn, UntypedEventFns}, event_registry::EventRegistry, }; use crate::core::{ @@ -31,9 +24,10 @@ use crate::core::{ pub trait ClientEventAppExt { /// Registers [`FromClient`] and `E` events. /// + /// The API matches [`ClientTriggerAppExt::add_client_trigger`](super::client_trigger::ClientTriggerAppExt::add_client_trigger): /// [`FromClient`] will be emitted on the server after sending `E` event on client. - /// In listen-server mode `E` will be drained right after sending and re-emitted as - /// [`FromClient`] with [`ClientId::SERVER`](crate::core::ClientId::SERVER). + /// When [`RepliconClient`](crate::core::replicon_client::RepliconClient) is inactive, the event + /// will be drained right after sending and re-emitted locally as [`FromClient`] with [`ClientId::SERVER`](crate::core::ClientId::SERVER). /// /// Can be called for events that were registered with [add_event](bevy::app::App::add_event). /// A duplicate registration for `E` won't be created. @@ -52,7 +46,6 @@ pub trait ClientEventAppExt { /// Same as [`Self::add_client_event`], but additionally maps client entities to server inside the event before sending. /// /// Always use it for events that contain entities. - /// See also [`Self::add_client_event`]. fn add_mapped_client_event( &mut self, channel: impl Into, @@ -67,6 +60,8 @@ pub trait ClientEventAppExt { /** Same as [`Self::add_client_event`], but uses the specified functions for serialization and deserialization. + See also [`ClientTriggerAppExt::add_client_trigger`](super::client_trigger::ClientTriggerAppExt::add_client_trigger). + # Examples Register an event with [`Box`]: @@ -118,8 +113,8 @@ pub trait ClientEventAppExt { fn add_client_event_with( &mut self, channel: impl Into, - serialize: SerializeFn, - deserialize: DeserializeFn, + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, ) -> &mut Self; } @@ -127,29 +122,15 @@ impl ClientEventAppExt for App { fn add_client_event_with( &mut self, channel: impl Into, - serialize: SerializeFn, - deserialize: DeserializeFn, + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, ) -> &mut Self { debug!("registering event `{}`", any::type_name::()); - self.add_event::() - .add_event::>() - .init_resource::>(); - - let channel_id = self - .world_mut() - .resource_mut::() - .create_client_channel(channel); - - self.world_mut() - .resource_scope(|world, mut event_registry: Mut| { - event_registry.register_client_event(ClientEvent::new( - world.components(), - channel_id, - serialize, - deserialize, - )); - }); + let event_fns = EventFns::new(serialize, deserialize); + let event = ClientEvent::new(self, channel, event_fns); + let mut event_registry = self.world_mut().resource_mut::(); + event_registry.register_client_event(event); self } @@ -159,16 +140,13 @@ impl ClientEventAppExt for App { /// /// Needed so events of different types can be processed together. pub(crate) struct ClientEvent { - event_id: TypeId, - event_name: &'static str, - /// ID of [`Events`] resource. events_id: ComponentId, /// ID of [`ClientEventReader`] resource. reader_id: ComponentId, - /// ID of [`Events>`] resource. + /// ID of [`Events>`] resource. client_events_id: ComponentId, /// Used channel. @@ -178,54 +156,38 @@ pub(crate) struct ClientEvent { receive: ReceiveFn, resend_locally: ResendLocallyFn, reset: ResetFn, - serialize: unsafe fn(), - deserialize: unsafe fn(), + event_fns: UntypedEventFns, } impl ClientEvent { - fn new( - components: &Components, - channel_id: u8, - serialize: SerializeFn, - deserialize: DeserializeFn, + pub(super) fn new( + app: &mut App, + channel: impl Into, + event_fns: EventFns, ) -> Self { - let events_id = components.resource_id::>().unwrap_or_else(|| { - panic!( - "event `{}` should be previously registered", - any::type_name::() - ) - }); - let client_events_id = components - .resource_id::>>() - .unwrap_or_else(|| { - panic!( - "event `{}` should be previously registered", - any::type_name::>() - ) - }); - let reader_id = components - .resource_id::>() - .unwrap_or_else(|| { - panic!( - "resource `{}` should be previously inserted", - any::type_name::>() - ) - }); + let channel_id = app + .world_mut() + .resource_mut::() + .create_client_channel(channel); + + app.add_event::() + .add_event::>() + .init_resource::>(); + + let events_id = app.world().resource_id::>().unwrap(); + let client_events_id = app.world().resource_id::>>().unwrap(); + let reader_id = app.world().resource_id::>().unwrap(); Self { - event_id: TypeId::of::(), - event_name: any::type_name::(), events_id, reader_id, client_events_id, channel_id, - send: Self::send_typed::, - receive: Self::receive_typed::, + send: Self::send_typed::, + receive: Self::receive_typed::, resend_locally: Self::resend_locally_typed::, reset: Self::reset_typed::, - // SAFETY: these functions won't be called until the type is restored. - serialize: unsafe { mem::transmute::, unsafe fn()>(serialize) }, - deserialize: unsafe { mem::transmute::, unsafe fn()>(deserialize) }, + event_fns: event_fns.into(), } } @@ -261,21 +223,19 @@ impl ClientEvent { /// /// # Safety /// - /// The caller must ensure that `events` is [`Events>`], `reader` is [`ClientEventReader`], - /// and this instance was created for `E`. - unsafe fn send_typed( + /// The caller must ensure that `events` is [`Events`], `reader` is [`ClientEventReader`], + /// and this instance was created for `E` and `I`. + unsafe fn send_typed( &self, ctx: &mut ClientSendCtx, events: &Ptr, reader: PtrMut, client: &mut RepliconClient, ) { - self.check_type::(); - let reader: &mut ClientEventReader = reader.deref_mut(); for event in reader.read(events.deref()) { let mut message = Vec::new(); - self.serialize(ctx, event, &mut message) + self.serialize::(ctx, event, &mut message) .expect("client event should be serializable"); debug!("sending event `{}`", any::type_name::()); @@ -303,25 +263,23 @@ impl ClientEvent { /// # Safety /// /// The caller must ensure that `client_events` is [`Events>`] - /// and this instance was created for `E`. - unsafe fn receive_typed( + /// and this instance was created for `E` and `I`. + unsafe fn receive_typed( &self, ctx: &mut ServerReceiveCtx, - events: PtrMut, + client_events: PtrMut, server: &mut RepliconServer, ) { - self.check_type::(); - - let events: &mut Events> = events.deref_mut(); + let client_events: &mut Events> = client_events.deref_mut(); for (client_id, message) in server.receive(self.channel_id) { let mut cursor = Cursor::new(&*message); - match self.deserialize(ctx, &mut cursor) { + match self.deserialize::(ctx, &mut cursor) { Ok(event) => { debug!( "applying event `{}` from `{client_id:?}`", any::type_name::() ); - events.send(FromClient { client_id, event }); + client_events.send(FromClient { client_id, event }); } Err(e) => debug!( "ignoring event `{}` from {client_id:?} that failed to deserialize: {e}", @@ -346,8 +304,8 @@ impl ClientEvent { /// # Safety /// /// The caller must ensure that `events` is [`Events`] and `server_events` is [`Events>`]. - unsafe fn resend_locally_typed(client_events: PtrMut, events: PtrMut) { - let client_events: &mut Events> = client_events.deref_mut(); + unsafe fn resend_locally_typed(server_events: PtrMut, events: PtrMut) { + let client_events: &mut Events> = server_events.deref_mut(); let events: &mut Events = events.deref_mut(); if !events.is_empty() { debug!( @@ -389,48 +347,34 @@ impl ClientEvent { /// /// # Safety /// - /// The caller must ensure that this instance was created for `E`. - unsafe fn serialize( + /// The caller must ensure that this instance was created for `E` and `I`. + unsafe fn serialize( &self, ctx: &mut ClientSendCtx, event: &E, message: &mut Vec, ) -> bincode::Result<()> { - let serialize: SerializeFn = std::mem::transmute(self.serialize); - (serialize)(ctx, event, message) + self.event_fns + .typed::() + .serialize(ctx, event, message) } /// Deserializes an event from a cursor. /// /// # Safety /// - /// The caller must ensure that this instance was created for `E`. - unsafe fn deserialize( + /// The caller must ensure that this instance was created for `E` and `I`. + unsafe fn deserialize( &self, ctx: &mut ServerReceiveCtx, cursor: &mut Cursor<&[u8]>, ) -> bincode::Result { - let deserialize: DeserializeFn = std::mem::transmute(self.deserialize); - (deserialize)(ctx, cursor) - } - - fn check_type(&self) { - debug_assert_eq!( - self.event_id, - TypeId::of::(), - "trying to call event functions with `{}`, but they were created with `{}`", - any::type_name::(), - self.event_name, - ); + self.event_fns + .typed::() + .deserialize(ctx, cursor) } } -/// Signature of client event serialization functions. -pub type SerializeFn = fn(&mut ClientSendCtx, &E, &mut Vec) -> bincode::Result<()>; - -/// Signature of client event deserialization functions. -pub type DeserializeFn = fn(&mut ServerReceiveCtx, &mut Cursor<&[u8]>) -> bincode::Result; - /// Signature of client event sending functions. type SendFn = unsafe fn(&ClientEvent, &mut ClientSendCtx, &Ptr, PtrMut, &mut RepliconClient); diff --git a/src/core/event/client_trigger.rs b/src/core/event/client_trigger.rs new file mode 100644 index 00000000..1547b09b --- /dev/null +++ b/src/core/event/client_trigger.rs @@ -0,0 +1,217 @@ +use std::{any, io::Cursor}; + +use bevy::{ecs::entity::MapEntities, prelude::*, ptr::PtrMut}; +use integer_encoding::{VarIntReader, VarIntWriter}; +use serde::{de::DeserializeOwned, Serialize}; + +use super::{ + client_event::{self, ClientEvent, FromClient}, + ctx::{ClientSendCtx, ServerReceiveCtx}, + event_fns::{EventDeserializeFn, EventFns, EventSerializeFn}, + event_registry::EventRegistry, + RemoteTrigger, +}; +use crate::core::{channels::RepliconChannel, entity_serde}; + +/// An extension trait for [`App`] for creating client triggers. +/// +/// See also [`ClientTriggerExt`]. +pub trait ClientTriggerAppExt { + /// Registers an event that can be triggered using [`ClientTriggerExt::client_trigger`]. + /// + /// The API matches [`ClientEventAppExt::add_client_event`](super::client_event::ClientEventAppExt::add_client_event): + /// [`FromClient`] will be triggered on the server after triggering `E` event on client. + /// When [`RepliconClient`](crate::core::replicon_client::RepliconClient) is inactive, the event + /// will also be triggered locally as [`FromClient`] with [`ClientId::SERVER`](crate::core::ClientId::SERVER). + /// + /// See also [`Self::add_client_trigger_with`] and the [corresponding section](../index.html#from-client-to-server) + /// from the quick start guide. + fn add_client_trigger( + &mut self, + channel: impl Into, + ) -> &mut Self { + self.add_client_trigger_with( + channel, + client_event::default_serialize::, + client_event::default_deserialize::, + ) + } + + /// Same as [`Self::add_client_trigger`], but additionally maps client entities to server inside the event before sending. + /// + /// Always use it for events that contain entities. + fn add_mapped_client_trigger( + &mut self, + channel: impl Into, + ) -> &mut Self { + self.add_client_trigger_with( + channel, + client_event::default_serialize_mapped::, + client_event::default_deserialize::, + ) + } + + /// Same as [`Self::add_client_trigger`], but uses the specified functions for serialization and deserialization. + /// + /// See also [`ClientEventAppExt::add_client_event_with`](super::client_event::ClientEventAppExt::add_client_event_with). + fn add_client_trigger_with( + &mut self, + channel: impl Into, + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, + ) -> &mut Self; +} + +impl ClientTriggerAppExt for App { + fn add_client_trigger_with( + &mut self, + channel: impl Into, + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, + ) -> &mut Self { + debug!("registering trigger `{}`", any::type_name::()); + + let event_fns = EventFns::new(serialize, deserialize) + .with_outer(trigger_serialize, trigger_deserialize); + + let trigger = ClientTrigger::new(self, channel, event_fns); + let mut event_registry = self.world_mut().resource_mut::(); + event_registry.register_client_trigger(trigger); + + self + } +} + +/// Small abstraction on top of [`ClientEvent`] that stores a function to trigger them. +pub(crate) struct ClientTrigger { + event: ClientEvent, + trigger: TriggerFn, +} + +impl ClientTrigger { + fn new( + app: &mut App, + channel: impl Into, + event_fns: EventFns, E>, + ) -> Self { + Self { + event: ClientEvent::new(app, channel, event_fns), + trigger: Self::trigger_typed::, + } + } + + pub(crate) fn trigger(&self, commands: &mut Commands, events: PtrMut) { + unsafe { + (self.trigger)(commands, events); + } + } + + /// Drains received [`FromClient>`] events and triggers them as [`FromClient`]. + /// + /// # Safety + /// + /// The caller must ensure that `client_events` is [`Events>>`] + /// and this instance was created for `E`. + unsafe fn trigger_typed(commands: &mut Commands, client_events: PtrMut) { + let client_events: &mut Events>> = client_events.deref_mut(); + for FromClient { client_id, event } in client_events.drain() { + debug!( + "triggering `{}` from `{client_id:?}`", + any::type_name::>() + ); + commands.trigger_targets( + FromClient { + client_id, + event: event.event, + }, + event.targets, + ); + } + } + + pub(crate) fn event(&self) -> &ClientEvent { + &self.event + } +} + +/// Signature of client trigger functions. +type TriggerFn = unsafe fn(&mut Commands, PtrMut); + +/// Serializes targets for [`RemoteTrigger`], maps them and delegates the event +/// serialiaztion to `serialize`. +/// +/// Used as outer function for [`EventFns`]. +fn trigger_serialize<'a, E>( + ctx: &mut ClientSendCtx<'a>, + trigger: &RemoteTrigger, + message: &mut Vec, + serialize: EventSerializeFn, E>, +) -> bincode::Result<()> { + message.write_varint(trigger.targets.len())?; + for &entity in &trigger.targets { + let entity = ctx.map_entity(entity); + entity_serde::serialize_entity(message, entity)?; + } + + (serialize)(ctx, &trigger.event, message) +} + +/// Deserializes targets for [`RemoteTrigger`], maps them and delegates the event +/// deserialiaztion to `deserialize`. +/// +/// Used as outer function for [`EventFns`]. +fn trigger_deserialize<'a, E>( + ctx: &mut ServerReceiveCtx<'a>, + cursor: &mut Cursor<&[u8]>, + deserialize: EventDeserializeFn, E>, +) -> bincode::Result> { + let len = cursor.read_varint()?; + let mut targets = Vec::with_capacity(len); + for _ in 0..len { + let entity = entity_serde::deserialize_entity(cursor)?; + targets.push(entity); + } + + let event = (deserialize)(ctx, cursor)?; + + Ok(RemoteTrigger { event, targets }) +} + +/// Extension trait for triggering client events. +/// +/// See also [`ClientTriggerAppExt`]. +pub trait ClientTriggerExt { + /// Like [`Commands::trigger`], but triggers [`FromClient`] on server and locally + /// if [`RepliconClient`](crate::core::replicon_client::RepliconClient) is inactive. + fn client_trigger(&mut self, event: impl Event); + + /// Like [`Self::client_trigger`], but allows you to specify target entities, similar to + /// [`Commands::trigger_targets`]. + fn client_trigger_targets(&mut self, event: impl Event, targets: impl Into>); +} + +impl ClientTriggerExt for Commands<'_, '_> { + fn client_trigger(&mut self, event: impl Event) { + self.client_trigger_targets(event, []); + } + + fn client_trigger_targets(&mut self, event: impl Event, targets: impl Into>) { + self.send_event(RemoteTrigger { + event, + targets: targets.into(), + }); + } +} + +impl ClientTriggerExt for World { + fn client_trigger(&mut self, event: impl Event) { + self.client_trigger_targets(event, []); + } + + fn client_trigger_targets(&mut self, event: impl Event, targets: impl Into>) { + self.send_event(RemoteTrigger { + event, + targets: targets.into(), + }); + } +} diff --git a/src/core/event/event_fns.rs b/src/core/event/event_fns.rs new file mode 100644 index 00000000..9958d944 --- /dev/null +++ b/src/core/event/event_fns.rs @@ -0,0 +1,198 @@ +use std::{ + any::{self, TypeId}, + io::Cursor, + mem, +}; + +/// Type-erased version of [`EventFns`]. +/// +/// Stored inside events after their creation. +#[derive(Clone, Copy)] +pub(super) struct UntypedEventFns { + serialize_ctx_id: TypeId, + serialize_ctx_name: &'static str, + deserialize_ctx_id: TypeId, + deserialize_ctx_name: &'static str, + event_id: TypeId, + event_name: &'static str, + inner_id: TypeId, + inner_name: &'static str, + + outer_serialize: unsafe fn(), + outer_deserialize: unsafe fn(), + serialize: unsafe fn(), + deserialize: unsafe fn(), +} + +impl UntypedEventFns { + /// Restores the original [`EventFns`] from which this type was created. + /// + /// # Safety + /// + /// The caller must ensure that the function is called with the same generics with which this instance was created. + pub(super) unsafe fn typed(self) -> EventFns { + // `TypeId` can only be obtained for `'static` types, but we can't impose this requirement because our context has a lifetime. + // So, we use the `typeid` crate for non-static `TypeId`, as we don't care about the lifetime and only need to check the type. + // This crate is already used by `erased_serde`, so we don't add an extra dependency. + debug_assert_eq!( + self.serialize_ctx_id, + typeid::of::(), + "trying to call event functions with serialize context `{}`, but they were created with `{}`", + any::type_name::(), + self.serialize_ctx_name, + ); + debug_assert_eq!( + self.deserialize_ctx_id, + typeid::of::(), + "trying to call event functions with deserialize context `{}`, but they were created with `{}`", + any::type_name::(), + self.deserialize_ctx_name, + ); + debug_assert_eq!( + self.event_id, + TypeId::of::(), + "trying to call event functions with event `{}`, but they were created with `{}`", + any::type_name::(), + self.event_name, + ); + debug_assert_eq!( + self.inner_id, + TypeId::of::(), + "trying to call event functions with inner type `{}`, but they were created with `{}`", + any::type_name::(), + self.inner_name, + ); + + EventFns { + outer_serialize: unsafe { + mem::transmute::>(self.outer_serialize) + }, + outer_deserialize: unsafe { + mem::transmute::>(self.outer_deserialize) + }, + serialize: unsafe { + mem::transmute::>(self.serialize) + }, + deserialize: unsafe { + mem::transmute::>(self.deserialize) + }, + } + } +} + +impl From> for UntypedEventFns { + fn from(value: EventFns) -> Self { + // SAFETY: these functions won't be called until the type is restored. + Self { + serialize_ctx_id: typeid::of::(), + serialize_ctx_name: any::type_name::(), + deserialize_ctx_id: typeid::of::(), + deserialize_ctx_name: any::type_name::(), + event_id: TypeId::of::(), + event_name: any::type_name::(), + inner_id: TypeId::of::(), + inner_name: any::type_name::(), + outer_serialize: unsafe { + mem::transmute::, unsafe fn()>(value.outer_serialize) + }, + outer_deserialize: unsafe { + mem::transmute::, unsafe fn()>(value.outer_deserialize) + }, + serialize: unsafe { + mem::transmute::, unsafe fn()>(value.serialize) + }, + deserialize: unsafe { + mem::transmute::, unsafe fn()>(value.deserialize) + }, + } + } +} + +/// Serialization and deserialization functions for an event. +/// +/// For triggers, we want to allow users to customize these functions, but it would be inconvenient +/// to write serialization and deserialization logic for trigger target entities every time. +/// Since closures can't be used, we provide outer functions that accept regular serialization functions. +/// By default, these outer functions simply call the inner function, but they can be overridden +/// to write common serde logic. +pub(super) struct EventFns { + outer_serialize: OuterSerializeFn, + outer_deserialize: OuterDeserializeFn, + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, +} + +impl EventFns { + /// Creates a new instance with default outer functions. + pub(super) fn new( + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, + ) -> Self { + Self { + outer_serialize: default_outer_serialize::, + outer_deserialize: default_outer_deserialize::, + serialize, + deserialize, + } + } +} + +impl EventFns { + /// Overrides current outer functions. + pub(super) fn with_outer( + self, + outer_serialize: OuterSerializeFn, + outer_deserialize: OuterDeserializeFn, + ) -> EventFns { + EventFns { + outer_serialize, + outer_deserialize, + serialize: self.serialize, + deserialize: self.deserialize, + } + } + + pub(super) fn serialize( + self, + ctx: &mut S, + event: &E, + message: &mut Vec, + ) -> bincode::Result<()> { + (self.outer_serialize)(ctx, event, message, self.serialize) + } + + pub(super) fn deserialize(self, ctx: &mut D, cursor: &mut Cursor<&[u8]>) -> bincode::Result { + (self.outer_deserialize)(ctx, cursor, self.deserialize) + } +} + +fn default_outer_serialize( + ctx: &mut C, + event: &E, + message: &mut Vec, + serialize: EventSerializeFn, +) -> bincode::Result<()> { + (serialize)(ctx, event, message) +} + +fn default_outer_deserialize( + ctx: &mut C, + cursor: &mut Cursor<&[u8]>, + deserialize: EventDeserializeFn, +) -> bincode::Result { + (deserialize)(ctx, cursor) +} + +/// Signature of event serialization functions. +pub type EventSerializeFn = fn(&mut C, &E, &mut Vec) -> bincode::Result<()>; + +/// Signature of event deserialization functions. +pub type EventDeserializeFn = fn(&mut C, &mut Cursor<&[u8]>) -> bincode::Result; + +/// Signature of outer serialization functions. +pub(super) type OuterSerializeFn = + fn(&mut C, &E, &mut Vec, EventSerializeFn) -> bincode::Result<()>; + +/// Signature of outer deserialization functions. +pub(super) type OuterDeserializeFn = + fn(&mut C, &mut Cursor<&[u8]>, EventDeserializeFn) -> bincode::Result; diff --git a/src/core/event/event_registry.rs b/src/core/event/event_registry.rs index b3c55756..216ea310 100644 --- a/src/core/event/event_registry.rs +++ b/src/core/event/event_registry.rs @@ -1,32 +1,64 @@ use bevy::prelude::*; -use super::{client_event::ClientEvent, server_event::ServerEvent}; +use super::{ + client_event::ClientEvent, client_trigger::ClientTrigger, server_event::ServerEvent, + server_trigger::ServerTrigger, +}; /// Registered server and client events. +/// +/// We use store triggers separately for quick iteration over them, +/// but they are events under the hood. #[derive(Resource, Default)] pub(crate) struct EventRegistry { - server: Vec, - client: Vec, + server_events: Vec, + client_events: Vec, + server_triggers: Vec, + client_triggers: Vec, } impl EventRegistry { - pub(super) fn register_server_event(&mut self, event_data: ServerEvent) { - self.server.push(event_data); + pub(super) fn register_server_event(&mut self, event: ServerEvent) { + self.server_events.push(event); } - pub(super) fn register_client_event(&mut self, event_data: ClientEvent) { - self.client.push(event_data); + pub(super) fn register_client_event(&mut self, event: ClientEvent) { + self.client_events.push(event); + } + + pub(super) fn register_server_trigger(&mut self, trigger: ServerTrigger) { + self.server_triggers.push(trigger); + } + + pub(super) fn register_client_trigger(&mut self, trigger: ClientTrigger) { + self.client_triggers.push(trigger); } pub(crate) fn iter_server_events_mut(&mut self) -> impl Iterator { - self.server.iter_mut() + self.server_events.iter_mut().chain( + self.server_triggers + .iter_mut() + .map(|trigger| trigger.event_mut()), + ) } pub(crate) fn iter_server_events(&self) -> impl Iterator { - self.server.iter() + self.server_events + .iter() + .chain(self.server_triggers.iter().map(|trigger| trigger.event())) } pub(crate) fn iter_client_events(&self) -> impl Iterator { - self.client.iter() + self.client_events + .iter() + .chain(self.client_triggers.iter().map(|trigger| trigger.event())) + } + + pub(crate) fn iter_server_triggers(&self) -> impl Iterator { + self.server_triggers.iter() + } + + pub(crate) fn iter_client_triggers(&self) -> impl Iterator { + self.client_triggers.iter() } } diff --git a/src/core/event/server_event.rs b/src/core/event/server_event.rs index a35437a4..41af341e 100644 --- a/src/core/event/server_event.rs +++ b/src/core/event/server_event.rs @@ -1,5 +1,5 @@ use std::{ - any::{self, TypeId}, + any, collections::HashSet, io::{Cursor, Write}, marker::PhantomData, @@ -7,10 +7,7 @@ use std::{ }; use bevy::{ - ecs::{ - component::{ComponentId, Components}, - entity::MapEntities, - }, + ecs::{component::ComponentId, entity::MapEntities}, prelude::*, ptr::{Ptr, PtrMut}, }; @@ -21,6 +18,7 @@ use serde::{de::DeserializeOwned, Serialize}; use super::{ ctx::{ClientReceiveCtx, ServerSendCtx}, + event_fns::{EventDeserializeFn, EventFns, EventSerializeFn, UntypedEventFns}, event_registry::EventRegistry, }; use crate::core::{ @@ -122,8 +120,8 @@ pub trait ServerEventAppExt { fn add_server_event_with( &mut self, channel: impl Into, - serialize: SerializeFn, - deserialize: DeserializeFn, + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, ) -> &mut Self; /// Marks the event `E` as an independent event. @@ -153,29 +151,15 @@ impl ServerEventAppExt for App { fn add_server_event_with( &mut self, channel: impl Into, - serialize: SerializeFn, - deserialize: DeserializeFn, + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, ) -> &mut Self { debug!("registering event `{}`", any::type_name::()); - self.add_event::() - .add_event::>() - .init_resource::>(); - - let channel_id = self - .world_mut() - .resource_mut::() - .create_server_channel(channel); - - self.world_mut() - .resource_scope(|world, mut event_registry: Mut| { - event_registry.register_server_event(ServerEvent::new( - world.components(), - channel_id, - serialize, - deserialize, - )); - }); + let event_fns = EventFns::new(serialize, deserialize); + let event = ServerEvent::new(self, channel, event_fns); + let mut event_registry = self.world_mut().resource_mut::(); + event_registry.register_server_event(event); self } @@ -193,7 +177,7 @@ impl ServerEventAppExt for App { }); let mut event_registry = self.world_mut().resource_mut::(); - let event_data = event_registry + let event = event_registry .iter_server_events_mut() .find(|event| event.events_id() == events_id) .unwrap_or_else(|| { @@ -203,7 +187,7 @@ impl ServerEventAppExt for App { ) }); - event_data.independent = true; + event.independent = true; self } @@ -213,9 +197,6 @@ impl ServerEventAppExt for App { /// /// Needed so events of different types can be processed together. pub(crate) struct ServerEvent { - event_id: TypeId, - event_name: &'static str, - /// Whether this event depends on replication or not. /// /// Events like a chat message event do not have to wait for replication to @@ -239,55 +220,39 @@ pub(crate) struct ServerEvent { receive: ReceiveFn, resend_locally: ResendLocallyFn, reset: ResetFn, - serialize: unsafe fn(), - deserialize: unsafe fn(), + event_fns: UntypedEventFns, } impl ServerEvent { - fn new( - components: &Components, - channel_id: u8, - serialize: SerializeFn, - deserialize: DeserializeFn, + pub(super) fn new( + app: &mut App, + channel: impl Into, + event_fns: EventFns, ) -> Self { - let events_id = components.resource_id::>().unwrap_or_else(|| { - panic!( - "event `{}` should be previously registered", - any::type_name::() - ) - }); - let server_events_id = components - .resource_id::>>() - .unwrap_or_else(|| { - panic!( - "event `{}` should be previously registered", - any::type_name::>() - ) - }); - let queue_id = components - .resource_id::>() - .unwrap_or_else(|| { - panic!( - "resource `{}` should be previously inserted", - any::type_name::>() - ) - }); + let channel_id = app + .world_mut() + .resource_mut::() + .create_server_channel(channel); + + app.add_event::() + .add_event::>() + .init_resource::>(); + + let events_id = app.world().resource_id::>().unwrap(); + let server_events_id = app.world().resource_id::>>().unwrap(); + let queue_id = app.world().resource_id::>().unwrap(); Self { - event_id: TypeId::of::(), - event_name: any::type_name::(), independent: false, events_id, server_events_id, queue_id, channel_id, - send_or_buffer: Self::send_or_buffer_typed::, - receive: Self::receive_typed::, + send_or_buffer: Self::send_or_buffer_typed::, + receive: Self::receive_typed::, resend_locally: Self::resend_locally_typed::, reset: Self::reset_typed::, - // SAFETY: these functions won't be called until the type is restored. - serialize: unsafe { mem::transmute::, unsafe fn()>(serialize) }, - deserialize: unsafe { mem::transmute::, unsafe fn()>(deserialize) }, + event_fns: event_fns.into(), } } @@ -336,8 +301,8 @@ impl ServerEvent { /// # Safety /// /// The caller must ensure that `server_events` is [`Events>`] - /// and this instance was created for `E`. - unsafe fn send_or_buffer_typed( + /// and this instance was created for `E` and `I`. + unsafe fn send_or_buffer_typed( &self, ctx: &mut ServerSendCtx, server_events: &Ptr, @@ -345,8 +310,6 @@ impl ServerEvent { connected_clients: &ConnectedClients, buffered_events: &mut BufferedServerEvents, ) { - self.check_type::(); - let events: &Events> = server_events.deref(); // For server events we don't track read events because // all of them will always be drained in the local resending system. @@ -354,10 +317,10 @@ impl ServerEvent { debug!("sending event `{}` with `{mode:?}`", any::type_name::()); if self.is_independent() { - self.send_independent_event(ctx, event, mode, server, connected_clients) + self.send_independent_event::(ctx, event, mode, server, connected_clients) .expect("independent server event should be serializable"); } else { - self.buffer_event(ctx, event, *mode, buffered_events) + self.buffer_event::(ctx, event, *mode, buffered_events) .expect("server event should be serializable"); } } @@ -367,10 +330,10 @@ impl ServerEvent { /// /// # Safety /// - /// The caller must ensure that `event_data` was created for `E`. + /// The caller must ensure that this instance was created for `E` and `I`. /// /// For regular events see [`Self::buffer_event`]. - unsafe fn send_independent_event( + unsafe fn send_independent_event( &self, ctx: &mut ServerSendCtx, event: &E, @@ -379,7 +342,7 @@ impl ServerEvent { connected_clients: &ConnectedClients, ) -> bincode::Result<()> { let mut message = Vec::new(); - self.serialize(ctx, event, &mut message)?; + self.serialize::(ctx, event, &mut message)?; let message: Bytes = message.into(); match *mode { @@ -409,17 +372,17 @@ impl ServerEvent { /// /// # Safety /// - /// The caller must ensure that this instance was created for `E`. + /// The caller must ensure that this instance was created for `E` and `I`. /// /// For independent events see [`Self::send_independent_event`]. - unsafe fn buffer_event( + unsafe fn buffer_event( &self, ctx: &mut ServerSendCtx, event: &E, mode: SendMode, buffered_events: &mut BufferedServerEvents, ) -> bincode::Result<()> { - let message = self.serialize_with_padding(ctx, event)?; + let message = self.serialize_with_padding::(ctx, event)?; buffered_events.insert(mode, self.channel_id, message); Ok(()) } @@ -430,8 +393,8 @@ impl ServerEvent { /// /// # Safety /// - /// The caller must ensure that this instance was created for `E`. - unsafe fn serialize_with_padding( + /// The caller must ensure that this instance was created for `E` and `I`. + unsafe fn serialize_with_padding( &self, ctx: &mut ServerSendCtx, event: &E, @@ -439,7 +402,7 @@ impl ServerEvent { let mut message = Vec::new(); let padding = [0; mem::size_of::()]; message.write_all(&padding)?; - self.serialize(ctx, event, &mut message)?; + self.serialize::(ctx, event, &mut message)?; let message = SerializedMessage::Raw(message); Ok(message) @@ -467,8 +430,8 @@ impl ServerEvent { /// # Safety /// /// The caller must ensure that `events` is [`Events`], `queue` is [`ServerEventQueue`] - /// and this instance was created for `E`. - unsafe fn receive_typed( + /// and this instance was created for `E` and `I`. + unsafe fn receive_typed( &self, ctx: &mut ClientReceiveCtx, events: PtrMut, @@ -476,14 +439,12 @@ impl ServerEvent { client: &mut RepliconClient, update_tick: RepliconTick, ) { - self.check_type::(); - let events: &mut Events = events.deref_mut(); let queue: &mut ServerEventQueue = queue.deref_mut(); while let Some((tick, message)) = queue.pop_if_le(update_tick) { let mut cursor = Cursor::new(&*message); - match self.deserialize(ctx, &mut cursor) { + match self.deserialize::(ctx, &mut cursor) { Ok(event) => { debug!( "applying event `{}` from queue with `{tick:?}`", @@ -523,7 +484,7 @@ impl ServerEvent { } } - match self.deserialize(ctx, &mut cursor) { + match self.deserialize::(ctx, &mut cursor) { Ok(event) => { debug!("applying event `{}`", any::type_name::()); events.send(event); @@ -606,31 +567,35 @@ impl ServerEvent { /// /// # Safety /// - /// The caller must ensure that this instance was created for `E`. - unsafe fn serialize( + /// The caller must ensure that this instance was created for `E` and `I`. + unsafe fn serialize( &self, ctx: &mut ServerSendCtx, event: &E, message: &mut Vec, ) -> bincode::Result<()> { - let serialize: SerializeFn = std::mem::transmute(self.serialize); - (serialize)(ctx, event, message) + self.event_fns + .typed::() + .serialize(ctx, event, message) } /// Deserializes an event from a cursor. /// /// # Safety /// - /// The caller must ensure that this instance was created for `E`. - unsafe fn deserialize( + /// The caller must ensure that this instance was created for `E` and `I`. + unsafe fn deserialize( &self, ctx: &mut ClientReceiveCtx, cursor: &mut Cursor<&[u8]>, ) -> bincode::Result { - let deserialize: DeserializeFn = std::mem::transmute(self.deserialize); - let event = (deserialize)(ctx, cursor); + let event = self + .event_fns + .typed::() + .deserialize(ctx, cursor)?; + if ctx.invalid_entities.is_empty() { - event + Ok(event) } else { let message = format!( "unable to map entities `{:?}` from server, \ @@ -641,24 +606,8 @@ impl ServerEvent { Err(bincode::ErrorKind::Custom(message).into()) } } - - fn check_type(&self) { - debug_assert_eq!( - self.event_id, - TypeId::of::(), - "trying to call event functions with `{}`, but they were created with `{}`", - any::type_name::(), - self.event_name, - ); - } } -/// Signature of server event serialization functions. -pub type SerializeFn = fn(&mut ServerSendCtx, &E, &mut Vec) -> bincode::Result<()>; - -/// Signature of server event deserialization functions. -pub type DeserializeFn = fn(&mut ClientReceiveCtx, &mut Cursor<&[u8]>) -> bincode::Result; - /// Signature of server event sending functions. type SendOrBufferFn = unsafe fn( &ServerEvent, diff --git a/src/core/event/server_trigger.rs b/src/core/event/server_trigger.rs new file mode 100644 index 00000000..ad5d27d3 --- /dev/null +++ b/src/core/event/server_trigger.rs @@ -0,0 +1,229 @@ +use std::{any, io::Cursor}; + +use bevy::{ecs::entity::MapEntities, prelude::*, ptr::PtrMut}; +use integer_encoding::{VarIntReader, VarIntWriter}; +use serde::{de::DeserializeOwned, Serialize}; + +use super::{ + ctx::{ClientReceiveCtx, ServerSendCtx}, + event_fns::{EventDeserializeFn, EventFns, EventSerializeFn}, + event_registry::EventRegistry, + server_event::{self, ServerEvent, ToClients}, + RemoteTrigger, +}; +use crate::core::{channels::RepliconChannel, entity_serde}; + +/// An extension trait for [`App`] for creating server triggers. +/// +/// See also [`ServerTriggerExt`]. +pub trait ServerTriggerAppExt { + /// Registers an event that can be triggered using [`ServerTriggerExt::server_trigger`]. + /// + /// The API matches [`ServerEventAppExt::add_server_event`](super::server_event::ServerEventAppExt::add_server_event): + /// `E` will be triggered on the client after triggering [`ToClients`] event on server. + /// If [`ClientId::SERVER`](crate::core::ClientId::SERVER) is a recipient of the event, then `E` events will be emitted on the server + /// as well. + /// + /// See also [`Self::add_server_trigger_with`] and the [corresponding section](../index.html#from-server-to-client) + /// from the quick start guide. + fn add_server_trigger( + &mut self, + channel: impl Into, + ) -> &mut Self { + self.add_server_trigger_with( + channel, + server_event::default_serialize::, + server_event::default_deserialize::, + ) + } + + /// Same as [`Self::add_server_trigger`], but additionally maps client entities to server inside the event before receiving. + /// + /// Always use it for events that contain entities. + fn add_mapped_server_trigger( + &mut self, + channel: impl Into, + ) -> &mut Self { + self.add_server_trigger_with( + channel, + server_event::default_serialize::, + server_event::default_deserialize_mapped::, + ) + } + + /// Same as [`Self::add_server_trigger`], but uses the specified functions for serialization and deserialization. + /// + /// See also [`ServerEventAppExt::add_server_event_with`](super::server_event::ServerEventAppExt::add_server_event_with). + fn add_server_trigger_with( + &mut self, + channel: impl Into, + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, + ) -> &mut Self; +} + +impl ServerTriggerAppExt for App { + fn add_server_trigger_with( + &mut self, + channel: impl Into, + serialize: EventSerializeFn, + deserialize: EventDeserializeFn, + ) -> &mut Self { + debug!("registering trigger `{}`", any::type_name::()); + + let event_fns = EventFns::new(serialize, deserialize) + .with_outer(trigger_serialize, trigger_deserialize); + let trigger = ServerTrigger::new(self, channel, event_fns); + let mut event_registry = self.world_mut().resource_mut::(); + event_registry.register_server_trigger(trigger); + + self + } +} + +/// Small abstraction on top of [`ServerEvent`] that stores a function to trigger them. +pub(crate) struct ServerTrigger { + trigger: TriggerFn, + event: ServerEvent, +} + +impl ServerTrigger { + fn new( + app: &mut App, + channel: impl Into, + event_fns: EventFns, E>, + ) -> Self { + let event = ServerEvent::new(app, channel, event_fns); + Self { + trigger: Self::trigger_typed::, + event, + } + } + + pub(crate) fn trigger(&self, commands: &mut Commands, events: PtrMut) { + unsafe { + (self.trigger)(commands, events); + } + } + + /// Drains received [`RemoteTrigger`] events and triggers them as `E`. + /// + /// # Safety + /// + /// The caller must ensure that `events` is [`Events>`] + /// and this instance was created for `E`. + unsafe fn trigger_typed(commands: &mut Commands, events: PtrMut) { + let events: &mut Events> = events.deref_mut(); + for trigger in events.drain() { + debug!("triggering `{}`", any::type_name::()); + commands.trigger_targets(trigger.event, trigger.targets); + } + } + + pub(crate) fn event(&self) -> &ServerEvent { + &self.event + } + + pub(super) fn event_mut(&mut self) -> &mut ServerEvent { + &mut self.event + } +} + +/// Signature of server trigger functions. +type TriggerFn = unsafe fn(&mut Commands, PtrMut); + +/// Serializes targets for [`RemoteTrigger`] and delegates the event +/// serialiaztion to `serialize`. +/// +/// Used as outer function for [`EventFns`]. +fn trigger_serialize<'a, E>( + ctx: &mut ServerSendCtx<'a>, + trigger: &RemoteTrigger, + message: &mut Vec, + serialize: EventSerializeFn, E>, +) -> bincode::Result<()> { + message.write_varint(trigger.targets.len())?; + for &entity in &trigger.targets { + entity_serde::serialize_entity(message, entity)?; + } + + (serialize)(ctx, &trigger.event, message) +} + +/// Deserializes targets for [`RemoteTrigger`] and delegates the event +/// deserialiaztion to `deserialize`. +/// +/// Used as outer function for [`EventFns`]. +fn trigger_deserialize<'a, E>( + ctx: &mut ClientReceiveCtx<'a>, + cursor: &mut Cursor<&[u8]>, + deserialize: EventDeserializeFn, E>, +) -> bincode::Result> { + let len = cursor.read_varint()?; + let mut targets = Vec::with_capacity(len); + for _ in 0..len { + let entity = entity_serde::deserialize_entity(cursor)?; + targets.push(ctx.map_entity(entity)); + } + + let event = (deserialize)(ctx, cursor)?; + + Ok(RemoteTrigger { event, targets }) +} + +/// Extension trait for triggering server events. +/// +/// See also [`ServerTriggerAppExt`]. +pub trait ServerTriggerExt { + /// Like [`Commands::trigger`], but triggers [`E`] on server and locally + /// if [`ClientId::SERVER`](crate::core::ClientId::SERVER) is a recipient of the event). + fn server_trigger(&mut self, event: ToClients); + + /// Like [`Self::server_trigger`], but allows you to specify target entities, similar to + /// [`Commands::trigger_targets`]. + fn server_trigger_targets( + &mut self, + event: ToClients, + targets: impl Into>, + ); +} + +impl ServerTriggerExt for Commands<'_, '_> { + fn server_trigger(&mut self, event: ToClients) { + self.server_trigger_targets(event, []); + } + + fn server_trigger_targets( + &mut self, + event: ToClients, + targets: impl Into>, + ) { + self.send_event(ToClients { + mode: event.mode, + event: RemoteTrigger { + event: event.event, + targets: targets.into(), + }, + }); + } +} + +impl ServerTriggerExt for World { + fn server_trigger(&mut self, event: ToClients) { + self.server_trigger_targets(event, []); + } + + fn server_trigger_targets( + &mut self, + event: ToClients, + targets: impl Into>, + ) { + self.send_event(ToClients { + mode: event.mode, + event: RemoteTrigger { + event: event.event, + targets: targets.into(), + }, + }); + } +} diff --git a/src/lib.rs b/src/lib.rs index 51143fa4..92cc0c85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -381,9 +381,9 @@ replicate that one. This crate provides [`ParentSync`] component that replicates Bevy hierarchy. For your custom components with relations you need to write your own with a similar pattern. -## Network events +## Network events and triggers -Network event replace RPCs (remote procedure calls) in other engines and, +This replaces RPCs (remote procedure calls) in other engines and, unlike components, can be sent both from server to clients and from clients to server. @@ -468,6 +468,31 @@ require access to the [`AppTypeRegistry`] resource. Don't forget to validate the contents of every [`Box`] from a client, it could be anything! +Alternatively you can use triggers with similar API. First, you need to register the event +with [`ClientTriggerAppExt::add_client_trigger()`] and then use [`ClientTriggerExt::client_trigger`]: + +``` +# use bevy::prelude::*; +# use bevy_replicon::prelude::*; +# use serde::{Deserialize, Serialize}; +# let mut app = App::new(); +# app.add_plugins(RepliconPlugins); +app.add_client_trigger::(ChannelKind::Ordered) + .add_observer(receive_events) + .add_systems(Update, send_events.run_if(client_connected)); + +fn send_events(mut commands: Commands) { + commands.client_trigger(DummyEvent); +} + +fn receive_events(trigger: Trigger>) { + let FromClient { client_id, event } = trigger.event(); + info!("received event {event:?} from {client_id:?}"); +} +# #[derive(Debug, Default, Deserialize, Event, Serialize, Clone, Copy)] +# struct DummyEvent; +``` + ### From server to client A similar technique is used to send events from server to clients. To do this, @@ -520,6 +545,33 @@ If the event contains an entity, then For events that require special serialization and deserialization functions you can use [`ServerEventAppExt::add_server_event_with()`]. +Trigger-based API available for server events as well. First, you need to register the event +with [`ServerTriggerAppExt::add_server_trigger()`] and then use [`ServerTriggerExt::server_trigger`]: + +``` +# use bevy::prelude::*; +# use bevy_replicon::prelude::*; +# use serde::{Deserialize, Serialize}; +# let mut app = App::new(); +# app.add_plugins(RepliconPlugins); +app.add_server_trigger::(ChannelKind::Ordered) + .add_observer(receive_events) + .add_systems(Update, send_events.run_if(server_running)); + +fn send_events(mut commands: Commands) { + commands.server_trigger(ToClients { + mode: SendMode::Broadcast, + event: DummyEvent, + }); +} + +fn receive_events(trigger: Trigger) { + info!("received event {:?} from server", trigger.event()); +} +# #[derive(Debug, Default, Deserialize, Event, Serialize, Clone, Copy)] +# struct DummyEvent; +``` +
Just like with components, all networked events should be registered in the same order. @@ -630,7 +682,9 @@ pub mod prelude { connected_clients::ConnectedClients, event::{ client_event::{ClientEventAppExt, FromClient}, + client_trigger::{ClientTriggerAppExt, ClientTriggerExt}, server_event::{SendMode, ServerEventAppExt, ToClients}, + server_trigger::{ServerTriggerAppExt, ServerTriggerExt}, }, replication::{ command_markers::AppMarkerExt, diff --git a/src/server/event.rs b/src/server/event.rs index dd637767..da3b78c9 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -61,6 +61,18 @@ impl Plugin for ServerEventPlugin { .build_state(app.world_mut()) .build_system(Self::receive); + let trigger = ( + FilteredResourcesMutParamBuilder::new(|builder| { + for trigger in event_registry.iter_client_triggers() { + builder.add_write_by_id(trigger.event().client_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_server_events() { @@ -80,7 +92,12 @@ impl Plugin for ServerEventPlugin { app.insert_resource(event_registry) .add_systems( PreUpdate, - receive.in_set(ServerSet::Receive).run_if(server_running), + ( + receive.run_if(server_running), + trigger.run_if(server_or_singleplayer), + ) + .chain() + .in_set(ServerSet::Receive), ) .add_systems( PostUpdate, @@ -112,14 +129,14 @@ impl ServerEventPlugin { registry: ®istry.read(), }; - for event_data in event_registry.iter_server_events() { + for event in event_registry.iter_server_events() { let server_events = server_events - .get_by_id(event_data.server_events_id()) + .get_by_id(event.server_events_id()) .expect("server events resource should be accessible"); // SAFETY: passed pointer was obtained using this event data. unsafe { - event_data.send_or_buffer( + event.send_or_buffer( &mut ctx, &server_events, &mut server, @@ -150,13 +167,26 @@ impl ServerEventPlugin { registry: ®istry.read(), }; - 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()) - .expect("client events shouldn't be removed"); + .get_mut_by_id(event.client_events_id()) + .expect("client events resource should be accessible"); // SAFETY: passed pointer was obtained using this event data. - unsafe { event_data.receive(&mut ctx, client_events.into_inner(), &mut server) }; + unsafe { event.receive(&mut ctx, client_events.into_inner(), &mut server) }; + } + } + + fn trigger( + mut client_events: FilteredResourcesMut, + mut commands: Commands, + event_registry: Res, + ) { + for trigger in event_registry.iter_client_triggers() { + let client_events = client_events + .get_mut_by_id(trigger.event().client_events_id()) + .expect("client events resource should be accessible"); + trigger.trigger(&mut commands, client_events.into_inner()); } } @@ -165,16 +195,16 @@ impl ServerEventPlugin { mut events: FilteredResourcesMut, event_registry: Res, ) { - for event_data in event_registry.iter_server_events() { + for event in event_registry.iter_server_events() { let server_events = server_events - .get_mut_by_id(event_data.server_events_id()) - .expect("server events shouldn't be removed"); + .get_mut_by_id(event.server_events_id()) + .expect("server events resource should be accessible"); let events = events - .get_mut_by_id(event_data.events_id()) - .expect("events shouldn't be removed"); + .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(server_events.into_inner(), events.into_inner()) }; + unsafe { event.resend_locally(server_events.into_inner(), events.into_inner()) }; } } } diff --git a/tests/client_event.rs b/tests/client_event.rs index f39c0dd2..1e8d71ac 100644 --- a/tests/client_event.rs +++ b/tests/client_event.rs @@ -38,7 +38,7 @@ fn mapping_and_sending_receiving() { let mut client_app = App::new(); for app in [&mut server_app, &mut client_app] { app.add_plugins((MinimalPlugins, RepliconPlugins)) - .add_mapped_client_event::(ChannelKind::Ordered) + .add_mapped_client_event::(ChannelKind::Ordered) .finish(); } @@ -53,7 +53,7 @@ fn mapping_and_sending_receiving() { client_app .world_mut() - .send_event(MappedEvent(client_entity)); + .send_event(EntityEvent(client_entity)); client_app.update(); server_app.exchange_with_client(&mut client_app); @@ -61,7 +61,7 @@ fn mapping_and_sending_receiving() { let mapped_entities: Vec<_> = server_app .world_mut() - .resource_mut::>>() + .resource_mut::>>() .drain() .map(|event| event.event.0) .collect(); @@ -129,9 +129,9 @@ fn local_resending() { struct DummyEvent; #[derive(Deserialize, Event, Serialize, Clone)] -struct MappedEvent(Entity); +struct EntityEvent(Entity); -impl MapEntities for MappedEvent { +impl MapEntities for EntityEvent { fn map_entities(&mut self, entity_mapper: &mut M) { self.0 = entity_mapper.map_entity(self.0); } diff --git a/tests/client_trigger.rs b/tests/client_trigger.rs new file mode 100644 index 00000000..02b8d319 --- /dev/null +++ b/tests/client_trigger.rs @@ -0,0 +1,178 @@ +use bevy::{ecs::entity::MapEntities, prelude::*, time::TimePlugin}; +use bevy_replicon::{ + core::server_entity_map::ServerEntityMap, prelude::*, test_app::ServerTestAppExt, +}; +use serde::{Deserialize, Serialize}; + +#[test] +fn sending_receiving() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins((MinimalPlugins, RepliconPlugins)) + .add_client_trigger::(ChannelKind::Ordered) + .finish(); + } + server_app.init_resource::>(); + + server_app.connect_client(&mut client_app); + + client_app.world_mut().client_trigger(DummyEvent); + + client_app.update(); + server_app.exchange_with_client(&mut client_app); + server_app.update(); + + let reader = server_app.world().resource::>(); + assert_eq!(reader.entities, [Entity::PLACEHOLDER]); +} + +#[test] +fn sending_receiving_with_target() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins((MinimalPlugins, RepliconPlugins)) + .add_client_trigger::(ChannelKind::Ordered) + .finish(); + } + server_app.init_resource::>(); + + server_app.connect_client(&mut client_app); + + let client_entity = Entity::from_raw(0); + let server_entity = Entity::from_raw(client_entity.index() + 1); + client_app + .world_mut() + .resource_mut::() + .insert(server_entity, client_entity); + + client_app + .world_mut() + .client_trigger_targets(DummyEvent, [client_entity]); + + client_app.update(); + server_app.exchange_with_client(&mut client_app); + server_app.update(); + + let reader = server_app.world().resource::>(); + assert_eq!(reader.entities, [server_entity]); +} + +#[test] +fn mapping_and_sending_receiving() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins((MinimalPlugins, RepliconPlugins)) + .add_mapped_client_trigger::(ChannelKind::Ordered) + .finish(); + } + server_app.init_resource::>(); + + server_app.connect_client(&mut client_app); + + let client_entity = Entity::from_raw(0); + let server_entity = Entity::from_raw(client_entity.index() + 1); + client_app + .world_mut() + .resource_mut::() + .insert(server_entity, client_entity); + + client_app + .world_mut() + .client_trigger(EntityEvent(client_entity)); + + client_app.update(); + server_app.exchange_with_client(&mut client_app); + server_app.update(); + + let reader = server_app.world().resource::>(); + let mapped_entities: Vec<_> = reader.events.iter().map(|event| event.event.0).collect(); + assert_eq!(mapped_entities, [server_entity]); +} + +#[test] +fn sending_receiving_without_plugins() { + let mut server_app = App::new(); + let mut client_app = App::new(); + server_app + .add_plugins(( + MinimalPlugins, + RepliconPlugins.build().disable::(), + )) + .add_client_trigger::(ChannelKind::Ordered) + .finish(); + client_app + .add_plugins(( + MinimalPlugins, + RepliconPlugins.build().disable::(), + )) + .add_client_trigger::(ChannelKind::Ordered) + .finish(); + server_app.init_resource::>(); + + server_app.connect_client(&mut client_app); + + client_app.world_mut().client_trigger(DummyEvent); + + client_app.update(); + server_app.exchange_with_client(&mut client_app); + server_app.update(); + + let reader = server_app.world().resource::>(); + assert_eq!(reader.entities.len(), 1); +} + +#[test] +fn local_resending() { + let mut app = App::new(); + app.add_plugins((TimePlugin, RepliconPlugins)) + .add_client_trigger::(ChannelKind::Ordered) + .finish(); + app.init_resource::>(); + + app.world_mut().client_trigger(DummyEvent); + + // Requires 2 updates because local resending runs + // in `PostUpdate` and triggering runs in `PreUpdate`. + app.update(); + app.update(); + + let reader = app.world().resource::>(); + assert_eq!(reader.entities.len(), 1); +} + +#[derive(Deserialize, Event, Serialize, Clone)] +struct DummyEvent; + +#[derive(Deserialize, Event, Serialize, Clone)] +struct EntityEvent(Entity); + +impl MapEntities for EntityEvent { + fn map_entities(&mut self, entity_mapper: &mut M) { + self.0 = entity_mapper.map_entity(self.0); + } +} + +#[derive(Resource)] +struct TriggerReader { + events: Vec>, + entities: Vec, +} + +impl FromWorld for TriggerReader { + fn from_world(world: &mut World) -> Self { + world.add_observer( + |trigger: Trigger>, mut counter: ResMut| { + counter.events.push(trigger.event().clone()); + counter.entities.push(trigger.entity()); + }, + ); + + Self { + events: Default::default(), + entities: Default::default(), + } + } +} diff --git a/tests/server_trigger.rs b/tests/server_trigger.rs new file mode 100644 index 00000000..a506bbee --- /dev/null +++ b/tests/server_trigger.rs @@ -0,0 +1,224 @@ +use bevy::{ecs::entity::MapEntities, prelude::*, time::TimePlugin}; +use bevy_replicon::{ + core::server_entity_map::ServerEntityMap, prelude::*, test_app::ServerTestAppExt, +}; +use serde::{Deserialize, Serialize}; + +#[test] +fn sending_receiving() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .add_server_trigger::(ChannelKind::Ordered) + .finish(); + } + client_app.init_resource::>(); + + server_app.connect_client(&mut client_app); + + server_app.world_mut().server_trigger(ToClients { + mode: SendMode::Broadcast, + event: DummyEvent, + }); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let reader = client_app.world().resource::>(); + assert_eq!(reader.entities, [Entity::PLACEHOLDER]); +} + +#[test] +fn sending_receiving_with_target() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .add_server_trigger::(ChannelKind::Ordered) + .finish(); + } + client_app.init_resource::>(); + + server_app.connect_client(&mut client_app); + + let client_entity = Entity::from_raw(0); + let server_entity = Entity::from_raw(client_entity.index() + 1); + client_app + .world_mut() + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.world_mut().server_trigger_targets( + ToClients { + mode: SendMode::Broadcast, + event: DummyEvent, + }, + [server_entity], + ); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let reader = client_app.world().resource::>(); + assert_eq!(reader.entities, [client_entity]); +} + +#[test] +fn sending_receiving_and_mapping() { + let mut server_app = App::new(); + let mut client_app = App::new(); + for app in [&mut server_app, &mut client_app] { + app.add_plugins(( + MinimalPlugins, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .add_mapped_server_trigger::(ChannelKind::Ordered) + .finish(); + } + client_app.init_resource::>(); + + server_app.connect_client(&mut client_app); + + let client_entity = Entity::from_raw(0); + let server_entity = Entity::from_raw(client_entity.index() + 1); + client_app + .world_mut() + .resource_mut::() + .insert(server_entity, client_entity); + + server_app.world_mut().server_trigger(ToClients { + mode: SendMode::Broadcast, + event: EntityEvent(server_entity), + }); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let reader = client_app.world().resource::>(); + let mapped_entities: Vec<_> = reader.events.iter().map(|event| event.0).collect(); + assert_eq!(mapped_entities, [client_entity]); +} + +#[test] +fn sending_receiving_without_plugins() { + let mut server_app = App::new(); + let mut client_app = App::new(); + server_app + .add_plugins(( + MinimalPlugins, + RepliconPlugins + .build() + .set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }) + .disable::() + .disable::(), + )) + .add_server_trigger::(ChannelKind::Ordered) + .finish(); + client_app + .add_plugins(( + MinimalPlugins, + RepliconPlugins + .build() + .disable::() + .disable::(), + )) + .add_server_trigger::(ChannelKind::Ordered) + .finish(); + client_app.init_resource::>(); + + server_app.connect_client(&mut client_app); + + server_app.world_mut().server_trigger(ToClients { + mode: SendMode::Broadcast, + event: DummyEvent, + }); + + server_app.update(); + server_app.exchange_with_client(&mut client_app); + client_app.update(); + + let reader = client_app.world().resource::>(); + assert_eq!(reader.entities.len(), 1); +} + +#[test] +fn local_resending() { + let mut app = App::new(); + app.add_plugins(( + TimePlugin, + RepliconPlugins.set(ServerPlugin { + tick_policy: TickPolicy::EveryFrame, + ..Default::default() + }), + )) + .add_server_trigger::(ChannelKind::Ordered) + .finish(); + app.init_resource::>(); + + app.world_mut().server_trigger(ToClients { + mode: SendMode::Broadcast, + event: DummyEvent, + }); + + // Requires 2 updates because local resending runs + // in `PostUpdate` and triggering runs in `PreUpdate`. + app.update(); + app.update(); + + let reader = app.world().resource::>(); + assert_eq!(reader.entities.len(), 1); +} + +#[derive(Event, Serialize, Deserialize, Clone)] +struct DummyEvent; + +#[derive(Event, Deserialize, Serialize, Clone)] +struct EntityEvent(Entity); + +impl MapEntities for EntityEvent { + fn map_entities(&mut self, entity_mapper: &mut T) { + self.0 = entity_mapper.map_entity(self.0); + } +} + +#[derive(Resource)] +struct TriggerReader { + events: Vec, + entities: Vec, +} + +impl FromWorld for TriggerReader { + fn from_world(world: &mut World) -> Self { + world.add_observer(|trigger: Trigger, mut counter: ResMut| { + counter.events.push(trigger.event().clone()); + counter.entities.push(trigger.entity()); + }); + + Self { + events: Default::default(), + entities: Default::default(), + } + } +}