C++のRxにおけるダブルクリックの実装

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
inemaru
記事: 108
登録日時: 3年前

C++のRxにおけるダブルクリックの実装

#1

投稿記事 by inemaru » 2年前

お世話になります。

現在、「Reactive Extensions」の勉強として
rxcppを使用して、ダブルクリックのイベント処理を実装しています。
実装途中で、行き詰まり質問することにしました。

ダブルクリックのイベントを定義する際に、
「時間内に2回クリックしたとき」を表現したいのですが、イマイチ実装がわかりません。

とりあえず、bufferメソッドを使用して2個ずつイベントを通知する実装としている為、
「2回クリックしたとき」しか表現できていない状態です。
ここに、制限時間を定義するにはどうしたらよいでしょうか。
可能であれば、具体的な実装例が欲しいです。

宜しくお願い致します。

下記は、再現コードです。
  • C++の理解度
    C++11入門書レベルの内容であれば、辛うじてわかります。
  • 環境
    VS2015(Unicodeプロジェクト) / Windows 10
    Reactive Extensions for C++ 3.0.1

コード:

/*
*	・「Reactive Extensions」の調査
*		- RxCppによるダブルクリックの実装
//*/

#include "DxLib.h"      // DxLib

// nuget rxcpp 3.0.1
// This software includes the work that is distributed in the Apache License 2.0
#include <rx.hpp>
namespace Rx
{
	using namespace rxcpp;
	using namespace rxcpp::subjects;
	using namespace rxcpp::operators;
	using namespace rxcpp::util;
}

// DXライブラリ簡易ラップクラス
template<int windowWidth_ = 640, int windowHeight_ = 480, int colorBitDepth_ = 32>
struct DxLibApp final
{
	DxLibApp(const wchar_t* pWindowText = L"無題")
	{
		DxLib::SetWindowText(pWindowText);
		SetGraphMode(windowWidth_, windowHeight_, colorBitDepth_);
		ChangeWindowMode(TRUE);
		if (DxLib_Init() == -1) {
			throw;
		}
		SetDrawScreen(DX_SCREEN_BACK);
	}

	~DxLibApp() {
		DxLib_End();
	}

	static bool Update() {
		return (ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0);
	}
};

// 簡易入力クラス
struct Input final
{
	Input()
		: mouse_()
	{}

	~Input() {}

	inline int Update() {
		mouse_.Update();
		return 0;
	}

	struct Mouse final
	{
		Mouse()
			: keyBuffer_(0)
			, backKeyBuffer_(0)
		{}

		inline int Update() {
			backKeyBuffer_ = keyBuffer_;
			keyBuffer_ = GetMouseInput();
			return 0;
		}

		inline bool IsPress(const int keyCode) const {
			return ((keyBuffer_ & keyCode) != 0);
		}

		inline bool IsDown(const int keyCode) const {
			return ((keyBuffer_ & keyCode) && !(backKeyBuffer_ & keyCode));
		}

		inline bool IsUp(const int keyCode) const {
			return (!(keyBuffer_ & keyCode) && (backKeyBuffer_ & keyCode));
		}

	private:
		int  keyBuffer_;
		int  backKeyBuffer_;
	};

	Mouse		mouse_;
};

static DxLibApp<> app(L"Rxによるイベント処理");
static Input input;

// エントリーポイント
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	// Updateイベント (<int>はダミー)
	Rx::subject<int> update_subject;
	auto update_subscriber = update_subject.get_subscriber();
	auto update_observable = update_subject.get_observable();

	auto clickStream = update_observable
		.filter([](int) {return input.mouse_.IsDown(MOUSE_INPUT_LEFT);});

	// ここのイベント通知の定義に、
	// ダブルクリックらしく制限時間を設けたい
	auto doubleClickStream = clickStream.buffer(2);
	auto send_mousePos = doubleClickStream
		.map([](std::vector<int>) {
			// とりあえず bool型を送信
			return true;
	});
	auto subscription = send_mousePos.subscribe([](bool) {
		printfDx(L"2クリック\n");
	});
	
	// メインループ
	while (DxLibApp<>::Update() && input.Update() == 0)
	{
		// Update通知
		update_subscriber.on_next(1);
	}

	// 解除
	subscription.unsubscribe();

	return 0;
}


Math

Re: C++のRxにおけるダブルクリックの実装

#3

投稿記事 by Math » 2年前

http://csi.nisinippon.com/r0.png
を始め私の環境ではエラーをなくすのが大変。確実に動くところからC++11,14,17のイディオム集、コンセプト、ポインター・インプリメンテーションの話に入るのならいいけど。時間がかかりすぎるので確実に動くものになったら教えてください。(Z80のエミュレータができてから時間があったらかんがえます。そのうちだれか答えを出されるでしょう)

YuO
記事: 941
登録日時: 8年前
住所: 東京都世田谷区

Re: C++のRxにおけるダブルクリックの実装

#4

投稿記事 by YuO » 2年前

inemaru さんが書きました:とりあえず、bufferメソッドを使用して2個ずつイベントを通知する実装としている為、
「2回クリックしたとき」しか表現できていない状態です。
ここに、制限時間を定義するにはどうしたらよいでしょうか。
前回の時間との差がある程度より小さければダブルクリックになるのですから,単純に前回との差を調べればよいことになります。
面白そうなので,コードを書いてみました。

コード:

// プログラムの先頭に, #define NOMINMAXを書いておくこと。<windows.h>のminマクロのために,minの呼び出しでエラーになる。
    auto doubleClickStream = clickStream
        .filter([](int) {
        static std::chrono::steady_clock::time_point last = std::chrono::steady_clock::time_point::min(); // 初期の「前回の時刻」はminを使う。
        auto now = std::chrono::steady_clock::now();
        auto duration = now - last;
        auto count = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); // 現在時刻と前回の時刻の差をミリ秒で取得
        if (0 < count && count < 500) { // 500ms未満はダブルクリック
            last = std::chrono::steady_clock::time_point::min(); // ダブルクリックを検知したので,前回の時刻は「押していない」状態
            return true;
        }
        else {
            last = now; // ダブルクリックでなかったので,前回の時刻は「今回の時刻」
            return false;
        }
    });

    auto send_mousePos = doubleClickStream
        .map([](int) {
        // とりあえず bool型を送信
        return true;
    });
他のデータが載る場合は,前回のデータの扱いを考えないといけませんが……。
オフトピック
「Rx ダブルクリック」でググってRxJava on Androidのコードをもとに考えついたのは,buffer_with_time_or_countを使う解でした。
ただ、coordinationを指定しないとメインループを止めるので別スレッドで回す必要があったり、指定時間ごとに次段が呼ばれるためfilterが必要になる、時間ごとの区切りを超えるダブルクリックを検知できないなどの問題があるものです。

コード:

    auto doubleClickStream = clickStream
        .buffer_with_time_or_count(std::chrono::milliseconds(500), 2, rxcpp::observe_on_new_thread())
        .filter([](std::vector<int> & v) {
            return v.size() == 2;
        });
これを書いて見たものの,正攻法の方が確実なので,正攻法にしてみました。

inemaru
記事: 108
登録日時: 3年前

Re: C++のRxにおけるダブルクリックの実装

#5

投稿記事 by inemaru » 2年前

Yuo さん 回答ありがとうございます"
YuO さんが書きました: 前回の時間との差がある程度より小さければダブルクリックになるのですから,単純に前回との差を調べればよいことになります。
面白そうなので,コードを書いてみました。
分かりやすいコードを用意していただき助かりました。
提示して頂いたコードを使用する事で、ダブルクリックが実現できました。

Reactive Extensions 的なコードを意識しすぎて難しく考えすぎていたようです。
YuO さんが書きました:
オフトピック
「Rx ダブルクリック」でググってRxJava on Androidのコードをもとに考えついたのは,buffer_with_time_or_countを使う解でした。
ただ、coordinationを指定しないとメインループを止めるので別スレッドで回す必要があったり、指定時間ごとに次段が呼ばれるためfilterが必要になる、時間ごとの区切りを超えるダブルクリックを検知できないなどの問題があるものです。

コード:

    auto doubleClickStream = clickStream
        .buffer_with_time_or_count(std::chrono::milliseconds(500), 2, rxcpp::observe_on_new_thread())
        .filter([](std::vector<int> & v) {
            return v.size() == 2;
        });
これを書いて見たものの,正攻法の方が確実なので,正攻法にしてみました。
言われてみれば確かに、
bufferの範囲をまたぐ判定が行えない問題がありました。

timer関係の機能は、Rxを使用する事で扱いやすいくなっているようですが、
別スレッドで並列処理を意識する必要があるので
微妙な感じになってしまいますね。

[hr]

Math さん 確認ありがとうございます。
Math さんが書きました:http://csi.nisinippon.com/r0.png
を始め私の環境ではエラーをなくすのが大変。確実に動くところからC++11,14,17のイディオム集、コンセプト、ポインター・インプリメンテーションの話に入るのならいいけど。時間がかかりすぎるので確実に動くものになったら教えてください。(Z80のエミュレータができてから時間があったらかんがえます。そのうちだれか答えを出されるでしょう)
これについてですが、VS側の都合だと思います。

自分の環境でも、表示されていますが
コンパイル/実行 は問題なく行えます。

[hr]

YuO さん から頂いた実装を使用する事で、
ダブルクリックは実現できました。
ありがとうございます。

Rxらしい一時変数を定義しないような実装が可能か
もう少し、調査してみたいと思います。

進展があり次第、追記します。

inemaru
記事: 108
登録日時: 3年前

Re: C++のRxにおけるダブルクリックの実装

#6

投稿記事 by inemaru » 2年前

進展あったので 追記/解決 します。

非常にコンパクトにまとまりました。
Rxの恩恵が大きいと感じます。

最終的な実装は下記の通りです。

コード:

auto doubleClickStream = clickStream
	.time_interval()		// 前回からの経過時間を送信
	.filter([](auto v) {	// 500ms より短いイベントのみ処理
	using namespace std::chrono;
	return (duration_cast<milliseconds>(v).count() < 500);
});

auto send_mousePos = doubleClickStream
	.map([](auto) {
	// とりあえず bool型を送信
	return true;
});
time_intervalメソッドを使用する事で、
前回のイベントからの経過時間を取得できるようです。
余り、(特に日本語の)ドキュメントが揃っていないので、このメソッドの存在を見落としていました。

時間が空いてしまいましたが、これで解決とします。
ありがとうございました。

返信

“C言語何でも質問掲示板” へ戻る