Created
July 22, 2024 07:51
-
-
Save FDelporte/c74cdf59ecd9ef1b14df86e08faa0c56 to your computer and use it in GitHub Desktop.
Test script to compare performance of many moving Nodes versus drawing on the Canvas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///usr/bin/env jbang "$0" "$@" ; exit $? | |
//DEPS org.openjfx:javafx-controls:22.0.2 | |
//DEPS org.openjfx:javafx-graphics:22.0.2:${os.detected.jfxname} | |
import javafx.animation.AnimationTimer; | |
import javafx.animation.KeyFrame; | |
import javafx.animation.Timeline; | |
import javafx.application.Application; | |
import javafx.geometry.Pos; | |
import javafx.scene.control.Label; | |
import javafx.scene.Node; | |
import javafx.scene.Scene; | |
import javafx.scene.canvas.Canvas; | |
import javafx.scene.canvas.GraphicsContext; | |
import javafx.scene.layout.Pane; | |
import javafx.scene.layout.VBox; | |
import javafx.scene.paint.Color; | |
import javafx.scene.shape.Circle; | |
import javafx.stage.Stage; | |
import javafx.util.Duration; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Random; | |
/** | |
* To execute: | |
* - Install a Java runtime with JavaFX, eg. from https://www.azul.com/downloads/?package=jdk-fx#zulu | |
* - Install J'BANG! from https://www.jbang.dev/ | |
* - Start it with `jbang FxNodesVersusCanvas.java` | |
*/ | |
public class FxNodesVersusCanvas extends Application { | |
private static int TYPE_OF_TEST = 2; // 1 = Nodes, 2 = Canvas | |
private static int ADD_BALLS_PER_TICK = 10; | |
private static int APP_WIDTH = 600; | |
private static int APP_HEIGHT = 800; | |
private static int TOP_OFFSET = 80; | |
private Random r = new Random(); | |
private Scene scene; | |
private long lastUpdate = 0; | |
// For the Node demo | |
private Pane paneBalls = new Pane(); | |
// For the Canvas demo | |
private List<BallDrawing> ballDrawings = new ArrayList<>();; | |
private Canvas canvas = new Canvas(); | |
private GraphicsContext context; | |
private Label lblBallCount = new Label(); | |
@Override | |
public void start(Stage stage) { | |
var javaVersion = System.getProperty("java.version"); | |
var javaVendor = System.getProperty("java.vendor"); | |
var javaVendorVersion = System.getProperty("java.vm.version"); | |
var javafxVersion = System.getProperty("javafx.version"); | |
VBox holder = new VBox(); | |
scene = new Scene(holder, APP_WIDTH, APP_HEIGHT); | |
holder.setFillWidth(true); | |
holder.setAlignment(Pos.TOP_CENTER); | |
holder.setSpacing(5); | |
holder.getChildren().addAll( | |
new Label("Java: " + javaVersion + ", " + javaVendor + ", " + javaVendorVersion), | |
new Label("JavaFX: " + javafxVersion), | |
lblBallCount, | |
getFramerateLabel(), | |
getDemo() | |
); | |
stage.setTitle("JavaFX Demo, Nodes versus Canvas"); | |
stage.setScene(scene); | |
stage.show(); | |
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(5), t -> onTick())); | |
timeline.setCycleCount(Timeline.INDEFINITE); | |
timeline.play(); | |
} | |
public static void main(String[] args) { | |
launch(); | |
} | |
private Node getDemo() { | |
if (TYPE_OF_TEST == 1) { | |
// Nodes | |
paneBalls.setPrefWidth(Double.MAX_VALUE); | |
paneBalls.setPrefHeight(Double.MAX_VALUE); | |
paneBalls.setMinHeight(100.0); | |
return paneBalls; | |
} else if (TYPE_OF_TEST == 2) { | |
// Canvas | |
context = canvas.getGraphicsContext2D(); | |
canvas.widthProperty().bind(scene.widthProperty()); | |
canvas.heightProperty().bind(scene.heightProperty().subtract(TOP_OFFSET)); | |
return canvas; | |
} else { | |
return new Label("Set type of test to 1 or 2!"); | |
} | |
} | |
private void onTick() { | |
if (TYPE_OF_TEST == 1) { | |
// Nodes | |
// Add ball nodes to the pane | |
for (var i = 0; i < ADD_BALLS_PER_TICK; i++) { | |
paneBalls.getChildren().add(new BallNode()); | |
} | |
// Update the counter | |
lblBallCount.setText("Number of balls: " + paneBalls.getChildren().size()); | |
// Move all the balls in the pane | |
for (Node ballNode : paneBalls.getChildren()) { | |
((BallNode) ballNode).move(); | |
} | |
} else if (TYPE_OF_TEST == 2) { | |
// Canvas | |
// Add balls to the list of balls to be drawn | |
for (var i = 0; i < ADD_BALLS_PER_TICK; i++) { | |
ballDrawings.add(new BallDrawing()); | |
} | |
// Update the counter | |
lblBallCount.setText("Number of balls: " + ballDrawings.size()); | |
// Clear the canvas (remove all the previously balls that were drawn) | |
context.clearRect(0.0, 0.0, canvas.getWidth(), canvas.getHeight()); | |
// Move all the balls in the list, and draw them on the Canvas | |
for (BallDrawing ballDrawing : ballDrawings) { | |
ballDrawing.move(); | |
context.setFill(ballDrawing.getFill()); | |
context.fillOval(ballDrawing.getX(), ballDrawing.getY(), ballDrawing.getSize(), ballDrawing.getSize()); | |
} | |
} | |
} | |
private Label getFramerateLabel() { | |
Label lbl = new Label(); | |
AnimationTimer frameRateMeter = new AnimationTimer() { | |
@Override | |
public void handle(long now) { | |
if (lastUpdate > 0) { | |
long nanosElapsed = now - lastUpdate; | |
double frameRate = 1_000_000_000.0 / nanosElapsed; | |
lbl.setText(String.format("Current frame rate: %d", Math.round(frameRate))); | |
} | |
lastUpdate = now; | |
} | |
}; | |
frameRateMeter.start(); | |
return lbl; | |
} | |
class BallNode extends Circle { | |
private final Color randomColor = Color.color(Math.random(), Math.random(), Math.random()); | |
private final int size = r.nextInt(1, 10); | |
private double dx = r.nextInt(1, 5); | |
private double dy = r.nextInt(1, 5); | |
public BallNode() { | |
this.setRadius(size / 2); | |
this.setFill(randomColor); | |
relocate(r.nextInt(380), r.nextInt(620)); | |
} | |
public void move() { | |
if (hitRightOrLeftEdge()) { | |
dx *= -1; // Ball hit right or left wall, so reverse direction | |
} | |
if (hitTopOrBottom()) { | |
dy *= -1; // Ball hit top or bottom, so reverse direction | |
} | |
setLayoutX(getLayoutX() + dx); | |
setLayoutY(getLayoutY() + dy); | |
} | |
private boolean hitRightOrLeftEdge() { | |
return (getLayoutX() < (scene.getX() + getRadius())) || | |
(getLayoutX() > (scene.getWidth() - getRadius())); | |
} | |
private boolean hitTopOrBottom() { | |
return (getLayoutY() < (scene.getY() - getRadius())) || | |
(getLayoutY() > (scene.getHeight() - getRadius() - 60)); | |
} | |
} | |
class BallDrawing { | |
private final Color fill = Color.color(Math.random(), Math.random(), Math.random()); | |
private final int size = r.nextInt(1, 10); | |
private double x = r.nextInt(APP_WIDTH); | |
private double y = r.nextInt(APP_HEIGHT - TOP_OFFSET); | |
private double dx = r.nextInt(1, 5); | |
private double dy = r.nextInt(1, 5); | |
public void move() { | |
if (hitRightOrLeftEdge()) { | |
dx *= -1; // Ball hit right or left wall, so reverse direction | |
} | |
if (hitTopOrBottom()) { | |
dy *= -1; // Ball hit top or bottom, so reverse direction | |
} | |
x += dx; | |
y += dy; | |
} | |
private boolean hitRightOrLeftEdge() { | |
return (x < (scene.getX() + size)) || | |
(x > (scene.getWidth() - size)); | |
} | |
private boolean hitTopOrBottom() { | |
return (y < (scene.getY() - size)) || | |
(y > (scene.getHeight() - size - 60)); | |
} | |
public Color getFill() { | |
return fill; | |
} | |
public int getSize() { | |
return size; | |
} | |
public double getX() { | |
return x; | |
} | |
public void setX(double x) { | |
this.x = x; | |
} | |
public double getY() { | |
return y; | |
} | |
public void setY(double y) { | |
this.y = y; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment