バイナリログ転送ツール bingo

前回のエントリから相当間が空いてしまいました。
ネタがあればちゃんと書いていきたい所です。

で、タイトルの通り bingo というアプリケーションをリリースしました。

何をするアプリケーションかと言いますと、
MySQLのバイナリログ(更新差分)を簡単にリアルタイム転送するツール、です。

MySQL to MySQLであればMySQLのレプリケーションでいいじゃん、となるのですが
例えばBigQuery等にリアルタイムでデータ転送しようと思うと結構面倒くさいと思います。
(トリガーを使ったり、短時間毎に差分を取得したり。。)

bingo はMySQLのレプリケーション技術で更新データを読み取りますので、
非同期ではありますが、ほぼリアルタイムに更新データを取得できます。
現状はfluentdにhttp postする事しか出来ませんが、色々と応用できるとは思います。

使い方(詳しくはREADMEへ)

bingoは起動するとすぐにMySQLに接続します。

$ bingo
2016/09/02 01:19:10 connected to mysql(root@127.0.0.1:3306)
2016/09/02 01:19:10 start reading binlog
2016/09/02 01:19:10     Binlog Version:  4
2016/09/02 01:19:10     Server Version:  5.7.14-log

MySQLに何かしらデータを書き込みます。

$ mysql -u root -e 'create database testdb default character set utf8mb4'
$ mysql -u root -e 'create table testdb.testtable (id bigint, name varchar(32))'
$ mysql -u root -e 'insert into testdb.testtable (id, name) values (1, "hello world")'
$ mysql -u root -e 'insert into testdb.testtable (id, name) values (2, "はろーわーるど")'

localhost:8888 で待ち受けるfluentdにはこんな感じでデータがpostされます。

2016-09-02 01:23:28 -0400 bingo.data: {"database":"testdb","table":"testtable","columns":["1","hello world"]}
2016-09-02 01:23:36 -0400 bingo.data: {"database":"testdb","table":"testtable","columns":["2","はろーわーるど"]}

余力があればカラム名つきでpostするような実装を入れたいところですが、ひとまずはここまで。
テストも途中からほとんど書けていないのでこれもIssueですね。。

タッチイベント内でのRenderTextureの不具合

TiledMapを使わずにSpriteを敷き詰める場合、
描画高速化と継ぎ目を目立たなくする対策としてRenderTextureを使用しているのですが、
以下の条件を満たしているとオフスクリーンの一部の描画がされないケースがありました。

  • 一定サイズより大きいRenderTextureを作成する
  • タッチイベント内で描画を行う

理屈はよくわかりませんが、再現したのでコードをメモしておきます。

  • プロジェクト作成
cocos new cocos-test -p com.test -l cpp
  • HelloWorld に onEnter を追加します。
class HelloWorld : public cocos2d::Layer
{
public:
...
+    void onEnter();
...
}
  • HelloWorld.cpp に下記のコードを追記します。
#define SIZE 64

void HelloWorld::onEnter() {
	Layer::onEnter();
	
	testRenderTextureBug();
	
	auto listener = EventListenerTouchOneByOne::create();
	listener->onTouchBegan = [=](Touch*, Event*){
		testRenderTextureBug();
		auto delay = DelayTime::create(2);
		auto call = CallFunc::create([=]{
			this->testRenderTextureBug();
		});
		runAction(Sequence::create(delay, call, NULL));
		return true;
	};
	getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
}

void HelloWorld::testRenderTextureBug() {
	
	auto tex = RenderTexture::create(SIZE*10, SIZE*10);
	tex->setPosition(SIZE*2, SIZE*5);
	tex->beginWithClear(0.3, 0.3, 0.3, 1);
	for (int i = 0; i < 10; i++) {
		for (int j = 0; j < 10; j++) {
			auto color = Color3B(0, i*16, j*16);
			auto s = Sprite::create();
			s->setTextureRect(Rect(0, 0, SIZE, SIZE));
			s->setColor(color);
			s->getTexture()->setAliasTexParameters();
			s->setPosition(j*SIZE, i*SIZE);
			s->visit();
		}
	}
	tex->end();
	addChild(tex);
}

testRenderTextureBug() を呼ぶ度にRenderTextureを生成して重ねます。(下のRenderTextureは見えなくなる)
実行し、画面タッチするとバグの再現を確認できます。

  • onEnterで生成すると、正常なRenderTextureが表示される

rt_test1

  • 画面タッチイベント内で生成すると、一部欠けたRenderTextureが表示される

rt_test2

  • 画面タッチ後のCallFunc内で生成すると、正常なRenderTextureが表示される

rt_test3

この通り、対策としてはCallFuncで生成する事でしょうか。
根が深そうなのでソースコードまでは追っていません。

Asepriteのマウスポインタを固定する

個人的にドット絵を描くならAsepriteが一番使いやすいと思っているのですが、
マウスポインタがよく消えたり見えにくかったりするのが難点だと感じていました。
つい最近ソースが公開されている事を知ったので、ソースに手をいれて強制的に固定するようにしてみました。

まずは普通にcloneして最新のv1.1.1のタグに切り替えます

git clone --recursive https://github.com/aseprite/aseprite.git
cd aseprite
git checkout v1.1.1

そして、下記の2ファイルを変更します

  • src/ui/system.cpp 109行目付近
       case ui::kSizeWCursor: nativeCursor = she::kSizeWCursor; break;
       case ui::kSizeNWCursor: nativeCursor = she::kSizeNWCursor; break;
     }
+    nativeCursor = she::kArrowCursor;
   }
  • src/ui/window.cpp 411行目付近
           case HitTestBorderS: cursor = kSizeSCursor; break;
         }

+        cursor = kArrowCursor;
         set_mouse_cursor(cursor);
         return true;
       }

これでasepriteをビルドします。(INSTALL.mdをご参考ください)
Visual Studioの開発者用コマンドプロンプトから、下記の通り実行します。

mkdir build
cd build
cmake .. -G "NMake Makefiles"
nmake aseprite

Visual Studio 2015 Community (Update 1) をインストールした Windows 10 でビルド成功を確認しました。
成果物として build/bin/aseprite.exe が出力されるので、これを起動して動作確認します。
メニューバーに “WARNING” と表示される場合は、設定ファイルとバイナリのバージョン差異があるため、src/config.h の25行目付近を書き換えます。

 // General information
 #define PACKAGE "Aseprite"
-#define VERSION "1.1.1-dev"
+#define VERSION "1.1.1"

 #ifdef CUSTOM_WEBSITE_URL
 #define WEBSITE                 CUSTOM_WEBSITE_URL // To test web server

マウスポインタの切り替えがなくなり、見えなくなる事もなくなってより使いやすくなった気がします。
ただ、見ての通り無理矢理固定するので、ドット絵なマウスポインタと切り替えたりはできなくなります。
また、拡大縮小ツールを使った時や、アプリ内ウィンドウの端っこをつかむときもカーソルが変化しなくなる為、部分的には使いにくくなります。
それでも個人的にはこの方が作業ストレスが減るので、しばらくこのまま使ってみようと思います。

そして cocos2d-x に帰ってくる

あれから結局 CocosSharp はまだ早いと断念し、本家 cocos2d-x に乗り換えました。
さすがに本家では基本的な機能で問題が出ることはなかったのですが、
いくつかの問題があったのでまとめて起きます。

ClippingNode::setStencil で SIGABRT になる

また ClippingNode かい、という感じです。
ちゃんと原因がわかると納得なんですが、同様のタイミングでエラーになった方を
検索してもあまり見かけなかったので、参考までに。

再現させるコードを簡潔に書くと、こんな感じ。

auto clip = ClippingNode::create();
addChild(clip);

auto mask = DrawNode::create();
clip->setStencil(mask);

auto move = MoveBy::create(...);
auto callfunc = CallFunc::create([clip]{
	auto newMask = DrawNode::create();
	clip->setStencil(newMask);
});
clip->runAction(Sequence::create(move, callfunc, NULL));

まず、マスク用のイメージは DrawNode を使い、動的に生成します。
その後、stencil を書き換える CallFunc を実行します。
すると以下のエラーメッセージを出力する CCASSERT に引っかかります。

Node still marked as running on node destruction! Was base class onExit() called in derived class onExit() implementations?

setStencil の中で、古い stencil に対して CC_SAFE_RELEASE が実行され、
参照カウンタが 0 になった場合はそのままデストラクタが呼ばれるという流れになるのですが、
stencil ノードが表示中の為、ASSERT に引っかかってしまうという事の用です。

最終的に DrawNode のインスタンスを保持しておいて、マスク用のイメージを書き換える事で回避しました。

auto clip = ClippingNode::create();
addChild(clip);

auto mask = DrawNode::create();
clip->setStencil(mask);

auto move = MoveBy::create(...);
auto callfunc = CallFunc::create([clip, mask]{
	// 既存のDrawNodeを書き換える
	mask->clear();
	mask->drawPolygon(...)
});
clip->runAction(Sequence::create(move, callfunc, NULL));

基本的な事なのかもしれませんが、
addChildやremoveChildでは SIGABRT になる事はなかったので少し面食らいました。
ちなみにアクション中に限らず、onTouchBegan 中に同様に setStencil すると SIGABRT が起きます。
ClippingNode のステンシルノードは入れ替えたりしない想定で使いましょう、という事で。

RemoveSelfの後のアクションが実行されない事がある

これも基本的な事っぽいのですが、
以下のコードだと “action completed.” はログ出力されません。

auto move = MoveBy::create(...);
auto remove = RemoveSelf::create();
auto delay = DelayTime::create(1);
auto cb = CallFunc::create([=]{
	log("action completed.");
});
node->runAction(Sequence::create(move, remove, delay, cb, NULL));

これは、RemoveSelf の実行後、nodeがどこからも参照されていなければ一定のタイミングで
メモリ上から解放され、その後のアクションは実行されないからです。
間に DelayTime を挟んでいなければ実行されるとは思いますが、あまり信用しない方がいい気がします。

画面上から Node を削除した後 CallFunc を実行した場合は、下記の様に記述するのが正解のようです。

auto move = MoveBy::create(...);
auto hide = Hide::create();
auto delay = DelayTime::create(1);
auto cb = CallFunc::create([=]{
	log("action completed.");
});
auto remove = RemoveSelf::create();
node->runAction(Sequence::create(move, hide, delay, cb, remove, NULL));

cocos2d-xでの本格的な開発は経験がないので、
オープンソースのコードを参考にさせてもらいたいのですが、
ゲームはあまりオープンソースでコードを公開されるケースが少ないですね。
特許だったりチートされる可能性だったり色々あるので仕方ないのですが・・・

またネタが溜まったら書きます。

CocosSharpでClippingNodeでクリッピングされない問題

またまた問題にぶつかりましたよっと・・・
クリッピングしたいのに一切クリッピングされずに表示されてしまう。
むしろStencil(マスク用のSprite)がそのまま表示されているという謎の挙動。

昔からある既知の不具合の一つなんですかね。

Issueには上がっているものの修正されてはいないようです。
GameDrawPrimitives のアルファ処理がiOSやMacでは動作しないといった原因のようなのですが、Androidの実機でも同様の症状が出ました。

CocosSharpはまだちょっと早いかもしれませんね・・・

Windows Update で KATANA01 のタッチパネルの精度が改善

KATANA01 に Windows Update で 10586.36 が降ってきていたので、アップデートしたところ、
タッチパネル下部分の精度が非常に悪かったのが改善されました。

正直ソフトキーボードで文章を入力するのが苦痛なレベルで悪かったのが、まともに使えるレベルになりました。
まだもっさりとはしていますが、これは端末スペック上しかたがない部分ですので。。

ハードウェアの不具合ではなくてよかったよかった。

CocosSharpで独自フォントが表示されない問題

以前取得した Xamarin のライセンスを有効活用しようと、
Xamarin Studio と CocosSharp を使ってアプリを作ってみています。
1.7.0 はまだpre1バージョンだったので、あえて 1.6.2.0 にダウングレードして使い始めました。
(Official NuGet Gallery から取得できる CocosSharp PCL)

とりあえず各グラフィックス機能の使い方を確認しようと、
SpriteだったりLabelだったりをテストしていたところ、いきなり独自フォントが表示されない問題に躓きました。

var label = new CCLabel("hello world", "myfont.ttf", 30, CCLabelFormat.SystemFont);
AddChild(label);

たったこれだけのコード。。
参考にした CoinTime というサンプルプロジェクトを参考に、色々試して見るもうまくいかない。
https://developer.xamarin.com/samples/mobile/CoinTime/

何でだろうとさらに調査したところ、なんと普通に不具合だそうです。

1.7.0.0 pre2 でようやく修正されるって・・・どれだけ使われてないの・・・

自力でパッチを当てようと試みた結果

幸い CocosSharp はオープンソースプロダクトなので、自力でパッチを当てることが可能です。
(ただしうまくいくとは言ってない)

README を読みながらソースを git clone し、 git submodule update を実行。
Xamarin Studioでソリューションファイルを開く前に、下記のおまじないを実行します。

mono MonoGame/Protobuild.exe default.build

そして、CocosSharp.iOS.sln を開いてバグを修正し、プロジェクトをビルド。
できあがった CocosSharp.dll を元々あった CocosSharp.dll と差し替えて実行すると、先のコードのままで独自フォントが利用できるようになりました。

いきなりパッチ当てる必要が出てくるとは、幸先悪いなーという感じですが、とりあえずこれで先に進めそう。
Android版のdllも同じようにビルドして配置したところエラーが出たけど無視。
Android版なんてなかった。

KATANA01にUWPアプリをインストールしてみた

KATANA01買いました に続いて、
とりあえず自作アプリをインストールしてみるところまでやってみました。

USB接続でデバッグ実行する

KATANA01をUSB接続した状態で Visual Studio 2015 を起動して、Universal アプリのプロジェクトを作成、
MainPageに適当にテキストを配置してビルド。ビルドターゲットは ARM + Device にします。

vs_ss_hello

そしてデバッグ実行。

wp_ss_app1

動いたー。
これは簡単に動作しました。

色々調べていると、どうやらWiFi経由でデバッグ実行できるらしい。
ということを知ってしまったのが運の尽き。
ここからすごい勢いでドハマりするのであった。。
本当の地獄はここからだ!

WiFi経由でリモートデバッグする

USB接続を解除して、ビルドターゲットを ARM + リモートコンピューター (Remote Machine) にします。

vs_ss_target_remote

接続先を設定するダイアログが表示されます。
すると、自分のKATANA01が表示されているので選択し、デバッグ実行します。

vs_ss_remote_dialog

PINを要求されるので、デバイスポータルでログインしたときと同じように端末でPINを発行して入力。
が・・・ダメ・・・!

vs_ss_wp_pinerror

何度やってもWindowsやVisual Studio、端末を再起動してもダメ。
ペアリングをすべて解除してもダメ。
Windows 10 も最新の build 10586 にアップデートしてもダメ。
Visual Studio は Update 1。

ネットを検索してみると、同じような症状でハマっている人が多数。

レジストリをいじってみましたが、DEP6100, DEP6200が消えただけでデプロイに失敗するのは変わらず。
今のところWiFi経由でリモートデバッグは出来ていません。
もう少しこなれてからなんですかねー。

KATANA01買いました

発売日には届いたもののあまり触る時間が無く放置していましたが、
Windows 10 Mobile デバイスの KATANA01 を購入しました。

https://www.freetel.jp/product/smartphone/katana01/

Windows Phone の新しい実機を開発用に用意したいなーと思っていたところに
安価な端末が登場したのでこれはちょうど良いなと。

ざっと触ってみた所感

  • 動作が若干もっさり。昔のAndroid端末みたいな感じ。
  • 画面下部のタッチパネルの精度が相当おかしい。(座標がずれてる?)
  • ゲームは割とさくさく動く。(FF3が安定して30FPSぐらいでる)
  • CPU不足なのかEdgeもかなり重い。

普段使いにするにはちょっと厳しいという所。
まあ、開発用なので個人的には問題なし。

とりあえず開発機として設定

メニューから、開発用の設定をONにしていきます。

wp_ss_main wp_ss_config

wp_ss_update

wp_ss_dev1 wp_ss_dev2

これで開発用に使えます。

デバイスポータル

Windows 10 Mobileになって良くなったと思ったのは、こちらのデバイスポータル機能。
リモートからブラウザ越しにデバイスを管理する事ができるモノです。

ブラウザで端末のIPアドレスを指定してアクセスすると、PINを要求されます。
設定画面スクリーンショットの「ペアリング」ボタンをタップすると、PINが発行されるのでここに入力します。

wp_ss_dp_pin

出来る事としては、こんな所のようです。
素の状態でここまで出来るとはちょっと予想外。

  • アプリのインストール・管理

wp_ss_dp_apps

  • 各プロセスの情報やリソース使用状況の確認

wp_ss_dp_processes

  • リソースの使用状況メトリクスの確認

wp_ss_dp_stats

  • デバイスの情報確認

wp_ss_dp_devicemanager

  • ネットワーク設定の確認

wp_ss_dp_network1

wp_ss_dp_network2

イマドキな開発者向けツールという感じでよく出来てます。