Everything lives in `Udonite.Observables`.

## Observable&lt;T&gt;

```csharp
new Observable<int>()        // default value, nobody notified
new Observable<int>(7)       // seeded, nobody notified
```

| Member | Notifies |
|---|---|
| `T Value { get; set; }` | on set, only when the new value differs |
| `Subscribe(Action<T> handler)` | no |
| `Subscribe(Action<T> handler, bool notifyNow)` | `notifyNow` calls **this handler only** |
| `Unsubscribe(Action<T> handler)` | no; removing one that was never subscribed does nothing |
| `UnsubscribeAll()` | no |
| `SetSilently(T value)` | no |
| `Publish()` | yes, every subscriber, changed or not |
| `bool CascadeStalled { get; }` | no |
| `ToString()` | no; the value, or `""` when there is none |

**Equality is `object.Equals`.** Two equal strings are not a change; `null` is a value like any other, so `null` to `null` is silent and `null` to a value is not.

**Subscribing twice subscribes once.** The usual way to end up there is a `Start` that runs again, and being called twice per change is a bug every time.

**A subscriber may live anywhere.** `score.Subscribe(display.Show)` reaches a handler on another behaviour, `score.Subscribe(OnChanged)` one on this behaviour, and a class that is not a behaviour subscribes one of its own methods from inside itself. One observable can hold a mixture of all three.

The exception is handing over *another* object's method when that object is not a behaviour — `score.Subscribe(tally.Add)` where `tally` is an ordinary class. Udon has nowhere to put the receiver, and the compiler refuses it. Give the class a method that subscribes itself:

```csharp
public class Tally
{
    public void Watch(Observable<int> score) { score.Subscribe(Add); }

    private void Add(int value) { ... }
}
```

**There is no subscriber count, and `Unsubscribe` reports nothing.** Removal matches by content, so a handler can be removed with a delegate built fresh at the call site rather than the exact value that was added, but Udon registers no way to inspect a list of subscribers. Code that needs to know what it subscribed should keep track of that itself.

`CascadeStalled` reports a publish that restarted sixteen times without settling. Reaching it takes a subscriber that writes back to the same observable, which the compiler refuses, so in a world it stays `false`.

## ObservableList&lt;T&gt;

Subscribers take `Action<ObservableList<T>>` and are handed the list itself.

| Member | Notifies | `LastChange` |
|---|---|---|
| `T this[int] { get; set; }` | on set, only when different | `Replaced` |
| `Add(T)` | yes | `Added` |
| `AddRange(T[])` | once for the whole range; nothing for an empty or null one | `None` |
| `Insert(int, T)` | yes | `Added` |
| `bool Remove(T)` | only if it was there | `Removed` |
| `RemoveAt(int)` | yes | `Removed` |
| `Clear()` | only if it held anything | `Cleared` |
| `AddSilently(T)` | no | |
| `Contains`, `IndexOf`, `ToArray`, `Count` | no | |

Inside a handler, `LastChange`, `LastIndex` and `LastItem` describe what happened, so a subscriber can spawn one object rather than rebuild everything:

```csharp
private void OnNamesChanged(ObservableList<string> list)
{
    if (list.LastChange == CollectionChange.Added)
        Spawn(list.LastItem);
    else
        RedrawEverything(list);
}
```

They describe the notification in progress and are only meaningful inside a handler.

## ObservableDictionary&lt;TKey, TValue&gt;

Subscribers take `Action<ObservableDictionary<TKey, TValue>>`.

| Member | Notifies | `LastChange` |
|---|---|---|
| `TValue this[TKey] { get; }` | no | |
| `Set(TKey, TValue)` | only when the value differs | `Added` for a new key, `Replaced` for a known one |
| `bool Remove(TKey)` | only if it was there | `Removed` |
| `Clear()` | only if it held anything | `Cleared` |
| `SetSilently(TKey, TValue)` | no | |
| `ContainsKey`, `TryGetValue`, `Count` | no | |

`LastKey` and `LastValue` name what changed.

**There is no indexer setter, on purpose.** A dictionary in Udon is a `DataDictionary`, and reading a key that is not there faults rather than returning a default, so a read-before-write hidden inside a setter would be a trap. `Set` keeps the read explicit: ask `ContainsKey` first, or use `TryGetValue`.

## CollectionChange

`None`, `Added`, `Removed`, `Replaced`, `Cleared`.

`None` is both "nothing has happened yet" and "a bulk change with no single item": `AddRange`, or a `Subscribe(handler, true)` that is showing the collection rather than reporting a change.

## If a handler throws

An exception in Udon halts the behaviour for the rest of the session, and there is no catching one subscriber's and carrying on to the next. A handler that throws takes down the rest of the notification and everything that behaviour would have done afterwards, so keep handlers to reading the value and updating something.
