feat(benchmark): add keyboard shortcut to skip tests
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 15 Mar 2026 04:22:54 +0000 (06:22 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 15 Mar 2026 04:22:54 +0000 (06:22 +0200)
Allow pressing Space to skip to the next benchmark test immediately.
Also improve cleanup by properly removing listeners and stopping the
render loop when the benchmark finishes.

doc/index.org
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java

index 2ad0fd7..c6606cb 100644 (file)
@@ -212,6 +212,7 @@ three tests sequentially, each for 30 seconds:
 The camera follows a deterministic orbital path around the scene,
 ensuring reproducible results across runs.
 
+Example benchmark results:
 #+begin_example
 ================================================================================
                          GRAPHICS BENCHMARK RESULTS
index 14e4d1b..d336dee 100644 (file)
@@ -10,8 +10,11 @@ import eu.svjatoslav.sixth.e3d.gui.Camera;
 import eu.svjatoslav.sixth.e3d.gui.FrameListener;
 import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
 import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardInputHandler;
 import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
 
+import javax.swing.SwingUtilities;
+import java.awt.event.KeyEvent;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
@@ -23,7 +26,9 @@ import java.util.List;
  * reproducible benchmark results to standard output.
  * 
  * <p>The benchmark creates a 16x16x16 grid of cubes (4096 total) with the camera
- * following a deterministic orbital path. Each test runs for 30 seconds.</p>
+ * following a deterministic orbital path. Each test runs for 30 seconds by default.</p>
+ * 
+ * <p>Press <b>Space</b> to skip to the next test immediately.</p>
  * 
  * <p>Available tests:</p>
  * <ul>
@@ -32,7 +37,7 @@ import java.util.List;
  *   <li>{@link WireframeCubesTest} - Line rendering</li>
  * </ul>
  */
-public class GraphicsBenchmark implements FrameListener {
+public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
 
     private static final int WINDOW_WIDTH = 1920;
     private static final int WINDOW_HEIGHT = 1080;
@@ -52,6 +57,7 @@ public class GraphicsBenchmark implements FrameListener {
     private long frameCount;
     private BenchmarkTest currentTest;
     private boolean testFinished = false;
+    private boolean benchmarkFinished = false;
     private final List<TestResult> results = new ArrayList<>();
     private final List<BenchmarkTest> tests = new ArrayList<>();
 
@@ -78,6 +84,7 @@ public class GraphicsBenchmark implements FrameListener {
         viewPanel.setFrameRate(0);
         shapes = viewPanel.getRootShapeCollection();
         viewPanel.addFrameListener(this);
+        viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
         camera = viewPanel.getCamera();
     }
 
@@ -90,7 +97,7 @@ public class GraphicsBenchmark implements FrameListener {
     private void startNextTest() {
         int nextIndex = results.size();
         if (nextIndex >= tests.size()) {
-            finishBenchmark();
+            scheduleBenchmarkFinish();
             return;
         }
 
@@ -103,8 +110,30 @@ public class GraphicsBenchmark implements FrameListener {
         currentTest.setup(shapes);
     }
 
+    private void finishCurrentTest() {
+        if (currentTest == null || testFinished) {
+            return;
+        }
+
+        testFinished = true;
+        long elapsed = System.currentTimeMillis() - testStartTime;
+        double durationSeconds = elapsed / 1000.0;
+        results.add(new TestResult(currentTest.getName(), frameCount, durationSeconds));
+
+        currentTest.teardown(shapes);
+        startNextTest();
+    }
+
+    private void scheduleBenchmarkFinish() {
+        benchmarkFinished = true;
+        SwingUtilities.invokeLater(this::finishBenchmark);
+    }
+
     private void finishBenchmark() {
         currentTest = null;
+        viewPanel.getKeyboardFocusStack().popFocusOwner();
+        viewPanel.removeFrameListener(this);
+        viewPanel.stop();
         viewFrame.dispose();
         printResults();
     }
@@ -138,6 +167,10 @@ public class GraphicsBenchmark implements FrameListener {
 
     @Override
     public boolean onFrame(ViewPanel viewPanel, int millisecondsSinceLastFrame) {
+        if (benchmarkFinished) {
+            return false;
+        }
+
         if (currentTest == null) {
             return true;
         }
@@ -155,14 +188,33 @@ public class GraphicsBenchmark implements FrameListener {
 
         long elapsed = System.currentTimeMillis() - testStartTime;
         if (elapsed >= TEST_DURATION_MS && !testFinished) {
-            testFinished = true;
-            double durationSeconds = elapsed / 1000.0;
-            results.add(new TestResult(currentTest.getName(), frameCount, durationSeconds));
-
-            currentTest.teardown(shapes);
-            startNextTest();
+            finishCurrentTest();
         }
 
         return true;
     }
+
+    @Override
+    public boolean keyPressed(KeyEvent event, ViewPanel viewPanel) {
+        if (event.getKeyChar() == ' ') {
+            finishCurrentTest();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean keyReleased(KeyEvent event, ViewPanel viewPanel) {
+        return false;
+    }
+
+    @Override
+    public boolean focusLost(ViewPanel viewPanel) {
+        return false;
+    }
+
+    @Override
+    public boolean focusReceived(ViewPanel viewPanel) {
+        return false;
+    }
 }
\ No newline at end of file