Skip to content

Commit

Permalink
Improved the FPS setting
Browse files Browse the repository at this point in the history
  • Loading branch information
BenCheung0422 authored and ChristofferHolmesland committed Jan 19, 2023
1 parent 95cf4ed commit 01ba33a
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 56 deletions.
2 changes: 1 addition & 1 deletion src/main/java/minicraft/core/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
35 changes: 20 additions & 15 deletions src/main/java/minicraft/core/Initializer.java
Original file line number Diff line number Diff line change
@@ -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() {}
Expand All @@ -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) {
Expand Down Expand Up @@ -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
}
Expand Down
53 changes: 31 additions & 22 deletions src/main/java/minicraft/core/Renderer.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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() {}
Expand Down Expand Up @@ -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();
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/minicraft/core/Updater.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
132 changes: 120 additions & 12 deletions src/main/java/minicraft/core/io/Settings.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
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 {

private static final HashMap<String, ArrayEntry<?>> 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);
Expand Down Expand Up @@ -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<FPSEntry.FPSValue> {
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<FPSValue> 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);
}
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/minicraft/saveload/Load.java
Original file line number Diff line number Diff line change
Expand Up @@ -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")) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/minicraft/screen/OptionsMainMenuDisplay.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
2 changes: 1 addition & 1 deletion src/main/java/minicraft/screen/OptionsWorldDisplay.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@ private List<ListEntry> 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();
}
}
4 changes: 2 additions & 2 deletions src/main/java/minicraft/screen/entry/ArrayEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

public class ArrayEntry<T> extends ListEntry {

private String label;
private T[] options;
private final String label;
protected T[] options;
private boolean[] optionVis;

private int selection;
Expand Down

0 comments on commit 01ba33a

Please sign in to comment.