define-macroのみ備える処理系のsyntax-rulesサポートは可能か?

追記: Redditの投稿 https://www.reddit.com/r/lisp_ja/comments/8kttfy で、Common Lispでの実装 http://www.ccs.neu.edu/home/dorai/mbe/mbe-lsp.html に言及がある。
追記: (最後の段はあんまり正しくない。 __ でgensymする場合、単一のsyntax-rulesテンプレート中に2回以上 __ が出現した場合は同じシンボルを出力するように出力をキャッシュする必要がある。こういう感じに: https://github.com/okuoku/yuni/blob/e03dc40e14389f9b1b63fd07fbb63ffbfe3fd228/lib-runtime/generic/synrules.scm#L40 )
yuniで提供されるライブラリの殆どはFFIを経由したCライブラリのwrapperになると考えられる。このため、ライブラリはマクロや補助構文をexportしないことが殆どになると言える。この要件の元でなら、define-macro(伝統的マクロ)しか備えない処理系でも自前のマクロ展開器を実装せずにR7RSライブラリやsyntax-rulesを実装できるのではないか。
(以前書いたように、マクロをエクスポートする場合は他のバインディングもグローバルにbindするという制約が必要になる http://d.hatena.ne.jp/mjt/20160825/p1 )

各種define-macro処理系

ここでのdefine-macroはScheme処理系にたまに見られるマクロ機構で、特定シンボルに"変換器"となる手続きをバインドし、ソースコードを実行前に変形することができる。

通常のシチュエーションでは、ランダムなシンボルを生成するgensym手続きとセットで提供される。Gambitやs7では、シンボルがgensymで生成されたものかどうかを判別することが可能だが、BiwaSchemeやbiglooではそうではない。
gensymが追加のオブジェクトを取れるかどうかにも微妙な差異がある。

define-macroでsyntax-rules(もどき)を実現するための制約

s7のように自由に環境クエリが可能なものを除くと、define-macroによる伝統的なマクロは、

  1. Scheme手続きによる式の変形
  2. gensymによる他と衝突しないシンボルの生成

のみが可能となっている。このため、syntax-rulesによるマクロが本来備えている"自動的な健全性"はどうやっても実現できない。なので何らかの現実的な制約を加えてどうにかdefine-macroでも実装可能なレベルに落としてやる必要が生じる。
... これがなかなか難しい。というか、制約として存在して困るケースが全然思いつかないので、何がダメになるのかが分からない。
define-macro処理系ではマクロの定義にdefineだけが使える。つまり、let-syntaxやletrec-syntaxに相当する構文が存在しないということになる。ただ、let-syntaxは通常のletとdefine-syntaxに開くことができるし(たぶん)、letrec-syntaxはそもそもdefine-syntaxでエミュレートできる(letrec*をdefineでエミュレートできるのと同じ)。
健全なマクロの例によく出てくる有名な例としては、"elseのような補助構文を別の意味にbindした場合に正常に動作しない"というケースだろう。でも個人的にはこれを真剣にやりたいケースに遭遇したことが無い(Cとかだと有るんだけど)。define-macroでsyntax-rulesを実現する上では、補助構文(syntax-rulesで与えたリテラル)は字面上での一致しか見ることができないため、補助構文をマスクするような記述はできないことになる。

(define-syntax checkelse
  (syntax-rules (else)
    ((_ else) (display "Is Else\n"))
    ((_ otherwise) (display "Is NOT Else\n"))))

(checkelse else) ;; => "Is Else" が出力される
(checkelse if) ;; => "Is NOT Else" が出力される
(let ((else #t))
 (checkelse else)) ;; => define-macroではこちらも "Is Else" になる。通常の処理系では "Is NOT Else"。


というわけで、健全性は一切気にしない方向でsyntax-rulesを実装してBiwaSchemeで実行させてみた。https://gist.github.com/okuoku/968caa7fa751beb029398a20838fbb24 今回は、chibi-schemeのexplicit renamingマクロで実装されたsyntax-rulesを適当にwrapしている。
(BiwaSchemeはnpmに収録されているので"npm install biwascheme -g"とすることでコマンドライン版biwasがインストールできる。)
要するに、syntax-rulesが単にテンプレートに従った式変形ツールになったとして、現実的な問題で困ることができるだろうか?他のポイントとしては、syntax-rulesのテンプレート内のシンボルはそのまま展開されるため、:

  1. マクロを定義した場所の環境 == マクロを使用した場所の環境 となっている必要がある。つまり、マクロを使用して変数をライブラリからimplicitに持ち出すことはできなくなる。...これは実はChickenのような通常のScheme処理系でもできないことが有るため、移植性という意味ではそこまで大きな問題では無い気がしている。
  2. "束縛されていないものに関してはマクロ展開のたびに異なる識別子が挿入される"要件を満たすことができない。R6RSで言うところの generate-temporaries https://practical-scheme.net/wiliki/wiliki.cgi?R6RS%3A%E7%BF%BB%E8%A8%B3%3AStandard%20Libraries%3A12.7%20Generating%20lists%20of%20temporaries が実装できないということになる。

逆に、generate-temporariesを専用の構文として分けてしまえば十分かもしれない。
例えば、R7RSの例として載っているletrec実装は、define-macroで書かれたsyntax-rulesでは正常に動作しない。

- (10 20)が正解
これは途中のダンプ結果に現われているように、変数"newtemp"が2回のsyntax-rules展開で被ってしまっているため出力結果が間違っている。本来syntax-rulesでは、マクロの展開結果が未知の識別子を挿入する場合は、その挿入した識別子は他のsyntax-rules展開で挿入されるものと絶対に被らないことを保証しなければならない。通常のdefine-macroでは、挿入しようとしている識別子が未知かどうかを知る手段が存在しないため、この制約を良く実装することができない。
もっとも頭の悪い解法は、適当なidentifier-macroを定義してそれをgensymの意味で使うことだろう。つまり、↑の実装でシンボルフィルタとして渡している baselib 手続を

(define (yuni/synrule-baselib sym)
  (case sym
    ((__) (yuni/gensym))
    (else sym)))

のように、ある適当なシンボル(ここではアンダースコア2つ)をgensymの意味になるように設定し、

     (letrec0 "generate temp names" (y ...)
       (__ temp ...)
       ((var1 init1) ...)
       body ...)

のように、新しい識別子が必要な場所で明示的に使うことで、マクロ手続き側で環境の知識を持っていなくても適切にgensymに誘導するようにできる。
これで、letrecも期待通り動作するようになった。


正直ここまでして処理系のマクロ展開器を使いつづける意義ってのが有るのかどうか微妙な線になってきたが、とにかく今のところは受け入れ可能な制約でdefine-macro処理系にyuniを実装することができる気がしている。

ゲーム向けファイルシステムの設計の難しさ

ゲーム向けのファイルシステム抽象化は近年超クッソ激烈に複雑化しており、大分手を付けられない問題になってきた。

ゲーム配布/ストレージ様式の変化


...とにかく歴史的に様々な方式が使用されている。例えば、ゲームを発売後パッチするにはHDDやSSDのようなFSを持った大容量ストレージが必要になるが、それが可能になったのは2001年以降(PS B.B. Unitやoriginal Xboxなどの登場以後)と言える。(サテラビュー('95)やDreamcast('98)のようなプラットフォームでもDLCの類は存在したがゲームの不具合修正をpost releaseで行う目的には使えなかった。)
PS4ではプログレッシブダウンロード(ゲームを複数セグメントに分割し、一部のダウンロードが完了した段階で起動許可)をサポートしているが、導入自体はPS3The Last of Usの方が早い( https://www.theverge.com/2013/5/18/4344260/the-last-of-us-play-while-downloading-ps4-ps3 )。
光学ディスクは非常に長いこと使用されているが、実は光学ディスクから直接ゲームを起動できるのは現行機ではPS4だけで、XboxOneは完全インストールを前提としている。PS4も、ディスクと同容量のHDDをゲームプレイには必要としている。

ゲームストレージの課題

ゲームは大量のデータを消費者に届け、かつ動画のようなストリーミングと異なり複雑なアクセスパターンを持つという点では特異なユースケースと言える。このため様々なゲーム固有の考察がファイルシステムには求められている。
シークは非常に古典的だが現在も問題になる。CD-ROMゲーム機の登場以降、ゲームはCD/DVD/BDのような光学ディスクで配布されるだけでなく、ダウンロードゲームも結局はHDDにインストールされる可能性があるためよく使用されるデータはコピーしてでも近傍に配置する等の配慮が必要になる。
"スクールガールストライカーズの内製クライアントエンジン"( http://www.jp.square-enix.com/conference/2014/technical_seminar/img/pdf/SQEX_DevCon_sugimoto.pdf )は既に4年も前の講演だが現代でも有効な内容で、この領域にいくつかの知見を提供している。スクールガールストライカーズのようなid引きおよびオンメモリ配列によるによるファイルアクセスは多くのゲームで伝統的に使用されており、パッケージ化されたミドルウェアとしては例えばファイルマジックPRO( http://www.cri-mw.co.jp/product/cs/filemajikpro/index.html )が有る。
(モバイルデバイスではフラッシュメモリを前提として良くなったため、データのコピー等を伴うシーク最適化はほぼ不要になった。しかし、プラットフォーム/OSのファイルアクセスは依然非効率なケースがあるため特に理由の無い限りはパックファイルを採用する方がパフォーマンス上は有利なことが多い気がする。)
パッチはシークに比べると最近出てきた問題だがどのプラットフォームでも等しく問題になっている。例えば、UWPではパッチのダウンロードサイズを減らすために以下のようなアドバイスをしている: https://blogs.msdn.microsoft.com/appinstaller/2016/12/03/differential-updates-for-uwp-apps/

  1. パッケージに含まれるファイル一つ一つのサイズを小さくする
  2. ファイルの変更ではなく、ファイルの追加を行う
  3. ファイルを変更しなければならない場合は変更を64KiBのブロック単位にする

そもそもUWPではファイル単位でのパッチを行うが、Unreal Engineのような通常のゲームエンジンはファイルをpackしてしまうためUWPのパッチ戦略はあまり上手く適合していない。もし本当にパッチを効率的に配信する必要があるならば、パックファイル内のファイルを64KiB境界にアラインする必要がある。
...当然このアライン単位はプラットフォーム毎に異なり、例えばSteamの場合は128MiBになっている。Androidはbsdiffを採用しているためアライン単位は存在しない https://developers-jp.googleblog.com/2016/12/saving-data-reducing-the-size-of-app-updates-by-65-percent.html (が、そもそもサイズ制約から.apkでゲームアセット全体を配布するのはあまり現実的でもない)。
大きく分けて、パッチには2戦略存在する:

  1. 完全適用型: ネットワークからダウンロードしたパッチファイルを元にストレージ上のパッケージを書き換えてしまうもの。ユーザはパッチの適用処理を待つ必要がある。UWPやSteam等プラットフォームが提供するパッチシステムはこちらを実装している事が多い。
  2. オーバーレイ型: パッチではファイルの追加を行い、ゲーム側でファイル読み取り先を置き換えるもの。前出のファイルマジックPROはこちらを実装している。適用コストは安いがパッケージの占める容量は膨張していくため大きな修正には向かない。

ゲームエンジンから見た場合、これらのパッチ戦略はプラットフォームが提供するものに従うしかないためなかなか匙加減が難しい。ファイルを一切パックせずにシステムが提供するファイルシステムに全てを任せるのが最も(パッチという面では)効率的になるが、パックファイルを使用したアクセス面での効率化を図ることができないことになる。
プログレッシブダウンロードは新しい話題で、ゲームを完全にダウンロードする前でも起動できるようにし、必要なアセットをOn Demandで供給したり不要となったアセットをデバイスから削除して容量を節約できるようにする。この分野ではiOSのOn demand resourcesが必要な機能性をよく要約している https://developer.apple.com/jp/documentation/FileManagement/Conceptual/On_Demand_Resources_Guide/index.html
iOSのOn-demand resourceではファイルを"タグ"に分類し、アプリケーションからは必要なタグを都度宣言させる方法を取る。タグに対応したファイルのダウンロードとマウントおよび削除iOS側で自動的に管理される。
プログレッシブダウンロードの難しい点はデバッグで、ネットワーク処理はiOSが代行するもののエラー等に関してはアプリケーション側でハンドルする必要がありアプリケーション実装の複雑さを増加させてしまう。しかもプラットフォーム要求なので使わないわけにも行かないという問題がある。(マルチプラットフォームでのリリースを考えると、デバッグのことを最初から考慮して自前のシステムを組んだ方が楽だが。。)

どこまで抽象化できるか

とりあえず現時点では、UnityやUnreal Engineのような統合型のゲームエンジンを採用するプロジェクトではあんまりチャンスが無い。これらのゲームエンジンでは自前のアセットパイプラインを前提に置いており、エンジンのユーザには細かい制御を提供していない。

Unity公式の仕組みには細かくファイル(アセット)を「管理する」という設計思想がありませんので、Unityなら共通であろうという処理が作れないためです。

Unityのシーンシステムを使わず、ゲーム内の複数シーンを単一シーンに纏めるといった工夫をするケースも有るが、この"宴"のケースのようにゲームエンジンゲームエンジンを載せてしまうといった解法も有る。(ただし、Unity 2018からAddressable Asset System https://www.slideshare.net/UnityTechnologiesJapan/unite-2018-tokyo-96499382/UnityTechnologiesJapan/unite-2018-tokyo-96499382スクリプト可能なアセットパイプラインでこれらの問題への対処を始めた。)
クライアント実装の基本的な指針としては:

  1. 可能な限りOSのファイルシステム機能を直接使用しない。例外: プログレッシブダウンロードなどプラットフォーム機能が必要な場合。通常、ミドルウェア類はファイルシステムインターフェースを外部から与えられることが多い。通常のシチュエーションではstdioのような標準ライブラリが使用できなくなるのに注意する。
  2. 階層化ファイルシステムを期待しない。実はパスの解決は重い操作で、ファイルシステムによってはシークが必要になる可能性がある。例えば"Assets/a/b/c.png"のようなパスはディレクトリをAssets→a→bと辿る操作が入る可能性がある。一般的には、ファイルをpackすることで階層化ファイルシステムのオーバヘッドを削減していることが多い。
  3. "カレントディレクトリ"を使用しない(変更しない)。過去にはWinCEのようにカレントディレクトリのコンセプト自体が無いシステムも存在したが、殆どのシステムには依然カレントディレクトリのコンセプト自体は存在する。しかし、packファイル上に実現されるストレージでカレントディレクトリをエミュレーションするのは非効率と言える。また、カレントディレクトリはプロセスグローバルなので、スレッド環境下では良く使えない。"Multithreaded applications and shared library code should not use the GetCurrentDirectory function and should avoid using relative path names. " ( https://msdn.microsoft.com/en-us/library/aa364934%28VS.85%29.aspx )
  4. 可能な限り文字列パスではなくIDベースのアクセスを採用する(= パスオブジェクトを抽象化する)。ただし、これが有効なシチュエーションは非常に限られている(自前のアーカイブシステムを実装する余地があるケースくらい)。Addressable Assetのように文字列をファイルの識別子として使用できることが多く、ミドルウェア類も通常は文字列でのファイル指定を期待していることが多い。
  5. ファイルがダウンロードされていない可能性を考慮し、ファイルのavailabilityをクエリするインターフェースを用意する。例えば、iOSのOn demand resourceではタグの単位でファイルのavailablityが変わることになる。

エンジン実装時のポイントになり得るのは、良いプロファイラを実装することと、プラットフォームのパッチ/ストリーミング戦略に良く適合することと言える。ファイルアクセスのプロファイラは一般的な機能になりつつある。例えばゲーム起動時にアクセスされるファイルをpackファイル中の近所に配置することでシーク距離を減らしHDDインストールでのパフォーマンスを向上できる。
プラットフォームのパッチ/ストリーミング戦略への適合は今のところ良い指針が無いが、現状のプラットフォームではiOSのOn demand resourceが最も保守的な実装となっているため、そこに合わせるのが良いと考えられる。

QKK 800x480 小型プロジェクタ

Amazonで9800円、プライム会員タイムセール割引で購入。パネル解像度800x480、LED光源の小型プロジェクタ。
2200ルーメンを謳ってはいるけどNP-L50WJDの500ルーメンに比べても明かに暗い。まぁ基本的に部屋を真っ暗にして使うものなので昼間使うなら部屋に遮光カーテンなり必要
執拗に製品レビューを書くように迫っているのがちょっと面白いと思った。(購入するとAmazonマーケットプレイスのメールシステムを通して直メールが来て、キャリングケースプレゼントキャンペーンの告知とレビューを書く依頼が載っている。)

製品同梱のカードにはフローチャートまで載っている。

良い点

やっぱし大画面で映るのは面白い。映像ソースが手元にあって正常に動作する品が届けば確実に値段ぶん(これでも1万円未満)は楽しめると思う。以下その辺の壁に映して60インチ相当で使用。...カメラにとっては暗いので写真に撮るとかなりピンボケになってしまうが、実際にはドット感が気になるレベルでクッキリ映っている。

  • 地味にHDMI CECに対応している。SwitchとかPS4のような対応機器であれば電源ONは連動可能。ただ、OFF側はうまく動かず。
  • 電源内蔵。いわゆるメガネケーブル一本で電源供給できるのは便利。これは以前使っていたNP-L50WJDでも一緒だけど。
  • ちゃんとドットが見える程度に合焦する。以前のこの手の製品は中央にしか合焦しないとかかなりフォーカスが甘いのが普通だったけど、特にピントを気にせずゲームができた。

手元ではAmazon FireTV Stick(初代)、Switch、PS4を接続して特に遅延等気にせずゲームができた。
あと、DLPプロジェクタと違ってこれは(たぶん)単板LCDレインボーノイズの類いは一切ないのでアクションゲームのような動きの大きなソースでも特に違和感はない。

  • 残像感はそれほど気にならない。写真では出てるけど。

  • よく見ると縦方向にRGBになっているのがわかる (あんまりピントが合ってないが、実際はドットが見える)

気になったところ

とにかく光学系が簡素なので、その辺のLED + DLPプロジェクタに比べるとどうしても"焦点距離が遠く"、"暗い"。以前使っていたDLPプロジェクタは部屋で使うと迷惑なくらい大きく映っていたが、コレは相当離す必要がある。またパネルの開口率がちょっと低く、格子感が有る。
説明書では800x600等の解像度を薦めているので、低めの解像度を使った方が良い結果が得られるんじゃないかという気がするが、どうも手元では逆の結果で、デバイスには1080pで出力させた方が良い結果だった。

  • 1080p

  • 720p


Switchの画面サイズ調整を100 %にして、1080pと720p出力を比較してみた。1080pはちゃんと四隅が描画されているが、720pではちょっと切れてしまっている。というわけで表示部分を活かすには1080pを指定した方が良いんじゃないかと思われる。
アナログ入力、内蔵メディアプレーヤ、内蔵スピーカーのSRS TruSurroundは試していない。モノラル機器でSRS TruSurround..? というか外部出力の類いが無いのかコレ。。Switchは本体に直刺し、PS4はコントローラ側にイヤホンを接続することができるので、一応それで音を聴くことはできるわけだけど。
全体に、購入前は800x480の解像度って何の役に立つのかという感じだったけど、冷静に考えるとHDMIの最低解像度だって480pだから一応大概の機器やコンテンツは480pでも文字とかは読めるように作ってあるわけで。。思ったより実用的という感想を持った。

Irrlicht EngineをXbox OneのUWPに移植してみた

以前用意したビルドシステムを整理してゲームエンジンであるIrrlichtをXboxOneに移植してみた。

Irrlicht( http://irrlicht.sourceforge.net/ )は2000年代前半から開発されているゲームエンジンで、最近DX8のサポートは切ったもののソフトウェアレンダリング、スレッド不要といった古いハードウェア向けの特徴を色濃く残している。残念ながら商用ゲームでの大きなdesign winは2010年のOctodad以降無いようだが、シーンローダのような必要最低限の機能は備えている。
今回はそのソフトウェアレンダラの活用を目指して、XboxOneに移植してみた。が、FullHDを描画するにはちょっとパフォーマンス的に厳しいという事情が有り一旦OpenGL ESバックエンドを使用してANGLE上で動作させた。

移植/ビルド戦略

Irrlichtにもプラットフォームレイヤは存在するが、今回は移植層としては完全にSDL2に依存し、SDL2プラットフォーム一般で使用できるようにIrrlicht側を改造する方針を取った。SDL2側のプラットフォーム層はそれなりに使用実績もありある程度動作に確証があったのと、Win32ネイティブ版と可能な限りコードを共通化したかったため。
ビルドは今回もOSライブラリ以外の全てを静的リンクする方針としている。これはVisual Studioのgraphics debuggerを使用する上でDLL側に描画を追い出すと何故かあんまり安定しなかったのと、最終手段としてのLTCG(Link Time Code Generation -- いわゆるリンク時最適化)の可能性を残しておきたかったことに因る。
描画にはANGLEを使用する。Google本家のANGLEは今年に入ってからGLES1の実装が始まっているものの、まだ実用できる段階には無いのでGLES2フロントエンドを使用している。残念ながらGoogle本家のANGLEは依然UWPビルドが壊れているのでビルドシステム側でMSの実装を元に修正している。
SDL2では、SDL2の各APIを静的リンクするのを明示的に禁止しており、通常はSDL2を共有ライブラリとしてビルドするように要求している。このため、ソースコードを騙す方向で#defineを入れ https://github.com/okuoku/sdl2-static-cmake/blob/ee04bc39448c086d723ecc338e65c8cb81b81f2d/CMakeLists.txt#L28 ている。
Irrlichtの側はGLESブランチを使用している。GLESブランチは2008年から開発されているものの依然trunk側にはマージされておらず、trunk側のコードではGLES1/2バックエンドは使用できない。
ビルドには今回もCMakeを使用している。Irrlicht側のブランチ事情もあり、今回はIrrlichtのMakefileをCMake上でパースしてビルド用のデータを生成する( https://github.com/okuoku/irrlicht-cmake/blob/ffc3bf2e950e5ab6e81f744cac345507887240a0/CMakeLists.txt#L40 )方針としている。これを行うことで、ファイル構成が異なるtrunkとOpenGLESブランチの両方を1つのCMakeLists.txtでサポートすることができる。当然、Irrlicht側のMakefileの書き方には依存してしまうが。。
SDL2サポートはIrrlichtに元々存在するSDL1サポートを元にSDL2向けに改造している。IrrlichtではOpenGLコンテキストの管理を自前で実施している(CEGLManager、CWGLManager 等)が、これはSDL上では不要なので特に何もせずflipのみを行うCSDLContextManager( https://github.com/okuoku/irrlicht-generic-sdl/blob/af608e1362f7d50fce3846cf43f023fc9cbd6680/source/Irrlicht/CIrrDeviceSDL2.cpp#L38 )を用意している。
IrrlichtはSDLをサポートしているものの、特に純粋なSDLプラットフォームというのを想定していないため、GENERIC_SDL1_PLATFORMとGENERIC_SDL2_PLATFORMを導入し、可能な限りSDLの移植層を使用したい場合はそちらを定義するというルールにした。UWPはWin32のある種サブセットであるため、いくつかのWin32にあった機能が欠けている。

パッケージング

パッケージにファイルアセットを含める上手い方法はよくわからなかった。CMakeでは.appxmanifestやリソースファイルもexecutableのソースとして追加する方向としており、更にそれらのソースコードプロパティを適切に設定することでリソースファイルをアセットとしてパッケージに含めることができるようになる。

これらのドキュメンテーションは見当らなかったが、CMakeのテストにはUWPアプリをビルドするVSWinStorePhone( https://gitlab.kitware.com/cmake/cmake/tree/ccd17a557cbf8ada18207a72eea78d2adcc9d752/Tests/VSWinStorePhone )があり、それを参考にした。

さよならcall/ccとwith-exception-handlerやguardへの置き換えの検討

yuniではfull-continuationの提供を止めることにした。単純な理由はKawaがこれを提供していないことだが、じゃあこれに代えるプリミティブを何にするのかという点がちょっと悩ましい。
call/ccに対する批判はOlegのページが詳しい( http://okmij.org/ftp/continuations/against-callcc.html )。要するに現実的なプログラムで使うのは難しいという話で、実際継続を介したポートI/OやFFIは難しい。yuniやその前身で書かれたSchemeプログラムはそれなりの量が有るが、全てone-shotな継続しか使用しておらず、full-continuationが真に必要なケースは無い。
Rubyはcall/ccを1.9でContinuationライブラリに移し、

非推奨になりました。代わりにfiberを使ってください。

と地獄のようなことが書かれている(Fiberって継続の替わりになるの?)。
yuniも同様にcall/ccを(yuni continuation)あたりのライブラリに追い出してオプショナルにすることを考える。ただ、今のところ、yuniがサポートするScheme処理系で"call/ccが無いことを前提にパフォーマンスメリットを提供している処理系"は存在しないようだ。

with-exception-handlerとguardのセマンティクス

当のKawaはcall/ccをJavaの例外で実装している。

Kawa continuations are implemented using Java exceptions, and can be used to prematurely exit (throw), but not to implement co-routines (which should use threads anyway).

また、KawaはguardなどのSchemeの例外機構をJavaの例外機構にマップしている。

The Scheme exception model is implemented on top of the Java VM’s native exception model where the only objects that can be thrown are instances of java.lang.Throwable. Kawa also provides direct access to this native model, as well as older Scheme exception models.

実際、C++JavaScriptのように言語のコア機能として例外機構を実装しているケースは多いため、これらと良くマッチするならばSchemeの例外機構はプリミティブとして適切だろう。ただちょっと難しいのは、Schemeの例外機構のセマンティクスをより理解して進めないと危ない気がしている。...つまりyuniの場合10近くあるScheme処理系でちゃんと挙動が一致するのかどうかを比較的真面目に気にする必要が出てくることになる。
(正直、Schemeの例外手続きの使いかたを覚えるのが面倒だからcall/ccで書かれているコードがそれなりにあるので、それらをguardなりwith-exception-handlerなりに書き換えたときに挙動を変えない自信が現状あんまり無い。)
Kawaの場合、guardはwith-excpetion-handlerより制約のあるプリミティブで、guardの方が効率的としている:

Performance note: Using guard is moderately efficient: there is some overhead compared to using native exception handling, but both the body and the handlers in the cond-clause are inlined.

(with-exception-handlerはlambdaの分のコストがある -- はずなんだけど上手いこと逆コンパイルできなかったので調査できていない。ToDo。)
ただ、通常の処理系ではwith-exception-handlerをプリミティブとして使用している。例えば、Gaucheのpull request https://github.com/shirok/Gauche/pull/335 は興味深い調整で、Gaucheに元から有ったSRFI-18の with-exception-handler とR7RSの同名手続きの挙動差への言及がある。
guardとwith-exception-handlerはどちらも例外ハンドラを表現するが、その差については最終的なコミットのドキュメントに要約されている:

(SRFI-18のwith-exception-handlerについて、)
例外がraiseやerrorで通知されると、投げられたコンディションを引数としてhandlerが呼び出し元と全く同じ動的環境で呼び出されます。つまり、handler中でraiseを呼び出すと、再びhandlerが呼ばれます。また、handlerから戻ると、制御はraiseの呼び出し元に戻ります。

この振る舞いはSRFI-18により定義されました。これは、この手続きが例外制御の最もプリミティブな構成要素になることを意図しています。例外処理中にアクティブなハンドラを切り替えたければ、自分でそう書く必要があります。

通常、例外処理中に例外が発生したら、それは「外側の」ハンドラで処理したいでしょう。そういった典型的な使い方には、guardを使ってください。この手続きはあくまで、例外処理の最も低層にアクセスしたい時のみ使うとよいでしょう。

R7RSにも同名の手続きがありますが、一つだけ違いがあります。R7RS版はhandlerを呼び出す前に現在の例外ハンドラを一つ「外側」の例外ハンドラに置き換えます。R7RSのwith-exception-handlerの説明はR7RS base libraryを参照してください。

このR7RS(= SRFI-34)とSRFI-18の差は https://github.com/shirok/Gauche/commit/234d0ef154ade1983283ece93f52d462d7833ca4#diff-fb36b06efa4fdb8b419faa6d294f0e0aR350 のコードでSRFI-18 with-exception-handlerを元にr7rs:with-exception-handlerを定義することで吸収している。
Gaucheでは例外ハンドラを戻す挙動をwith-exception-handler内に実装しているが、SRFI-34ではraiseの内部に実装しているという微妙な違いがある。

;; (注: raiseの内部で呼ばれているのはwith-exception-handler"s"であることに注意。
;;      参照実装はwith-exception-handlerを実装するために、例外スタックを直接
;;      設定できるwith-exception-handlersを定義して使っている)
(define (raise obj)
  (let ((handlers *current-exception-handlers*))
    (with-exception-handlers (cdr handlers)  ;; ★ 例外スタックから現在のハンドラを除く
      (lambda ()
        ((car handlers) obj)
        (error "handler returned"
               (car handlers)
               obj)))))

guardの実装にcall/ccが必要な問題

では処理系はwith-exception-handlerさえ提供すれば十分なのかと言うと、実はそうでもないという問題がある。guardの実装にはcall/ccが必要になる

(define-syntax guard
  (syntax-rules ()
    ((guard (var clause ...) e1 e2 ...)
     ((call/cc ;; ★ guardの動的環境のキャプチャ
        (lambda (guard-k)
          (with-exception-handler
            (lambda (condition)
              ((call/cc ;; ★ re-raise用にexception-handler用の動的環境をキャプチャ
                 (lambda (handler-k)
                   (guard-k
                     (lambda ()
                       (let ((var condition))
                         (guard-aux
                           (handler-k
                             (lambda ()
                               (raise-continuable condition)))
                           clause ...))))))))
            (lambda () ;; with-exception-handlerで囲まれたbody部分
              (call-with-values
                (lambda () e1 e2 ...)
                (lambda args
                  (guard-k
                    (lambda ()
                      (apply values args)))))))))))))

この"動的環境のキャプチャ"が曲者で、基本的に他でcall/ccが使われることを想定する限りはcall/ccをここでも使わざるを得ない。例えば、Chezはcall/ccの他にone-shot continuationのためにcall/1ccを持つ( http://cisco.github.io/ChezScheme/csug9.4/control.html#./control:h3 )が、guardの実装には通常のcall/ccを使っている https://github.com/cisco/ChezScheme/blob/75107ee73f3619e1afcf043822cf5fbf675522e3/s/exceptions.ss#L254
通常のシチュエーションではguardやその各clauseの継続から複数回返ることは無いが、guardの内部でキャプチャされた継続から複数回返る場合に問題になる。
yuniでは一部の処理系で自前のexpanderを実装している関係上、移植レイヤに構文を使わないのが原則なので、guardの内部で使用されるcall/ccも何らかの形で抽象化したい。。もちろん、guardが構文である所以は単に変数のバインディングを行っているという特徴だけなので、define-record-typeのように特別扱いしても良いのかもしれない。(R7RS small(= SRFI-9)のrecordには手続きレイヤが無いため、expanderはdefine-record-typeの知識を持っていないと実装できない。つまり、letとかlambdaのようなコンパイラプリミティブと同等の扱いがdefine-record-typeには必要となってしまっている。)
逆に言うと、guardはR7RSの標準構文で唯一call/ccを内包する構文になるため、これさえ特別扱いすれば移植性要求からcall/ccを一掃できる。
ちょっと気になるのは、では、guardやwith-exception-handlerの存在を仮定した場合、内部的にはネィティブ例外で実装されているKawaの継続キャプチャを表現することは可能なのかという点。つまり、call/1ccはguardやwith-exception-handler、dynamic-wind等の自然な例外プリミティブで実装可能か?という問題が考えられる。まだあまり真面目に考えていないが、C++とかJavaScriptのようなネィティブ例外機構を持つ言語ランタイムの上にSchemeを実装する場合にcall/1ccが自然に実装できると言えるならば、call/1ccは良いプリミティブになり得る気がしている。

キラッとプリ☆チャン はどうなるのか

Switch版のプリパラを速攻で買ったのは、1.0.0がヤバいというだけでなく、これが初のキラッとプリ☆チャンコンテンツであるという理由もある。
来週からはアニメ等の展開が始まってしまうのでゼロ付近から予測を書ける最後のチャンスということで。。答え合せは来年。

プリパラとは

"プリパラ"は、簡単には"ガチャ要素のあるアイドルゲームを女児向けアーケード筐体にしたもの"と要約できる。基本的にメディアミックス前提で設計されアニメ等も放映されている。収集対象はアイドル自体ではなく、自分の分身である"マイキャラ"に着せるコーデ(衣装)となる。通常は4スロットあるため最低でも400円(ボトムスとトップスの複合アイテムであるワンピースを使う場合は3スロットになるため300円)掛かる。ただし収集した衣装は何度でも再使用可能なので1プレイあたりの最低コストは100円になる。

ガチャなので排出されるコーデ(衣装)はランダムであり、当初は自由に貸し借りする事さえできなかった。このように厳しい仕様でも400万IDを超えるヒットになっている。仕様上複数キャラクタの同時進行が非常に容易なため単純にプレイ人口と考えることはできないが、単にアーケードゲームとして見ても相当なヒットと言える。
我々大人は無限に課金して高レアリティのコーデを集めることができるわけで、逆に低レアリティ、かつコーデの揃わない状態でどういうプレイになるかをSwitch版で試してみた。最高レアリティの衣装は4000点を超えるが、動画では全て3000点未満にしている:

...その辺の小学生かよ。実際には2年目以降では200円でフルコーデが揃うドリームシアターが実装されたり、3年目で自由に衣装を貸し借り出来るようになる等敷居を下げる方向に向かっては行ったが。。初回のみ自動的に適当なコーデが選ばれるが、以降のプレイでは相当額の課金をしないと公式サイトのイメージのようなプレーはできない。(ゲームではアニメキャラクタも選択できるが、その衣装は別途収集の必要がある。)
基本的にプリパラは、"ムシキング"→"オシャレ魔女ラブandベリー"(いずれもセガ)の流れを組む女児向けのアーケードゲームで、新開発のプリンタ筐体(プリチケ筐体)によるビジュアルコミュニケーション要素を最大の特徴とする。大人がtwitterなりなんなりにスクリーンショットをアップロードするのを、印字されるカードで代行している。

印刷された「プリチケ」は、折ることで、自分のアイドル・マイキャラの服装の情報、コーデが記録された「マイチケ」と、交換することで自分のマイキャラがほかの子のライブに出演することのできる「トモチケ」に分かれる。

プリパラの素晴しい点は"み〜んなトモダチ、み〜んなアイドル"という一貫したコンセプトを実現しつづけた点で、プレスリリースでは

“普段は普通の私でもキラキラのアイドルに変身できる!”という女の子の永遠の憧れをかなえるコンテンツです。

のように語りつつ、男の娘アイドルに多数の男性ファンを付けるなど実績を残した。個人的にはプリパラは(日本のベンダによる)アニメ・クリエイティブの青春と言って過言でない成功を収めたと評価していて、次世代のコンテンツであるプリ☆チャンがこれを超えるのは非常に難しい問題になると感じている。

勝算がわからないプリ☆チャン

キラッとプリ☆チャンは、前作プリパラのプラットフォームを継承しつつゲームシステムを刷新したコンテンツとなっている。システム上は前作プリパラからの正当進化と言って良い内容で、

  1. ネットワーク機能の強化。前作では他人のゲームにマイキャラが出演した場合に、そのカウントが表示されるだけだった。本作では地域等を含めたより詳細なアナリティクスやレポートを含むようになりネットワーク中でのプレイ動向を知ることができるようになった。
  2. 印刷機能の強化。なんとプリンタを追加した。。
  3. 演出の強化。プレイ中にメッセージ性のあるパートは、プリパラではランダムに選択される"メイキングドラマ"が1度あるだけだったが、おそらく、他の演出(ランウェイ/メンバーアピール)を置き代える形で3度に増やされた。

... のようにゲームがかなり強化されている。
が、そもそも現実にできることのゲーム化を子ども向けに展開してヒットさせるのは非常に難しい。現実のSNSは子ども向けにパッケージはされていないものの、ゲームでやらなくても実際にやれば良いというポイントがどうしても出てきてしまう。
ポケモンが現実の虫取り遊びをパッケージしてゲームにしたように、おそらく、プリ☆チャンもポケモンにおける"対戦"のように現実では難しいものを何か提供するのではないかと思うが、それが見えてこない。スーベニアとしてのカードの魅力、定評のあるキャラクタやコーデや楽曲の品質、店舗大会のようなローカルネットワーク、..? とにかく、プリ☆チャンの企画の勝算として、何か現実では難しい内容を持っているはずで、それが気になっている。
ちなみにアニメの主人公はプリパラの小学生からプリ☆チャンでは中学生に変更している。先のインタビューで、

小さい子にとって、小学生と中学生の差は大きいですよね。その成長の部分がキャラクター性に影響を与えてしまうと各キャラクターの個性や魅力をスポイルするのではないかと思いました。まあ、もともと年を取らせる必要はあったのかなって話にもなりましたが(笑)。議論を重ねに重ねた結果、やはり小学生のらぁらが「かしこまっ!」と言いながら、年上の子を引っ張っていくのが『プリパラ』なんです。

としていて、この後のアニメではシナリオにおける小学生の比重が高まったり(神アイドル編)、そもそも主人公チーム全員を小学生にしたり(アイドルタイム編)と振っていたのを大きく揺り戻している。
現状、プリパラからは別のものを提供しようという意思は感じるものの、コアのコンセプトを "認められる満足感" という外部観察できないものに据えてしまっている。このため、そこに至るまでの過程が欠落しているように感じられ、プリパラの "アイドルになれるゲーム" というコンセプトの解りやすさから比べると大分距離がある。
これら各種の不連続な変化は原点回帰によるクリエイティブ規模の縮小を狙っているのではないかと思う。プリパラを現実に再現する事業、つまり、声優ミュージカルをやり、リアル店舗を何店舗も構え、ライブも展開するような多数のリスクを抱えた構造を一旦リセットしてコンセプトに共感する仲間を増やす時期を取っているように見える。前身のプリティーリズムがリカちゃんの文法を採用していてプリパラではそこから離れたように、プリ☆チャンでもプリパラの文法から離れる必要があったのではないだろうか。

展開の予測

プリ☆チャンのアニメ/ゲーム投入は4月で、これは準備期間を経て7月に投入されたプリパラに比べると相当な垂直立ち上げになっている。今回は筐体の刷新等も無いためプリパラのようなスタートダッシュは狙わず、定期的に話題を提供して都度新規プレイヤを獲得する方針(2年目以降に必要なモメンタムを1年掛けて獲得する)に見える。
間違いなくVTuber類はシナリオに出てくるだろうと言える。プリパラ時代もボーカロイドを模したボーカルドールが初期のシナリオで重要な役割を演じ、後のシリーズの根幹設定としても女神/精霊の形で入っている。問題は人間のYouTuberに比べてターゲット層での認知が弱いのと投入タイミングが難しいところで、文化的にいろいろ出揃った今のタイミングからデザインを詰めて半年後〜つまり年末とかになってしまう。これはモチーフに選んだ "自分発信" の構造上の問題で、プリパラのように"重い"クリエイティブで(現状の自分発信という面で大きな比重を占めている)ミームの変化に付いていくのは非常に難しい。
(プリパラの展開は楽曲やアニメ、製品のリンクなど事前に事業計画を用意する必要があるもので占められている。このため、一般的なソーシャルゲームのような短いスパンでアップデートする構造を持てない。ただ、事前のコラボ計画を要求するのではなく筐体でJANコードをスキャンさせてリンクするまたは画像認識等の手法でコラボを後付けできる可能性はある。)
何か現実に"やってみた"できる項目を用意する必要がある。たぶんいくつか偉業(実績/トロフィー)を用意してそれを実際のプレイヤに課すだろう。例えば、プリパラにおけるご当地アイテムのように店舗限定要素を用意し、東京のプレイヤに"広島県で人気上昇中!"のように告知した上で実際に出向かせて特典を与える等。ターゲット層には難しくても、そのフォロワーにはかなりの割合で大人が含まれるので実際にやるのが0.1 %未満でも十分と言える。プリパラにはプレーヤーランクが存在し、アニメでもその最高ランクである"神アイドル"を目指すのが当初のストーリー根幹とされたが、実際のゲームでは累積スコアで決定される廃課金プレーヤという意味合いでしかなかった。実績システムはその点を補強できる。アニメやライブなど現実のイベントもフォローできるようにする可能性がある。実績システムよりも手間は掛かるが。。
筐体外でのタッチポイントを増やす必要が有るが...良い場所が無い。チャットボットやスマートスピーカ類。。?プリチケの優秀な点はそれ自体がタッチポイントであることで、デジタルデバイスには置き代えづらい。アニメキャストが他のプレーヤを品評する番組を制作する等ここに関してはコストを掛けるしか無いんじゃないだろうか。。プリパズや海外で展開しているようなスマホアプリ類は正直キーになるような可能性を感じない。祖父祖母世代が応援できるような仕組は有っても良いと思うがリスクに見合うかは何とも言えない。
新規に導入された会員証に加えて、ドリームシアター相当のライブでチーム証を発行するだろう。会員証は何度も使用されるのでそこにプレミアムを載せることは価値が高いし、そもそもペアライブで既に相当するもの(= ペアプリチケ)を発行している。通常のプレイで作られるephemeralなチームとの差別化は悩ましいし、チームチャンネルのようなものを作らせてもゲームデザインを難しくするだけなので何か別の形で絆を感じさせるものを用意すると思う。例えばマイルームを用意してそこに招待する等。
おもちゃ展開は。。この内容でマイク売るとも思えないので。。マスコット一切無しはシリーズの過去に例が無いのでどこかのタイミングで出すのかもしれないが。。(プリパラと違いリズムゲームシーンではマイクを持たなくなった。)
アニメの方はそれこそ日記のようなどうでも良い内容から宇宙創成まで大小さまざまなスケールの"やってみた"が登場するのではないだろうか。...シリーズ通してチアリーダー常に出てるなという気はしなくもないが、そこは作品のカラーということで残したのだろう。キャラクタは色シリーズで、紫というか前作らぁらの色が空いている。といっても1年目でいきなりクロスオーバされても困る(映画で十分)わけで新キャラクタ用に使われるだろう。
... で、制作側が勝算を見出すような要素がこの中に有るかというと。。ターゲット層にとりこのゲームで疑似体験できる体験のうちもっともコアになるのはおそらく"初めて実際の人間からネットワーク越しに自分が評価される" という体験で、それを具体的なコミュニケーションなしでどうやって実在感を伴って実現できるかに鍵が有るのではないかと思う。ゲームシステム上の数値で表現される"ファン"と実際にチケットを交換した具体的な人間の間には、マジックテープでくっついた野菜を切るおもちゃと、実際に料理ができるママレンジくらいの違いはある。
なので個人的には、"本物の人間がシステムの向うにいること"にフォーカスすると予想している。従来のプリパラは"システムでーす"というセリフを話すゲームシステムを擬人化したキャラクまで用意して、どこか不気味なまでにシステムを強調していたが、その点を180度転換するのがプリ☆チャンなのではないか。

SDL用の1ソース完結OpenAL実装 MojoAL

久々に新しいOpenAL実装が現われた気がする。

MojoALはSDL作者のRyan C. GordonによるステレオOpenAL実装で、1ソースでOpenALの実装に必要なチャンネル管理やステレオミキサを実装している。もともと氏はLokiでSDL関連の開発をやっていて、そのLokiはOpenALの開祖でもあるので、ある意味本家筋から追加の実装が出てきたと言える。(今普及しているOpenAL-SoftもCreativeがメンテナンスしていた参照実装ベースなので本家筋に相当する。)
オープンソースOpenAL実装には既に多機能なOpenAL-Softが有るが、これはLGPLなのでプロジェクトによっては使いづらい。またSteam RuntimeにもOpenAL-Softは含まれているが激烈に古く( https://github.com/ValveSoftware/steam-runtime/issues/18 )、あまり良いWindows向けのディストリビューションも無い。
OpenALは元々OpenGLのようにIGDを提供してハードウェアベンダがそれぞれのハードウェアに最適化したOpenALドライバを提供できるように配慮しているが、それを実際に行ったのはCreative(とnForce時代のnVidia)くらいで、結局のところ単なるオーディオAPIとして使用されることが多い。
MojoALで興味深いのはマルチスレッド安全であることを念頭に置いている点で、ソースコードのコメント https://github.com/spurious/mojoAL-mirror/blob/a0d889c447a1f4e1e4dabedf63ad1e45722c5789/mojoal.c#L51 でポイントを解説している。

SDLが提供するオーディオI/Fは"pull型"、つまり、オーディオデバイスが再生を進めるにつれ、非同期のコールバックが呼び出されてアプリケーションに新しいPCMデータを要求する。対して、OpenALは"push型"で、アプリケーションは適当なタイミングでオーディオを生成し、再生デバイスに対してpushする形になっている。
MojoALはこのギャップを埋めてSDL上でpush型のAPIを提供する。Waveファイルを再生するといったハイレベル機能は提供しない。(これは既にSDL_Mixer https://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_frame.html が有る)

各種OpenAL実装

現時点で気にする必要のあるOpenAL実装は3つ:

これにMojoALも加えて良い気はする。
Creativeはここ数年ハードウェアアクセラレーションに対応したOpenALバイスを開発していない。
VR/xRの普及はOpenALのようなAPIに良い影響を与える気がするが、実際にはOculus等のSDKは専用のAPIを提供しており、従来OpenALを統合用のAPIとしていたBlueRippleも専用のC++ API( http://www.blueripplesound.com/products/rapture3d-universal-sdk )を販売している。AeonWaveも去年発表した3.xではOpenAL実装を更新しなかった ( http://www.adalin.com/ )。
...というわけで世間の立体音響への盛り上りとは裏腹に、OpenALのようなAPIはあまり盛り上りを見せていないのが現状と言える。