Skip to content

Commit

Permalink
Add signalling utils to bevy_matchbox (#215)
Browse files Browse the repository at this point in the history
* Move socket extensions to own module

* Tidy socket docs

* Make matchbox_signaling topologies public

* Add bevy_matchbox signaling feature

* Move task handles to components / resources

* Feature gate bevy signaling server
  • Loading branch information
garryod authored Jun 23, 2023
1 parent 824f88c commit 8e864f1
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 169 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions bevy_matchbox/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ readme = "../README.md"

[features]
ggrs = ["matchbox_socket/ggrs"]
signaling = ["matchbox_signaling"]

[dependencies]
bevy = { version = "0.10", default-features = false }
matchbox_socket = { path = "../matchbox_socket", version = "0.6" }
cfg-if = "1.0"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
matchbox_signaling = { path = "../matchbox_signaling", version = "0.6", optional = true }
178 changes: 13 additions & 165 deletions bevy_matchbox/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,183 +2,31 @@
#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]

use bevy::{
ecs::system::Command,
prelude::{Commands, Component, Deref, DerefMut, Resource, World},
tasks::IoTaskPool,
};
pub use matchbox_socket;
use matchbox_socket::{
BuildablePlurality, MessageLoopFuture, SingleChannel, WebRtcSocket, WebRtcSocketBuilder,
};
use std::marker::PhantomData;
use cfg_if::cfg_if;

/// A [`WebRtcSocket`] as a [`Component`] or [`Resource`].
///
/// As a [`Component`], directly
/// ```
/// use bevy_matchbox::prelude::*;
/// use bevy::prelude::*;
///
/// fn open_socket_system(mut commands: Commands) {
/// let room_url = "wss://matchbox.example.com";
/// let builder = WebRtcSocketBuilder::new(room_url).add_channel(ChannelConfig::reliable());
/// commands.spawn(MatchboxSocket::from(builder));
/// }
///
/// fn close_socket_system(
/// mut commands: Commands,
/// socket: Query<Entity, With<MatchboxSocket<SingleChannel>>>
/// ) {
/// let socket = socket.single();
/// commands.entity(socket).despawn();
/// }
/// ```
///
/// As a [`Resource`], with [`Commands`]
/// ```
/// # use bevy_matchbox::prelude::*;
/// # use bevy::prelude::*;
/// fn open_socket_system(mut commands: Commands) {
/// let room_url = "wss://matchbox.example.com";
/// commands.open_socket(WebRtcSocketBuilder::new(room_url).add_channel(ChannelConfig::reliable()));
/// }
///
/// fn close_socket_system(mut commands: Commands) {
/// commands.close_socket::<SingleChannel>();
/// }
/// ```
///
/// As a [`Resource`], directly
///
/// ```
/// # use bevy_matchbox::prelude::*;
/// # use bevy::prelude::*;
/// fn open_socket_system(mut commands: Commands) {
/// let room_url = "wss://matchbox.example.com";
///
/// let socket: MatchboxSocket<SingleChannel> = MatchboxSocket::new_reliable(room_url);
///
/// commands.insert_resource(socket);
/// }
///
/// fn close_socket_system(mut commands: Commands) {
/// commands.remove_resource::<MatchboxSocket<SingleChannel>>();
/// }
/// ```
#[derive(Resource, Component, Debug, Deref, DerefMut)]
pub struct MatchboxSocket<C: BuildablePlurality>(WebRtcSocket<C>);
mod socket;
pub use socket::*;

impl<C: BuildablePlurality> From<WebRtcSocketBuilder<C>> for MatchboxSocket<C> {
fn from(builder: WebRtcSocketBuilder<C>) -> Self {
Self::from(builder.build())
}
}

impl<C: BuildablePlurality> From<(WebRtcSocket<C>, MessageLoopFuture)> for MatchboxSocket<C> {
fn from((socket, message_loop_fut): (WebRtcSocket<C>, MessageLoopFuture)) -> Self {
let task_pool = IoTaskPool::get();
task_pool.spawn(message_loop_fut).detach();
MatchboxSocket(socket)
}
}

/// A [`Command`] used to open a [`MatchboxSocket`] and allocate it as a resource.
struct OpenSocket<C: BuildablePlurality>(WebRtcSocketBuilder<C>);

impl<C: BuildablePlurality + 'static> Command for OpenSocket<C> {
fn write(self, world: &mut World) {
world.insert_resource(MatchboxSocket::from(self.0));
}
}

/// A [`Commands`] extension used to open a [`MatchboxSocket`] and allocate it as a resource.
pub trait OpenSocketExt<C: BuildablePlurality> {
/// Opens a [`MatchboxSocket`] and allocates it as a resource.
fn open_socket(&mut self, socket_builder: WebRtcSocketBuilder<C>);
}

impl<'w, 's, C: BuildablePlurality + 'static> OpenSocketExt<C> for Commands<'w, 's> {
fn open_socket(&mut self, socket_builder: WebRtcSocketBuilder<C>) {
self.add(OpenSocket(socket_builder))
}
}

/// A [`Command`] used to close a [`WebRtcSocket`], deleting the [`MatchboxSocket`] resource.
struct CloseSocket<C: BuildablePlurality>(PhantomData<C>);

impl<C: BuildablePlurality + 'static> Command for CloseSocket<C> {
fn write(self, world: &mut World) {
world.remove_resource::<MatchboxSocket<C>>();
}
}

/// A [`Commands`] extension used to close a [`WebRtcSocket`], deleting the [`MatchboxSocket`]
/// resource.
pub trait CloseSocketExt {
/// Delete the [`MatchboxSocket`] resource.
fn close_socket<C: BuildablePlurality + 'static>(&mut self);
}

impl<'w, 's> CloseSocketExt for Commands<'w, 's> {
fn close_socket<C: BuildablePlurality + 'static>(&mut self) {
self.add(CloseSocket::<C>(PhantomData::default()))
cfg_if! {
if #[cfg(all(not(target_arch = "wasm32"), feature = "signaling"))] {
mod signaling;
pub use signaling::*;
}
}

/// use `bevy_matchbox::prelude::*;` to import common resources and commands
pub mod prelude {
pub use crate::{CloseSocketExt, MatchboxSocket, OpenSocketExt};
use cfg_if::cfg_if;
pub use matchbox_socket::{
BuildablePlurality, ChannelConfig, MultipleChannels, PeerId, PeerState, SingleChannel,
WebRtcSocketBuilder,
};
}

impl MatchboxSocket<SingleChannel> {
/// Create a new socket with a single unreliable channel
///
/// ```rust
/// # use bevy_matchbox::prelude::*;
/// # use bevy::prelude::*;
/// # fn open_channel_system(mut commands: Commands) {
/// let room_url = "wss://matchbox.example.com";
/// let socket = MatchboxSocket::new_unreliable(room_url);
/// commands.spawn(socket);
/// # }
/// ```
pub fn new_unreliable(room_url: impl Into<String>) -> MatchboxSocket<SingleChannel> {
Self::from(WebRtcSocket::new_unreliable(room_url))
}

/// Create a new socket with a single reliable channel
///
/// ```rust
/// # use bevy_matchbox::prelude::*;
/// # use bevy::prelude::*;
/// # fn open_channel_system(mut commands: Commands) {
/// let room_url = "wss://matchbox.example.com";
/// let socket = MatchboxSocket::new_reliable(room_url);
/// commands.spawn(socket);
/// # }
/// ```
pub fn new_reliable(room_url: impl Into<String>) -> MatchboxSocket<SingleChannel> {
Self::from(WebRtcSocket::new_reliable(room_url))
}

/// Create a new socket with a single ggrs-compatible channel
///
/// ```rust
/// # use bevy_matchbox::prelude::*;
/// # use bevy::prelude::*;
/// # fn open_channel_system(mut commands: Commands) {
/// let room_url = "wss://matchbox.example.com";
/// let socket = MatchboxSocket::new_ggrs(room_url);
/// commands.spawn(socket);
/// # }
/// ```
#[cfg(feature = "ggrs")]
pub fn new_ggrs(room_url: impl Into<String>) -> MatchboxSocket<SingleChannel> {
Self::from(WebRtcSocket::new_ggrs(room_url))
cfg_if! {
if #[cfg(all(not(target_arch = "wasm32"), feature = "signaling"))] {
pub use crate::signaling::{MatchboxServer, StartServerExt, StopServerExt};
pub use matchbox_signaling::SignalingServerBuilder;
}
}
}
161 changes: 161 additions & 0 deletions bevy_matchbox/src/signaling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use std::net::SocketAddr;

use bevy::{
ecs::system::Command,
prelude::{Commands, Resource},
tasks::{IoTaskPool, Task},
};
pub use matchbox_signaling;
use matchbox_signaling::{
topologies::{
client_server::{ClientServer, ClientServerCallbacks, ClientServerState},
full_mesh::{FullMesh, FullMeshCallbacks, FullMeshState},
SignalingTopology,
},
Error, SignalingCallbacks, SignalingServer, SignalingServerBuilder, SignalingState,
};

/// A [`SignalingServer`] as a [`Resource`].
///
/// As a [`Resource`], with [`Commands`]
/// ```
/// use std::net::Ipv4Addr;
/// use bevy_matchbox::{
/// prelude::*,
/// matchbox_signaling::topologies::full_mesh::{FullMesh, FullMeshState}
/// };
/// use bevy::prelude::*;
///
/// fn start_server_system(mut commands: Commands) {
/// let builder = SignalingServerBuilder::new(
/// (Ipv4Addr::UNSPECIFIED, 3536),
/// FullMesh,
/// FullMeshState::default(),
/// );
/// commands.start_server(builder);
/// }
///
/// fn stop_server_system(mut commands: Commands) {
/// commands.stop_server();
/// }
/// ```
///
/// As a [`Resource`], directly
/// ```
/// use std::net::Ipv4Addr;
/// use bevy_matchbox::{
/// prelude::*,
/// matchbox_signaling::topologies::full_mesh::{FullMesh, FullMeshState}
/// };
/// use bevy::prelude::*;
///
/// fn start_server_system(mut commands: Commands) {
/// let server: MatchboxServer = SignalingServerBuilder::new(
/// (Ipv4Addr::UNSPECIFIED, 3536),
/// FullMesh,
/// FullMeshState::default(),
/// ).into();
///
/// commands.insert_resource(MatchboxServer::from(server));
/// }
///
/// fn stop_server_system(mut commands: Commands) {
/// commands.remove_resource::<MatchboxServer>();
/// }
/// ```
#[derive(Debug, Resource)]
pub struct MatchboxServer(Task<Result<(), Error>>);

impl<Topology, Cb, S> From<SignalingServerBuilder<Topology, Cb, S>> for MatchboxServer
where
Topology: SignalingTopology<Cb, S>,
Cb: SignalingCallbacks,
S: SignalingState,
{
fn from(value: SignalingServerBuilder<Topology, Cb, S>) -> Self {
MatchboxServer::from(value.build())
}
}

impl From<SignalingServer> for MatchboxServer {
fn from(server: SignalingServer) -> Self {
let task_pool = IoTaskPool::get();
let task = task_pool.spawn(server.serve());
MatchboxServer(task)
}
}

struct StartServer<Topology, Cb, S>(SignalingServerBuilder<Topology, Cb, S>)
where
Topology: SignalingTopology<Cb, S>,
Cb: SignalingCallbacks,
S: SignalingState;

impl<Topology, Cb, S> Command for StartServer<Topology, Cb, S>
where
Topology: SignalingTopology<Cb, S> + Send + 'static,
Cb: SignalingCallbacks,
S: SignalingState,
{
fn write(self, world: &mut bevy::prelude::World) {
world.insert_resource(MatchboxServer::from(self.0))
}
}

/// A [`Commands`] extension used to start a [`MatchboxServer`].
pub trait StartServerExt<
Topology: SignalingTopology<Cb, S>,
Cb: SignalingCallbacks,
S: SignalingState,
>
{
/// Starts a [`MatchboxServer`] and allocates it as a resource.
fn start_server(&mut self, builder: SignalingServerBuilder<Topology, Cb, S>);
}

impl<'w, 's, Topology, Cb, S> StartServerExt<Topology, Cb, S> for Commands<'w, 's>
where
Topology: SignalingTopology<Cb, S> + Send + 'static,
Cb: SignalingCallbacks,
S: SignalingState,
{
fn start_server(&mut self, builder: SignalingServerBuilder<Topology, Cb, S>) {
self.add(StartServer(builder))
}
}

struct StopServer;

impl Command for StopServer {
fn write(self, world: &mut bevy::prelude::World) {
world.remove_resource::<MatchboxServer>();
}
}

/// A [`Commands`] extension used to stop a [`MatchboxServer`].
pub trait StopServerExt {
/// Delete the [`MatchboxServer`] resource.
fn stop_server(&mut self);
}

impl<'w, 's> StopServerExt for Commands<'w, 's> {
fn stop_server(&mut self) {
self.add(StopServer)
}
}

impl MatchboxServer {
/// Creates a new builder for a [`SignalingServer`] with full-mesh topology.
pub fn full_mesh_builder(
socket_addr: impl Into<SocketAddr>,
) -> SignalingServerBuilder<FullMesh, FullMeshCallbacks, FullMeshState> {
SignalingServer::full_mesh_builder(socket_addr)
}

/// Creates a new builder for a [`SignalingServer`] with client-server topology.
pub fn client_server_builder(
socket_addr: impl Into<SocketAddr>,
) -> SignalingServerBuilder<ClientServer, ClientServerCallbacks, ClientServerState> {
SignalingServer::client_server_builder(socket_addr)
}
}
Loading

0 comments on commit 8e864f1

Please sign in to comment.