acdream/src/AcDream.Core.Net/Messages/GameEventEnvelope.cs
Erik e928c5dd02 perf(net): borrow inbound packet storage
Decode headers, optional fields, fragments, and single-fragment messages directly over pooled datagrams. Copy only fragment state that crosses a datagram lifetime, preserve synchronous dispatch and ACK ordering, and lock the path to the owned decoder with differential and zero-allocation tests.
2026-07-25 05:58:55 +02:00

69 lines
2.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Buffers.Binary;
namespace AcDream.Core.Net.Messages;
/// <summary>
/// Parsed header of a retail AC <c>0xF7B0</c> GameEvent envelope (S→C).
///
/// <para>
/// Wire layout
/// (references/ACE/Source/ACE.Server/Network/GameEvent/GameEventMessage.cs):
/// <code>
/// u32 0xF7B0 // GameMessage opcode (GameEvent envelope)
/// u32 guid // session's player guid (0 if none yet)
/// u32 gameEventSequence // per-session, incremented by server
/// u32 eventType // GameEventType sub-opcode (94 values)
/// &lt;payload&gt; // variable; type-dispatched
/// </code>
/// </para>
///
/// <para>
/// Payload is returned as a <see cref="ReadOnlyMemory{T}"/> view into the
/// original message body — no copy. Per-event parsers slice from this.
/// </para>
/// </summary>
public readonly record struct GameEventEnvelope(
uint PlayerGuid,
uint Sequence,
GameEventType EventType,
ReadOnlyMemory<byte> Payload)
{
/// <summary>GameMessage opcode of the outer envelope.</summary>
public const uint Opcode = 0xF7B0u;
/// <summary>Header size (4×u32 = 16 bytes before payload).</summary>
public const int HeaderSize = 16;
/// <summary>
/// Parse a raw 0xF7B0 GameMessage body into the header + payload view.
/// Returns null if the body is shorter than the header or the outer
/// opcode doesn't match.
/// </summary>
public static GameEventEnvelope? TryParse(byte[] body)
{
ArgumentNullException.ThrowIfNull(body);
return TryParseBorrowed(body);
}
/// <summary>
/// Internal receive-path parser. The returned payload borrows
/// <paramref name="body"/> and must not escape synchronous dispatch.
/// </summary>
internal static GameEventEnvelope? TryParseBorrowed(
ReadOnlyMemory<byte> body)
{
if (body.Length < HeaderSize) return null;
ReadOnlySpan<byte> span = body.Span;
uint outer = BinaryPrimitives.ReadUInt32LittleEndian(span);
if (outer != Opcode) return null;
uint guid = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(4));
uint sequence = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(8));
uint eventType = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(12));
ReadOnlyMemory<byte> payload = body.Slice(HeaderSize);
return new GameEventEnvelope(guid, sequence, (GameEventType)eventType, payload);
}
}