1use super::action::InputAction;
6
7#[derive(Debug, Clone)]
12pub struct InputFrame {
13 pub actions: Vec<InputAction>,
15
16 pub movement: [f32; 2],
19
20 pub look_delta: [f32; 2],
24
25 pub scroll_delta: f32,
27
28 pub cursor_position: [f32; 2],
30
31 pub cursor_delta: [f32; 2],
33
34 pub timestamp: f32,
36}
37
38impl InputFrame {
39 pub fn has_action(&self, action: InputAction) -> bool {
41 self.actions.contains(&action)
42 }
43
44 pub fn has_movement(&self) -> bool {
46 self.movement[0] != 0.0 || self.movement[1] != 0.0
47 }
48
49 pub fn has_look(&self) -> bool {
51 self.look_delta[0] != 0.0 || self.look_delta[1] != 0.0
52 }
53
54 pub fn has_scroll(&self) -> bool {
56 self.scroll_delta != 0.0
57 }
58}
59
60impl Default for InputFrame {
61 fn default() -> Self {
62 Self {
63 actions: Vec::new(),
64 movement: [0.0; 2],
65 look_delta: [0.0; 2],
66 scroll_delta: 0.0,
67 cursor_position: [0.0; 2],
68 cursor_delta: [0.0; 2],
69 timestamp: 0.0,
70 }
71 }
72}
73
74pub struct InputFrameBuilder {
76 actions: Vec<InputAction>,
77 movement: [f32; 2],
78 look_delta: [f32; 2],
79 scroll_delta: f32,
80 cursor_position: [f32; 2],
81 cursor_delta: [f32; 2],
82 timestamp: f32,
83}
84
85impl InputFrameBuilder {
86 pub fn new(timestamp: f32) -> Self {
87 Self {
88 actions: Vec::new(),
89 movement: [0.0; 2],
90 look_delta: [0.0; 2],
91 scroll_delta: 0.0,
92 cursor_position: [0.0; 2],
93 cursor_delta: [0.0; 2],
94 timestamp,
95 }
96 }
97
98 pub fn action(mut self, action: InputAction) -> Self {
100 if !self.actions.contains(&action) {
101 self.actions.push(action);
102 }
103 self
104 }
105
106 pub fn movement(mut self, x: f32, y: f32) -> Self {
108 self.movement = [x, y];
109 self
110 }
111
112 pub fn look(mut self, dx: f32, dy: f32) -> Self {
114 self.look_delta = [dx, dy];
115 self
116 }
117
118 pub fn scroll(mut self, delta: f32) -> Self {
120 self.scroll_delta = delta;
121 self
122 }
123
124 pub fn cursor(mut self, x: f32, y: f32) -> Self {
126 self.cursor_position = [x, y];
127 self
128 }
129
130 pub fn cursor_delta(mut self, dx: f32, dy: f32) -> Self {
132 self.cursor_delta = [dx, dy];
133 self
134 }
135
136 pub fn build(self) -> InputFrame {
138 InputFrame {
139 actions: self.actions,
140 movement: self.movement,
141 look_delta: self.look_delta,
142 scroll_delta: self.scroll_delta,
143 cursor_position: self.cursor_position,
144 cursor_delta: self.cursor_delta,
145 timestamp: self.timestamp,
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn empty_frame() {
156 let frame = InputFrame::default();
157 assert!(!frame.has_movement());
158 assert!(!frame.has_look());
159 assert!(!frame.has_scroll());
160 assert!(frame.actions.is_empty());
161 }
162
163 #[test]
164 fn builder_actions() {
165 let frame = InputFrameBuilder::new(0.0)
166 .action(InputAction::MoveForward)
167 .action(InputAction::Sprint)
168 .build();
169
170 assert!(frame.has_action(InputAction::MoveForward));
171 assert!(frame.has_action(InputAction::Sprint));
172 assert!(!frame.has_action(InputAction::Jump));
173 }
174
175 #[test]
176 fn builder_no_duplicate_actions() {
177 let frame = InputFrameBuilder::new(0.0)
178 .action(InputAction::Attack)
179 .action(InputAction::Attack)
180 .build();
181
182 assert_eq!(frame.actions.len(), 1);
183 }
184
185 #[test]
186 fn builder_movement() {
187 let frame = InputFrameBuilder::new(0.0).movement(-1.0, 1.0).build();
188
189 assert!(frame.has_movement());
190 assert_eq!(frame.movement, [-1.0, 1.0]);
191 }
192
193 #[test]
194 fn builder_look_and_scroll() {
195 let frame = InputFrameBuilder::new(0.0).look(5.0, -3.0).scroll(1.5).build();
196
197 assert!(frame.has_look());
198 assert!(frame.has_scroll());
199 assert_eq!(frame.look_delta, [5.0, -3.0]);
200 assert_eq!(frame.scroll_delta, 1.5);
201 }
202
203 #[test]
204 fn builder_cursor() {
205 let frame = InputFrameBuilder::new(1.0)
206 .cursor(100.0, 200.0)
207 .cursor_delta(2.0, -1.0)
208 .build();
209
210 assert_eq!(frame.cursor_position, [100.0, 200.0]);
211 assert_eq!(frame.cursor_delta, [2.0, -1.0]);
212 assert_eq!(frame.timestamp, 1.0);
213 }
214
215 #[test]
216 fn full_frame_build() {
217 let frame = InputFrameBuilder::new(0.016)
218 .action(InputAction::MoveForward)
219 .action(InputAction::Sprint)
220 .movement(0.0, 1.0)
221 .look(0.0, 0.0)
222 .scroll(-0.5)
223 .cursor(640.0, 480.0)
224 .cursor_delta(0.0, 0.0)
225 .build();
226
227 assert!(frame.has_action(InputAction::MoveForward));
228 assert!(frame.has_action(InputAction::Sprint));
229 assert!(frame.has_movement());
230 assert!(!frame.has_look());
231 assert!(frame.has_scroll());
232 assert_eq!(frame.timestamp, 0.016);
233 }
234}
235
236#[derive(Debug, Clone)]
245pub struct InputPacket {
246 pub movement: [f32; 2],
249 pub camera_yaw: f32,
251
252 pub jump: bool,
254 pub interact: bool,
255
256 pub sprint: bool,
258 pub gather: bool,
259 pub emit_strength: f32,
261 pub coherence_target: f32,
263
264 pub dt: f32,
266 pub timestamp: f64,
268 pub tick: u64,
269
270 pub grounded: bool,
272}
273
274impl InputPacket {
275 pub fn idle(dt: f32, tick: u64) -> Self {
277 Self {
278 movement: [0.0; 2],
279 camera_yaw: 0.0,
280 jump: false,
281 interact: false,
282 sprint: false,
283 gather: false,
284 emit_strength: 0.0,
285 coherence_target: 1.0,
286 dt,
287 timestamp: 0.0,
288 tick,
289 grounded: true,
290 }
291 }
292
293 pub fn from_frame(
295 frame: &InputFrame,
296 camera_yaw: f32,
297 dt: f32,
298 timestamp: f64,
299 tick: u64,
300 grounded: bool,
301 coherence_target: f32,
302 gather: bool,
303 emit_strength: f32,
304 ) -> Self {
305 Self {
306 movement: frame.movement,
307 camera_yaw,
308 jump: frame.has_action(InputAction::Jump),
309 interact: frame.has_action(InputAction::Interact),
310 sprint: frame.has_action(InputAction::Sprint),
311 gather,
312 emit_strength,
313 coherence_target,
314 dt,
315 timestamp,
316 tick,
317 grounded,
318 }
319 }
320
321 pub fn actions_as_u8(&self) -> Vec<u8> {
323 let mut out = Vec::with_capacity(4);
324 if self.movement[1] > 0.0 {
325 out.push(0);
326 } if self.movement[1] < 0.0 {
328 out.push(1);
329 } if self.movement[0] < 0.0 {
331 out.push(2);
332 } if self.movement[0] > 0.0 {
334 out.push(3);
335 } if self.sprint {
337 out.push(6);
338 } if self.jump {
340 out.push(8);
341 } if self.interact {
343 out.push(16);
344 } if self.gather {
346 out.push(100);
347 } out
349 }
350
351 pub fn digest(&self) -> [u8; 32] {
353 let mut hasher = blake3::Hasher::new();
354 hasher.update(&self.movement[0].to_le_bytes());
355 hasher.update(&self.movement[1].to_le_bytes());
356 hasher.update(&self.camera_yaw.to_le_bytes());
357 hasher.update(&[
358 self.jump as u8,
359 self.interact as u8,
360 self.sprint as u8,
361 self.gather as u8,
362 ]);
363 hasher.update(&self.emit_strength.to_le_bytes());
364 hasher.update(&self.coherence_target.to_le_bytes());
365 hasher.update(&self.dt.to_le_bytes());
366 hasher.update(&self.timestamp.to_le_bytes());
367 hasher.update(&self.tick.to_le_bytes());
368 hasher.update(&[self.grounded as u8]);
369 *hasher.finalize().as_bytes()
370 }
371
372 pub fn has_movement(&self) -> bool {
374 self.movement[0] != 0.0 || self.movement[1] != 0.0
375 }
376}
377
378#[cfg(test)]
379mod input_packet_tests {
380 use super::*;
381
382 #[test]
383 fn idle_packet_is_zero() {
384 let p = InputPacket::idle(1.0 / 60.0, 0);
385 assert!(!p.has_movement());
386 assert!(!p.jump);
387 assert!(!p.sprint);
388 assert!(p.grounded);
389 assert_eq!(p.coherence_target, 1.0);
390 }
391
392 #[test]
393 fn from_frame_maps_actions() {
394 let frame = InputFrameBuilder::new(0.0)
395 .action(InputAction::Jump)
396 .action(InputAction::Sprint)
397 .movement(0.5, 1.0)
398 .build();
399 let p = InputPacket::from_frame(&frame, 0.0, 0.016, 1.0, 1, true, 0.8, false, 0.0);
400 assert!(p.jump);
401 assert!(p.sprint);
402 assert!(!p.interact);
403 assert_eq!(p.movement, [0.5, 1.0]);
404 assert_eq!(p.coherence_target, 0.8);
405 }
406
407 #[test]
408 fn actions_as_u8_encodes_movement() {
409 let mut p = InputPacket::idle(0.016, 0);
410 p.movement = [0.0, 1.0]; p.sprint = true;
412 let actions = p.actions_as_u8();
413 assert!(actions.contains(&0)); assert!(actions.contains(&6)); assert!(!actions.contains(&8)); }
417
418 #[test]
419 fn digest_is_nonzero() {
420 let p = InputPacket::idle(0.016, 0);
421 let d = p.digest();
422 assert_ne!(d, [0u8; 32]);
423 }
424
425 #[test]
426 fn digest_changes_with_input() {
427 let p1 = InputPacket::idle(0.016, 0);
428 let mut p2 = InputPacket::idle(0.016, 0);
429 p2.jump = true;
430 assert_ne!(p1.digest(), p2.digest());
431 }
432}