State

Create, Read, And Write State

Create AzState<T>, read current values, and write accepted source values.

  • Initial values
  • Comparers
  • Value
  • SourceSet

This page covers the basic AzState<T> shape: when to use it, how to create it, how to read it, and how to write with SourceSet.

What AzState Is

AzState<T> is a reactive property.

Use it when you have a value that changes over time and other code should react when that value changes.

using Azkar.Eda;
using UnityEngine;

public sealed class PlayerModel
{
    public readonly AzState<int> Health = new(100);
    public readonly AzState<int> Score = new(0);
    public readonly AzState<bool> IsMuted = new(false);
}

The most important operation is SourceSet.

Health.SourceSet(90);

If the value changed, subscribers are notified. If the value is the same, subscribers are not notified unless you force it.

Health.SourceSet(90);              // notifies if previous value was not 90
Health.SourceSet(90);              // no notification
Health.SourceSet(90, force: true); // notification even though unchanged

When to Use AzState

Use AzState<T> for mutable values:

  • health
  • score
  • selected item
  • current weapon
  • volume
  • settings toggles
  • UI field values
  • model/view-model values
  • cached derived status text

Use AzEvent or AzPriorityEvent for discrete events:

  • jump pressed
  • damage happened
  • projectile spawned
  • item collected
  • save completed
  • round started

The difference is simple:

"This value currently is X."
AzState<T>
"A discrete event occurred."
AzEvent
"A discrete event occurred, and ordered phases matter."
AzPriorityEvent

Core Mental Model

An AzState<T> owns:

  • a current value
  • a version number
  • an equality comparer
  • optional validation
  • a notification schedule
  • a subscriber list

Subscribers receive the current value when the state changes.

private readonly AzEventTokenBag _bindings = new();

private void OnEnable()
{
    Player.Health.Subscribe(OnHealthChanged).AddTo(_bindings);
}

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

private void OnHealthChanged(int health)
{
    Debug.Log($"Health is now {health}");
}

Every subscription returns an AzEventToken. Store that token or add it to an AzEventTokenBag.

Creating State

With an Initial Value

public readonly AzState<int> Health = new(100);
public readonly AzState<float> Volume = new(0.75f);
public readonly AzState<string> Status = new("Ready");

When a state has an initial value:

  • HasValue is true
  • Value returns that value
  • Subscribe(..., notifyImmediately: true) invokes immediately with that value

Without an Initial Value

public readonly AzState<string> SelectedSaveSlot = new();

When a state has no value yet:

  • HasValue is false
  • Subscribe(..., notifyImmediately: true) does not call the subscriber until a value is set
  • the first successful SourceSet establishes the value

With a Custom Comparer

AzState<T> uses EqualityComparer<T?>.Default unless you provide a comparer.

For reference types, default equality is often reference equality unless the type overrides equality.

public sealed class InventorySnapshot
{
    public int ItemCount;
}

public sealed class InventorySnapshotComparer : IEqualityComparer<InventorySnapshot?>
{
    public bool Equals(InventorySnapshot? x, InventorySnapshot? y)
        => x?.ItemCount == y?.ItemCount;

    public int GetHashCode(InventorySnapshot? obj)
        => obj?.ItemCount.GetHashCode() ?? 0;
}

public readonly AzState<InventorySnapshot> Inventory =
    new(new InventorySnapshotComparer());

Use a custom comparer when you want "same content" to mean "no state change."

Reading State

int health = Health.Value;
bool hasHealth = Health.HasValue;
int version = Health.Version;
AzStateSchedule schedule = Health.Schedule;

Version increments when the state accepts a changed or forced value.

ValueSource is a property wrapper around SourceSet:

Health.ValueSource = 80;

Most code should prefer explicit SourceSet(...) because it makes mutation visible.

Writing State with SourceSet

bool changed = Health.SourceSet(80);

SourceSet returns:

  • true on the main thread when the value changed or was forced
  • false on the main thread when the value was unchanged or rejected by strict validation
  • true off-thread when the write request was accepted for later main-thread application

Force Notification

Health.SourceSet(Health.Value, force: true);

Use this when the value is the same but bindings need to reapply, such as after UI reconstruction.

Refresh

Refresh() notifies subscribers with the current value without changing it.

Health.Refresh();

Refresh():

  • does nothing if HasValue is false
  • increments Version
  • skips validation
  • follows the state's schedule

Use it when a view needs to re-read current state because external presentation context changed.