sdec-bevy 0.8.0

Bevy adapter for sdec schema + delta encoding
Documentation
use std::time::Instant;

use anyhow::{anyhow, Result};
use bevy_ecs::prelude::World;
use codec::{
    decode_session_packet, encode_delta_from_changes, CodecLimits, SessionEncoder, SessionState,
};
use wire::{decode_packet, Limits as WireLimits};

use crate::apply::apply_changes;
use crate::extract::{extract_changes_with_scratch, BevyChangeSet, ExtractScratch};
use crate::mapping::EntityMap;
use crate::metrics::{EncodeMetrics, MetricsSink};
use crate::schema::BevySchema;

pub struct BevyReplicator {
    schema: BevySchema,
    limits: CodecLimits,
    wire_limits: WireLimits,
    entities: EntityMap,
    session: Option<SessionState>,
    metrics: Option<Box<dyn MetricsSink>>,
    change_set: BevyChangeSet,
    extract_scratch: ExtractScratch,
}

impl BevyReplicator {
    #[must_use]
    pub fn new(schema: BevySchema) -> Self {
        Self {
            schema,
            limits: CodecLimits::default(),
            wire_limits: WireLimits::default(),
            entities: EntityMap::new(),
            session: None,
            metrics: None,
            change_set: BevyChangeSet::default(),
            extract_scratch: ExtractScratch::default(),
        }
    }

    pub fn with_limits(mut self, limits: CodecLimits, wire_limits: WireLimits) -> Self {
        self.limits = limits;
        self.wire_limits = wire_limits;
        self
    }

    pub fn set_metrics_sink(&mut self, sink: Box<dyn MetricsSink>) {
        self.metrics = Some(sink);
    }

    pub fn update_session(&mut self, session: SessionState) {
        self.session = Some(session);
    }

    pub fn encode_frame(
        &mut self,
        world: &mut World,
        tick: codec::SnapshotTick,
        baseline_tick: codec::SnapshotTick,
        out: &mut [u8],
    ) -> Result<usize> {
        extract_changes_with_scratch(
            &self.schema,
            world,
            &mut self.entities,
            &mut self.extract_scratch,
            &mut self.change_set,
        );
        let mut encoder = SessionEncoder::new(self.schema.schema(), &self.limits);
        let start = Instant::now();
        let bytes = encode_delta_from_changes(
            &mut encoder,
            tick,
            baseline_tick,
            &self.change_set.creates,
            &self.change_set.destroys,
            &self.change_set.updates,
            out,
        )?;
        if let Some(metrics) = self.metrics.as_mut() {
            metrics.record_encode(EncodeMetrics {
                bytes,
                encode_time: start.elapsed(),
            });
        }
        Ok(bytes)
    }

    pub fn apply_frame(&mut self, world: &mut World, bytes: &[u8]) -> Result<()> {
        let packet = decode_packet(bytes, &self.wire_limits)?;
        let decoded = codec::decode_delta_packet(self.schema.schema(), &packet, &self.limits)?;
        apply_changes(
            &self.schema,
            world,
            &mut self.entities,
            &decoded.creates,
            &decoded.destroys,
            &decoded.updates,
        )?;
        Ok(())
    }

    pub fn apply_compact_frame(&mut self, world: &mut World, bytes: &[u8]) -> Result<()> {
        let Some(session) = self.session.as_mut() else {
            return Err(anyhow!("session state missing; call update_session first"));
        };
        let packet =
            decode_session_packet(self.schema.schema(), session, bytes, &self.wire_limits)?;
        let decoded = codec::decode_delta_packet(self.schema.schema(), &packet, &self.limits)?;
        apply_changes(
            &self.schema,
            world,
            &mut self.entities,
            &decoded.creates,
            &decoded.destroys,
            &decoded.updates,
        )?;
        Ok(())
    }
}