« 2014年7月 | トップページ | 2014年9月 »

2014年8月

2014年8月27日 (水)

Andoroidへの道

Android素人でもあるので、これもまた勉強の日々なんですが…

Nexus 7 をKitKat(4.4.4)にバージョンアップしてから、acoreというプロセスが結構な頻度で落ちるので困ってます。
# 頼みますよ、Androidさん(;ω;)

ま、それでもいろいろ使ってみれば楽しいこともありますね。

前回libqtbrynhildr.soまで作って放置していたのですが、少しAndroidのアプリ開発プロセスについて勉強しました。

nativeで作ったlibqtbrynhildr.soをjavaでwrappingするんですね。Qt for androidがサポートしているのはarm v7とarm v5らしいですが、この条件で大体のandroidがサポートできるのかは謎です。スマホ、タブレットで解像度もいろいろあるらしいし。

アプリとしてリリースするにはまだまだ前途多難な気がします。とりあえず、Windows7/8/8.1とUbuntu(x86/x64)で動くものをまず目標としたいです。その後androidですかね。iOS/Angstromとかもあるなぁ…

今回は以下を読みました。

「良いAndroidアプリを作る139の鉄則」 技術評論社

アプリ開発の全体を眺めるにはよいかもしれません(個人の見解です)。

通信スレッドの構造がマズイらしい…

データ通信のチェックを始めました。
で、サーバへの接続を実行するとすぐエラーとなりました(;ω;)

$ ./release/qtbrynhildr.exe
[ControlThread] start thread...
[GraphicsThread] start thread...
[SoundThread] start thread...
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTcpSocket(0x3928c50), parent's thread is QThread(0x1ffbe08), current thread is brynhildr::ControlThread(0x3918b30)

現在のスレッドはControlThreadだけど, QTcpSocketが違うスレッドに属してますよ、と怒られました。
QThreadを継承したControlThreadは、メインスレッドで生成しますが、その際にQTckSocketも生成していました。そのため、QTcpSocketはメインスレッドに属するものとなっていたようです。

これを回避するにはQTcpSocketの実体をメインスレッドでなくControlThreadで生成する必要があります。
具体的には、ControlThreadにはQTcpSocketへのポインタのみを生成しておきます。で、通信を始める時に動的にQTcpThreadを生成し、通信を閉じる時に削除するように変更します。

スレッドはやっぱりいろいろ考慮しなければいけないことが多くて面倒ですね。

2014年8月24日 (日)

スレッド起動までは出来たのですが

スレッドの起動、停止まで確認しました。

$ ./release/qtbrynhildr.exe
[ControlThread] start thread...
[GraphicsThread] start thread...
[SoundThread] start thread...
[ControlThread] stop thread...
[GraphicsThread] stop thread...
[SoundThread] stop thread...


通信の開始とデータ転送の確認をしようと思ったのですが、ちょっと気になったのでタスクマネージャを起動しながら、再度実行しました。

「うーん、CPU使用率が80%を超えている…」

レスポンスはいいのですが、システム全体としてこの重たさは頂けません。

どうやらQThreadの派生クラスのスレッド処理本体であるメソッドrun()が問題のようです。

Qtではネットワーク通信の処理は基本的に非同期でsignal/slotの仕組みを利用して実装できます。ですので、run()の中でやることは、最初の通信のオープンとクローズ処理だけで事実上あとはなにもしません。
ですので、こんな風にbusy loopにしてました。3つのスレッドがビジーループしていたのです。CPUの無駄遣いですね。
// thread
void NetThread::run()
{
  cout << "[" << name << "]" << " start thread..." << endl << flush;

  // busy loop
  while (!stopped){
	//
	if (startToConnect && !connected){
	  connected = connectToServer();
	  if (!connected){
		// failed to connect to server
		startToConnect = false;
		stopped = true;
	  }
	}
  } // busy loop

  // stopped == true

  // close connection
  if (connected){
	disconnectToServer();
  }

  // clear flag
  stopped = false;

  cout << "[" << name << "]" << " stop thread..." << endl << flush;
}


そこで、ビジーループの中でThreadをsleepさせて、CPUを解放するようにしました。これによりCPU使用率は数%まで落ちました。

// thread
void NetThread::run()
{
  cout << "[" << name << "]" << " start thread..." << endl << flush;

  // busy loop
  while (!stopped){
	//
	if (startToConnect && !connected){
	  connected = connectToServer();
	  if (!connected){
		// failed to connect to server
		startToConnect = false;
		stopped = true;
	  }
	}

	// thread sleep
	msleep(QTB_NETTHREAD_SLEEP_TIME); //  <= ここでsleepする
  } // busy loop

  // stopped == true

  // close connection
  if (connected){
	disconnectToServer();
  }

  // clear flag
  stopped = false;

  cout << "[" << name << "]" << " stop thread..." << endl << flush;
}

ただし、0.5(s)ほどsleepさせるので、Threadの終了(stopppedフラグのチェック)まで最大0.5(s)ほど掛かり、結果3つのスレッドを停止するのに最大1.5(s)ほど掛かるようになりました。ま、起動中のシステムの重さに比べれば終了時の少しのモタツキくらいはいいかなと判断しました。

さて、やっとデータ転送の確認に入れます…

2014年8月22日 (金)

マルチスレッド化と入力デバイス制御実装とソースコードの整理

入力デバイス用の通信、デスクトップ画像転送/再生、サウンドデータ転送/再生の3つのマルチスレッド化とWindows向けの入力デバイス(キーボード、マウス)の実装とソースコードの整理をぼちぼちとしてます。

まず、入力デバイス。QtのイベントをWindowsのメッセージ相当に変換するクラスを設けました。キーボードについてはサーバのキーボートに対応したメッセージへ変換する必要があるので、共通部分を抽象クラスとして実装してキーボードごとの変換処理を派生クラスで実装します。USキーボードとか使いたい人には派生クラスを実装してもらいましょう(^_^;

とりあえずは私が使っているキーボードのみに対応します。Androidなどへ対応もこのクラスの派生クラスで対応できるのではないかと思いますが…

マルチスレッド化は、QtのスレッドクラスであるQThreadを派生したものをスーパークラス(NetThread)にしました。これをもとにそれぞれ入力デバイス、画像、サウンドの派生クラスを構成します。基本的に3つのスレッドは各自勝手に走ります。但し、通信開始の最初方だけ手順が決まっているので、その仕組みを入れました。

派生クラスでは、各々入力デバイスのデータ通信、画像、サウンドを再生する仕組みを実装します。

あとは力仕事なんですが、キーボード実装が結構面倒です…#機械的にできればいいのですが。

2014年8月15日 (金)

ノイズが出るのは…

やはり、3つの通信をシングルスレッドでやろうとしたため…ですね…

Qtの通信イベントでやればいいかなぁと安直に考えていましたが、タイミングが良ければ綺麗にPCMデータを再生できるのですが、かなりの確立でPCMデータが間に合わわずノイズだらけです。

すべての通信を別スレッドにしないとどちらにしてもGUIが固まったりすることは予想できたのですが…

2014年8月13日 (水)

キーボード操作実装の準備をしましょう…

Brynhildrの作者さんから操作系通信のサンプルソースを頂いたので、ぼちぼちとまずキーボード操作の実装を始めました。

最終的には、iOS, Android, Surface Proなどタッチパネル系のデバイスも対応したいのですが、まずはWindows/Linuxなどのキーボード/マウスデバイスへの対応を進めます。確認が容易なので…(*^-^)

通信内容の概要としては、Windowsの仮想キーコード(実際はマウスも含まれたりしてますが…)を送ればよいようです。Windowのメッセージ通信をトラップして、内容をバッファリングして、適宜送り出すようですね。

QtではWindowsの仮想キーコードに依存しないデータ構造を使っているので、うまく変換してやる必要があります。Qt AssistantでKey_Upとかで検索するとQtというヘッダでキーボード関係のデータ構造が定義されていますね。

ただ、予想通りWindowsの仮想キーコードとQtのイベントで渡されるデータとは一対一対応しないようです。
例えば、ダブルクォート(")はQtではSHIFTキーのデータの後ダブルクォート(")の情報が送られてきますが、WindowsではSHIFTキーのデータの後2キー(日本語キーボードの場合)のデータが送られてきます。Qtでは入力された文字の情報そのものが来ますが、Windowsでは実際の操作が送られてくるので、受け取ってから、それを解釈する必要があるということです。

このため、Qtから受け取ったデータを1つ以上のWindows仮想キーコードに変換してやる必要があります。日本語キーボードだけなら、やっつけコーディングでもいいのですが他の言語用キーボードも考慮する必要があるので、少しデータ構造を検討する必要がありそうです。今日はとりあえずここまでにしておきます。

P.S.
Nexus 7 (2013:Wi-Fi Only Model) を購入しましたが、中々楽しいですね…
KitKat(4.4.2)にバージョンアップしてしまいましたが、大丈夫かな…

多言語対応のその後

少しソースコードの整理をして,Qt5.3.1にバージョンアップしたので、チェックのために再ビルドしました。
その際一部メニューの日本語表記を変更したので、lupdateコマンドを実行後Linguistで修正後lreleaseを実行しました。

すると…メインウィンドウの表記が軒並み英語表記になってしまいました。

いろいろ変更して、何度もlupdateとlreleaseを実行したのですが、うんともすんともいいません…

数時間を費やしても結局理由が分からず、結局最後の手段でソースコード整理前のソースコードを引っ張りだしてきました。

恥ずかしい話ですが、ソースコードの整理前にローカルのgitリポジトリも綺麗さっぱり削除してしまったのです。暫定リポジトリだったので、綺麗にして再登録しようとしたのが仇となりました。そのおかげで修正後差分がすぐ取れない状況だったので…ドツボにハマりました。

結局整理前のディレクトリを別な場所に展開し、ディレクトリごと差分を調べました。すると…

transrations/brynhildr_ja_JP.ts

に怪しい差分がありました。

@@ -2,7 +2,7 @@
<!DOCTYPE TS>
<TS version="2.1" language="ja_JP">
<context>
- <name>brynhildr::Brynhildr</name>
+ <name>Brynhildr</name>
<message>
<location filename="../brynhildr.cpp" line="64"/>
<source>Bootup.</source>

「やや、名前空間の記述が消えている」

名前空間が指定されていないので、グローバル空間のBrynhildrクラスへの指定となってしまっています…
名前空間が違うので反映されるはずがありません。

ソースコードの整理作業が原因でした。

emacsでの編集で、インデントが気に入らなかったので、名前空間の指定記述部分をマクロに置き換えてました。
つまり、

#define QTB_BEGIN_OF_NAMESPACE namespace brynhildr {
#define QTB_END_OF_NAMESPACE }

と定義して、このマクロを使って名前空間を定義していたのです。

lupdateコマンドは、すべてのソースコードを解析し、namespaceを見つけた場合その名前空間も付加して正しい名前空間指定を.tsファイルに出力するのですが、マクロ展開前のソースコードを解析しているのかもしれません…
結果brynhildrという名前空間を見つけられずに、誤った指定を出力してしまったようです。

自業自得といえばそのとおりなんですが…

gitで差分を取ることができれば数分で分かったはずですが、結局半日近く無駄にしてしまいました。
整理が終わるまでgitリポジトリは消すべきではありませんでした。

« 2014年7月 | トップページ | 2014年9月 »