bones_framework/networking/
proto.rs

1//! Serializable data types for network messages used by the game.
2
3// use bevy::reflect::Reflect;
4use numquant::{IntRange, Quantized};
5
6use crate::prelude::*;
7
8/// A newtype around [`Vec2`] that implements [`From<u16>`] and [`Into<u16>`] as a way to compress
9/// user stick input for use in [`self::input::DenseInput`].
10#[derive(Debug, Deref, DerefMut, Default)]
11pub struct DenseMoveDirection(pub Vec2);
12
13/// This is the specific [`Quantized`] type that we use to represent movement directions in
14/// [`DenseMoveDirection`]. This encodes magnitude of direction, but sign is encoded separately.
15type MoveDirQuant = Quantized<IntRange<u16, 0b11111, 0, 1>>;
16
17impl From<u16> for DenseMoveDirection {
18    fn from(bits: u16) -> Self {
19        // maximum movement value representable, we use 6 bits to represent each movement direction.
20        // Most significant is sign, and other 5 encode float value between 0 and
21        let bit_length = 6;
22        let quantized = 0b011111;
23        let sign = 0b100000;
24        // The first six bits represent the x movement
25        let x_move_bits = bits & quantized;
26        let x_move_sign = if bits & sign == 0 { 1.0 } else { -1.0 };
27        // The second six bits represents the y movement
28        let y_move_bits = (bits >> bit_length) & quantized;
29        let y_move_sign = if (bits >> bit_length) & sign == 0 {
30            1.0
31        } else {
32            -1.0
33        };
34
35        // Round near-zero values to zero
36        let mut x = MoveDirQuant::from_raw(x_move_bits).to_f32();
37        x *= x_move_sign;
38        if x.abs() < 0.02 {
39            x = 0.0;
40        }
41        let mut y = MoveDirQuant::from_raw(y_move_bits).to_f32();
42        y *= y_move_sign;
43        if y.abs() < 0.02 {
44            y = 0.0;
45        }
46
47        DenseMoveDirection(Vec2::new(x, y))
48    }
49}
50
51impl From<DenseMoveDirection> for u16 {
52    fn from(dir: DenseMoveDirection) -> Self {
53        let x_bits = MoveDirQuant::from_f32(dir.x.abs()).raw();
54        let y_bits = MoveDirQuant::from_f32(dir.y.abs()).raw();
55        let x_sign_bit = if dir.x.is_sign_positive() {
56            0
57        } else {
58            0b100000
59        };
60        let y_sign_bit = if dir.y.is_sign_positive() {
61            0
62        } else {
63            0b100000
64        };
65
66        (x_bits | x_sign_bit) | ((y_bits | y_sign_bit) << 6)
67    }
68}
69
70impl From<u32> for DenseMoveDirection {
71    fn from(bits: u32) -> Self {
72        let bits_16 = bits as u16;
73        bits_16.into()
74    }
75}
76
77impl From<DenseMoveDirection> for u32 {
78    fn from(dir: DenseMoveDirection) -> Self {
79        let bits_16 = u16::from(dir);
80        bits_16 as u32
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use crate::prelude::proto::DenseMoveDirection;
87
88    #[test]
89    /// GGRS currently uses zero'd player input as prediction on first frame,
90    /// so ensure our move direction representation is no input when built from
91    /// 0 bits.
92    pub fn zeroed_dense_move_dir() {
93        let bits: u16 = 0;
94        let dense_move_dir = DenseMoveDirection::from(bits);
95        assert_eq!(dense_move_dir.0, glam::Vec2::ZERO);
96    }
97}