breakwater_parser/
original.rs

1#[cfg(feature = "binary-sync-pixels")]
2use core::slice;
3use std::{
4    simd::{Simd, num::SimdUint, u32x8},
5    sync::Arc,
6};
7
8use crate::{ALT_HELP_TEXT, FrameBuffer, HELP_TEXT, Parser};
9
10pub const PARSER_LOOKAHEAD: usize = "PX 1234 1234 rrggbbaa\n".len(); // Longest possible command
11
12pub(crate) const PX_PATTERN: u64 = string_to_number(b"PX \0\0\0\0\0");
13pub(crate) const PB_PATTERN: u64 = string_to_number(b"PB\0\0\0\0\0\0");
14pub(crate) const OFFSET_PATTERN: u64 = string_to_number(b"OFFSET \0\0");
15pub(crate) const SIZE_PATTERN: u64 = string_to_number(b"SIZE\0\0\0\0");
16pub(crate) const HELP_PATTERN: u64 = string_to_number(b"HELP\0\0\0\0");
17#[cfg(feature = "binary-sync-pixels")]
18pub(crate) const PXMULTI_PATTERN: u64 = string_to_number(b"PXMULTI\0");
19
20pub struct OriginalParser<FB: FrameBuffer> {
21    connection_x_offset: usize,
22    connection_y_offset: usize,
23    fb: Arc<FB>,
24    #[cfg(feature = "binary-sync-pixels")]
25    remaining_pixel_sync: Option<RemainingPixelSync>,
26}
27
28#[cfg(feature = "binary-sync-pixels")]
29#[derive(Debug)]
30pub struct RemainingPixelSync {
31    current_index: usize,
32    bytes_remaining: usize,
33}
34
35impl<FB: FrameBuffer> OriginalParser<FB> {
36    pub fn new(fb: Arc<FB>) -> Self {
37        Self {
38            connection_x_offset: 0,
39            connection_y_offset: 0,
40            fb,
41            #[cfg(feature = "binary-sync-pixels")]
42            remaining_pixel_sync: None,
43        }
44    }
45}
46
47impl<FB: FrameBuffer> Parser for OriginalParser<FB> {
48    fn parse(&mut self, buffer: &[u8], response: &mut Vec<u8>) -> usize {
49        let mut last_byte_parsed = 0;
50        let mut help_count = 0;
51
52        let mut i = 0; // We can't use a for loop here because Rust don't lets use skip characters by incrementing i
53        let loop_end = buffer.len().saturating_sub(PARSER_LOOKAHEAD); // Let's extract the .len() call and the subtraction into it's own variable so we only compute it once
54
55        #[cfg(feature = "binary-sync-pixels")]
56        if let Some(remaining) = &self.remaining_pixel_sync {
57            let buffer = &buffer[0..loop_end];
58
59            if remaining.bytes_remaining <= buffer.len() {
60                // Easy going here
61                self.fb
62                    .set_multi_from_start_index(remaining.current_index, unsafe {
63                        slice::from_raw_parts(buffer.as_ptr(), remaining.bytes_remaining)
64                    });
65                i += remaining.bytes_remaining;
66                last_byte_parsed = i;
67                self.remaining_pixel_sync = None;
68            } else {
69                // The client requested to write more bytes that are currently in the buffer, we need to remember
70                // what the client is doing.
71
72                // We need to round down to the 4 bytes of a pixel alignment
73                let pixel_bytes = buffer.len() / 4 * 4;
74
75                let mut index = remaining.current_index;
76                index += self
77                    .fb
78                    .set_multi_from_start_index(remaining.current_index, unsafe {
79                        slice::from_raw_parts(buffer.as_ptr(), pixel_bytes)
80                    });
81
82                self.remaining_pixel_sync = Some(RemainingPixelSync {
83                    current_index: index,
84                    bytes_remaining: remaining.bytes_remaining.saturating_sub(pixel_bytes),
85                });
86
87                // Nothing to do left, we can early return
88                // I have absolutely no idea why we need to subtract 1 here, but it is what it is. At least we have
89                // tests for this madness :)
90                return i + pixel_bytes.saturating_sub(1);
91            }
92        }
93
94        while i < loop_end {
95            let current_command =
96                unsafe { (buffer.as_ptr().add(i) as *const u64).read_unaligned() };
97            if current_command & 0x00ff_ffff == PX_PATTERN {
98                i += 3;
99
100                let (mut x, mut y, present) = parse_pixel_coordinates(buffer.as_ptr(), &mut i);
101
102                if present {
103                    x += self.connection_x_offset;
104                    y += self.connection_y_offset;
105
106                    // Separator between coordinates and color
107                    if unsafe { *buffer.get_unchecked(i) } == b' ' {
108                        i += 1;
109
110                        // TODO: Determine what clients use more: RGB, RGBA or gg variant.
111                        // If RGBA is used more often move the RGB code below the RGBA code
112
113                        // Must be followed by 6 bytes RGB and newline or ...
114                        if unsafe { *buffer.get_unchecked(i + 6) } == b'\n' {
115                            last_byte_parsed = i + 6;
116                            i += 7; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop
117
118                            let rgba: u32 = simd_unhex(unsafe { buffer.as_ptr().add(i - 7) });
119
120                            self.fb.set(x, y, rgba & 0x00ff_ffff);
121                            continue;
122                        }
123
124                        // ... or must be followed by 8 bytes RGBA and newline
125                        #[cfg(not(feature = "alpha"))]
126                        if unsafe { *buffer.get_unchecked(i + 8) } == b'\n' {
127                            last_byte_parsed = i + 8;
128                            i += 9; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop
129
130                            let rgba: u32 = simd_unhex(unsafe { buffer.as_ptr().add(i - 9) });
131
132                            self.fb.set(x, y, rgba & 0x00ff_ffff);
133                            continue;
134                        }
135                        #[cfg(feature = "alpha")]
136                        if unsafe { *buffer.get_unchecked(i + 8) } == b'\n' {
137                            last_byte_parsed = i + 8;
138                            i += 9; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop
139
140                            let rgba = simd_unhex(unsafe { buffer.as_ptr().add(i - 9) });
141
142                            let alpha = (rgba >> 24) & 0xff;
143
144                            if alpha == 0 || x >= self.fb.get_width() || y >= self.fb.get_height() {
145                                continue;
146                            }
147
148                            let alpha_comp = 0xff - alpha;
149                            let current = unsafe { self.fb.get_unchecked(x, y) };
150                            let r = (rgba >> 16) & 0xff;
151                            let g = (rgba >> 8) & 0xff;
152                            let b = rgba & 0xff;
153
154                            let r: u32 = (((current >> 24) & 0xff) * alpha_comp + r * alpha) / 0xff;
155                            let g: u32 = (((current >> 16) & 0xff) * alpha_comp + g * alpha) / 0xff;
156                            let b: u32 = (((current >> 8) & 0xff) * alpha_comp + b * alpha) / 0xff;
157
158                            self.fb.set(x, y, (r << 16) | (g << 8) | b);
159                            continue;
160                        }
161
162                        // ... for the efficient/lazy clients
163                        if unsafe { *buffer.get_unchecked(i + 2) } == b'\n' {
164                            last_byte_parsed = i + 2;
165                            i += 3; // We can advance one byte more than normal as we use continue and therefore not get incremented at the end of the loop
166
167                            let base = simd_unhex(unsafe { buffer.as_ptr().add(i - 3) }) & 0xff;
168
169                            let rgba: u32 = (base << 16) | (base << 8) | base;
170
171                            self.fb.set(x, y, rgba);
172
173                            continue;
174                        }
175                    }
176
177                    // End of command to read Pixel value
178                    if unsafe { *buffer.get_unchecked(i) } == b'\n' {
179                        last_byte_parsed = i;
180                        i += 1;
181                        if let Some(rgb) = self.fb.get(x, y) {
182                            response.extend_from_slice(
183                                format!(
184                                    "PX {} {} {:06x}\n",
185                                    // We don't want to return the actual (absolute) coordinates, the client should also get the result offseted
186                                    x - self.connection_x_offset,
187                                    y - self.connection_y_offset,
188                                    rgb.to_be() >> 8
189                                )
190                                .as_bytes(),
191                            );
192                        }
193                        continue;
194                    }
195                }
196            }
197            #[cfg(feature = "binary-set-pixel")]
198            if current_command & 0x0000_ffff == PB_PATTERN {
199                let command_bytes =
200                    unsafe { (buffer.as_ptr().add(i + 2) as *const u64).read_unaligned() };
201
202                let x = u16::from_le((command_bytes) as u16);
203                let y = u16::from_le((command_bytes >> 16) as u16);
204                let rgba = u32::from_le((command_bytes >> 32) as u32);
205
206                // TODO: Support alpha channel (behind alpha feature flag)
207                self.fb.set(x as usize, y as usize, rgba & 0x00ff_ffff);
208                //                 P   B   XX  YY  RGBA
209                last_byte_parsed = i + 1 + 2 + 2 + 4;
210                i += 10;
211                continue;
212            }
213            #[cfg(feature = "binary-sync-pixels")]
214            if current_command & 0x00ff_ffff_ffff_ffff == PXMULTI_PATTERN {
215                i += "PXMULTI".len();
216                let header = unsafe { (buffer.as_ptr().add(i) as *const u64).read_unaligned() };
217                i += 8;
218
219                let start_x = u16::from_le((header) as u16);
220                let start_y = u16::from_le((header >> 16) as u16);
221                let len = u32::from_le((header >> 32) as u32);
222                let len_in_bytes = len as usize * 4;
223                let bytes_left_in_buffer = loop_end.saturating_sub(i);
224
225                if len_in_bytes <= bytes_left_in_buffer {
226                    // Easy going here
227                    self.fb
228                        .set_multi(start_x as usize, start_y as usize, unsafe {
229                            slice::from_raw_parts(buffer.as_ptr().add(i), len_in_bytes)
230                        });
231
232                    i += len_in_bytes;
233                    last_byte_parsed = i;
234                    continue;
235                } else {
236                    // We need to round down to the 4 bytes of a pixel alignment
237                    let pixel_bytes: usize = bytes_left_in_buffer / 4 * 4;
238
239                    // The client requested to write more bytes that are currently in the buffer, we need to remember
240                    // what the client is doing.
241                    let mut current_index =
242                        start_x as usize + start_y as usize * self.fb.get_width();
243                    current_index += self.fb.set_multi_from_start_index(current_index, unsafe {
244                        slice::from_raw_parts(buffer.as_ptr().add(i), pixel_bytes)
245                    });
246
247                    self.remaining_pixel_sync = Some(RemainingPixelSync {
248                        current_index,
249                        bytes_remaining: len_in_bytes - pixel_bytes,
250                    });
251
252                    // Nothing to do left, we can early return
253                    // I have absolutely no idea why we need to subtract 1 here, but it is what it is. At least we have
254                    // tests for this madness :)
255                    return i + pixel_bytes.saturating_sub(1);
256                }
257            }
258            if current_command & 0x00ff_ffff_ffff_ffff == OFFSET_PATTERN {
259                i += 7;
260
261                let (x, y, present) = parse_pixel_coordinates(buffer.as_ptr(), &mut i);
262
263                // End of command to set offset
264                if present && unsafe { *buffer.get_unchecked(i) } == b'\n' {
265                    last_byte_parsed = i;
266                    self.connection_x_offset = x;
267                    self.connection_y_offset = y;
268                    continue;
269                }
270            }
271            if current_command & 0xffff_ffff == SIZE_PATTERN {
272                i += 4;
273                last_byte_parsed = i + 1;
274
275                response.extend_from_slice(
276                    format!("SIZE {} {}\n", self.fb.get_width(), self.fb.get_height()).as_bytes(),
277                );
278                continue;
279            }
280            if current_command & 0xffff_ffff == HELP_PATTERN {
281                i += 4;
282                last_byte_parsed = i + 1;
283
284                match help_count {
285                    0..=2 => {
286                        response.extend_from_slice(HELP_TEXT);
287                        help_count += 1;
288                    }
289                    3 => {
290                        response.extend_from_slice(ALT_HELP_TEXT);
291                        help_count += 1;
292                    }
293                    _ => {
294                        // The client has requested the help to often, let's just ignore it
295                    }
296                }
297                continue;
298            }
299
300            i += 1;
301        }
302
303        last_byte_parsed
304        // last_byte_parsed.saturating_sub(1)
305    }
306
307    fn parser_lookahead(&self) -> usize {
308        PARSER_LOOKAHEAD
309    }
310}
311
312const fn string_to_number(input: &[u8]) -> u64 {
313    ((input[7] as u64) << 56)
314        | ((input[6] as u64) << 48)
315        | ((input[5] as u64) << 40)
316        | ((input[4] as u64) << 32)
317        | ((input[3] as u64) << 24)
318        | ((input[2] as u64) << 16)
319        | ((input[1] as u64) << 8)
320        | (input[0] as u64)
321}
322
323const SHIFT_PATTERN: Simd<u32, 8> = u32x8::from_array([4, 0, 12, 8, 20, 16, 28, 24]);
324const SIMD_6: Simd<u32, 8> = u32x8::from_array([6; 8]);
325const SIMD_F: Simd<u32, 8> = u32x8::from_array([0xf; 8]);
326const SIMD_9: Simd<u32, 8> = u32x8::from_array([9; 8]);
327
328/// Parse a slice of 8 characters into a single u32 number
329/// is undefined behavior for invalid characters
330#[inline(always)]
331pub(crate) fn simd_unhex(value: *const u8) -> u32 {
332    // Feel free to find a better, but fast, way, to cast all integers as u32
333    let input = unsafe {
334        u32x8::from_array([
335            *value as u32,
336            *value.add(1) as u32,
337            *value.add(2) as u32,
338            *value.add(3) as u32,
339            *value.add(4) as u32,
340            *value.add(5) as u32,
341            *value.add(6) as u32,
342            *value.add(7) as u32,
343        ])
344    };
345    // Heavily inspired by https://github.com/nervosnetwork/faster-hex/blob/a4c06b387ddeeea311c9e84a3adcaf01015cf40e/src/decode.rs#L80
346    let sr6 = input >> SIMD_6;
347    let and15 = input & SIMD_F;
348    let mul = sr6 * SIMD_9;
349    let hexed = and15 + mul;
350    let shifted = hexed << SHIFT_PATTERN;
351    shifted.reduce_or()
352}
353
354#[inline(always)]
355fn parse_coordinate(buffer: *const u8, current_index: &mut usize) -> (usize, bool) {
356    let digits = unsafe { (buffer.add(*current_index) as *const usize).read_unaligned() };
357
358    let mut result = 0;
359    let mut visited = false;
360    // The compiler will unroll this loop, but this way, it is more maintainable
361    for pos in 0..4 {
362        let digit = (digits >> (pos * 8)) & 0xff;
363        if digit >= b'0' as usize && digit <= b'9' as usize {
364            result = 10 * result + digit - b'0' as usize;
365            *current_index += 1;
366            visited = true;
367        } else {
368            break;
369        }
370    }
371
372    (result, visited)
373}
374
375#[inline(always)]
376pub(crate) fn parse_pixel_coordinates(
377    buffer: *const u8,
378    current_index: &mut usize,
379) -> (usize, usize, bool) {
380    let (x, x_visited) = parse_coordinate(buffer, current_index);
381    *current_index += 1;
382    let (y, y_visited) = parse_coordinate(buffer, current_index);
383    (x, y, x_visited && y_visited)
384}