SetNet.StateSync.Rpc
1.1.0
dotnet add package SetNet.StateSync.Rpc --version 1.1.0
NuGet\Install-Package SetNet.StateSync.Rpc -Version 1.1.0
<PackageReference Include="SetNet.StateSync.Rpc" Version="1.1.0" />
<PackageVersion Include="SetNet.StateSync.Rpc" Version="1.1.0" />
<PackageReference Include="SetNet.StateSync.Rpc" />
paket add SetNet.StateSync.Rpc --version 1.1.0
#r "nuget: SetNet.StateSync.Rpc, 1.1.0"
#:package SetNet.StateSync.Rpc@1.1.0
#addin nuget:?package=SetNet.StateSync.Rpc&version=1.1.0
#tool nuget:?package=SetNet.StateSync.Rpc&version=1.1.0
<p align="center"> <img src="https://raw.githubusercontent.com/Povstalez/SetNet/master/assets/icon.png" alt="SetNet" width="96"> </p>
SetNet.StateSync.Rpc
Entity-scoped RPCs for SetNet.StateSync.
Regular SetNet.StateSync replicates continuous state (positions, health) every tick. But some things are one-off events tied to a specific entity — "this door opened", "that unit fired", "play the hit VFX on player 7". Streaming those as replicated fields is wasteful and lossy. This package adds a small targeted RPC channel keyed by NetId:
- server → client: call a method on one client's copy of an entity (its owner, or any observer).
- client → server: the owner of an entity invokes a method on the server (which validates and applies it).
It's a thin, reliable message channel — you map a methodId to your own C# method on the matching object. For fully-typed request/response (with a return value) use SetNet.Rpc instead; this one is fire-and-forget and entity-addressed.
Install
dotnet add package SetNet
dotnet add package SetNet.StateSync
dotnet add package SetNet.StateSync.Rpc
Setup
StateSyncRuntime.Enable();
StateSyncRpcRuntime.Enable(); // once at startup, both ends, before creating client/server
Server
Register a typed handler per method id with On<T> — the payload is deserialized for you, so you work with your DTO, not bytes:
var rpc = server.UseStateSyncRpc();
// one typed handler per method — validate ownership/authority inside it:
rpc.On<FireCommand>((ushort)UnitRpc.Fire, (peer, netId, shot) =>
{
if (Owns(peer, netId)) ApplyFire(netId, shot);
});
rpc.On<UseItem>((ushort)UnitRpc.UseItem, (peer, netId, cmd) => { /* … */ });
// push a call to a specific client (typed — serialized for you):
await rpc.SendAsync(ownerPeer, netId, (ushort)UnitRpc.Fire, new FireCommand { Dir = dir });
// …or to several observers (you decide who):
foreach (var observer in observersOf(netId))
await rpc.SendAsync(observer, netId, (ushort)UnitRpc.PlayHitFx, hitInfo);
Client
var rpc = client.UseStateSyncRpc();
rpc.On<HitInfo>((ushort)UnitRpc.PlayHitFx, (netId, hit) => FindObject(netId)?.PlayHit(hit));
rpc.On<DoorState>((ushort)UnitRpc.DoorChanged, (netId, s) => FindObject(netId)?.SetDoor(s));
// invoke on the entity you own (server validates):
await rpc.SendAsync(myNetId, (ushort)UnitRpc.Fire, new FireCommand { Dir = aimDir });
Typed handlers vs the raw catch-all
The channel routes each incoming call like this:
- If a
On<T>(methodId, …)handler is registered for the method → the payload is deserialized toTand that handler runs. This is the recommended path — one typed handler per method id, nobyte[]. - Otherwise the raw
Receivedevent fires with(…, methodId, byte[] payload)— a catch-all for methods you didn't register (relays, logging, dynamic dispatch). Decode it yourself withSetNetSerializer.Deserialize<T>(payload).
Why the split? A single C# event can't be generic over your type when each methodId carries a different argument type — so the typed layer is a registration (On<T>), and the untyped event is the fallback. Off(methodId) removes a typed handler.
API
| Member | Side | Purpose |
|---|---|---|
server.UseStateSyncRpc() → ServerStateRpc |
server | enable + get the channel |
ServerStateRpc.On<T>(methodId, Action<BasePeer,uint,T>) |
server | typed handler for a method (deserializes the arg) |
ServerStateRpc.SendAsync<T>(peer, netId, methodId, arg, delivery?) |
server | call a method on one client's entity |
ServerStateRpc.Received / Off(methodId) |
server | raw catch-all for unregistered methods / unregister |
client.UseStateSyncRpc() → ClientStateRpc |
client | enable + get the channel |
ClientStateRpc.On<T>(methodId, Action<uint,T>) |
client | typed handler for a method |
ClientStateRpc.SendAsync<T>(netId, methodId, arg, delivery?) |
client | call a method on the server (owned entity) |
ClientStateRpc.Received / Off(methodId) |
client | raw catch-all / unregister |
Reliable by default; pass DeliveryMethod.Unreliable for frequent, loss-tolerant effects. Reserved wire type 65516.
Notes
- One handler per method id.
On<T>overwrites any previous handler for the same id; use distinct method ids per call type. The Unity layer can bind these to methods on aNetworkObject. - Authority: the server must validate that a client-invoked RPC is legal for the peer that sent it (e.g. the peer owns
netId). The server is authoritative — never trust the client. - Routing to the right client uses a process-wide registry (one client per process is the common case; co-located clients share routing).
Documentation & source
- 🐙 https://github.com/Povstalez/SetNet — full module catalog in docs/MODULES.md
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- SetNet.StateSync (>= 1.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.0 | 130 | 7/2/2026 |