APropertyPrecomputed#
Readonly property that holds a value computed by an expression.
| Header: | #include <AUI/Common/APropertyPrecomputed.h> |
| CMake: | aui_link(my_target PUBLIC aui::core) |
Detailed Description#
Experimental Feature
This API is experimental. Experimental APIs are likely to contain bugs, might be changed or removed in the future.
APropertyPrecomputed<T> is a readonly property similar to AProperty<T>. It holds an instance of T as well.
Its value is determined by the C++ function specified in its constructor, typically a C++ lambda expression.
See property system for usage info.
APropertyPrecomputed<T> is a readonly property similar to AProperty<T>. It holds an instance of T as well.
Its value is determined by a reactive expression specified in APropertyPrecomputed<T>'s
constructor, typically a C++ lambda.
It's convenient to access values from another properties inside the expression. The properties accessed during
invocation of the expression are tracked behind the scenes so they become dependencies of APropertyPrecomputed
automatically. If one of the tracked properties fires changed signal, APropertyPrecomputed invalidates its
T. APropertyPrecomputed follows lazy semantics so the expression is re-evaluated and the new
result is applied to APropertyPrecomputed as soon as the latter is accessed for the next time.
In other words, it allows to specify relationships between different object properties and reactively update
APropertyPrecomputed value whenever its dependencies change. APropertyPrecomputed<T> is somewhat similar to
Qt Bindable Properties.
APropertyPrecomputed is a readonly property, hence you can't update its value with assignment. You can get its
value with value() method or implicit conversion operator T() as with other properties.
Declaration#
Declare a property with custom expression determining it's value as follows:
/// [APropertyPrecomputed User]
struct User {
AProperty<AString> name;
AProperty<AString> surname;
APropertyPrecomputed<AString> fullName = [&] { return "{} {}"_format(name, surname); };
};
/// [APropertyPrecomputed User]
class LogObserver : public AObject {
public:
void log(const AString& msg) {
ALogger::info("LogObserver") << "Received value: " << msg;
}
};
auto u = aui::ptr::manage_shared(new User {
.name = "Emma",
.surname = "Watson",
});
auto observer = _new<LogObserver>();
EXPECT_CALL(*observer, log(AString("Emma Watson"))).Times(1);
AObject::connect(u->fullName, AUI_SLOT(observer)::log);
The example above prints "Emma Watson". If we try to update one of dependencies of APropertyPrecomputed (i.e.,
name or surname), APropertyPrecomputed responds immediately:
EXPECT_CALL(*observer, log(AString("Emma Stone"))).Times(1);
u->surname = "Stone";
The example above prints "Emma Stone".
Valid Expressions#
Any C++ callable evaluating to T can be used as an expression for APropertyPrecomputed<T>. However, to
formulate correct expression, some rules must be satisfied.
Dependency tracking only works on other properties. It is the developer's responsibility to ensure all values referenced in the expression are properties, or, at least, non-property values that wouldn't change or whose changes are not interesting. You definitely can use branching inside the expression, but you must be confident about what are you doing. Generally speaking, use as trivial expressions as possible.
struct User {
AProperty<AString> name;
AProperty<AString> surname;
APropertyPrecomputed<AString> fullName = [&]() -> AString {
if (name->empty()) {
return "-";
}
if (surname->empty()) {
return "-";
}
return "{} {}"_format(name, surname);
};
};
In this expression, we have a fast path return if name is empty.
User u = {
.name = "Emma",
.surname = "Watson",
};
// trivial: we've accessed all referenced properties
EXPECT_EQ(u.fullName, "Emma Watson");
name to "", we don't access surname. If we try to trigger the fast path return:
surname can't trigger re-evaluation anyhow. Re-evaluation can be triggered by name only. So, at the moment,
we are interested in name changes only.
APropertyPrecomputed might evaluate its expression several times during its lifetime. The developer must make
sure that all objects referenced in the expression live longer than APropertyPrecomputed.
The expression should not read from the property it's a binding for, including other referenced APropertyPrecomputes. Otherwise, there's an infinite evaluation loop, and AEvaluationLoopException is thrown.
Copying and moving APropertyPrecomputed#
Warning
Despite the underlying value and factory callback are both copy constructible and movable, the copy and move constructor are explicitly deleted to avoid potential object lifetime errors created by the lambda capture and prevent non-intuitive behavior.
User
If copy construction of APropertyPrecomputed were possible, consider the following code:
User user { .name = "Hello" };
auto copy = user; // WON'T COMPILE
auto moved = std::move(user); // WON'T COMPILE
copy has copied factory function of user, which refers to fields of user, not to copy's fields. Copy
construction of a class or struct discards default values of all fields - this is the way APropertyPrecomputed's
factory function is set to APropertyPrecomputed.
Examples#
examples/7guis/timer/src/main.cpp
7GUIs Timer - Timer example.
high_resolution_clock::time_point mStartTime = high_resolution_clock::now();
AProperty<high_resolution_clock::time_point> mCurrentTime;
AProperty<high_resolution_clock::duration> mDuration = 30s;
APropertyPrecomputed<high_resolution_clock::duration> mElapsedTime = [&] {
return std::min(mCurrentTime - mStartTime, *mDuration);
};
APropertyPrecomputed<aui::float_within_0_1> mElapsedTimeRatio = [&] {
return float(mElapsedTime->count()) / float(mDuration->count());
examples/7guis/cells/src/Cell.h
7GUIs Cells - Spreadsheet processor (Excel).
Public Methods#
invalidate#
Marks this precomputed property to be reevaluated.
In common, you won't need to use this function. APropertyPrecomputed is reevaluated automatically as soon as one updates a property expression depends on.