通知规则
订阅者在被通知的过程中订阅、退订或回写时会发生什么。
大多数时候,订阅者只是读一下值然后更新点什么。这一页讲的是剩下的情况:订阅者在自己的回调里改动订阅者列表,或者改动那个值本身。
订阅者不能回写自己的 observable
下面这段编译不过:
score.Subscribe(Clamp);
private void Clamp(int value)
{
if (value > 10)
score.Value = 10; // UDN0012
}
Udonite 会拒绝它。订阅者又碰回自己的 observable,等于经由委托闭合了一个环;而 Udon 无法为间接调用保存栈帧,于是生成出来的程序不能正确返回。
被拒绝才是好结果。它拦下的形状里也包括更不显眼的那一种:回去的那一跳是属性 setter 而不是方法。真让这种代码跑起来的世界并不会很快出错,它会一直冻着,直到 VRChat 用 Program execution time exceeded max VM time of 10,0 seconds 把 behaviour 掐掉,留下的是冻了十秒的世界和一条没有用的信息。
改成赋值前先夹紧:
score.Value = raw > 10 ? 10 : raw;
或者写到另一个 observable 上,那是完全正常的做法。
通知过程中的退订从下一次开始生效
订阅者可以在回调里退订自己,也可以退订别人。正在进行的这一轮通知仍然按开始时的订阅者走完,退订从下一次通知起生效。
private void Once(int value)
{
score.Unsubscribe(Once); // 没问题,其余的照样会被调用,
} // 而 Once 不会再被调用
这和 C# 事件是一样的。 移除订阅者会新建一份列表,而不是改动正在遍历的那一份,这也正是能在回调里退订而不出事的原因。
所以正在被拆掉的 behaviour 可能还会被调用一次,就在已经跑起来的那一轮里。如果某个处理器在对象消失后绝对不能再跑,请在处理器里自己挡住,而不要指望退订:
private void OnScoreChanged(int value)
{
if (closing)
return;
label.text = value.ToString();
}
通知过程中的订阅要等下一次
在通知进行途中加进来的处理器,本轮不会被调用。它并没有错过任何它在场时发生的事,而在它刚加入的那一轮里就被调用反而是意外。
订阅两次,第二次什么也不做
变成订阅两次的常见路径是 Start 又跑了一遍。每次变化被调用两次一定是 bug,所以第二次订阅被忽略而不是被采纳。
通知所有人,和通知一个人
Publish() 会通知每一个订阅者,不管有没有东西改变。
如果只想让一个迟到的订阅者跟上进度,改成给 Subscribe 传 true。用 Publish 也能做到,但那会毫无理由地让其他所有订阅者重绘一遍,也会让任何在计数的订阅者数错。
score.Subscribe(OnScoreChanged, true); // 只有这一个处理器
score.Publish(); // 全部
读取永远是安静的
Count、Contains、IndexOf、ToArray、ContainsKey、TryGetValue 以及所有 getter 都不通知任何人。只有改变才会。
SetSilently 和 AddSilently 改值而不通知,用来放一个订阅者本就没道理去反应的初始值,比如进入世界时恢复的分数。