ActionScriptのThreadライブラリ(そうめん)をHaxe/OpenFLに移植した際にやったこと


はじめに

ActionScript Thread Library 1.0 (そうめん)をHaxe/OpenFLに移植しました。

HxThread (https://github.com/utibenkei/hxthread)

この記事ではAS3のライブラリをHaxeへ移植した際に行った作業を手順を追って説明したいと思います。

ActionScript Thread Library(そうめん)とは

yossy氏が開発された、非常に便利なライブラリです。

ActionScript Thread Library 1.0 (そうめん) は、タスクシステムと Java のスレッドモデルをベースとした疑似スレッドライブラリです。 複雑で冗長になりがちな、イベント処理や非同期処理、リアルタイム処理を、分かりやすくスマートに記述することを可能にします。 もちろん、割り込みやキャンセル、例外処理もバッチリです。スレッドを使って、イベントリスナー地獄から抜け出しましょう。

具体的な使い方はここを見てもらえればわかると思います。

開発環境

この移植作業の動作検証は全て、以下の環境で行いました。

  • Windows8.1
  • FlashDevlop
  • Haxe(3.1.3)
  • OpenFL(2.1.7)
  • munit(2.1.0)
  • async-tests(1.0.0)

AS3とHaxeの文法の違いを確認する

まずは移植作業の参考になるサイトを一通り眺めて、 AS3とHaxeの文法の違いを確認します。

AS3 to Haxeコンバータで変換する

元となるActionScript Thread LibraryのソースコードをSVN等から入手してください。

AS3 to Haxeのコンバータはいくつか存在しますが、今回は一番良さげなas3hxというコンバータを利用します。

使い方

haxelibでインストールしてから

haxelib run as3hx Thread/ out/

で、出力フォルダに変換された.hxファイルが生成されます。

※ホームディレクトリに生成される".as3hx_config.xml"という設定ファイル編集することで、変換内容をカスタマイズすることができるようです。

手作業でコードを修正する

単純なコードならコンバータによる変換結果がそのままでHaxeで動くかもしれませんが、今回はいくつかの部分で手直しが必要でした。

* リフレクション

as3
runHandler.apply(this, [error, errorThread]);
haxe
Reflect.callMethod(this, runHandler, [error, errorThread]);

* インスタンスオブジェクトからクラス名を得る

as3
getQualifiedClassName(error);
haxe
Type.getClassName(Type.getClass(error));

* パッケージを含まないクラス名を得る

as3
var names:Array = getQualifiedClassName(this).split(/::/);
return names.length == 2 ? names[1] : names[0];
haxe
return Type.getClassName(Class).split('.').pop();

これらの手直しにより、Haxeのflash出力でコンパイルが通るようになりました。
flash出力ではオリジナルと同じ「as3unit」というswcのテストライブラリを利用したかったのですが、HaxeでAS3のnamespaceを指定する方法が(いくら調べても)判らなかったので、あきらめて「munit」というHaxeのユニットテストライブラリを利用しました。
ユニットテスト、サンプル共に無事にに動作しているようです。

OpenFLでも動くようする

ここからはこの移植の本当の目的であるOpenFLでの動作を目指します。
もしかしたら、修正無しでクロスプラットフォームで動くかなぁと淡い期待をしていたのですが、世の中にそんな都合のよい話はありません。
以下の修正が必要でした。

* native/html5ターゲットではaddChild()しないとEnterFlameイベントが発行されない

EventDispatcherをaddChildする

EnterFrameThreadExecutor.hx
        #if (native || html5)
        // openflの native/html5 ターゲットではaddChild()しないとイベントが発行されない現象への対処
        openfl.Lib.current.addChild(_clip);
        #end

* nativeターゲットでLoaderでローカルのファイルをロードすると同期的に完了イベントが発行される

(Loader/URLLoaderではnativeのみ、SoundLoaderではnativeとhtml5でもみられる現象)
イベントリスナーの適用を待ってからロード開始する

LoaderThread.hx
    override private function run():Void
    {
        events();
        // 割り込みハンドラを設定
        Thread.interrupted(interruptedHandler);

        // ロード開始
        #if (native)
            // イベントリスナーの適用を待ってからロード開始する
            // openflの native ターゲットではローカル上の画像ファイルをロードした際に同期的に完了イベントが発行される?
            // (internalExecute()内のeventHandler.register()でリスナーが設定される前にロード完了イベントが発行されてしまうことへの対処)
            var funcThread = new FunctionThread(function() { load(); }, []);
            funcThread.start();
        #else
            load();
        #end
    }

これでやっとOpenfFLでも使えるようになりました。
OpenFLでも「munit」でユニットテストをしたかったのですが、なぜかnativeターゲットにおいて非同期テストを動作させることができなかったので今度は「async-tests」というテストライブラリを利用しました。

とりあえず今のところ、OpenFLの以下のターゲットでテストとサンプルが動作することを確認しています。

  • flash
  • native (neko / windows)
  • html5

おわりに

コンバータがあったので移植作業は思ったより楽でした。
なにより元のライブラリのコードにきめ細やかな日本語のコメントが付いていたので助かりました。
素晴らしいライブラリを開発していただいたyossy氏に改めて感謝と共にお礼申し上げます。

追記

記事の投稿後に素晴らしいAS3ライブラリ移植のプロジェクトを見つけたので、ぜひ紹介させてください。

SiON (https://github.com/gunnbr/SiON)

SiONライブラリとは、こんなライブラリです。(オリジナル版ページより引用)

SiON (“サイオン”) は,Flash Player 10 上で動作する ソフトウェア音源 です.
このライブラリによって mp3 データを用意しなくても様々な音を合成し,楽譜に合わせて演奏する事ができます.また,曲シーケンスと DisplayObject の連携や,動的な音楽生成が,シンプルに実装できます.もちろん mp3 データにも対応しており,Sound クラスを PCM 音源波形として取り込む事で,スライシング/ループ再生,ピッチ/エンベープ/モジュレーション/フィルタなどの操作やエフェクタによる加工,mp3 データ自体を音色として楽譜の演奏,などが可能です.
楽譜や音源の設定は Music Macro Language (MML) によって記述する事ができるため,非常に軽量なテキストデータによって音楽や効果音を表現できます.

コミットログもとても参考になります。
まだ移植途中のようですが、頑張っていただきたいです。