これはなに
Scratch2の野良拡張許容バージョンであるScratchXを使って、
WebSocket経由でejectしてみます。
「小学生でも(☝ ՞ਊ ՞)☝したい!」、と要望にお応えします!
# 残念ながら、後述のWebSocketを喋るEjectサーバーが必要なので、純粋なScratchX単体でejectできるわけではないです。
ScratchXとは
ScratchXはブラウザで動作する学習向けビジュアルプログラミング環境の
Scratch2の派生版のひとつで、
JavaScriptでプラグインを自作して機能拡張をすることができます。
WebSocketとは
HTML5の一部として規格化が進んでいる通信プロトコルです。汎用プロトコルなので、
テキストもバイナリもなんでも流せる、なんとなく everythings through 80 portな感じも
しなくはないですが、そんな感じの奴です。
各種ブラウザで実装がすすんでいて、「ブラウザ上のJavaScriptのサンドボックスから外界に出られる窓」として使えます。
特に重要なのは、従来ブラウザからできなかった、「つなぎっぱなしにしておいて、サーバーからのpush通知を受ける」という
使い方ができることです。
これは、スタンドアロンでのネットワークプログラミングのモデルが使えるということでもあります。
実施例
ScratchXで動作するサンプル
http://scratchx.org/?url=http://soburi.github.io/scratchx-websocket/examples/eject.sbx#scratch
nodejsで実装したWebSocketを受けてEjectするサーバー(Windows用、要wsライブラリ)
https://github.com/soburi/scratchx-websocket/blob/gh-pages/server/eject-server.js
自ホストでサーバーを動かして、Scratchの緑の旗の実行で、延々eject/closeします。
MS系ブラウザではlocalhostにつなげない、FirefoxはScratchの動作が怪しげ、
なんでChrome推奨です。
Fig1. Scratchならではの極めて直截的なejectの表現
ScratchXのプラグインの作成
必要なことはすべて本家のWikiに書いてありますので、要所だけ紹介。
作れるブロック
Scratchでは一般的なプログラミング言語の「文」に相当する、「ブロック」を
組み合わせて、プログラムを記述します。
ブロックにはいくつかの種類がありますが、プラグインでは、
を新たに定義できます。
ブーリアンブロック
ドキュメントには記載無いですが、レポーターブロックの特殊な形として、
も作れるようです。
プラグインの登録
- ScratchExtensions.register()
この関数を実行することで、Scratchにプラグインを登録します。
プラグインのJavaScriptが読み込まれるシーケンスで実行する必要があります。
デスクリプタ
*ScratchExtensions.register()*に渡すデスクリプタは、以下のような定義になっています。
// Block and block menu descriptions
let descriptor = {
blocks: [
['w', 'connect to %s', 'connect'],
['w', 'disconnect', 'disconnect'],
[' ', '(☝ ՞ਊ ՞)☝', 'send_eject'],
[' ', 'close drive', 'send_close'],
['h', 'when disc ejected', 'onDiskEjected'],
['h', 'when drive closed', 'onDriveClosed'],
]
};
blocksのセクションがプラグインのメインの定義で、Scratch上での表現と、その内部実装となる関数の
対応付けを行います。
各エントリは、
- ブロックの種別
- ブロックの書式文字列
- JavaScriptの関数名
- [オプションで]引数のデフォルト値
を指定します。
ブロックの種別
1文字のブロックの指定子でブロックの種別を指定します。
ブロック指定子 | 形状 | 詳細 |
---|---|---|
' ' | スタックブロックを指定します。 | |
'w' | スタックブロックを指定します。非同期版 | |
'r' | レポーターブロックを指定します。 | |
'R' | レポーターブロックを指定します。非同期版 | |
'h' | ハットブロックを指定します。 | |
'b' | ブーリアンブロックを指定します。 |
ブロックの書式文字列
ブロックに表示される書式文字列を指定します。この書式文字列でJavaScriptの関数が取る
パラメータを指定します。
書式指定子 | 値 | |
---|---|---|
%n | 数値 | |
%s | 文字列 | |
%b | ブール値 | |
%m.[メニュー項目] | メニューから選択される文字列 |
関数名
関数名は *ScratchExtensions.register()*の第3引数に渡すオブジェクトが持つ関数の名前を指定します。
eject-extension 解題
eject-extensionの本体は、ここにいます。
https://github.com/soburi/scratchx-websocket/blob/gh-pages/eject-extension.js
エントリポイント
new (function() {
...
// Register the extension
})();
単発で無名関数作って実行してるだけです。
普通は関数の最後でScratchExtensions.register() を実行して、プラグインを登録します。
このプラグインでは、websocketの共通的な部分を別ファイルに分離して、
jQueryのgetScriptで読み込んでいるので、読み込み完了のコールバックで、
*ScratchExtensions.register()*を実行しています。
ブロックの実装
send_eject, send_close
eject-extensionでは、スタックブロックでeject要求、close要求を実装しています。
内部的にはsendでJSONデータを投げるだけなので、同期で実行します。
送信するコマンドのJSONの形式は
ejectなら
{"command: "eject"}
closeなら
{"command: "close"}
という単純なモノです。
ここでは、通信の応答はチェックしていないので同期で実行していますが、request and responseの形式であれば、
非同期で応答を待つ実装を取ることになります。(当然エラー応答の処理や、タイムアウトの考慮が必要になってくる)
onDiskEject, onDriveClose
Eject完了時、Close完了時の通知はハットブロックで実装しています。
eject, closeの要求に対して、ejectが完了したら、
{"status: "ejected", "error": 0}
closeが完了したら、
{"status: "closed", "error": 0}
とこれまた、単純なステータス応答が帰ってきます。
WebSocketからの通知で現在の状態としてキャッシュして、
ハットブロックの関数では、以前の比較を行った時点での値と比較しています。
ハットブロックはポーリングで動作しているので、WebSocketからのイベント通知時には直接
Scratchにイベント発生を通知できません。
ポーリング時に処理できるように状態を保持しておく必要があります。
connect, disconnect
WebSocketのconnect, disconnectは非同期版のスタックブロックを指定しています。
WebSocketのconnectの完了通知、もしくはエラー通知は、非同期で通知されるので、
この通知を受けた時点でコールバックを実行して完了を通知します。
非同期版を指定した場合には最後の引数にcallback通知の関数オブジェクトが渡されます。
この関数を実行することで、Scratch側の待ち合わせが完了します。
connect, disconnectはWebSocket共通の面倒な処理なので、
https://github.com/soburi/scratchx-websocket/blob/gh-pages/ws-ext.js
に分離しています。
API設計
スタックブロック、レポーターブロックとJavaScriptの対応
スタックブロックはコマンドとして何らかの処理を行うブロックで、
JavaScript上では「値を戻さない関数」でプロシージャ的に処理を
実行します。
レポーターブロックは値が取得できるブロックで、
JavaScriptでは「値を戻す関数」に対応づけられます。
Scratch上ではスタックブロックは基本的な実行単位として、
ほぼプログラム上のどこでも実行できる、
レポーターブロックは取得した値を受け取る箇所でしか使えない
という動作になります。
API設計としては、「スタックブロックは値を返さない」というところが、
単純にJavaScriptのAPI設計と対応しないところになります。
基本的には、ブロックで行う操作の抽象度を上げる方向で対処することになるかと思います。
エラー処理
Scratchには例外機構など、エラー処理の仕組みがないので、この辺も「特別な考慮」が必要です。
特に、ネットワークで通信させる場合には、通信エラー処理とは長いお付合いなので、
この辺は都度考慮が必要になるかと思います。
ハットブロックの動作
ハットブロックはイベント通知を契機に処理を開始する構造です。
Scratchでは処理のエントリポイントとして表現されますが、
JavaScriptでは状態をポーリングする関数として実装します。
ブロックの実行は、falseからtrueに値が変わったときに行われます。
イベント的な動作をさせる場合は、一度trueにしたのを記憶して、
すぐfalseに戻すパルス的な動作をさせる必要があります。
websocket-extensionによるeject実装
基本的なWebSocket通信と、簡易JSON処理をwebsocket-extensionとして分離しています。
JSON作る程度ならScratchでも出来ないことはない、ということで作成しましたが、
Scratch的な、高い処理の抽象度で処理を図形で表現するというところからは、かなり遠いところにいるので、
適当なScratchXプラグインを作成したほうが良い、ということが理解いただけるかと思います。
プラグインの実行
scratchx.orgのページから色々入力することでも実行できますが、直接実行するには、
http://scratchx.org/?url=[プラグインのjavascriptのurl]
のようにすればOKです。
github使う場合は、GitHub Pages の機能でブラウザで直接見えるところに置きます。
eject-extensionの場合はこんな感じ。
http://scratchx.org/?url=http://soburi.github.io/scratchx-websocket/eject-extension.js
サンプルのsbxファイルを直接実行する場合も
http://scratchx.org/?url=[sbxファイルのurl]
で良いのですが、こちらの場合はsbxファイルが置いてあるドメインのトップに
crossdomain.xml
が必要になります。
詳細は、
https://github.com/LLK/scratchx/wiki#setting-up-crossdomainxml
を参照のこと
まとめ
- ScratchXではJavaScriptでプラグインが書ける。
- WebSocketを使うと、JavaScriptから外部に通信できる。
- なので、WebSocketを受け付けるサーバーを作ると、Scratchから色々ちょっかい出して遊べる。