State

Threading And Lifecycle

Keep Unity state updates on the right thread and tie subscriptions to the owning lifecycle.

  • Threading
  • Token bags
  • Read-only state
  • Cleanup

Keep Unity object work on the main thread and keep subscription ownership tied to the lifecycle that owns the listener.

Threading

SourceSet is thread-safe.

Task.Run(() =>
{
    Health.SourceSet(50);
});

Off-thread writes are queued and applied on the main thread. The write request returns true when accepted, but Value, validation, version, and subscribers do not update until the main thread pump applies the pending state.

Off-thread writes are last-writer-wins while pending:

Task.Run(() =>
{
    Health.SourceSet(90);
    Health.SourceSet(80);
    Health.SourceSet(70);
});

// Main thread later applies 70.

Do not subscribe off-thread.

// Do not do this.
Task.Run(() => Health.Subscribe(OnHealthChanged));

Create subscriptions on the Unity main thread, usually in Awake, OnEnable, or initialization code.

Notifications always run on the main thread.

Tokens and Token Bags

All AzState subscription methods return AzEventToken, except BindTwoWay, which returns IAzPausableToken because it controls two internal tokens.

AzEventToken token = Health.Subscribe(OnHealthChanged);
IAzPausableToken twoWay = ModelVolume.BindTwoWay(UiVolume);

Token operations:

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

Bag pattern:

private readonly AzEventTokenBag _bindings = new();

private void OnEnable()
{
    Health.Subscribe(OnHealthChanged).AddTo(_bindings);
    Score.BindTo(SetScoreText).AddTo(_bindings);
}

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

BindTwoWay does not return AzEventToken, so it cannot be added to AzEventTokenBag. Store it separately:

private IAzPausableToken _twoWayBinding;

private void OnEnable()
{
    _twoWayBinding = ModelVolume.BindTwoWay(UiVolume, snapToThisOnBind: true);
}

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

Read-Only State

Expose IReadOnlyAzState<T> when other code should observe but not write.

public sealed class GameState
{
    private readonly AzState<int> _score = new(0);

    public IReadOnlyAzState<int> Score => _score.AsReadOnly();

    public void AddScore(int amount)
    {
        _score.SourceSet(_score.Value + amount);
    }
}

Read-only consumers can:

  • read Value
  • read Version
  • read HasValue
  • read Schedule
  • subscribe
  • subscribe mapped values
  • subscribe changed values
  • bind to setters

They cannot call SourceSet.

Reset and Cleanup

UnsubscribeAll

UnsubscribeAll removes every subscriber from one state but keeps the current value and version.

Health.UnsubscribeAll();

Use it when the state owner is resetting subscriptions globally.

Most component subscribers should dispose their own tokens instead.

ResetToDefault

ResetToDefault removes subscribers, clears the value, resets the version, and clears pending writes.

Health.ResetToDefault();

After this:

  • HasValue is false
  • Version is 0
  • subscribers are gone
  • pending writes are cleared

Use it for complete reuse or pool reset.