Forums

PlayOnLinux development: Behind the scene

Chapter I - SetupWindow synchronization

Auteur Réponses
Quentin PÂRIS Mercredi 24 Juin 2015 à 1:18
Quentin PÂRIS
Admin

Hi everyone!

I'm just posting this message because I want to share some little tips and some mistake we have made while developing PlayOnLinux in the past. I will try to post as many as I can if I find any interesting topic.

Disclamer: If you are an advanced developer, you may find this post a little simplistic. Indeed it is highly simplified. Its intent is to try to untangle the design of PlayOnLinux 4 and to explain what solution we are using in PlayOnLinux 5.

Chapter I - SetupWindow synchronization

The main target of this post is to explain an anti-pattern that we've used in PlayOnLinux 4, and also how to avoid it.

Context

We you are using POL_SetupWindow_* commands, you are able to create some messages like this one:

POL_SetupWindow_message "Hello World!" 

 

If you have only one comand, it is fine. However let see what's happening when you try to show two messages in a row.

POL_SetupWindow_message "message 1" # Shows the message 1
# Wait until the user has clicked on the Next button
POL_SetupWindow_message "message 2" # Shows the message 2

The problem we want to solve in this topic is preventing the bash process to continue until the user has clicked on the next button. Otherwise, we would not have the time to see the first message.

We are going to assume that POL_SetupWindow_message is bound to a Python or Java message() method. We are not going to explain here the communication mechanism between bash and Python or between bash and Java, which is a different problem.

Let's take the following Python code:

message("message 1")
message("message 2")

The problem is: How can we wait for the user interaction before showing message number 2?

PlayOnLinux 4 approach: Busy waiting

This approach is an anti pattern. I'm just presenting it to explain why it should be avoided. To simplify the problem, we can assume that we have two differents threads in our program:

  • Thread 1 : Thread of the UI
  • Thread 2 : Thread of the script

The idea here is to block the thread 2 by creating a loop and periodically ask the UI if the user as clicked on the next button:

Pseudo-code (will not run) for the script Thread:

class ScriptThread():
    def message(self, messageToShow):
        UI.showMessage(messageToShow)
        while(UI.hasClicked()):
            thread.sleep(200)

Pseudo-code for the UI Thread:

class UIThread():
    def showMessage(self, messageToShow):
        self.hasClicked = False
        # Call UI code that will show the message

    def hasClicked(self):
        return self.hasClicked

    def eventOnClickNextButton(self):
        self.hasClicked = True

 

PlayOnLinux 5 approach: Use of signals

The PlayOnLinux 4 approach is not acceptable in terms of performance. Not only are we going to loose at least 200ms (in this example), but we are also uselessly wasting CPU time.

In PlayOnLinux 5, we are going to use the Semaphore object. A semaphore that has two methods: acquire (decrement) and release (increment). A semaphore cannot be negative. If a thread try to decrease a semaphore whose value is equal to 0, it will be blocked until another thread increments it.

Let's have a look to the code:
 

Pseudo-code (will not run) for the script Thread:

class ScriptThread():
    def message(self, messageToShow):
        semaphore = Semaphore(0) 
        UI.showMessage(semaphore, messageToShow)
        semaphore.acquire() 

Pseudo-code for the UI Thread:

class UIThread():
    def showMessage(self, semaphore, messageToShow):
        self.semaphore = semaphore
        # Call UI code that will show the message

    def eventOnClickNextButton(self):
        self.semaphore.release() 

Performance comparison

Now we need to prove that the new design is more efficient by doing some tests. We are going to test the POL_SetupWindow_wait command, because it does not need any user interaction. We are going to use the following scripts:

Bash version (PlayOnLinux 4 and PlayOnLinux 5):

#!/bin/bash

source "$PLAYONLINUX/lib/sources"

test() {
    POL_SetupWindow_Init

    for ((i = 0; i <= "$1"; i++)); do
        POL_SetupWindow_wait "$i"
    done

    POL_SetupWindow_Close
}

time test 100
time test 500
time test 1000

Python version (PlayOnLinux 5 only):

#!/usr/bin/env python
from com.playonlinux.framework.templates import Installer

import time

class Example(Installer):
    logContext = "TestScript"
    title = "title"

    def test(self, numberOfIterations):
        start = time.time()
        setupWindow = self.getSetupWizard()
        for i in xrange(numberOfIterations):
            setupWindow.wait(str(i))
        setupWindow.close()
        end = time.time()
        print end - start
        
    def main(self):
        self.test(100)
        self.test(500)
        self.test(1000)

The idea is to count how long does it take to run POL_SetupWindow_wait 100, 500 and 1000 times.

Here are the result on my Laptop:

The difference is pretty amazing. We can observe that it takes about 10 minutes to execute POL_SetupWindow_wait 1000 times in PlayOnLinux 4 while it takes only few seconds with PlayOnLinux 5.

Conclusion

Of course, we will never find scripts that run POL_SetupWindow_wait 1000 times. But still, it is always a good practice to avoid losing performances when it is not necessary.

The fact that PlayOnLinux 5 is written in Java may also interfere with this benchmark. Ideally, we would need to make a fourth test to spot the differences of performances due to the choice of the language.

Also you can notice that POLv5 Python (Jython scripts) are way faster than POLv5 bash scripts. This difference will be explained in a next post, so stay tuned :)

Edité par Tinou

Quentin PÂRIS Vendredi 26 Juin 2015 à 21:56
Quentin PÂRIS
Admin

And here the video in action. As you can see, POLv5 runs much faster. This will be pretty useful to create progress bar directly from scripts (which is nearly impossible with POLv4)

https://www.youtube.com/watch?v=b57JIFCQg54

Edité par Tinou