1. Run step() for a while to avoid very short steps
This way, we batch items-changed() emissions.
2. Track the change region accurately
This way, we can avoid invalidating the whole list if our step just
touched a small part of a huge list.
As this is a merge sort, this is a common occurence when we're buys
merging chunks: The rest of the model outside those chunks isn't
changed.
Note that the tracking is accurate: It determines the minimum change
region in the model.
This will be important, because the testsuite is going to test this.
... and use it in the SortListModel
Setting runs allows declaring already sorted regions so the sort does
not attempt to sort them again.
This massively speeds up partial inserts where we can reuse the sorted
model as a run and only resort the newly inserted parts.
Benchmarks:
appending half the model
qsort timsort
128,000 items 94ms 69ms
256,000 items 202ms 143ms
512,000 items 488ms 328ms
appending 1 item
qsort timsort
8,000 items 1.5ms 0.0ms
16,000 items 3.1ms 0.0ms
...
512,000 items --- 1.8ms
Simply replace the old qsort() call with a timsort() call.
This is ultimately relevant because timsort is a LOT faster in merging
to already sorted lists (think items-chaged adding some items) or
reversing an existing list (think columnview sort order changes).
Benchmarks:
initially sorting the model
qsort timsort
128,000 items 124ms 111ms
256,000 items 264ms 250ms
The model now tracks the original positions on top of just the items so that
it can remove items in an items-changed emission.
It now takes twice as much memory but removes items much faster.
Benchmarks:
Removing 50% of a model:
before after
250,000 items 135ms 10ms
500,000 items 300ms 25ms
Removing 1 item:
4,000 items 2.2ms 0ms
8,000 items 4.6ms 0ms
500,000 items --- 0.01ms
This is the dumbest possible sortmodel using an array:
Just grab all the items, put them in the array, qsort() the array.
Some benchmarks (setting a new model):
125,000 items - old: 549ms
new: 115ms
250,000 items - new: 250ms
This performance can not be kept for simple additions and removals
though.
Bring back the actions tab; we don't receive
changes anymore, since GtkActionMuxer lost
the GActionGroup signals for this, and the
action observer machinery has no way to listen
for all changes.
Instead of implementing the GActionGroup interface
and using its signals for propagating changes up
and down the muxer hierarchy, use the GtkActionObserver
mechanism. This cuts down on the signal emission
overhead.
We should not rely on GtkWindow to have global
"activate-default" key bindings that happen to
fall back to activating the focus widget. This is
unreliable, since the bubbling up from the button
to the toplevel may run across other widgets that
may want to use Enter for their own purpose, and
then the button loses out. By adding our own
key bindings, the button gets to handle it before
its ancestors.
This fixes check buttons in the inspector property
list not reacting to Enter despite having focus.
If we don't, an ancestor (such a GtkListItemWidget)
may interpret the click as "I should grab focus!",
and still our focus away. This was causing hard-to-focus
entries in the property list in the inspector.