Last active
September 27, 2016 23:01
-
-
Save Groostav/638e31d4cd2842aa2f05bd9f50c43156 to your computer and use it in GitHub Desktop.
UI workload sample problem
This file contains hidden or 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
class LoopWorker { | |
private val executor = SingleThreadedScheduledExecutorService() | |
fun doLoops() { | |
val stopwatch = Stopwatch.createUnstarted() | |
executor.scheduleWithFixedDelay(5000.ms) { | |
Platform.runLater { | |
println("starting work...") | |
stopwatch.start() | |
for (ignored in 0..60) politely { //note the "politely" between the 'for' and '{'. | |
//This is kotlin sugar, but effectively means 'politely' is an executor | |
doMysteryWorkload() | |
print("work ") | |
} | |
println() | |
println("finished work in ${stopwatch.elapsed(TimeUnit.MILLISECONDS)}ms, taking a break...") | |
stopwatch.reset() | |
} | |
} | |
} | |
private fun doMysteryWorkload() = try { Thread.sleep(20) } catch (e: InterruptedException) { } | |
fun politely(work: () -> Unit){ | |
work.invoke() | |
yieldUIEventQueue() | |
} | |
fun yieldUIEventQueue() { | |
val key = UUID.randomUUID() | |
Platform.runLater { | |
Toolkit.getToolkit().exitNestedEventLoop(key, null) | |
} | |
Toolkit.getToolkit().enterNestedEventLoop(key) | |
} | |
} |
This file contains hidden or 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
/** | |
* Displays a "responsiveness spinner" over top of a UI component, | |
* indicating the liveliness of the UI thread. | |
* | |
* In the event that a big job is executed on the UI thread, the UI thread will be blocked, and this | |
* component will not be re-rendered, causing it to "freeze" in place for some period of time. Once | |
* the job is completed, all of the update jobs this component has enqueued since it was last executed | |
* will run in quick succession, causing this component to "warp" to its most recent frame. | |
*/ | |
public class ResponsivenessSpinner extends Canvas { | |
public static final Logger log = Logger.getLogger(ResponsivenessSpinner.class.getCanonicalName()); | |
private static boolean wasInstanced = false; | |
public static boolean wasInstanced() { | |
return wasInstanced; | |
} | |
public static final int SpinerRadius = 100; | |
public static final double StrokeFudgeFactor = 1.05; | |
public static final int SceneWidth = 300; | |
public static final int SceneHeight = 250; | |
public static final int SpeedDivisor = 800; | |
private final Instant start = Instant.now(); | |
public ResponsivenessSpinner() { | |
super(SceneWidth, SceneHeight); | |
wasInstanced = true; | |
setMouseTransparent(true); | |
setDepthTest(DepthTest.DISABLE); | |
setFocusTraversable(false); | |
GraphicsContext gc = getGraphicsContext2D(); | |
gc.translate(SceneWidth / 2, SceneHeight / 2); | |
gc.setStroke(DisplayValues.EmpowerOpsTeal); | |
gc.setLineWidth(3); | |
new Thread(() -> { | |
while (true) { | |
Platform.runLater(() -> drawShapes(gc)); | |
try { | |
int sixtyFPS = (int) (1.0 / 60 * 1_000); | |
assert sixtyFPS > 1; | |
Thread.sleep(sixtyFPS); | |
} | |
catch (InterruptedException e) { | |
log.info("Interrupted on responsiveness thread?"); | |
continue; | |
} | |
} | |
}, "Responsiveness test").start(); | |
} | |
private void drawShapes(GraphicsContext gc) { | |
double offset = - SpinerRadius * StrokeFudgeFactor; | |
double across = 2 * SpinerRadius * StrokeFudgeFactor; | |
gc.clearRect(offset, offset, across, across); | |
Long secs = start.until(Instant.now(), ChronoUnit.MILLIS); | |
double x = SpinerRadius * Math.cos(secs.doubleValue() / SpeedDivisor); | |
double y = SpinerRadius * Math.sin(secs.doubleValue() / SpeedDivisor); | |
gc.strokeLine(x, y, - x, - y); | |
} | |
} |
This file contains hidden or 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
package com.empowerops.front_end; | |
import com.empowerops.common.ui.ResponsivenessSpinner; | |
import javafx.application.Application; | |
import javafx.scene.Group; | |
import javafx.scene.Scene; | |
import javafx.scene.canvas.Canvas; | |
import javafx.stage.Stage; | |
public class SimpleSpinnerDriver extends Application { | |
public static void main(String[] args) { | |
launch(args); | |
} | |
@Override | |
public void start(Stage primaryStage) { | |
primaryStage.setTitle("Drawing Operations Test"); | |
Group root = new Group(); | |
Canvas canvas = new ResponsivenessSpinner(); | |
root.getChildren().add(canvas); | |
primaryStage.setScene(new Scene(root)); | |
primaryStage.show(); | |
LoopWorker loopWorker = new LoopWorker(); | |
loopWorker.doLoops(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note the difference in UI responsiveness between when
politely
exists and when it doesn't is dramatic:politely
the UI thread is perfectly responsive, with a max frame-to-frame delay of 20ms + graphics overhead (16ms is 60fps)