前からわかっていた話ですが念の為に調査しました。
Networking.GetServerTimeInSeconds()
(以下 ServerTime) は各プレイヤーでほぼ*1等しいので共通の時間軸として使えて便利です。
しかしこの値はだんだん増えます。増えるということは (絶対時間じゃないから) 上限に達するということで、そうなったら巻き戻ると推測されています。
よって、「ServerTime が単調増加する」ことに依存したコードは意図しない挙動を起こす可能性があります。
GetServerTimeInMilliseconds()
が int 型なことから推測すると、範囲は -2147483.648 ~ 2147483.647
になってると思われます。
2147483
秒くらいになった次の瞬間には -2147483
秒ということになってる…というわけです。これでは困ります。
GetServerTimeInSeconds() がオーバーフローする動画 pic.twitter.com/DtFOPC3pSJ
— phi16 (@phi16_) 2025年1月10日
var c = Networking.GetServerTimeInSeconds(); var s = ""; s += $"{c}\n"; s += $"{Networking.CalculateServerDeltaTime(c, 0)}\n"; // 後を読むとわかりますが1行目と自明に一致します s += $"{Networking.CalculateServerDeltaTime(c, firstFrame)}\n"; // こうすると大丈夫そうという話 text.text = s;
対応を考える
自前で繰り返しを処理しても一応動きそうですが、ここで、Networking.CalculateServerDeltaTime(double timeInSeconds, double previousTimeInSeconds)
という関数があります (previous の方が第二引数なことに注意)。
これは ServerTime の差分を計算してくれる関数のようです。が、細かい仕様が結局わかりません。
適当な入力を与えて観察してみたところ、中身はこんな感じっぽいです:
CalculateServerDeltaTime = (time, prevTime) => { double delta = time - prevTime; if(delta < -2147483.6475) delta += 4294967.295; return delta; }
結構思ってたのと違った。というか色々気になる*2。とは言え、目的には十分そうです。
つまりワールドに master が join したときの ServerTime を baseTime
として記録・同期しておき、常に CalculateServerDeltaTime(GetServerTimeInSeconds(), baseTime)
を、インスタンス内時間軸として使えばよい。
もしも ServerTime がオーバーフローして -2147483
秒になったとしても、概ね ↑ の if が機能して、期待通り連続かつ単調増加な値を返してくれることと思います。
ここで概ねというのは「baseTime
が 0
未満なインスタンスでオーバーフローが発生したとき、if の条件を通らない」という話なんですが、これが発生するには 24.9 日インスタンスに滞在する必要があるので、まぁ気にしなくていいと思います。
一応丁寧に処理をすれば 49.8 日まで不具合を引き伸ばせると思いますが、コードがシンプルな方がいいような気がします*3。
ちなみに、ClientSim だと単なる引き算で実装されているっぽいです。まぁ GetServerTimeInSeconds
が 0 始まりの値を返すので大丈夫だろうということなんでしょう。
どれくらい起こり得るのか
ちょっと気になるのは、「これに起因したバグが発生する/している確率」です。
ServerTime は実際各サーバー?によってだいぶ異なるようです。インスタンスを作ろうとしたときに空いているサーバーがランダムに割り振られてるのかな?
rejoin を繰り返して ServerTime の分布を確認してみました。USE を 20 回確認し終える直前でアカウントが Too many requests で VRChat にログインできなくなりました。おすすめしません(30分くらいで解除されました)。EU/JP はテスト用アカウントで少なめに確認しました。
近い値のサーバーが複数ある*4のはサーバー起動タイミングが揃っているとかですかね。なんだかかわいいですね。
ともかく、値が不規則であること、リージョン毎の癖みたいなものはありそうだけどだからと言って何か確定するようなことを言えそうにないことがわかります。
実際独立して 49.8 日周期で繰り返しているとしたら -2147483.648 ~ 2147483.647
が一様に出現しうるはずです。
というわけで「ServerTime が一様出現するとき、1インスタンスで 時間過ごしたときに ServerTime がオーバーフローする確率」を考えますと、明らかに です。
つまり「1時間過ごして ServerTime がオーバーフローする確率は 0.083%」ということです。
これは例えば100インスタンスの独立試行を考えると約8%の確率で起きているということです。そこそこありますね。
public instance とかだと長い事インスタンスが維持されることも多いと思うので、まぁ正直かなり起きていると思います。
うーん。うちのワールドのバグの原因が本当にこれだけだったらいいんですけどね……。