はかせのラボ

私の頭の中を書いていく雑記ブログです

C++ タスクシステム⑩ ~ReactiveProperty~

あいさつ

どうも、はかせです。
今回はReactivePropertyの実装をしていきます。

ReactivePropertyとは

変数+Subjectのようなものです。
値が変化したら通知してくれます。

実装方法

前回の能動的に動くSubjectの実装とほとんど同じです。

前回は別スレッドで通知を行っていましたが、
今回は値が変わったら通知を行います。

オペレータのオーバーロード

値の変更を検出する方法はいくつかあると思います。
今回はc++のオペレータのオーバーロードで実装したいと思います。
理由としてはReactivePropertyは変数+Subjectという説明をしました。
なので使い方をなるべく普通の変数に寄せたいということで
この方法を選択しました。

オペレータのオーバーロードは簡単です。
普段メンバ関数を作るときと同じ要領で作成できます。
違うのは+や-をメソッド名にできないので
operatorというキーワードをつけて宣言します。

T operator +(T value)
{
	mValue += value;
	std::cout << "RP" << std::endl;
	mSubject->OnNext(mValue);
	return mValue;
}

オーバーロードした中身でOnNextをしています。

C++は組み込み型の初期化をしてくれない

今回の実装をしていく中で詰まって調べたことです。
C#Java等の言語では組み込み型やプリミティブ型と呼ばれる型は
初期値を設定しない限り0なりnullなりの既定の初期値が入ります

ですが、C++では既定の初期値は代入されず値は不定となります
なぜそうなっているかの根拠や理由は発見できませんでした。
「変数の初期化ぐらいプログラマーが責任もってやれや!」
というC++からのメッセージなのかもしれませんね。

ということで初期化を自前実装しました。
といっても何かテクいことをしているわけではなく
オーバーロードでひたすらメソッドを増産してやってます。

//今後便利クラスとか作ったらここに追加していこう
namespace MyLib
{
	/*
	Init
	各組み込み型を初期化する
	c++は組み込み型を初期化してくれないみたいだから自前実装
	*/
	void Init(int & value)
	{
		value = 0;
	}

	void Init(double & value)
	{
		value = 0.0;
	}

	void Init(float & value)
	{
		value = 0.0f;
	}
}

私はReactivePropertyをスコア管理とかに使おうと考えているので
よく使う数値型3つを実装しました。
今後必要に応じて増やします。

実装

では実際のコードです。

template <class T>
class ReactiveProperty
{
public:
	//初期値なし
	ReactiveProperty() 
	{
		mSubject = new Subject<T>();
		MyLib::Init(mValue);
	};
	//初期値あり
	ReactiveProperty(T value) 
	{
		mSubject = new Subject<T>();
		mValue = value;
	};
	//関数登録
	virtual void Subscribe
        (std::function<void(T value)> next);
	virtual void Subscribe
        (std::function<void(T value)> next, 
         std::function<void()> completed);
	virtual void Subscribe
        (std::function<void(T value)> next, 
         std::function<void()> completed, 
         std::function<void()> error);
	//オペレータオーバーロード
	T operator +(T value)
	{
		mValue += value;
		std::cout << "RP" << std::endl;
		mSubject->OnNext(mValue);
		return mValue;
	}
	//値を取得
	T GetValue() const { return mValue; }
	//解放
	void Dispose() 
	{
		mSubject->OnCompleted();
		delete this; 
	};
private:
	Subject<T>* mSubject;
	T mValue;
	~ReactiveProperty() 
	{
		std::cout << "Delete RP!" << std::endl;
	};
};
template<class T>
inline void ReactiveProperty<T>::Subscribe
(std::function<void(T value)> next)
{
	mSubject->Subscribe(next);
}
template<class T>
inline void ReactiveProperty<T>::Subscribe
(std::function<void(T value)> next, 
 std::function<void()> completed)
{
	mSubject->Subscribe(next, completed);
}
template<class T>
inline void ReactiveProperty<T>::Subscribe
(std::function<void(T value)> next,
 std::function<void()> completed,
 std::function<void()> error)
{
	mSubject->Subscribe(next, completed, error);
}

使い方です。

int main(int argc, char const* argv[])
{
#if _DEBUG
	_CrtSetDbgFlag
       (_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
#endif
	//int型のReactiveProperty生成
	auto intrp = new ReactiveProperty<int>();
	//初期値設定版
	auto intrp2 = new ReactiveProperty<int>(10);

	//処理登録
	intrp->Subscribe
	(
		[&](int value)->void {std::cout << "Value:" << value << std::endl; },
		[&]()->void {std::cout << "OnCompleted!" << std::endl; }
	);

	intrp2->Subscribe
	(
		[&](int value)->void {std::cout << "Value:" << value << std::endl; },
		[&]()->void {std::cout << "OnCompleted!" << std::endl; }
	);

	getchar();
	//オペレータのオーバーロードは実体じゃなきゃ無理ぽ?
	auto rp = *intrp + 10;
	auto rp2 = *intrp2 + 10;

	getchar();
	//解放
	intrp->Dispose();
	intrp2->Dispose();
	getchar();
}

実行結果です。
f:id:hakase0274:20181228233823p:plain

あとがき

今回はReactivePropertyの実装でした。
実装はシンプルですが結構使い勝手のいいものです。

体力をReactivePropertyで作って体力に応じた処理をさせたり
(自身のテクスチャを無傷のものから中破ぐらいのものに変えたり)
スコアを管理したり
(スコアの変更を受けて表示するテキストを変更したり)

本プロジェクト導入が楽しみです。
それでは今回はこの辺でノシ