diff --git a/core/src/main/java/dev/weisz/boba/RenderCallback.java b/core/src/main/java/dev/weisz/boba/RenderCallback.java new file mode 100644 index 0000000..076d5dc --- /dev/null +++ b/core/src/main/java/dev/weisz/boba/RenderCallback.java @@ -0,0 +1,6 @@ +package dev.weisz.boba; + +@FunctionalInterface +public interface RenderCallback { + String render(); +} diff --git a/core/src/main/java/dev/weisz/boba/StandardRenderer.java b/core/src/main/java/dev/weisz/boba/StandardRenderer.java index 2a611d7..8fcc1ff 100644 --- a/core/src/main/java/dev/weisz/boba/StandardRenderer.java +++ b/core/src/main/java/dev/weisz/boba/StandardRenderer.java @@ -2,6 +2,7 @@ import dev.weisz.ansi.*; import dev.weisz.ansi.parser.Width; +import dev.weisz.boba.jfr.BobaFrameRenderEvent; import dev.weisz.boba.tea.Msg; import dev.weisz.boba.terminal.WinSize; import org.jspecify.annotations.Nullable; @@ -19,6 +20,7 @@ public class StandardRenderer implements Renderer { private static final Logger LOGGER = LoggerFactory.getLogger(StandardRenderer.class); private final OutputStream out; + private final RenderCallback frameRenderer; private final int fps; private @Nullable Timer timer; // timer that calls the flush function (started based on fps) @@ -40,8 +42,9 @@ public class StandardRenderer implements Renderer { private String lastRender = ""; // used to check for any differences in the render private List lastRenderedLines = new ArrayList<>(); - public StandardRenderer(OutputStream out, int fps) { + public StandardRenderer(OutputStream out, RenderCallback frameRenderer, int fps) { this.out = out; + this.frameRenderer = frameRenderer; this.fps = fps; } @@ -132,7 +135,6 @@ public void start() { } running.set(true); - timer.scheduleAtFixedRate( new TimerTask() { @Override @@ -142,7 +144,17 @@ public void run() { return; } + // frame render will include the flush too, + var renderEvent = new BobaFrameRenderEvent(); + renderEvent.begin(); + renderEvent.activeRenderer = "Standard Renderer"; // TODO: replace with static key + + var frameContent = frameRenderer.render(); + + write(frameContent); flush(); + + renderEvent.commit(); } }, 0, @@ -371,6 +383,10 @@ public void handleMessages(Msg msg) { case Msg.PrintLineMsg(String line) -> write(line); + case Msg.ClearScreenMsg ignored -> clearScreen(); + + case Msg.SetTitleMsg(String title) -> setWindowTitle(title); + default -> {} } } diff --git a/core/src/main/java/dev/weisz/boba/jfr/BobaErrorEvent.java b/core/src/main/java/dev/weisz/boba/jfr/BobaErrorEvent.java new file mode 100644 index 0000000..459c4ef --- /dev/null +++ b/core/src/main/java/dev/weisz/boba/jfr/BobaErrorEvent.java @@ -0,0 +1,17 @@ +package dev.weisz.boba.jfr; + +import jdk.jfr.*; + +@Name(BobaErrorEvent.NAME) +@Label("Boba Error") +@Category("Boba") +public class BobaErrorEvent extends Event { + static final String NAME = "dev.weisz.boba.jfr.BobaError"; + + @Label("Message") + public String message; + + @Label("Stacktrace") + @DataAmount("kB") + public String stackTrace; +} diff --git a/core/src/main/java/dev/weisz/boba/jfr/BobaFrameRenderEvent.java b/core/src/main/java/dev/weisz/boba/jfr/BobaFrameRenderEvent.java new file mode 100644 index 0000000..fe89033 --- /dev/null +++ b/core/src/main/java/dev/weisz/boba/jfr/BobaFrameRenderEvent.java @@ -0,0 +1,18 @@ +package dev.weisz.boba.jfr; + +import jdk.jfr.*; + +@Name(BobaFrameRenderEvent.NAME) +@Label("Boba Frame Render") +@Category({"Boba", "Boba Renderer"}) +public class BobaFrameRenderEvent extends Event { + static final String NAME = "dev.weisz.boba.jfr.BobaFrameRender"; + + @Label("Active Renderer") + public String activeRenderer; + + @Label("Frame Render Duration") + @Description("The time that Program#view took to render the current frame") + @Timespan(Timespan.MICROSECONDS) + public long frameRenderDuration; +} diff --git a/core/src/main/java/dev/weisz/boba/tea/Program.java b/core/src/main/java/dev/weisz/boba/tea/Program.java index 9c460af..51eb91e 100644 --- a/core/src/main/java/dev/weisz/boba/tea/Program.java +++ b/core/src/main/java/dev/weisz/boba/tea/Program.java @@ -71,7 +71,7 @@ public void run(ProgramOpts opts) { }); // TODO: allow configurable renderer - renderer = new StandardRenderer(opts.output(), opts.fps()); + renderer = new StandardRenderer(opts.output(), this::view, opts.fps()); renderer.hideCursor(); if (!opts.startupTitle().isEmpty()) { @@ -102,7 +102,6 @@ public void run(ProgramOpts opts) { }); renderer.start(); - renderer.write(view()); LOGGER.debug("Program started with initial frame printed."); @@ -119,6 +118,9 @@ public void run(ProgramOpts opts) { eventLoop(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); + + + throw new RuntimeException(e); } @@ -141,11 +143,6 @@ private void eventLoop() throws InterruptedException { case Msg.QuitMsg _ -> { return; } - case Msg.ClearScreenMsg _ -> - renderer.clearScreen(); - - case Msg.SetTitleMsg(String title) -> - renderer.setWindowTitle(title); case BatchCmd.BatchMsg(List cmds) -> cmds.forEach(this::processCmd); @@ -153,6 +150,7 @@ private void eventLoop() throws InterruptedException { default -> { /* ignore */ } } + // handle all internal handling of messages renderer.handleMessages(msg); var updateCmd = update(msg); @@ -161,7 +159,10 @@ private void eventLoop() throws InterruptedException { processCmd(updateCmd); } - renderer.write(view()); + // don't force renderer to render because it already runs on a timer + // todo: maybe this should be toggable incase the user wants a low fps or + // todo: possibly only re-render on an event/command + //renderer.write(view()); } } diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 8f50634..cce6066 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -10,4 +10,5 @@ requires dev.mccue.color.terminal; requires org.apache.commons.lang3; requires org.slf4j; + requires jdk.jfr; } \ No newline at end of file