-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes slow update in field calculator #8177
Fixes slow update in field calculator #8177
Conversation
by blocking the vector signals ... ... and emitting dataChanged at the end. I'm a bit worried of side effects, but I can't see any other solution. The root of the issue here is that for each changed field/row an attribute valueChanged signal is emitted, and the QgsVectorLayerCache::featureAtId loads the feature again.
@m-kuhn I'd appreciate a slice of your brain time here. |
Just a thought -- both the attribute table dialogs and field calculator live in app, so couldn't we directly block just the attribute table dialog updates from field calculator? That'd avoid any unwanted side effects which would come from the global signal blocking (which I'm also concerned about) |
Thanks for tackling this by the way! |
much better approach ...
@nyalldawson I slept on it and came to a similar conclusion: blocking just the layer cache signals, from the attribute table scope: simpler, safer, better (TM) |
But what about the situation where a user has an attribute table open, and then uses the field calculator from the main window toolbar? Or layer properties window? |
I don't understand your proposal: QgsFieldCalculator is instanciated inside the field calculator, it does not live in app. Btw, you are right and my solution only works for the attribute table that directly opened the field calculator. It does not solve the issue for other attr tables that might have been opened separately on the same vector layer or for field calculator opened from the properties dialog. |
What I mean is that they are both app library classes. So we could potentially call from the field calculator something like QgisApp::instance()->blockTableUpdates, which then blocks the updates of all open table dialogs. |
Fixes qgis#20094 - Extracting z-coordinates takes ages and makes the system unresponsive
Actually, the problem was not strictly related to the field calculator: rollbacks of massive changes triggered the bug as well, I've fixed that too. |
src/app/qgisapp.h
Outdated
* | ||
* \since QGIS 3.4 | ||
*/ | ||
void blockAttributeTableUpdates( const QgsVectorLayer *layer ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could use one signal and one slot here with a bool blocked
argument
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I first implemented it like that. But I was in doubt, because in general we don't like bool switches, do we?
But you solved my doubt, so I'm fine with the bool now. (I've always thought that blockSignals(bool)
was not very consistent with the rest of Qt API).
if ( layer == mLayer ) | ||
this->blockCacheUpdateSignals( false ); | ||
} ); | ||
// Massive rollbacks can also freeze the GUI due to the feature cache signals |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have the same issue with attribute addition/deletion too, don't we?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I you mean removing the columns as a whole, apparently not, at least I didn't observe the issue when removing the z
field, I'll double check it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed: with my PR removing a colum is FAST.
One thing I'm not sure of here -- do we need to do a single "update cache/model" call after the calculator finishes? |
To be honest I wasn't sure either, here we have a babel of signals/slots firing. It's a spagetti-signal-mess! It wouldn't be much much easier if we could have just three signals?
|
Are you referring only to qgis implemented signals? or also to all emitted by qt infrastructure? |
@luipir nevermind: I know that's not going to happen until QGIS 7 I was talking about the various signals emitted by QgsVectorLayer when editing. |
First thing on the api break list for qgis 7 -- "rm -rf src/gui/attributetable" ;) But in all seriousness -- attribute table is crying out for a complete, ground up rework. |
IIRC the idea was to use the |
@elpaso did you read my comment? |
Yes, I'll have a look to that signals, just to be sure: are you suggesting that the attribute table should listen to those signals and block feature cache signals instead of the solution I've implemented in this PR? Something like:
|
I would avoid to block signals if somehow possible. The original idea was (IIRC) to set a variable inEditCommand (or similar) and then buffer all incoming change notifications but defer updating the attribute table to the end. This way a full cache invalidation and potentially expensive reload can be avoided for partial updates. I am not sure what the current problem is: either the approach explained here is not implemented properly and the buffering somehow fails, or if it is actually the signal-and-buffering code and not the attribute table update code that is too expensive and makes the calculation slow. An alternative would be to selectively disconnect/reconnect the signals inside the attribute table instead of completely avoiding the emission of signals if it's actually the connections which are causing the troubles. |
connect( QgisApp::instance(), &QgisApp::attributeTableUpdateBlocked, [ = ]( const QgsVectorLayer * layer, const bool blocked ) | ||
{ | ||
if ( layer == mLayer ) | ||
this->blockCacheUpdateSignals( blocked ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not python, we don't need this
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for reminding me that it's not Python but C++, I normally use this->
to get autocompletion and I forget to remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ctrl+space is your friend
@m-kuhn the original issue was that the field calculator loops through all records and change attribute values, but the feature cache listens to that signal (among others) and called |
And in the etc. etc. part it will actually send requests to the provider? The code was likely written under the assumption that the feature should already be cached in the edit buffer at this time. What are your thoughts on
|
About this fix (that is MUCH appreciated): when clicking "ok" in the field calculator as also when saving edits the table is refreshed a couple of times, is this expected? |
@gioman I assume you are testing #8200 . But, the important thing to check is that it does not hang and it completes in a reasonable time (in my test 60k rows with a default cache size of 10k completeted in ~8 seconds). Note that #8200 is a few seconds slower than #8177 because I'm not blocking all signals but just deferring them to a later stage (and prevent cache reloads). |
Thanks for the explanation! |
@elpaso I believe there are no notifications around that could be used for one single refresh even if someone came forward with the most beautiful mess of code, but I'm probably wrong. |
I can't find a QT way to do debouncing as in JS https://davidwalsh.name/javascript-debounce-function, this could (hopefully) avoid to retrigger events in short intervals. Probably with QEvetFilter depending on running context? |
The diff is misleading, it's just a single
change in the logic: block the vector signals ...
... and emit dataChanged at
the end.
I don't really like the idea of blocking
signals (the edit buffer basically)
and I'm a bit worried of side effects,
but I can't see any other solution.
The root of the issue here is that
for each changed field/row an attribute
valueChanged signal is emitted, and the
QgsVectorLayerCache::featureAtId
loads the feature again.
Pocessing time for the test layer in the
issue tracker went down from ~20 minutes
to less than 3 seconds.