Skip to content

Commit

Permalink
Independent events [skip ci] (#314)
Browse files Browse the repository at this point in the history
Co-authored-by: Hennadii Chernyshchyk <[email protected]>
Co-authored-by: UkoeHB <[email protected]>
  • Loading branch information
3 people authored Jul 31, 2024
1 parent 899783d commit 4769df6
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `ServerEventAppExt::make_independent` to let events be triggered without waiting for replication on the same tick.

## [0.27.0] - 2024-07-04

### Changed
Expand Down
13 changes: 12 additions & 1 deletion src/core/event_registry.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub(crate) mod client_event;
pub(crate) mod server_event;

use bevy::prelude::*;
use bevy::{ecs::component::ComponentId, prelude::*};
use client_event::ClientEvent;
use server_event::ServerEvent;

Expand All @@ -21,6 +21,17 @@ impl EventRegistry {
self.client.push(event_data);
}

pub(crate) fn make_independent(&mut self, events_id: ComponentId) {
let event = self
.server
.iter_mut()
.find(|event| event.events_id() == events_id)
.unwrap_or_else(|| {
panic!("event with ID {events_id:?} should be previously registered");
});
event.make_independent();
}

pub(crate) fn iter_server_events(&self) -> impl Iterator<Item = &ServerEvent> {
self.server.iter()
}
Expand Down
63 changes: 62 additions & 1 deletion src/core/event_registry/server_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,27 @@ pub trait ServerEventAppExt {
serialize: SerializeFn<E>,
deserialize: DeserializeFn<E>,
) -> &mut Self;

/// Marks the event `E` as an independent event.
///
/// By default, all events from the server are buffered until all
/// insertions, removals and despawns (value changes doesn't count) are
/// replicated for the tick on which the event was triggered. This is
/// necessary to ensure that the executed logic during the event does not
/// affect components or entities that the client has not yet received.
///
/// However, if you know your event doesn't rely on that, you can mark it
/// as independent to always emit it immediately. For example, a chat
/// message event - which does not hold references to any entities - may be
/// marked as independent.
///
/// <div class="warning">
///
/// Use this method very carefully; it can lead to logic errors that are
/// very difficult to debug!
///
/// </div>
fn make_independent<E: Event>(&mut self) -> &mut Self;
}

impl ServerEventAppExt for App {
Expand Down Expand Up @@ -150,6 +171,24 @@ impl ServerEventAppExt for App {

self
}

fn make_independent<E: Event>(&mut self) -> &mut Self {
self.world_mut()
.resource_scope(|world, mut event_registry: Mut<EventRegistry>| {
let events_id = world
.components()
.resource_id::<Events<E>>()
.unwrap_or_else(|| {
panic!(
"event `{}` should be previously registered",
any::type_name::<E>()
)
});
event_registry.make_independent(events_id);
});

self
}
}

/// Type-erased functions and metadata for a registered server event.
Expand All @@ -159,6 +198,13 @@ pub(crate) struct ServerEvent {
type_id: TypeId,
type_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
/// be synced. If set to `true`, the event will always be applied
/// immediately.
independent: bool,

/// ID of [`Events<E>`].
events_id: ComponentId,

Expand Down Expand Up @@ -213,6 +259,7 @@ impl ServerEvent {
Self {
type_id: TypeId::of::<E>(),
type_name: any::type_name::<E>(),
independent: false,
events_id,
server_events_id,
queue_id,
Expand All @@ -238,6 +285,14 @@ impl ServerEvent {
self.queue_id
}

pub(crate) fn is_independent(&self) -> bool {
self.independent
}

pub(crate) fn make_independent(&mut self) {
self.independent = true
}

/// Sends an event to client(s).
///
/// # Safety
Expand Down Expand Up @@ -414,7 +469,13 @@ unsafe fn receive<E: Event>(
let (tick, event) = deserialize_with(ctx, event_data, &mut cursor)
.expect("server should send valid events");

if tick <= init_tick {
if event_data.is_independent() {
trace!(
"applying independent event `{}` with `{tick:?}`",
any::type_name::<E>()
);
events.send(event);
} else if tick <= init_tick {
trace!("applying event `{}` with `{tick:?}`", any::type_name::<E>());
events.send(event);
} else {
Expand Down
10 changes: 5 additions & 5 deletions src/core/replicon_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ use crate::core::ClientId;
///
/// The messaging backend is responsible for updating this resource:
/// - When the messaging client changes its status (connected, connecting and disconnected),
/// [`Self::set_status`] should be used to reflect this.
/// [`Self::set_status`] should be used to reflect this.
/// - For receiving messages, [`Self::insert_received`] should be to used.
/// A system to forward backend messages to Replicon should run in
/// [`ClientSet::ReceivePackets`](crate::client::ClientSet::ReceivePackets).
/// A system to forward backend messages to Replicon should run in
/// [`ClientSet::ReceivePackets`](crate::client::ClientSet::ReceivePackets).
/// - For sending messages, [`Self::drain_sent`] should be used to drain all sent messages.
/// A system to forward Replicon messages to the backend should run in
/// [`ClientSet::SendPackets`](crate::client::ClientSet::SendPackets).
/// A system to forward Replicon messages to the backend should run in
/// [`ClientSet::SendPackets`](crate::client::ClientSet::SendPackets).
///
/// Inserted as resource by [`ClientPlugin`](crate::client::ClientPlugin).
#[derive(Resource, Default)]
Expand Down
4 changes: 2 additions & 2 deletions src/core/replicon_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use crate::core::ClientId;
/// The messaging backend is responsible for updating this resource:
/// - When the server is started or stopped, [`Self::set_running`] should be used to reflect this.
/// - For receiving messages, [`Self::insert_received`] should be used.
/// A system to forward messages from the backend to Replicon should run in [`ServerSet::ReceivePackets`](crate::server::ServerSet::ReceivePackets).
/// A system to forward messages from the backend to Replicon should run in [`ServerSet::ReceivePackets`](crate::server::ServerSet::ReceivePackets).
/// - For sending messages, [`Self::drain_sent`] should be used to drain all sent messages.
/// A system to forward messages from Replicon to the backend should run in [`ServerSet::SendPackets`](crate::server::ServerSet::SendPackets).
/// A system to forward messages from Replicon to the backend should run in [`ServerSet::SendPackets`](crate::server::ServerSet::SendPackets).
///
/// Inserted as resource by [`ServerPlugin`](crate::server::ServerPlugin).
#[derive(Resource, Default)]
Expand Down
45 changes: 45 additions & 0 deletions tests/server_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,51 @@ fn event_queue() {
assert_eq!(client_app.world().resource::<Events<DummyEvent>>().len(), 1);
}

#[test]
fn independent_event() {
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()
}),
))
.replicate::<DummyComponent>()
.add_server_event::<DummyEvent>(ChannelKind::Ordered)
.make_independent::<DummyEvent>();
}

server_app.connect_client(&mut client_app);

// Spawn entity to trigger world change.
server_app.world_mut().spawn((Replicated, DummyComponent));

server_app.update();
server_app.exchange_with_client(&mut client_app);
client_app.update();
server_app.exchange_with_client(&mut client_app);

// Artificially reset the init tick.
// Normal events would be queued and not triggered yet,
// but our independent event should be triggered immediately.
*client_app.world_mut().resource_mut::<ServerInitTick>() = Default::default();
server_app.world_mut().send_event(ToClients {
mode: SendMode::Broadcast,
event: DummyEvent,
});

server_app.update();
server_app.exchange_with_client(&mut client_app);
client_app.update();

// Event should have already been triggered, even without resetting the tick,
// since it's independent.
assert_eq!(client_app.world().resource::<Events<DummyEvent>>().len(), 1);
}

#[test]
fn different_ticks() {
let mut server_app = App::new();
Expand Down

0 comments on commit 4769df6

Please sign in to comment.