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;
}
}