参考
Observable、ObservableList 和 ObservableDictionary 的每个成员,以及各自会通知什么。
所有内容都在 Udonite.Observables 中。
Observable<T>
new Observable<int>() // 默认值,不通知任何人
new Observable<int>(7) // 带初值,不通知任何人
| 成员 | 是否通知 |
|---|---|
T Value { get; set; } |
设置时,且仅当新值不同 |
Subscribe(Action<T> handler) |
否 |
Subscribe(Action<T> handler, bool notifyNow) |
notifyNow 只调用这一个处理器 |
Unsubscribe(Action<T> handler) |
否;移除一个从未订阅的处理器不会发生任何事 |
UnsubscribeAll() |
否 |
SetSilently(T value) |
否 |
Publish() |
是,通知每个订阅者,无论有没有变化 |
bool CascadeStalled { get; } |
否 |
ToString() |
否;返回值本身,没有值时返回 "" |
相等性用的是 object.Equals。 两个相等的字符串不算变化;null 和别的值一样,所以从 null 到 null 是安静的,从 null 到某个值则不是。
订阅两次只算一次。 通常是 Start 又跑了一遍,而每次变化被调用两次一定是个 bug。
订阅者可以在任何地方。 score.Subscribe(display.Show) 能到达另一个 behaviour 上的处理器,score.Subscribe(OnChanged) 到达本 behaviour 上的处理器,而不是 behaviour 的普通类则在自己内部订阅自己的方法。一个 Observable 可以同时持有这三种。
例外是把别的对象的方法交出去,而那个对象不是 behaviour:tally 是普通类时的 score.Subscribe(tally.Add) 会被拒绝,因为 Udon 没有地方安放接收者。让那个类自己订阅:
public class Tally
{
public void Watch(Observable<int> score) { score.Subscribe(Add); }
private void Add(int value) { ... }
}
没有订阅者数量,Unsubscribe 也不告诉你结果。 移除是按内容匹配的,所以可以用当场新建的委托取消订阅,不必是当初添加的那一个;但 Udon 没有注册任何查看订阅者列表的手段。需要知道自己订阅了什么的代码,请自己记录。
CascadeStalled 报告的是一次重启十六轮仍未收敛的 publish。要走到那一步需要一个会回写同一个 observable 的订阅者,而编译器拒绝这种写法,所以在世界里它始终是 false。
ObservableList<T>
订阅者签名是 Action<ObservableList<T>>,拿到的是列表本身。
| 成员 | 是否通知 | LastChange |
|---|---|---|
T this[int] { get; set; } |
设置时,且仅当不同 | Replaced |
Add(T) |
是 | Added |
AddRange(T[]) |
整段只通知一次;空或 null 则不通知 | None |
Insert(int, T) |
是 | Added |
bool Remove(T) |
仅当原本存在 | Removed |
RemoveAt(int) |
是 | Removed |
Clear() |
仅当原本非空 | Cleared |
AddSilently(T) |
否 | |
Contains、IndexOf、ToArray、Count |
否 |
在处理器内部,LastChange、LastIndex 和 LastItem 描述发生了什么,订阅者因此可以只生成一个对象,而不必整个重建:
private void OnNamesChanged(ObservableList<string> list)
{
if (list.LastChange == CollectionChange.Added)
Spawn(list.LastItem);
else
RedrawEverything(list);
}
它们描述的是正在进行的这一次通知,只有在处理器内部才有意义。
ObservableDictionary<TKey, TValue>
订阅者签名是 Action<ObservableDictionary<TKey, TValue>>。
| 成员 | 是否通知 | LastChange |
|---|---|---|
TValue this[TKey] { get; } |
否 | |
Set(TKey, TValue) |
仅当值不同 | 新键为 Added,已有键为 Replaced |
bool Remove(TKey) |
仅当原本存在 | Removed |
Clear() |
仅当原本非空 | Cleared |
SetSilently(TKey, TValue) |
否 | |
ContainsKey、TryGetValue、Count |
否 |
LastKey 和 LastValue 指出改变的是什么。
这里故意没有索引器 setter。 Udon 里的字典是 DataDictionary,读一个不存在的键会直接出错,而不是返回默认值;把一次读取藏在 setter 内部就成了陷阱。Set 让读取保持显式:先问 ContainsKey,或者用 TryGetValue。
CollectionChange
None、Added、Removed、Replaced、Cleared。
None 同时表示"还什么都没发生"和"一次没有单一元素的批量变化":AddRange,或者一次在展示集合而非报告变化的 Subscribe(handler, true)。
如果处理器抛出异常
在 Udon 里,一个异常会让 behaviour 在本次会话余下的时间里停摆,也没有办法只接住某个订阅者的异常再继续下一个。抛出异常的处理器会把本轮剩下的通知、以及那个 behaviour 之后本该做的一切一并带走,所以处理器最好只做读值和更新这两件事。