use std::collections::{BTreeMap, VecDeque};
use std::io;
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::sync::mpsc;
use std::time::Duration;
use serde::Serialize;
use serde::de::DeserializeOwned;
use crate::{Instant, World, WorldReflect};
mod messages;
pub use messages::{Lane, MAX_MESSAGE_BYTES, MessageEvent, MessageStats, NetMessages, PeerId};
mod interest;
mod sequence;
mod snapshots;
pub use interest::{
Interest, InterestChange, InterestChangeKind, InterestConfig, InterestEntity, InterestEntry, InterestTier,
InterestViewer, MAX_INTEREST_TIERS,
};
pub use sequence::{Authority, AuthorityCheck, LatestWins, UpdateStamp, seq_diff, seq_newer};
pub use snapshots::{
ClockSync, Interpolate, Pose, SnapshotBuffer, SnapshotConfig, SnapshotPush, SnapshotSample, SnapshotState,
SnapshotStream,
};
#[cfg(feature = "3d")]
pub use snapshots::{NetPose, apply_net_poses};
#[cfg(feature = "steam")]
mod steam;
#[cfg(feature = "steam")]
mod steam_messages;
#[cfg(feature = "steam")]
pub use steam::{steam_lobby_from_args, steam_wait_for_invite};
#[cfg(feature = "steam")]
pub use steam_messages::{SteamLobby, SteamLobbyVisibility};
pub const MAX_PLAYERS: usize = 8;
pub const MAX_INPUT_BYTES: usize = 1024;
const MAGIC: [u8; 4] = *b"CRBN";
const VERSION: u8 = 1;
const HELLO: u8 = 1;
const WELCOME: u8 = 2;
const START: u8 = 3;
const INPUTS: u8 = 4;
const CHECK: u8 = 5;
const REJECT: u8 = 6;
const BYE: u8 = 7;
const RING: usize = 256;
const MAX_DELAY: u32 = 64;
const PACKET: usize = 1400;
const RESEND: Duration = Duration::from_millis(20);
const HELLO_EVERY: Duration = Duration::from_millis(100);
const LINGER: Duration = Duration::from_secs(1);
const GONE: Duration = Duration::from_millis(250);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum NetState {
Offline,
Connecting,
Running,
Lost,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Desync {
pub tick: u64,
pub player: usize,
pub components: Vec<String>,
}
impl std::fmt::Display for Desync {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"desync with player {} at tick {}: {} differ",
self.player,
self.tick,
self.components.join(", ")
)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NetStats {
pub packets_sent: u64,
pub bytes_sent: u64,
}
pub struct Net {
session: Option<Box<Session>>,
pending: Vec<u8>,
current: Vec<Vec<u8>>,
player: usize,
players: usize,
delay: u32,
checksum_every: u32,
timeout: Duration,
state: NetState,
error: Option<String>,
desync: Option<Desync>,
waits: u32,
}
impl Default for Net {
fn default() -> Self {
Self {
session: None,
pending: Vec::new(),
current: vec![Vec::new()],
player: 0,
players: 1,
delay: 0,
checksum_every: 0,
timeout: Duration::from_secs(10),
state: NetState::Offline,
error: None,
desync: None,
waits: 0,
}
}
}
impl std::fmt::Debug for Net {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Net")
.field("state", &self.state)
.field("player", &self.player)
.field("players", &self.players)
.finish_non_exhaustive()
}
}
impl Net {
pub fn host(addr: impl ToSocketAddrs, players: usize) -> io::Result<Net> {
if !(2..=MAX_PLAYERS).contains(&players) {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "2 to 8 players"));
}
let transport = Udp::bind(addr)?;
Ok(Self::with(Session::new(Box::new(transport), Role::Host), 0, players))
}
pub fn join(host: impl ToSocketAddrs) -> io::Result<Net> {
let host = host
.to_socket_addrs()?
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "no address"))?;
let any: SocketAddr = if host.is_ipv4() {
"0.0.0.0:0".parse().expect("address")
} else {
"[::]:0".parse().expect("address")
};
let transport = Udp::bind(any)?;
Ok(Self::with(Session::new(Box::new(transport), Role::Client(host)), 0, 0))
}
#[must_use]
pub fn loopback(players: usize) -> Vec<Net> {
Self::loopback_lossy(players, 0)
}
#[doc(hidden)]
#[must_use]
pub fn loopback_lossy(players: usize, drop_one_in: u32) -> Vec<Net> {
assert!((2..=MAX_PLAYERS).contains(&players), "2 to 8 players");
let (txs, rxs): (Vec<_>, Vec<_>) = (0..players).map(|_| mpsc::channel()).unzip();
let txs = std::sync::Arc::new(txs);
rxs.into_iter()
.enumerate()
.map(|(i, rx)| {
let transport = Local {
me: Local::addr(i),
rx,
peers: txs.clone(),
drop_one_in,
sent: i as u32,
};
if i == 0 {
Self::with(Session::new(Box::new(transport), Role::Host), 0, players)
} else {
Self::with(Session::new(Box::new(transport), Role::Client(Local::addr(0))), 0, 0)
}
})
.collect()
}
fn with(session: Session, player: usize, players: usize) -> Net {
let mut net = Net::default();
net.session = Some(Box::new(session));
net.player = player;
net.players = players;
net.current = vec![Vec::new(); players.max(1)];
net.delay = 3;
net.checksum_every = 60;
net.state = NetState::Connecting;
net
}
#[must_use]
pub fn input_delay(mut self, ticks: u32) -> Self {
assert!(ticks <= MAX_DELAY, "input delay is at most {MAX_DELAY} ticks");
self.delay = ticks;
self
}
#[must_use]
pub fn checksum_every(mut self, ticks: u32) -> Self {
self.checksum_every = ticks;
self
}
#[must_use]
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub fn local_addr(&self) -> Option<SocketAddr> {
self.session.as_ref().map(|s| s.transport.local_addr())
}
#[must_use]
pub fn player(&self) -> usize {
self.player
}
#[must_use]
pub fn players(&self) -> usize {
self.players
}
#[must_use]
pub fn state(&self) -> NetState {
self.state
}
#[must_use]
pub fn error(&self) -> Option<&str> {
self.error.as_deref()
}
pub fn stats(&self) -> NetStats {
self.session.as_ref().map_or_else(NetStats::default, |s| s.stats)
}
#[must_use]
pub fn desync(&self) -> Option<&Desync> {
self.desync.as_ref()
}
pub fn set_input<T: Serialize>(&mut self, input: &T) {
self.pending.clear();
serde_json::to_writer(&mut self.pending, input).expect("inputs serialize");
assert!(
self.pending.len() <= MAX_INPUT_BYTES,
"input is over {MAX_INPUT_BYTES} bytes"
);
}
pub fn set_input_bytes(&mut self, input: &[u8]) {
assert!(input.len() <= MAX_INPUT_BYTES, "input is over {MAX_INPUT_BYTES} bytes");
self.pending.clear();
self.pending.extend_from_slice(input);
}
#[must_use]
pub fn input<T: DeserializeOwned>(&self, player: usize) -> Option<T> {
let bytes = self.input_bytes(player);
if bytes.is_empty() {
return None;
}
serde_json::from_slice(bytes).ok()
}
pub fn input_bytes(&self, player: usize) -> &[u8] {
self.current.get(player).map_or(&[], Vec::as_slice)
}
pub(crate) fn begin(&mut self, seed: u64) {
if let Some(s) = self.session.as_deref_mut() {
s.seed = seed;
}
}
pub(crate) fn ready(&mut self, tick: u64) -> bool {
let Some(s) = self.session.as_deref_mut() else {
std::mem::swap(&mut self.current[0], &mut self.pending);
self.pending.clear();
return true;
};
if self.state == NetState::Lost {
return false;
}
let now = Instant::now();
s.poll(
now,
&mut Settings {
player: &mut self.player,
players: &mut self.players,
delay: &mut self.delay,
checksum_every: &mut self.checksum_every,
desync: &mut self.desync,
},
);
if let Some(e) = s.error.take() {
return self.lose(e);
}
if !s.started {
return false;
}
self.state = NetState::Running;
if self.current.len() != self.players {
self.current.resize(self.players, Vec::new());
}
let due = tick + u64::from(self.delay);
if s.streams[self.player].have <= due {
s.streams[self.player].push(due, &self.pending);
self.pending.clear();
}
s.flush(now, self.player, self.players);
if let Some(quiet) = s.silent_peer(now, self.timeout) {
return self.lose(format!("player {quiet} stopped answering"));
}
if s.streams.iter().any(|st| st.have <= tick) {
return false;
}
self.waits = 0;
for (p, st) in s.streams.iter().enumerate() {
self.current[p].clear();
self.current[p].extend_from_slice(st.get(tick));
}
true
}
fn lose(&mut self, error: String) -> bool {
eprintln!("crabby lockstep: {error}");
self.error = Some(error);
self.state = NetState::Lost;
false
}
pub(crate) fn end_tick(&mut self, world: &World, tick: u64) {
let Some(s) = self.session.as_deref_mut() else { return };
if self.checksum_every == 0 || !(tick + 1).is_multiple_of(u64::from(self.checksum_every)) {
return;
}
let check = Check::of(world, tick);
s.send_check(&check);
if self.desync.is_none() {
self.desync = s.compare_stored(&check);
if let Some(d) = &self.desync {
eprintln!("crabby lockstep: {d}");
}
}
s.own_checks.push_back(check);
if s.own_checks.len() > 16 {
s.own_checks.pop_front();
}
}
pub(crate) fn wait(&mut self) -> bool {
self.waits += 1;
if self.waits < 64 {
std::thread::yield_now();
} else {
std::thread::sleep(Duration::from_micros(200));
}
if self.state == NetState::Connecting {
let s = self.session.as_deref_mut().expect("online");
let since = *s.connecting_since.get_or_insert_with(Instant::now);
if since.elapsed() > self.timeout {
return !self.lose("nobody answered".into());
}
}
self.state != NetState::Lost
}
}
impl Drop for Net {
fn drop(&mut self) {
let Some(s) = self.session.as_deref_mut() else { return };
if !s.started || self.state == NetState::Lost {
return;
}
let end = Instant::now() + LINGER.min(self.timeout);
loop {
let now = Instant::now();
s.poll(
now,
&mut Settings {
player: &mut self.player,
players: &mut self.players,
delay: &mut self.delay,
checksum_every: &mut self.checksum_every,
desync: &mut self.desync,
},
);
if now >= end || s.settled(now, self.player) {
for i in 0..s.peers.len() {
let addr = s.peers[i].addr;
s.send_simple(addr, BYE);
}
return;
}
s.flush(now, self.player, self.players);
std::thread::sleep(Duration::from_millis(1));
}
}
}
struct Settings<'a> {
player: &'a mut usize,
players: &'a mut usize,
delay: &'a mut u32,
checksum_every: &'a mut u32,
desync: &'a mut Option<Desync>,
}
struct Stream {
have: u64,
ring: Vec<(u64, Vec<u8>)>,
}
impl Stream {
fn new(delay: u32) -> Self {
Self {
have: u64::from(delay),
ring: (0..RING).map(|_| (u64::MAX, Vec::new())).collect(),
}
}
fn push(&mut self, tick: u64, data: &[u8]) {
if tick < self.have || tick >= self.have + RING as u64 {
return;
}
let slot = &mut self.ring[tick as usize % RING];
slot.0 = tick;
slot.1.clear();
slot.1.extend_from_slice(data);
while self.ring[self.have as usize % RING].0 == self.have {
self.have += 1;
}
}
fn get(&self, tick: u64) -> &[u8] {
let slot = &self.ring[tick as usize % RING];
if slot.0 == tick { &slot.1 } else { &[] }
}
}
enum Role {
Host,
Client(SocketAddr),
}
struct Peer {
addr: SocketAddr,
player: usize,
acked: [u64; MAX_PLAYERS],
sent: [u64; MAX_PLAYERS],
last_heard: Instant,
last_sent: Option<Instant>,
running: bool,
left: bool,
}
struct Session {
transport: Box<dyn Transport>,
role: Role,
peers: Vec<Peer>,
streams: Vec<Stream>,
started: bool,
seed: u64,
last_hello: Option<Instant>,
last_start: Option<Instant>,
connecting_since: Option<Instant>,
error: Option<String>,
own_checks: VecDeque<Check>,
their_checks: VecDeque<(usize, Check)>,
out: Vec<u8>,
buf: Vec<u8>,
stats: NetStats,
}
impl Session {
fn new(transport: Box<dyn Transport>, role: Role) -> Self {
Self {
transport,
role,
peers: Vec::new(),
streams: Vec::new(),
started: false,
seed: 0,
last_hello: None,
last_start: None,
connecting_since: None,
error: None,
own_checks: VecDeque::new(),
their_checks: VecDeque::new(),
out: Vec::with_capacity(PACKET),
buf: vec![0; 64 * 1024],
stats: NetStats::default(),
}
}
fn poll(&mut self, now: Instant, settings: &mut Settings) {
if self.streams.is_empty() && matches!(self.role, Role::Host) {
self.streams = (0..*settings.players).map(|_| Stream::new(*settings.delay)).collect();
}
if let Some(e) = self.transport.error() {
self.error = Some(e);
}
while let Some((from, n)) = self.transport.recv(&mut self.buf) {
let packet = std::mem::take(&mut self.buf);
self.receive(from, &packet[..n], now, settings);
self.buf = packet;
}
match self.role {
Role::Host => {
if !self.started && self.peers.len() + 1 == *settings.players {
self.start(now);
}
if self.started && self.last_start.is_none_or(|t| now - t >= RESEND) {
self.last_start = Some(now);
for i in 0..self.peers.len() {
if !self.peers[i].running {
let addr = self.peers[i].addr;
self.send_simple(addr, START);
}
}
}
}
Role::Client(host) => {
if self.peers.is_empty() && self.last_hello.is_none_or(|t| now - t >= HELLO_EVERY) {
self.last_hello = Some(now);
self.begin(HELLO);
self.out.extend_from_slice(&self.seed.to_le_bytes());
self.emit(host);
}
}
}
}
fn emit(&mut self, to: SocketAddr) {
self.stats.packets_sent += 1;
self.stats.bytes_sent += self.out.len() as u64;
self.transport.send(to, &self.out);
}
fn begin(&mut self, kind: u8) {
self.out.clear();
self.out.extend_from_slice(&MAGIC);
self.out.push(VERSION);
self.out.push(kind);
}
fn send_simple(&mut self, to: SocketAddr, kind: u8) {
self.begin(kind);
self.emit(to);
}
fn receive(&mut self, from: SocketAddr, packet: &[u8], now: Instant, settings: &mut Settings) {
let mut r = Reader(packet);
if r.take(4) != Some(&MAGIC[..]) || r.u8() != Some(VERSION) {
return;
}
let Some(kind) = r.u8() else { return };
if let Some(peer) = self.peers.iter_mut().find(|p| p.addr == from) {
peer.last_heard = now;
}
match (kind, &self.role) {
(HELLO, Role::Host) => {
let Some(seed) = r.u64() else { return };
if seed != self.seed {
self.begin(REJECT);
self.out.push(1);
self.emit(from);
return;
}
let player = match self.peers.iter().find(|p| p.addr == from) {
Some(p) => p.player,
None if self.peers.len() + 1 < *settings.players => {
let player = self.peers.len() + 1;
self.peers.push(Peer::new(from, player, now));
player
}
None => {
self.begin(REJECT);
self.out.push(2);
self.emit(from);
return;
}
};
self.begin(WELCOME);
self.out.push(player as u8);
self.out.push(*settings.players as u8);
self.out.extend_from_slice(&settings.delay.to_le_bytes());
self.out.extend_from_slice(&settings.checksum_every.to_le_bytes());
self.emit(from);
}
(WELCOME, Role::Client(host)) if from == *host && self.peers.is_empty() => {
let (Some(player), Some(players), Some(delay), Some(every)) = (r.u8(), r.u8(), r.u32(), r.u32()) else {
return;
};
let (player, players) = (player as usize, players as usize);
if player == 0 || player >= players || players > MAX_PLAYERS || delay > MAX_DELAY {
return;
}
*settings.player = player;
*settings.players = players;
*settings.delay = delay;
*settings.checksum_every = every;
self.streams = (0..players).map(|_| Stream::new(delay)).collect();
self.peers.push(Peer::new(from, 0, now));
}
(START, Role::Client(host)) if from == *host && !self.peers.is_empty() && !self.started => self.start(now),
(REJECT, Role::Client(host)) if from == *host => {
self.error = Some(match r.u8() {
Some(1) => "the host uses a different Game::seed".into(),
_ => "the session is full".into(),
});
}
(INPUTS, _) => self.receive_inputs(from, r, now, settings),
(BYE, _) => {
if let Some(peer) = self.peers.iter_mut().find(|p| p.addr == from) {
peer.left = true;
}
}
(CHECK, _) => {
let Some(i) = self.peers.iter().position(|p| p.addr == from) else {
return;
};
let Some(check) = Check::read(&mut r) else { return };
let player = self.peers[i].player;
if settings.desync.is_none() {
match self.own_checks.iter().find(|c| c.tick == check.tick) {
Some(own) => {
*settings.desync = own.compare(&check, player);
if let Some(d) = settings.desync.as_ref() {
eprintln!("crabby lockstep: {d}");
}
}
None => {
self.their_checks.push_back((player, check));
if self.their_checks.len() > 64 {
self.their_checks.pop_front();
}
}
}
}
}
_ => {}
}
}
fn start(&mut self, now: Instant) {
self.started = true;
for p in &mut self.peers {
p.last_heard = now;
}
}
fn receive_inputs(&mut self, from: SocketAddr, mut r: Reader, now: Instant, settings: &Settings) {
let Some(i) = self.peers.iter().position(|p| p.addr == from) else {
return;
};
let players = *settings.players;
if self.streams.len() != players {
return;
}
if matches!(self.role, Role::Client(_)) && !self.started {
self.start(now); }
self.peers[i].running = true;
let Some(n) = r.u8() else { return };
for s in 0..(n as usize).min(MAX_PLAYERS) {
let Some(have) = r.u64() else { return };
let acked = &mut self.peers[i].acked[s];
*acked = (*acked).max(have);
}
let Some(count) = r.u8() else { return };
for _ in 0..count {
let (Some(stream), Some(first), Some(ticks)) = (r.u8(), r.u64(), r.u8()) else {
return;
};
let stream = stream as usize;
let allowed = stream < players
&& match self.role {
Role::Host => stream == self.peers[i].player,
Role::Client(_) => stream != *settings.player,
};
for t in 0..u64::from(ticks) {
let Some(len) = r.u16() else { return };
let Some(data) = r.take(len as usize) else { return };
if allowed {
self.streams[stream].push(first + t, data);
}
}
}
}
fn flush(&mut self, now: Instant, me: usize, players: usize) {
if !self.started || self.streams.len() != players {
return;
}
for i in 0..self.peers.len() {
let peer = &self.peers[i];
let streams: &[usize] = match self.role {
Role::Host => &[0, 1, 2, 3, 4, 5, 6, 7][..players],
Role::Client(_) => &[me],
};
let fresh = streams
.iter()
.any(|&s| s != peer.player && self.streams[s].have > peer.sent[s]);
if !fresh && peer.last_sent.is_some_and(|t| now - t < RESEND) {
continue;
}
self.begin(INPUTS);
self.out.push(players as u8);
for st in &self.streams {
self.out.extend_from_slice(&st.have.to_le_bytes());
}
let count_at = self.out.len();
self.out.push(0);
let mut sent = self.peers[i].sent;
for &s in streams {
let peer = &self.peers[i];
if s == peer.player {
continue;
}
let st = &self.streams[s];
let first = peer.acked[s];
if first >= st.have || self.out.len() + 12 > PACKET {
continue;
}
let ticks_at = self.out.len() + 9;
self.out.push(s as u8);
self.out.extend_from_slice(&first.to_le_bytes());
self.out.push(0);
let mut ticks = 0u8;
let mut t = first;
while t < st.have && ticks < u8::MAX {
let data = st.get(t);
if ticks > 0 && self.out.len() + 2 + data.len() > PACKET {
break;
}
self.out.extend_from_slice(&(data.len() as u16).to_le_bytes());
self.out.extend_from_slice(data);
ticks += 1;
t += 1;
}
self.out[ticks_at] = ticks;
self.out[count_at] += 1;
sent[s] = t;
}
let peer = &mut self.peers[i];
peer.sent = sent;
peer.last_sent = Some(now);
let addr = peer.addr;
self.emit(addr);
}
}
fn settled(&self, now: Instant, me: usize) -> bool {
self.peers.iter().all(|p| {
let has_all = match self.role {
Role::Host => (0..self.streams.len()).all(|s| s == p.player || p.acked[s] >= self.streams[s].have),
Role::Client(_) => p.acked[me] >= self.streams[me].have,
};
has_all || p.left || now - p.last_heard > GONE
})
}
fn silent_peer(&self, now: Instant, timeout: Duration) -> Option<usize> {
self.peers
.iter()
.find(|p| now - p.last_heard > timeout)
.map(|p| p.player)
}
fn send_check(&mut self, check: &Check) {
self.begin(CHECK);
check.write(&mut self.out);
for i in 0..self.peers.len() {
let addr = self.peers[i].addr;
self.emit(addr);
}
}
fn compare_stored(&mut self, own: &Check) -> Option<Desync> {
let mut found = None;
self.their_checks.retain(|(player, theirs)| {
if theirs.tick != own.tick {
return theirs.tick > own.tick;
}
if found.is_none() {
found = own.compare(theirs, *player);
}
false
});
found
}
}
impl Peer {
fn new(addr: SocketAddr, player: usize, now: Instant) -> Self {
Self {
addr,
player,
acked: [0; MAX_PLAYERS],
sent: [0; MAX_PLAYERS],
last_heard: now,
last_sent: None,
running: false,
left: false,
}
}
}
struct Check {
tick: u64,
total: u64,
parts: Vec<(String, u64)>,
}
fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
for &b in bytes {
h ^= u64::from(b);
h = h.wrapping_mul(0x100_0000_01b3);
}
h
}
const FNV_START: u64 = 0xcbf2_9ce4_8422_2325;
impl Check {
fn of(world: &World, tick: u64) -> Check {
let save = world.save();
let mut parts: BTreeMap<String, u64> = BTreeMap::new();
for e in save["entities"].as_array().into_iter().flatten() {
let id = e["entity"].as_u64().unwrap_or(0).to_le_bytes();
for (name, value) in e["components"].as_object().into_iter().flatten() {
let h = parts.entry(name.clone()).or_insert(FNV_START);
*h = fnv(fnv(*h, &id), value.to_string().as_bytes());
}
}
for (name, value) in save["resources"].as_object().into_iter().flatten() {
parts.insert(format!("resource:{name}"), fnv(FNV_START, value.to_string().as_bytes()));
}
let parts: Vec<(String, u64)> = parts.into_iter().collect();
let total = parts
.iter()
.fold(FNV_START, |h, (n, v)| fnv(fnv(h, n.as_bytes()), &v.to_le_bytes()));
Check { tick, total, parts }
}
fn write(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.tick.to_le_bytes());
out.extend_from_slice(&self.total.to_le_bytes());
let parts = self.parts.len().min(64);
out.push(parts as u8);
for (name, hash) in &self.parts[..parts] {
let name = &name.as_bytes()[..name.len().min(255)];
out.push(name.len() as u8);
out.extend_from_slice(name);
out.extend_from_slice(&hash.to_le_bytes());
}
}
fn read(r: &mut Reader) -> Option<Check> {
let tick = r.u64()?;
let total = r.u64()?;
let n = r.u8()?;
let mut parts = Vec::with_capacity(n as usize);
for _ in 0..n {
let len = r.u8()?;
let name = String::from_utf8_lossy(r.take(len as usize)?).into_owned();
parts.push((name, r.u64()?));
}
Some(Check { tick, total, parts })
}
fn compare(&self, theirs: &Check, player: usize) -> Option<Desync> {
if self.total == theirs.total {
return None;
}
let mut components: Vec<String> = Vec::new();
for (name, hash) in &self.parts {
if theirs.parts.iter().find(|(n, _)| n == name).map(|(_, h)| h) != Some(hash) {
components.push(name.clone());
}
}
for (name, _) in &theirs.parts {
if !self.parts.iter().any(|(n, _)| n == name) {
components.push(name.clone());
}
}
Some(Desync {
tick: self.tick,
player,
components,
})
}
}
struct Reader<'a>(&'a [u8]);
impl<'a> Reader<'a> {
fn take(&mut self, n: usize) -> Option<&'a [u8]> {
if self.0.len() < n {
return None;
}
let (head, rest) = self.0.split_at(n);
self.0 = rest;
Some(head)
}
fn u8(&mut self) -> Option<u8> {
self.take(1).map(|b| b[0])
}
fn u16(&mut self) -> Option<u16> {
self.take(2).map(|b| u16::from_le_bytes(b.try_into().expect("2 bytes")))
}
fn u32(&mut self) -> Option<u32> {
self.take(4).map(|b| u32::from_le_bytes(b.try_into().expect("4 bytes")))
}
fn u64(&mut self) -> Option<u64> {
self.take(8).map(|b| u64::from_le_bytes(b.try_into().expect("8 bytes")))
}
}
trait Transport: Send {
fn send(&mut self, to: SocketAddr, data: &[u8]);
fn recv(&mut self, buf: &mut [u8]) -> Option<(SocketAddr, usize)>;
fn local_addr(&self) -> SocketAddr;
fn error(&mut self) -> Option<String> {
None
}
#[cfg(feature = "steam")]
fn steam(&self) -> Option<(&steamworks::Client, Option<steamworks::LobbyId>)> {
None
}
}
struct Udp(UdpSocket);
impl Udp {
fn bind(addr: impl ToSocketAddrs) -> io::Result<Udp> {
let socket = UdpSocket::bind(addr)?;
socket.set_nonblocking(true)?;
Ok(Udp(socket))
}
}
impl Transport for Udp {
fn send(&mut self, to: SocketAddr, data: &[u8]) {
let _ = self.0.send_to(data, to);
}
fn recv(&mut self, buf: &mut [u8]) -> Option<(SocketAddr, usize)> {
loop {
match self.0.recv_from(buf) {
Ok((n, from)) => return Some((from, n)),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => return None,
Err(_) => continue,
}
}
}
fn local_addr(&self) -> SocketAddr {
self.0.local_addr().expect("bound socket")
}
}
type Packet = (SocketAddr, Vec<u8>);
struct Local {
me: SocketAddr,
rx: mpsc::Receiver<Packet>,
peers: std::sync::Arc<Vec<mpsc::Sender<Packet>>>,
drop_one_in: u32,
sent: u32,
}
impl Local {
fn addr(i: usize) -> SocketAddr {
SocketAddr::from(([127, 0, 0, 1], 1 + i as u16))
}
}
impl Transport for Local {
fn send(&mut self, to: SocketAddr, data: &[u8]) {
self.sent = self.sent.wrapping_add(1);
let mix = (u64::from(self.sent) | u64::from(self.me.port()) << 32).wrapping_mul(0x9e37_79b9_7f4a_7c15);
if self.drop_one_in > 0 && (mix >> 40).is_multiple_of(u64::from(self.drop_one_in)) {
return;
}
if let Some(tx) = self.peers.get(to.port() as usize - 1) {
let _ = tx.send((self.me, data.to_vec()));
}
}
fn recv(&mut self, buf: &mut [u8]) -> Option<(SocketAddr, usize)> {
let (from, data) = self.rx.try_recv().ok()?;
let n = data.len().min(buf.len());
buf[..n].copy_from_slice(&data[..n]);
Some((from, n))
}
fn local_addr(&self) -> SocketAddr {
self.me
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn streams_fill_in_order_and_ignore_old_ticks() {
let mut s = Stream::new(2);
assert_eq!(s.have, 2);
s.push(3, b"b");
assert_eq!(s.have, 2, "tick 2 is missing");
s.push(2, b"a");
assert_eq!(s.have, 4);
s.push(2, b"x");
assert_eq!(s.get(2), b"a", "duplicates are ignored");
assert!(s.get(0).is_empty(), "ticks before the delay are empty");
}
#[test]
fn checks_name_the_components_that_differ() {
let mut a = World::new();
a.reflect::<crate::Transform2D>();
a.spawn((crate::Transform2D::default(),));
let mut b = World::new();
b.reflect::<crate::Transform2D>();
b.spawn((crate::Transform2D::at(crate::vec2(1.0, 0.0)),));
let (ca, cb) = (Check::of(&a, 5), Check::of(&b, 5));
assert!(ca.compare(&Check::of(&a, 5), 1).is_none());
let d = ca.compare(&cb, 1).expect("different");
assert_eq!(d.components, ["Transform2D"]);
let mut out = Vec::new();
cb.write(&mut out);
let back = Check::read(&mut Reader(&out)).unwrap();
assert_eq!((back.tick, back.total, back.parts), (cb.tick, cb.total, cb.parts));
}
}