Scratch のメッセージングは、普通のオブジェクト指向言語と変わっていて(Scratch 自体はれっきとした「オブジェクト指向言語」、内部は Smalltalk だったわけだし)、メッセージをブロードキャストで投げます。ブロードキャストメッセージとは何か?そもそもブロードキャストってどういう動きをするのか?と言えば、
普通のメッセージは、送り手のクラスがあって、受け手のクラスがある。値を送る(参照でもいいけど)という1対1のイメージですね。いわゆる 受け手.method( 値 ) で、受け手のクラスに送るパターンです。
これがブロードキャストになると、1対多の関係になって、送り手.method( 値 ) のイメージになります。
この図だと、送り手が複数の受け手に対して送るので、1対1の関係を増やしたような感じになりますが、実際のところはメッセージをブロードキャストで送るってのは、中間的なクラスがあってブロードキャストの場合はそれぞれに送るというスタイルになります。
送り手からは1回のメッセージを送っただけで、複数の受け手にデータが流れるという感じですね。
これ、ちょうど UDP の配信と同じ仕組みで、双方向のコネクションをとらない UDP 通信ではブロードキャストが可能になります。TCP とは異なり一方的にデータを流します。
同じことは、映像配信にも言えて、いわゆる昔のテレビの電波だとか、地デジに配信も似た感じになります。ブロードキャストは一方向にしか流せないけど、非常に多い受け手に対して同時配信をするときの良い仕組みです。
物理的にブロードキャストを実現するならば、電気信号を分配してそのまま増幅させればよいし、光通信も光を分割して後から増幅すればいくらでも同じデータを増やせます。まあ、完全に同じではないことは量子的な解析になるわけですが、普通の範囲では分割してデータを流すことが可能です。
Scratch のブロードキャスト
Scratch には、メッセージを送るためのブロックがあって、
- 受け手が、メッセージを受け取ったときのイベント処理
- 送り手が、メッセージを送り、処理を待たない
- 送り手が、メッセージを送り、処理を待つ
という3つのブロックがあります。「送って待つ」ブロックは、いわゆる処理を受け取るパターンになるのですが、実はこのメッセージは相手が複数あってもよい(ブロードキャストだから)、「送って待つ」というのは、ブロードキャストを送ったすべての相手の処理を待つという、all wait のパターンになります。なかなか複雑で、おもしろいですよね。
このメッセージングが複数持てるということは、メッセージ先の「受け手」はスレッドで動いているのでは?という想像ができます。おそらくScratch はラウンドロビン形式で動いているはずなので、メッセージの先(各スクリプト)が OS レベルでのスレッドで動いているとは思えませんが、少なくとも「スレッドもどき」で同時実行できるということになります。
マルチスレッドなので無限ループでブロッキングしない
猫弾幕3 https://scratch.mit.edu/projects/249352885/ のネズミのスクリプトを見るとわかるのですが、「旗をクリックされたとき」の処理が3つあります。これもそれぞれ同時実行しています。
左上のブロックで「ずっと」を使っていて無限ループになっているのは「怖い」感じがするのは C言語を想定するからで(実際、怖い感じがしますw)、Scratch の場合はこのような無限ループにしても適度に OS に処理を戻してくれるので問題ありません。ループはだいたい 20 msec 位で動いています。
猫弾幕3では、2つの変数(残りの数、秒数)同時に別々のループでチェックしています。ふつうにプログラミングをするならば、ひとつのループの中で2つの変数を2つの if 文を使ってチェックするところですが、Scratch の場合はこんな風にループ自体を分けてしまうことができます。2つのループが同時に実行されるために、2つの変数が同時にチェックされる、という仕組みですね。意外とこの仕組みは重宝します。
親指の数をどう勘定するか
さて、いっせーのせに戻ると、この上がっている親指の数をどうやって数えようか?と悩みます。
ひとつの方法としては、8つの右手と左手についてサムアップしているかどうかをひとつずつチェックします。
幸いにして、右手と左手はそれぞれ別のスプライトとなっているので、別々に勘定ができます。
まあ、これでもいいんですけどね。リストに入れて効率化してもいいですけど、さっきのブロードキャストメッセージを活用してみましょう。
「いっせーのせ」メッセージを送って待つことにします。ブロードキャストメッセージで送るので、メッセージが送る先の数は「送り手」にはわかりません。どこに送るかもわかりません。どれだけ多くの相手がいるかわからないけど、すべて相手が応答を返すまで待つのは確かなことです。
さて、受け取った右手や左手のスプライトは、「いっせーのせ」メッセージを受け取ったら、サムアップをしている(コスチューム番号が1になっている)場合は、合計を1だけ足して返します。これは、どの右手、左手のスプライトも同じです。
さて、「いっせーのせ」メッセージを送った送り手は、最終的に何を受け取るでしょうか?そう、サムアップしているが「合計」に入っているというわけです。ちょうど、ブロードキャストにメッセージを配信している形と同じになりますね。
この右手と左手のスプライトは、どの順番で実行されるかわかりません(実際は、スプライトの番号順になるだろうけど)。順番は問わないし、受け手となるスプライトの数も関係ありません。
例えば、人数を増やしたいと思ったとき先の「親指の数を数える」の定義では、if 文を追加していかないといけませんが、ブロードキャストメッセージを利用したパターンでは追加したスクリプトに「いっせーのせ」メッセージを受け取ったときの処理を追加しておくだけです。このあたりはオブジェクト指向のインターフェースという形になりますが、そのあたりも含めて Scratch は非常にオブジェクト指向言語として綺麗に動きます。
サンプル
- いっせーのせ on Scratch https://scratch.mit.edu/projects/249455536/
- 猫弾幕3 on Scratch https://scratch.mit.edu/projects/249352885/
UDPのブロードキャストはどうするのか?
参考までに、UDP通信のブロードキャストの例を晒しておきます。C#だとこんな感じ。ネットワーク内(ネットワークマスク内)にあるPCに一斉通信をすることができます。Wi-Fiは通らないので有線LANのみ有効な技ですが重宝します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public async Task< bool > Connect() { // ブロードキャスト送信 var client = new UdpClient(); client.EnableBroadcast = true ; int n = await client.SendAsync( CMD_REQUEST_RESPONSE, CMD_REQUEST_RESPONSE.Length, new IPEndPoint(IPAddress.Parse(BROADCAST_ADDR), BROADCAST_PORT)); var recv = await client.ReceiveAsync(); _ep = recv.RemoteEndPoint; client.Dispose(); return true ; } |