use crate::messages::{Broadcast, NetworkQueue, PeerStateQueue};
use crate::systems;
use bevy::prelude::*;
use bevy_matchbox::prelude::*;
use matchbox_socket::RtcIceServerConfig;
use serde::{Serialize, de::DeserializeOwned};
use std::marker::PhantomData;
#[derive(Resource, Debug, Clone)]
pub struct SymbiosMultiuserConfig<T> {
pub room_url: String,
pub ice_servers: Option<RtcIceServerConfig>,
#[doc(hidden)]
pub _marker: PhantomData<T>,
}
#[cfg(feature = "client")]
#[derive(Resource)]
struct SocketOpened<T> {
room_url: String,
ice_key: Option<(Vec<String>, Option<String>, Option<String>)>,
_marker: PhantomData<T>,
}
#[cfg(feature = "client")]
fn ice_key(
ice: &Option<matchbox_socket::RtcIceServerConfig>,
) -> Option<(Vec<String>, Option<String>, Option<String>)> {
ice.as_ref()
.map(|c| (c.urls.clone(), c.username.clone(), c.credential.clone()))
}
pub struct SymbiosMultiuserPlugin<T> {
config: Option<SymbiosMultiuserConfig<T>>,
_marker: PhantomData<T>,
}
impl<T> SymbiosMultiuserPlugin<T> {
pub fn new(room_url: impl Into<String>) -> Self {
Self {
config: Some(SymbiosMultiuserConfig {
room_url: room_url.into(),
ice_servers: None,
_marker: PhantomData,
}),
_marker: PhantomData,
}
}
pub fn with_config(config: SymbiosMultiuserConfig<T>) -> Self {
Self {
config: Some(config),
_marker: PhantomData,
}
}
pub fn deferred() -> Self {
Self {
config: None,
_marker: PhantomData,
}
}
}
#[cfg(feature = "client")]
impl<T> Plugin for SymbiosMultiuserPlugin<T>
where
T: Serialize + DeserializeOwned + Send + Sync + 'static + std::fmt::Debug + Clone,
{
fn build(&self, app: &mut App) {
app.init_resource::<NetworkQueue<T>>()
.init_resource::<PeerStateQueue<T>>()
.add_message::<Broadcast<T>>()
.add_systems(
Update,
(
open_socket::<T>,
(
systems::poll_peers::<T>,
systems::receive_messages::<T>,
systems::transmit_messages::<T>,
)
.chain()
.run_if(resource_exists::<MatchboxSocket>),
)
.chain(),
);
if let Some(ref config) = self.config {
app.insert_resource(config.clone());
}
}
}
#[cfg(feature = "client")]
fn open_socket<T: Send + Sync + 'static>(
mut commands: Commands,
config: Option<Res<SymbiosMultiuserConfig<T>>>,
opened: Option<Res<SocketOpened<T>>>,
socket: Option<Res<MatchboxSocket>>,
#[cfg(feature = "client")] token_source: Option<Res<crate::signaller::TokenSourceRes>>,
) {
if let Some(ref marker) = opened {
let needs_teardown = match config.as_ref() {
None => true,
Some(cfg) => {
cfg.room_url != marker.room_url || ice_key(&cfg.ice_servers) != marker.ice_key
}
};
if needs_teardown {
tracing::info!("tearing down socket (config removed or room changed)");
commands.remove_resource::<SocketOpened<T>>();
if socket.is_some() {
commands.remove_resource::<MatchboxSocket>();
}
return;
}
if socket.is_none() {
tracing::info!("socket was lost while config unchanged, clearing marker for reconnect");
commands.remove_resource::<SocketOpened<T>>();
}
return;
}
let Some(config) = config else {
return;
};
let mut builder = WebRtcSocketBuilder::new(&config.room_url)
.add_channel(ChannelConfig::reliable())
.add_channel(ChannelConfig::unreliable());
if let Some(ref ice) = config.ice_servers {
builder = builder.ice_server(ice.clone());
}
#[cfg(feature = "client")]
{
let signaller = if let Some(ts) = token_source {
crate::signaller::signaller_with_token_source(ts.0.clone())
} else {
crate::signaller::signaller_anonymous()
};
builder = builder.signaller_builder(signaller);
}
commands.open_socket(builder);
commands.insert_resource(SocketOpened::<T> {
room_url: config.room_url.clone(),
ice_key: ice_key(&config.ice_servers),
_marker: PhantomData,
});
}