by Pivec Labs
API Reference Generated code

API Reference — Generated Code

API reference for NetCrafter Relay-generated C# classes and the runtime components that ship with the package.

This reference describes the classes that NetCrafter Relay generates from your schema. Client files are placed in Definitions/{Name}/Generated/Client/, host logic in Definitions/{Name}/Generated/Host/ — both share the namespace set in Project Settings → Naming Conventions.

01

Field type mapping#

Schema type C# type Write (send) Read (receive)
int int writer.WriteInt32(value) reader.ReadInt32()
float float writer.WriteFloat(value) reader.ReadFloat()
bool bool writer.WriteBool(value) reader.ReadBool()
string string writer.WriteString(value) reader.ReadString()
PlayerId int writer.WriteInt32(value) reader.ReadInt32()
ItemId string writer.WriteString(value) reader.ReadString()
Vector2 Vector2 writer.WriteVector2(value) reader.ReadVector2()
Vector3 Vector3 writer.WriteVector3(value) reader.ReadVector3()
Quaternion Quaternion writer.WriteQuaternion(value) reader.ReadQuaternion()
ModelName ModelName model.WriteTo(writer) ModelName.ReadFrom(reader)
Any [] List<T> int count, then each element in order int count, then .Add(...) per element

There are no per-field keys on the wire — RelayWriter/RelayReader are purely positional. See Scripts/Relay/RelayWriter.cs and RelayReader.cs.

02

Request classes#

Generated from ClientToHost (and ClientToHostBroadcast) messages.

Filename: {MessageName}Request.cs Class name: {MessageName}Request

Members

Member Description
Public fields One public field per message field, typed as shown in the mapping table above
byte[] Serialize() Serialises all fields into a new RelayWriter and returns the resulting bytes
void Send(INetCrafterConnection connection, bool reliable = true) Serialises and sends via connection.Send(cmd, bytes, reliable), using the message name as the command string
static {MessageName}Request Deserialize(byte[] data) Used host-side by HostActionRouter to reconstruct an incoming trigger message

Example

Schema message PlayerMove with fields playerId: PlayerId, position: Vector3, speed: float generates:

csharp
// Usage
var req = new PlayerMoveRequest
{
    playerId = RelayConnectionManager.Instance.LocalUserId,
    position = transform.position,
    speed    = currentSpeed
};
req.Send(RelayConnectionManager.Instance);

The generated class:

csharp
public class PlayerMoveRequest
{
    public int     playerId;
    public Vector3 position;
    public float   speed;

    public byte[] Serialize()
    {
        var writer = new PivecLabs.NetCrafterRelay.RelayWriter();
        writer.WriteInt32(playerId);
        writer.WriteVector3(position);
        writer.WriteFloat(speed);
        return writer.ToArray();
    }

    public void Send(PivecLabs.NetCrafterRelay.INetCrafterConnection connection, bool reliable = true)
    {
        connection.Send("PlayerMove", Serialize(), reliable);
    }

    public static PlayerMoveRequest Deserialize(byte[] data)
    {
        var reader = new PivecLabs.NetCrafterRelay.RelayReader(data);
        var req = new PlayerMoveRequest();
        req.playerId = reader.ReadInt32();
        req.position = reader.ReadVector3();
        req.speed    = reader.ReadFloat();
        return req;
    }
}
03

Event classes#

Generated from HostToClient messages.

Filename: {EventName}Event.cs Class name: {EventName}Event

Members

Member Description
Public fields One public field per event field
byte[] Serialize() Built by host-side HostActionRouter logic before broadcasting to peers
static {EventName}Event Deserialize(byte[] data) Used by the receiving peer's RelayConnectionManager to reconstruct the event

Example

Schema event PlayerStateUpdate with a nested model field state: PlayerState generates:

csharp
// Usage — called automatically by RelayConnectionManager
private void OnPlayerStateUpdate(PlayerStateUpdateEvent ev)
{
    Debug.Log($"Health: {ev.state.health}, Position: {ev.state.position}");
}

The generated class:

csharp
public class PlayerStateUpdateEvent
{
    public PlayerState state;

    public byte[] Serialize()
    {
        var writer = new PivecLabs.NetCrafterRelay.RelayWriter();
        state?.WriteTo(writer);
        return writer.ToArray();
    }

    public static PlayerStateUpdateEvent Deserialize(byte[] data)
    {
        var reader = new PivecLabs.NetCrafterRelay.RelayReader(data);
        var ev = new PlayerStateUpdateEvent();
        ev.state = PlayerState.ReadFrom(reader);
        return ev;
    }
}
04

Model classes#

Generated from Data Definitions.

Filename: {ModelName}.cs Class name: {ModelName}

Members

Member Description
Public fields One public field per model field
void WriteTo(RelayWriter writer) Serialises all fields into the given writer. Use this when embedding a model inside a request or event.
static {ModelName} ReadFrom(RelayReader reader) Deserialises all fields from the given reader. Use this when extracting a model from an incoming payload.

Example

Data Definition PlayerState with fields position: Vector3, health: int, username: string, isAlive: bool generates:

csharp
// Embed in a request/event field
var state = new PlayerState
{
    position = transform.position,
    health   = currentHealth,
    username = playerName,
    isAlive  = true
};
ev.state = state;  // event field of type PlayerState

// Reading happens automatically inside the parent Request/Event's Deserialize() —
// you don't call ReadFrom directly unless you're hand-rolling custom (de)serialization.
05

RelayConnectionManager#

Generated once per schema and placed in Generated/Client/RelayConnectionManager.cs.

Class: RelayConnectionManager : MonoBehaviour, INetCrafterConnection

Lifecycle

Method When it runs
Awake() Initialises the singleton. Destroys duplicate instances. Calls DontDestroyOnLoad.
Start() Calls Connect().
Update() Drains the thread-safe incoming-datagram queue fed by the background StunTurnClient receive loop, dispatches events, and retries any pending Reliable sends.
OnApplicationQuit() Stops the relay client cleanly.

Public API (INetCrafterConnection)

Member Description
static RelayConnectionManager Instance Singleton accessor. Available after the first Awake.
bool IsConnected true once the relay allocation (and, for joiners, the permission to the host) has succeeded.
bool IsHost true only on the peer that called Connect() with no join code set — i.e. whichever peer is hosting this session.
int LocalUserId 0 for the host; assigned by the host for each joining peer.
string LocalUserName Guest name assigned per Relay Settings → Login.
void Send(string cmd, byte[] payload, bool reliable) Sends to the host (no-op with a warning if called on the host itself — see BroadcastToAll/SendToPeer below).
void UpdateHost(string joinCode) Sets the join code to resolve on the next Connect(). Empty code (the default) means "host a new session" instead of joining. Resets the connection so Connect() starts fresh.
void Connect() Hosts a new session (no join code set) or joins an existing one (join code set via UpdateHost).

payload should stay under roughly 1200-1400 bytes. There's no application-level fragmentation — a larger payload is silently dropped (not truncated), confirmed live: a 4000-byte payload never arrived while 1400 bytes round-tripped fine. See Manual.html → Known Limitations → Message size limits.

Host-only API

Available only when IsHost is true — used by the generated HostActionRouter, not typically called directly from game code:

Member Description
void BroadcastToAll(string cmd, byte[] payload, bool reliable, int excludePeerId = -1) Relays to every known peer except excludePeerId.
void SendToPeer(int peerId, string cmd, byte[] payload, bool reliable) Relays to one specific peer by assigned id.

Events

Member Description
event Action<int> OnPeerDisconnected Fires once per peer after roughly 15 seconds of total silence from them (no heartbeat and no other traffic). Passes the peer's id (0 from a joiner's perspective, since its only peer is the host; the assigned peer id from the host's perspective).

This is basic silence-based disconnect detection, not a connection-quality or reconnection system — no lag-spike-vs-real-outage distinction, no automatic retry, fires exactly once per peer. See Manual.html → Known Limitations → Disconnect detection.

Inspector fields

The generated manager has a single Inspector field, connectionSettings — a live reference to this schema's own RelaySettings asset (Definitions/{Name}/RelaySettings.asset), the same asset the Relay Settings tab edits. It is auto-wired whenever the prefab is (re)created via Prefab Generator — there is nothing to fill in by hand unless you built the GameObject yourself instead of using the tool.

Earlier versions of this component carried relayHost/relayPort/rendezvousUrl/credentialMode/turnUsername/turnSharedSecret as its own literal fields, baked in at code-generation time. That design let a scene's already-placed instance silently keep serving a stale value after a Relay Settings edit — editing Settings, regenerating, and even recreating the prefab all failed to update a GameObject already sitting in a scene. The single live reference replaces that entirely: there is no copy to go stale, so there is nothing left to override per-scene.

Read the live values via the referenced asset directly, e.g. connectionSettings.relayHost, connectionSettings.rendezvousUrl, connectionSettings.credentialMode, connectionSettings.debugLogging — see RelaySettings.cs for the full field list.

Event handling

The generated manager routes every incoming relay message by command name. Each switch case deserialises the payload, forwards it to NetCrafterEventDispatcher (if present in the scene), then calls the private stub method:

csharp
private void DispatchLocal(string cmd, byte[] payload)
{
    switch (cmd)
    {
        case "PlayerStateUpdate":
        {
            var ev = PlayerStateUpdateEvent.Deserialize(payload);
            NetCrafterEventDispatcher.Instance?.Dispatch("PlayerStateUpdate", ev);
            OnPlayerStateUpdate(ev);
            break;
        }
        // ... one case per event in your schema
    }
}

Both the dispatcher call and the stub method always fire. This means you can handle events either by subscribing to NetCrafterEventDispatcher from any script, or by overriding the stub in a subclass — or both.

Each handler stub is generated as a private method. Fill them in to respond to host pushes:

csharp
private void OnPlayerStateUpdate(PlayerStateUpdateEvent ev)
{
    // Your game logic here
}

Editing generated code

The generated file is yours to edit — Generate All Files with Overwrite Existing Files enabled will replace it, so keep custom code in a subclass or copy the file out of the Generated/ folder.

06

HostActionRouter#

Generated once per schema and placed in Generated/Host/HostActionRouter.cs. Executed only on the peer where connection.IsHost is true — every other peer's copy of this class simply never has Handle() invoked by its own RelayConnectionManager.

Class: HostActionRouter : MonoBehaviour

Members

Member Description
static HostActionRouter Instance Singleton accessor.
void Handle(string cmd, int senderId, byte[] payload, RelayConnectionManager connection) Called by RelayConnectionManager for every incoming message while this peer is host. Dispatches to one generated method per Host Action.

Generated handler methods

One Handle_{HandlerName} method per Host Action:

csharp
private void Handle_HandlePlayerMove(int senderId, byte[] payload, RelayConnectionManager connection)
{
    var trigger = PlayerMoveRequest.Deserialize(payload);

    var response = new PlayerStateUpdateEvent();
    response.playerId = senderId; // auto-mapped if the response event has a matching "playerId" int field
    byte[] responseBytes = response.Serialize();

    connection.BroadcastToAll("PlayerStateUpdate", responseBytes, reliable: true);
}

Response fields are auto-populated when a same-named field exists on the trigger message, or when the field is named playerId (auto-mapped from senderId). Anything else is left at its C# default with a // TODO comment — fill it in, or set Custom Host Code on the Host Action for full control over the method body.

07

NetCrafterEventDispatcher#

A runtime MonoBehaviour that ships with NetCrafter Relay in Scripts/NetCrafterEventDispatcher.cs. Not generated — it is a permanent part of the package.

Namespace: PivecLabs.NetCrafterRelay Class: NetCrafterEventDispatcher : MonoBehaviour

Summary

Provides a publish–subscribe bus for host events. Any script in your project can subscribe to a command name and receive the already-deserialised typed event object whenever that event arrives, without subclassing or modifying the generated RelayConnectionManager.

Members

Member Description
static NetCrafterEventDispatcher Instance Singleton accessor. Available after Awake. The GameObject persists across scene loads.
void Subscribe(string cmd, Action<object> callback) Registers callback to receive events for cmd. Multiple callbacks for the same command are supported.
void Unsubscribe(string cmd, Action<object> callback) Removes a previously registered callback. Safe to call if the callback was never registered.
void Dispatch(string cmd, object eventObject) Called internally by the generated RelayConnectionManager. Invokes all registered callbacks for cmd. Each callback runs in an individual try/catch so one failure does not block the others.

Usage example

csharp
using PivecLabs.NetCrafterRelay;

public class ChatController : MonoBehaviour
{
    private void OnEnable()
    {
        NetCrafterEventDispatcher.Instance.Subscribe("ChatBroadcast", OnChat);
        NetCrafterEventDispatcher.Instance.Subscribe("PlayerStateUpdate", OnPlayerState);
    }

    private void OnDisable()
    {
        // Always unsubscribe — failing to do so leaves a dangling reference
        NetCrafterEventDispatcher.Instance.Unsubscribe("ChatBroadcast", OnChat);
        NetCrafterEventDispatcher.Instance.Unsubscribe("PlayerStateUpdate", OnPlayerState);
    }

    private void OnChat(object payload)
    {
        var ev = (ChatBroadcastEvent)payload;
        Debug.Log($"{ev.senderName}: {ev.message}");
    }

    private void OnPlayerState(object payload)
    {
        var ev = (PlayerStateUpdateEvent)payload;
        Debug.Log($"Player health: {ev.state.health}, position: {ev.state.position}");
    }
}

Notes

  • Command names are the schema event names verbatim. Schema event ChatBroadcast → subscribe with "ChatBroadcast".
  • Both paths always fire. Dispatch runs before the On{Event} stub in the generated ConnectionManager. Whether or not any subscriber is registered has no effect on the stub.
  • Thread safety. Incoming relay datagrams arrive on a background thread inside StunTurnClient, but are only ever acted on in RelayConnectionManager.Update() (main thread) — Dispatch is not thread-safe and must only be called from the main thread, which is already guaranteed by this design.
  • Scene Assistant. Enable Add Event Dispatcher in the Scene Assistant tab to have the GameObject added automatically when creating or patching a scene.
08

INetCrafterConnection#

An interface that decouples the sync runtime (and your join-flow UI) from the generated namespace. Implemented by the generated RelayConnectionManager.

Namespace: PivecLabs.NetCrafterRelay File: Scripts/INetCrafterConnection.cs

Members

Member Description
bool IsConnected { get; } true once the relay connection is established.
bool IsHost { get; } true only on the hosting peer.
int LocalUserId { get; } This peer's assigned id (0 for host).
string LocalUserName { get; } This peer's guest/display name.
void Send(string cmd, byte[] payload, bool reliable) Sends to the host. No-op if not connected.
void UpdateHost(string joinCode) Sets the join code for the next Connect().
void Connect() Hosts or joins, depending on whether a join code is set.

Why it exists

NetCrafterSyncManager needs to send data through the generated RelayConnectionManager, which lives in a user-defined namespace unknown to the runtime. INetCrafterConnection provides a namespace-agnostic contract. SyncManager finds the implementor at Start by scanning FindObjectsByType<MonoBehaviour>() — the same pattern PrefabGeneratorTab and SceneAssistantTab use in the editor to locate the connection-manager type by interface rather than a hardcoded class name.

09

NetCrafterSyncManager#

Singleton MonoBehaviour that owns the object registry and routes sync traffic.

Namespace: PivecLabs.NetCrafterRelay File: Scripts/NetCrafterSyncManager.cs Class: NetCrafterSyncManager : MonoBehaviour

Members

Member Description
static NetCrafterSyncManager Instance Singleton. Persists across scene loads via DontDestroyOnLoad.
MonoBehaviour connectionManagerOverride Inspector field. Drag the scene's ConnectionManager here. If null, auto-detection scans for any INetCrafterConnection in the scene.
void RegisterBinding(string ruleName, int objectId, NetCrafterSyncBinding binding) Called by NetCrafterSyncBinding.Start. Maps objectId to the binding, scoped per ruleName — a single object can carry more than one NetCrafterSyncBinding at once (e.g. one rule for position, another for an Animator parameter), each registered independently so one doesn't overwrite another's registration under the same objectId.
void UnregisterBinding(string ruleName, int objectId) Called by NetCrafterSyncBinding.OnDestroy.
void SubscribeToSyncRule(string ruleName) Subscribes the manager to incoming sync payloads for ruleName via NetCrafterEventDispatcher. Deduplicates: only one subscription per rule name regardless of how many remote bindings share it.
bool TrySendSync(string cmd, byte[] payload) Sends a sync payload (unreliable) through the INetCrafterConnection. Returns false and does nothing if not ready or not connected.

Setup

Add NetCrafterSyncManager to a persistent GameObject in the scene (or enable Add Event Dispatcher in Scene Assistant and add it alongside NetCrafterEventDispatcher). Both NetCrafterSyncManager and NetCrafterEventDispatcher must be present in the scene for sync to function.

Graceful degradation

If no INetCrafterConnection is found during Start, a Debug.LogError is emitted and _ready is set to false. All subsequent TrySendSync calls return false immediately — there is no repeated NullReferenceException per FixedUpdate tick.

10

NetCrafterSyncBinding#

Per-object MonoBehaviour that drives the sync tick loop, builds outbound payloads, and applies inbound values.

Namespace: PivecLabs.NetCrafterRelay File: Scripts/NetCrafterSyncBinding.cs Class: NetCrafterSyncBinding : MonoBehaviour Menu path: Component > NetCrafter > Sync Binding

Inspector fields

Field Type Description
Rule SyncRuleDefinition The sync rule asset from Definitions/{Name}/SyncRules/. Determines what is synced and how.
Object Id int Unique integer identifying this object in the network session. Assign from game logic (e.g. RelayConnectionManager.Instance.LocalUserId after connecting).
Has Authority bool true = this client owns this object and sends sync data. false = receives and applies incoming data.

Sync Modes

Mode Behaviour
Always Sends every FixedUpdate. Use for values that change every frame.
OnChange Sends only when the value has changed by more than an epsilon since the last send. Epsilons: 0.001 units (Vector3), 0.1 degrees (Quaternion).
Interval Sends every N seconds (SyncRuleDefinition.interval).

Sync Targets

Enum value Component Property Wire order (after id)
Transform_Position Transform position Vector3
Transform_Rotation Transform rotation Quaternion
Transform_Scale Transform localScale Vector3
Rigidbody_Velocity Rigidbody linearVelocity Vector3
Rigidbody_AngularVelocity Rigidbody angularVelocity Vector3
Animator_Float Animator GetFloat/SetFloat(SyncRuleDefinition.animatorParameterName) float
Animator_Bool Animator GetBool/SetBool(SyncRuleDefinition.animatorParameterName) bool

Animator_Float and Animator_Bool read SyncRuleDefinition.animatorParameterName (set in the Sync Settings tab) to know which Animator Controller parameter to read/write — it must exactly match a parameter name defined on the character's Animator Controller. Read/written on the GetComponent<Animator>() found on the same GameObject as the binding.

⚠ Multiple bindings, one object. Since each SyncRuleDefinition syncs exactly one property, an object that needs several synced properties at once (e.g. a character's position, rotation, and four Animator parameters) carries several NetCrafterSyncBinding components side by side, one per rule, all sharing the same objectId/hasAuthority. NetCrafterSyncManager's registry is keyed by (ruleName, objectId), not objectId alone, specifically so this works — registering a second binding under the same objectId but a different rule doesn't overwrite the first. See the Character tutorial for a worked example with 6 bindings per character.

Usage example

csharp
// After connecting, assign objectId from the relay connection
private void OnConnected()
{
    int myId = RelayConnectionManager.Instance.LocalUserId;

    // Local player — authority binding
    var localPlayer = Instantiate(playerPrefab);
    var binding = localPlayer.GetComponent<NetCrafterSyncBinding>();
    binding.objectId     = myId;
    binding.hasAuthority = true;

    // Remote player — already in scene with hasAuthority = false and matching rule
    // objectId must be set before Start runs (i.e. before the object is active)
}

Notes

  • NetCrafterSyncBinding.Start validates that a SyncRuleDefinition and a NetCrafterSyncManager are present. If either is missing, the component self-disables with a Debug.LogWarning.
  • Remote bindings (non-authority) subscribe to their rule via NetCrafterSyncManager.SubscribeToSyncRule. This wires the NetCrafterEventDispatcher callback that routes the payload back to ApplyIncoming.
  • ApplyIncoming is internal — only NetCrafterSyncManager calls it, and only after it has already consumed the leading id field from the shared RelayReader.
11

StunTurnClient#

The pure C# STUN (RFC 5389) / TURN (RFC 5766) relay client underlying RelayConnectionManager. Not generated — a permanent part of the package runtime, usable directly if you need lower-level relay access.

Namespace: PivecLabs.NetCrafterRelay File: Scripts/Relay/StunTurnClient.cs

Members

Member Description
StunTurnClient(IPEndPoint serverEndpoint, string username, string password) Constructs a client bound to a TURN server, ready to Start().
void Start() / void Stop() Opens/closes the underlying UDP socket and background receive loop.
IPEndPoint RelayedAddress This client's allocated relayed transport address, once AllocateAsync succeeds.
bool IsAllocated true once RelayedAddress is populated.
Task<IPEndPoint> AllocateAsync(int lifetimeSeconds = 600) Requests a TURN allocation, handling the long-term-credential 401 challenge automatically.
Task RefreshAsync(int lifetimeSeconds = 600) Extends (or, with 0, releases) the allocation.
Task CreatePermissionAsync(IPEndPoint peerAddress) Authorizes a peer's address to exchange data through this allocation.
void SendToPeer(IPEndPoint peerAddress, byte[] data) Fire-and-forget relay send (TURN Send Indication) — no response, no reliability.
event Action<IPEndPoint, byte[]> OnDataReceived Fires from a background thread whenever a TURN Data Indication arrives — marshal to the main thread before touching Unity APIs (RelayConnectionManager does this via a ConcurrentQueue drained in Update()).

Channel binding (RFC 5766 §11) is not implemented — every relayed packet currently goes through a Send/Data Indication pair, which has more per-packet overhead than a bound channel. This is a known follow-up, not required for the MVP's session sizes (2–4 players typical, up to 8–10).