« 吉里吉里Z本体に9 patch ( 9 slice )を入れるか検討する | メイン | 64bit 版をリリースした »

2016年04月28日

吉里吉里Z 開発:: 部分的マルチプラットフォーム化による段階的移行

    

一気にやるのは難しいから、部分的に下位部分を変更して行って、その内マルチプラットフォームになっている作戦。
一気にやる場合でもどこをどのように変更すべきかについてまとめておいた方が良いと言うことで。
必要とわかっているものを列挙する(他にもある可能性あり)。

1. サウンド
2. ウィンドウシステム(グラフィックAPI)
3. 動画
4. メインスレッドへの処理委譲
5. タイマー
6. スレッド
7. アトミックAPI
8. プラグイン
9. 環境固有情報
10. ファイルシステム
11. 文字列処理関数
12. メッセージリソース
13. システムカラー
14. IME
15. マウスカーソル
16. クリップボード
17. フォント

使用するライブラリに関係して Windows、Linux+MacOSX、Android、(iOS) と言う順になりそうだが、気持ち的には、Windows、Android、MacOSX、Linux、(iOS) なので、どの環境が二番目に動くようになるかは不確定。

1. サウンド
現在 DirectSound が使われているが、少し古いのでマルチプラットフォーム対応のライブラリで新しい API にも対応できると良い。
OpenAL が有名だが、BSD → LGPL → プロプラエタリ or OpenAL soft(LGPL) と使いづらい変遷をたどっている。
他には PortAudio がある。
PortAudio に対応すれば、少し新しい WASAPI をサポートできる。
Windows/OSX/Linux 対応で MIT License 。
Android/iOS 対応はないが、マルチプラットフォームに対応されていて抽象化されているだろうから、インプリメントしやすい可能性が高い。
iOS は、Core Audio で OSX と共通で使えるかもしれない。
Android は、OpenSL で別実装の必要があるはず。
マルチプラットフォームためだけでなく、Windows でも新 API サポートと言うことでメリットがある。
XAudio2 もサポートできるとより良いが。

2. ウィンドウシステム(グラフィックAPI)
マルチプラットフォームなウィジェットライブラリは色々とあるが、吉里吉里Z で使うには大きすぎる。
吉里吉里Z だと、ほとんどウィンドウの表示くらいが出来ればよく、ボタン等のGUI部品ウィジェットは不要。
マルチプラットフォームのGUIツールキット で以前書いた通り。
GLFW は Window+OpenGL 周りのライブラリ。
Windows/OSX/Linux 対応で zlib/libpng license.
Android/iOS 対応はない。Android は NativeActivity ではなく Java の Activity で実装してから C 側を呼び出す実装の方が色々と対応しやすい。
OpenGL なので、その部分に関しては全プラットフォーム共通となる。
Windows では、現在の win32 API を直接叩き Direct3D で描画するものがメインで、GLFW 版 Window の扱いをどうするか検討の余地がある。
マルチプラットフォームとなると OpenGL が標準的だが、Windows では DirectX の方が都合が良い。
デバッグオプションとして ifdef で切り替えるか、動的にも切り替えられるかだが、動的に切り替える用途も特にないのでデバッグオプションで切り替えてしまって良さそう。
ウィンドウは TTVPWindowForm を抽象化して、WindowImpl は共通化してマルチ展開するのが楽(羽々斬(吉里吉里Java)でもそのように実装)。
DrawDevice 用などに iTVPWindow と言うインターフェイスも存在しているので、インターフェイス名をよく考えないと紛らわしくなる。
また、DrawDevice の変更予告 で書いた Bitmap 上下反転解消は先に欲しい。

3. 動画
各環境固有で実装。
環境ごとの API を使いハードウェア支援を受けて再生出来ないと負荷が厳しい。
抽象化層を作り、汎用的な API を切り出してライブラリ化出来ると再利用性が高くなるが、吉里吉里Z 専用で作ってしまうのが早い。
実装に時間がかかるので、今ある Windows 以外は後回しか。

4. メインスレッドへの処理委譲
NativeEventQueue にてある程度抽象化して実装しているが、機能自体は Win32 のメッセージ機構を用いて実現されている。
他環境では自前で排他キューを準備して、キューに入ったらメインスレッド起こして読んでもらうなどの実装が必要。
GLFW では、null イベント投げて起こす機能があるので、そこでキューを読んで処理すると実現できると思われる。
ロック機構は環境固有となるので、抽象化された API 等を作り、それ経由で呼び出すことになる。

5. タイマー
内部で使われる Win32 のタイマーを使ったメインスレッドでタイマーを呼び出すためのクラスと、TJS2 のタイマーがある。
TJS2 のタイマーはスレッドを使用した自前実装で環境非依存(ただし、スレッドとメインスレッド実行が環境依存あり)。
スレッドでタイマー計測し、メインスレッド呼び出しを行う。
TJS2 のタイマー実装(自前実装)に統一し、内部用か TJS2 用かを振り分けて処理するようにするのが良さそう。

6. スレッド
Windows 以外は pthread だが、POCO で実装すると共通化出来る。
C++1xで追加されたスレッドでもいいが、POCO の方が扱いやすそうに見える。
Windows は現在の実装でも、POCO の実装でもどちらでもよい。
動作に問題ないようなら POCO で統一してしまった方が後々楽。
グラフィック描画で使われているスレッドプールをどうするかと言う問題がある。
問題は CPUアフィニティ。
Android ではスレッドとコアの対応付けが期待したように動かないらしい。
Windows は現在の実装として、他の環境はアフィニティは諦めてしまっても良いか。
スレッドプールでは、アトミックAPIが使われているので、これは抽象化した方が良い。
当然ロックやイベントなども必要。
ロック機構が TJS2 側で実装されているのがやや難。
TJS2 が POCO 依存してしまうのは諦めるか、ifdef で pthead か Win32 で切り替える方法があるが、POCO 依存にしてしまうのが妥当か。

7. アトミックAPI
Windows では Interlock 系のメソッド。
単純に環境ごとに切り替える定義で問題なさそう。
tjsConfig などに追加するのが無難か。

8. プラグイン
Windows 以外は、共通の API でいける。
ifdef で環境ごとに呼ぶ API 切り替えてしまって良さそう。
iOS はそもそも dll が使えないのでサポートできない。
本体ではなく、プラグイン側に提供する tp_stub も環境依存しているので、そちらへ提供する API なども環境依存をなくすか、環境ごとに tp_stub を吐き出す必要がある。
どちらが良いかは迷うところ。

9. 環境固有情報
OS 名やフォルダなどの環境固有情報は、POCO を使用して取得する。
Windows は現在のままの実装か結果が変わらないものについては POCO に任せて共通部分を多くした方が良さそう。
どちらにしても一段ラッパーをかませて抽象化する。

10. ファイルシステム
Windows は、Win32 API で その他は FILE ポインタを使った標準的なファイル入出力。
API を抽象化して実装できるとよい。
Windows は、UTF-16 だが その他環境では UTF-8 になるので、Windows 以外は変換が必要。
また、Linux/Android はケースセンシティブなので、ファイル検索時などに見付からない場合、大文字小文字無視した比較で検索するなどの必要がある。
パスデリミタも¥と/の違いがある。

11. 文字列処理関数
wchar_t のバイトサイズが Windows では 2バイトだが、Linux 系は 4バイト。
tjs_char を 2バイト固定にしてしまい、各文字列処理関数を自前で持ってしまうのが問題が少ないと思われる。
また、文字列処理も SIMD 化可能なので、SIMD 実行も行えば高速化が期待できる。
実装は、Android の標準ライブラリの Bionic libc が BSD ライセンスなので、そちらを tjs_char に書き換えて使用するのが良いか。

12. メッセージリソース
環境ごとにリソースに埋め込むのが理想だが、ソースコードにメッセージを埋め込む形でも問題はない。
初期は Windows のみリソースに持ち、他は各環境のリソースに順次置き換えてくのが現実的か。
Android は、ロケールは Java を経由して取得する必要がある。

13. システムカラー
Windows での標準的な色を定数として返すか、テーマカラーシステムようなものを追加して、任意の定義ファイルから読み込めるか。初期は定数で十分。

14. IME
Windows 以外は非サポートか、環境ごとに実装するか、Wnn などを組み込んで自前でやるか。
PC 以外ではソフトウェアキーボードも必要になってくるが、それらは TJS2/Layer での実装も可能、変換さえできれば何とかなる。

15. マウスカーソル
環境ごとに実装が必要。
Android は変更できない。
初期は、Null Device で単に何も起こらない実装でよい。

16. クリップボード
環境ごとに実装。
初期は、Null Device で単に何も起こらない実装でよい。

17. フォント
現在 GDI と FreeType が使えるが、Windows 以外では FreeType 限定。
元々マルチプラットフォームを見越して実装していたもので、役立つ時。
Font.rasterizer は Windows 以外では意味がなくなる。
システムにインストールされているフォントの取得は環境依存となるが、全て対応していくのは大変だからまずはフォントファイルのみの対応。
Font.faceAsFileName などのオプションを追加して、このオプションが有効な時は face で指定された文字列はファイル名として扱ってフォントファイルを読み込む。
カンマ指定で複数スタイル(bold等)のファイルを読み込めるとより良い。
読み込まれるのはファイルの最初に合致する face。
Windows 以外は、Font.faceAsFileName は常に true (将来システムのフォントを引っ張って来れるよう実装したら、Windows と同じように false デフォルトとなる可能性あり)。
デフォルトのフォントはリソースやオプションで指定可能だが、Windows 以外では今あるフリーフォントを考えると Noto Sans CJK が妥当か。
Windows と同じように指定可能として。
互換性を考えるのならスクリプトの最初に――
Font.rasterizer = frFreeType;
Font.faceAsFileName = true;
――と指定する。
また、アーカイブには Noto Sans CJK の日本語フォントを入れる。


Null device
吉里吉里2 は Intf で共通 TJS2 関数を実装、Impl で環境固有 TJS2 関数を実装する形になっているが、TJS2 からは出来るだけ共通の関数が使用できることが望ましいため、TJS2 に公開するクラス/関数レベルでの抽象化ではなく、より下位の部分で C++ で抽象化する方が作りやすい。
C++ の抽象化されたクラスは、スタブや Null device と呼ばれる何もしないインスタンス化できるクラスを実装し、そのクラスを元に各環境用に実装をすると複数環境で実装しやすい。

抽象化されたマルチプラットフォーム API 群を整備し、基本的にはそちらを使うような実装へ変えていく。
それら API はドキュメントも整備されている方が望ましい。


64bit 版では、上記以外のもので一部マルチプラットフォーム対応になったものがある。
ShiftJIS UTF-16 変換を Win32 API から内部関数へ変更し、非 Windows 環境でも変換に差が出ないようになった。
SIMD 版 libjpeg を turbo jpeg へ変更し、PC(x86) のみでなく ARM でも SIMD 対応された libjpeg が使えることになった。


変更の優先順位は、現行の Windows 版でも意味のあるものから順に対応して、優先度が低いものは Null Device で対応。

第一は、Windows に恩恵があるサウンドと文字列処理のみ。
第二は、Windows 版にも変更が入るもの タイマー、スレッド、アトミックAPI、環境固有情報、ファイルシステム。
第三は、Windows 版では使われないが、Windows でも確認できるもの ウィンドウシステム、メインスレッドへの処理委譲、メッセージリソース、システムカラー。
第四は、Windows 版では使われないし、他環境用での確認が必要なもの プラグイン。
第五は、IME、マウスカーソル、クリップボード、動画はスタブでよい。

優先順位としては上述のようになるが、確保できる時間を考えて実装するものを考えることも。
例えば、システムカラーなどは数時間で実装できる。

開発は、細かくブランチ切って、出来たら master へちまちま入れて行くのが楽か。
長期間ブランチにして master と離れていくとマージが大変になる。


投稿者 Takenori : 2016年04月28日 22:29




comments powered by Disqus
Total : Today : Yesterday : なかのひと