1#![forbid(unsafe_code)]
4
5use sectorsync_core::prelude::{
6 BarrierId, BarrierState, ClientId, CommandEnvelope, CommandId, CommandPriority, ComponentId,
7 ComponentStore, EntityId, EventId, EventKind, EventPriority, OwnerEpoch, ReplicationPlan,
8 Station, StationEvent, StationId, Tick,
9};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum FrameKind {
14 Replication = 0,
16 CommandAck = 1,
18 Barrier = 2,
20 Command = 3,
22 StationEvent = 4,
24 CommandDispatch = 5,
26}
27
28impl FrameKind {
29 pub const fn from_byte(byte: u8) -> Option<Self> {
31 match byte {
32 0 => Some(Self::Replication),
33 1 => Some(Self::CommandAck),
34 2 => Some(Self::Barrier),
35 3 => Some(Self::Command),
36 4 => Some(Self::StationEvent),
37 5 => Some(Self::CommandDispatch),
38 _ => None,
39 }
40 }
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct ReplicationFrame {
46 pub client_id: ClientId,
48 pub server_tick: Tick,
50 pub entity_count: u32,
52 pub estimated_payload_bytes: u32,
54 pub entities: Vec<EntityDelta>,
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct EntityDelta {
61 pub entity_id: EntityId,
63 pub owner_epoch: OwnerEpoch,
65 pub components: Vec<ComponentDelta>,
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct ComponentDelta {
72 pub component_id: ComponentId,
74 pub version: u64,
76 pub flags: u8,
78 pub bytes: Vec<u8>,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct ReplicationFrameLimits {
85 pub max_entity_deltas: usize,
87 pub max_components_per_entity: usize,
89 pub max_component_bytes: usize,
91}
92
93impl Default for ReplicationFrameLimits {
94 fn default() -> Self {
95 Self {
96 max_entity_deltas: 256,
97 max_components_per_entity: 16,
98 max_component_bytes: 1024,
99 }
100 }
101}
102
103#[derive(Clone, Debug, Default, PartialEq, Eq)]
105pub struct ComponentSelection {
106 pub component_ids: Vec<ComponentId>,
108}
109
110#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
112pub struct ReplicationFrameBuildStats {
113 pub planned_entities: usize,
115 pub encoded_entities: usize,
117 pub encoded_components: usize,
119 pub skipped_entities_by_limit: usize,
121 pub skipped_components_by_limit: usize,
123 pub skipped_components_by_size: usize,
125}
126
127#[derive(Clone, Debug, PartialEq, Eq)]
129pub struct ReplicationFrameBuild {
130 pub frame: ReplicationFrame,
132 pub stats: ReplicationFrameBuildStats,
134}
135
136#[derive(Clone, Copy, Debug, Default)]
138pub struct ReplicationFrameBuilder {
139 pub limits: ReplicationFrameLimits,
141}
142
143impl ReplicationFrameBuilder {
144 pub const fn new(limits: ReplicationFrameLimits) -> Self {
146 Self { limits }
147 }
148
149 pub fn build(
151 &self,
152 client_id: ClientId,
153 server_tick: Tick,
154 station: &Station,
155 plan: &ReplicationPlan,
156 components: &ComponentStore,
157 selection: &ComponentSelection,
158 ) -> ReplicationFrameBuild {
159 let mut stats = ReplicationFrameBuildStats {
160 planned_entities: plan.entities.len(),
161 ..ReplicationFrameBuildStats::default()
162 };
163 let mut entity_deltas =
164 Vec::with_capacity(plan.entities.len().min(self.limits.max_entity_deltas));
165 let mut estimated_payload_bytes = 0_usize;
166
167 for handle in &plan.entities {
168 if entity_deltas.len() >= self.limits.max_entity_deltas {
169 stats.skipped_entities_by_limit += 1;
170 continue;
171 }
172 let Some(entity) = station.get(*handle) else {
173 continue;
174 };
175
176 let mut component_deltas = Vec::new();
177 for component_id in &selection.component_ids {
178 if component_deltas.len() >= self.limits.max_components_per_entity {
179 stats.skipped_components_by_limit += 1;
180 continue;
181 }
182 let Some(blob) = components.get_blob(*component_id, *handle) else {
183 continue;
184 };
185 if !blob.dirty {
186 continue;
187 }
188 if blob.bytes.len() > self.limits.max_component_bytes {
189 stats.skipped_components_by_size += 1;
190 continue;
191 }
192 estimated_payload_bytes = estimated_payload_bytes
193 .saturating_add(2 + 8 + 1 + 4)
194 .saturating_add(blob.bytes.len());
195 component_deltas.push(ComponentDelta {
196 component_id: *component_id,
197 version: blob.version,
198 flags: 0,
199 bytes: blob.bytes.clone(),
200 });
201 }
202
203 if component_deltas.is_empty() {
204 continue;
205 }
206
207 stats.encoded_components += component_deltas.len();
208 estimated_payload_bytes = estimated_payload_bytes.saturating_add(8 + 8 + 2);
209 entity_deltas.push(EntityDelta {
210 entity_id: entity.id,
211 owner_epoch: entity.role.owner_epoch(),
212 components: component_deltas,
213 });
214 }
215
216 stats.encoded_entities = entity_deltas.len();
217 ReplicationFrameBuild {
218 frame: ReplicationFrame {
219 client_id,
220 server_tick,
221 entity_count: u32::try_from(plan.entities.len()).unwrap_or(u32::MAX),
222 estimated_payload_bytes: u32::try_from(estimated_payload_bytes).unwrap_or(u32::MAX),
223 entities: entity_deltas,
224 },
225 stats,
226 }
227 }
228}
229
230#[derive(Clone, Debug, PartialEq, Eq)]
232pub struct CommandAckFrame {
233 pub client_id: ClientId,
235 pub command_id: CommandId,
237 pub server_tick: Tick,
239 pub accepted: bool,
241 pub reason_code: u16,
243}
244
245#[derive(Clone, Debug, PartialEq, Eq)]
251pub struct CommandFrame {
252 pub client_id: ClientId,
254 pub command_id: CommandId,
256 pub entity_id: EntityId,
258 pub sequence: u64,
260 pub kind: u32,
262 pub priority: CommandPriority,
264 pub payload: Vec<u8>,
266}
267
268impl CommandFrame {
269 pub fn into_envelope(self, received_at: Tick) -> CommandEnvelope {
271 CommandEnvelope {
272 id: self.command_id,
273 client_id: self.client_id,
274 entity_id: self.entity_id,
275 sequence: self.sequence,
276 received_at,
277 kind: self.kind,
278 priority: self.priority,
279 payload: self.payload,
280 }
281 }
282
283 pub fn from_envelope(envelope: &CommandEnvelope) -> Self {
286 Self {
287 client_id: envelope.client_id,
288 command_id: envelope.id,
289 entity_id: envelope.entity_id,
290 sequence: envelope.sequence,
291 kind: envelope.kind,
292 priority: envelope.priority,
293 payload: envelope.payload.clone(),
294 }
295 }
296}
297
298#[derive(Clone, Debug, PartialEq, Eq)]
303pub struct CommandDispatchFrame {
304 pub station_id: StationId,
306 pub client_id: ClientId,
308 pub command_id: CommandId,
310 pub entity_id: EntityId,
312 pub sequence: u64,
314 pub received_at: Tick,
316 pub kind: u32,
318 pub priority: CommandPriority,
320 pub payload: Vec<u8>,
322}
323
324impl CommandDispatchFrame {
325 pub fn from_envelope(station_id: StationId, envelope: &CommandEnvelope) -> Self {
327 Self {
328 station_id,
329 client_id: envelope.client_id,
330 command_id: envelope.id,
331 entity_id: envelope.entity_id,
332 sequence: envelope.sequence,
333 received_at: envelope.received_at,
334 kind: envelope.kind,
335 priority: envelope.priority,
336 payload: envelope.payload.clone(),
337 }
338 }
339
340 pub fn into_envelope(self) -> CommandEnvelope {
342 CommandEnvelope {
343 id: self.command_id,
344 client_id: self.client_id,
345 entity_id: self.entity_id,
346 sequence: self.sequence,
347 received_at: self.received_at,
348 kind: self.kind,
349 priority: self.priority,
350 payload: self.payload,
351 }
352 }
353}
354
355#[derive(Clone, Debug, PartialEq, Eq)]
357pub struct StationEventFrame {
358 pub event_id: EventId,
360 pub source_station: StationId,
362 pub target_station: StationId,
364 pub source_tick: Tick,
366 pub target_tick: Tick,
368 pub priority: EventPriority,
370 pub kind: EventKind,
372}
373
374impl StationEventFrame {
375 pub fn from_event(event: &StationEvent) -> Self {
377 Self {
378 event_id: event.id,
379 source_station: event.source,
380 target_station: event.target,
381 source_tick: event.source_tick,
382 target_tick: event.target_tick,
383 priority: event.priority,
384 kind: event.kind.clone(),
385 }
386 }
387
388 pub fn into_event(self) -> StationEvent {
390 StationEvent {
391 id: self.event_id,
392 source: self.source_station,
393 target: self.target_station,
394 source_tick: self.source_tick,
395 target_tick: self.target_tick,
396 priority: self.priority,
397 kind: self.kind,
398 }
399 }
400}
401
402#[derive(Clone, Debug, PartialEq, Eq)]
404pub struct BarrierFrame {
405 pub client_id: ClientId,
407 pub barrier_id: BarrierId,
409 pub server_tick: Tick,
411 pub state: BarrierState,
413}
414
415#[derive(Clone, Debug, PartialEq, Eq)]
417pub enum RuntimeFrame {
418 Replication(ReplicationFrame),
420 Command(CommandFrame),
422 CommandDispatch(CommandDispatchFrame),
424 CommandAck(CommandAckFrame),
426 Barrier(BarrierFrame),
428 StationEvent(StationEventFrame),
430}
431
432pub trait FrameEncoder {
434 type Error;
436
437 fn encode_replication(
439 &mut self,
440 frame: &ReplicationFrame,
441 out: &mut Vec<u8>,
442 ) -> Result<(), Self::Error>;
443
444 fn encode_command_ack(
446 &mut self,
447 frame: &CommandAckFrame,
448 out: &mut Vec<u8>,
449 ) -> Result<(), Self::Error>;
450
451 fn encode_command(
453 &mut self,
454 frame: &CommandFrame,
455 out: &mut Vec<u8>,
456 ) -> Result<(), Self::Error>;
457
458 fn encode_command_dispatch(
460 &mut self,
461 frame: &CommandDispatchFrame,
462 out: &mut Vec<u8>,
463 ) -> Result<(), Self::Error>;
464
465 fn encode_station_event(
467 &mut self,
468 frame: &StationEventFrame,
469 out: &mut Vec<u8>,
470 ) -> Result<(), Self::Error>;
471
472 fn encode_barrier(
474 &mut self,
475 frame: &BarrierFrame,
476 out: &mut Vec<u8>,
477 ) -> Result<(), Self::Error>;
478}
479
480pub trait FrameDecoder {
482 type Error;
484
485 fn decode(&mut self, input: &[u8]) -> Result<RuntimeFrame, Self::Error>;
487}
488
489#[derive(Clone, Debug, PartialEq, Eq)]
491pub enum BinaryDecodeError {
492 Empty,
494 UnknownFrameKind(u8),
496 Truncated {
498 needed: usize,
500 available: usize,
502 },
503 InvalidBarrierState(u8),
505 InvalidCommandPriority(u8),
507 InvalidEventPriority(u8),
509 InvalidEventKind(u8),
511 TrailingBytes(usize),
513}
514
515impl core::fmt::Display for BinaryDecodeError {
516 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
517 match self {
518 Self::Empty => f.write_str("empty frame"),
519 Self::UnknownFrameKind(kind) => write!(f, "unknown frame kind {kind}"),
520 Self::Truncated { needed, available } => {
521 write!(f, "truncated frame: needed {needed}, available {available}")
522 }
523 Self::InvalidBarrierState(state) => write!(f, "invalid barrier state {state}"),
524 Self::InvalidCommandPriority(priority) => {
525 write!(f, "invalid command priority {priority}")
526 }
527 Self::InvalidEventPriority(priority) => write!(f, "invalid event priority {priority}"),
528 Self::InvalidEventKind(kind) => write!(f, "invalid event kind {kind}"),
529 Self::TrailingBytes(bytes) => write!(f, "frame has {bytes} trailing bytes"),
530 }
531 }
532}
533
534impl std::error::Error for BinaryDecodeError {}
535
536#[derive(Clone, Debug, PartialEq, Eq)]
538pub enum BinaryEncodeError {
539 TooManyItems {
541 field: &'static str,
543 actual: usize,
545 },
546 PayloadTooLarge {
548 field: &'static str,
550 actual: usize,
552 },
553}
554
555impl core::fmt::Display for BinaryEncodeError {
556 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
557 match self {
558 Self::TooManyItems { field, actual } => {
559 write!(f, "{field} has too many items: {actual}")
560 }
561 Self::PayloadTooLarge { field, actual } => {
562 write!(f, "{field} payload is too large: {actual} bytes")
563 }
564 }
565 }
566}
567
568impl std::error::Error for BinaryEncodeError {}
569
570#[derive(Clone, Copy, Debug, Default)]
572pub struct BinaryFrameDecoder;
573
574impl FrameDecoder for BinaryFrameDecoder {
575 type Error = BinaryDecodeError;
576
577 fn decode(&mut self, input: &[u8]) -> Result<RuntimeFrame, Self::Error> {
578 let mut cursor = Cursor::new(input);
579 let kind_byte = cursor.read_u8()?;
580 let kind = FrameKind::from_byte(kind_byte)
581 .ok_or(BinaryDecodeError::UnknownFrameKind(kind_byte))?;
582 let frame = match kind {
583 FrameKind::Replication => RuntimeFrame::Replication(ReplicationFrame {
584 client_id: ClientId::new(cursor.read_u64()?),
585 server_tick: Tick::new(cursor.read_u64()?),
586 entity_count: cursor.read_u32()?,
587 estimated_payload_bytes: cursor.read_u32()?,
588 entities: {
589 let entity_delta_count = cursor.read_u32()? as usize;
590 let mut entities = Vec::with_capacity(entity_delta_count);
591 for _ in 0..entity_delta_count {
592 let entity_id = EntityId::new(cursor.read_u64()?);
593 let owner_epoch = OwnerEpoch::new(cursor.read_u64()?);
594 let component_count = cursor.read_u16()? as usize;
595 let mut components = Vec::with_capacity(component_count);
596 for _ in 0..component_count {
597 let component_id = ComponentId::new(cursor.read_u16()?);
598 let version = cursor.read_u64()?;
599 let flags = cursor.read_u8()?;
600 let byte_len = cursor.read_u32()? as usize;
601 let bytes = cursor.read_bytes(byte_len)?;
602 components.push(ComponentDelta {
603 component_id,
604 version,
605 flags,
606 bytes,
607 });
608 }
609 entities.push(EntityDelta {
610 entity_id,
611 owner_epoch,
612 components,
613 });
614 }
615 entities
616 },
617 }),
618 FrameKind::CommandAck => RuntimeFrame::CommandAck(CommandAckFrame {
619 client_id: ClientId::new(cursor.read_u64()?),
620 command_id: CommandId::new(cursor.read_u64()?),
621 server_tick: Tick::new(cursor.read_u64()?),
622 accepted: cursor.read_u8()? != 0,
623 reason_code: cursor.read_u16()?,
624 }),
625 FrameKind::Command => RuntimeFrame::Command(CommandFrame {
626 client_id: ClientId::new(cursor.read_u64()?),
627 command_id: CommandId::new(cursor.read_u64()?),
628 entity_id: EntityId::new(cursor.read_u64()?),
629 sequence: cursor.read_u64()?,
630 kind: cursor.read_u32()?,
631 priority: decode_command_priority(cursor.read_u8()?)?,
632 payload: {
633 let byte_len = cursor.read_u32()? as usize;
634 cursor.read_bytes(byte_len)?
635 },
636 }),
637 FrameKind::CommandDispatch => RuntimeFrame::CommandDispatch(CommandDispatchFrame {
638 station_id: StationId::new(cursor.read_u32()?),
639 client_id: ClientId::new(cursor.read_u64()?),
640 command_id: CommandId::new(cursor.read_u64()?),
641 entity_id: EntityId::new(cursor.read_u64()?),
642 sequence: cursor.read_u64()?,
643 received_at: Tick::new(cursor.read_u64()?),
644 kind: cursor.read_u32()?,
645 priority: decode_command_priority(cursor.read_u8()?)?,
646 payload: {
647 let byte_len = cursor.read_u32()? as usize;
648 cursor.read_bytes(byte_len)?
649 },
650 }),
651 FrameKind::Barrier => RuntimeFrame::Barrier(BarrierFrame {
652 client_id: ClientId::new(cursor.read_u64()?),
653 barrier_id: BarrierId::new(cursor.read_u64()?),
654 server_tick: Tick::new(cursor.read_u64()?),
655 state: decode_barrier_state(cursor.read_u8()?)?,
656 }),
657 FrameKind::StationEvent => RuntimeFrame::StationEvent(StationEventFrame {
658 event_id: EventId::new(cursor.read_u64()?),
659 source_station: StationId::new(cursor.read_u32()?),
660 target_station: StationId::new(cursor.read_u32()?),
661 source_tick: Tick::new(cursor.read_u64()?),
662 target_tick: Tick::new(cursor.read_u64()?),
663 priority: decode_event_priority(cursor.read_u8()?)?,
664 kind: decode_event_kind(&mut cursor)?,
665 }),
666 };
667 cursor.finish()?;
668 Ok(frame)
669 }
670}
671
672#[derive(Clone, Copy, Debug, Default)]
674pub struct BinaryFrameEncoder;
675
676impl FrameEncoder for BinaryFrameEncoder {
677 type Error = BinaryEncodeError;
678
679 fn encode_replication(
680 &mut self,
681 frame: &ReplicationFrame,
682 out: &mut Vec<u8>,
683 ) -> Result<(), Self::Error> {
684 out.push(FrameKind::Replication as u8);
685 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
686 out.extend_from_slice(&frame.server_tick.get().to_le_bytes());
687 out.extend_from_slice(&frame.entity_count.to_le_bytes());
688 out.extend_from_slice(&frame.estimated_payload_bytes.to_le_bytes());
689 write_len_u32("replication.entities", frame.entities.len(), out)?;
690 for entity in &frame.entities {
691 out.extend_from_slice(&entity.entity_id.get().to_le_bytes());
692 out.extend_from_slice(&entity.owner_epoch.get().to_le_bytes());
693 write_len_u16(
694 "replication.entity.components",
695 entity.components.len(),
696 out,
697 )?;
698 for component in &entity.components {
699 out.extend_from_slice(&component.component_id.get().to_le_bytes());
700 out.extend_from_slice(&component.version.to_le_bytes());
701 out.push(component.flags);
702 write_bytes("replication.component.bytes", &component.bytes, out)?;
703 }
704 }
705 Ok(())
706 }
707
708 fn encode_command_ack(
709 &mut self,
710 frame: &CommandAckFrame,
711 out: &mut Vec<u8>,
712 ) -> Result<(), Self::Error> {
713 out.push(FrameKind::CommandAck as u8);
714 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
715 out.extend_from_slice(&frame.command_id.get().to_le_bytes());
716 out.extend_from_slice(&frame.server_tick.get().to_le_bytes());
717 out.push(u8::from(frame.accepted));
718 out.extend_from_slice(&frame.reason_code.to_le_bytes());
719 Ok(())
720 }
721
722 fn encode_command(
723 &mut self,
724 frame: &CommandFrame,
725 out: &mut Vec<u8>,
726 ) -> Result<(), Self::Error> {
727 out.push(FrameKind::Command as u8);
728 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
729 out.extend_from_slice(&frame.command_id.get().to_le_bytes());
730 out.extend_from_slice(&frame.entity_id.get().to_le_bytes());
731 out.extend_from_slice(&frame.sequence.to_le_bytes());
732 out.extend_from_slice(&frame.kind.to_le_bytes());
733 out.push(encode_command_priority(frame.priority));
734 write_bytes("command.payload", &frame.payload, out)?;
735 Ok(())
736 }
737
738 fn encode_command_dispatch(
739 &mut self,
740 frame: &CommandDispatchFrame,
741 out: &mut Vec<u8>,
742 ) -> Result<(), Self::Error> {
743 out.push(FrameKind::CommandDispatch as u8);
744 out.extend_from_slice(&frame.station_id.get().to_le_bytes());
745 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
746 out.extend_from_slice(&frame.command_id.get().to_le_bytes());
747 out.extend_from_slice(&frame.entity_id.get().to_le_bytes());
748 out.extend_from_slice(&frame.sequence.to_le_bytes());
749 out.extend_from_slice(&frame.received_at.get().to_le_bytes());
750 out.extend_from_slice(&frame.kind.to_le_bytes());
751 out.push(encode_command_priority(frame.priority));
752 write_bytes("command_dispatch.payload", &frame.payload, out)?;
753 Ok(())
754 }
755
756 fn encode_station_event(
757 &mut self,
758 frame: &StationEventFrame,
759 out: &mut Vec<u8>,
760 ) -> Result<(), Self::Error> {
761 out.push(FrameKind::StationEvent as u8);
762 out.extend_from_slice(&frame.event_id.get().to_le_bytes());
763 out.extend_from_slice(&frame.source_station.get().to_le_bytes());
764 out.extend_from_slice(&frame.target_station.get().to_le_bytes());
765 out.extend_from_slice(&frame.source_tick.get().to_le_bytes());
766 out.extend_from_slice(&frame.target_tick.get().to_le_bytes());
767 out.push(encode_event_priority(frame.priority));
768 encode_event_kind(&frame.kind, out);
769 Ok(())
770 }
771
772 fn encode_barrier(
773 &mut self,
774 frame: &BarrierFrame,
775 out: &mut Vec<u8>,
776 ) -> Result<(), Self::Error> {
777 out.push(FrameKind::Barrier as u8);
778 out.extend_from_slice(&frame.client_id.get().to_le_bytes());
779 out.extend_from_slice(&frame.barrier_id.get().to_le_bytes());
780 out.extend_from_slice(&frame.server_tick.get().to_le_bytes());
781 out.push(encode_barrier_state(frame.state));
782 Ok(())
783 }
784}
785
786fn write_len_u16(
787 field: &'static str,
788 len: usize,
789 out: &mut Vec<u8>,
790) -> Result<(), BinaryEncodeError> {
791 let len =
792 u16::try_from(len).map_err(|_| BinaryEncodeError::TooManyItems { field, actual: len })?;
793 out.extend_from_slice(&len.to_le_bytes());
794 Ok(())
795}
796
797fn write_len_u32(
798 field: &'static str,
799 len: usize,
800 out: &mut Vec<u8>,
801) -> Result<(), BinaryEncodeError> {
802 let len =
803 u32::try_from(len).map_err(|_| BinaryEncodeError::TooManyItems { field, actual: len })?;
804 out.extend_from_slice(&len.to_le_bytes());
805 Ok(())
806}
807
808fn write_bytes(
809 field: &'static str,
810 bytes: &[u8],
811 out: &mut Vec<u8>,
812) -> Result<(), BinaryEncodeError> {
813 let len = u32::try_from(bytes.len()).map_err(|_| BinaryEncodeError::PayloadTooLarge {
814 field,
815 actual: bytes.len(),
816 })?;
817 out.extend_from_slice(&len.to_le_bytes());
818 out.extend_from_slice(bytes);
819 Ok(())
820}
821
822fn encode_barrier_state(state: BarrierState) -> u8 {
823 match state {
824 BarrierState::Running => 0,
825 BarrierState::Requested => 1,
826 BarrierState::WaitingTickBoundary => 2,
827 BarrierState::Frozen => 3,
828 BarrierState::Resuming => 4,
829 }
830}
831
832fn decode_barrier_state(state: u8) -> Result<BarrierState, BinaryDecodeError> {
833 match state {
834 0 => Ok(BarrierState::Running),
835 1 => Ok(BarrierState::Requested),
836 2 => Ok(BarrierState::WaitingTickBoundary),
837 3 => Ok(BarrierState::Frozen),
838 4 => Ok(BarrierState::Resuming),
839 _ => Err(BinaryDecodeError::InvalidBarrierState(state)),
840 }
841}
842
843fn encode_command_priority(priority: CommandPriority) -> u8 {
844 match priority {
845 CommandPriority::Normal => 0,
846 CommandPriority::High => 1,
847 CommandPriority::Low => 2,
848 }
849}
850
851fn decode_command_priority(priority: u8) -> Result<CommandPriority, BinaryDecodeError> {
852 match priority {
853 0 => Ok(CommandPriority::Normal),
854 1 => Ok(CommandPriority::High),
855 2 => Ok(CommandPriority::Low),
856 _ => Err(BinaryDecodeError::InvalidCommandPriority(priority)),
857 }
858}
859
860fn encode_event_priority(priority: EventPriority) -> u8 {
861 match priority {
862 EventPriority::Critical => 0,
863 EventPriority::Important => 1,
864 EventPriority::BestEffort => 2,
865 }
866}
867
868fn decode_event_priority(priority: u8) -> Result<EventPriority, BinaryDecodeError> {
869 match priority {
870 0 => Ok(EventPriority::Critical),
871 1 => Ok(EventPriority::Important),
872 2 => Ok(EventPriority::BestEffort),
873 _ => Err(BinaryDecodeError::InvalidEventPriority(priority)),
874 }
875}
876
877fn encode_event_kind(kind: &EventKind, out: &mut Vec<u8>) {
878 match kind {
879 EventKind::Custom(kind) => {
880 out.push(0);
881 out.extend_from_slice(&kind.to_le_bytes());
882 }
883 EventKind::HandoffPrepare { entity_id } => {
884 out.push(1);
885 out.extend_from_slice(&entity_id.get().to_le_bytes());
886 }
887 EventKind::HandoffCommit {
888 entity_id,
889 owner_epoch,
890 } => {
891 out.push(2);
892 out.extend_from_slice(&entity_id.get().to_le_bytes());
893 out.extend_from_slice(&owner_epoch.get().to_le_bytes());
894 }
895 }
896}
897
898fn decode_event_kind(cursor: &mut Cursor<'_>) -> Result<EventKind, BinaryDecodeError> {
899 match cursor.read_u8()? {
900 0 => Ok(EventKind::Custom(cursor.read_u32()?)),
901 1 => Ok(EventKind::HandoffPrepare {
902 entity_id: EntityId::new(cursor.read_u64()?),
903 }),
904 2 => Ok(EventKind::HandoffCommit {
905 entity_id: EntityId::new(cursor.read_u64()?),
906 owner_epoch: OwnerEpoch::new(cursor.read_u64()?),
907 }),
908 kind => Err(BinaryDecodeError::InvalidEventKind(kind)),
909 }
910}
911
912struct Cursor<'a> {
913 input: &'a [u8],
914 offset: usize,
915}
916
917impl<'a> Cursor<'a> {
918 fn new(input: &'a [u8]) -> Self {
919 Self { input, offset: 0 }
920 }
921
922 fn read_u8(&mut self) -> Result<u8, BinaryDecodeError> {
923 self.require(1)?;
924 let value = self.input[self.offset];
925 self.offset += 1;
926 Ok(value)
927 }
928
929 fn read_u16(&mut self) -> Result<u16, BinaryDecodeError> {
930 let bytes = self.read_array::<2>()?;
931 Ok(u16::from_le_bytes(bytes))
932 }
933
934 fn read_u32(&mut self) -> Result<u32, BinaryDecodeError> {
935 let bytes = self.read_array::<4>()?;
936 Ok(u32::from_le_bytes(bytes))
937 }
938
939 fn read_u64(&mut self) -> Result<u64, BinaryDecodeError> {
940 let bytes = self.read_array::<8>()?;
941 Ok(u64::from_le_bytes(bytes))
942 }
943
944 fn read_array<const N: usize>(&mut self) -> Result<[u8; N], BinaryDecodeError> {
945 self.require(N)?;
946 let mut out = [0_u8; N];
947 out.copy_from_slice(&self.input[self.offset..self.offset + N]);
948 self.offset += N;
949 Ok(out)
950 }
951
952 fn read_bytes(&mut self, len: usize) -> Result<Vec<u8>, BinaryDecodeError> {
953 self.require(len)?;
954 let bytes = self.input[self.offset..self.offset + len].to_vec();
955 self.offset += len;
956 Ok(bytes)
957 }
958
959 fn require(&self, count: usize) -> Result<(), BinaryDecodeError> {
960 let needed = self.offset.saturating_add(count);
961 if needed > self.input.len() {
962 Err(BinaryDecodeError::Truncated {
963 needed,
964 available: self.input.len(),
965 })
966 } else {
967 Ok(())
968 }
969 }
970
971 fn finish(&self) -> Result<(), BinaryDecodeError> {
972 if self.offset == self.input.len() {
973 Ok(())
974 } else {
975 Err(BinaryDecodeError::TrailingBytes(
976 self.input.len().saturating_sub(self.offset),
977 ))
978 }
979 }
980}
981
982#[cfg(test)]
983mod tests {
984 use super::*;
985
986 #[test]
987 fn binary_codec_roundtrips_replication_frame() {
988 let frame = ReplicationFrame {
989 client_id: ClientId::new(9),
990 server_tick: Tick::new(42),
991 entity_count: 17,
992 estimated_payload_bytes: 544,
993 entities: vec![EntityDelta {
994 entity_id: EntityId::new(100),
995 owner_epoch: OwnerEpoch::new(2),
996 components: vec![ComponentDelta {
997 component_id: ComponentId::new(1),
998 version: 9,
999 flags: 0,
1000 bytes: vec![1, 2, 3, 4],
1001 }],
1002 }],
1003 };
1004 let mut encoder = BinaryFrameEncoder;
1005 let mut bytes = Vec::new();
1006 encoder
1007 .encode_replication(&frame, &mut bytes)
1008 .expect("encoder is infallible");
1009
1010 let decoded = BinaryFrameDecoder
1011 .decode(&bytes)
1012 .expect("decode should work");
1013 assert_eq!(decoded, RuntimeFrame::Replication(frame));
1014 }
1015
1016 #[test]
1017 fn binary_codec_roundtrips_command_ack_frame() {
1018 let frame = CommandAckFrame {
1019 client_id: ClientId::new(1),
1020 command_id: CommandId::new(2),
1021 server_tick: Tick::new(3),
1022 accepted: false,
1023 reason_code: 7,
1024 };
1025 let mut encoder = BinaryFrameEncoder;
1026 let mut bytes = Vec::new();
1027 encoder
1028 .encode_command_ack(&frame, &mut bytes)
1029 .expect("encoder is infallible");
1030
1031 let decoded = BinaryFrameDecoder
1032 .decode(&bytes)
1033 .expect("decode should work");
1034 assert_eq!(decoded, RuntimeFrame::CommandAck(frame));
1035 }
1036
1037 #[test]
1038 fn binary_codec_roundtrips_command_frame() {
1039 let frame = CommandFrame {
1040 client_id: ClientId::new(1),
1041 command_id: CommandId::new(2),
1042 entity_id: EntityId::new(3),
1043 sequence: 4,
1044 kind: 5,
1045 priority: CommandPriority::High,
1046 payload: vec![9, 8, 7],
1047 };
1048 let mut encoder = BinaryFrameEncoder;
1049 let mut bytes = Vec::new();
1050 encoder
1051 .encode_command(&frame, &mut bytes)
1052 .expect("encoder is infallible");
1053
1054 let decoded = BinaryFrameDecoder
1055 .decode(&bytes)
1056 .expect("decode should work");
1057 assert_eq!(decoded, RuntimeFrame::Command(frame));
1058 }
1059
1060 #[test]
1061 fn binary_codec_roundtrips_command_dispatch_frame() {
1062 let frame = CommandDispatchFrame {
1063 station_id: StationId::new(10),
1064 client_id: ClientId::new(1),
1065 command_id: CommandId::new(2),
1066 entity_id: EntityId::new(3),
1067 sequence: 4,
1068 received_at: Tick::new(99),
1069 kind: 5,
1070 priority: CommandPriority::High,
1071 payload: vec![9, 8, 7],
1072 };
1073 let mut encoder = BinaryFrameEncoder;
1074 let mut bytes = Vec::new();
1075 encoder
1076 .encode_command_dispatch(&frame, &mut bytes)
1077 .expect("encoder is infallible");
1078
1079 let decoded = BinaryFrameDecoder
1080 .decode(&bytes)
1081 .expect("decode should work");
1082 assert_eq!(decoded, RuntimeFrame::CommandDispatch(frame));
1083 }
1084
1085 #[test]
1086 fn command_frame_converts_to_runtime_envelope() {
1087 let frame = CommandFrame {
1088 client_id: ClientId::new(1),
1089 command_id: CommandId::new(2),
1090 entity_id: EntityId::new(3),
1091 sequence: 4,
1092 kind: 5,
1093 priority: CommandPriority::Low,
1094 payload: vec![1, 2, 3],
1095 };
1096
1097 let envelope = frame.clone().into_envelope(Tick::new(99));
1098 assert_eq!(envelope.id, frame.command_id);
1099 assert_eq!(envelope.received_at, Tick::new(99));
1100 assert_eq!(CommandFrame::from_envelope(&envelope), frame);
1101 }
1102
1103 #[test]
1104 fn command_dispatch_frame_preserves_stamped_envelope_tick() {
1105 let envelope = CommandEnvelope {
1106 id: CommandId::new(2),
1107 client_id: ClientId::new(1),
1108 entity_id: EntityId::new(3),
1109 sequence: 4,
1110 received_at: Tick::new(99),
1111 kind: 5,
1112 priority: CommandPriority::Low,
1113 payload: vec![1, 2, 3],
1114 };
1115
1116 let frame = CommandDispatchFrame::from_envelope(StationId::new(10), &envelope);
1117 assert_eq!(frame.station_id, StationId::new(10));
1118 assert_eq!(frame.received_at, Tick::new(99));
1119 assert_eq!(frame.into_envelope(), envelope);
1120 }
1121
1122 #[test]
1123 fn binary_codec_roundtrips_barrier_frame() {
1124 let frame = BarrierFrame {
1125 client_id: ClientId::new(1),
1126 barrier_id: BarrierId::new(99),
1127 server_tick: Tick::new(11),
1128 state: BarrierState::Frozen,
1129 };
1130 let mut encoder = BinaryFrameEncoder;
1131 let mut bytes = Vec::new();
1132 encoder
1133 .encode_barrier(&frame, &mut bytes)
1134 .expect("encoder is infallible");
1135
1136 let decoded = BinaryFrameDecoder
1137 .decode(&bytes)
1138 .expect("decode should work");
1139 assert_eq!(decoded, RuntimeFrame::Barrier(frame));
1140 }
1141
1142 #[test]
1143 fn binary_codec_roundtrips_station_event_frame() {
1144 let frames = [
1145 StationEventFrame {
1146 event_id: EventId::new(1),
1147 source_station: StationId::new(10),
1148 target_station: StationId::new(11),
1149 source_tick: Tick::new(2),
1150 target_tick: Tick::new(3),
1151 priority: EventPriority::Critical,
1152 kind: EventKind::Custom(7),
1153 },
1154 StationEventFrame {
1155 event_id: EventId::new(2),
1156 source_station: StationId::new(10),
1157 target_station: StationId::new(11),
1158 source_tick: Tick::new(2),
1159 target_tick: Tick::new(3),
1160 priority: EventPriority::Important,
1161 kind: EventKind::HandoffPrepare {
1162 entity_id: EntityId::new(99),
1163 },
1164 },
1165 StationEventFrame {
1166 event_id: EventId::new(3),
1167 source_station: StationId::new(10),
1168 target_station: StationId::new(11),
1169 source_tick: Tick::new(2),
1170 target_tick: Tick::new(3),
1171 priority: EventPriority::BestEffort,
1172 kind: EventKind::HandoffCommit {
1173 entity_id: EntityId::new(99),
1174 owner_epoch: OwnerEpoch::new(5),
1175 },
1176 },
1177 ];
1178
1179 for frame in frames {
1180 let mut encoder = BinaryFrameEncoder;
1181 let mut bytes = Vec::new();
1182 encoder
1183 .encode_station_event(&frame, &mut bytes)
1184 .expect("encoder is infallible");
1185
1186 let decoded = BinaryFrameDecoder
1187 .decode(&bytes)
1188 .expect("decode should work");
1189 assert_eq!(decoded, RuntimeFrame::StationEvent(frame));
1190 }
1191 }
1192
1193 #[test]
1194 fn station_event_frame_converts_to_runtime_event() {
1195 let event = StationEvent {
1196 id: EventId::new(1),
1197 source: StationId::new(10),
1198 target: StationId::new(11),
1199 source_tick: Tick::new(2),
1200 target_tick: Tick::new(3),
1201 priority: EventPriority::Critical,
1202 kind: EventKind::HandoffPrepare {
1203 entity_id: EntityId::new(99),
1204 },
1205 };
1206
1207 let frame = StationEventFrame::from_event(&event);
1208 assert_eq!(frame.event_id, event.id);
1209 assert_eq!(frame.clone().into_event(), event);
1210 }
1211
1212 #[test]
1213 fn frame_builder_materializes_dirty_component_deltas() {
1214 use sectorsync_core::prelude::{
1215 Bounds, ComponentDescriptor, ComponentMigrationMode, ComponentSyncMode, InstanceId,
1216 NodeId, PolicyId, Position3, ReplicationPlan, StationConfig, Vec3, Vec3LeCodec,
1217 };
1218
1219 let mut station = Station::new(StationConfig {
1220 station_id: sectorsync_core::prelude::StationId::new(1),
1221 node_id: NodeId::new(1),
1222 instance_id: InstanceId::new(1),
1223 tick_rate_hz: 20,
1224 });
1225 let handle = station
1226 .spawn_owned(
1227 EntityId::new(10),
1228 Position3::new(0.0, 0.0, 0.0),
1229 Bounds::Point,
1230 PolicyId::new(0),
1231 )
1232 .expect("spawn should work");
1233
1234 let descriptor = ComponentDescriptor::sparse_blob(
1235 ComponentId::new(1),
1236 "velocity",
1237 ComponentSyncMode::Delta,
1238 ComponentMigrationMode::Copy,
1239 12,
1240 );
1241 let mut components = ComponentStore::default();
1242 components
1243 .set_typed(
1244 &descriptor,
1245 handle,
1246 3,
1247 &Vec3LeCodec,
1248 &Vec3::new(1.0, 2.0, 3.0),
1249 )
1250 .expect("typed component should encode");
1251
1252 let build = ReplicationFrameBuilder::new(ReplicationFrameLimits {
1253 max_entity_deltas: 8,
1254 max_components_per_entity: 4,
1255 max_component_bytes: 64,
1256 })
1257 .build(
1258 ClientId::new(5),
1259 Tick::new(9),
1260 &station,
1261 &ReplicationPlan {
1262 entities: vec![handle],
1263 stats: sectorsync_core::prelude::ReplicationStats::default(),
1264 },
1265 &components,
1266 &ComponentSelection {
1267 component_ids: vec![ComponentId::new(1)],
1268 },
1269 );
1270
1271 assert_eq!(build.stats.encoded_entities, 1);
1272 assert_eq!(build.stats.encoded_components, 1);
1273 assert_eq!(build.frame.entities[0].entity_id, EntityId::new(10));
1274 assert_eq!(build.frame.entities[0].components[0].version, 3);
1275 }
1276}