sdec-bevy 0.8.0

Bevy adapter for sdec schema + delta encoding
Documentation
use std::any::TypeId;
use std::collections::HashMap;
use std::marker::PhantomData;

use anyhow::{anyhow, Result};
use bevy_ecs::component::Mutable;
use bevy_ecs::prelude::{Component, Entity, World};
use codec::{ComponentSnapshot, DeltaUpdateComponent, DeltaUpdateEntity, FieldValue};
use schema::{ChangePolicy, ComponentDef, ComponentId, FieldCodec, FieldDef, FieldId, Schema};

#[derive(Debug, Clone)]
pub struct ReplicatedField {
    pub id: u16,
    pub codec: FieldCodec,
    pub change: Option<ChangePolicy>,
}

pub trait ReplicatedComponent: Component<Mutability = Mutable> {
    const COMPONENT_ID: u16;

    fn fields() -> Vec<ReplicatedField>;

    fn read_fields(&self) -> Vec<FieldValue>;

    fn apply_field(&mut self, index: usize, value: FieldValue) -> Result<()>;

    fn from_fields(fields: &[FieldValue]) -> Result<Self>
    where
        Self: Sized;
}

pub(crate) trait ComponentAdapter {
    fn type_id(&self) -> TypeId;
    fn component_id(&self) -> ComponentId;
    fn schema_def(&self) -> ComponentDef;
    fn snapshot_component(&self, world: &World, entity: Entity) -> Option<ComponentSnapshot>;
    fn update_component(&self, world: &World, entity: Entity) -> Option<DeltaUpdateComponent>;
    fn apply_update(
        &self,
        world: &mut World,
        entity: Entity,
        fields: &[(usize, FieldValue)],
    ) -> Result<()>;
    fn insert_component(
        &self,
        world: &mut World,
        entity: Entity,
        fields: &[FieldValue],
    ) -> Result<()>;
    fn added_entities(&self, world: &mut World) -> Vec<Entity>;
    fn changed_entities(&self, world: &mut World) -> Vec<Entity>;
    fn removed_entities(&self, world: &World) -> Vec<Entity>;
}

struct ComponentAdapterImpl<T: ReplicatedComponent> {
    component_id: ComponentId,
    fields: Vec<ReplicatedField>,
    _marker: PhantomData<T>,
}

impl<T: ReplicatedComponent> ComponentAdapterImpl<T> {
    fn new() -> Self {
        Self {
            component_id: ComponentId::new(T::COMPONENT_ID).expect("component id must be non-zero"),
            fields: T::fields(),
            _marker: PhantomData,
        }
    }

    fn snapshot_fields(&self, component: &T) -> Vec<FieldValue> {
        component.read_fields()
    }

    fn build_field_defs(&self) -> Vec<FieldDef> {
        self.fields
            .iter()
            .map(|field| {
                let mut def = FieldDef::new(
                    FieldId::new(field.id).expect("field id must be non-zero"),
                    field.codec,
                );
                if let Some(change) = field.change {
                    def = def.change(change);
                }
                def
            })
            .collect()
    }
}

impl<T: ReplicatedComponent> ComponentAdapter for ComponentAdapterImpl<T> {
    fn type_id(&self) -> TypeId {
        TypeId::of::<T>()
    }

    fn component_id(&self) -> ComponentId {
        self.component_id
    }

    fn schema_def(&self) -> ComponentDef {
        let mut def = ComponentDef::new(self.component_id);
        for field in self.build_field_defs() {
            def = def.field(field);
        }
        def
    }

    fn snapshot_component(&self, world: &World, entity: Entity) -> Option<ComponentSnapshot> {
        let component = world.get::<T>(entity)?;
        let fields = self.snapshot_fields(component);
        Some(ComponentSnapshot {
            id: self.component_id,
            fields,
        })
    }

    fn update_component(&self, world: &World, entity: Entity) -> Option<DeltaUpdateComponent> {
        let component = world.get::<T>(entity)?;
        let fields = self.snapshot_fields(component);
        let updates = fields.into_iter().enumerate().collect();
        Some(DeltaUpdateComponent {
            id: self.component_id,
            fields: updates,
        })
    }

    fn apply_update(
        &self,
        world: &mut World,
        entity: Entity,
        fields: &[(usize, FieldValue)],
    ) -> Result<()> {
        let mut component = world
            .get_mut::<T>(entity)
            .ok_or_else(|| anyhow!("missing component {:?}", self.component_id))?;
        for (index, value) in fields {
            component.apply_field(*index, *value)?;
        }
        Ok(())
    }

    fn insert_component(
        &self,
        world: &mut World,
        entity: Entity,
        fields: &[FieldValue],
    ) -> Result<()> {
        let component = T::from_fields(fields)?;
        world.entity_mut(entity).insert(component);
        Ok(())
    }

    fn added_entities(&self, world: &mut World) -> Vec<Entity> {
        let mut query = world.query_filtered::<Entity, bevy_ecs::query::Added<T>>();
        query.iter(world).collect()
    }

    fn changed_entities(&self, world: &mut World) -> Vec<Entity> {
        let mut query = world.query_filtered::<Entity, bevy_ecs::query::Changed<T>>();
        query.iter(world).collect()
    }

    fn removed_entities(&self, world: &World) -> Vec<Entity> {
        world.removed::<T>().collect()
    }
}

pub struct BevySchema {
    pub schema: Schema,
    adapters: Vec<Box<dyn ComponentAdapter>>,
    adapter_by_component: HashMap<ComponentId, usize>,
}

impl BevySchema {
    #[must_use]
    pub fn schema(&self) -> &Schema {
        &self.schema
    }

    pub(crate) fn adapters(&self) -> &[Box<dyn ComponentAdapter>] {
        &self.adapters
    }

    pub(crate) fn adapter_by_component(
        &self,
        component_id: ComponentId,
    ) -> Option<&dyn ComponentAdapter> {
        let index = self.adapter_by_component.get(&component_id).copied()?;
        self.adapters.get(index).map(|adapter| adapter.as_ref())
    }

    pub fn snapshot_entity(&self, world: &World, entity: Entity) -> Vec<ComponentSnapshot> {
        self.adapters
            .iter()
            .filter_map(|adapter| adapter.snapshot_component(world, entity))
            .collect()
    }

    pub fn apply_component_fields(
        &self,
        world: &mut World,
        entity: Entity,
        component_id: ComponentId,
        fields: &[(usize, FieldValue)],
    ) -> Result<()> {
        let adapter = self
            .adapter_by_component(component_id)
            .ok_or_else(|| anyhow!("unknown component {:?}", component_id))?;
        adapter.apply_update(world, entity, fields)
    }

    pub fn insert_component_fields(
        &self,
        world: &mut World,
        entity: Entity,
        component_id: ComponentId,
        fields: &[FieldValue],
    ) -> Result<()> {
        let adapter = self
            .adapter_by_component(component_id)
            .ok_or_else(|| anyhow!("unknown component {:?}", component_id))?;
        adapter.insert_component(world, entity, fields)
    }

    pub fn build_delta_update(
        &self,
        world: &World,
        entity: Entity,
        entity_id: codec::EntityId,
        component_ids: &[ComponentId],
    ) -> Option<DeltaUpdateEntity> {
        let mut components = Vec::with_capacity(component_ids.len());
        for component_id in component_ids {
            let adapter = self.adapter_by_component(*component_id)?;
            if let Some(update) = adapter.update_component(world, entity) {
                components.push(update);
            }
        }
        if components.is_empty() {
            None
        } else {
            Some(DeltaUpdateEntity {
                id: entity_id,
                components,
            })
        }
    }
}

#[derive(Default)]
pub struct BevySchemaBuilder {
    adapters: Vec<Box<dyn ComponentAdapter>>,
}

impl BevySchemaBuilder {
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    pub fn component<T: ReplicatedComponent + 'static>(&mut self) -> &mut Self {
        let adapter = ComponentAdapterImpl::<T>::new();
        if self
            .adapters
            .iter()
            .any(|existing| existing.type_id() == adapter.type_id())
        {
            return self;
        }
        self.adapters.push(Box::new(adapter));
        self
    }

    pub fn build(self) -> Result<BevySchema> {
        let mut components = Vec::with_capacity(self.adapters.len());
        for adapter in &self.adapters {
            components.push(adapter.schema_def());
        }
        let schema = Schema::new(components).map_err(|err| anyhow!("{err:?}"))?;
        let adapter_by_component = self
            .adapters
            .iter()
            .enumerate()
            .map(|(index, adapter)| (adapter.component_id(), index))
            .collect();
        Ok(BevySchema {
            schema,
            adapters: self.adapters,
            adapter_by_component,
        })
    }
}