追記: Gaucheのパス挙動部分をstrike。see comment。

かなり気の早い話しだけど、どうやってSchemeで書いたゲームを配布するかどうかを考える。実はpygameとかDXRuby、Love( https://love2d.org/ )のような既存のフレームワークでもこの部分をあまりリッチに考察しているとは言えないので、それなりに新しいアイデアが必要になる。

yuniは既に10を超える( https://github.com/okuoku/yuni#implementations )Scheme処理系で(ゲームを書くのに必要な)FFI抽象化層を持っているので、原理的にはこれらの処理系全部で動作させる可能性を検討できる。

意外とこのポイントにフォーカスしたゲームプロジェクトは少く、だいたい一度は明示的なビルドを挟む必要がある。既存の同様なゲーム フレームワーク は.exeのような実行可能形式の配布はよく考察しているものの、 ソースコード 形態での実行は良い考察がない。(これはElectronとかnw.jsのような最近流行のデスクトップ JavaScript でも同様と言える気がする) 処理系と"ランチャ"、つまり処理系のフロントエンドとなる GUI アプリは インストーラ で配布しても良いことにする。 ...実は最近の Windows には powershell が付いてくるし、 macOS には Ruby が有るので、原理的には依存関係ゼロで諸々を実装することも不可能ではないが。。

チャレンジとなりそうなのは、 Pygame とかDXRubyのような単一処理系を想定したシステムと違って 多数の処理系を同時にサポートする 点。Chezや Sagittarius のようにロードパスさえ指定すれば高速に動作する処理系もあれば、LarcenyやRacketのように明示的に バイトコード コンパイル しないとロードが遅い処理系もある。 逆に言えば、動作する処理系の数では世界記録を狙えるかもしれない(!)

ランチャは.yuniappを読み、ライブラリの依存関係を必要に応じてセットアップしてからゲーム本体を起動する。 ... 要するに このランチャにどのような機能が必要なのか が問題の中心になる。処理系毎にライブラリの置き方や中間コード/ネイティブコードへの コンパイル 手法等が丸ごと異なるため。 ゲーム本体を R6RS ライブラリで書くのは:

処理系の分析

とりあえず以下の処理系をTier1として、これらの処理系での動作を中心に考える。

(nmoshはそもそもアプリケーション組込みが最初からサポートされているので追加の考察はしない。)

Sagittarius

ChezScheme

Racket

Gauche

Gambit

これらの処理系はWindowsのインストーラが有る(Chezには現状無いが、商用時代には存在したのと、現在開発ブランチは存在するのでそのうちサポートされると仮定)。mit-schemeが選から漏れているのは、まだmit-schemeでyuniのFFIを実現する方法を思いついていないため(non-movableなオブジェクトがmallocでしか作れないので自前のGCが必要)。。

まず、SagittariusとChezSchemeはR6RSライブラリを直接ロードできるので単にライブラリパスを適切に指定して処理系本体を起動すれば良い。これは非常にシンプルなケースで、ランチャは処理系のパス(と、ライブラリパスの指定方法)さえ知っていれば良いことになる。

Racketはライブラリの先頭に #!r6rs を付与し、かつ、r6rs-libsがインストールされていないとR6RSライブラリをロードできない。通常のインストーラを使用すればr6rs-libsはインストールされているはずなので#!r6rsの付与だけが問題になる。ファイルを直接置換すべきかは何とも言えない。そもそもRacketはracoを使用して.exeの生成まで行えるため、可能な限りこのシステムに乗るように構成を考えた方が良いのかもしれない。racoのバイトコード(.zo)生成APIもプログラムから使用できる( https://docs.racket-lang.org/raco/API_for_Simple_Bytecode_Creation.html )。

GaucheはR7RSライブラリのみの対応でR6RSのライブラリは読み取れないため、ランチャは中間ライブラリを生成する必要がある。中間ライブラリから元のR6RSライブラリをincludeし、syntax-rulesでフォーマットの変換を行う。 更に、Gaucheはincludeの相対パスがCのようなソース相対でない (See comment)( https://github.com/okuoku/yuni/blob/master/doc/sibr/SIBR0005-C-STYLE-INCLUDEPATH.md )ため、アプリの物理的位置が変わるたびにライブラリを再生成する必要がある。Gaucheには現状ではバイトコードキャッシュが無いので、単にloadするだけにすべきかもしれない。

Gambitでは先日の方法( http://d.hatena.ne.jp/mjt/20160825/p1 )を使用して完全にexpandしてコンパイルするしか無い。行番号情報を保持するために伝統的マクロとincludeを駆使するとか、マクロをエクスポートしないライブラリだけでも分割コンパイルできるようにするとか色々と考察すべきことは有るが。。

まとめると、ランチャに求められるのは:

システムにインストールされている処理系を検出し、ユーザに選択させる機能

アプリに含まれるR6RSライブラリを読み取り、各種処理系に対応したライブラリを出力する機能

適切なコマンドラインオプションで処理系を起動する機能

GaucheのようにyuniのFFIにネイティブ拡張が必要な場合はそのネイティブ拡張

FFIで呼ばれるゲーム用のランタイムライブラリバインディング - 要するにSDL2のDLL

のパッケージということになる。

例えばゲームパッドの設定とかセーブデータ管理のようなゲームの共通インフラはランタイムの機能ということでランチャと一緒に配布する方が扱い易い気はする。