OCT
12
2009

The PySide Effect - work begins on Smoke based QtScript and Python bindings

I was most surprised when the PySide Python bindings project was announced a few weeks ago. Simon Edwards wrote that "To be honest I'm not all that happy with the current situation." Meanwhile, I wasn't too happy that they had worked for eight months in secret without talking to the KDE bindings community either. I think that the PyQt/PyKDE bindings are very high quality and really well maintained, but if someone insists there really must be an LPGL'd Python binding, I personally would much prefer that it was based on 'Smoke'.

The actual effect of the announcement on me, has been to stimulate a flurry of activity. It is a bit similar to when Trolltech announced that they were working on a Java binding, QtJambi, a year or two ago. I found out about it on one of Aaron's blogs, and for me it meant that over two man years work was totally down the tubes. But I didn't get depressed about it, as I was pretty sick of working on Java bindings by then. I don't even like Java much, and the QtRuby bindings were proving much more popular, and it was starting to feel like I was only flogging a dead horse. However, I had been designing a version of both Java and C# bindings for Qt4 that made use of Proxies/Transparent proxies, and I didn't want to waste the design. I went into high gear after the QtJambi announcement and really got going on C# stuff, inventing the name 'Qyoto' for the project about a week later. I blogged about the dramatic progress I had made, Arno Rehn joined the project soon after. Arno has made some pretty major contributions to the Smoke bindings in general, and not just C#. So if it wasn't for the stimulus of the QtJambi competition, we would have certainly been worse off.

So the 'PySide Effect' has been pretty similar for me to the 'QtJambi Jolt'; they were both like giant attacks on my bindings projects immune systems, but have actually had very healthy effects in the long term.

I started studying the Python C api, and reviewing the code for various Python bindings projects, and thinking about how a Smoke based implementation would work. I sent my findings to the PySide mailing list Using 'Smoke' as a basis for a Qt Python binding, and started a lengthy thread. The PySide guys were pretty reasonable considering how critical I was of their current approach using Boost::Python, and even suggested that I set up an experimental PySide clone of a PySmoke binding in their gitorious project.

Meanwhile, the Amarok guys had been having problems with the large size and slow startup time of the Qt Labs QtScript bindings. As a result of the discussion on Simon's blog about the PySide announcement, Ian Monroe did a 'Proof of Concept' of a Smoke based QtScript binding. By following the documentation on TechBase he was able to very nearly get started. He just needed my help with an extra thing you had to do when an instance had been created, which I think we had left off the docs as it was quite new. So I spent a couple of hours sorting that problem out, and mailed a patch to Ian to get the 'hello world' app working.

After not much more than two man weeks work, the Smoke QtScript bindings are really starting to come together. You can call methods, override virtual methods, the marshallers for all the Qt types like lists and so on are pretty complete. The main thing that isn't implemented yet are slots and signals in the QtScript style. I got the AnalogClock.qs example working, by adding C++ style SLOT() and SIGNAL() functions like this:


function SLOT(str) {
    return "1" + str;
}

function SIGNAL(str) {
    return "2" + str;
}

function AnalogClock(parent) {
    QWidget.call(this, parent);

    var timer = new QTimer(this);
        
    // FIXME: We need to implement slots/signals handling
    //timer.timeout.connect(this, "update()");
    
    // Use C++ style syntax for now:
    this.connect(timer, SIGNAL("timeout()"), this, SLOT("update()"));

    timer.start(1000);

    this.setWindowTitle("Analog Clock");
    this.resize(200, 200);
}

AnalogClock.prototype = new QWidget();

AnalogClock.prototype.paintEvent = function() {
    var side = Math.min(this.width(), this.height());
    var time = new Date();

    var painter = new QPainter();
    painter.begin(this);
    painter.setRenderHint(QPainter.Antialiasing);
    painter.translate(this.width() / 2, this.height() / 2);
    painter.scale(side / 200.0, side / 200.0);

    painter.setPen(new QPen(Qt.NoPen));
    painter.setBrush(new QBrush(AnalogClock.hourColor));

    painter.save();
    painter.rotate(30.0 * ((time.getHours() + time.getMinutes() / 60.0)));
    painter.drawConvexPolygon(AnalogClock.hourHand);
    painter.drawLine(0, 0, 100, 100);
    painter.restore();

    painter.setPen(AnalogClock.hourColor);

    for (var i = 0; i < 12; ++i) {
        painter.drawLine(88, 0, 96, 0);
        painter.rotate(30.0);
    }

    painter.setPen(new QPen(Qt.NoPen));
    painter.setBrush(new QBrush(AnalogClock.minuteColor));

    painter.save();
    painter.rotate(6.0 * (time.getMinutes() + time.getSeconds() / 60.0));
    painter.drawConvexPolygon(AnalogClock.minuteHand);
    painter.restore();

    painter.setPen(AnalogClock.minuteColor);

    for (var j = 0; j < 60; ++j) {
        if ((j % 5) != 0)
	        painter.drawLine(92, 0, 96, 0);
        painter.rotate(6.0);
    }
    painter.end();
};

AnalogClock.hourColor = new QColor(127, 0, 127);

AnalogClock.minuteColor = new QColor(0, 127, 127, 191);

AnalogClock.hourHand = new QPolygon([new QPoint(7, 8),
                                     new QPoint(-7, 8),
                                     new QPoint(0, -40)]);
AnalogClock.minuteHand = new QPolygon([new QPoint(7, 8),
                                       new QPoint(-7, 8),
                                       new QPoint(0, -70)]);

var clock = new AnalogClock();
clock.show();

This morning I compared the QtScript Smoke AnalogClock with the Qt Labs one, and Python and Ruby versions. In terms of memory use and start up time, it is the best of the lot. About 10 times less memory use and 3 times faster than the Qt Labs one. On my very slow netbook (1.2 MHz Via7), it take about 10% CPU time to refresh the clock once a second, while Qt Labs QtScript takes 33%! According to the KDE System Monitor, the QtScript Smoke clock takes up 2.3 Mb, while the Qt Labs one takes up 20.2 Mb. In comparison both Ruby and Python were smaller that the Qt Labs clock, but more than twice the memory still of the Smoke based QtScript one. Python and Ruby briefly consumed 1% of the CPU, to update the clock once a second. So it seems that QtScript is pretty dog slow from this test whichever version you use, and I don't know if it will be a problem with a Plasma binding yet.

Anyhow, please have a look at the project on gitorious and try it out - I really think it is nearly useful. Any help with getting it finished or performance measurements is really welcome.

Comments

Awesome work!

Is the QtScript version you used the stable one or the experimental one that used WebKits JavaScript engine (what's it called)? If the first is the case than it would be interesting how fast the WebKit based one is in this test (with and without your Smoke bindings).

Keep up the good work. :)


By Mathias Panzenböck at Mon, 10/12/2009 - 23:54

Is the QtScript version you used the stable one or the experimental one that used WebKits JavaScript engine

I just used the standard Qt 4.5.2 one, and I agree it would certainly be interesting if someone could get the two QtScript bindings built with the Webkit javascript engine though. Is that standard in Qt 4.6, or do you need a special branch of Qt?


By Richard Dale at Tue, 10/13/2009 - 10:48

QtScript is entirely based on JavaScriptCore in 4.6, but I could be wrong.


By Gábor Lehel at Tue, 10/13/2009 - 19:18

> had worked for eight months in secret

this is not really how it went; actual engineers didn't really work on it for very long. In a company there is a lot more time between idea and start-of-execution than in volunteer projects which you should account for.
The pyside announcement was more like the start of the project with some initial research being put online.

I think its awesome that you show a different direction as being a better one in the most constructive of ways; with code :) Maybe the ideas can be reused for python too.

Thanks!


By Thomas Zander at Tue, 10/13/2009 - 09:35

Maybe the ideas can be reused for python too

Yes certainly, the QtScript Smoke code is just the 'essence' of a Smoke based binding, and is nice and simple and entirely in C++. So it is much easier to understand and port to other languages, than the code in PerlQt, QtRuby or Qyoto etc, as they all have large amounts of language specific weirdness in them, that makes it hard to follow the code, or to profile and tune it.


By Richard Dale at Tue, 10/13/2009 - 10:54

Eh, instead of making N+1 python bindings, I'd much prefer if someone finally made a rival to perl-Gtk. It's been years since anyone touched a PerlQt port...


By ralesk at Sat, 10/24/2009 - 00:49

It's been years since anyone touched a PerlQt port...

No, not so - Chris Burel and the current PerlQt team are actively developing a Qt4 version of PerlQt. As far as I know it is pretty to close to being released.


By Richard Dale at Thu, 10/29/2009 - 19:38

In the referenced mailing list thread there was at some point talked about that reflection on classes using __getattribute__ does not yield the attributes of the object. Python 2.6 (and 3.something) added the special member __dir__(). You can overload this method to return the attribute names of an object. Using meta classes this can also be used on classes in addition to instance objects.


By Mathias Panzenböck at Mon, 10/26/2009 - 22:40

The idea wasn't to get a list of the attributes of an object or class, but to return a callable for the given name, then can then be bound if for an instance object, and called as a method in the smoke library.

So the methods wouldn't necessarily in the dict for the instance, as they would be retrieved from the smoke library. Because of that problem, you're right, we could have a 'fake' version of __dir()__ that would solve the problem being talked about in the list. Excellent!


By Richard Dale at Thu, 10/29/2009 - 19:45