Skip to content

Commit 7a2f98e

Browse files
ShaturUkoeHB
andauthored
Rework events (#262)
Events was implemented this way since the initial release of replicon (it was more then a year ago, time flies quickly!), but it have some flaws: - Public API for custom ser/de require user to define the whole sending and receiving functions which is not ergonomic. It's especially bad for server events where user need to use a few public helpers that we provided to reduce the boilerplate. - It creates a new set of systems for each replicon event. It's not very efficient and Bevy recently did [a nice optimization](bevyengine/bevy#12936) which we also can do. It won't be that noticeable since user register much less amount of replicon events, but nice to have. I think it's time to revisit the implementation and fix the mentioned flaws. Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
1 parent 3146286 commit 7a2f98e

27 files changed

+1559
-848
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Custom events are now registered with serialization and deserialization functions instead of systems. This makes the API more convenient since the purpose of custom systems was to customize serialization.
13+
- All events are processed in one system instead of a separate system for each event. Bevy does a [similar optimization](https://github.com/bevyengine/bevy/pull/12936) for event updates. It won't be that noticeable since users register much fewer replicon events.
14+
1015
## [0.25.3] - 2024-05-24
1116

1217
### Fixed

src/client.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ use varint_rs::VarintReader;
1313
use crate::core::{
1414
command_markers::{CommandMarkers, EntityMarkers},
1515
common_conditions::{client_connected, client_just_connected, client_just_disconnected},
16-
replication_fns::{
17-
ctx::{DespawnCtx, RemoveCtx, WriteCtx},
18-
ReplicationFns,
19-
},
16+
ctx::{DespawnCtx, RemoveCtx, WriteCtx},
17+
replication_fns::ReplicationFns,
2018
replicon_channels::{ReplicationChannel, RepliconChannels},
2119
replicon_tick::RepliconTick,
2220
Replicated,

src/client_event.rs

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
mod client_event_data;
2+
3+
use std::io::Cursor;
4+
5+
use bevy::{
6+
ecs::{entity::MapEntities, event::ManualEventReader},
7+
prelude::*,
8+
};
9+
use bincode::{DefaultOptions, Options};
10+
use serde::{de::DeserializeOwned, Serialize};
11+
12+
use crate::{
13+
client::{replicon_client::RepliconClient, server_entity_map::ServerEntityMap, ClientSet},
14+
core::{
15+
common_conditions::*,
16+
ctx::{ClientSendCtx, ServerReceiveCtx},
17+
replicon_channels::{RepliconChannel, RepliconChannels},
18+
ClientId,
19+
},
20+
server::{replicon_server::RepliconServer, ServerSet},
21+
};
22+
use client_event_data::ClientEventData;
23+
24+
/// An extension trait for [`App`] for creating client events.
25+
pub trait ClientEventAppExt {
26+
/// Registers [`FromClient<E>`] and `E` events.
27+
///
28+
/// [`FromClient<E>`] will be emitted on the server after sending `E` event on client.
29+
/// In listen-server mode `E` will be drained right after sending and re-emitted as
30+
/// [`FromClient<E>`] with [`ClientId::SERVER`](crate::core::ClientId::SERVER).
31+
///
32+
/// Can be called for events that were registered with [add_event](bevy::app::App::add_event).
33+
/// A duplicate registration for `E` won't be created.
34+
/// But be careful, since on listen servers all events `E` are drained,
35+
/// which could break other Bevy or third-party plugin systems that listen for `E`.
36+
///
37+
/// See also [`Self::add_client_event_with`] and the [corresponding section](../index.html#from-client-to-server)
38+
/// from the quick start guide.
39+
fn add_client_event<E: Event + Serialize + DeserializeOwned>(
40+
&mut self,
41+
channel: impl Into<RepliconChannel>,
42+
) -> &mut Self {
43+
self.add_client_event_with(channel, default_serialize::<E>, default_deserialize::<E>)
44+
}
45+
46+
/// Same as [`Self::add_client_event`], but additionally maps client entities to server inside the event before sending.
47+
///
48+
/// Always use it for events that contain entities.
49+
/// See also [`Self::add_client_event`].
50+
fn add_mapped_client_event<E: Event + Serialize + DeserializeOwned + MapEntities + Clone>(
51+
&mut self,
52+
channel: impl Into<RepliconChannel>,
53+
) -> &mut Self {
54+
self.add_client_event_with(
55+
channel,
56+
default_serialize_mapped::<E>,
57+
default_deserialize::<E>,
58+
)
59+
}
60+
61+
/**
62+
Same as [`Self::add_client_event`], but uses the specified functions for serialization and deserialization.
63+
64+
# Examples
65+
66+
Register an event with [`Box<dyn Reflect>`]:
67+
68+
```
69+
use std::io::Cursor;
70+
71+
use bevy::{
72+
prelude::*,
73+
reflect::serde::{ReflectSerializer, UntypedReflectDeserializer},
74+
};
75+
use bevy_replicon::{
76+
core::ctx::{ClientSendCtx, ServerReceiveCtx},
77+
prelude::*,
78+
};
79+
use bincode::{DefaultOptions, Options};
80+
use serde::de::DeserializeSeed;
81+
82+
let mut app = App::new();
83+
app.add_plugins((MinimalPlugins, RepliconPlugins));
84+
app.add_client_event_with::<ReflectEvent>(
85+
ChannelKind::Ordered,
86+
serialize_reflect,
87+
deserialize_reflect,
88+
);
89+
90+
fn serialize_reflect(
91+
ctx: &mut ClientSendCtx,
92+
event: &ReflectEvent,
93+
cursor: &mut Cursor<Vec<u8>>,
94+
) -> bincode::Result<()> {
95+
let serializer = ReflectSerializer::new(&*event.0, ctx.registry);
96+
DefaultOptions::new().serialize_into(cursor, &serializer)
97+
}
98+
99+
fn deserialize_reflect(
100+
ctx: &mut ServerReceiveCtx,
101+
cursor: &mut Cursor<&[u8]>,
102+
) -> bincode::Result<ReflectEvent> {
103+
let mut deserializer = bincode::Deserializer::with_reader(cursor, DefaultOptions::new());
104+
let reflect =
105+
UntypedReflectDeserializer::new(ctx.registry).deserialize(&mut deserializer)?;
106+
Ok(ReflectEvent(reflect))
107+
}
108+
109+
#[derive(Event)]
110+
struct ReflectEvent(Box<dyn Reflect>);
111+
```
112+
*/
113+
fn add_client_event_with<E: Event>(
114+
&mut self,
115+
channel: impl Into<RepliconChannel>,
116+
serialize: SerializeFn<E>,
117+
deserialize: DeserializeFn<E>,
118+
) -> &mut Self;
119+
}
120+
121+
impl ClientEventAppExt for App {
122+
fn add_client_event_with<E: Event>(
123+
&mut self,
124+
channel: impl Into<RepliconChannel>,
125+
serialize: SerializeFn<E>,
126+
deserialize: DeserializeFn<E>,
127+
) -> &mut Self {
128+
self.add_event::<E>()
129+
.add_event::<FromClient<E>>()
130+
.init_resource::<ClientEventReader<E>>();
131+
132+
let channel_id = self
133+
.world
134+
.resource_mut::<RepliconChannels>()
135+
.create_client_channel(channel.into());
136+
137+
self.world
138+
.resource_scope(|world, mut event_registry: Mut<ClientEventRegistry>| {
139+
event_registry.0.push(ClientEventData::new(
140+
world.components(),
141+
channel_id,
142+
serialize,
143+
deserialize,
144+
));
145+
});
146+
147+
self
148+
}
149+
}
150+
151+
pub struct ClientEventPlugin;
152+
153+
impl Plugin for ClientEventPlugin {
154+
fn build(&self, app: &mut App) {
155+
app.init_resource::<ClientEventRegistry>()
156+
.add_systems(
157+
PreUpdate,
158+
(
159+
Self::reset.in_set(ClientSet::ResetEvents),
160+
Self::receive
161+
.in_set(ServerSet::Receive)
162+
.run_if(server_running),
163+
),
164+
)
165+
.add_systems(
166+
PostUpdate,
167+
(
168+
Self::send.run_if(client_connected),
169+
Self::resend_locally.run_if(has_authority),
170+
)
171+
.chain()
172+
.in_set(ClientSet::Send),
173+
);
174+
}
175+
}
176+
177+
impl ClientEventPlugin {
178+
fn send(world: &mut World) {
179+
world.resource_scope(|world, mut client: Mut<RepliconClient>| {
180+
world.resource_scope(|world, registry: Mut<AppTypeRegistry>| {
181+
world.resource_scope(|world, entity_map: Mut<ServerEntityMap>| {
182+
world.resource_scope(|world, event_registry: Mut<ClientEventRegistry>| {
183+
let mut ctx = ClientSendCtx {
184+
entity_map: &entity_map,
185+
registry: &registry.read(),
186+
};
187+
188+
let world_cell = world.as_unsafe_world_cell();
189+
for event_data in &event_registry.0 {
190+
// SAFETY: both resources mutably borrowed uniquely.
191+
let (events, reader) = unsafe {
192+
let events = world_cell
193+
.get_resource_by_id(event_data.events_id())
194+
.expect("events shouldn't be removed");
195+
let reader = world_cell
196+
.get_resource_mut_by_id(event_data.reader_id())
197+
.expect("event reader shouldn't be removed");
198+
(events, reader)
199+
};
200+
201+
// SAFETY: passed pointers were obtained using this event data.
202+
unsafe {
203+
event_data.send(
204+
&mut ctx,
205+
&events,
206+
reader.into_inner(),
207+
&mut client,
208+
);
209+
}
210+
}
211+
});
212+
});
213+
});
214+
});
215+
}
216+
217+
fn receive(world: &mut World) {
218+
world.resource_scope(|world, mut server: Mut<RepliconServer>| {
219+
world.resource_scope(|world, registry: Mut<AppTypeRegistry>| {
220+
world.resource_scope(|world, event_registry: Mut<ClientEventRegistry>| {
221+
let mut ctx = ServerReceiveCtx {
222+
registry: &registry.read(),
223+
};
224+
225+
for event_data in &event_registry.0 {
226+
let client_events = world
227+
.get_resource_mut_by_id(event_data.client_events_id())
228+
.expect("client events shouldn't be removed");
229+
230+
// SAFETY: passed pointer was obtained using this event data.
231+
unsafe {
232+
event_data.receive(&mut ctx, client_events.into_inner(), &mut server)
233+
};
234+
}
235+
});
236+
});
237+
});
238+
}
239+
240+
fn resend_locally(world: &mut World) {
241+
world.resource_scope(|world, event_registry: Mut<ClientEventRegistry>| {
242+
let world_cell = world.as_unsafe_world_cell();
243+
for event_data in &event_registry.0 {
244+
// SAFETY: both resources mutably borrowed uniquely.
245+
let (client_events, events) = unsafe {
246+
let client_events = world_cell
247+
.get_resource_mut_by_id(event_data.client_events_id())
248+
.expect("client events shouldn't be removed");
249+
let events = world_cell
250+
.get_resource_mut_by_id(event_data.events_id())
251+
.expect("events shouldn't be removed");
252+
(client_events, events)
253+
};
254+
255+
// SAFETY: passed pointers were obtained using this event data.
256+
unsafe {
257+
event_data.resend_locally(client_events.into_inner(), events.into_inner())
258+
};
259+
}
260+
});
261+
}
262+
263+
fn reset(world: &mut World) {
264+
world.resource_scope(|world, event_registry: Mut<ClientEventRegistry>| {
265+
for event_data in &event_registry.0 {
266+
let events = world
267+
.get_resource_mut_by_id(event_data.events_id())
268+
.expect("events shouldn't be removed");
269+
270+
// SAFETY: passed pointer was obtained using this event data.
271+
unsafe { event_data.reset(events.into_inner()) };
272+
}
273+
});
274+
}
275+
}
276+
277+
/// Registered client events.
278+
#[derive(Resource, Default)]
279+
struct ClientEventRegistry(Vec<ClientEventData>);
280+
281+
/// Tracks read events for [`ClientEventPlugin::send`].
282+
///
283+
/// Unlike with server events, we don't always drain all events in [`ClientEventPlugin::resend_locally`].
284+
#[derive(Resource, Deref, DerefMut)]
285+
struct ClientEventReader<E: Event>(ManualEventReader<E>);
286+
287+
impl<E: Event> FromWorld for ClientEventReader<E> {
288+
fn from_world(world: &mut World) -> Self {
289+
let events = world.resource::<Events<E>>();
290+
Self(events.get_reader())
291+
}
292+
}
293+
294+
/// Signature of client event serialization functions.
295+
pub type SerializeFn<E> = fn(&mut ClientSendCtx, &E, &mut Cursor<Vec<u8>>) -> bincode::Result<()>;
296+
297+
/// Signature of client event deserialization functions.
298+
pub type DeserializeFn<E> = fn(&mut ServerReceiveCtx, &mut Cursor<&[u8]>) -> bincode::Result<E>;
299+
300+
/// Default event serialization function.
301+
pub fn default_serialize<E: Event + Serialize>(
302+
_ctx: &mut ClientSendCtx,
303+
event: &E,
304+
cursor: &mut Cursor<Vec<u8>>,
305+
) -> bincode::Result<()> {
306+
DefaultOptions::new().serialize_into(cursor, event)
307+
}
308+
309+
/// Like [`default_serialize`], but also maps entities.
310+
pub fn default_serialize_mapped<E: Event + MapEntities + Clone + Serialize>(
311+
ctx: &mut ClientSendCtx,
312+
event: &E,
313+
cursor: &mut Cursor<Vec<u8>>,
314+
) -> bincode::Result<()> {
315+
let mut event = event.clone();
316+
event.map_entities(ctx);
317+
DefaultOptions::new().serialize_into(cursor, &event)
318+
}
319+
320+
/// Default event deserialization function.
321+
pub fn default_deserialize<E: Event + DeserializeOwned>(
322+
_ctx: &mut ServerReceiveCtx,
323+
cursor: &mut Cursor<&[u8]>,
324+
) -> bincode::Result<E> {
325+
DefaultOptions::new().deserialize_from(cursor)
326+
}
327+
328+
/// An event indicating that a message from client was received.
329+
/// Emited only on server.
330+
#[derive(Clone, Copy, Event)]
331+
pub struct FromClient<T> {
332+
pub client_id: ClientId,
333+
pub event: T,
334+
}

0 commit comments

Comments
 (0)