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.
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.
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:
// Usage
var req = new PlayerMoveRequest
{
playerId = RelayConnectionManager.Instance.LocalUserId,
position = transform.position,
speed = currentSpeed
};
req.Send(RelayConnectionManager.Instance);
The generated class:
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;
}
}
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:
// Usage — called automatically by RelayConnectionManager
private void OnPlayerStateUpdate(PlayerStateUpdateEvent ev)
{
Debug.Log($"Health: {ev.state.health}, Position: {ev.state.position}");
}
The generated class:
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;
}
}
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:
// 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.
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:
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:
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.
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:
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.
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
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.
Dispatchruns before theOn{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 inRelayConnectionManager.Update()(main thread) —Dispatchis 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.
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.
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.
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
SyncRuleDefinitionsyncs exactly one property, an object that needs several synced properties at once (e.g. a character's position, rotation, and four Animator parameters) carries severalNetCrafterSyncBindingcomponents side by side, one per rule, all sharing the sameobjectId/hasAuthority.NetCrafterSyncManager's registry is keyed by(ruleName, objectId), notobjectIdalone, specifically so this works — registering a second binding under the sameobjectIdbut a different rule doesn't overwrite the first. See the Character tutorial for a worked example with 6 bindings per character.
Usage example
// 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.Startvalidates that aSyncRuleDefinitionand aNetCrafterSyncManagerare present. If either is missing, the component self-disables with aDebug.LogWarning.- Remote bindings (non-authority) subscribe to their rule via
NetCrafterSyncManager.SubscribeToSyncRule. This wires theNetCrafterEventDispatchercallback that routes the payload back toApplyIncoming. ApplyIncomingisinternal— onlyNetCrafterSyncManagercalls it, and only after it has already consumed the leadingidfield from the sharedRelayReader.
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).