#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
mod conditioner;
pub mod server;
use alloc::{collections::vec_deque::Drain, string::String};
pub use crate::conditioner::LinkConditioner;
use alloc::collections::VecDeque;
use bevy_app::{App, Plugin, PostUpdate, PreUpdate};
use bevy_ecs::lifecycle::HookContext;
use bevy_ecs::prelude::*;
use bevy_ecs::world::DeferredWorld;
use bytes::Bytes;
use core::time::Duration;
use lightyear_core::time::Instant;
pub mod prelude {
pub use crate::conditioner::LinkConditionerConfig;
pub use crate::server::{LinkOf, Server};
pub use crate::{
Link, LinkStart, LinkStats, LinkSystems, Linked, Linking, RecvLinkConditioner, Unlink,
Unlinked,
};
pub mod server {
pub use crate::server::{LinkOf, Server};
}
}
pub type RecvPayload = Bytes;
pub type SendPayload = Bytes;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkState {
Linked,
Linking,
#[default]
Unlinked,
}
#[derive(Component, Default)]
pub struct Link {
pub recv: LinkReceiver,
pub send: LinkSender,
pub state: LinkState,
pub stats: LinkStats,
}
pub type RecvLinkConditioner = LinkConditioner<RecvPayload>;
impl Link {
pub fn new(recv_conditioner: Option<RecvLinkConditioner>) -> Self {
Self {
recv: LinkReceiver {
buffer: VecDeque::new(),
conditioner: recv_conditioner,
},
send: LinkSender::default(),
state: Default::default(),
stats: LinkStats::default(),
}
}
}
#[derive(Default)]
pub struct LinkReceiver {
buffer: VecDeque<RecvPayload>,
pub conditioner: Option<LinkConditioner<RecvPayload>>,
}
impl LinkReceiver {
pub fn drain(&mut self) -> Drain<'_, RecvPayload> {
self.buffer.drain(..)
}
pub fn pop(&mut self) -> Option<RecvPayload> {
self.buffer.pop_front()
}
pub fn push_raw(&mut self, value: RecvPayload) {
self.buffer.push_back(value);
}
pub fn push(&mut self, value: RecvPayload, instant: Instant) {
if let Some(conditioner) = &mut self.conditioner {
conditioner.condition_packet(value, instant);
} else {
self.push_raw(value);
}
}
pub fn len(&self) -> usize {
self.buffer.len()
}
#[cfg(feature = "test_utils")]
pub fn iter(&self) -> impl Iterator<Item = &SendPayload> {
self.buffer.iter()
}
}
#[derive(Default)]
pub struct LinkSender(VecDeque<SendPayload>);
impl LinkSender {
pub fn drain(&mut self) -> Drain<'_, SendPayload> {
self.0.drain(..)
}
pub fn pop(&mut self) -> Option<SendPayload> {
self.0.pop_front()
}
pub fn push(&mut self, value: SendPayload) {
self.0.push_back(value)
}
pub fn push_front(&mut self, value: SendPayload) {
self.0.push_front(value)
}
pub fn len(&self) -> usize {
self.0.len()
}
#[cfg(feature = "test_utils")]
pub fn iter(&self) -> impl Iterator<Item = &SendPayload> {
self.0.iter()
}
}
impl Link {
pub fn send(&mut self, payload: SendPayload) {
self.send.push(payload);
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct LinkStats {
pub rtt: Duration,
pub jitter: Duration,
}
#[deprecated(note = "Use LinkSystems instead")]
pub type LinkSet = LinkSystems;
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum LinkSystems {
Receive,
Send,
}
#[deprecated(note = "Use LinkReceiveSystems instead")]
pub type LinkReceiveSet = LinkReceiveSystems;
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum LinkReceiveSystems {
BufferToLink,
ApplyConditioner,
}
#[derive(EntityEvent)]
pub struct LinkStart {
pub entity: Entity,
}
#[derive(EntityEvent, Clone, Debug)]
pub struct Unlink {
#[event_target]
pub entity: Entity,
pub reason: String,
}
#[derive(Component, Default, Debug)]
#[component(on_insert = Linking::on_insert)]
pub struct Linking;
impl Linking {
fn on_insert(mut world: DeferredWorld, context: HookContext) {
if world.get::<Linked>(context.entity).is_some() {
return;
}
if let Some(mut link) = world.get_mut::<Link>(context.entity) {
link.state = LinkState::Linking;
}
world
.commands()
.entity(context.entity)
.remove::<(Linked, Unlinked)>();
}
}
#[derive(Component, Default, Debug)]
#[component(on_insert = Linked::on_insert)]
pub struct Linked;
impl Linked {
fn on_insert(mut world: DeferredWorld, context: HookContext) {
if let Some(mut link) = world.get_mut::<Link>(context.entity) {
link.state = LinkState::Linked;
}
world
.commands()
.entity(context.entity)
.remove::<(Linking, Unlinked)>();
}
}
#[derive(Component, Default, Debug)]
#[component(on_insert = Unlinked::on_insert)]
pub struct Unlinked {
pub reason: String,
}
impl Unlinked {
fn on_insert(mut world: DeferredWorld, context: HookContext) {
if let Some(mut link) = world.get_mut::<Link>(context.entity) {
link.state = LinkState::Unlinked;
}
world
.commands()
.entity(context.entity)
.remove::<(Linked, Linking)>();
}
}
pub struct LinkPlugin;
impl LinkPlugin {
pub fn apply_link_conditioner(mut query: Query<&mut Link>) {
query.par_iter_mut().for_each(|mut link| {
let recv = &mut link.recv;
if let Some(conditioner) = &mut recv.conditioner {
while let Some(packet) = conditioner.pop_packet(Instant::now()) {
recv.buffer.push_back(packet);
}
}
});
}
fn unlink(mut unlink: On<Unlink>, mut commands: Commands) {
if let Ok(mut c) = commands.get_entity(unlink.entity) {
c.insert(Unlinked {
reason: core::mem::take(&mut unlink.reason),
});
}
}
}
impl Plugin for LinkPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PreUpdate,
Self::apply_link_conditioner.in_set(LinkReceiveSystems::ApplyConditioner),
);
app.configure_sets(
PreUpdate,
(
LinkReceiveSystems::BufferToLink,
LinkReceiveSystems::ApplyConditioner,
)
.in_set(LinkSystems::Receive)
.chain(),
);
app.configure_sets(PostUpdate, LinkSystems::Send);
app.add_observer(Self::unlink);
}
}