Priority events

Slots And Subscriptions

Declare priority events, choose slot conventions, subscribe handlers, and manage tokens.

  • Slots 1..9
  • Subscribe
  • Token bags
  • Lifecycle

Priority slots are byte values from 1 through 9. Lower slots run earlier.

Declaring Priority Events

Static bus style:

using Azkar.Eda;

public static class CombatBus
{
    public static readonly AzPriorityEvent Tick = new();
    public static readonly AzPriorityEvent<int> HealthChanged = new();
    public static readonly AzPriorityEventRef<HitReport> HitReported = new();
}

Instance-owned style:

using Azkar.Eda;
using UnityEngine;

public sealed class Door : MonoBehaviour
{
    public readonly AzPriorityEvent Opened = new();

    public void Open()
    {
        Opened.Invoke();
    }
}

Use static events for broad application or gameplay messages. Use instance events when a specific object owns the event.

Priority Slots

Slots are byte values in the inclusive range 1..9.

evt.Subscribe(EarlyHandler, slot: 1);
evt.Subscribe(DefaultHandler, slot: 5);
evt.Subscribe(LateHandler, slot: 9);

Invalid slots throw ArgumentOutOfRangeException.

evt.Subscribe(Handler, slot: 0);  // throws
evt.Subscribe(Handler, slot: 10); // throws

Recommended Slot Convention

Pick a convention for your project and document it. For example:

1

validation, guards, global veto checks

2

authoritative state preparation

3

core systems that must run early

4

pre-gameplay observers

5

normal gameplay, default slot

6

post-gameplay observers

7

presentation state

8

UI updates

9

analytics, logging, late observers

The slot number is the contract. If Handler A must always run before Handler B, put them in different slots.

Same-Slot Ordering

Do not rely on same-slot ordering for correctness.

evt.Subscribe(HandlerA, slot: 5);
evt.Subscribe(HandlerB, slot: 5);

Both are slot 5; if order matters, do this instead:

evt.Subscribe(HandlerA, slot: 4);
evt.Subscribe(HandlerB, slot: 5);

Subscribing

Preferred Pattern: Subscribe and Store the Token

AzEventToken token = CombatBus.HealthChanged.Subscribe(OnHealthChanged, slot: 5);

In Unity components, add tokens to a bag:

private readonly AzEventTokenBag _subscriptions = new();

private void OnEnable()
{
    CombatBus.HealthChanged.Subscribe(OnHealthChanged, slot: 5).AddTo(_subscriptions);
}

private void OnDisable()
{
    _subscriptions.DisposeAll();
}

No-Payload Subscription

CombatBus.Tick.Subscribe(OnTick, slot: 1).AddTo(_subscriptions);

private void OnTick()
{
}

Value Payload Subscription

CombatBus.HealthChanged.Subscribe(OnHealthChanged, slot: 5).AddTo(_subscriptions);

private void OnHealthChanged(int health)
{
}

Ref Payload Subscription

CombatBus.HitReported.Subscribe(OnHitReported, slot: 3).AddTo(_subscriptions);

private void OnHitReported(in HitReport report)
{
}

Default Slot

If you omit the slot, the default is slot 5.

CombatBus.Tick.Subscribe(OnTick);              // slot 5
CombatBus.HealthChanged.Subscribe(OnHealth);   // slot 5
CombatBus.HitReported.Subscribe(OnHitReported); // slot 5

SubscribeUnique and AddUnique

AzPriorityEvent exposes AddUnique, which returns bool and does not return a token.

bool added = CombatBus.Tick.AddUnique(OnTick, slot: 5);

Use this when you truly do not need token-based lifecycle management.

The lower-level slot core supports token-returning unique subscriptions, but the public priority event wrappers expose the ergonomic AddUnique surface. For new users, prefer normal Subscribe(...).AddTo(bag) and structure your component lifecycle so duplicates do not occur.

Add vs Subscribe

Add subscribes a handler but does not return a token:

CombatBus.Tick.Add(OnTick, slot: 5);

This is harder to clean up from a MonoBehaviour. Prefer Subscribe for user code:

CombatBus.Tick.Subscribe(OnTick, slot: 5).AddTo(_subscriptions);

Use Add only when the subscription is intentionally permanent or managed by a higher-level owner.

SubscribeUnmanaged

SubscribeUnmanaged returns IDisposable instead of AzEventToken.

private IDisposable _subscription;

private void OnEnable()
{
    _subscription = CombatBus.HealthChanged.SubscribeUnmanaged(OnHealthChanged, slot: 5);
}

private void OnDisable()
{
    _subscription?.Dispose();
    _subscription = null;
}

Use it when you only need Dispose() and do not need pause/resume, token diagnostics, or token bags.

For most Unity components, AzEventToken plus AzEventTokenBag is more flexible.

Tokens and Token Bags

AzPriorityEvent subscriptions return AzEventToken.

AzEventToken token = CombatBus.Tick.Subscribe(OnTick, slot: 5);

For priority subscriptions, DiagnosticId typically starts with AZP.

Debug.Log(token.DiagnosticId); // example: AZP-12-3

The same operations apply:

token.Pause();
token.Resume();
token.Dispose();

And the same bag works:

private readonly AzEventTokenBag _subscriptions = new();

private void OnEnable()
{
    CombatBus.Tick.Subscribe(OnEarlyTick, slot: 1).AddTo(_subscriptions);
    CombatBus.Tick.Subscribe(OnNormalTick, slot: 5).AddTo(_subscriptions);
    CombatBus.Tick.Subscribe(OnLateTick, slot: 9).AddTo(_subscriptions);
}

private void OnDisable()
{
    _subscriptions.DisposeAll();
}

Pause vs Dispose

Pause keeps the subscription but prevents it from running.

token.Pause();
token.Resume();

Dispose removes the subscription.

token.Dispose();

Use pause/resume for temporary inactivity, especially pooled objects. Use dispose for final cleanup or ordinary OnEnable/OnDisable subscription lifecycles.

Pooled Object Pattern

using Azkar.Eda;
using UnityEngine;

public sealed class PooledEnemyHud : MonoBehaviour
{
    private readonly AzEventTokenBag _subscriptions = new();

    private void Awake()
    {
        CombatBus.HealthChanged.Subscribe(OnHealthChanged, slot: 8).AddTo(_subscriptions);
    }

    private void OnEnable()
    {
        _subscriptions.ResumeAll();
    }

    private void OnDisable()
    {
        _subscriptions.PauseAll();
    }

    private void OnDestroy()
    {
        _subscriptions.Dispose();
    }

    private void OnHealthChanged(int health)
    {
    }
}

Ordinary Component Pattern

using Azkar.Eda;
using UnityEngine;

public sealed class HealthHud : MonoBehaviour
{
    private readonly AzEventTokenBag _subscriptions = new();

    private void OnEnable()
    {
        CombatBus.HealthChanged.Subscribe(OnHealthChanged, slot: 8).AddTo(_subscriptions);
    }

    private void OnDisable()
    {
        _subscriptions.DisposeAll();
    }

    private void OnHealthChanged(int health)
    {
    }
}