まるまるこふこふ

数々の次元が崩壊し、全ての生命が塵と化すのを見てきた。私ほどの闇の心の持ち主でも、そこには何の喜びも無かった。

toho-like-js のソースコードを読む 2

進捗

LoadingState.js(ローディング画面)を読んだ OpeningState.js(オープニング及びStart・Replayセレクト画面)を読んだ GameState.js(各シーンの基底クラス)を読んだ

わかったこと

LoadingState.js

1.LoadingState で各画像, BGM, SEを読み込み 2.読み込みカウントが最大になったら、Game オブジェクトに通知 3.Game オブジェクトは通知を受けてシーン切り替え

OpeningState.js

1.中でさらにロゴシーン・タイトルシーンのインスタンスを持つ。 2. OpeningStateはそれらの管理を行う

toho-like-js のソースコードを読む 1

進捗

index.html を読んだ
Game.js を読んだ

わかったこと

1.index.html で Game インスタンスの作成及び run をしている
2.Gameインスタンスは各シーンの管理及び、BGMや画像、描画用canvasなどのグローバルなデータを管理している
3.Game インスタンスは requestAnimationFrame を再帰的に呼び出して再描画を行っている
4.Game オブジェクトは各シーンに引き渡され、各シーンのインスタンスから使用される
5.Game インスタンスが各シーンのインスタンスを持ち、各シーンインスタンスが Game オブジェクトを持つので、GCの実装によってはメモリリークする
6.シーンの切り替えは、Gameインスタンスメソッドによって行う

各シーン

LoadingState.js
ローディング画面
OpeningState.js
オープニング画面
ReplaySelectState.js
リプレイ選択画面
CharacterSelectState.js
キャラクターセレクト画面
StageState.js
ゲーム画面
PostReplayState.js
リプレイ投稿画面
EndingState.js
エンディング画面
StaffRollState.js
スタッフロール画面
GameState.js
各シーンの基底クラス

toho-like-js のソースコードを読む 0

toho-like-js とはブラウザでプレイできる某国産同人弾幕STGっぽいSTGです。
http://takahirox.github.io/toho-like-js/

色んな人に「JSでSTG?出来らぁ!」と言っちゃったので
作るための努力をします。

まず既存のSTGの実装を読んでいきたいと思います。
よって、toho-like-js のソースコードを読んでいきたいと思います。

読んで「良かった」ってなるだけでなく、ちゃんと作れるように頑張ります。

情報処理学会のゲーム情報学の論文を読む

さいです。
ミーハーなので論を文して読み始めました。

情報処理学会で発表された論文は2年以上前の論文は
無料で読めます。最近の論文も、1つ600円ほどで
読むことができます。

言語が日本語かつ研究報告であればそれほどページ数も
多くないので論を文して読みたいみなさまの入門にも
便利です。

今日は2013年〜2009年頃のゲーム情報学の研究報告で
面白いのをちらほら紹介します。

コンピュータ大貧民における手札推定の有効性について
http://id.nii.ac.jp/1001/00092709/

コンピュータゲームの研究では、囲碁や将棋と同じ頻度で
大貧民も 研究対象として取り上げられる。囲碁や将棋が
完全ゲーム (プレイヤー同士が得られる情報が互いに同じ)
である一方、大貧民は不完全ゲーム(相手の手札など
一部得ることのできない情報が存在する。)なので。

機械学習を用いた棋力の調整方法の提案と認知科学的評価
http://id.nii.ac.jp/1001/00092712/

機械学習により将棋AIに「人間らしい」プレイを学習させる。
強いだけのAIではなく、人間がプレイしていて楽しいAIを
目指すアプローチ。

プレイヤの効用を学習し行動選択するチームメイトAIの構成
http://id.nii.ac.jp/1001/00090385/

人間がプレイして楽しいAIとして、上記論文は
敵AIについての論だったが、こちらは味方(チームメイト)を、
RPG形式のゲームを使って論してる。

完全プレイのためのデータベースのサイズの削減
http://id.nii.ac.jp/1001/00080940/

完全ハッシュ関数とウェーブレット木を用いて
どうぶつしょうぎ」の完全プレイのデータベースを
846MB -> 54.8MB に圧縮する。

TCGにおけるシャッフル手法に関する計算機実験を用いた考察
http://id.nii.ac.jp/1001/00073008/

人間の手によるシャッフル方法には、ヒンズーシャッフルや
ファローシャッフル、ディールシャッフルといった方法が
あるが、それらをコンピュータでシミュレートして
ランダム性について実験した論。

強化学習による評価関数の獲得における報酬設定について http://id.nii.ac.jp/1001/00069716/

コンピュータがゲームを行う上で、コンピュータの取る戦略を
どのように評価するかを考えなくてはならない。
教科学習をゲームの評価関数として使用する場合、
その戦略におけるゲーム終了後の局面の勝敗を報酬として
評価し、途中局面の報酬を 0 とするのが一般的だが、
途中局面にも報酬を与えるとどうなるか実験した論。

将棋プログラムの大規模並列実行
http://id.nii.ac.jp/1001/00069710/

コンピュータ将棋プログラムは強力なマルチコアGPUと、
膨大な主記憶装置によって実行されることで、
プロ棋士を破るレベルの強さを実現している。
こうした1台のPCの性能に頼るのではなく、複数台のPCを
クラスタ構築して、演算を行う実験を論した論。
課題として通信遅延及び負荷分散が述べられている。

終わり

アブストラクトと結論だけ読んでてもそれなりに楽しい。

REPEATABLE READをスナップショットという言葉使って語るのは今すぐやめるべき

MYSQLのデフォルトのトランザクション分離レベルは
REPEATABLE READです。

REPEATABLE READにおいてもファントムリードが起こることは
避けられませんが、MySQLのREPEATABLE READでは、
MVCCを採用しているため、ファントムリードは起こりません。

MVCCにおいてはよく「トランザクション毎にスナップショットを作成し、それを 参照することで他のトランザクションからの干渉を受けない」といった説明がされます。

スナップショットという表現のため、我々はトランザクションが始まるたびに、
そのトランザクションが扱うテーブルのメモリコピーをいちいち作成するような
イメージを受けますが、実はそのようなスナップショットという表現のイメージと
実装は異なっています。

MVCC とは

MVCC(MultiVersion Concurrency Control)という名の通り、
バージョニングによりトランザクションの独立性を保ちます。

以下は MySQLInnoDB のMVCC 実装の話です。

MySQLはシステムバージョン番号を持っています。これはトランザクション
1つ開始されるたびに +1 インクリメントされます。これによって
トランザクションは一意なシステムバージョン番号を持っています。
古いトランザクションより新しいトランザクションの方が
大きいシステムバージョン番号を持ちます。

InnoDB上の各レコードは、内部的に2つの値を持っています。1つが行が
作成された時のシステムバージョン番号、もう1つが行が削除された時の
システムバージョン番号です。

これを踏まえて、CRUDの各処理において MySQLがどのような挙動をしているかを示します。

SELECT

SELECT時に、トランザクションは自分の持っているシステムバージョン番号と
同じかそれ以前のバージョンの行しか取得しません。

これにより自分より新しいトランザクションのレコード作成や更新の影響を
受けません。 (なぜ更新の影響まで受けないかはUPDATEの項で説明します)

またトランザクションはSELECT時に、行の削除バージョンが未定義か、
自分の持っているシステムバージョン番号より新しいバージョンの行を
取得します。 これにより自分より新しいトランザクション
レコード削除の影響を受けません。

INSERT

トランザクションはINSERT時に自分のシステムバージョン番号を
新規レコードに記録します。

DELETE

トランザクションはDELETE対象のレコードに、自分のシステムバージョン番号を記録します。
自分より古いトランザクションがこのレコードを参照する可能性があるため、
この段階では物理的にレコードはまだ削除されません。
SELECT時の挙動によりDELETEしたレコードはトランザクションから
あたかも削除されたかのように見えなくなります。

UPDATE

UPDATE時、トランザクションUPDATE対象の行をコピーして新しい行を作成します。

新しい行には自分のシステムバージョン番号を記録し、古い行には
削除バージョンとして自分のシステムバージョン番号を記録します。

これにより古いトランザクションはUPDATEされる前の古いレコードを参照し、
新しいトランザクションはUPDATEされた新しいレコードを参照することで、
トランザクションの独立性を保ちます。

終わり

こうしたMVCCの仕組みによりREPEATABLE READな MySQLInnoDB
行をロックすることなく、またテーブル全体のスナップショットを取るような
マネもせず、トランザクションの独立性を担保しています。

こうした仕組みはスナップショットと呼ばれ、各種RDBMSの公式docでも
使用されていますが、スナップショットという言葉のイメージに惑わされ、
InnoDBのREPEATABLE READ の挙動を勘違いする人たちが後を絶たないので、
皆様も今すぐスナップショットという言葉でREPEATABLE READを理解するのでなく、
バージョニングの概念を使って REPEATABLE READ の仕組みを理解するようになってほしい。

転職して1年が経った

お疲れ様です。さいです。
現会社に入社しておおよそ1年が経ったので
振り返るいい機会だと思ったので振り返ります。

リードエンジニアリング

既存のWebサービスの機能追加・改修のリードエンジニアを
担当しました。エンジニア8人チームのうち、
2〜3名と一緒に開発を行ったり、あるいはプランナーやデザイナーと
コミュニケーションして、Webサービスの機能追加・改修に対して
QCDの面から責任を持ちました。

Backbone.js 導入

UI/UX向上施策に乗じて、フロントエンドに
backbone.jsの導入を進めました。

単体テスト導入

jenkins サーバーを使って、単体テストできる仕組みを
導入しました。

人のパフォーマンスを最大化するのは難しい

あなたはリーダーです。あなたの周りには2名ないし3名の
エンジニアがいます。最近アサインされた彼らは
技術には知見がありますが、業務ドメイン
社内手続きには詳しくありません。

あなたが責任を持つプロダクトに対して、
最大限のQCDを担保するには、あなたは何をすべきでしょうか。

彼らが最大限のパフォーマンスを発揮できるように、
露払いをすることだと思います。

彼らの知識が疎い業務ドメインについて、適切に情報を共有し、
社内手続きを肩代わりすること。彼らのスキル次第では技術的なところも
知識を共有してサポートします。

あなたが優秀なプレイヤーである自負があればあるほど、
自分でやったほうが早い病になると思います。あるいは、
自分が開発のメインを担当して、彼らに落穂拾いをさせることを
考えるかもしれません。

自分のプレイヤーとしての能力に限界を感じ、見切りをつけられるまで
マネジメントというのは上手にならないと思います。

自分より優秀な人たちのパフォーマンスをよりよくするにはどうするか。

自分の方が優秀だと驕っていると、彼らがパフォーマンスを発揮できないときに、
なぜ自分と同じようにできないんだと、非生産的な考え方に陥ると思います。

ノウハウのない技術を導入するのは難しい

Backbone.js 導入にあたっては、私/社内にフロントエンド開発の
知識がない点がもろにネックとなりました。

教訓として、ノウハウのない技術を導入する際は、いきなりプロダクション環境に
導入することを進めず、社内システムやツールに導入して、
ある程度知見を貯めてから、プロダクション環境への導入を考慮するべきでは
等があります。

社外でのアウトプット

あと Elasticsearch や fluentd で遊んでました。成果は特に
ありませんが、ブログ記事くらいにはなりました。

自分がどう変わったか

稚拙ながらも個人でアウトプットをするようになりました。
作りたいと思うものを曲がりなりにも一人で完成まで持っていける
ようになったと思います。

一方アウトプット自体はまだまだ稚拙で、人々から評価を
得られるレベルまで達していません。

第一言語perl なのですが、第二言語として javascript
触るようになりました。フロントエンドからサーバサイドまで
全部 javascript で書きます。hubotスクリプト
google app script なんかも javascript で書けるので、
ほとんどなんでもできる感があって javascript を選択しました。

これから

個人で Webサービスをガシガシ作っていきたいと思います。またリリースした
既存のWebサービスについてもガシガシアップデートしていきます。
それらを通してまずはリーンスタートアップを実践していきます。

github上の何かしらのOSSにコミットしていきたいです。
コントリビュートを豪華にしたいだけの動機なので、
優先度は低いです。

あとはちょっとゲーム作ってみたいですね。弾幕シューティングゲーム
phina.js や ツクールMVの登場で、JS製ゲームエンジン
自分の中でアツいです。

同人活動としてグッズ作成を継続していきます。

お仕事では引き続き単体テスト・UIテストの自動化を進めていきます。
そして既存コードのリファクタ。

終わり

そういえば昨年の前半はMySQL周りの情報を集めてた気がします。
RDBMSの仕組みは面白いですが、そろそろ外部から見える挙動を
確認するのに飽き、Cを学んでソースコードとか読みたいなと
思って、最近手をつけてない感が。

MySQL の order by と index

MySQLの order by と index の仕組みがわからなくなったので調査。

前提の自分の仮定

  • MySQLは降順インデックスをサポートしないので order by desc にインデックスを使用できない
  • (user_id, point)という複合インデックスがあれば、where user_id = ? order by point ascというクエリはインデックスを最大限に使用できる

準備

MySQLのバージョンは 5.1.61

CREATE TABLE `sample` (
  `id` int(10) unsigned NOT NULL,
  `user_id` int(10) unsigned NOT NULL,
  `point` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `i1` (`user_id`,`point`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
mysql> select count(*) from sample;
+----------+
| count(*) |
+----------+
|  1048576 |
+----------+
1 row in set (0.22 sec)

試す

mysql> explain select * from sample where user_id = 50;
+----+-------------+--------+------+---------------+------+---------+-------+------+-------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref   | rows | Extra       |
+----+-------------+--------+------+---------------+------+---------+-------+------+-------------+
|  1 | SIMPLE      | sample | ref  | i1            | i1   | 4       | const |   98 | Using index |
+----+-------------+--------+------+---------------+------+---------+-------+------+-------------+
1 row in set (0.00 sec)

user_id を where 句に指定すると当然i1インデックスを使用してselectできる。 key_lenint型なので4

mysql> explain select * from sample where user_id = 50 and point = 1000;
+----+-------------+--------+------+---------------+------+---------+-------------+------+-------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref         | rows | Extra       |
+----+-------------+--------+------+---------------+------+---------+-------------+------+-------------+
|  1 | SIMPLE      | sample | ref  | i1            | i1   | 8       | const,const |    1 | Using index |
+----+-------------+--------+------+---------------+------+---------+-------------+------+-------------+
1 row in set (0.00 sec)

user_id 及び point を指定しても、i1インデックスを最大限利用して select できる。key_len8なので、user_id及びpointまで インデックスを使用している。

mysql> explain select * from sample order by user_id asc limit 10;
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | sample | index | NULL          | i1   | 8       | NULL |   10 | Using index |
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

user_idascで order by。limitに10を指定してrowsが10なので インデックスを使用して絞っていることがわかる。

mysql> explain select * from sample order by user_id desc limit 10;
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | sample | index | NULL          | i1   | 8       | NULL |   10 | Using index |
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

ascでなくdescでも同様。

mysql> explain select * from sample order by user_id asc, point asc limit 10;
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | sample | index | NULL          | i1   | 8       | NULL |   10 | Using index |
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

(user_id, point)の複合キーなので、order by に user_id と pointを 指定しても絞り込める。

mysql> explain select * from sample order by user_id desc, point desc limit 10;
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | sample | index | NULL          | i1   | 8       | NULL |   10 | Using index |
+----+-------------+--------+-------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

こちらもdescでも同様。

mysql> explain select * from sample order by user_id asc, point desc limit 10;
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
| id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows    | Extra                       |
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
|  1 | SIMPLE      | sample | index | NULL          | i1   | 8       | NULL | 1049137 | Using index; Using filesort |
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
1 row in set (0.00 sec)

ascdescが混合するとインデックスが有効に活用されない。

mysql> explain select * from sample order by user_id desc, point asc limit 10;
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
| id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows    | Extra                       |
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
|  1 | SIMPLE      | sample | index | NULL          | i1   | 8       | NULL | 1049137 | Using index; Using filesort |
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
1 row in set (0.00 sec)

desc asc の順でも同様。

mysql> explain select * from sample order by user_id asc, id asc limit 10;
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
| id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows    | Extra                       |
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
|  1 | SIMPLE      | sample | index | NULL          | i1   | 8       | NULL | 1049137 | Using index; Using filesort |
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
1 row in set (0.00 sec)

pointでなくidをorder by に含めるとインデックスが効かない

mysql> explain select * from sample order by id asc,user_id asc limit 10;
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
| id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows    | Extra                       |
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
|  1 | SIMPLE      | sample | index | NULL          | i1   | 8       | NULL | 1049137 | Using index; Using filesort |
+----+-------------+--------+-------+---------------+------+---------+------+---------+-----------------------------+
1 row in set (0.00 sec)

order by の順序を逆にしてもしかり。

mysql> explain select * from sample order by id asc limit 10;
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | sample | index | NULL          | PRIMARY | 4       | NULL |   10 |       |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
1 row in set (0.00 sec)

idをorder by に指定すると、idはPRIMARY KEY なのでそっちの インデックスが使用される。

mysql> explain select * from sample where user_id = 50 order by point limit 10;
+----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref   | rows | Extra                    |
+----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | sample | ref  | i1            | i1   | 4       | const |   98 | Using where; Using index |
+----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

where 句にuser_id, order by にpointを使用すると、user_idだけ インデックスを使用して探索される。

mysql> explain select * from sample where user_id = 50 order by point desc limit 10;
+----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref   | rows | Extra                    |
+----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | sample | ref  | i1            | i1   | 4       | const |   98 | Using where; Using index |
+----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

descを使用しても同様の結果。

結果

  • MySQLは降順インデックスをサポートしないが order by desc にインデックスを使用できる
  • (user_id, point)という複合インデックスがあっても、where user_id = ? order by point ascというクエリは user_id 部分までしかインデックスが使用されない