Skip to content

C# — Client

Client API is in the Nnrp.Client namespace.

Import

csharp
using Nnrp.Client;
using Nnrp.Core;

ClientProfile

Client configuration (mutable class).

PropertyTypeDefaultDescription
MaxViewsint1Max concurrent views
EnableCachebooltrueEnable server-side cache negotiation
MaxCacheEntriesint256Max cache entries
MaxCacheByteslong8388608Max cache bytes (8 MB)

NnrpClient

Top-level client that manages transport connection and session creation.

csharp
public sealed class NnrpClient : IAsyncDisposable
{
    public static Task<NnrpClient> ConnectAsync(
        string host, int port,
        ClientProfile profile,
        NnrpClientOptions? options = null,
        CancellationToken ct = default);

    public Task<INnrpClientSession> OpenSessionAsync(
        CancellationToken ct = default);

    public ValueTask DisposeAsync();
}

NnrpClientOptions

PropertyTypeDefaultDescription
TransportPolicyTransportPolicyAutoTransport selection policy
LossToleranceLossToleranceBestEffortLoss tolerance
AuthBlockbyte[]Array.Empty<byte>()Auth block bytes
ConnectTimeoutMsint10000Connection timeout (ms)

INnrpClientSession

Client session interface for frame submission and result receive.

csharp
public interface INnrpClientSession : IAsyncDisposable
{
    uint SessionId { get; }
    TransportId ActiveTransportId { get; }

    // Frame submission
    Task<NnrpSubmitResult> SubmitAsync(
        NnrpSubmitRequest request,
        CancellationToken ct = default);
    Task SubmitFireAndForgetAsync(
        NnrpSubmitRequest request,
        CancellationToken ct = default);

    // Result receive
    Task<NnrpSubmitResult> ReceiveResultAsync(
        uint frameId,
        CancellationToken ct = default);

    // Session management
    Task<SessionPatchAckMessage> PatchSessionAsync(
        SessionPatchMessage patch,
        CancellationToken ct = default);
    Task CloseAsync(CancellationToken ct = default);
}

Method Parameter Reference

MethodParametersReturnsDescription
SubmitAsyncrequest: NnrpSubmitRequest (requires FrameId); ctNnrpSubmitResultSubmit a frame and block until the result arrives; throws NnrpResultDroppedException if dropped
SubmitFireAndForgetAsyncrequest: NnrpSubmitRequest; ctTaskNon-blocking submit; use ReceiveResultAsync to poll for the result separately
ReceiveResultAsyncframeId: the ID passed in the prior SubmitFireAndForgetAsyncNnrpSubmitResultAwait the result for a specific frame; must be paired with SubmitFireAndForgetAsync
PatchSessionAsyncpatch: SessionPatchMessage with Fields (SessionPatchField bitmask) and new valuesSessionPatchAckMessageDynamically adjust TargetCadence, QualityTier, active lane mask, preferred codec, etc. Only fields whose bit is set in Fields are applied
CloseAsyncTaskSend CLOSE and gracefully terminate the connection; automatically called by DisposeAsync()

NnrpSubmitRequest key fields: FrameId (required, monotonically increasing), TileIds (changed tile IDs), Sections (NnrpTensorSection array), InputProfile (InputProfile), SubmitMode (Inline = carry data inline / Reference = reference a cached object), BudgetPolicy (BudgetPolicy bitmask controlling whether degraded/partial results are acceptable), InferenceBudgetMs (inference time budget in ms; 0 = unlimited).


Data Types

NnrpSubmitRequest

Frame submit request.

PropertyTypeDescription
FrameIduintFrame ID
TileIdsReadOnlyMemory<ushort>Tile IDs
SectionsIReadOnlyList<NnrpTensorSection>Tensor sections
TypedPayloadsIReadOnlyList<NnrpTypedPayload>Non-tensor payloads
InputProfileInputProfileInput data format
SubmitModeSubmitModeSubmission mode
BudgetPolicyBudgetPolicyAllowed degradation
InferenceBudgetMsintMax inference budget (ms)
DeadlineMslongAbsolute deadline

NnrpTensorSection

PropertyTypeDescription
RoleIdintTensor role ID
DTypeIdDTypeIdData type
LayoutIdTensorLayoutIdMemory layout
DefaultCodecIdintDefault codec ID
TilePayloadsIReadOnlyList<ReadOnlyMemory<byte>>Per-tile payload bytes

NnrpTypedPayload

PropertyTypeDescription
PayloadKindPayloadKindPayload type
DataReadOnlyMemory<byte>Raw payload bytes

NnrpSubmitResult

PropertyTypeDescription
FrameIduintFrame ID
ResultClassResultClassResult class
ResultFlagsResultFlagsResult flags
InferenceMsintInference time (ms)
ServerTotalMsintTotal server time (ms)
SectionsIReadOnlyList<NnrpTensorSection>Result tensor sections
TypedPayloadsIReadOnlyList<NnrpTypedPayload>Non-tensor payload frames

Example

csharp
using Nnrp.Client;
using Nnrp.Core;

var profile = new ClientProfile { MaxViews = 1, EnableCache = true };
await using var client = await NnrpClient.ConnectAsync("localhost", 4433, profile);
await using var session = await client.OpenSessionAsync();

var request = new NnrpSubmitRequest
{
    FrameId = 1,
    InputProfile = InputProfile.DenseLumaFrame,
    BudgetPolicy = BudgetPolicy.AllowPartial,
    Sections = new[] { tileSection },
};

var result = await session.SubmitAsync(request);
Console.WriteLine($"Result: {result.ResultClass}, InferenceMs={result.InferenceMs}");

Typical Use Cases

Full Render Loop

csharp
var config = new NnrpQuicClientConfiguration { CaFile = "ca.pem" };
var profile = new NnrpClientProfile { TransportPolicy = TransportPolicy.PreferQuic };
await using var client = await NnrpClient.ConnectAsync("render.example.com", 4433, profile);
await using var session = await client.OpenSessionAsync();

for (int frameId = 0; ; frameId++)
{
    var (tiles, tensor) = CaptureChangedTiles();
    var result = await session.SubmitAsync(new NnrpSubmitRequest
    {
        FrameId      = frameId,
        TileIds      = tiles,
        Sections     = new[] { tensor },
        InputProfile = InputProfile.ChangedTilesLuma,
        BudgetPolicy = BudgetPolicy.AllowPartial,
    });
    if (result.ResultClass == ResultClass.Complete)
        Display(result.Sections);
}

Responding to Backpressure

csharp
session.OnResultHint += hint =>
{
    if (hint.CongestionState == ResultHintCongestionState.Saturated)
        _paused = true;
    else if (hint.CongestionState == ResultHintCongestionState.None)
        _paused = false;
};

Common Pitfalls

WARNING

  1. Always await using var sessionNnrpClientSession is IAsyncDisposable. Not disposing leaks the underlying transport connection.

  2. Do not call SubmitAsync concurrently from multiple threads. The send path is not thread-safe. Use Channel<T> to serialize requests.

  3. After SubmitAsync times out, the frame ID slot is still held. Call session.DiscardFrame(frameId) to release it.

  4. DeadlineMs is an absolute Unix timestamp in milliseconds, not a relative offset. Confusing it with InferenceBudgetMs causes server-side timeout misdetection.

NNRP Documentation