bevy_replicon_matchbox/
lib.rs

1//! A simple transport intended only for examples.
2//! This transport does not implement any reliability or security features.
3//! DO NOT USE in a real project
4#![cfg_attr(docsrs, feature(doc_auto_cfg))]
5
6#[cfg(feature = "client")]
7mod client;
8#[cfg(feature = "server")]
9mod server;
10
11#[cfg(feature = "client")]
12pub use client::*;
13#[cfg(feature = "server")]
14pub use server::*;
15
16use bevy::{app::PluginGroupBuilder, prelude::*};
17use bevy_matchbox::MatchboxSocket;
18use bevy_matchbox::matchbox_socket::{ChannelConfig, Packet, PeerId};
19use bevy_replicon::postcard;
20use bevy_replicon::postcard::to_slice;
21use bevy_replicon::prelude::*;
22use bytes::Bytes;
23use serde::{Deserialize, Serialize};
24
25//Required to communicate which peer is the host before we start using replicon
26const SYSTEM_CHANNEL_ID: usize = 0;
27
28#[derive(Debug, Serialize, Deserialize, PartialEq)]
29enum SystemChannelMessage {
30    ConnectedToHost,
31    Disconnect,
32}
33
34
35/// Plugin group for all replicon example backend plugins.
36///
37/// Contains the following:
38/// * [`RepliconMatchboxServerPlugin`] - with feature `server`.
39/// * [`RepliconMatchboxClientPlugin`] - with feature `client`.
40pub struct RepliconMatchboxPlugins;
41
42impl PluginGroup for RepliconMatchboxPlugins {
43    fn build(self) -> PluginGroupBuilder {
44        let mut group = PluginGroupBuilder::start::<Self>();
45
46        #[cfg(feature = "server")]
47        {
48            group = group.add(RepliconMatchboxServerPlugin);
49        }
50
51        #[cfg(feature = "client")]
52        {
53            group = group.add(RepliconMatchboxClientPlugin);
54        }
55
56        group = group.add(RepliconMatchboxSharedPlugin);
57
58        group
59    }
60}
61
62pub(crate) trait RepliconChannelsExt<'a> {
63    type Iter: Iterator<Item = &'a Channel>;
64
65    fn all_channels(&'a self) -> Self::Iter;
66}
67
68impl<'a> RepliconChannelsExt<'a> for RepliconChannels {
69    type Iter = std::iter::Chain<std::slice::Iter<'a, Channel>, std::slice::Iter<'a, Channel>>;
70    fn all_channels(&'a self) -> Self::Iter {
71        self.server_channels()
72            .iter()
73            .chain(self.client_channels().iter())
74    }
75}
76
77struct RepliconMatchboxSharedPlugin;
78
79impl Plugin for RepliconMatchboxSharedPlugin {
80    fn build(&self, app: &mut App) {
81        app.add_systems(Last, cleanup_matchbox_socket_on_exit);
82    }
83}
84
85fn cleanup_matchbox_socket_on_exit(
86    mut exit_events: EventReader<AppExit>,
87    mut server: Option<ResMut<MatchboxHost>>,
88    mut client: Option<ResMut<MatchboxClient>>,
89) {
90    //seems not to work on all platforms
91    for _ in exit_events.read() {
92        if let Some(client) = &mut client {
93            client.socket.close();
94        }
95        if let Some(server) = &mut server {
96            error!("Closing matchbox socket");
97            server.socket.close();
98            server.client_entities.clear();
99        }
100    }
101}
102
103fn create_matchbox_socket(
104    room_url: impl Into<String>,
105    replicon_channels: &RepliconChannels,
106) -> MatchboxSocket {
107    let mut web_rtc_socket = bevy_matchbox::matchbox_socket::WebRtcSocketBuilder::new(room_url);
108    //add system channel
109    web_rtc_socket = web_rtc_socket.add_reliable_channel();
110    for &channel in replicon_channels.all_channels() {
111        match channel {
112            Channel::Unreliable => {
113                web_rtc_socket = web_rtc_socket.add_unreliable_channel();
114            }
115            Channel::Unordered => {
116                web_rtc_socket = web_rtc_socket.add_channel(ChannelConfig {
117                    ordered: false,
118                    max_retransmits: None,
119                });
120            }
121            Channel::Ordered => {
122                web_rtc_socket = web_rtc_socket.add_reliable_channel();
123            }
124        };
125    }
126    let socket = web_rtc_socket.build();
127    MatchboxSocket::from(socket)
128}
129
130fn uuid_to_u64_truncated(peer_id: PeerId) -> u64 {
131    let bytes = peer_id.0.as_bytes();
132    u64::from_le_bytes(bytes[0..8].try_into().unwrap())
133}
134
135///Marker added as matchbox seems to drop 0 sized packages
136fn add_marker(data: &[u8]) -> Packet {
137    let mut payload = Vec::with_capacity(data.len() + 1);
138    payload.push(0);
139    payload.extend_from_slice(data);
140    payload.into()
141}
142
143///Marker stripped as matchbox seems to drop 0 sized packages
144fn strip_marker(packet: &[u8]) -> Bytes {
145    Bytes::copy_from_slice(&packet[1..])
146}
147
148
149
150fn to_packet<'a, T: Serialize>(msg: &T, buf: &'a mut [u8]) -> &'a [u8] {
151    to_slice(msg, buf).expect("serialize failed")
152}
153
154fn from_packet<'a, T: Deserialize<'a>>(data: &'a [u8]) -> Result<T, postcard::Error> {
155    postcard::from_bytes(data)
156}
157
158
159#[test]
160fn test_packaging() {
161    let messages = [SystemChannelMessage::ConnectedToHost, SystemChannelMessage::Disconnect];
162    for msg in messages.iter() {
163        let msg = SystemChannelMessage::ConnectedToHost;
164        let mut buf = [0u8; 1];
165        let p = to_packet(&msg, &mut buf);
166        let deserialized: SystemChannelMessage = from_packet(&*p).unwrap();
167        assert_eq!(msg, deserialized);
168    }
169
170}