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.

EventDescription
InstantiateGONetParticipantEventObject spawned on network
DespawnGONetParticipantEventObject removed from network
SceneLoadEventServer loaded a new scene
ServerSaysClientInitializationCompletionClient fully initialized
PersistentRpcEventRPC stored for late-joiners
ClientStateChangedEventClient connection state changed (local)
RemoteClientStateChangedEventRemote client state changed (from server)
GONetParticipantStartedEventParticipant started, GONetId first available (local only)
SyncEvent_ValueChangeProcessedBase class for all sync value change events
ReparentGONetParticipantEventObject 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.

InterfaceEffect
ILocalOnlyPublishEvent only fires locally, never sent over the network
ICancelOutOtherEventsEvent cancels previously stored events (e.g., despawn cancels spawn)
IsSingularRecipientOnlyEvent is only sent to one specific recipient
ISelfReturnEventEvent 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(), not Awake() or Start(). 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.

Next Steps

Event System | GONet Docs