Priority: P2: Important
Affects Version/s: None
Fix Version/s: None
Component/s: Core: Object Model
I feel it would be beneficial to add to QProperty a feature where you can update multiple properties in a "transaction", then only rerun dependent property bindings and callbacks once even if they depend on multiple properties updated. This would result in less redundant work being performed.
I recently discovered a library which achieves this in C++:
- Update minimality: nothing is re-calculated or processed unnecessarily
- Glitch freedom: no transiently inconsistent data sets
To perform several changes atomically they should be grouped into a transaction
QProperty's current design can lead to performance issues. In the Qt Bindable Properties example, if you were to change firstname, lastname, and age, then fullname's binding function, as well as any onValueChanged and addNotifier functions, get called 3 times with intermediate states. I would prefer if you could wrap all 3 assignments in a "transaction" which queues up all binding updates until the transaction completes.
Additionally, its current design can lead to bugs from exposing inconsistent states. In the Qt Bindable Properties page, I think "Writing Bindable Properties in Transitional States" imposes a severe restriction on how QProperty can currently be used. For example, if both radius and area were QProperty, then no order of writing to the two variables could prevent callbacks from seeing an inconsistent view of the object! I feel the most logical solution is to update radius and area in a single transaction, which invokes change callbacks once committed.
What are the ways you can design a transaction API, and what are their strengths and weaknesses?
uReact's do_transaction() accepts a lambda where all property writes within are intercepted.
My solution to this problem instead constructs a "transaction object" which gets passed around, and only the transaction object provides mutable access to properties, while tracking which ones are mutated. I picked this design because I didn't know how to use thread-local variables to track mutations, the way that QProperty and uReact do it. It has the advantage (for my use case) that it's impossible to mutate a variable outside of a transaction (so you can't write sloppy code that forgets to create a transaction, then calls other code that writes to properties, resulting in exposing intermediate states by mistake). Unfortunately my approach is limited because it only tracks a fixed set of modification flags and calls a fixed set of callbacks in response to changes, and I can make a mistake and forget to call an update function (because I manually write out which functions to call in response to changes, rather than tracking which properties are read at runtime, and binding to that exact set of properties).