From 01ba33a8cafc254c6661c27fb2fc34c087ca0184 Mon Sep 17 00:00:00 2001 From: BenCheung0422 <74168521+BenCheung0422@users.noreply.github.com> Date: Sat, 14 Jan 2023 00:02:41 +0800 Subject: [PATCH] Improved the FPS setting --- src/main/java/minicraft/core/Game.java | 2 +- src/main/java/minicraft/core/Initializer.java | 35 +++-- src/main/java/minicraft/core/Renderer.java | 53 ++++--- src/main/java/minicraft/core/Updater.java | 2 +- src/main/java/minicraft/core/io/Settings.java | 132 ++++++++++++++++-- src/main/java/minicraft/saveload/Load.java | 2 +- .../screen/OptionsMainMenuDisplay.java | 2 +- .../minicraft/screen/OptionsWorldDisplay.java | 2 +- .../minicraft/screen/entry/ArrayEntry.java | 4 +- 9 files changed, 178 insertions(+), 56 deletions(-) diff --git a/src/main/java/minicraft/core/Game.java b/src/main/java/minicraft/core/Game.java index b6373542d..78a11d426 100644 --- a/src/main/java/minicraft/core/Game.java +++ b/src/main/java/minicraft/core/Game.java @@ -82,7 +82,7 @@ public static void main(String[] args) { World.resetGame(); // "half"-starts a new game, to set up initial variables player.eid = 0; new Load(true); // This loads any saved preferences. - MAX_FPS = (int) Settings.get("fps"); // DO NOT put this above. + MAX_FPS = Settings.getFPS(); // DO NOT put this above. // Update fullscreen frame if Updater.FULLSCREEN was updated previously if (Updater.FULLSCREEN) { diff --git a/src/main/java/minicraft/core/Initializer.java b/src/main/java/minicraft/core/Initializer.java index d69098b58..5c60a702a 100644 --- a/src/main/java/minicraft/core/Initializer.java +++ b/src/main/java/minicraft/core/Initializer.java @@ -1,25 +1,28 @@ package minicraft.core; -import java.awt.*; +import minicraft.core.io.FileHandler; +import minicraft.core.io.Settings; +import minicraft.util.Logging; +import minicraft.util.TinylogLoggingProvider; +import org.jetbrains.annotations.Nullable; +import org.tinylog.provider.ProviderRegistry; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Image; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.awt.image.BufferedImage; -import java.awt.image.ImageObserver; import java.io.IOException; import java.util.Objects; -import java.util.function.Supplier; import javax.imageio.ImageIO; -import javax.swing.*; - -import minicraft.core.io.FileHandler; -import minicraft.util.Logging; -import minicraft.util.TinylogLoggingProvider; - -import org.jetbrains.annotations.Nullable; -import org.tinylog.provider.ProviderRegistry; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.WindowConstants; public class Initializer extends Game { private Initializer() {} @@ -31,6 +34,7 @@ private Initializer() {} static LogoSplashCanvas logoSplash = new LogoSplashCanvas(); static int fra, tik; // These store the number of frames and ticks in the previous second; used for fps, at least. + public static JFrame getFrame() { return frame; } public static int getCurFps() { return fra; } static void parseArgs(String[] args) { @@ -85,17 +89,18 @@ static void run() { unprocessed--; } - if ((now - lastRender) / 1.0E9 > 1.0 / MAX_FPS) { + if (MAX_FPS == Settings.FPS_UNLIMITED || now - lastRender >= 1E9D / MAX_FPS) { frames++; lastRender = System.nanoTime(); Renderer.render(); } if (System.currentTimeMillis() - lastTimer1 > 1000) { //updates every 1 second - lastTimer1 += 1000; // Adds a second to the timer + long interval = System.currentTimeMillis() - lastTimer1; + lastTimer1 = System.currentTimeMillis(); // Adds a second to the timer - fra = frames; // Saves total frames in last second - tik = ticks; // Saves total ticks in last second + fra = (int) (frames * 1000 / interval); // Saves total frames in last second + tik = (int) (ticks * 1000 / interval); // Saves total ticks in last second frames = 0; // Resets frames ticks = 0; // Resets ticks; ie, frames and ticks only are per second } diff --git a/src/main/java/minicraft/core/Renderer.java b/src/main/java/minicraft/core/Renderer.java index a0b4a9355..702548aed 100644 --- a/src/main/java/minicraft/core/Renderer.java +++ b/src/main/java/minicraft/core/Renderer.java @@ -1,38 +1,22 @@ package minicraft.core; -import java.awt.Canvas; -import java.awt.Graphics; -import java.awt.geom.AffineTransform; -import java.awt.image.AffineTransformOp; -import java.awt.image.BufferStrategy; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; -import java.io.File; -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - import minicraft.core.CrashHandler.ErrorInfo; import minicraft.core.io.Localization; import minicraft.core.io.Settings; import minicraft.entity.furniture.Bed; import minicraft.entity.mob.Player; import minicraft.gfx.Color; +import minicraft.gfx.Ellipsis; import minicraft.gfx.Ellipsis.DotUpdater.TickUpdater; -import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.gfx.Ellipsis.SmoothEllipsis; -import minicraft.gfx.Ellipsis; import minicraft.gfx.Font; import minicraft.gfx.FontStyle; import minicraft.gfx.MinicraftImage; import minicraft.gfx.Point; import minicraft.gfx.Screen; import minicraft.gfx.SpriteLinker; +import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; import minicraft.item.PotionType; import minicraft.item.ToolItem; @@ -47,10 +31,27 @@ import minicraft.screen.entry.StringEntry; import minicraft.util.Quest; import minicraft.util.Quest.QuestSeries; +import org.json.JSONObject; -import javax.imageio.ImageIO; +import java.awt.AWTException; +import java.awt.Canvas; +import java.awt.Graphics; +import java.awt.GraphicsEnvironment; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferStrategy; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.File; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; -import org.json.JSONObject; +import javax.imageio.ImageIO; public class Renderer extends Game { private Renderer() {} @@ -105,7 +106,15 @@ public static void initScreen() { hudSheet = new LinkedSprite(SpriteType.Gui, "hud"); Initializer.startCanvasRendering(); - canvas.createBufferStrategy(3); + try { // Reference: https://stackoverflow.com/a/61843644. + canvas.createBufferStrategy(3, GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice() + .getDefaultConfiguration() + .getBufferCapabilities()); + } catch (AWTException e) { + CrashHandler.crashHandle(e, new ErrorInfo("Canvas Initialization Failure", ErrorInfo.ErrorType.UNEXPECTED, true, "The canvas is unable to be initialized.")); + } + canvas.requestFocus(); } diff --git a/src/main/java/minicraft/core/Updater.java b/src/main/java/minicraft/core/Updater.java index 1adeefc2e..35febd562 100644 --- a/src/main/java/minicraft/core/Updater.java +++ b/src/main/java/minicraft/core/Updater.java @@ -69,7 +69,7 @@ static void updateFullscreen() { // Dispose is needed to set undecorated value Initializer.frame.dispose(); - GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[0]; + GraphicsDevice device = Initializer.frame.getGraphicsConfiguration().getDevice(); if (Updater.FULLSCREEN) { Initializer.frame.setUndecorated(true); device.setFullScreenWindow(Initializer.frame); diff --git a/src/main/java/minicraft/core/io/Settings.java b/src/main/java/minicraft/core/io/Settings.java index 369145026..2d7fbb4a2 100644 --- a/src/main/java/minicraft/core/io/Settings.java +++ b/src/main/java/minicraft/core/io/Settings.java @@ -1,10 +1,12 @@ package minicraft.core.io; +import minicraft.core.Initializer; import minicraft.screen.entry.ArrayEntry; import minicraft.screen.entry.BooleanEntry; import minicraft.screen.entry.RangeEntry; import java.awt.*; +import java.util.ArrayList; import java.util.HashMap; public class Settings { @@ -12,7 +14,7 @@ public class Settings { private static final HashMap> options = new HashMap<>(); static { - options.put("fps", new RangeEntry("minicraft.settings.fps", 10, 300, getRefreshRate())); // Has to check if the game is running in a headless mode. If it doesn't set the fps to 60 + options.put("fps", new FPSEntry("minicraft.settings.fps")); // Has to check if the game is running in a headless mode. If it doesn't set the fps to 60 options.put("screenshot", new ArrayEntry<>("minicraft.settings.screenshot_scale", 1, 2, 5, 10, 15, 20)); // The magnification of screenshot. I would want to see ultimate sized. options.put("diff", new ArrayEntry<>("minicraft.settings.difficulty", "minicraft.settings.difficulty.easy", "minicraft.settings.difficulty.normal", "minicraft.settings.difficulty.hard")); options.get("diff").setSelection(1); @@ -61,19 +63,125 @@ public static void setIdx(String option, int idx) { options.get(option.toLowerCase()).setSelection(idx); } - private static int getRefreshRate() { - if (GraphicsEnvironment.isHeadless()) return 60; + public static final int FPS_UNLIMITED = -1; - int hz; - try { - hz = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getRefreshRate(); - } catch (HeadlessException e) { - return 60; + /** + * Getting the set value for maximum framerate. + * @return The set or device framerate value, or {@link #FPS_UNLIMITED} if unlimited. + */ + public static int getFPS() { + return ((FPSEntry) getEntry("fps")).getFPSValue(); + } + + private static class FPSEntry extends ArrayEntry { + public static class FPSValue { + private int fps = DisplayMode.REFRESH_RATE_UNKNOWN; + public enum ValueType { Normal, VSync, Unlimited; } + private final ValueType type; + + public FPSValue(ValueType type) { + this.type = type == ValueType.Normal ? ValueType.Unlimited : type; + } + public FPSValue(int fps) { + type = ValueType.Normal; + if (fps > 300) fps = 300; + if (fps < 10) fps = 10; + this.fps = fps; + } + + public int getFPS() { + if (type == ValueType.VSync) { + // There is currently no actual way to do VSync by only Swing/AWT. + // Instead, the device refresh rate is used. + int rate = Initializer.getFrame().getGraphicsConfiguration().getDevice().getDisplayMode().getRefreshRate(); + return rate == DisplayMode.REFRESH_RATE_UNKNOWN ? FPS_UNLIMITED : rate; + } else if (type == ValueType.Unlimited) { + return FPS_UNLIMITED; + } else { + return fps; + } + } + + @Override + public String toString() { + return type == ValueType.Normal ? String.valueOf(fps) : type.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (obj instanceof FPSValue) + return type == ((FPSValue) obj).type && + fps == ((FPSValue) obj).fps; + return false; + } + + @Override + public int hashCode() { + return type.ordinal() * fps + fps; + } + } + + private static FPSValue[] getArray() { + ArrayList list = new ArrayList<>(); + for (int i = 10; i <= 300; i += 10) { + list.add(new FPSValue(i)); + } + + list.add(new FPSValue(FPSValue.ValueType.VSync)); + list.add(new FPSValue(FPSValue.ValueType.Unlimited)); + + return list.toArray(new FPSValue[0]); } - if (hz == DisplayMode.REFRESH_RATE_UNKNOWN) return 60; - if (hz > 300) return 60; - if (10 > hz) return 60; - return hz; + public FPSEntry(String label) { + super(label, false, getArray()); + } + + /** + * Getting the set value for maximum framerate. + * @return The set or device framerate value, or {@link #FPS_UNLIMITED} if unlimited. + */ + public int getFPSValue() { + return getValue().getFPS(); + } + + @Override + public void setValue(Object value) { + if (value instanceof FPSValue) { + super.setValue(value); + } else { + if (!(value instanceof FPSValue.ValueType)) try { // First tests if it is an integral value. + int v; + if (value instanceof Integer) v = (int) value; + else v = Integer.parseInt(value.toString()); + if (v < 10) v = 10; + if (v > 300) v = 300; + v -= v % 10; + for (FPSValue val : options) { + if (val.fps == v) { + super.setValue(val); + return; + } + } + } catch (NumberFormatException ignored) {} + + try { // Then falls back to enums. + FPSValue.ValueType type; + if (value instanceof FPSValue.ValueType) type = (FPSValue.ValueType) value; + else type = FPSValue.ValueType.valueOf(value.toString()); + if (type != FPSValue.ValueType.Normal) for (FPSValue val : options) { + if (val.type == type) { + super.setValue(val); + return; + } + } + } catch (IllegalArgumentException ignored) {} + + // Finally VSync is applied. + super.setValue(FPSValue.ValueType.VSync); + } + } } } diff --git a/src/main/java/minicraft/saveload/Load.java b/src/main/java/minicraft/saveload/Load.java index 255170125..c5716d966 100644 --- a/src/main/java/minicraft/saveload/Load.java +++ b/src/main/java/minicraft/saveload/Load.java @@ -358,7 +358,7 @@ private void loadPrefs(String filename) { // Settings Settings.set("sound", json.getBoolean("sound")); Settings.set("autosave", json.getBoolean("autosave")); - Settings.set("fps", json.getInt("fps")); + Settings.set("fps", json.getString("fps")); Settings.set("showquests", json.optBoolean("showquests", true)); if (json.has("lang")) { diff --git a/src/main/java/minicraft/screen/OptionsMainMenuDisplay.java b/src/main/java/minicraft/screen/OptionsMainMenuDisplay.java index 4bffa026d..6ba7987cb 100644 --- a/src/main/java/minicraft/screen/OptionsMainMenuDisplay.java +++ b/src/main/java/minicraft/screen/OptionsMainMenuDisplay.java @@ -33,6 +33,6 @@ public OptionsMainMenuDisplay() { public void onExit() { Localization.changeLanguage(((LocaleInformation)Settings.get("language")).locale.toLanguageTag()); new Save(); - Game.MAX_FPS = (int)Settings.get("fps"); + Game.MAX_FPS = Settings.getFPS(); } } diff --git a/src/main/java/minicraft/screen/OptionsWorldDisplay.java b/src/main/java/minicraft/screen/OptionsWorldDisplay.java index e798d4ab9..b9e0c6074 100644 --- a/src/main/java/minicraft/screen/OptionsWorldDisplay.java +++ b/src/main/java/minicraft/screen/OptionsWorldDisplay.java @@ -87,6 +87,6 @@ private List getEntries() { public void onExit() { Localization.changeLanguage(((LocaleInformation)Settings.get("language")).locale.toLanguageTag()); new Save(); - Game.MAX_FPS = (int)Settings.get("fps"); + Game.MAX_FPS = Settings.getFPS(); } } diff --git a/src/main/java/minicraft/screen/entry/ArrayEntry.java b/src/main/java/minicraft/screen/entry/ArrayEntry.java index ba576daaf..97cd3d64b 100644 --- a/src/main/java/minicraft/screen/entry/ArrayEntry.java +++ b/src/main/java/minicraft/screen/entry/ArrayEntry.java @@ -9,8 +9,8 @@ public class ArrayEntry extends ListEntry { - private String label; - private T[] options; + private final String label; + protected T[] options; private boolean[] optionVis; private int selection;