#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
mod conditioner;
mod mtu;
pub mod server;
use alloc::{collections::vec_deque::Drain, string::String};
pub use crate::conditioner::LinkConditioner;
pub use crate::mtu::{DEFAULT_MTU, LinkMtu, MtuTooSmall};
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 bevy_reflect::Reflect;
use bytes::{Bytes, BytesMut};
use core::time::Duration;
use lightyear_core::time::Instant;
use lightyear_utils::adaptive_for_each_mut;
pub mod prelude {
pub use crate::conditioner::{LinkConditionerConfig, LinkConditionerState};
pub use crate::server::{LinkOf, Server};
pub use crate::{
DEFAULT_MTU, Link, LinkMtu, LinkStart, LinkStats, LinkSystems, Linked, Linking,
MtuTooSmall, RecvLinkConditioner, Unlink, UnlinkReason, Unlinked,
};
pub mod server {
pub use crate::server::{LinkOf, Server};
}
}
pub type RecvPayload = BytesMut;
pub type SendPayload = Bytes;
pub fn recv_payload_from_bytes(payload: Bytes) -> RecvPayload {
match payload.try_into_mut() {
Ok(payload) => payload,
Err(payload) => BytesMut::from(payload),
}
}
#[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,
mtu: LinkMtu,
}
pub type RecvLinkConditioner = LinkConditioner<RecvPayload>;
impl Link {
pub fn with_conditioner(
mut self,
recv_conditioner: impl Into<Option<RecvLinkConditioner>>,
) -> Self {
self.recv.conditioner = recv_conditioner.into();
self
}
pub fn with_mtu(mut self, mtu: LinkMtu) -> Self {
self.mtu = mtu;
self
}
pub const fn mtu(&self) -> usize {
self.mtu.mtu()
}
pub const fn min_mtu(&self) -> usize {
self.mtu.min_mtu()
}
pub const fn set_mtu(&mut self, mtu: usize) -> Result<(), MtuTooSmall> {
self.mtu.set_mtu(mtu)
}
}
#[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 = &RecvPayload> {
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(Default, Debug, Clone, PartialEq, Eq, Reflect)]
pub enum UnlinkReason {
#[default]
Initial,
UserRequested(Option<String>),
ServerStopped,
ByPeer(String),
TransportError(String),
}
impl core::fmt::Display for UnlinkReason {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Initial => f.write_str("Not connected"),
Self::UserRequested(Some(reason)) => write!(f, "User requested: {reason}"),
Self::UserRequested(None) => f.write_str("User requested"),
Self::ServerStopped => f.write_str("Server stopped"),
Self::ByPeer(reason) => write!(f, "Disconnected by peer: {reason}"),
Self::TransportError(reason) => write!(f, "Transport error: {reason}"),
}
}
}
#[derive(EntityEvent, Clone, Debug)]
pub struct Unlink {
#[event_target]
pub entity: Entity,
pub reason: UnlinkReason,
}
#[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: UnlinkReason,
}
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>) {
let query = adaptive_for_each_mut!(query);
query.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);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn immutable_receive_payload_reuses_unique_allocation() {
let bytes = Bytes::from(alloc::vec![1, 2, 3]);
let allocation = bytes.as_ptr();
let payload = recv_payload_from_bytes(bytes);
assert_eq!(payload.as_ptr(), allocation);
}
#[test]
fn immutable_receive_payload_copies_shared_allocation() {
let bytes = Bytes::from(alloc::vec![1, 2, 3]);
let shared = bytes.clone();
let allocation = bytes.as_ptr();
let payload = recv_payload_from_bytes(bytes);
assert_ne!(payload.as_ptr(), allocation);
assert_eq!(payload.as_ref(), shared.as_ref());
}
#[test]
fn explicit_link_mtu_does_not_change_link_owned_latency_stats() {
let mut link = Link::default().with_mtu(LinkMtu::new(512));
link.stats.rtt = Duration::from_millis(20);
link.stats.jitter = Duration::from_millis(3);
assert_eq!(link.mtu(), 512);
assert_eq!(link.min_mtu(), 512);
assert_eq!(link.stats.rtt, Duration::from_millis(20));
assert_eq!(link.stats.jitter, Duration::from_millis(3));
}
#[test]
fn link_builder_configures_conditioner_and_mtu() {
let conditioner =
RecvLinkConditioner::new(crate::conditioner::LinkConditionerConfig::default());
let link = Link::default()
.with_conditioner(conditioner)
.with_mtu(LinkMtu::new(512));
assert!(link.recv.conditioner.is_some());
assert_eq!(link.mtu(), 512);
assert_eq!(link.min_mtu(), 512);
}
#[test]
fn current_mtu_can_change_but_minimum_mtu_cannot() {
let mut link = Link::default().with_mtu(LinkMtu::new(512));
link.set_mtu(900).unwrap();
assert_eq!(link.mtu(), 900);
assert_eq!(link.min_mtu(), 512);
assert_eq!(link.set_mtu(511), Err(MtuTooSmall { mtu: 511, min: 512 }));
assert_eq!(link.mtu(), 900);
assert_eq!(link.min_mtu(), 512);
}
}