using System.Runtime.InteropServices; namespace Olei.LidarSensor; /// /// High-performance parser for LiDAR data packets /// Uses Span<byte> and MemoryMarshal for zero-allocation parsing /// public static class LidarPacketParser { /// /// Parse raw UDP packet data into LidarDataPacket /// /// Raw byte buffer from UDP socket /// Actual length of received data /// Output packet to populate (reusable) /// True if parsing successful, false if invalid data public static bool TryParse(byte[] buffer, int length, LidarDataPacket packet) { if (length < LidarDataPacket.PACKET_SIZE) return false; return TryParse(buffer.AsSpan(0, length), packet); } /// /// Parse raw UDP packet data into LidarDataPacket using Span /// High performance, zero-allocation parsing /// /// Raw byte span /// Output packet to populate (reusable) /// True if parsing successful, false if invalid data public static bool TryParse(ReadOnlySpan data, LidarDataPacket packet) { if (data.Length < LidarDataPacket.PACKET_SIZE) return false; // Parse header (40 bytes) if (!TryParseHeader(data.Slice(0, LidarDataPacket.HEADER_SIZE), out var header)) return false; packet.Header = header; packet.ReceivedTime = DateTime.UtcNow; // Parse data blocks (150 blocks × 8 bytes) ReadOnlySpan dataBlocksSpan = data.Slice( LidarDataPacket.HEADER_SIZE, LidarDataPacket.DATA_BLOCKS_TOTAL_SIZE ); for (int i = 0; i < LidarDataPacket.DATA_BLOCK_COUNT; i++) { int offset = i * LidarDataPacket.DATA_BLOCK_SIZE; ReadOnlySpan blockSpan = dataBlocksSpan.Slice(offset, LidarDataPacket.DATA_BLOCK_SIZE); packet.DataBlocks[i] = ParseDataBlock(blockSpan); } return true; } /// /// Parse header from byte span /// private static bool TryParseHeader(ReadOnlySpan data, out LidarHeader header) { if (data.Length < LidarDataPacket.HEADER_SIZE) { header = default; return false; } // Use MemoryMarshal for fast struct parsing header = MemoryMarshal.Read(data); // Validate frame ID return header.IsValidFrame; } /// /// Parse single data block from byte span /// private static LidarDataBlock ParseDataBlock(ReadOnlySpan data) { // Use MemoryMarshal for fast struct parsing return MemoryMarshal.Read(data); } /// /// Validate packet without full parsing /// Useful for quick validation before processing /// public static bool IsValidPacket(ReadOnlySpan data) { if (data.Length < LidarDataPacket.PACKET_SIZE) return false; // Check frame ID (first 4 bytes) uint frameId = MemoryMarshal.Read(data); return frameId == LidarHeader.FRAME_ID; } /// /// Extract distance scale from packet without full parsing /// public static byte GetDistanceScale(ReadOnlySpan data) { if (data.Length < 7) return 0; return data[6]; // Distance scale is at offset 6 } /// /// Extract timestamp from packet without full parsing /// public static uint GetTimestamp(ReadOnlySpan data) { if (data.Length < 32) return 0; return MemoryMarshal.Read(data.Slice(28, 4)); } /// /// Extract error status from packet without full parsing /// public static byte GetErrorStatus(ReadOnlySpan data) { if (data.Length < 36) return 0; return data[35]; // Error status is at offset 35 } /// /// Quick check if packet has any errors /// public static bool HasErrors(ReadOnlySpan data) { return GetErrorStatus(data) != 0; } }