Event System
GONet uses an event-driven architecture for network communication. The EventBus is the central publish/subscribe system that decouples producers from consumers across the network. Events fall into two categories: Transient (fire-and-forget) and Persistent (stored for late-joiners).
Subscribing to Events
Subscribe to events using GONetMain.EventBus.Subscribe<T>(). Always subscribe inside OnGONetReady() to ensure GONet is fully initialized before your handler runs.
public override void OnGONetReady()
{
GONetMain.EventBus.Subscribe<InstantiateGONetParticipantEvent>(OnObjectSpawned);
GONetMain.EventBus.Subscribe<DespawnGONetParticipantEvent>(OnObjectDespawned);
}
void OnObjectSpawned(InstantiateGONetParticipantEvent evt)
{
Debug.Log($"New networked object spawned with GONetId: {evt.GONetId}");
}
void OnObjectDespawned(DespawnGONetParticipantEvent evt)
{
Debug.Log($"Object {evt.GONetId} was despawned");
}Built-in Event Types
GONet ships with several built-in events that cover the most common networking scenarios. You can subscribe to any of these out of the box.
| Event | Description |
|---|---|
InstantiateGONetParticipantEvent | Object spawned on network |
DespawnGONetParticipantEvent | Object removed from network |
SceneLoadEvent | Server loaded a new scene |
ServerSaysClientInitializationCompletion | Client fully initialized |
PersistentRpcEvent | RPC stored for late-joiners |
ClientStateChangedEvent | Client connection state changed (local) |
RemoteClientStateChangedEvent | Remote client state changed (from server) |
GONetParticipantStartedEvent | Participant started, GONetId first available (local only) |
SyncEvent_ValueChangeProcessed | Base class for all sync value change events |
ReparentGONetParticipantEvent | Object reparented in hierarchy |
Creating Custom Events
To define your own event, implement the IGONetEvent interface and decorate the class with [MemoryPackable] for serialization. Choose between ITransientEvent and IPersistentEvent depending on whether the event should be replayed for late-joiners.
[MemoryPackable]
public partial class PlayerScoredEvent : ITransientEvent
{
public long OccurredAtElapsedTicks { get; set; }
public ushort PlayerId { get; set; }
public int Points { get; set; }
}
// Publishing
var evt = new PlayerScoredEvent
{
PlayerId = GONetMain.MyAuthorityId,
Points = 100
};
GONetMain.EventBus.Publish(evt);Transient vs Persistent Events
Transient (ITransientEvent)
Processed immediately and not stored. Good for visual effects, UI notifications, and temporary state changes. Can use object pooling to reduce allocations.
Persistent (IPersistentEvent)
Stored for the entire session. Late-joining clients receive all persistent events on connect. Good for match state, announcements, and game-changing actions.
WARNING: Never use object pooling with persistent events. They are stored by reference, so reusing the object corrupts data for late-joiners.
Event Interfaces
GONet provides additional interfaces you can apply to your events to control their routing and lifecycle behavior.
| Interface | Effect |
|---|---|
ILocalOnlyPublish | Event only fires locally, never sent over the network |
ICancelOutOtherEvents | Event cancels previously stored events (e.g., despawn cancels spawn) |
IsSingularRecipientOnly | Event is only sent to one specific recipient |
ISelfReturnEvent | Event is automatically returned to its object pool after publishing |
Object Pooling for Transient Events
Transient events that fire frequently (e.g., sync value changes) can use object pooling to eliminate allocation pressure. Implement ISelfReturnEvent on your transient event class, and GONet will automatically return the event to its pool after all handlers have processed it.
[MemoryPackable]
public partial class FrequentGameEvent : ITransientEvent, ISelfReturnEvent
{
public long OccurredAtElapsedTicks { get; set; }
public float Value { get; set; }
// Return this event to the pool
public void Return()
{
// Reset fields and return to your pool
Value = 0;
MyEventPool.Return(this);
}
}WARNING: Never implement ISelfReturnEvent on persistent events. Persistent events are stored by reference for the entire session. Returning them to a pool corrupts the data that late-joiners receive.
Performance: TypeHierarchyCache
GONet uses a TypeHierarchyCache to pre-compute and cache type hierarchies for all event types. This eliminates runtime reflection overhead, making subscription operations 100-500x faster.
- --Lazy initialization on first Subscribe/Publish call (~10-20ms one-time cost).
- --Dirty-type tracking batches cache rebuilds: 10 Subscribe calls trigger only 1 rebuild.
- --Subscribers registered for base types automatically receive derived type events.
Best Practices
- --Subscribe in
OnGONetReady(), notAwake()orStart(). GONet must be fully initialized before subscriptions are safe. - --Use transient events for anything that does not need late-joiner replay. This keeps the persistent event store lean.
- --Never pool persistent events. They are held by reference for the entire session.
- --Custom
[MemoryPackable]types must be declared at namespace level, not nested inside another class.