- * Will have no effect if no integrated or discrete graphics card is installed in the system. + * This value will have no effect on windows with V-Sync enabled. + * Set to {@code 0} for no limit. * * @since v1-alpha2 * * -- GETTER -- - * Gets the value for {@link #disallowIntegratedGraphics}. + * Gets the value for {@link #maximumFramesPerSecond} * * @return variable value - * @see GraphicsSubsystemConfiguration#disallowIntegratedGraphics + * @see #maximumFramesPerSecond * @since v1-alpha2 */ - private boolean disallowIntegratedGraphics; + private int maximumFramesPerSecond; /** * Constructs this class. @@ -175,17 +177,20 @@ public final class GraphicsSubsystemConfiguration implements SubsystemConfigurat switch (property) { case "debug" -> debug = parser.getBoolean(group + property); case "debugInput" -> debugInput = parser.getBoolean(group + property); + case "debugFrames" -> debugFrames = parser.getBoolean(group + property); case "errorGraphicsError" -> errorGraphicsError = parser.getBoolean(group + property); - case "disallowTearing" -> disallowTearing = parser.getBoolean(group + property); + case "maximumFramesPerSecond" -> maximumFramesPerSecond = parser.getInteger(group + property, true); } } catch (NullPointerException ignored) {} } // Disable all debug options if 'debug' is disabled - if (!debug) + if (!debug) { debugInput = false; + debugFrames = false; + } } /** {@inheritDoc} */ @@ -197,10 +202,11 @@ public final class GraphicsSubsystemConfiguration implements SubsystemConfigurat public void loadDefaultConfiguration() { debug = false; debugInput = false; + debugFrames = false; errorGraphicsError = true; - disallowTearing = false; + maximumFramesPerSecond = 60; } /** {@inheritDoc} */ @@ -209,10 +215,11 @@ public final class GraphicsSubsystemConfiguration implements SubsystemConfigurat switch (setting) { case "debug" -> { return debug; } case "debugInput" -> { return debugInput; } + case "debugFrames" -> { return debugFrames; } case "errorGraphicsError" -> { return errorGraphicsError; } - - case "disallowTearing" -> { return disallowTearing; } + + case "maximumFramesPerSecond" -> { return maximumFramesPerSecond; } default -> { return null; } } } diff --git a/graphics/src/main/java/de/staropensource/sosengine/graphics/classes/ApiManagementClass.java b/graphics/src/main/java/de/staropensource/sosengine/graphics/classes/ApiManagementClass.java index 3194b09..950fe3f 100644 --- a/graphics/src/main/java/de/staropensource/sosengine/graphics/classes/ApiManagementClass.java +++ b/graphics/src/main/java/de/staropensource/sosengine/graphics/classes/ApiManagementClass.java @@ -19,6 +19,10 @@ package de.staropensource.sosengine.graphics.classes; +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedHashMap; + /** * The interface for Graphics API management classes. * @@ -33,4 +37,25 @@ public interface ApiManagementClass { * @since v1-alpha2 */ boolean mustRunOnMainThread(); + + /** + * Runs the render loop once. + * To run the render loop continuously, see {@link #runRenderLoopContinuously()}. + * + * @return map of windows and their thrown throwables + * @since v1-alpha2 + */ + LinkedHashMap<@NotNull Window, @NotNull Throwable> runRenderLoop(); + + /** + * Runs the render loop for ever. + * To run the render loop only once, see {@link #runRenderLoop()}. + *
+ * Immediately returns with when a {@link #runRenderLoop()} call fails. + * + * @param frameCode code that should be invoked on during a frame. will be counted to frame time + * @return see {@link #runRenderLoop()} + * @since v1-alpha2 + */ + LinkedHashMap<@NotNull Window, @NotNull Throwable> runRenderLoopContinuously(@NotNull Runnable frameCode); } diff --git a/graphics/src/main/java/de/staropensource/sosengine/graphics/classes/Window.java b/graphics/src/main/java/de/staropensource/sosengine/graphics/classes/Window.java index f106af7..0c15e6d 100644 --- a/graphics/src/main/java/de/staropensource/sosengine/graphics/classes/Window.java +++ b/graphics/src/main/java/de/staropensource/sosengine/graphics/classes/Window.java @@ -494,8 +494,8 @@ public abstract class Window implements AutoCloseable { * @since v1-alpha2 */ @NotNull - public static ImmutableHashSet<@NotNull Window> getWindows() { - return new ImmutableHashSet<>(windows); + public static HashSet<@NotNull Window> getWindows() { + return new HashSet<>(windows); } /** diff --git a/testapp/src/main/java/de/staropensource/sosengine/testapp/Main.java b/testapp/src/main/java/de/staropensource/sosengine/testapp/Main.java index 5378a79..7baad6f 100644 --- a/testapp/src/main/java/de/staropensource/sosengine/testapp/Main.java +++ b/testapp/src/main/java/de/staropensource/sosengine/testapp/Main.java @@ -27,13 +27,22 @@ import de.staropensource.sosengine.base.types.CodePart; import de.staropensource.sosengine.base.types.logging.LogIssuer; import de.staropensource.sosengine.base.types.vectors.Vec2i; import de.staropensource.sosengine.base.utility.Miscellaneous; +import de.staropensource.sosengine.base.utility.parser.StackTraceParser; import de.staropensource.sosengine.graphics.GraphicsSubsystem; import de.staropensource.sosengine.graphics.classes.ApiMainClass; import de.staropensource.sosengine.graphics.classes.ApiManagementClass; import de.staropensource.sosengine.graphics.classes.Window; +import de.staropensource.sosengine.graphics.events.InputEvent; +import de.staropensource.sosengine.graphics.types.input.Key; +import de.staropensource.sosengine.graphics.types.input.KeyState; +import de.staropensource.sosengine.graphics.types.window.VsyncMode; import lombok.Getter; import lombok.SneakyThrows; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashMap; +import java.util.LinkedList; /** * The initialization class for sos!engine's development application. @@ -75,6 +84,13 @@ public class Main { */ private final LoggerInstance logger = new LoggerInstance(new LogIssuer(getClass(), CodePart.APPLICATION)); + /** + * Used for terminating the render loop. + * + * @since v1-alpha2 + */ + private boolean shutdown; + /** * Constructs this class. */ @@ -123,40 +139,38 @@ public class Main { .setTitle("test application window") .setSize(new Vec2i(960, 540)) .setPosition(new Vec2i(10, 10)) + .setVsyncMode(VsyncMode.OFF) .build(); } catch (Throwable throwable) { logger.crash("Window.Builder#build() failed", throwable); return; } - // Sleep for 2.5 seconds - logger.diag("Sleeping for 2.5s"); - try { - Thread.sleep(2500); - } catch (InterruptedException exception) { - logger.crash("Was unable to sleep for 2500ms", exception); + LinkedHashMap<@NotNull Window, @NotNull Throwable> renderLoopFailures = GraphicsSubsystem + .getInstance() + .getApi() + .getManagement() + .runRenderLoopContinuously(() -> { + if (shutdown || window.isClosureRequested()) + Engine.getInstance().shutdown(); + }); + StringBuilder message = new StringBuilder(); + message.append("Render loop failed on some windows:\n"); + + for (Window windowFailed : renderLoopFailures.keySet()) { + StackTraceParser parser = new StackTraceParser(renderLoopFailures.get(windowFailed)); + + message + .append("-> ") + .append(window) + .append(": ") + .append(parser.getHeader()) + .append("\n") + .append(parser.getStackTrace()) + .append("\n"); } - // Update state and render window - try { - logger.diag("Updating state"); - window.updateState(); - logger.diag("Rendering"); - window.render(); - } catch (Throwable throwable) { - logger.crash("Window updating or rendering failed", throwable); - } - - // Sleep for five seconds - logger.diag("Sleeping for 2.5s"); - try { - Thread.sleep(2500); - } catch (InterruptedException exception) { - logger.crash("Was unable to sleep for 2500ms", exception); - } - - // Shutdown - Engine.getInstance().shutdown(); + logger.crash(message.toString()); }, "mainThread"); } @@ -167,9 +181,24 @@ public class Main { * @since v1-alpha2 */ @EventListener(event = ThrowableCatchEvent.class) - public static void onThrowable(@NotNull Throwable throwable, @NotNull String identifier) { - if (identifier.equals("mainThread")) { + private static void onThrowable(@NotNull Throwable throwable, @NotNull String identifier) { + if (identifier.equals("mainThread") && instance != null) instance.logger.crash("The main thread threw an exception", throwable); + } + + /** + * Handles input events. + * + * @param window origin window + * @param key key pressed + * @param state key state + * @since v1-alpha2 + */ + @EventListener(event = InputEvent.class) + private static void onInput(@Nullable Window window, @NotNull Key key, @NotNull KeyState state) { + if (key == Key.ESCAPE && instance != null) { + instance.logger.diag("ESC pressed, setting shutdown flag"); + instance.shutdown = true; } } }