CultLib/src/GameCult.Mesh/CultMeshStreaming.cs
2026-07-13 15:49:53 +01:00

635 lines
23 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using static GameCult.Mesh.CultMeshGuards;
#pragma warning disable CS1591
namespace GameCult.Mesh
{
public enum CultMeshStreamKind
{
Audio,
Video,
Tensor,
Bytes
}
public enum CultMeshStreamBodyTransport
{
SharedMemory,
SharedD3D12Texture,
SharedD3D11Texture,
DmaBuf,
IOSurface,
AHardwareBuffer,
CultCachePage,
InlineBytes
}
public enum CultMeshStreamAccess
{
Read,
Write,
ReadWrite
}
public enum CultMeshStreamCopyBudget
{
ZeroCopyTarget,
OneCopyFallback,
OpaqueRuntime
}
public sealed class CultMeshStreamClock
{
public CultMeshStreamClock(
string clockDomainId,
string? sourceId = null,
int sampleRate = 0,
long offsetToVerseTimeNs = 0,
double confidence = 0,
string? evidenceKind = null)
{
ClockDomainId = RequireNonEmpty(clockDomainId, nameof(clockDomainId));
SourceId = sourceId;
SampleRate = sampleRate;
OffsetToVerseTimeNs = offsetToVerseTimeNs;
Confidence = confidence;
EvidenceKind = evidenceKind;
}
public string ClockDomainId { get; }
public string? SourceId { get; }
public int SampleRate { get; }
public long OffsetToVerseTimeNs { get; }
public double Confidence { get; }
public string? EvidenceKind { get; }
}
public sealed class CultMeshAudioStreamFormat
{
public CultMeshAudioStreamFormat(int sampleRate, int channels, string sampleFormat, int framesPerPacket = 0)
{
if (sampleRate <= 0) throw new ArgumentOutOfRangeException(nameof(sampleRate));
if (channels <= 0) throw new ArgumentOutOfRangeException(nameof(channels));
SampleRate = sampleRate;
Channels = channels;
SampleFormat = RequireNonEmpty(sampleFormat, nameof(sampleFormat));
FramesPerPacket = framesPerPacket;
}
public int SampleRate { get; }
public int Channels { get; }
public string SampleFormat { get; }
public int FramesPerPacket { get; }
}
public sealed class CultMeshVideoStreamFormat
{
public CultMeshVideoStreamFormat(int width, int height, string pixelFormat, double framesPerSecond = 0, int planeCount = 1)
{
if (width <= 0) throw new ArgumentOutOfRangeException(nameof(width));
if (height <= 0) throw new ArgumentOutOfRangeException(nameof(height));
if (planeCount <= 0) throw new ArgumentOutOfRangeException(nameof(planeCount));
Width = width;
Height = height;
PixelFormat = RequireNonEmpty(pixelFormat, nameof(pixelFormat));
FramesPerSecond = framesPerSecond;
PlaneCount = planeCount;
}
public int Width { get; }
public int Height { get; }
public string PixelFormat { get; }
public double FramesPerSecond { get; }
public int PlaneCount { get; }
}
public sealed class CultMeshStreamDescriptor
{
public CultMeshStreamDescriptor(
string streamId,
string verseId,
string ownerPeerId,
CultMeshStreamKind kind,
CultMeshStreamClock clock,
IReadOnlyList<CultMeshStreamBodyTransport> preferredTransports,
string? label = null,
CultMeshAudioStreamFormat? audio = null,
CultMeshVideoStreamFormat? video = null,
CultMeshStreamAccess requiredAccess = CultMeshStreamAccess.Read,
int maxInFlightFrames = 0,
string? metadataSchemaId = null)
{
StreamId = RequireNonEmpty(streamId, nameof(streamId));
VerseId = RequireNonEmpty(verseId, nameof(verseId));
OwnerPeerId = RequireNonEmpty(ownerPeerId, nameof(ownerPeerId));
Kind = kind;
Clock = clock ?? throw new ArgumentNullException(nameof(clock));
PreferredTransports = preferredTransports?.ToArray() ?? throw new ArgumentNullException(nameof(preferredTransports));
if (PreferredTransports.Count == 0)
{
throw new ArgumentException("At least one body transport must be preferred.", nameof(preferredTransports));
}
Label = label;
Audio = audio;
Video = video;
RequiredAccess = requiredAccess;
MaxInFlightFrames = maxInFlightFrames;
MetadataSchemaId = metadataSchemaId;
}
public string StreamId { get; }
public string VerseId { get; }
public string OwnerPeerId { get; }
public CultMeshStreamKind Kind { get; }
public string? Label { get; }
public CultMeshStreamClock Clock { get; }
public CultMeshAudioStreamFormat? Audio { get; }
public CultMeshVideoStreamFormat? Video { get; }
public IReadOnlyList<CultMeshStreamBodyTransport> PreferredTransports { get; }
public CultMeshStreamAccess RequiredAccess { get; }
public int MaxInFlightFrames { get; }
public string? MetadataSchemaId { get; }
}
public sealed class CultMeshStreamConsumerProfile
{
public CultMeshStreamConsumerProfile(
string peerId,
string verseId,
IReadOnlyList<CultMeshStreamBodyTransport> supportedTransports,
IReadOnlyList<CultMeshStreamKind>? acceptedKinds = null,
bool canImportGpuHandles = false,
bool canMapSharedMemory = false,
int maxInFlightFrames = 0)
{
PeerId = RequireNonEmpty(peerId, nameof(peerId));
VerseId = RequireNonEmpty(verseId, nameof(verseId));
SupportedTransports = supportedTransports?.ToArray() ?? throw new ArgumentNullException(nameof(supportedTransports));
AcceptedKinds = acceptedKinds?.ToArray() ?? Array.Empty<CultMeshStreamKind>();
CanImportGpuHandles = canImportGpuHandles;
CanMapSharedMemory = canMapSharedMemory;
MaxInFlightFrames = maxInFlightFrames;
}
public string PeerId { get; }
public string VerseId { get; }
public IReadOnlyList<CultMeshStreamBodyTransport> SupportedTransports { get; }
public IReadOnlyList<CultMeshStreamKind> AcceptedKinds { get; }
public bool CanImportGpuHandles { get; }
public bool CanMapSharedMemory { get; }
public int MaxInFlightFrames { get; }
}
public sealed class CultMeshStreamNegotiation
{
public CultMeshStreamNegotiation(
string streamId,
string producerPeerId,
string consumerPeerId,
CultMeshStreamBodyTransport transport,
CultMeshStreamAccess access,
int maxInFlightFrames,
CultMeshStreamCopyBudget copyBudget)
{
StreamId = streamId;
ProducerPeerId = producerPeerId;
ConsumerPeerId = consumerPeerId;
Transport = transport;
Access = access;
MaxInFlightFrames = maxInFlightFrames;
CopyBudget = copyBudget;
}
public string StreamId { get; }
public string ProducerPeerId { get; }
public string ConsumerPeerId { get; }
public CultMeshStreamBodyTransport Transport { get; }
public CultMeshStreamAccess Access { get; }
public int MaxInFlightFrames { get; }
public CultMeshStreamCopyBudget CopyBudget { get; }
}
public sealed class CultMeshStreamFrameHandle
{
public CultMeshStreamFrameHandle(
string streamId,
ulong sequence,
long timestampNs,
CultMeshStreamBodyTransport transport,
long durationNs = 0,
int byteLength = 0,
string? nativeHandle = null,
string? resourceKey = null,
string? pageRef = null,
string? fenceHandle = null,
ulong fenceValue = 0,
int unavoidableCopyCount = 0,
IReadOnlyDictionary<string, string>? metadata = null)
{
StreamId = RequireNonEmpty(streamId, nameof(streamId));
Sequence = sequence;
TimestampNs = timestampNs;
DurationNs = durationNs;
Transport = transport;
ByteLength = byteLength;
NativeHandle = nativeHandle;
ResourceKey = resourceKey;
PageRef = pageRef;
FenceHandle = fenceHandle;
FenceValue = fenceValue;
UnavoidableCopyCount = unavoidableCopyCount;
Metadata = metadata ?? new Dictionary<string, string>();
}
public string StreamId { get; }
public ulong Sequence { get; }
public long TimestampNs { get; }
public long DurationNs { get; }
public CultMeshStreamBodyTransport Transport { get; }
public int ByteLength { get; }
public string? NativeHandle { get; }
public string? ResourceKey { get; }
public string? PageRef { get; }
public string? FenceHandle { get; }
public ulong FenceValue { get; }
public int UnavoidableCopyCount { get; }
public IReadOnlyDictionary<string, string> Metadata { get; }
}
public readonly struct CultMeshFrameWriteLease
{
private readonly CultMeshFrameRegionWriteLease _lease;
internal CultMeshFrameWriteLease(CultMeshFrameRegionWriteLease lease)
{
_lease = lease;
}
public int SlotIndex => _lease.SlotIndex;
public ulong Sequence => checked((ulong)_lease.Sequence);
public Memory<byte> Memory => _lease.Memory;
public Span<byte> Span => Memory.Span;
internal CultMeshFrameRegionWriteLease RegionLease => _lease;
}
public readonly struct CultMeshFrameReadLease : IDisposable
{
private readonly CultMeshFrameRegionReadLease? _lease;
internal CultMeshFrameReadLease(
CultMeshFrameRegionReadLease lease,
CultMeshStreamFrameHandle handle,
int slotIndex)
{
_lease = lease;
SlotIndex = slotIndex;
Handle = handle;
}
public int SlotIndex { get; }
public CultMeshStreamFrameHandle Handle { get; }
public ReadOnlyMemory<byte> Memory => _lease?.Memory ?? ReadOnlyMemory<byte>.Empty;
public ReadOnlySpan<byte> Span => Memory.Span;
public void Dispose()
{
_lease?.Dispose();
}
}
public sealed class CultMeshSharedMemoryFrameRingStats
{
public CultMeshSharedMemoryFrameRingStats(
string streamId,
int slotCount,
int slotByteLength,
ulong publishedFrames,
ulong droppedFrames,
ulong blockedWrites,
ulong latestSequence,
ulong unavoidableCopyCount)
{
StreamId = streamId;
SlotCount = slotCount;
SlotByteLength = slotByteLength;
PublishedFrames = publishedFrames;
DroppedFrames = droppedFrames;
BlockedWrites = blockedWrites;
LatestSequence = latestSequence;
UnavoidableCopyCount = unavoidableCopyCount;
}
public string StreamId { get; }
public int SlotCount { get; }
public int SlotByteLength { get; }
public ulong PublishedFrames { get; }
public ulong DroppedFrames { get; }
public ulong BlockedWrites { get; }
public ulong LatestSequence { get; }
public ulong UnavoidableCopyCount { get; }
}
public sealed class CultMeshSharedMemoryFrameRing : IDisposable
{
private readonly CultMeshFrameRegion _region;
public CultMeshSharedMemoryFrameRing(string streamId, int slotCount, int slotByteLength)
{
StreamId = RequireNonEmpty(streamId, nameof(streamId));
if (slotCount != CultMeshFrameRegion.SlotCount)
throw new ArgumentOutOfRangeException(nameof(slotCount), "CultMesh frame regions use exactly three slots.");
_region = new CultMeshFrameRegion(
streamId,
$"{streamId}.frame",
layoutVersion: 1,
capacity: slotByteLength,
producerEpoch: 0,
slotByteLength);
}
public string StreamId { get; }
public int SlotCount => CultMeshFrameRegion.SlotCount;
public int SlotByteLength => _region.SlotByteLength;
public bool TryAcquireWriteSlot(out CultMeshFrameWriteLease lease)
{
if (_region.TryAcquireWrite(out var regionLease))
{
lease = new CultMeshFrameWriteLease(regionLease);
return true;
}
lease = default;
return false;
}
public CultMeshStreamFrameHandle CommitWriteSlot(
CultMeshFrameWriteLease lease,
long timestampNs,
int byteLength,
long durationNs = 0,
int unavoidableCopyCount = 0,
IReadOnlyDictionary<string, string>? metadata = null)
{
var generation = _region.Commit(
lease.RegionLease, byteLength, timestampNs, durationNs, unavoidableCopyCount, metadata);
return ToHandle(generation);
}
public bool TryPublishCopy(
ReadOnlySpan<byte> bytes,
long timestampNs,
long durationNs,
out CultMeshStreamFrameHandle handle)
{
if (bytes.Length > SlotByteLength)
{
throw new ArgumentOutOfRangeException(nameof(bytes));
}
if (!TryAcquireWriteSlot(out var lease))
{
handle = default!;
return false;
}
bytes.CopyTo(lease.Span);
handle = CommitWriteSlot(lease, timestampNs, bytes.Length, durationNs, unavoidableCopyCount: 1);
return true;
}
public bool TryAcquireLatestRead(out CultMeshFrameReadLease lease)
{
var request = new CultMeshBodyValidationRequest
{
BodyId = _region.BodyId,
SchemaId = _region.SchemaId,
LayoutVersion = _region.LayoutVersion,
ProducerEpoch = _region.ProducerEpoch,
Capacity = _region.Capacity,
AccessMode = CultMeshBodyAccessMode.ReadOnly
};
if (!_region.TryAcquireLatestRead(request, out var regionLease))
{
lease = default;
return false;
}
var generation = regionLease.Generation;
lease = new CultMeshFrameReadLease(regionLease, ToHandle(generation), generation.SlotIndex);
return true;
}
public CultMeshSharedMemoryFrameRingStats Stats()
{
var stats = _region.Stats();
return new CultMeshSharedMemoryFrameRingStats(
StreamId,
SlotCount,
SlotByteLength,
stats.PublishedFrames,
stats.DroppedFrames,
stats.BlockedWrites,
checked((ulong)stats.LatestSequence),
stats.UnavoidableCopyCount);
}
public void Dispose() => _region.Dispose();
private CultMeshStreamFrameHandle ToHandle(CultMeshFrameGeneration generation) => new(
StreamId,
checked((ulong)generation.Descriptor.Sequence),
generation.TimestampNs,
CultMeshStreamBodyTransport.SharedMemory,
generation.DurationNs,
checked((int)generation.Descriptor.ByteSize),
resourceKey: $"{StreamId}:slot:{generation.SlotIndex}",
unavoidableCopyCount: generation.UnavoidableCopyCount,
metadata: generation.Metadata);
}
public sealed class CultMeshStreamCatalog
{
private readonly Dictionary<string, CultMeshStreamDescriptor> _streams = new(StringComparer.Ordinal);
private readonly Dictionary<string, CultMeshStreamFrameHandle> _latestFrames = new(StringComparer.Ordinal);
private readonly Dictionary<string, CultMeshSharedMemoryFrameRing> _rings = new(StringComparer.Ordinal);
private readonly object _gate = new();
public IReadOnlyList<CultMeshStreamDescriptor> Streams
{
get
{
lock (_gate)
{
return _streams.Values.OrderBy(stream => stream.StreamId, StringComparer.Ordinal).ToArray();
}
}
}
public void Declare(CultMeshStreamDescriptor stream)
{
if (stream == null) throw new ArgumentNullException(nameof(stream));
lock (_gate)
{
_streams[stream.StreamId] = stream;
}
}
public CultMeshStreamDescriptor? Get(string streamId)
{
lock (_gate)
{
return _streams.TryGetValue(RequireNonEmpty(streamId, nameof(streamId)), out var stream)
? stream
: null;
}
}
public IReadOnlyList<CultMeshStreamDescriptor> Find(string verseId, CultMeshStreamKind? kind = null)
{
verseId = RequireNonEmpty(verseId, nameof(verseId));
lock (_gate)
{
return _streams.Values
.Where(stream => string.Equals(stream.VerseId, verseId, StringComparison.Ordinal) &&
(!kind.HasValue || stream.Kind == kind.Value))
.OrderBy(stream => stream.StreamId, StringComparer.Ordinal)
.ToArray();
}
}
public CultMeshStreamNegotiation Negotiate(string streamId, CultMeshStreamConsumerProfile consumer)
{
if (consumer == null) throw new ArgumentNullException(nameof(consumer));
CultMeshStreamDescriptor stream;
lock (_gate)
{
stream = _streams.TryGetValue(RequireNonEmpty(streamId, nameof(streamId)), out var existing)
? existing
: throw new InvalidOperationException($"Unknown CultMesh stream '{streamId}'.");
}
if (!string.Equals(stream.VerseId, consumer.VerseId, StringComparison.Ordinal))
{
throw new InvalidOperationException("Stream and consumer must belong to the same Verse.");
}
if (consumer.AcceptedKinds.Count > 0 && !consumer.AcceptedKinds.Contains(stream.Kind))
{
throw new InvalidOperationException($"Consumer does not accept {stream.Kind} streams.");
}
var transport = FirstCompatibleTransport(stream, consumer);
if (!transport.HasValue)
{
throw new InvalidOperationException("Stream and consumer have no compatible body transport.");
}
var producerMax = stream.MaxInFlightFrames <= 0 ? int.MaxValue : stream.MaxInFlightFrames;
var consumerMax = consumer.MaxInFlightFrames <= 0 ? int.MaxValue : consumer.MaxInFlightFrames;
return new CultMeshStreamNegotiation(
stream.StreamId,
stream.OwnerPeerId,
consumer.PeerId,
transport.Value,
stream.RequiredAccess,
Math.Min(producerMax, consumerMax),
CopyBudgetFor(transport.Value));
}
public void PublishFrame(CultMeshStreamFrameHandle handle)
{
if (handle == null) throw new ArgumentNullException(nameof(handle));
lock (_gate)
{
if (!_streams.ContainsKey(handle.StreamId))
{
throw new InvalidOperationException($"Unknown CultMesh stream '{handle.StreamId}'.");
}
_latestFrames[handle.StreamId] = handle;
}
}
public CultMeshStreamFrameHandle? LatestFrame(string streamId)
{
lock (_gate)
{
return _latestFrames.TryGetValue(RequireNonEmpty(streamId, nameof(streamId)), out var handle)
? handle
: null;
}
}
public CultMeshSharedMemoryFrameRing CreateSharedMemoryRing(string streamId, int slotCount, int slotByteLength)
{
lock (_gate)
{
if (!_streams.ContainsKey(RequireNonEmpty(streamId, nameof(streamId))))
{
throw new InvalidOperationException($"Unknown CultMesh stream '{streamId}'.");
}
var ring = new CultMeshSharedMemoryFrameRing(streamId, slotCount, slotByteLength);
_rings[streamId] = ring;
return ring;
}
}
public CultMeshSharedMemoryFrameRing? Ring(string streamId)
{
lock (_gate)
{
return _rings.TryGetValue(RequireNonEmpty(streamId, nameof(streamId)), out var ring)
? ring
: null;
}
}
private static CultMeshStreamCopyBudget CopyBudgetFor(CultMeshStreamBodyTransport transport)
{
return transport switch
{
CultMeshStreamBodyTransport.SharedMemory => CultMeshStreamCopyBudget.ZeroCopyTarget,
CultMeshStreamBodyTransport.SharedD3D12Texture => CultMeshStreamCopyBudget.ZeroCopyTarget,
CultMeshStreamBodyTransport.SharedD3D11Texture => CultMeshStreamCopyBudget.ZeroCopyTarget,
CultMeshStreamBodyTransport.DmaBuf => CultMeshStreamCopyBudget.ZeroCopyTarget,
CultMeshStreamBodyTransport.IOSurface => CultMeshStreamCopyBudget.ZeroCopyTarget,
CultMeshStreamBodyTransport.AHardwareBuffer => CultMeshStreamCopyBudget.ZeroCopyTarget,
CultMeshStreamBodyTransport.CultCachePage => CultMeshStreamCopyBudget.OneCopyFallback,
_ => CultMeshStreamCopyBudget.OpaqueRuntime
};
}
private static CultMeshStreamBodyTransport? FirstCompatibleTransport(
CultMeshStreamDescriptor stream,
CultMeshStreamConsumerProfile consumer)
{
foreach (var transport in stream.PreferredTransports)
{
if (consumer.SupportedTransports.Contains(transport))
{
return transport;
}
}
return null;
}
}
internal static partial class CultMeshGuards
{
internal static string RequireNonEmpty(string value, string name)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Value must be non-empty.", name);
}
return value;
}
}
}