@@ -46,6 +46,7 @@ mod operations;
|
||||
mod orset;
|
||||
mod plugin;
|
||||
mod rga;
|
||||
mod sync_component;
|
||||
mod tombstones;
|
||||
mod vector_clock;
|
||||
|
||||
@@ -67,5 +68,6 @@ pub use operations::*;
|
||||
pub use orset::*;
|
||||
pub use plugin::*;
|
||||
pub use rga::*;
|
||||
pub use sync_component::*;
|
||||
pub use tombstones::*;
|
||||
pub use vector_clock::*;
|
||||
|
||||
160
crates/lib/src/networking/sync_component.rs
Normal file
160
crates/lib/src/networking/sync_component.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
//! Sync Component trait and supporting types for RFC 0003
|
||||
//!
|
||||
//! This module defines the core trait that all synced components implement,
|
||||
//! along with the types used for strategy selection and merge decisions.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Sync strategy enum - determines how conflicts are resolved
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SyncStrategy {
|
||||
/// Last-Write-Wins: Newer timestamp wins, node ID tiebreaker for concurrent
|
||||
LastWriteWins,
|
||||
/// OR-Set: Observed-Remove Set for collections
|
||||
Set,
|
||||
/// Sequence: RGA (Replicated Growable Array) for ordered lists
|
||||
Sequence,
|
||||
/// Custom: User-defined conflict resolution
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// Result of comparing vector clocks
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ClockComparison {
|
||||
/// Remote vector clock is strictly newer
|
||||
RemoteNewer,
|
||||
/// Local vector clock is strictly newer
|
||||
LocalNewer,
|
||||
/// Concurrent (neither is newer)
|
||||
Concurrent,
|
||||
}
|
||||
|
||||
/// Decision made during component merge operation
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ComponentMergeDecision {
|
||||
/// Kept local value
|
||||
KeptLocal,
|
||||
/// Took remote value
|
||||
TookRemote,
|
||||
/// Merged both (for CRDTs)
|
||||
Merged,
|
||||
}
|
||||
|
||||
/// Core trait for synced components
|
||||
///
|
||||
/// This trait is automatically implemented by the `#[derive(Synced)]` macro.
|
||||
/// All synced components must implement this trait.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use bevy::prelude::*;
|
||||
/// use lib::networking::{SyncComponent, SyncStrategy, ClockComparison, ComponentMergeDecision};
|
||||
///
|
||||
/// // Example showing what the trait looks like - normally generated by #[derive(Synced)]
|
||||
/// #[derive(Component, Reflect, Clone, serde::Serialize, serde::Deserialize)]
|
||||
/// struct Health(f32);
|
||||
///
|
||||
/// // The SyncComponent trait defines these methods that the macro generates
|
||||
/// // You can serialize and deserialize components for sync
|
||||
/// ```
|
||||
pub trait SyncComponent: Component + Reflect + Sized {
|
||||
/// Schema version for this component
|
||||
const VERSION: u32;
|
||||
|
||||
/// Sync strategy for conflict resolution
|
||||
const STRATEGY: SyncStrategy;
|
||||
|
||||
/// Serialize this component to bytes
|
||||
///
|
||||
/// Uses bincode for efficient binary serialization.
|
||||
fn serialize_sync(&self) -> anyhow::Result<Vec<u8>>;
|
||||
|
||||
/// Deserialize this component from bytes
|
||||
///
|
||||
/// Uses bincode to deserialize from the format created by `serialize_sync`.
|
||||
fn deserialize_sync(data: &[u8]) -> anyhow::Result<Self>;
|
||||
|
||||
/// Merge remote state with local state
|
||||
///
|
||||
/// The merge logic is strategy-specific:
|
||||
/// - **LWW**: Takes newer value based on vector clock, uses tiebreaker for concurrent
|
||||
/// - **Set**: Merges both sets (OR-Set semantics)
|
||||
/// - **Sequence**: Merges sequences preserving order (RGA semantics)
|
||||
/// - **Custom**: Calls user-defined ConflictResolver
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `remote` - The remote state to merge
|
||||
/// * `clock_cmp` - Result of comparing local and remote vector clocks
|
||||
///
|
||||
/// # Returns
|
||||
/// Decision about what happened during the merge
|
||||
fn merge(&mut self, remote: Self, clock_cmp: ClockComparison) -> ComponentMergeDecision;
|
||||
}
|
||||
|
||||
/// Marker component for entities that should be synced
|
||||
///
|
||||
/// Add this to any entity with synced components to enable automatic
|
||||
/// change detection and synchronization.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use bevy::prelude::*;
|
||||
/// use lib::networking::Synced;
|
||||
/// use sync_macros::Synced as SyncedDerive;
|
||||
///
|
||||
/// #[derive(Component, Reflect, Clone, serde::Serialize, serde::Deserialize)]
|
||||
/// #[derive(SyncedDerive)]
|
||||
/// #[sync(version = 1, strategy = "LastWriteWins")]
|
||||
/// struct Health(f32);
|
||||
///
|
||||
/// #[derive(Component, Reflect, Clone, serde::Serialize, serde::Deserialize)]
|
||||
/// #[derive(SyncedDerive)]
|
||||
/// #[sync(version = 1, strategy = "LastWriteWins")]
|
||||
/// struct Position { x: f32, y: f32 }
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// world.spawn((
|
||||
/// Health(100.0),
|
||||
/// Position { x: 0.0, y: 0.0 },
|
||||
/// Synced, // Marker enables sync
|
||||
/// ));
|
||||
/// ```
|
||||
#[derive(Component, Reflect, Default, Clone, Copy)]
|
||||
#[reflect(Component)]
|
||||
pub struct Synced;
|
||||
|
||||
/// Diagnostic component for debugging sync issues
|
||||
///
|
||||
/// Add this to an entity to get detailed diagnostic output about
|
||||
/// its sync status.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use bevy::prelude::*;
|
||||
/// use lib::networking::DiagnoseSync;
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let entity = world.spawn_empty().id();
|
||||
/// world.entity_mut(entity).insert(DiagnoseSync);
|
||||
/// // A diagnostic system will check this entity and log sync status
|
||||
/// ```
|
||||
#[derive(Component, Reflect, Default)]
|
||||
#[reflect(Component)]
|
||||
pub struct DiagnoseSync;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn strategy_enum_works() {
|
||||
assert_eq!(SyncStrategy::LastWriteWins, SyncStrategy::LastWriteWins);
|
||||
assert_ne!(SyncStrategy::LastWriteWins, SyncStrategy::Set);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clock_comparison_works() {
|
||||
assert_eq!(ClockComparison::RemoteNewer, ClockComparison::RemoteNewer);
|
||||
assert_ne!(ClockComparison::RemoteNewer, ClockComparison::LocalNewer);
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,8 @@ use serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
};
|
||||
// Re-export the macros
|
||||
pub use sync_macros::{
|
||||
Synced,
|
||||
synced,
|
||||
};
|
||||
// Re-export the Synced derive macro
|
||||
pub use sync_macros::Synced;
|
||||
|
||||
pub type NodeId = String;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user