use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ActionDiff<A> {
Pressed {
action: A,
timestamp: u64,
},
Released {
action: A,
timestamp: u64,
},
AxisChanged {
action: A,
value: f32,
timestamp: u64,
},
DualAxisChanged {
action: A,
x: f32,
y: f32,
timestamp: u64,
},
}
impl<A: Clone> ActionDiff<A> {
#[must_use]
pub fn action(&self) -> A {
match self {
Self::Pressed { action, .. }
| Self::Released { action, .. }
| Self::AxisChanged { action, .. }
| Self::DualAxisChanged { action, .. } => action.clone(),
}
}
#[must_use]
pub fn timestamp(&self) -> u64 {
match self {
Self::Pressed { timestamp, .. }
| Self::Released { timestamp, .. }
| Self::AxisChanged { timestamp, .. }
| Self::DualAxisChanged { timestamp, .. } => *timestamp,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ActionDiffBuffer<A> {
diffs: VecDeque<ActionDiff<A>>,
current_timestamp: u64,
max_buffer_size: usize,
}
impl<A: Clone + PartialEq> ActionDiffBuffer<A> {
#[must_use]
pub fn new() -> Self {
Self {
diffs: VecDeque::new(),
current_timestamp: 0,
max_buffer_size: 256,
}
}
#[must_use]
pub fn with_max_size(max_size: usize) -> Self {
Self {
max_buffer_size: max_size,
..Self::new()
}
}
pub fn set_timestamp(&mut self, timestamp: u64) {
self.current_timestamp = timestamp;
}
pub fn tick(&mut self) {
self.current_timestamp += 1;
}
pub fn record_press(&mut self, action: A) {
self.push_diff(ActionDiff::Pressed {
action,
timestamp: self.current_timestamp,
});
}
pub fn record_release(&mut self, action: A) {
self.push_diff(ActionDiff::Released {
action,
timestamp: self.current_timestamp,
});
}
pub fn record_axis(&mut self, action: A, value: f32) {
self.push_diff(ActionDiff::AxisChanged {
action,
value,
timestamp: self.current_timestamp,
});
}
pub fn record_dual_axis(&mut self, action: A, x: f32, y: f32) {
self.push_diff(ActionDiff::DualAxisChanged {
action,
x,
y,
timestamp: self.current_timestamp,
});
}
fn push_diff(&mut self, diff: ActionDiff<A>) {
if self.diffs.len() >= self.max_buffer_size {
self.diffs.pop_front();
}
self.diffs.push_back(diff);
}
#[must_use]
pub fn peek_diffs(&self) -> &VecDeque<ActionDiff<A>> {
&self.diffs
}
pub fn drain_diffs(&mut self) -> Vec<ActionDiff<A>> {
self.diffs.drain(..).collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.diffs.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.diffs.is_empty()
}
pub fn clear(&mut self) {
self.diffs.clear();
}
}
#[derive(Component, Debug, Default)]
pub struct NetworkedInput<A> {
pub outgoing: ActionDiffBuffer<A>,
pub incoming: VecDeque<ActionDiff<A>>,
}
impl<A: Clone + PartialEq> NetworkedInput<A> {
#[must_use]
pub fn new() -> Self {
Self {
outgoing: ActionDiffBuffer::new(),
incoming: VecDeque::new(),
}
}
pub fn receive_diffs(&mut self, diffs: impl IntoIterator<Item = ActionDiff<A>>) {
self.incoming.extend(diffs);
}
pub fn next_incoming(&mut self) -> Option<ActionDiff<A>> {
self.incoming.pop_front()
}
#[must_use]
pub fn has_incoming(&self) -> bool {
!self.incoming.is_empty()
}
}
pub fn serialize_diffs<A: Serialize>(
diffs: &[ActionDiff<A>],
) -> Result<Vec<u8>, serde_json::Error> {
serde_json::to_vec(diffs)
}
pub fn deserialize_diffs<A: for<'de> Deserialize<'de>>(
bytes: &[u8],
) -> Result<Vec<ActionDiff<A>>, serde_json::Error> {
serde_json::from_slice(bytes)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionStateSnapshot<A> {
pub pressed: Vec<A>,
pub axes: Vec<(A, f32)>,
pub dual_axes: Vec<(A, f32, f32)>,
pub timestamp: u64,
}
impl<A: Clone + PartialEq> ActionStateSnapshot<A> {
#[must_use]
pub fn new(timestamp: u64) -> Self {
Self {
pressed: Vec::new(),
axes: Vec::new(),
dual_axes: Vec::new(),
timestamp,
}
}
pub fn add_pressed(&mut self, action: A) {
if !self.pressed.contains(&action) {
self.pressed.push(action);
}
}
pub fn add_axis(&mut self, action: A, value: f32) {
self.axes.push((action, value));
}
pub fn add_dual_axis(&mut self, action: A, x: f32, y: f32) {
self.dual_axes.push((action, x, y));
}
}
#[derive(Resource, Debug, Clone)]
pub struct NetworkInputConfig {
pub snapshot_interval: u32,
pub delta_compression: bool,
pub max_diff_age: u64,
pub interpolation: bool,
}
impl Default for NetworkInputConfig {
fn default() -> Self {
Self {
snapshot_interval: 60, delta_compression: true,
max_diff_age: 30, interpolation: true,
}
}
}
pub struct NetworkInputPlugin<A: Clone + PartialEq + Send + Sync + 'static> {
_marker: std::marker::PhantomData<A>,
}
impl<A: Clone + PartialEq + Send + Sync + 'static> Default for NetworkInputPlugin<A> {
fn default() -> Self {
Self {
_marker: std::marker::PhantomData,
}
}
}
impl<A: Clone + PartialEq + Send + Sync + 'static> Plugin for NetworkInputPlugin<A> {
fn build(&self, app: &mut App) {
app.init_resource::<NetworkInputConfig>();
}
}
pub trait ActionDiffExt<A: Clone + PartialEq> {
fn diff_states(old_pressed: &[A], new_pressed: &[A], timestamp: u64) -> Vec<ActionDiff<A>>;
}
impl<A: Clone + PartialEq> ActionDiffExt<A> for ActionDiff<A> {
fn diff_states(old_pressed: &[A], new_pressed: &[A], timestamp: u64) -> Vec<ActionDiff<A>> {
let mut diffs = Vec::new();
for action in new_pressed {
if !old_pressed.contains(action) {
diffs.push(ActionDiff::Pressed {
action: action.clone(),
timestamp,
});
}
}
for action in old_pressed {
if !new_pressed.contains(action) {
diffs.push(ActionDiff::Released {
action: action.clone(),
timestamp,
});
}
}
diffs
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_action_diff_buffer() {
let mut buffer = ActionDiffBuffer::<u32>::new();
buffer.tick();
buffer.record_press(1);
buffer.tick();
buffer.record_release(1);
let diffs = buffer.drain_diffs();
assert_eq!(diffs.len(), 2);
assert!(matches!(
diffs[0],
ActionDiff::Pressed {
action: 1,
timestamp: 1
}
));
assert!(matches!(
diffs[1],
ActionDiff::Released {
action: 1,
timestamp: 2
}
));
}
#[test]
fn test_diff_states() {
let old = vec![1, 2, 3];
let new = vec![2, 3, 4];
let diffs = ActionDiff::diff_states(&old, &new, 100);
assert_eq!(diffs.len(), 2);
let has_release = diffs
.iter()
.any(|d| matches!(d, ActionDiff::Released { action: 1, .. }));
let has_press = diffs
.iter()
.any(|d| matches!(d, ActionDiff::Pressed { action: 4, .. }));
assert!(has_release);
assert!(has_press);
}
#[test]
fn test_serialization() {
let diffs = vec![
ActionDiff::Pressed::<u32> {
action: 1,
timestamp: 100,
},
ActionDiff::Released::<u32> {
action: 2,
timestamp: 101,
},
];
let bytes = serialize_diffs(&diffs).unwrap();
let restored: Vec<ActionDiff<u32>> = deserialize_diffs(&bytes).unwrap();
assert_eq!(diffs, restored);
}
#[test]
fn test_buffer_max_size() {
let mut buffer = ActionDiffBuffer::<u32>::with_max_size(3);
buffer.record_press(1);
buffer.record_press(2);
buffer.record_press(3);
buffer.record_press(4);
assert_eq!(buffer.len(), 3);
let diffs = buffer.drain_diffs();
assert!(diffs.iter().any(|d| d.action() == 4));
assert!(!diffs.iter().any(|d| d.action() == 1)); }
#[test]
fn test_action_snapshot() {
let mut snapshot = ActionStateSnapshot::<u32>::new(100);
snapshot.add_pressed(1);
snapshot.add_pressed(2);
snapshot.add_axis(3, 0.5);
snapshot.add_dual_axis(4, 0.3, -0.7);
assert_eq!(snapshot.pressed.len(), 2);
assert_eq!(snapshot.axes.len(), 1);
assert_eq!(snapshot.dual_axes.len(), 1);
}
}