>
>
> Imo, #1068 <https://github.com/leo-editor/leo-editor/issues/1068> is 
> likely the best way to do this. 
>

I would not be so sure about #1068 being the best way. But it certainly 
deserves further exploration. The idea of finding the difference and 
applying only small set of operations to transform the tree is very much 
like the way most popular front end javascripts frameworks (like React and 
Vue) deal with the dom updates. So, if they can use this for speed, then it 
might be the way for Leo too. 

However, there might be a better way. The proposed idea (in #1068), assumes 
Leo is using QTreeWidget for displaying the outline. Now, both Leo and 
QTreeWidget keep their data representation of the outline on their own. And 
if something changes in the Leo, problem arises with the keeping 
QTreeWidget's data in sync. If OTOH, we use QGraphicsScene for displaying 
the outline, it would contain only a handful of QGraphicItems (as many as 
there are visible nodes on the screen, i.e. at most 50). QTreeWidget in the 
worse case contains all nodes. Calculating diffs can be expensive too if 
the number of nodes is large. But if the lists are never longer than 50 
items, it will be fast enough.

The script I wrote above shows that it is possible to pass through the 
large outline (more than 8000 nodes) in less than 10ms. Adding a filter to 
skip hidden nodes may add some time to this but I don't expect more than 
5ms. 

... a few hours later

I have just looked in the p.isExpanded and c.shouldBeExpanded methods to 
see how it is achieved separate expanded state for clones. There is a 
better way. Currently each v instance keeps track of its expanded positions 
in the v.expandedPositions. As we know, positions can become invalid and 
lots of these lists become also invalidated. There is a better way. Let's 
imagine a list of gnxes in the outline order. To distinguish between 
clones, it would be enough to take in account the number of the previous 
occurrences of same gnx in the outline list. When we encounter gnx for the 
first time its number is 1. Every next occurrence of this same gnx will 
have this number increased by 1. Then we can keep a set of pairs (gnx, 
occurrence_number) in c.expandedNodes. So, to check if some cloned  vnode 
is expanded or not, it would be enough to check  if c.expandedPositions 
contains (gnx, occ_num) for that particular occurrence.

Here is the function that would generate list of nodes that should be 
displayed:

import timeit
from collections import Counter

def get_exp():
    cnt = Counter()
    exp = set()
    exp.add((1, c.hiddenRootNode.fileIndex)) # root node is always expanded
    for p in c.all_positions_iter():
        gnx = p.v.fileIndex
        cnt[gnx] += 1
        if p.isExpanded():
            exp.add((cnt[gnx], gnx))
    return exp

expanded = get_exp() # lets cache expanded state

def f():
    cnt = Counter()
    def it(root, lev):
        gnx = root.fileIndex
        cnt[gnx] += 1
        yield root, lev
        if (cnt[gnx], gnx): # or True (to fully expand outline)
            for v in root.children:
                yield from it(v, lev+1)
    return list(it(c.hiddenRootNode, 0))

def report(fun, n=100):
    t = timeit.timeit(fun, number=n)/n * 1000
    g.es('function %s took %.1fms'%(fun.__name__, t))

report(f, 10)
# gives 0.3ms for LeoPyRef (not fully expanded)


For the same outline, FastRedraw.flatten_outline(c) gives 1.5ms. When fully 
expanded function f takes 17.7ms, and FastRedraw.flatten_outline(c) takes 
88.4ms. 

I think that allowing clone instances to have separate expanded state is 
not much of a feature. Most likely the need for such a feature is caused by 
the drawing code being too slow. If the drawing code was fast enough, I 
doubt that this feature would ever existed. So, I would vote to drop this 
feature and have all clones expand/collapse simultaneousely. That would 
enable even faster redraws. 

With the list of nodes to display generated in less than 18ms, adjusting 
all of the (<50) visible QGraphicsItems in the QGraphicsScene should not 
take much more time. So, complete redraw might take less than 50ms in the 
worse case scenario. and about 20ms for the normal cases.

I would welcome your help with this.  I promise not to touch anything you 
> do until you declare your work finished.
>

I really would like to help, but I believe the best way to help is to just 
share my ideas and let you do the coding. If you need some clarification or 
more detailed explanation of my ideas, ask away and I would be glad to 
answer ASAP.

Vitalije

PS: I have recently writen in cython a module for calculating diffs and 
encoding them using fossil source. The author of sqlite and fossil D. 
Richard Hipp wrote in C this few functions in a way that they can be used 
as a standalone functions. It was really easy to make a Python extension 
module of this C code using cython. The main reason for me doing this was 
to enable Leo to keep track of changes in the outline. I wrote a plugin a 
while ago using SequenceMatcher but I have never made any real usage of it. 
On one of the currently open issues there is a label `waiting`. I was 
surprised to find out that it is waiting for me to publish this plugin and 
I totally have forgotten about it. So, while looking again on this plugin I 
tried to use fossil super fast and super efficient code that calculates 
diffs very fast and encodes them in very compressed (but ascii friendly) 
form. This has a potential to make unlimited undo history very efficiently, 
very fast and very reliable.

-- 
You received this message because you are subscribed to the Google Groups 
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to leo-editor+unsubscr...@googlegroups.com.
To post to this group, send email to leo-editor@googlegroups.com.
Visit this group at https://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.

Reply via email to