FPSを作ってみる@wiki
10)
最終更新:
slice
-
view
(2011/10/31)
ソケットとスレッド
現在,ソケット通信はブロッキング動作のAPIを各TCPセッション毎にスレッドを割り振り行っている.
各スレッドは並列に動くのでブロッキングしても問題ない.
しかしこれでは後々具合が悪い事に気付いた.
各スレッドは並列に動くのでブロッキングしても問題ない.
しかしこれでは後々具合が悪い事に気付いた.
具体的には割り込み処理が出来ない.
SSL_write()やSSL_read()でブロッキングされているスレッドにアクセスする手段が無いのだ ※1
幸いtwitterクライアント程度のプログラムではコンマ秒を争うリアルタイム性は要らないので
一番単純な方法としてwrite()やread()をノンブロッキング動作に設定し
0.5秒毎とかにパケットが来てるか確認すれば目的は達成できる.
だがこれは所謂ポーリング,スマートとは言い難いし返答が来てから最悪(無意味に)0.5秒待たされる訳で効率も悪い.
SSL_write()やSSL_read()でブロッキングされているスレッドにアクセスする手段が無いのだ ※1
幸いtwitterクライアント程度のプログラムではコンマ秒を争うリアルタイム性は要らないので
一番単純な方法としてwrite()やread()をノンブロッキング動作に設定し
0.5秒毎とかにパケットが来てるか確認すれば目的は達成できる.
だがこれは所謂ポーリング,スマートとは言い難いし返答が来てから最悪(無意味に)0.5秒待たされる訳で効率も悪い.
何か要求が来るまではスリープしておいて,来たら直ぐに処理する・・・これはイベント(シグナル)を使わなければ出来まい.
ノンブロッキングにした所で意味は無い.
ノンブロッキングにした所で意味は無い.
であるならばブロッキング動作はそのままにしwinsock側でソケットにイベントを設定しシグナルを待つ(WaitFor~),
1つのスレッドで複数のTCPセッションを管理する・・以前やった手法である.
スレッドに分ける意味は?と問われれば「それなりに重いHTTPヘッダやJSONの解析を,(もしあれば)複数のCPUで分担して出来る」位しか思いつかないが・・まあいいだろう ※2
1つのスレッドで複数のTCPセッションを管理する・・以前やった手法である.
スレッドに分ける意味は?と問われれば「それなりに重いHTTPヘッダやJSONの解析を,(もしあれば)複数のCPUで分担して出来る」位しか思いつかないが・・まあいいだろう ※2
#追記
ノンブロッキングにした所で~の項が誤解を招く表現になってしまっているので訂正.
winsockのソケット動作をWSAEventSelect()でノンブロッキングに設定しておき,データが受信されたらシグナルがセットされるようにする.
初回recv()を呼んでWSAEWOULDBLOCKが返ってきたら(つまりまだ受信されていない)先程のシグナルに加えユーザーキャンセル通知用のシグナルを一緒に
WaitForMultipleObjects()にて待つ.
そして通知が来たらデータ受信か,キャンセルかで分岐という寸法.
winsockのソケット動作をWSAEventSelect()でノンブロッキングに設定しておき,データが受信されたらシグナルがセットされるようにする.
初回recv()を呼んでWSAEWOULDBLOCKが返ってきたら(つまりまだ受信されていない)先程のシグナルに加えユーザーキャンセル通知用のシグナルを一緒に
WaitForMultipleObjects()にて待つ.
そして通知が来たらデータ受信か,キャンセルかで分岐という寸法.
※1 スレッドをterminateとかは無しで.
※2 ゲームではどちらが優れているか?これもまた判断しかねる.どちらにせよメインスレッドと同期が必要だし.
※2 ゲームではどちらが優れているか?これもまた判断しかねる.どちらにせよメインスレッドと同期が必要だし.
(2011/10/29)
無駄骨
引き続きStreamAPIを受信する仕組みを実装中.
JSONデータが延々送りつけられるからこれを順番に解析してLuaに渡せばいいな・・・と思いきや
コンテンツタイプはJSONの筈なのになぜか必ず最初の行に数値が入っている事に気付く.
コンテンツタイプはJSONの筈なのになぜか必ず最初の行に数値が入っている事に気付く.
当初は「よくわからんけど最初の行だけとばせば良いんだろ?」程度にしか考えてなかったのだが
ふと見たらHTTPヘッダのTransfer-EncodingフィールドにChunkedとか書いてあるではないか.
早速調べるとこれはHTTP1.1で定義されている方式で,最初にBodyのサイズを決定せず文字通りChunkに分けて
本文を送信するらしい.
Chunkの形式は単純で,最初に数値でChunkのバイトサイズ,
改行を挟み内容,改行,・・・・が1セットで必要なだけこれを続ける.
通信を終わりたくなったらサイズを0にして内容は空行のChunkを送ればこれが終了の合図になる.
ふと見たらHTTPヘッダのTransfer-EncodingフィールドにChunkedとか書いてあるではないか.
早速調べるとこれはHTTP1.1で定義されている方式で,最初にBodyのサイズを決定せず文字通りChunkに分けて
本文を送信するらしい.
Chunkの形式は単純で,最初に数値でChunkのバイトサイズ,
改行を挟み内容,改行,・・・・が1セットで必要なだけこれを続ける.
通信を終わりたくなったらサイズを0にして内容は空行のChunkを送ればこれが終了の合図になる.
ということで無駄な小細工をしていて時間を浪費してしまった.そういう仕様なのね.
(2011/10/28)
SSL対応
予定通りOpenSSLを使用してのSSL対応が完了した.
SSL/TLS自体HTTPから独立しているお陰か初期化とソケット送受信の所を少し抽象化しただけで
本当にアッサリ動いてしまったので驚いた.
が,その後に早速Stream-APIを試そうとするもヘッダに記載するホスト名だけ書き換えて(api.twitter.com => stream.twitter.com)
実際の送付先(要するにIP)を変更し忘れるという凡ミスを犯す.
なんでNotFoundなんだろう~とか,何故かHTMLページ返して来るんですけど~とか.延々とトライアンドエラーの繰り返し.
ちなみにHTMLページの内容は存在しないアドレスにアクセスした時に表示される「そんなページありません云々」だった.
SSL/TLS自体HTTPから独立しているお陰か初期化とソケット送受信の所を少し抽象化しただけで
本当にアッサリ動いてしまったので驚いた.
が,その後に早速Stream-APIを試そうとするもヘッダに記載するホスト名だけ書き換えて(api.twitter.com => stream.twitter.com)
実際の送付先(要するにIP)を変更し忘れるという凡ミスを犯す.
なんでNotFoundなんだろう~とか,何故かHTMLページ返して来るんですけど~とか.延々とトライアンドエラーの繰り返し.
ちなみにHTMLページの内容は存在しないアドレスにアクセスした時に表示される「そんなページありません云々」だった.
Stream-APIに対応出来ればまた完成に一歩近づく.
ただREST-APIの動作前提でLuaと連携を作っちゃったから実際にUserStreamで取得した呟きを垂れ流すには手直しが必要だろうな.
ただREST-APIの動作前提でLuaと連携を作っちゃったから実際にUserStreamで取得した呟きを垂れ流すには手直しが必要だろうな.
(2011/10/25)
マージのミス
行列クラスを操作していて直したはずの不具合が直ってない(※1)事に気付いた.早速ソースを見ると何か違和感が.
あれ?これって古いソースじゃん・・・
どうやらこの前書き直した数学ライブラリもどきを,SVNの不慣れな操作でコピー元と先を反対にマージしていたらしい.
ただ,どんな操作をしても履歴から消せないのはSVNの良い所だろうか(元々そう言う物らしいが)
無事に履歴を遡って上書きする前のファイルを救出できた.
その後古いソースに合わせて作っていた部分が山のようにエラーを吐くのでひたすら修正など.
あれ?これって古いソースじゃん・・・
どうやらこの前書き直した数学ライブラリもどきを,SVNの不慣れな操作でコピー元と先を反対にマージしていたらしい.
ただ,どんな操作をしても履歴から消せないのはSVNの良い所だろうか(元々そう言う物らしいが)
無事に履歴を遡って上書きする前のファイルを救出できた.
その後古いソースに合わせて作っていた部分が山のようにエラーを吐くのでひたすら修正など.
折角ツイートを表示できるんだし次は
HTTPでツイートの発言者アイコンを取り込んで表示したいと思ったが
ホストアドレスやその他諸処の部分が「どうせTwitter.comとしか通信しないだろう」ってんでハードコーディングされてたり
構造体の名前とか通信スレッドの設計がTwitterAPIクラスと密になっているので分離せにゃイカンなあと.
HTTPでツイートの発言者アイコンを取り込んで表示したいと思ったが
ホストアドレスやその他諸処の部分が「どうせTwitter.comとしか通信しないだろう」ってんでハードコーディングされてたり
構造体の名前とか通信スレッドの設計がTwitterAPIクラスと密になっているので分離せにゃイカンなあと.
他にはStreamAPIを将来的には使いたいのもあって予習を兼ねて
ドキュメントを眺めているとSSLしか対応していない模様.
自前でSSLを実装するのは・・・様々な暗号化アルゴリズムに対応させる部分が特に面倒で退屈そうなので遠慮しておく
ここは素直にOpenSSLを使う予定だ.
ドキュメントを眺めているとSSLしか対応していない模様.
自前でSSLを実装するのは・・・様々な暗号化アルゴリズムに対応させる部分が特に面倒で退屈そうなので遠慮しておく
ここは素直にOpenSSLを使う予定だ.
※1 主に行列とベクトルのエクスプレッションテンプレートな部分だけど詳細は割愛
(2011/10/22)
はいはい放置放置
作業時間の大半をLuaで過ごす日々.Lua・・というかスクリプト言語は単純な記述ミスでさえ実行しないと分からないという欠点が目に付く.
特にLuaに関して言えばlocalで定義したvalueという変数を参照しようと
間違えてValueと書いてしまった場合,ローカルに無い変数という事でそれはグローバル変数を意味する.
グローバル変数にそんなの無いよ!で終われば有り難いがLuaでは存在しない変数を読むと単にnilを返す.
通常nilに演算やテーブル参照を行うとその時点でエラーになるから通常,大きな問題にはならないかと思うが
偶然グローバルにValueがあったら大変な事に・・・
これは値をセットする時も同じだ.
特にLuaに関して言えばlocalで定義したvalueという変数を参照しようと
間違えてValueと書いてしまった場合,ローカルに無い変数という事でそれはグローバル変数を意味する.
グローバル変数にそんなの無いよ!で終われば有り難いがLuaでは存在しない変数を読むと単にnilを返す.
通常nilに演算やテーブル参照を行うとその時点でエラーになるから通常,大きな問題にはならないかと思うが
偶然グローバルにValueがあったら大変な事に・・・
これは値をセットする時も同じだ.
さて,前回から進んだことをつらつら書く.
1.インデックス・頂点バッファのラップクラスを整備.
これらは元々UPバッファ(DrawPrimitiveUPで使う,GPU上に確保しないバッファ)を
効率よくハードウェアバッファに格納し纏めて描画する際に用いていたのだが,他のクラスで使いたくなったので分離.
機能は任意のサイズを全領域ロック,追加書き込みロックなど.
バッファ残量が足りる場合はD3DLOCK_NOOVERWRITEで,そうでなければD3DLOCK_DISCARDでロックしなるべくGPUを待たせないような工夫も.(当たり前の事だが)
これらは元々UPバッファ(DrawPrimitiveUPで使う,GPU上に確保しないバッファ)を
効率よくハードウェアバッファに格納し纏めて描画する際に用いていたのだが,他のクラスで使いたくなったので分離.
機能は任意のサイズを全領域ロック,追加書き込みロックなど.
バッファ残量が足りる場合はD3DLOCK_NOOVERWRITEで,そうでなければD3DLOCK_DISCARDでロックしなるべくGPUを待たせないような工夫も.(当たり前の事だが)
2.自前HUDのウィンドウ描画の効率化.
今まではZバッファを使わず馬鹿正直に奥から描画していた所を
ZソートをきちんとしてZバッファの力を借りて手前から描画するとか(Zクリップの期待),
テクスチャでソートしてGPUの負担を減らす努力.
最初Z値の割り振りをどうするかなと悩んだが単純に手前からfloatの解像度限界ギリギリの 1.0/1e-22f 刻みでやってけばいいなと.
今まではZバッファを使わず馬鹿正直に奥から描画していた所を
ZソートをきちんとしてZバッファの力を借りて手前から描画するとか(Zクリップの期待),
テクスチャでソートしてGPUの負担を減らす努力.
最初Z値の割り振りをどうするかなと悩んだが単純に手前からfloatの解像度限界ギリギリの 1.0/1e-22f 刻みでやってけばいいなと.
3.3D空間にテキストを描画する際にUPバッファ + CPUでビルボード計算していた所を
頂点数が多いのだからGPU上に確保した方が速そうな事に気付き,
ハードウェアバッファとワールド行列を使うようにした.
頂点数が多いのだからGPU上に確保した方が速そうな事に気付き,
ハードウェアバッファとワールド行列を使うようにした.
4.プールアロケータのクラスやbase64変換,URL変換,ハッシュ等々の関数は
他のアプリにも使いそうだなと思ったのでこれらをDLLへ分離した.
DLLを作ったのは久しぶりだから勝手がわからぬ.
基本的にSTLなんかのテンプレートクラス,関数はエクスポート出来なかったよな~とか復習しつつ.
そういえば例外も使えないのか・・・
他のアプリにも使いそうだなと思ったのでこれらをDLLへ分離した.
DLLを作ったのは久しぶりだから勝手がわからぬ.
基本的にSTLなんかのテンプレートクラス,関数はエクスポート出来なかったよな~とか復習しつつ.
そういえば例外も使えないのか・・・
あぁやっぱり速度関連なのねと思って頂いて結構.
ついでにこれからやろうとしている事をば.
3D空間をツイートが流れていくエフェクトに手をつけ始めた.
ようやく後のゲーム作成も視野に入れた下地が出来てきたかなあという印象で,割とサクサク進む <= モチベーションの維持に効果有り
現状だとツイートが光の柱に沿ってふわふわ流れていくだけなんだけどこれじゃゴチャゴチャして見難いから
遠くに見えるツイートは文字は表示せずにアイコンだけにして近くに寄ったら文字がフェードインする仕様に変えたい.
3D空間をツイートが流れていくエフェクトに手をつけ始めた.
ようやく後のゲーム作成も視野に入れた下地が出来てきたかなあという印象で,割とサクサク進む <= モチベーションの維持に効果有り
現状だとツイートが光の柱に沿ってふわふわ流れていくだけなんだけどこれじゃゴチャゴチャして見難いから
遠くに見えるツイートは文字は表示せずにアイコンだけにして近くに寄ったら文字がフェードインする仕様に変えたい.
(2011/10/11)
リファクタリングはここまでにしておく
やっちまった.途中まで書いていた記事を,ついうっかり別のページをクリックして吹っ飛ばしてしまった.
他人がやったとなればハハンと笑って済ます自分だが,遂にというかやはりというか・・
ブログだと書きかけの文章を自動保存してくれたりもするがそこまで贅沢は言わない.
しかし,せめて草稿(アップするけどまだ公開しない)機能くらい欲しい.
他人がやったとなればハハンと笑って済ます自分だが,遂にというかやはりというか・・
ブログだと書きかけの文章を自動保存してくれたりもするがそこまで贅沢は言わない.
しかし,せめて草稿(アップするけどまだ公開しない)機能くらい欲しい.
閑話休題.今一度気力を振り絞り,書く(メモ帳で)
LuaValueクラスの最適化は終了.だが結果は・・・労力の割に微妙.数パーセント程度の高速化に留まる.
次はシングルスレッド専用リソースハンドル.
前回の「マルチスレッド対応させなくて良いリソースは,ロック・アンロックしない」という奴.
エンジン根幹に関わる変更なので折角だからとSVNのブランチ機能を試してみたり(内部的にはコピーのようだ)
そして一通り動作確認が終わったらマージ.大体手順は掴めた.
次はシングルスレッド専用リソースハンドル.
前回の「マルチスレッド対応させなくて良いリソースは,ロック・アンロックしない」という奴.
エンジン根幹に関わる変更なので折角だからとSVNのブランチ機能を試してみたり(内部的にはコピーのようだ)
そして一通り動作確認が終わったらマージ.大体手順は掴めた.
これもどのくらい速くなったかと言えばご想像の範囲内という事で.
それよりもテクスチャのソートやシェーダー変数のキャッシュが効いている.
1つ100文字程度の文章をビルボードで20個表示する部分をテクスチャソートしたら
(デバッグビルド基準で申し訳ないが)40FPSだったのが60FPSになった.キャッシュの力は凄い.
それよりもテクスチャのソートやシェーダー変数のキャッシュが効いている.
1つ100文字程度の文章をビルボードで20個表示する部分をテクスチャソートしたら
(デバッグビルド基準で申し訳ないが)40FPSだったのが60FPSになった.キャッシュの力は凄い.
自前GUIも現状は半透明を使う関係で奥から手前へ,きちんと順序を守って描画しなければいけないのだが
不透明オンリーにしてテクスチャ + 距離でソートしたらどの位効果があるだろうか.ちょっと興味ある.
ウィンドウの枠もPOINTSTRIPではなく,何らかの手段でサイズを取得して「細長い四角形」として描画すべきだな.等々
不透明オンリーにしてテクスチャ + 距離でソートしたらどの位効果があるだろうか.ちょっと興味ある.
ウィンドウの枠もPOINTSTRIPではなく,何らかの手段でサイズを取得して「細長い四角形」として描画すべきだな.等々
さて先月「パフォーマンスアップばかりで云々~」と書いたばかりなのに関わらず今月も二の舞になりそうな香り・・