From 9b735772431c29c25c39c0bec29cb57fd505a93c Mon Sep 17 00:00:00 2001 From: extio-de Date: Sun, 21 Dec 2025 19:28:37 +0100 Subject: [PATCH] Renderer full life cycle; --- .../extio/game_engine/renderer/Renderer.java | 2 + .../renderer/RendererConfiguration.java | 12 +- .../game_engine/renderer/RendererData.java | 11 +- .../renderer/RendererLauncher.java | 113 ++++++++++++++++++ .../game_engine/renderer/RendererLoop.java | 19 +-- .../renderer/g2d/G2DMainFrame.java | 4 +- .../game_engine/renderer/g2d/G2DRenderer.java | 19 +++ .../renderer/g2d/G2DRendererControl.java | 14 ++- 8 files changed, 172 insertions(+), 22 deletions(-) create mode 100644 src/main/java/de/extio/game_engine/renderer/RendererLauncher.java diff --git a/src/main/java/de/extio/game_engine/renderer/Renderer.java b/src/main/java/de/extio/game_engine/renderer/Renderer.java index da03708..76a3742 100644 --- a/src/main/java/de/extio/game_engine/renderer/Renderer.java +++ b/src/main/java/de/extio/game_engine/renderer/Renderer.java @@ -11,6 +11,8 @@ public interface Renderer { void run(List renderingBOs) throws InterruptedException; void reset(); + + void shutdown(); void takeScreenshot(); diff --git a/src/main/java/de/extio/game_engine/renderer/RendererConfiguration.java b/src/main/java/de/extio/game_engine/renderer/RendererConfiguration.java index 86d9e93..d57ad40 100644 --- a/src/main/java/de/extio/game_engine/renderer/RendererConfiguration.java +++ b/src/main/java/de/extio/game_engine/renderer/RendererConfiguration.java @@ -8,6 +8,7 @@ import java.util.function.Consumer; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import de.extio.game_engine.renderer.model.RenderingBo; @@ -32,8 +33,8 @@ public class RendererConfiguration { } @Bean - public RendererData rendererData(final Renderer renderer, final RendererControl rendererControl, final RenderingBoPool renderingBoPool, @Qualifier("gameEngineEventConsumer") final Consumer eventConsumer) { - final var rendererData = new RendererData(renderer, rendererControl, renderingBoPool, eventConsumer); + public RendererData rendererData(final ApplicationContext applicationContext, final Renderer renderer, final RendererControl rendererControl, final RenderingBoPool renderingBoPool, @Qualifier("gameEngineEventConsumer") final Consumer eventConsumer) { + final var rendererData = new RendererData(applicationContext, renderer, rendererControl, renderingBoPool, eventConsumer); renderer.setRendererData(rendererData); rendererControl.setRendererData(rendererData); renderingBoPool.setRendererData(rendererData); @@ -41,11 +42,8 @@ public class RendererConfiguration { } @Bean - public RendererLoop rendererLoop(final RendererData rendererData) { - final var rendererLoop = new RendererLoop(rendererData); - rendererData.setRendererLoop(rendererLoop); - rendererLoop.start(); - return rendererLoop; + public RendererLauncher rendererLauncher(final ApplicationContext applicationContext, final RendererData rendererData) { + return new RendererLauncher(applicationContext, rendererData); } } diff --git a/src/main/java/de/extio/game_engine/renderer/RendererData.java b/src/main/java/de/extio/game_engine/renderer/RendererData.java index fec7e11..4fc2b0e 100644 --- a/src/main/java/de/extio/game_engine/renderer/RendererData.java +++ b/src/main/java/de/extio/game_engine/renderer/RendererData.java @@ -3,10 +3,14 @@ package de.extio.game_engine.renderer; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import org.springframework.context.ApplicationContext; + import de.extio.game_engine.renderer.model.options.UiOptions; import de.extio.game_engine.renderer.model.options.VideoOptions; public class RendererData { + + private final ApplicationContext applicationContext; private final Renderer renderer; @@ -24,12 +28,17 @@ public class RendererData { private RendererLoop rendererLoop; - public RendererData(final Renderer renderer, final RendererControl rendererControl, final RenderingBoPool renderingBoPool, final Consumer eventConsumer) { + public RendererData(final ApplicationContext applicationContext, final Renderer renderer, final RendererControl rendererControl, final RenderingBoPool renderingBoPool, final Consumer eventConsumer) { + this.applicationContext = applicationContext; this.renderer = renderer; this.rendererControl = rendererControl; this.renderingBoPool = renderingBoPool; this.eventConsumer = eventConsumer; } + + public ApplicationContext getApplicationContext() { + return applicationContext; + } public RendererControl getRendererControl() { return this.rendererControl; diff --git a/src/main/java/de/extio/game_engine/renderer/RendererLauncher.java b/src/main/java/de/extio/game_engine/renderer/RendererLauncher.java new file mode 100644 index 0000000..be9b287 --- /dev/null +++ b/src/main/java/de/extio/game_engine/renderer/RendererLauncher.java @@ -0,0 +1,113 @@ +package de.extio.game_engine.renderer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ApplicationContext; + +public class RendererLauncher implements InitializingBean, DisposableBean { + + private static final Logger LOGGER = LoggerFactory.getLogger(RendererLauncher.class); + + private final ApplicationContext applicationContext; + + private final RendererData rendererData; + + private RendererLoop rendererLoop; + + private Thread rendererLoopThread; + + private Thread watchDogThread; + + public RendererLauncher(final ApplicationContext applicationContext, final RendererData rendererData) { + this.applicationContext = applicationContext; + this.rendererData = rendererData; + } + + // @Override + // public void start() { + // this.launch(); + // } + + @Override + public void afterPropertiesSet() throws Exception { + this.launch(); + } + + @Override + public void destroy() throws Exception { + this.shutdown(); + } + + // @Override + // public void stop() { + // this.shutdown(); + // } + + // @Override + // public boolean isRunning() { + // return this.rendererLoopThread != null && this.rendererLoopThread.isAlive(); + // } + + private void launch() { + if (this.rendererLoopThread != null && this.rendererLoopThread.isAlive()) { + return; + } + + LOGGER.info("Launching RendererLoop"); + this.rendererLoop = new RendererLoop(this.rendererData); + rendererData.setRendererLoop(rendererLoop); + this.rendererLoopThread = Thread.ofPlatform() + .name("RendererLoop") + .daemon(false) + .start(this.rendererLoop); + + LOGGER.info("Launching RendererWatchDog"); + this.watchDogThread = Thread.ofPlatform() + .name("RendererWatchDog") + .daemon(true) + .start(() -> { + try { + this.rendererLoopThread.join(); + LOGGER.error("RendererLoop thread has exited unexpectedly, shutting down application"); + SpringApplication.exit(this.applicationContext, () -> 1); + } + catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } + + private void shutdown() { + if (this.watchDogThread != null && this.watchDogThread.isAlive()) { + LOGGER.info("Shutting down RendererWatchDog"); + this.watchDogThread.interrupt(); + try { + this.watchDogThread.join(5000); + } + catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + this.watchDogThread = null; + + if (this.rendererLoopThread != null && this.rendererLoopThread.isAlive() && this.rendererLoop != null) { + LOGGER.info("Shutting down RendererLoop"); + this.rendererLoop.shutdown(); + try { + this.rendererLoopThread.join(5000); + } + catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + this.rendererLoopThread = null; + this.rendererLoop = null; + this.rendererData.setRendererLoop(null); + + this.rendererData.getRenderer().shutdown(); + } + +} diff --git a/src/main/java/de/extio/game_engine/renderer/RendererLoop.java b/src/main/java/de/extio/game_engine/renderer/RendererLoop.java index 2965c92..0900a11 100644 --- a/src/main/java/de/extio/game_engine/renderer/RendererLoop.java +++ b/src/main/java/de/extio/game_engine/renderer/RendererLoop.java @@ -5,28 +5,28 @@ import java.util.ArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RendererLoop extends Thread { +public class RendererLoop implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(RendererLoop.class); - private final RendererData rendererData; + private volatile boolean shutdown; - private volatile boolean running = true; + private final RendererData rendererData; public RendererLoop(final RendererData rendererData) { this.rendererData = rendererData; - this.setName("Renderer-Loop"); - this.setDaemon(false); } @Override public void run() { + this.shutdown = false; + try { this.rendererData.getRenderer().show(); - + LOGGER.info("Renderer loop started"); int exceptionCount = 0; - while (this.running) { + while (!this.shutdown) { try { this.rendererData.nextFrame(); this.rendererData.getRenderer().run(new ArrayList<>()); // TODO: Pass actual renderables @@ -45,7 +45,7 @@ public class RendererLoop extends Thread { } } } - + LOGGER.info("Renderer loop exited"); } catch (final Throwable t) { @@ -54,7 +54,8 @@ public class RendererLoop extends Thread { } public void shutdown() { - this.running = false; + LOGGER.info("RendererLoop shutdown requested"); + this.shutdown = true; } } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/G2DMainFrame.java b/src/main/java/de/extio/game_engine/renderer/g2d/G2DMainFrame.java index cdcb771..b83d789 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/G2DMainFrame.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/G2DMainFrame.java @@ -27,6 +27,7 @@ import javax.imageio.ImageIO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; import de.extio.game_engine.keyboard.KeyModifiers; import de.extio.game_engine.renderer.RendererControl; @@ -93,7 +94,8 @@ public class G2DMainFrame extends Frame { @Override public void windowClosing(final WindowEvent e) { - G2DMainFrame.this.rendererData.getRendererLoop().shutdown(); + LOGGER.info("Window closing requested"); + SpringApplication.exit(G2DMainFrame.this.rendererData.getApplicationContext(), () -> 0); } @Override diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java index a6d1b6a..ebb2285 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java @@ -98,6 +98,25 @@ public class G2DRenderer implements Renderer { public void reset() { G2DDrawControl.reset(); } + + @Override + public void shutdown() { + LOGGER.info("shutdown()"); + + this.rwLock.writeLock().lock(); + try { + if (this.mainFrame != null) { + this.mainFrame.unregisterFullScreenWindow(); + this.reset(); + this.mainFrame.setVisible(false); + this.mainFrame.dispose(); + this.mainFrame = null; + } + } + finally { + this.rwLock.writeLock().unlock(); + } + } @Override public void run(final List renderingBOs) throws InterruptedException { diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRendererControl.java b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRendererControl.java index f5e4283..2013d38 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/G2DRendererControl.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/G2DRendererControl.java @@ -163,8 +163,11 @@ public class G2DRendererControl implements RendererControl { if (this.updateAbsoluteViewportPortDimension()) { this.recalculate(); } - this.renderer.getMainFrame().createBufferStrategy(); - this.renderer.getMainFrame().resizeListenerEnabled = true; + final var mainFrame = this.renderer.getMainFrame(); + if (mainFrame != null) { + mainFrame.createBufferStrategy(); + mainFrame.resizeListenerEnabled = true; + } } finally { if (lockRenderer) { @@ -174,7 +177,10 @@ public class G2DRendererControl implements RendererControl { }; if (async) { - EventQueue.invokeLater(run); + // EventQueue.invokeLater(run); // Using EventQueue here can clutter the AWT event queue with too many events when resizing on a system with high event rate + Thread.ofVirtual() + .name("G2DRendererControl-ViewportUpdater", 0) + .start(run); } else { run.run(); @@ -189,7 +195,7 @@ public class G2DRendererControl implements RendererControl { private boolean updateAbsoluteViewportPortDimension() { boolean changed = false; final var absoluteDim = this.absoluteViewportDimension; - if (absoluteDim == null || absoluteDim.getX() != this.renderer.getMainFrame().getWidth() || absoluteDim.getY() != this.renderer.getMainFrame().getHeight()) { + if (absoluteDim == null || (this.renderer.getMainFrame() != null && (absoluteDim.getX() != this.renderer.getMainFrame().getWidth() || absoluteDim.getY() != this.renderer.getMainFrame().getHeight()))) { this.absoluteViewportDimension = ImmutableCoordI2.create(this.renderer.getMainFrame().getWidth(), this.renderer.getMainFrame().getHeight()); changed = true; }