fix(demos): explicitly start render thread and fix benchmark timing
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 15 Mar 2026 17:04:32 +0000 (19:04 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 15 Mar 2026 17:04:32 +0000 (19:04 +0200)
- Add ensureRenderThreadStarted() call to all demo constructors
- Reorder GraphicsBenchmark initialization (registerTests before initializeWindow)
- Defer test transitions to beginning of next frame to avoid threading issues
- Add pendingTestTransition flag for safe test state changes

src/main/java/eu/svjatoslav/sixth/e3d/examples/GraphDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/RandomPolygonsDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/ShadedShapesDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/PointCloudDemo.java
src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java

index ae7ff23..5aa5e3f 100755 (executable)
@@ -197,6 +197,9 @@ public class GraphDemo {
         addWobblySurface(geometryCollection, -200);
 
         setCameraLocation(viewFrame);
+        
+        // Ensure the render thread is started
+        viewFrame.getViewPanel().ensureRenderThreadStarted();
     }
 
     /**
index 23a5282..6b3accc 100755 (executable)
@@ -161,6 +161,9 @@ public class OctreeDemo extends WorldNavigationUserInputTracker {
 
         viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
         viewPanel.repaintDuringNextViewUpdate();
+        
+        // Ensure the render thread is started
+        viewPanel.ensureRenderThreadStarted();
     }
 
     /**
index 7f97540..b3c9d3c 100644 (file)
@@ -94,5 +94,8 @@ public class RainingNumbersDemo implements FrameListener {
         }
 
         viewFrame.getViewPanel().addFrameListener(this);
+        
+        // Ensure the render thread is started
+        viewFrame.getViewPanel().ensureRenderThreadStarted();
     }
 }
index 8754044..30c4ef9 100755 (executable)
@@ -96,5 +96,8 @@ public class RandomPolygonsDemo {
         for (int i = 0; i < POLYGON_COUNT; i++)
             addRandomPolygon(shapeCollection);
 
+        // Ensure the render thread is started
+        viewFrame.getViewPanel().ensureRenderThreadStarted();
+
     }
 }
index c0cbd50..7d0b27d 100644 (file)
@@ -103,6 +103,9 @@ public class ShadedShapesDemo {
         viewPanel.addFrameListener(animator);
 
         viewPanel.repaintDuringNextViewUpdate();
+        
+        // Ensure the render thread is started
+        viewPanel.ensureRenderThreadStarted();
     }
 
     /**
index ec5c6b6..c9292a5 100644 (file)
@@ -44,6 +44,9 @@ public class TextEditorDemo {
         addGrid(shapeCollection);
 
         addTextEditors(viewPanel, shapeCollection);
+        
+        // Ensure the render thread is started
+        viewFrame.getViewPanel().ensureRenderThreadStarted();
     }
 
     /**
index 887fb30..936aed6 100644 (file)
@@ -66,6 +66,9 @@ public class TextEditorDemo2 {
         addGrid(shapeCollection);
 
         addCity(viewPanel, shapeCollection);
+        
+        // Ensure the render thread is started
+        viewFrame.getViewPanel().ensureRenderThreadStarted();
     }
 
     /**
index dc93a19..1f93318 100644 (file)
@@ -59,6 +59,7 @@ public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
     private BenchmarkTest currentTest;
     private boolean testFinished = false;
     private boolean benchmarkFinished = false;
+    private boolean pendingTestTransition = false;
     private final List<TestResult> results = new ArrayList<>();
     private final List<BenchmarkTest> tests = new ArrayList<>();
 
@@ -70,13 +71,12 @@ public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
         new GraphicsBenchmark();
     }
 
-    /**
+/**
      * Constructs and runs the graphics benchmark.
      */
     public GraphicsBenchmark() {
-        initializeWindow();
-        registerTests();
-        startNextTest();
+        registerTests();        // populate tests FIRST, before render thread starts
+        initializeWindow();     // now onFrame can safely access the tests list
     }
 
     private void initializeWindow() {
@@ -87,6 +87,8 @@ public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
         viewPanel.addFrameListener(this);
         viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
         camera = viewPanel.getCamera();
+        // Now explicitly start the render thread after all listeners are registered
+        viewPanel.ensureRenderThreadStarted();
     }
 
     private void registerTests() {
@@ -122,7 +124,14 @@ public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
         double durationSeconds = elapsed / 1000.0;
         results.add(new TestResult(currentTest.getName(), frameCount, durationSeconds));
 
-        currentTest.teardown(shapes);
+        // Defer test transition to safe point (beginning of next frame)
+        pendingTestTransition = true;
+    }
+
+    private void performTestTransition() {
+        if (currentTest != null) {
+            currentTest.teardown(shapes);
+        }
         startNextTest();
     }
 
@@ -169,12 +178,23 @@ public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
 
     @Override
     public boolean onFrame(ViewPanel viewPanel, int millisecondsSinceLastFrame) {
+        // Perform deferred test transition at safe point (before render cycle starts)
+        if (pendingTestTransition) {
+            pendingTestTransition = false;
+            performTestTransition();
+        }
+
+        // Deferred first-test start: runs on the render thread, before renderFrame()
+        if (currentTest == null && !benchmarkFinished && results.isEmpty()) {
+            startNextTest();
+        }
+
         if (benchmarkFinished) {
             return false;
         }
 
         if (currentTest == null) {
-            return true;
+            return false;
         }
 
         orbitAngle += ORBIT_SPEED * millisecondsSinceLastFrame;
index bc9f227..02d3c3e 100644 (file)
@@ -33,5 +33,8 @@ public class PointCloudDemo {
         // add galaxy
         geometryCollection.addShape(new Galaxy(500, 3, 10000, transform));
 
+        // Ensure the render thread is started
+        viewFrame.getViewPanel().ensureRenderThreadStarted();
+
     }
 }
index 86ac7f1..fd8398b 100644 (file)
@@ -87,6 +87,9 @@ public class Main extends WorldNavigationUserInputTracker {
 
         // Done! World is built. So ensure screen is updated too.
         viewPanel.repaintDuringNextViewUpdate();
+        
+        // Ensure the render thread is started
+        viewPanel.ensureRenderThreadStarted();
     }
 
     /**