Lively BestOf, Volume #1

vom 30. November 2007

Im Rahmen der Vorlesung MSWA mussten wir den Code vom Lively Kernel (ein Research-Projekt von Sun) analysieren und einen Teilaspekt bis ins kleinste Detail untersuchen und verstehen. Florian und ich nahmen uns Morph als Spezielgebiet, was sich im Nachhinein ziemlich in Arbeit ausartete. Um die heutige Abgabe zu zelebrieren, beendeten wir dieses Projekt mit einem Bierchen (thx Andreas).

Bei der Analyse des Codes (>10k Zeilen Javascript) sind uns ein paar lustige und/oder merkwürdige Stellen aufgefallen, die ich euch nicht vorenthalten möchte :) Hier also die BestOf Codezeilen von Lively.

Da alle Javascript-Objekte auch wie eine Hashmap angesprochen werden können, ist es z.B. möglich die Superklasse des Objekts zu löschen oder aber dieses tolle Konstrukt (ähnliches geht übrigens auch in PHP):

addVariable: function(varName, initialValue) {
    // functional programming is fun!
    this[varName] = initialValue;
    this[getter(varName)] = function(name) { 
        return function() { return this[name]; } 
    }(varName); // let name = varName ()

    this[setter(varName)] = function(name) {
        return function(newValue, v) {
            this[name] = newValue;
            this.changed(getter(name), v);
        }
    }(varName);
},

Auch ziemlich cool fand ich folgende Funktion:

// my kingdom for a Smalltalk block!
applyFunctionToShape: function() {
    var args = $A(arguments);
    var func = args.shift();
    func.apply(this.shape, args);
    if (this.clipPath) {
        console.log('clipped to new shape ' + this.shape);
        this.clipToShape();
    }
    this.adjustForNewBounds();
}.wrap(Morph.onLayoutChange('shape')),

Der Funktion wird ein Funktionsblock und eine beliebige Anzahl Parameter übergeben (Funktionsparameter zu definieren oder zummindest in einem Kommentar zu hinterlassen was denn evtl. in die Funktion reinkommt ist was für Weicheier). Der Funktionsblock wird mit einem zusätzlichen Parameter und den restlichen Argumenten aufgerufen ($A() ist übrigends eine Prototype-Funktion die alle Argumente in ein Array schiebt) und am Ende noch mit der Funktion wrap() noch mit dem Aspekt von onLayoutChange() ausgeführt.

Allerdings war nicht alles so genial wie es jetzt den Anschein hat.

for (var i = 1; i <= vertices.length; i++) {
    var p2 = vertices[i % vertices.length];
    if (p.y > Math.min(p1.y, p2.y)) {
        if (p.y <= Math.max(p1.y, p2.y)) {
            if (p.x <= Math.max(p1.x, p2.x)) {
                if (p1.y != p2.y) {
                    var xinters = (p.y-p1.y)*(p2.x-p1.x)/(p2.y-p1.y)+p1.x;
                    if (p1.x == p2.x || p.x <= xinters)
                        counter ++;
                }
            }
        }
    }
    p1 = p2;
}

Oder das hier:

/* ca. 5 Seiten von diesen Zuweisungen */
this.images[1][0] = this.images[0][0];
this.images[1][1] = this.images[0][1];
this.images[1][2] = this.images[0][2];
this.images[1][3] = this.images[0][3];
this.images[1][4] = this.images[0][4];
//images[2] = images[1]; // DO NOT DO THIS, -> javascript nice copy-features
//images[1] = images[0];

Manche Dinge waren aber auch total sinnlos:

setToggle: function(flag) {
    this.setAttributeNS(Namespace.LIVELY, "toggle", !!flag);
},

Nachtrag: Wie sich heute in MSWA herausgestellt hat, ist die doppelte Negation doch nicht so sinnlos wie es offensichtlich scheint. Sie sorgt in diesem Fall für eine Typkonvertierung. Damit die Funktion setAttributeNS() auf jeden Fall einen Boolean als dritten Parameter bekommt, wird der Wert zuerst negiert (also zu einem echten boolschen False gemacht) und anschließend zu einem echten True. (Hintergrund: In dynamisch typisierten Sprachen wird alles was kein False ist automatisch als True interpretiert.)

Erst nach dem fünften Durchsehen des Codes wurde mir klar das das hier eine Art Interfacedefinition sein muss:

recordChange: function(fieldName/*:String*/) {
    // Update sever or change log or something
    return;
},

Auch eine "Ellegante" Art Copy&Paste zu betreiben - einfach von einer anderen Prototype-Klasse klauen:

// poorman's traits :)
bounds: PolygonShape.prototype.bounds,
vertices: PolygonShape.prototype.vertices,
inspect: PolygonShape.prototype.inspect,
setVertices: PolygonShape.prototype.setVertices,
reshape: PolygonShape.prototype.reshape,
/* ... */

... oder einfach mal bei jedem Schleifendurchgang das Array neu definieren und die i-te Position rausziehen:

for (var i = 0; i < 12; i++) {
    ... ['XII','I','II','III','IV','V','VI','VII','VIII','IX','X','XI'][i];
    /* ... */
}

Bei manchen Funktionen waren sich die Programmierer auch nicht so ganz sicher was sie da gemacht haben.

compositionWidth: function() {
    if (this.wrap === WrapStyle.NORMAL) return this.shape.bounds().width - (2*this.inset.x);
    else return 9999; // Huh??
},
Function.prototype.inspect = function() {
    return this.toString().substring(8, 88);
};
// yes yes.. so its a little laggy to add the current line and delete it...
parent.setIMText("");

... und kleine Zankereien unter den Entwicklern ist auch zu finden:

// KP: note layoutChanged will be called on addition to the tree
// DI: ... and yet this seems necessary!
this.layoutChanged();
// this code is all copied -- should be factored or, better, removed

Das leidige Thema Browserkompatibilität ...

if (newShape.pathSegList.numberOfItems != this.pathSegList.numberOfItems) {
    // ARGH, Safari doesn't clone lists properly???
    for (var i = 0; i < this.pathSegList.numberOfItems; i++) {
        // How annoying, no way of cloning path segments
        var seg = this.pathSegList.getItem(i);
// KP: add the top morph to the world first, to make firefox happy
WorldMorph.current().addMorphAt(WindowMorph(engine, 'A Lively Engine'), pt(250, 5));
engine.openAllToDnD();  // have a little fun...

Performance ist auch so ein Thema für sich. An einer ziemlich zentralen Stelle:

// TODO: there MUST be a better way to do this
// there "might" be some performance issues with this :)
while (sin < 0) sin += 360; // Can be slow...

Und wer genau hinschaut findet sogar ein Easteregg :)

// uncomment for extra icon fun
/*
sign = NodeFactory.create("use").withHref("#GearIcon");
sign.setAttributeNS(null, 'transform', 'translate(-10, -10) scale(0.040)');
menuButton.addChildElement(sign);
*/

In diesem Sinne:

this.state = "shutdown"; // no one will ever know...

delicious bookmark del.icio.us,


Kommentare


nougad am 1. December 2007
Sehr cool! :D

Dieser Beitrag fasst unsere letzten vier Wochen sehr gut zusammen! ;-)

Ich kann PHP dynamisch erweitern? Wusste ich gar nicht.

Bleib nur festzuhalten: ES IST (fast) VORBEI!

PS: Cooles Syntax Highliting!

Andreas am 1. December 2007
:-D. Lively makes programmers live lively, sag ich da nur:). Echt geil:)....

Denis am 2. December 2007
Sehr geiler Blogeintrag! Endlich vorbei. Ich weiß nicht wie Sun auf Lively kommt. Nach der Tortur bezeichne ich es lieber als Deadly. Viele Grüße Denis

Hans Maulwurf am 4. December 2007
einfach herrlich!

Kommentar schreiben