State

Subscriptions And Binding

Subscribe to state, map values, observe changes, and bind state to view setters.

  • Subscribe
  • SubscribeMap
  • SubscribeChanged
  • BindTo

Use subscriptions and bindings to connect state values to readers without giving those readers write access.

Subscribing

Basic Subscribe

AzEventToken token = Health.Subscribe(OnHealthChanged);

private void OnHealthChanged(int health)
{
}

By default, Subscribe immediately invokes the handler with the current value if the state has one.

Health.Subscribe(OnHealthChanged, notifyImmediately: true);

Disable immediate notification when you only care about future changes:

Health.Subscribe(OnHealthChanged, notifyImmediately: false);

Token Bag Pattern

using Azkar.Eda;
using UnityEngine;

public sealed class HealthHud : MonoBehaviour
{
    [SerializeField] private PlayerViewModel _model;

    private readonly AzEventTokenBag _bindings = new();

    private void OnEnable()
    {
        _model.Health.Subscribe(SetHealthText).AddTo(_bindings);
    }

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

    private void OnDestroy()
    {
        _model?.Dispose();
    }

    private void SetHealthText(int health)
    {
    }
}

Subscriber Priority

Subscribers with higher priority run earlier.

Health.Subscribe(ValidateUiState, priority: 100);
Health.Subscribe(UpdateHud, priority: 0);
Health.Subscribe(RecordAnalytics, priority: -10);

For equal priorities, subscription order is preserved.

Use priority sparingly. Most state bindings should use the default priority 0.

Weak Subscribers

Pass weak: true to allow Unity-object targets to be removed automatically when destroyed.

Health.Subscribe(OnHealthChanged, weak: true).AddTo(_bindings);

This is most useful for instance methods on MonoBehaviour or ScriptableObject targets.

Avoid weak subscriptions with throwaway lambda captures:

// Avoid this pattern with weak: true.
Health.Subscribe(value => UpdateHealth(value), weak: true);

Prefer a stable instance method:

Health.Subscribe(OnHealthChanged, weak: true);

Even with weak subscriptions, explicit token cleanup is still a good habit.

SubscribeMap

SubscribeMap transforms the state value before passing it to a sink.

Health.SubscribeMap(
    hp => hp / 100f,
    fill => healthBar.fillAmount = fill).AddTo(_bindings);

Common uses:

Score.SubscribeMap(
    value => $"Score: {value:N0}",
    text => scoreLabel.text = text).AddTo(_bindings);

Muted.SubscribeMap(
    muted => muted ? "Muted" : "Sound On",
    text => muteLabel.text = text).AddTo(_bindings);

Health.SubscribeMap(
    hp => hp < 25,
    isCritical => criticalWarning.SetActive(isCritical)).AddTo(_bindings);

SubscribeMap supports notifyImmediately, priority, and weak.

Health.SubscribeMap(
    hp => Mathf.Clamp01(hp / 100f),
    SetFill,
    notifyImmediately: true,
    priority: 0,
    weak: true).AddTo(_bindings);

SubscribeChanged

SubscribeChanged receives old and new values.

Health.SubscribeChanged(OnHealthChanged).AddTo(_bindings);

private void OnHealthChanged(int oldHealth, int newHealth)
{
    if (newHealth < oldHealth)
    {
        int damage = oldHealth - newHealth;
        Debug.Log($"Took {damage} damage");
    }
}

Important details:

  • SubscribeChanged does not invoke immediately.
  • The first callback happens after the first observed change after subscription.
  • The old value for that first callback is the state value captured by the wrapper during its first notification.
  • If the token is paused, skipped notifications do not update the captured previous value.

Use SubscribeChanged for:

  • old/new logging
  • threshold crossing
  • damage deltas
  • undo history
  • analytics deltas

Use normal Subscribe or BindTo for UI display.

BindTo

BindTo is one-way binding from state to a setter.

Health.BindTo(SetHealthText).AddTo(_bindings);

It is equivalent to subscribing, but the name communicates intent.

Health.BindTo(hp => healthText.text = $"HP: {hp}").AddTo(_bindings);

Object Binding Overload

Use the object overload to avoid closure-heavy bindings and make the target explicit.

Health.BindTo(this, static (self, hp) => self.SetHealthText(hp)).AddTo(_bindings);

For UI components:

Health.BindTo(healthBar, static (bar, hp) =>
{
    bar.fillAmount = Mathf.Clamp01(hp / 100f);
}).AddTo(_bindings);

This overload requires a reference-type target.