JavaOne: What a Difference a Year Makes

I haven't been blogging for a while.  JavaOne examples and slides are taking up all my time.  Marcus and Attila leaked out that I was doing a demo of the Nashorn Debugger prototype, but I still have some surprises up my sleeve.  I was reviewing examples from last year and felt that they needed some updating.  The FX Fireworks example had all kinds of Java code to support applications, timers and callbacks.  This year's Fireworks version is pure Nashorn.  I posted source and backdrop here.  Just run with jjs -fx -scripting fireworks.js  Enjoy.

var ArrayList      = Java.type("java.util.ArrayList");
var File           = Java.type("java.io.File");

var AnimationTimer = Java.type("javafx.animation.AnimationTimer");
var BlendMode      = Java.type("javafx.scene.effect.BlendMode");
var Canvas         = Java.type("javafx.scene.canvas.Canvas");
var Color          = Java.type("javafx.scene.paint.Color");
var CycleMethod    = Java.type("javafx.scene.paint.CycleMethod");
var Group          = Java.type("javafx.scene.Group");
var ImageView      = Java.type("javafx.scene.image.ImageView");
var Pane           = Java.type("javafx.scene.layout.Pane");
var RadialGradient = Java.type("javafx.scene.paint.RadialGradient");
var Reflection     = Java.type("javafx.scene.effect.Reflection");
var Scene          = Java.type("javafx.scene.Scene");
var Stop           = Java.type("javafx.scene.paint.Stop");

var AnimationTimerExtend = Java.extend(AnimationTimer);

var doubleArray    = Java.type("double[]");

var GRAVITY = 0.06;

function Particle(posX, posY, velX, velY, targetX, targetY, color, size, usePhysics, shouldExplodeChildren, hasTail) {
    this.posX                  = posX;
    this.posY                  = posY;
    this.velX                  = velX;
    this.velY                  = velY;
    this.targetX               = targetX;
    this.targetY               = targetY;
    this.color                 = color;
    this.size                  = size;
    this.usePhysics            = usePhysics;
    this.shouldExplodeChildren = shouldExplodeChildren;
    this.hasTail               = hasTail;
    this.alpha                 = 1;
    this.easing                = Math.random() * 0.02;
    this.fade                  = Math.random() * 0.1;
}

Particle.prototype.update = function() {
    this.lastPosX = this.posX;
    this.lastPosY = this.posY;

    if(this.usePhysics) { // on way down
        this.velY += GRAVITY;
        this.posY += this.velY;
        this.alpha -= this.fade; // fade out particle
    } else { // on way up
        var distance = (this.targetY - this.posY);
        // ease the position
        this.posY += distance * (0.03 + this.easing);
        // cap to 1
        this.alpha = Math.min(distance * distance * 0.00005, 1);
    }

    this.posX += this.velX;

    return this.alpha < 0.005;
}


Particle.prototype.draw = function(context) {
    var x    = Math.round(this.posX);
    var y    = Math.round(this.posY);
    var xVel = (x - this.lastPosX) * -5;
    var yVel = (y - this.lastPosY) * -5;

    // set the opacity for all drawing of this particle
    context.globalAlpha = Math.random() * this.alpha;
    // draw particle
    context.fill = this.color;
    context.fillOval(x - this.size, y - this.size, this.size + this.size, this.size + this.size);

    // draw the arrow triangle from where we were to where we are now
    if (this.hasTail) {
        context.fill = Color.rgb(255, 255, 255, 0.3);
        var x = new doubleArray(3);
        var y = new doubleArray(3);
        x[0] = this.posX + 1.5;  y[0] = this.posY;
        x[1] = this.posX + xVel; y[1] = this.posY + yVel;
        x[2] = this.posX - 1.5;  y[2] = this.posY;
        context.fillPolygon(x, y, 3);
    }
}

function drawFireworks(gc) {
    var iter = particles.iterator();
    var newParticles = new ArrayList();

    while(iter.hasNext()) {
        var firework = iter.next();

        // if the update returns true then particle has expired
        if(firework.update()) {
            // remove particle from those drawn
            iter.remove();

            // check if it should be exploded
            if(firework.shouldExplodeChildren) {
                if(firework.size == 9) {
                    explodeCircle(firework, newParticles);
                } else if(firework.size == 8) {
                    explodeSmallCircle(firework, newParticles);
                }
            }
        }

        firework.draw(gc);
    }

    particles.addAll(newParticles);
}

function fireParticle() {
    particles.add(new Particle(
        surface.width * 0.5, surface.height + 10,
        Math.random() * 5 - 2.5, 0,
        0, 150 + Math.random() * 100,
        colors[0], 9,
        false, true, true));
}

function explodeCircle(firework, newParticles) {
    var count = 20 + (60 * Math.random());
    var shouldExplodeChildren = Math.random() > 0.5;
    var angle = (Math.PI * 2) / count;
    var color = (Math.random() * (colors.length - 1));

    for(var i=count; i>0; i--) {
        var randomVelocity = 4 + Math.random() * 4;
        var particleAngle = i * angle;

        newParticles.add(
            new Particle(
                firework.posX, firework.posY,
                Math.cos(particleAngle) * randomVelocity, Math.sin(particleAngle) * randomVelocity,
                0, 0,
                colors[Math.ceil(color)],
                8,
                true, shouldExplodeChildren, true));
    }
}

function explodeSmallCircle(firework, newParticles) {
    var angle = (Math.PI * 2) / 12;

    for(var count = 12; count > 0; count--) {
        var randomVelocity = 2 + Math.random() * 2;
        var particleAngle = count * angle;
        newParticles.add(
            new Particle(
                firework.posX, firework.posY,
                Math.cos(particleAngle) * randomVelocity, Math.sin(particleAngle) * randomVelocity,
                0, 0,
                firework.color,
                4,
                true, false, false));
    }
}

function fileToURL(file) {
    return new File(file).toURI().toURL().toExternalForm();
}

var timer = new AnimationTimerExtend() {
    handle: function handle(now) {
        var gc = surface.graphicsContext2D;
        // clear area with transparent black
        gc.fill = Color.rgb(0, 0, 0, 0.2);
        gc.fillRect(0, 0, 1024, 708);
        // draw fireworks
        drawFireworks(gc);

        // countdown to launching the next firework
        if (countDownTillNextFirework <= 0) {
            countDownTillNextFirework = 10 + (Math.random() * 30);
            fireParticle();
        }

        countDownTillNextFirework--;
    }
};

// Kill timer before exiting.
function stop() {
    timer.stop();
}

var particles = new ArrayList();
var countDownTillNextFirework = 40;

// create a color palette of 180 colors
var colors = new Array(181);
var stops = new ArrayList();
stops.add(new Stop(0, Color.WHITE));
stops.add(new Stop(0.2, Color.hsb(59, 0.38, 1)));
stops.add(new Stop(0.6, Color.hsb(59, 0.38, 1,0.1)));
stops.add(new Stop(1, Color.hsb(59, 0.38, 1,0)));
colors[0] = new RadialGradient(0, 0, 0.5, 0.5, 0.5, true, CycleMethod.NO_CYCLE, stops);

for (var h = 0; h < 360; h += 2) {
    var stops2 = new ArrayList();
    stops2.add(new Stop(0, Color.WHITE));
    stops2.add(new Stop(0.2, Color.hsb(h, 1, 1)));
    stops2.add(new Stop(0.6, Color.hsb(h, 1, 1,0.1)));
    stops2.add(new Stop(1, Color.hsb(h, 1, 1,0)));
    colors[1 + (h / 2)] = new RadialGradient(0, 0, 0.5, 0.5, 0.5, true, CycleMethod.NO_CYCLE, stops2);
}

var surface = new Canvas(1024, 500);
surface.blendMode = BlendMode.ADD;
surface.effect = new Reflection(0, 0.4, 0.15, 0);

var imageUrl = fileToURL("sf.jpg");
var background = new ImageView(imageUrl);

var pane = new Pane();
pane.children.add(background);
pane.children.add(surface);

var root = new Group();
root.children.add(pane);
$STAGE.scene = new Scene(root);

timer.start();


Comments:

I just tried this with JDK 8 b104 but it throws this exception:

java.lang.ClassCastException: Cannot cast jdk.nashorn.internal.runtime.Undefined to javafx.scene.paint.Paint
at sun.invoke.util.ValueConversions.newClassCastException(ValueConversions.java:461)
at sun.invoke.util.ValueConversions.castReference(ValueConversions.java:456)
at jdk.nashorn.internal.scripts.Script$fireworks._L59(fireworks.js:68)
at jdk.nashorn.internal.scripts.Script$fireworks.drawFireworks(fireworks.js:102)
at jdk.nashorn.internal.scripts.Script$fireworks.handle(fireworks.js:166)
at javafx.animation.AnimationTimer$$NashornJavaAdapter.handle(Unknown Source)
at com.sun.scenario.animation.AbstractMasterTimer.timePulseImpl(AbstractMasterTimer.java:357)
at com.sun.scenario.animation.AbstractMasterTimer$MainLoop.run(AbstractMasterTimer.java:267)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:475)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:459)
at com.sun.javafx.tk.quantum.QuantumToolkit$13.run(QuantumToolkit.java:326)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.access$300(WinApplication.java:39)
at com.sun.glass.ui.win.WinApplication$3$1.run(WinApplication.java:101)
at java.lang.Thread.run(Thread.java:724)

Am I doing something wrong?

Posted by Felix Bembrick on September 04, 2013 at 04:07 PM PDT #

Which version of JDK 8 do you have installed. `java -fullversion`

Posted by jlaskey on September 04, 2013 at 04:12 PM PDT #

Well the code would be sensitive to reordering. colors[0] needs to be set before

function fireParticle() {
particles.add(new Particle(
surface.getWidth()*0.5, surface.getHeight()+10,
Math.random() * 5 - 2.5, 0,
0, 150 + Math.random() * 100,
colors[0], 9,
false, true, true));
}

is called by the timer. But the timer is not triggered until the end. Did you download from the link or paste from the blog? Is anyone else seeing this issue?

Posted by jlaskey on September 04, 2013 at 04:22 PM PDT #

OK, false alarm. I copied the JS from this post originally but now that I have downloaded from the link it all works fine.

Great work!

Posted by Felix Bembrick on September 04, 2013 at 04:28 PM PDT #

That could do it. Formatting can sometimes concat two lines. If the first is a comment...

Thanks for trying it out.

Cheers,

-- Jim

Posted by jlaskey on September 04, 2013 at 04:36 PM PDT #

Oops, it looks like I spoke too soon. After running the demo for about 5 minutes, the same exception that I posted is thrown and the animation crashes. Then I ran it again and it crashed after only 90 seconds in the same way.

Posted by Felix Bembrick on September 04, 2013 at 05:01 PM PDT #

Change line 121 to

var color = (Math.random() * (colors.length - 1));

Apologies to all.

Posted by jlaskey on September 04, 2013 at 05:30 PM PDT #

Thanks Jim, looks like it works well now. The performance is impressive with no noticeable difference between the JS and Java implementations to the naked eye.

Posted by Felix Bembrick on September 04, 2013 at 06:20 PM PDT #

With that fix, I ran it overnight and it is still running.

Posted by jlaskey on September 05, 2013 at 04:59 AM PDT #

Yeah, same here Jim.

I am really excited about Nashorn, great effort by the entire team :-)

Posted by Felix Bembrick on September 05, 2013 at 01:50 PM PDT #

I tweaked the code today. Uses less memory and less cpu. try jjs -J-Xmx16m -fx fireworks.js

Posted by jlaskey on September 05, 2013 at 02:46 PM PDT #

I tried the tweaked version and it now freezes (no exceptions) after about 10 minutes but I have also updated to JDK 8 b105 so I am not sure exactly what the cause is.

Posted by Felix Bembrick on September 06, 2013 at 12:46 AM PDT #

I think there may be a JSR-292 bug in b105 that causes internal errors. I'll have to double check. I'm nervous about upgrading myself, since I have JavaOne demos to prepare and want a stable environment.

Posted by jlaskey on September 06, 2013 at 04:56 AM PDT #

Hmm, I am finding non-Canvas apps are locking-up after a while as well with b105...

Posted by Felix Bembrick on September 06, 2013 at 05:11 AM PDT #

Really cool, worked out-of-the-box with b105@Mac. But is there a GWT-like approach where you can run JavaFX apps inside a browser only using HTML5/JavaScript code?

Posted by Alexander Orlov on September 07, 2013 at 07:27 PM PDT #

I have a copy running in 16M for 4 days and still running fine (Mac.)

Re GWT, nothing yet, but as part of my talk at JavaOne I will be making a call to arms to port libraries over to Nashorn. Much of what we've tested has just worked, but we really need the community to help us get through the volumes of JavaScript out in the wild.

Posted by jlaskey on September 08, 2013 at 05:16 AM PDT #

Alexander, check out bck2brwsr for Java/JavaFX running inside a browser without plugins.

Posted by Felix Bembrick on September 08, 2013 at 11:09 AM PDT #

Jim, the modified code with b105 definitely freezes quite quickly on Windows 7 but I don't know which of those factors is the cause.

Posted by Felix Bembrick on September 08, 2013 at 11:12 AM PDT #

Felix, bck2brwsr looks promising but what I don't get from the demo: is this a statefull approach? E.g. can the client/browser use parts of the application without having a connection to the application server, i.e. performing actions like sorting or locally stored (HTML5' IndexDB) Drag&Drop operations without having a connection to the application server?

Posted by Alexander Orlov on September 12, 2013 at 04:16 AM PDT #

Good news: Since upgrading to b106 I can run the Fireworks script for hours without it locking up. Problem seems to be solved!

Posted by Felix Bembrick on September 12, 2013 at 06:42 PM PDT #

Jim, Just an FYI, it doesn't seem to work on 64bit WinXP using b106. No error, just an empty white frame.

Also, why did you choose to use

var ArrayList = Java.type("java.util.ArrayList");

instead of simply

var ArrayList = java.util.ArrayList;

Thanks and nice work!

Posted by guest on September 13, 2013 at 10:39 AM PDT #

I don't normally use windows, but I'll see if I can rustle up a machine.

var ArrayList = java.util.ArrayList; is the traditional way and will continue to work. The issue is that it is a bit slower and creates more code/data than var ArrayList = Java.type("java.util.ArrayList");. This adds up if you have several hundred classes. We are recommending that people switch for that reason. Also you can get to other types like Java.type("int") and Java.type("double[]") that you can't get at with Packages notation.

Posted by jlaskey on September 13, 2013 at 11:08 AM PDT #

Just ran on WIndows 32 and 64 bit. Works fine. I think you must be missing the sf.jpg file.

Posted by jlaskey on September 13, 2013 at 12:02 PM PDT #

Jim, false alarm. I was able to figure out the issue. I was running it from the parent directory. Once I changed directories it worked like a champ. And thanks for the detailed answer.

Posted by guest on September 13, 2013 at 12:16 PM PDT #

Hello Jim

You have mentioned Nashorn Debugger prototype.
Is it open for contribution (e.g. GitHub)?
Please share some links for tooling. I am particular interested in Eclipse.

Posted by Paul Verest on October 04, 2013 at 06:45 AM PDT #

I haven't as yet. I hope to by the end of this week.

Posted by jlaskey on October 07, 2013 at 11:14 AM PDT #

Askari debugger example now at https://github.com/wickund/nashornexamples/

Posted by jlaskey on October 09, 2013 at 04:43 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Technical discussions and status of the Nashorn JavaScript Project.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today