Reference
Every member of Observable, ObservableList and ObservableDictionary, and what each one notifies.
Everything lives in Udonite.Observables.
Observable<T>
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:
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<T>
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:
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<TKey, TValue>
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.