tdf#105575 LibreLogo: speed up by lock (re)paint

Speed up program execution by default partial
locking of repaint and custom locking of paint,
using UNO lockControllers().

– Default: speed up LABEL and TEXT by locking their repaint
  during the various text operations, i.e. setting size and
  text portion formatting changes were visible, and slow.
  Locking while manipulating a shape was suggested by Noel Grandin.

– Custom: SLEEP command with negative argument calls lockControllers(),
  SLEEP command with not negative argument (and program termination)
  call unlockControllers(), so it's possible to lock program parts to
  speed up program execution e.g. 3-4 times or more. For example:

  ; lock paint until program termination
  SLEEP -1
  program

  or

  ; lock until SLEEP 0
  SLEEP -1
  commands_without_paint
  SLEEP 0 ; unlock a single level and paint/repaint everything

– Add __get_time__() command to get the elapsed time from
  program start for profiling.

Add unit tests.

Note: custom locking with SLEEP has known problems: not connected
lines, bad position of arc/pie, non-working CLEARSCREEN.

Note: The reported code is more than 32 times faster (4 sec vs 2.2 min)
with custom locking, also using the commented line to show not only
the result with 400 circle shapes, but the partial result with 100,
200 and 300 shapes, too:

SLEEP -1
REPEAT 400 [ CIRCLE 10 + REPCOUNT/10 FORWARD 5 + REPCOUNT/10 LEFT 10 ]
; IF REPCOUNT % 100 == 0 [ SLEEP 0 SLEEP -1 ] ; to show partial result
SLEEP 0
PRINT __get_time__()

Change-Id: I51f71b0143178e6d35ecced92a59525184392d17
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143640
Tested-by: László Németh <[email protected]>
Reviewed-by: László Németh <[email protected]>
diff --git a/librelogo/source/LibreLogo/LibreLogo.py b/librelogo/source/LibreLogo/LibreLogo.py
index 562095a..ccd504e 100644
--- a/librelogo/source/LibreLogo/LibreLogo.py
+++ b/librelogo/source/LibreLogo/LibreLogo.py
@@ -479,6 +479,9 @@
        selection.getStart().BreakType = 4
    __dispatcher__(".uno:ZoomPage")

def __get_time__():
    return __time__.process_time() - _.start_time

class LogoProgram(threading.Thread):
    def __init__(self, code):
        self.code = code
@@ -535,7 +538,10 @@
                parent = _.doc.CurrentController.Frame.ContainerWindow
                MessageBox(parent, "Document objects with%s script events" % [" possible", ""][secid-1], "LibreLogo program can't start", "errorbox")
            else:
                _.start_time = __time__.process_time()
                exec(self.code)
                while _.doc.hasControllersLocked():
                    _.doc.unlockControllers()
            if _.origcursor[0] and _.origcursor[1]:
                __dispatcher__(".uno:Escape")
                try:
@@ -544,6 +550,8 @@
                    _.doc.CurrentController.getViewCursor().gotoRange(_.origcursor[0].getStart(), False)
        except Exception as e:
            try:
              while _.doc.hasControllersLocked():
                  _.doc.unlockControllers()
              TRACEPATTERN = '"<string>", line '
              message = traceback.format_exc()
              l = re.findall(TRACEPATTERN + '[0-9]+', message)
@@ -834,6 +842,8 @@
    global __halt__
    with __lock__:
        __halt__ = True
    while _.doc.hasControllersLocked():
        _.doc.unlockControllers()
    return None

def home(arg=None):
@@ -1401,6 +1411,7 @@

def text(shape, orig_st):
    if shape:
        _.doc.lockControllers()
        # analyse HTML
        st, formatting, extra_data = __get_HTML_format__(orig_st)
        shape.setString(__string__(st, _.decimal))
@@ -1460,8 +1471,17 @@
                prev_format = i
                if len(extra_data) > 0:
                    prev_extra_data = extra_data.pop(0)
        _.doc.unlockControllers()

def sleep(t):
    if t < 0:
        # lock shape repaint, if SLEEP argument is negative
        _.doc.lockControllers()
        return
    else:
        # otherwise unlock one level
        if _.doc.hasControllersLocked():
            _.doc.unlockControllers()
    _.time = _.time + t
    __removeshape__(__ACTUAL__)
    for i in range(int(t/__SLEEP_SLICE_IN_MILLISECONDS__)):
diff --git a/sw/qa/uitest/librelogo/run.py b/sw/qa/uitest/librelogo/run.py
index 6bcca4a..a7f2d0a 100644
--- a/sw/qa/uitest/librelogo/run.py
+++ b/sw/qa/uitest/librelogo/run.py
@@ -100,7 +100,10 @@
            # new shape + previous two ones = 3
            self.assertEqual(document.DrawPage.getCount(), 3)

   def test_LABEL(self):
   def check_label(self, hasCustomLock):
        sLock = "CLEARSCREEN "
        if hasCustomLock:
            sLock = sLock + "SLEEP -1 "
        with self.ui_test.create_doc_in_start_center("writer") as document:
            xWriterDoc = self.xUITest.getTopFocusWindow()
            xWriterEdit = xWriterDoc.getChild("writer_edit")
@@ -109,7 +112,7 @@

            #1 run a program with basic LABEL command

            type_text(xWriterEdit, "LABEL 'Hello, World!'")
            type_text(xWriterEdit, sLock + "LABEL 'Hello, World!'")
            self.logo("run")
            # wait for LibreLogo program termination
            while xIsAlive.invoke((), (), ())[0]:
@@ -123,7 +126,7 @@

            #2 check italic, bold, underline + red and blue formatting

            document.Text.String = "CLEARSCREEN LABEL '<i><red>Hello</red>, <bold><blue>W<u>orld</blue></bold>!</i></u>'"
            document.Text.String = sLock + "LABEL '<i><red>Hello</red>, <bold><blue>W<u>orld</blue></bold>!</i></u>'"
            self.logo("run")
            # wait for LibreLogo program termination
            while xIsAlive.invoke((), (), ())[0]:
@@ -170,7 +173,7 @@
            #2 check strike out, sub, sup, font name and font size formatting

            document.Text.String = (
                "CLEARSCREEN FONTFAMILY 'Linux Biolinum G' FONTSIZE 12 " +
                sLock + "FONTFAMILY 'Linux Biolinum G' FONTSIZE 12 " +
                "LABEL '<s>x</s>, <sub>x</sub>, <sup>x</sup>, " +
                    "<FONTFAMILY Liberation Sans>x</FONTFAMILY>, " +
                    "<FONTHEIGHT 20>x</FONTHEIGHT>...'" )
@@ -223,8 +226,7 @@

            #3 check colors

            document.Text.String = (
                "CLEARSCREEN " +
            document.Text.String = ( sLock +
                "LABEL '<red>x</red>, <BLUE>x</BLUE>, " +  # check ignoring case
                    "<FONTCOLOR GREEN>x</FONTCOLOR>, " +   # check with command
                    "<FONTCOLOR 0x0000FF>x, " +            # check with hexa code
@@ -279,7 +281,7 @@
            #4 check font features

            document.Text.String = (
                "CLEARSCREEN FONTFAMILY 'Linux Biolinum G' " +
                sLock + "FONTFAMILY 'Linux Biolinum G' " +
                "LABEL 'a <smcp>smcp <pnum>1<onum>1</pnum> 1</onum>1</smcp>...'" )

            self.logo("run")
@@ -311,4 +313,10 @@
            c.goRight(1, False)
            self.assertEqual(c.CharFontName, "Linux Biolinum G:smcp")

   def test_LABEL(self):
        self.check_label(False)

   def test_custom_lock(self):
        self.check_label(True)

# vim: set shiftwidth=4 softtabstop=4 expandtab: