use alloc::{format, string::String};
use bevy_app::{App, Plugin};
use bevy_ecs::lifecycle::HookContext;
use bevy_ecs::prelude::*;
use bevy_ecs::{reflect::ReflectResource, world::DeferredWorld};
use bevy_platform::collections::HashMap;
use bevy_reflect::Reflect;
use lightyear_core::id::{PeerId, RemoteId};
use lightyear_link::LinkStart;
use lightyear_link::prelude::{Server, Unlinked};
#[allow(unused_imports)]
use tracing::{info, trace};
#[derive(thiserror::Error, Debug)]
pub enum ConnectionError {
#[error("io is not initialized")]
IoNotInitialized,
#[error("connection not found")]
NotFound,
#[error("client is not connected")]
NotConnected,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub enum ClientState {
Connected,
Connecting,
Disconnecting,
#[default]
Disconnected,
}
#[derive(Component, Default, Reflect)]
pub struct Client {
pub state: ClientState,
}
#[derive(EntityEvent)]
pub struct Connect {
pub entity: Entity,
}
#[derive(EntityEvent)]
pub struct Disconnect {
pub entity: Entity,
}
#[derive(Component, Debug, Reflect)]
#[component(on_add = Connected::on_add)]
pub struct Connected;
impl Connected {
fn on_add(mut world: DeferredWorld, context: HookContext) {
let peer_id = world
.get::<RemoteId>(context.entity)
.unwrap_or_else(|| {
panic!(
"A Connected entity ({:?}) must always have a RemoteId component",
context.entity
)
})
.0;
if let Some(mut client) = world.get_mut::<Client>(context.entity) {
client.state = ClientState::Connected;
};
world
.commands()
.entity(context.entity)
.remove::<(Connecting, Disconnected)>();
if let Some(mut metadata) = world.get_resource_mut::<PeerMetadata>() {
metadata.mapping.insert(peer_id, context.entity);
}
}
}
#[derive(Component, Default, Debug, Reflect)]
#[component(on_add = Connecting::on_add)]
pub struct Connecting;
impl Connecting {
fn on_add(mut world: DeferredWorld, context: HookContext) {
if let Some(mut client) = world.get_mut::<Client>(context.entity) {
client.state = ClientState::Connecting;
}
world
.commands()
.entity(context.entity)
.remove::<(Connected, Disconnecting, Disconnected)>();
}
}
#[derive(Component, Default, Debug, Reflect)]
#[component(on_add = Disconnected::on_add)]
pub struct Disconnected {
pub reason: Option<String>,
}
impl Disconnected {
fn on_add(mut world: DeferredWorld, context: HookContext) {
if let Some(mut client) = world.get_mut::<Client>(context.entity) {
client.state = ClientState::Disconnected;
}
if let Some(peer_id) = world.get::<RemoteId>(context.entity).map(|c| c.0) {
world
.resource_mut::<PeerMetadata>()
.mapping
.remove(&peer_id);
}
world
.commands()
.entity(context.entity)
.remove::<(Connecting, Disconnecting, Connected)>();
}
}
#[derive(Component, Default, Debug, Reflect)]
#[component(on_add = Disconnecting::on_add)]
pub struct Disconnecting;
impl Disconnecting {
fn on_add(mut world: DeferredWorld, context: HookContext) {
if let Some(mut client) = world.get_mut::<Client>(context.entity) {
client.state = ClientState::Disconnecting;
}
world
.commands()
.entity(context.entity)
.remove::<(Connected, Connecting, Disconnected)>();
}
}
#[derive(Resource, Debug, Default, Reflect)]
#[reflect(Resource)]
pub struct PeerMetadata {
pub mapping: HashMap<PeerId, Entity>,
}
pub struct ConnectionPlugin;
impl ConnectionPlugin {
fn connect(connect: On<Connect>, mut commands: Commands) {
trace!("Triggering LinkStart because Connect was triggered");
commands.trigger(LinkStart {
entity: connect.entity,
});
}
fn disconnect_if_link_fails(
trigger: On<Add, Unlinked>,
query: Query<&Unlinked, (Without<Disconnected>, Without<Server>)>,
mut commands: Commands,
) {
if let Ok(unlinked) = query.get(trigger.entity) {
trace!(
entity = ?trigger.entity,
"Adding Disconnected because the link got Unlinked (reason: {:?})",
unlinked.reason
);
commands.entity(trigger.entity).insert(Disconnected {
reason: Some(format!("Link failed: {:?}", unlinked.reason)),
});
}
}
}
impl Plugin for ConnectionPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<PeerMetadata>();
app.add_observer(Self::connect);
app.add_observer(Self::disconnect_if_link_fails);
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_connection() {}
}