コンピュータやソフトウェアのあれこれ@道民(&元道民)
JS
[JS][node]Connectのソースを読む(2)
12月 25th
前回の続きで、Connect 1.8.4のソースを読む。今日はミドルウェアから適当に抜粋。残ってるのはミドルウェアだけなので、今日で終わりのつもり(気が向いたら残りも書くかも?)。
middleware/logger.js
アクセスログを書き込むだけだが、connectのミドルウェアの仕組みではリクエスト完了後に割り込むことができない*1のでhackが必要となる。
具体的に言うと、res.endをラップして終了処理に割り込む。そして、何もせずnextを呼べば、リクエストの完了時に割り込んで処理をすることができる。ただし、immediate オプションを渡した場合はこのhackを行わず、ミドルウェアに処理が移った段階でログを吐く*2。
ログに使える:date、:method などは全てloggerオブジェクトのプロパティにメソッドとして定義されており、req, res, field の3引数を渡すことで値を取り出せる。ログのフォーマットとして文字列を渡すと、これらのメソッドを適宜呼び出すような関数へコンパイルされる。
middleware/errorHandler.js
4引数を持つ唯一のミドルウェア。4引数のミドルウェアはエラーハンドリングのために呼び出される。
~accept.indexOf の形式が多用されているが、ビット反転すると-1が0になることを使っていて、文字列が見つからなかった場合にのみfalseとなる。
内部はAcceptヘッダを見てレスポンスをhtml、json、text に分けてスタックを出力するだけの簡単な処理。
utils.pause, resume
ミドルウェアから使われているutils.js 内の関数。任意のObjectについてpauseすると、以後のdataイベントとonイベントがキャッシュされる。キャッシュされたイベントはresumeで再び元のObjectへ渡される。
ミドルウェアから非同期で後続のミドルウェアへ処理を渡そうとする場合は、このメソッドを使う必要がある。そうしないと、後続のミドルウェアがreqからデータを読むためにdataイベントやendイベントを拾おうとしている場合に、それらのミドルウェアがリスナーをしかける前に発生したdataやendのイベントが全て捨てられてしまう。
middleware/basicAuth.js
BASIC認証の実装。Authorization ヘッダがあって認証が成功していればreq.remoteUserへユーザ名をセット、そうでなければ401ステータスを返して認証を促す、というのを愚直に実装している。
basicAuth()の引数の取り方は3パターンで、「user, pass, realm」「cb(user, pass), realm」「cb(user, pass, cb(err, user)), realm」の3通り。最後のパターンは非同期で認証を行うパターンで、認証が完了したらcbへエラー、またはユーザ名を返せばよい。
middleware/router.js
Sinatraライクのルータの実現。ミドルウェアの中でソースは一番行数が多い。
router関数の中で、まずDSLを実現するためにgetやらpostやらのメソッドを持つmethodsというオブジェクトを作っている。利用者はこいつのgetやらpostやらへルーチング情報を渡せばいいというスンポー。getやpostには、Sinatraなどと同じようにPATHとハンドラを渡せるが、ハンドラに加えてミドルウェアも任意個指定ができる。
後、ドキュメント化されてないapp.paramって関数が生えていて、こいつを呼ぶとparamsという変数へコールバックを貯められるが、この辺の実装はかなり怪しい。要はルーチングでマッチしたパラメータに前処理するためのコールバックなのだけど、普通の関数の他にcb(req, res, next, val) というミドルウェアっぽい(けど違う)引数をとるコールバックも指定できて、整理されてない感がある(非同期対応が必要なのはDBから値を読むことを想定してるんだろうけど)。
ルーチングの情報はroutesオブジェクトへメソッドごとに分けて保存される。getやらpostを呼んだときの処理を作っているのがgenerateMethodFunction で、ここでは基本的にroutesの中へルーチング情報を詰め込むだけ。PATHの正規表現へのコンパイルはnormalizePath関数が行う。
本丸は内部のrouter(req, res, next)関数でこいつが実際にルーチングをする。match関数によってマッチするルートがあるか調べられ、マッチした場合はキャプチャーした値がfn.params へ保存される*3。
さらに内部にあるparamという関数は、前述のparamを前処理するコールバックを反復的に適用するために再帰呼び出しされる関数。これはcb(req, res, next, val)のシグネチャを持つ前処理関数が非同期呼び出しに対応しているために必要となる。なお、この前処理の最中でnextへエラーとして'route'という文字列を渡すと、このハンドラでの処理を諦めて大域脱出し、後続のルーターのマッチ処理へ戻る。
paramの再帰の最後で、ハンドラと共に指定したミドルウェアを適用するため、さらに内部にあるnextMiddleware という関数による再帰が起こる。ここでもミドルウェアがnextへエラーとして'route'を渡すと大域脱出できる。ここのミドルウェアの適用は自前で処理しているが、app(req, res, nextMiddleware) の形式のものしかサポートしていないため、エラー処理をするfn(err, req, res, nextMiddleware)の形式のミドルウェアを渡しても機能しない。
ここまでを経て、ようやく最終的にユーザがgetやpostへ指定したハンドラがキックされる。ハンドラ内からnextを呼ぶと、次のルーチングの検索へ移る。その必要がない場合はres.endでレスポンスを完了すればよい。
最終的にマッチするルートがなかった場合は、OPTIONSメソッドの場合はAllow:ヘッダ入りのレスポンスをするか、それにも該当しなければ後続のミドルウェアへ処理を渡す。
作成されたrouterミドルウェアには、.remove, .lookup, .match のメソッドが生えており、作成後にルーティングを削除したり、どんなリクエストがマッチするのかテストしたりできる。
[JS][node]Connectのソースを読む(1)
12月 23rd
node.js 使ったことがなかったので読んでみる。今日はコアの部分。
下準備
githubからcloneしてきて、checkout 1.8.4した。masterじゃないので注意*1。
npm install を実行すると依存モジュールがnode_modules/ 以下に入る。そうするとmake test でテストを走らせられるようになる。expressoというのでテストをしてるっぽいが、OS X上だとテストが通らない。2.x 系のコードだとテストは通るっぽい*2のでまあ気にしない。
依存とかは package.json を見れば分かる。
patch.js
node.js の機能へのモンキーパッチが入っている。具体的にはプライベートフィールド_headerSentを露出させるアクセサ、Set-CookieとContent-Typeヘッダ周りの拡張、HTTPヘッダ吐き出し直前に新しいフックの追加など。
util.js
ユーティリティ。特筆すべき点はないと思う。
connect.js
エントリポイント。createServer関数を提供しているが、こいつは単にhttp.Serverやhttps.Serverのコンストラクタを呼んでるだけ。typeof で引数を判定して関数のオーバーロードを実現するのはJSの常套句。
また、middleware の一覧の作成もここで行っている。middleware/直下で拡張子が.jsのファイルを片っ端から記憶する。readdirSync でロードしてるのでブロックするが、起動時なので実害はないはず。ここでは各middleware へのアクセサを作るだけで、ロードは利用時まで遅延して行われる。
http.js
ミドルウェアの機能を実現している心臓部。node.jsのhttp.Serverを継承している。(ドキュメントにはない気がするけど)http.Serverのコンストラクタにはrequestイベントのリスナを渡すことになっており、handleメソッドをリスナとして登録している。
handleの前にまずuseメソッド。ミドルウェア(後述)の他に、http.ServerやServer(handlerメソッドを持つもの)のインスタンスを渡せるよう考慮されている。ミドルウェアは登録順にstackプロパティへ保管される。
で、本丸のhandleメソッド。こいつがリスナとなっており、リクエストが発生するごとに適切なミドルウェアを適用してレスポンスを返す責務を担っている。ただし、通常のリスナはreq, resの2引数だが、このメソッドはoutを含んだ3引数となる。これは、handleメソッド自体もミドルウェアとなっているため。
handleメソッド内の記述を読むと、middleware の仕様が読み取れる。具体的には、err, req, res, nextの4引数をとる関数か、req, res, nextの3(以下)引数をとる関数となる。前者はエラーハンドリング、後者は通常の処理を担当している*3。
middlewareのnextという引数がコールバックとなっていて、この引数を呼び出すと次のミドルウェアへ処理が移る。後続のmiddlewareへ処理を渡さない場合は、nextを呼ばずにres.end を呼んでレスポンスを完了するとよい。
nextはエラーの運搬も担当している。全てのミドルウェアを処理してもレスポンスが返せなかった場合は、next経由でエラーが報告されていれば500、そうでなければ404ステータスとしてレスポンスを返している。
https.js
node.js の https.Server を継承したServerを作る。前述のhttp.js のServerからメソッドをコピーしているので、実質はhttp.jsと同じ。
[レポート][JS]今日は JavaScript ゲーム製作勉強会 Vol.2 の日です
8月 23rd
Akiba.jsという集まりは初めて聞いたのですが、今日はJavaScript ゲーム製作勉強会 Vol.2に出席しています。会場はリクルートさんです。
詳細はid:nakamura001 さんの撮影した動画を参照しましょう。
はじめに / @hakobera さん
- ブラウザが早くなった。node.jsができた。サーバとクライアントどちらもJS
- ブラウザで動くRPG風なデモ的なスライド
- 懇親会もリクルートさんのご好意で無料!
ngCore の話 / @shibukawa さん
- androidは5月に、iOS上は先日「忍者ロワイヤル」
- ngCoreで何ができるかは「忍者ロワイヤル」をやるとわかりやすい
- ngCore
- JSで開発、マルチプラットフォーム(android, iOS, Flash, HTML5)
- 配信方法が豊富(ポータルでDL、appファイルを作ったり)
- iOS上でもDLによる配布はできるが、規約が問題
- ゲームAPI。XNAよりシンプル(簡単、機能は少ないかも)
- ソーシャル用のAPI (友達情報、課金)
- ngCoreでの開発
- Build Server(node.js)が開発の中心
- BuildServerにあスマフォからアクセス
- ビルドされる。圧縮したり画像を2のベキ乗な正方形にしたり。
- ngCoreでの配信
- androidはmobage Dev site へビルドしたファイルを配置
- iOSの場合は、mobage Dev site へアップロードしてappファイルを作ってapple storeで配信
- ゲームのAPI
- シーングラフベースのAPI。2Dスプライト、簡単なポリゴン
- 将来的には3Dも
- BGM・SEの再生
- UI(ボタンやスクロール、リストビュー、WebView(ヘルプ画面などで使ってる)
- ネットワーク
- 物理演算(Box2Dベース)。パチンコでBox2Dをフルに使ったゲームが出てるっぽい
- ファイル操作、Key/Valueストア
- JSのユーティリティ → オブジェクト指向サポート、Observerパターン、MD5/SHA
- 回転、ジャイロの検出
- ソーシャルのAPI
- 課金、プロフィール、友達情報、ブラックリスト、アクセス分析
- CommonJS準拠 (require)
- .subclass メソッドによる継承
- エントリポイントは main
- UpdateEmitter → 画面の書き換えごとにハンドラを呼ぶ。これを使って作る
- シリアライズされた命令列をV8側で作り、ゲームエンジン(ネイティブ)側に渡して実行
- イベントは逆に、ゲームエンジンから命令列をJSへ送る
- JS側とネイティブ側が鏡のような関係で動く
- パフォーマンス
- ネイティブよりは遅い。HTML5よりは早い
- キャラクターを多く出しても満足いくパフォーマンス(弾幕はきつい)
- チューニングは日々行われている
- SDKは本家サイトから落としてね
- 環境
- node.jsさえ動けばいい。Snow LeopardやLinux,Windows
- node.jsは0.4.7以上。他、iOSやandroidの環境。Flashも開発用限定で使える。
- 今後の内容
- パフォーマンスアップ、3D、HTML5対応
- ngServer : JSによるゲームサーバ。(DeNA内ではPerl)
- ngBuilder : 実行環境の起動、JSのデバッガやプロファイラ、リソース管理
- 将来的にはGUI のレイアウトも
- ngGo → ライブラリ群。開発期間を1/3に
- スクリーン解像度、2Dカメラ、などなど
- まとめ
- ngCoreはマルチプラットフォームで動的・開発効率重視
- ネイティブ+JS
- 開発効率・パフォーマンスのアップに力を入れる予定
- ngCoreの弱いところ
- 趣味で個人がゲーム作るのには至ってない → DeNAへの登録が必要
- 頑張ってオープンにしたい(ただし、米国との絡みがある)
- 質疑応答
- Q. V8を選んだ理由は?
- A. iOSはWEB View。UI Web ViewのJSにはJITがない(Safariにはついてるぽい)。Appleの意向に沿っている
- Q. V8以外の選択肢は?
- A. (決定に携わってはいないが)C++さえあれば動く点が評価されたのでは
- Q. androidとiOSとの差は?
- A. 最近はandroidの方がパフォーマンスがよい。速度以外の差はない
- Q. デバグの方法は?
- A. Flash版でブラウザ上で動かし、ブラウザのデバッガを使うと楽。ngBuilderにも機能はある
- Q. ngCore以外のエンジンと比べて、ngCoreの決め手は?
- A. 使いやすさだけでなく、ngmoco:) のコンテンツ力を評価したのでは
- Q. ngCoreはユーザとしてみると使いやすい?
- A. 使えるなら趣味の開発でも使いたい。コンパイルしなくても裏でホットビルドされて動くのでストレスがない。端末の差にもノウハウが蓄積されてて強い
IMPACT - HTML5 + JavaScriptで作るゲーム開発 / @Seasons さん
- 有償ライセンス $99 円高だから8000円くらいで買えてお得!
- 2D。3Dは予定なさそう。HTML5 + JS。ゲームに特化。レベルエディタ、連携ツール。
- IMPACT。"impact javascript"でググる*1
- 購入するとライセンスキーを取得でき、サンプルやgithubのレポジトリへアクセスできるようになる
- version 1.18 → impact ライブラリ、 weltmeister(レベルエディタ)
- Animation, Entity, Collision, Soundなどの機能。
- 最近Debug 機能が追加された
- どのブラウザでも動作
- Weltmeister
- タイルを塗りつぶしてマップを作る。collisionも自動設定
- 背景レイヤも設定できる
- イベント設定ができる
- Chrome 推奨 (Weltmeisterはhtml5で書かれている)
- 連携するツール
- PhoneGap (iOS, androidゲームのパッケージング)
- Lawnchair : ドキュメントストアライブラリ(JSONで保存)
- TapJS : ゲーム配信用のプラットフォーム
- appMobi : web上でiOS、 androidアプリを作れるサービス
- Zeewe.com : クロスプラットフォームでのappstore。自分のゲームを販売できる
- Playtomic : 情報解析やスコアボードの提供
- 他のjsとの連携 → tween.js(緩急のアニメーション), box2d.js, director.js (シーン管理機能。cocos2dのCCDirectorに近い)
- 開発環境
- PC(Windows, Mac, Linux...)
- Apache (XAMPP推奨)
- ブラウザ
- テキストエディタ → textmateがおすすめ。またはKomodo Edit。個人的にはvim
- textmate
- $57
- RoRの人は必須のエディタらしい?
- カスタマイズ機能 → tmbundle・IMPACT用プラグイン
- 開発フロー
- 素材作成 → Entity作成 → レベル作成 →テスト
- Entityまでできれば、レベル作成とテストをひたすら繰り返す
- IMPACTはレトロゲームを作るのに最適なフレームワーク
- ドット絵
- SimpleDotX (OS X)
- EDGE (windows)
- Entityを作る
- モジュールロード → ig.module 〜 .requires 〜 .defines
- .extend で継承。クラスの名前は Entity + ファイル名
- initで初期化、updateでフレーム処理
- draw: カスタム描画, check: カスタム当たり判定
- レベルの作成
- タイルの描画
- 関連するイベントをコネクトしたり、コリジョンサイズを変えたり
- 最終的に .js にまとまる
- ゲームデザイナとの分業
- チューニング(新機能)
- Debugモジュール → パフォーマンスグラフ、Entityのデバッグ、背景マップのデバッグ
- 利用方法は、requireするだけ
- コリジョン、速度ベクトル、エンティティ名やターゲットの表示
- フレーム数を見れる
- ig.log (console.logの代替)、ig.show (デバグパネルへ表示できる)
- ig.mark (パフォーマンスグラフに表示される。毎フレーム呼んだりしないように)
- 配布
- bake.sh によってコンパイル → game_min.js を配布
- 誤ってフォルダごとアップするとIMPACTのソースが見られてまずいので注意
- iOSIMPACT
- キーパッドとゲームをiOS上に表示
- ソースがそのまま使える
- OpenGL, OpenAL, 30-60FPSをキープできる
- UIKitなどとも組み合わせられる
- OpenGL + OpenAL + JavaScriptCore を iOS上で動かす
- iOSIMPACT用のXcodeProject が配布されるので、これを使ってビルドして *.appを作る
- 作品例 → Biolab Disaster, Drop JS
- plugin 読み込みが必要
- 1.17を使うなら、 スクロールメソッドをig.game.setScreenPos(x, y) を使う必要あり
- bindTouchArea によってコントローラ部分を実装
- 解像度が複数あるので、設定のし直しが必要
- 背景描画のチャンクサイズを256にして、パフォーマンスをあげる
- サウンドはフォーマットを変更する必要あり → afconvert する
- サウンドは512KBを境に、OpenALを使うか使わないか決まる。
- Software Design 9月号と10月号にIMPACTの連載あるよ!
- デモ
- 10分くらいでiOSに移植ができる
スマホ向けCanvasゲームの作り方 / @chikathreesix さん
- 関連作品 → 海賊トレジャー、住み着き妖精セトルリン
- ブラウザゲーを作る
- CSS3を駆使
- 簡単、GPUアクセラレーション(iPhone)、3D
- アニメの経過点が取りにくい(衝突判定など)
- →シンプルなアニメーション、HTMLベースなものに向いてる
- Canvasを駆使
- 自由度は高いが、面倒。CSS3ほどのパフォーマンスが出ない
- →複雑なアニメーション、衝突判定が必要なアクションゲームに向いてる
- CSS3を駆使
- Canvasとは? → 画像描画のためのHTML要素
- canvas.getContext().drawImage 的な使い方
- 座標はdrawImageに直接指定するか、translateにて指示
- 拡大縮小はdrawImageに直接指定するか、scaleにて指示
- 回転はrotateで指示。変換行列は残るので、.saveと.restore と合わせて使った方がよい
- アニメーション
- setIntervalにてclearRect + drawImage → 処理が遅いと指定した秒ごとには呼ばれないので注意
- FPS → 1秒間にsetInterval(描画)が何度走るか
- iPhone4遅い(iPhone3GSよりも) → ディスプレイに描画するところが遅い(イベントループの周りが遅くなる)
- Canvasの高速化
- drawImageで拡大すると重い → すでに拡大されてる画像を使う。または、予めcanvasに拡大してdrawImageしてき、それを使う
- 縮小は問題ない。拡大を避ける
- 座標に小数を使わない ~~(0.001)
- Android 2.1 のキャンバスのバグ
- scale, rotate, translate あたりの挙動がおかしい → 回転はあきらめる
- アニメーション
- 環境によってFPSが変わってしまう
- タイムベースでフレームを呼ぶ (描画を省く)
- currentTime で時刻を取得して、その値から変化量を算出する
- Canvasフレームワークを作った
- AS3ライク、表示ツリー、イベント伝播モデル、アニメーション
- デモ : 実記の物をカメラで投影。iPhone3GSで気をつけて作れば30FPSくらい出る
- 質疑応答
- Q. レイヤは何個?
- A. 1個しか使っていない
- Q. 再描画は全画面?
- A. 毎回全領域を描画している
- Q. 端末の差異によるクレームってあった?
- A. 特にない。android 2.1が一番苦しかった
- Q. iOSとandroidでパフォーマンスは?
- A. androidの方が出ている
*1:impactjs でも出た
[JS]Re: 配列参照について
9月 29th
最近書くネタがなかったのでTwitterから転載。
Array型に対して添字を文字列にするなんて言語道断かと思いましたが、どっちも対して変わらないんですね。
["a","b"][0] // "a" ["a","b"]["0"] // "a"二つの方法で参照することが出来ます。
何故こういうことが出来るかというと、JavaScriptでは配列もオブジェクトとして扱うからなんだと思います。
あってるんじゃないかなあ。「プロパティ名P(文字列値形式)は、ToString(ToUint32(P))とPが等しく、かつToUint32(P)と232-1が等しくない場合、配列のインデックスであり」とのこと。 http://bit.ly/cJjdJv
さらに言うと[]演算子の中身はExpressionかidentifier stringで、[0]だと前者で["0"]だと後者。前者の場合は、疑似コードによればToStringを噛まされるので、結局"0"へアクセス。 http://bit.ly/c3pp7v
ちょっと嘘ついた。["0"]でアクセスした時もIdentifierのフォーマットを満たしてないのでExpressionとして評価されると思う。
ということは、あくまで[]演算子の中身は文字列リテラルであるべきっていうのがjsの考えですか?オブジェクトリテラルの書き方を見れば数値も識別子に出来たと思ったんですが。
PropertyNameがNumericLiteralの場合の評価方法を読むとこいつもToString噛まされているので、文字列と言っていいでしょう。 http://bit.ly/dx0I5v