1use core::ptr;
13
14pub const OPCODE_SIZE: usize = 16;
16
17#[repr(C, packed)]
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct Opcode {
26 pub category: u8,
28 pub command: u8,
30 pub flags: u16,
32 pub a: u32,
34 pub b: u32,
36 pub c: u32,
38}
39
40impl Opcode {
41 pub const SIZE: usize = OPCODE_SIZE;
43
44 #[inline]
46 pub const fn new(category: u8, command: u8, flags: u16, a: u32, b: u32, c: u32) -> Self {
47 Self {
48 category,
49 command,
50 flags,
51 a,
52 b,
53 c,
54 }
55 }
56
57 #[inline]
59 pub fn category(&self) -> u8 {
60 self.category
61 }
62
63 #[inline]
65 pub fn command(&self) -> u8 {
66 self.command
67 }
68
69 #[inline]
71 pub fn flags(&self) -> u16 {
72 unsafe { ptr::addr_of!(self.flags).read_unaligned() }
74 }
75
76 #[inline]
78 pub fn a(&self) -> u32 {
79 unsafe { ptr::addr_of!(self.a).read_unaligned() }
81 }
82
83 #[inline]
85 pub fn b(&self) -> u32 {
86 unsafe { ptr::addr_of!(self.b).read_unaligned() }
88 }
89
90 #[inline]
92 pub fn c(&self) -> u32 {
93 unsafe { ptr::addr_of!(self.c).read_unaligned() }
95 }
96
97 #[inline]
99 pub fn a_f32(&self) -> f32 {
100 f32::from_bits(self.a())
101 }
102
103 #[inline]
105 pub fn b_f32(&self) -> f32 {
106 f32::from_bits(self.b())
107 }
108
109 #[inline]
111 pub fn c_f32(&self) -> f32 {
112 f32::from_bits(self.c())
113 }
114
115 pub fn to_bytes(&self) -> [u8; OPCODE_SIZE] {
117 let mut out = [0u8; OPCODE_SIZE];
118 out[0] = self.category;
119 out[1] = self.command;
120 let flags = unsafe { ptr::addr_of!(self.flags).read_unaligned() };
122 let a = unsafe { ptr::addr_of!(self.a).read_unaligned() };
123 let b = unsafe { ptr::addr_of!(self.b).read_unaligned() };
124 let c = unsafe { ptr::addr_of!(self.c).read_unaligned() };
125 out[2..4].copy_from_slice(&flags.to_le_bytes());
126 out[4..8].copy_from_slice(&a.to_le_bytes());
127 out[8..12].copy_from_slice(&b.to_le_bytes());
128 out[12..16].copy_from_slice(&c.to_le_bytes());
129 out
130 }
131
132 pub fn from_bytes(bytes: &[u8; OPCODE_SIZE]) -> Self {
134 let category = bytes[0];
135 let command = bytes[1];
136 let flags = u16::from_le_bytes([bytes[2], bytes[3]]);
137 let a = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
138 let b = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
139 let c = u32::from_le_bytes(bytes[12..16].try_into().unwrap());
140 Self {
141 category,
142 command,
143 flags,
144 a,
145 b,
146 c,
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn size_is_16_bytes() {
157 assert_eq!(core::mem::size_of::<Opcode>(), 16);
158 }
159
160 #[test]
161 fn round_trips_through_bytes() {
162 let op = Opcode::new(0x01, 0x01, 0x0001, 0x1122_3344, 0x5566_7788, 0x99AA_BBCC);
163 let bytes = op.to_bytes();
164 let back = Opcode::from_bytes(&bytes);
165 assert_eq!(op, back);
166 }
167
168 #[test]
169 fn known_byte_layout() {
170 let op = Opcode::new(0x01, 0x01, 0x0000, 1, 0x0010, 0);
172 assert_eq!(
173 op.to_bytes(),
174 [0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
175 );
176 }
177
178 #[test]
179 fn float_payloads_round_trip() {
180 let op = Opcode::new(0x02, 0x01, 0, 42, 100.0f32.to_bits(), 200.0f32.to_bits());
181 let back = Opcode::from_bytes(&op.to_bytes());
182 assert_eq!(back.a(), 42);
183 assert_eq!(back.b_f32(), 100.0);
184 assert_eq!(back.c_f32(), 200.0);
185 }
186}