diff --git a/api/src/main/java/com/bartlomiejpluta/base/lib/gui/FPSMonitor.java b/api/src/main/java/com/bartlomiejpluta/base/lib/gui/FPSMonitor.java new file mode 100644 index 00000000..3317d346 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/lib/gui/FPSMonitor.java @@ -0,0 +1,88 @@ +package com.bartlomiejpluta.base.lib.gui; + +import com.bartlomiejpluta.base.api.context.Context; +import com.bartlomiejpluta.base.api.gui.Attribute; +import com.bartlomiejpluta.base.api.gui.Color; +import com.bartlomiejpluta.base.api.gui.Component; +import com.bartlomiejpluta.base.api.gui.GUI; +import com.bartlomiejpluta.base.api.screen.Screen; +import com.bartlomiejpluta.base.util.profiler.FPSProfiler; + +import java.util.Map; + +public class FPSMonitor extends BaseComponent { + private final Color color; + private final Color background; + private FPSProfiler fpsProfiler = FPSProfiler.create(5, 40); + private float lineWidth = 1f; + + public FPSMonitor(Context context, GUI gui, Map refs) { + super(context, gui, refs); + color = gui.createColor(); + background = gui.createColor(); + color.setRGBA(0xFF0000FF); + background.setRGBA(0x444444AA); + } + + @Attribute("monitor") + public void setMonitor(Integer[] options) { + if (options.length != 2) { + throw new IllegalArgumentException("Expected 2 parameters: batch size and number of samples"); + } + + this.fpsProfiler = FPSProfiler.create(options[0], options[1]); + } + + public void setColor(Integer hex) { + color.setRGBA(hex); + } + + public void setBackground(Integer hex) { + background.setRGBA(hex); + } + + public void setLineWidth(Float width) { + this.lineWidth = width; + } + + @Override + protected float getContentWidth() { + return width; + } + + @Override + protected float getContentHeight() { + return height; + } + + @Override + public void update(float dt) { + super.update(dt); + fpsProfiler.update(dt); + } + + @Override + public void draw(Screen screen, GUI gui) { + var values = fpsProfiler.getSamples(); + var actualHeight = height - paddingTop - paddingBottom; + var step = (width - paddingLeft - paddingRight) / values.size(); + + gui.beginPath(); + gui.drawRectangle(x, y, width, height); + gui.setFillColor(background); + gui.fill(); + gui.closePath(); + + gui.beginPath(); + gui.moveTo(x + paddingLeft, (float) (actualHeight - values.get(0) / 60 * actualHeight + y + paddingTop)); + + for (int i = 1; i < values.size(); ++i) { + gui.drawLineTo(i * step + x + paddingLeft, (float) (actualHeight - values.get(i) / 60 * actualHeight + y + paddingTop)); + } + + gui.setStrokeColor(color); + gui.setStrokeWidth(lineWidth); + gui.stroke(); + gui.closePath(); + } +} diff --git a/api/src/main/java/com/bartlomiejpluta/base/util/collection/LimitedQueue.java b/api/src/main/java/com/bartlomiejpluta/base/util/collection/LimitedQueue.java new file mode 100644 index 00000000..f9357a71 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/util/collection/LimitedQueue.java @@ -0,0 +1,21 @@ +package com.bartlomiejpluta.base.util.collection; + +import lombok.RequiredArgsConstructor; + +import java.util.LinkedList; + +@RequiredArgsConstructor +public class LimitedQueue extends LinkedList { + private final int limit; + + @Override + public boolean add(E o) { + super.add(o); + + while (size() > limit) { + super.remove(); + } + + return true; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/bartlomiejpluta/base/util/profiler/FPSProfiler.java b/api/src/main/java/com/bartlomiejpluta/base/util/profiler/FPSProfiler.java index 0d06edf8..ecdb472d 100644 --- a/api/src/main/java/com/bartlomiejpluta/base/util/profiler/FPSProfiler.java +++ b/api/src/main/java/com/bartlomiejpluta/base/util/profiler/FPSProfiler.java @@ -1,29 +1,31 @@ package com.bartlomiejpluta.base.util.profiler; +import com.bartlomiejpluta.base.util.collection.LimitedQueue; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import java.util.LinkedList; import java.util.List; -import java.util.Map; - -import static java.util.Comparator.comparingInt; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.counting; -import static java.util.stream.Collectors.groupingBy; @Slf4j public class FPSProfiler { private final int batchSize; - private final List values = new LinkedList<>(); + + @Getter + private final List samples; private float fpsAccumulator = 0; private int pointer = 0; @Getter private double instantFPS = 0; - private FPSProfiler(int batchSize) { + private FPSProfiler(int batchSize, int samples) { this.batchSize = batchSize; + this.samples = new LimitedQueue<>(samples); + this.samples.add(0.0); + } + + public static FPSProfiler create(int batchSize, int samples) { + return new FPSProfiler(batchSize, samples); } public void update(float dt) { @@ -34,37 +36,7 @@ public class FPSProfiler { fpsAccumulator = 0; pointer = 0; - values.add(instantFPS); + samples.add(instantFPS); } } - - public void logResult() { - log.info("Min FPS: {}, max FPS: {}, avg FPS: {}", - values.stream().min(Double::compareTo).orElse(-1.0), - values.stream().max(Double::compareTo).orElse(-1.0), - totalAverage() - ); - - printHistogram(); - } - - private double totalAverage() { - return values.stream().reduce(0.0, Double::sum) / values.size(); - } - - private void printHistogram() { - values - .stream() - .mapToInt(Double::intValue) - .boxed() - .collect(groupingBy(identity(), counting())) - .entrySet() - .stream() - .sorted(comparingInt(Map.Entry::getKey)) - .forEach(e -> log.info("{} FPS: {}% ({} occurrences)", e.getKey(), e.getValue() * 100.0f / values.size(), e.getValue())); - } - - public static FPSProfiler create(int batchSize) { - return new FPSProfiler(batchSize); - } -} +} \ No newline at end of file