Events & tokens

Subscribing And Invoking

Subscribe handlers, invoke events, and understand order, removal, safe dispatch, and snapshot behavior.

  • Subscribe
  • Invoke
  • Order
  • Snapshot behavior

Subscribe handlers, keep the returned token, and invoke events from the owner that knows when the event occurs.

Subscribing

Regular Subscription

AzEventToken token = CombatEvents.CombatStarted.Subscribe(OnCombatStarted);

For payload events:

AzEventToken token = CombatEvents.HealthChanged.Subscribe(OnHealthChanged);

private void OnHealthChanged(int health)
{
}

For ref payload events:

AzEventToken token = CombatEvents.DamageReported.Subscribe(OnDamageReported);

private void OnDamageReported(in DamageReport report)
{
}

Unique Subscription

SubscribeUnique adds the handler only if the same delegate is not already subscribed. If the handler is already present, it returns AzEventToken.Invalid.

AzEventToken token = CombatEvents.HealthChanged.SubscribeUnique(OnHealthChanged);

if (!token.IsValid)
{
    Debug.Log("Already subscribed.");
}

Use SubscribeUnique when repeated setup calls are possible and duplicate handlers would be a bug.

Important: uniqueness uses normal delegate equality. The safest pattern is to pass a stable method group:

CombatEvents.HealthChanged.SubscribeUnique(OnHealthChanged);

Avoid expecting two newly created lambdas to be treated as the same subscription:

// These are different delegate instances.
CombatEvents.HealthChanged.SubscribeUnique(value => Debug.Log(value));
CombatEvents.HealthChanged.SubscribeUnique(value => Debug.Log(value));

Removing by Handler

You can remove by handler:

bool removed = CombatEvents.HealthChanged.Remove(OnHealthChanged);

When duplicate copies of the same handler exist, Remove(handler) removes one matching subscription. For precise lifecycle control, prefer storing and disposing the token returned by Subscribe.

Invoking

Normal Invoke

Invoke calls active subscribers in subscription order.

CombatEvents.CombatStarted.Invoke();
CombatEvents.HealthChanged.Invoke(currentHealth);

var report = new DamageReport(25, transform.position);
CombatEvents.DamageReported.Invoke(in report);

Invoke is the hot-path call. It does not isolate exceptions. If a handler throws, that exception behaves like a normal thrown exception and can stop the remaining invocation flow.

Safe Invoke

InvokeSafe catches handler exceptions, logs them in the editor, continues invoking remaining subscribers, and returns an AzInvokeResult.

AzInvokeResult result = CombatEvents.HealthChanged.InvokeSafe(currentHealth);

if (result.Exceptions > 0)
{
    Debug.LogWarning($"HealthChanged had {result.Exceptions} handler exception(s).");
}

Use InvokeSafe when:

  • you are debugging a new event chain
  • you want one bad subscriber not to prevent later subscribers from running
  • you are invoking lower-trust or plugin-style handlers

Use normal Invoke in tight, trusted runtime paths where exceptions should fail loudly.

Subscription Order

AzEvent preserves subscription order. If A subscribes first, B subscribes second, and C subscribes third, they are invoked as A, B, C.

evt.Subscribe(A);
evt.Subscribe(B);
evt.Subscribe(C);

evt.Invoke(); // A, then B, then C

Pausing and resuming a token does not move it to the end of the list. A resumed subscription keeps its original relative position.

Re-entrancy and Snapshot Behavior

AzEvent uses a snapshot-style dispatch model.

This means the active subscriber list for a dispatch is stable while an Invoke is in progress.

If a handler subscribes another handler during an active invoke, the new handler does not run in that same dispatch. It can run on a later top-level invoke.

private void FirstHandler()
{
    evt.Subscribe(SecondHandler);
}

evt.Subscribe(FirstHandler);
evt.Invoke(); // FirstHandler runs. SecondHandler does not run yet.
evt.Invoke(); // FirstHandler runs, then SecondHandler can run.

The same idea applies to Resume(): if you resume a paused token from inside an active AzEvent.Invoke, that handler becomes eligible on a later top-level invoke, not the current snapshot.

This behavior prevents confusing "the subscriber list changed while I was iterating it" bugs.