猫でもわかるWebプログラミングと副業

本業エンジニアリングマネージャー。副業Webエンジニア。Web開発のヒントや、副業、日常生活のことを書きます。

Yjs の共同編集開始時の処理 writeSyncStep1 について

writeSyncStep1 とは

Yjs の WebSocket サーバーを実装していると、 writeSyncStep1 という謎のステップがあることがわかる。

共同編集に新しいクライアント(共同編集者)が参加した時に、今共同編集中のドキュメント全体を貰う必要がある。

クライアントが共同編集に参加した後に、writeSyncStep1 メッセージを送ると、その応答として、共同編集中の別のクライアントが、ドキュメント全体を送ってくれる。

新しく参加したクライアントは、この応答をもって初めて共同編集処理がスタートされる。writeSyncStep1 の応答がない場合は、共同編集が正しくスタートされない場合があるので注意。

複数のクライアントが共同編集に参加している場合は、複数のクライアントから応答が返ってきてしまうが、メッセージの重複を排除する処理が Yjs には組み込まれているので、重複して送られてきても問題は無い。

クライアントとサーバー間の通信(WebSocketの場合)を図で表すと以下の通りである。クライアント同士の接続の場合も、サーバーが別のユーザーになるだけで同じだ。

クライアントとサーバーで通信する時のシーケンス図
クライアントとサーバーで通信する時のシーケンス図

より詳細な仕様

Yjs では、バイナリでメッセージをやり取りしている。

まず、最初の1桁目の数字が何かによって以下の分類がされる。メッセージ名は y-websocket のサーバーサイド実装例の

数字 意味
0 共同編集中のドキュメントに関するメッセージ
1 カーソルの位置情報など、ドキュメントの内容以外のメッセージ

さらに、これが「0」の場合は、次の数字1桁(バイナリだと多分2ビット)によって、更に細分される。

数字 意味
0 writeSyncStep1 のメッセージ
1 writeSyncStep1に対する応答のメッセージ
2 ドキュメントの内容の更新に関するメッセージ

0と1は、ドキュメントの共同編集開始時に利用されるメッセージで、2は、共同編集中の更新内容のやり取りに使われるメッセージである。

y-websocket のサーバーサイド実装を見てみると、以下の部分でwriteSyncStep1 に対する応答のメッセージが送信されている(と思われる)。これが非常に分かりづらかった。

github.com

writeSyncStep1 も、messageSync のメッセージであるので、それに対する応答については、 case messageSync 以下の部分で送られている。

readSyncMessage に渡したメッセージが writeSyncStep1 だった場合は、それに対する応答のメッセージが encoder に書き込まれる。encoding.length(encoder) > 1 の場合に send としているのがそれである。

encoding.length(encoder) > 1 している2行上で、encoding.writeVarUint(encoder, messageSync) しているので、ここで encoder の length は1になっている。

その後、syncProtocol.readSyncMessage(decoder, encoder, doc, null) の中で、必要があれば(受け取ったメッセージの2桁目が0 = writeSyncStep1だった場合は)、encoder に、1(writeSyncStep1 の応答であること)と、ドキュメント全体が書き込まれる。

よって、encoding.length(encoder) が1よりも大きいということは、 writeSyncStep1 の応答メッセージである、ということになるので、writeSyncStep1 を送ってきたクライアントに、ドキュメント全体を返している。

ここは、「2桁目が1である」という判定でも良いと思うが(むしろ、わかりやすいのでそっちのほうが良い)、実装が簡単なのでこのようになっているのだろうか...