Using uproperty reflection system to create data-driven events

I was going through the UPROPERTY reflection system blog post recently.

I wanted to know if it possible to create an event when a UPROPERTY variable changes. Something exactly like what the ReplicatedUsing uproperty specifier does except it gets only called on the local machine where the variable changes.

I don’t want to use Tick to check when a variable changes. And I was thinking of a better alternative other than making the variable private and using Setters and Getters.

Hi, have you seen this ?

(edit: it is only for the editor, but I’d suppose there is a cooked-equivalent)

I think there isn’t a UPROPERTY specifier for that functionality. Here’s a hint towards that. You may have come across server-side code like this:

SomeClass.h:



UPROPERTY(ReplicatedUsing=OnRep_MyVar)
int32 MyVar;


SomeClass.cpp:



MyVar++;
OnRep_MyVar();


When the server changes a variable marked with ReplicatedUsing, it also calls the function manually so that it is also executed server-side. If there was a convenient method (UPROPERTY or otherwise) we would have seen that being used more.

From previous digging around in the engine source code I have definitely come across some kind of delegate that fires on property value changes, however this is definitely editor only stuff. It sounds as if you want in-game events triggered by property changes?

If so, in what way would it be better than using a setter method? If a property isn’t being changed over the network via replication, then it’s only changing because somewhere in your blueprint/code, you have caused it to change. So it makes sense that you either trigger the event manually at the point where you change it, or, if any and all changes should fire the event, then you make it modifiable only through a setter and have the setter trigger the event.

Encapsulating this kind of functionality in the reflection system would be both extremely difficult (detecting every possible way in which a variable’s value might have been changed through c++) and highly inefficient (any time you wanted to change the value of any property for any reason, there would have to be boilerplate code being run to check if any handlers need invoking).

I will try searching in the UObject related files… Lets see if something like that exists. Also thanks for the link I didn’t knew something like that also existed.

I came across this in the blog post…

Just a personal choice, I feel setters and getters takes a bit of consistency from the code. Also they don’t work in-case you are setting by reference directly.
But its more like I like using the = operator compared to using a getter and a setter.

I am planning on creating UStructs having a variable and the =operator working like a setter and getter. Not sure how good would that work… I don’t have much experience in writing wrappers or macros.

Well I would say that in this case, you should have inconsistency in order for your code to be clear: just assigning a value to a variable, and setting a variable to a new value whilst also triggering some side effect, are two different things.

You’re not going to have events getting triggered every time you change any variable; it will only be certain ones, and in those cases it should be clear in your code that this is, or may be, happening. While overriding the = op to work as a setter is functionally the same thing, I think it would be misleading to have it cause side effects other than simply assigning the new value. It goes against standard usage of that operator in c++.

Just my personal preference on the matter of course, there’s no reason it wouldn’t work.

That also makes sense…

What I was thinking of using was, Instead of having using the variable float, I can have a float in a structure like FEventFloat or something having a variable float and a delegate that binds a function taking the parameter float.

So that I can recognize what the variable does just by looking at its type. When initializing the variable in the constructor, I can give it a value as well as the function to which it shall bind.
This should work in C++ but i am not sure how =operator overloading works in blueprints but I think it shouldn’t cause any problem.

Something like this could work.
Note: plain C++ code that hasn’t been compiled nor tested.

If you need an explanation or have any questions, let me know.


#include <vector>

template<typename T>
class ChangingProperty
{
public:
	ChangingProperty()
	{
	}
	
	virtual ~ChangingProperty()
	{
		m_registeredChangeListeners.clear();
	}
	
	void addChangeListener(IChangeEventListener<T>* pListener)
	{
		if (pListener)
		{
			m_registeredChangeListeners.push_back(pListener);
		}
	}
	
	void removeChangeListener(IChangeEventListener<T>* pListener)
	{
		for (std::vector<IChangeEventListener<T>*>::iterator iter = m_registeredChangeListeners.begin(); iter != m_registeredChangeListeners.end() ++iter)
		{
			//remove element by assigning the last element over it
			*iter = m_registeredChangeListeners.back();
			//then pop_back to remove the duplicated element at the end
			m_registeredChangeListeners.pop_back();
		}
	}
	
protected:
	void notifyListeners(T * const pOldValue, T * const pNewValue)
	{
		for (std::vector<IChangeEventListener<T>*>::iterator iter = m_registeredChangeListeners.begin(); iter != m_registeredChangeListeners.end() ++iter)
		{
			(*iter)->onValueChanged(this, pOldValue, pNewValue);
		}
	}
	
private:
	std::vector<IChangeEventListener<T>*> m_registeredChangeListeners;

};


template<typename T>
class IChangeEventListener
{
public:
	virtual ~IChangeEventListener()
	{
	}
	
	virtual void onValueChanged(ChangingProperty<T>* pChangedProperty, T * const pOldValue, T * const pNewValue) = 0;

};



template<typename T>
class SimpleChangingProperty : public ChangingProperty<T>
{
public:
	SimpleChangingProperty(T defaultValue) : ChangingProperty<T>(), value(defaultValue)
	{
	}
	
	virtual ~SimpleChangingProperty()
	{
	}
	
	void setProperty(T newValue)
	{
		T oldValue = value;
		value = newValue;
		notifyListeners(&oldValue, &value);
	}
	
	T getProperty()
	{
		return value;
	}
	
private:
	T value;
	
};


class Example : IChangeEventListener<float>
{
public:
	Example() : m_someFloat(0.0f)
	{
		m_someFloat.addChangeListener(this);
	}
	
	virtual ~Example()
	{
	}
	
	void doSomething()
	{
		m_someFloat.setProperty(42.0f);
	}
	
	virtual void onValueChanged(ChangingProperty<float>* pChangedProperty, float * const pOldValue, float * const pNewValue)
	{
		printf("float value changed from %f to %f
", *pOldValue, *pNewValue);
	}
	
private:
	SimpleChangingProperty<float> m_someFloat;

}

1 Like

Thanks for the code…

I will check and see if it works.