Skip to content

Commit

Permalink
Rewrote error capturing and logging II
Browse files Browse the repository at this point in the history
  • Loading branch information
LatvianModder committed Dec 6, 2023
1 parent e92823e commit 0912c5b
Show file tree
Hide file tree
Showing 30 changed files with 697 additions and 305 deletions.
4 changes: 2 additions & 2 deletions common/src/main/java/dev/latvian/mods/kubejs/KubeJS.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ public KubeJS() throws Throwable {
KubeJSPlugins.forEachPlugin(KubeJSPlugin::init);
KubeJSPlugins.forEachPlugin(KubeJSPlugin::registerEvents);

startupScriptManager = new ScriptManager(ScriptType.STARTUP, KubeJSPaths.STARTUP_SCRIPTS);
clientScriptManager = new ScriptManager(ScriptType.CLIENT, KubeJSPaths.CLIENT_SCRIPTS);
startupScriptManager = new ScriptManager(ScriptType.STARTUP);
clientScriptManager = new ScriptManager(ScriptType.CLIENT);

startupScriptManager.reload(null);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dev.latvian.mods.kubejs;

import dev.latvian.mods.kubejs.script.ConsoleLine;
import dev.latvian.mods.kubejs.script.ScriptType;
import dev.latvian.mods.kubejs.script.data.ExportablePackResources;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.nbt.CompoundTag;
Expand Down Expand Up @@ -48,4 +50,10 @@ public void reloadStartupScripts(boolean dedicated) {

public void export(List<ExportablePackResources> packs) {
}

public void openErrors(ScriptType type) {
}

public void openErrors(ScriptType type, List<ConsoleLine> errors, List<ConsoleLine> warnings) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public void tick(Level level, BlockPos blockPos, BlockState blockState, BlockEnt
callback.accept(e);
} catch (Exception ex) {
if (server) {
ConsoleJS.SERVER.error("Error while ticking KubeJS block entity '" + info.blockBuilder.id + "'", ex, null);
ConsoleJS.SERVER.error("Error while ticking KubeJS block entity '" + info.blockBuilder.id + "'", ex);
} else {
ConsoleJS.CLIENT.error("Error while ticking KubeJS block entity '" + info.blockBuilder.id + "'", ex, null);
ConsoleJS.CLIENT.error("Error while ticking KubeJS block entity '" + info.blockBuilder.id + "'", ex);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import dev.latvian.mods.kubejs.item.ItemModelPropertiesEventJS;
import dev.latvian.mods.kubejs.net.NetworkEventJS;
import dev.latvian.mods.kubejs.registry.RegistryInfo;
import dev.latvian.mods.kubejs.script.ConsoleLine;
import dev.latvian.mods.kubejs.script.ScriptType;
import dev.latvian.mods.kubejs.script.data.ExportablePackResources;
import dev.latvian.mods.kubejs.script.data.GeneratedData;
Expand Down Expand Up @@ -187,4 +188,14 @@ public void export(List<ExportablePackResources> packs) {
}
}
}

@Override
public void openErrors(ScriptType type) {
Minecraft.getInstance().execute(() -> Minecraft.getInstance().setScreen(new KubeJSErrorScreen(Minecraft.getInstance().screen, type.console)));
}

@Override
public void openErrors(ScriptType type, List<ConsoleLine> errors, List<ConsoleLine> warnings) {
Minecraft.getInstance().execute(() -> Minecraft.getInstance().setScreen(new KubeJSErrorScreen(Minecraft.getInstance().screen, type, null, errors, warnings)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private void respawn(LocalPlayer oldPlayer, LocalPlayer newPlayer) {
@Nullable
public static Screen setScreen(Screen screen) {
if (screen instanceof TitleScreen && !ConsoleJS.STARTUP.errors.isEmpty() && CommonProperties.get().startupErrorGUI) {
return new KubeJSErrorScreen(ConsoleJS.STARTUP);
return new KubeJSErrorScreen(screen, ConsoleJS.STARTUP);
}

return screen;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,102 +1,274 @@
package dev.latvian.mods.kubejs.client;

import dev.latvian.mods.kubejs.CommonProperties;
import dev.latvian.mods.kubejs.script.ConsoleLine;
import dev.latvian.mods.kubejs.script.ScriptType;
import dev.latvian.mods.kubejs.util.ConsoleJS;
import dev.latvian.mods.kubejs.util.LogType;
import dev.latvian.mods.kubejs.util.UtilsJS;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.MultiLineLabel;
import net.minecraft.client.gui.components.ObjectSelectionList;
import net.minecraft.client.gui.navigation.CommonInputs;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.util.Mth;
import net.minecraft.util.FormattedCharSequence;
import org.jetbrains.annotations.Nullable;

import java.awt.Desktop;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Calendar;
import java.util.List;

public class KubeJSErrorScreen extends Screen {
public final ConsoleJS console;
private MultiLineLabel multilineMessage;
public final Screen lastScreen;
public final ScriptType scriptType;
public final Path logFile;
public final List<ConsoleLine> errors;
public final List<ConsoleLine> warnings;
//private MultiLineLabel multilineMessage;
private ErrorList list;

public KubeJSErrorScreen(ConsoleJS console) {
public KubeJSErrorScreen(Screen lastScreen, ScriptType scriptType, @Nullable Path logFile, List<ConsoleLine> errors, List<ConsoleLine> warnings) {
super(Component.empty());
this.console = console;
this.multilineMessage = MultiLineLabel.EMPTY;
this.lastScreen = lastScreen;
this.scriptType = scriptType;
this.logFile = logFile;
this.errors = errors;
this.warnings = warnings;
//this.multilineMessage = MultiLineLabel.EMPTY;
}

public KubeJSErrorScreen(Screen lastScreen, ConsoleJS console) {
this(lastScreen, console.scriptType, console.scriptType.getLogFile(), new ArrayList<>(console.errors), new ArrayList<>(console.warnings));
}

@Override
public Component getNarrationMessage() {
return Component.literal("There were KubeJS startup errors!");
return Component.literal("There were KubeJS " + scriptType.name + " errors!");
}

@Override
protected void init() {
super.init();
this.list = new ErrorList(this, this.minecraft, this.width, this.height, 32, this.height - 32);
this.addWidget(list);

/*
var list = new ArrayList<Component>();
list.add(Component.literal("There were KubeJS startup errors ").append(Component.literal("[" + console.errors.size() + "]").withStyle(ChatFormatting.DARK_RED)).append("!"));
list.add(Component.literal("There were KubeJS " + scriptType.name + " errors ").append(Component.literal("[" + errors.size() + "]").withStyle(ChatFormatting.DARK_RED)).append("!"));
var style = Style.EMPTY.withColor(0xD19893);
var errors = new ArrayList<>(console.errors);
var errorStyle = Style.EMPTY.withColor(0xD19893);
var warningStyle = Style.EMPTY.withColor(0xCEB692);
for (int i = 0; i < errors.size(); i++) {
list.add(Component.empty());
list.add(Component.literal((i + 1) + ") ").withStyle(ChatFormatting.DARK_RED).append(Component.literal(errors.get(i).getText().replace("Error occurred while handling event ", "Error in ").replace("dev.latvian.mods.kubejs.", "...")).withStyle(style)));
list.add(Component.literal((i + 1) + ") ").withStyle(ChatFormatting.DARK_RED).append(Component.literal(errors.get(i).getText().replace("Error occurred while handling event ", "Error in ").replace("dev.latvian.mods.kubejs.", "...")).withStyle(errorStyle)));
}
for (int i = 0; i < warnings.size(); i++) {
list.add(Component.empty());
list.add(Component.literal((i + 1) + ") ").withStyle(ChatFormatting.GOLD).append(Component.literal(warnings.get(i).getText().replace("Error occurred while handling event ", "Error in ").replace("dev.latvian.mods.kubejs.", "...")).withStyle(warningStyle)));
}
*/

this.multilineMessage = MultiLineLabel.create(this.font, CommonComponents.joinLines(list), this.width - 12);
// this.multilineMessage = MultiLineLabel.create(this.font, CommonComponents.joinLines(list), this.width - 12);
// this.multilineMessage = MultiLineLabel.create(this.font, CommonComponents.joinLines(Component.literal("Hi")), this.width - 12);
int i = this.height - 26;

Button openLog;

if (CommonProperties.get().startupErrorReportUrl.isBlank()) {
this.addRenderableWidget(Button.builder(Component.literal("Open startup.log"), this::openLog).bounds(this.width / 2 - 155, i, 150, 20).build());
this.addRenderableWidget(Button.builder(Component.literal("Quit"), this::quit).bounds(this.width / 2 - 155 + 160, i, 150, 20).build());
openLog = this.addRenderableWidget(Button.builder(Component.literal("Open Log File"), this::openLog).bounds(this.width / 2 - 155, i, 150, 20).build());
this.addRenderableWidget(Button.builder(Component.literal(scriptType.isStartup() ? "Quit" : "Close"), this::quit).bounds(this.width / 2 - 155 + 160, i, 150, 20).build());
} else {
this.addRenderableWidget(Button.builder(Component.literal("Open startup.log"), this::openLog).bounds(this.width / 4 - 55, i, 100, 20).build());
openLog = this.addRenderableWidget(Button.builder(Component.literal("Open Log File"), this::openLog).bounds(this.width / 4 - 55, i, 100, 20).build());
this.addRenderableWidget(Button.builder(Component.literal("Report"), this::report).bounds(this.width / 2 - 50, i, 100, 20).build());
this.addRenderableWidget(Button.builder(Component.literal("Quit"), this::quit).bounds(this.width * 3 / 4 - 45, i, 100, 20).build());
this.addRenderableWidget(Button.builder(Component.literal(scriptType.isStartup() ? "Quit" : "Close"), this::quit).bounds(this.width * 3 / 4 - 45, i, 100, 20).build());
}

openLog.active = logFile != null;
}

private void quit(Button button) {
minecraft.stop();
if (scriptType.isStartup()) {
minecraft.stop();
} else {
onClose();
}
}

private void report(Button button) {
handleComponentClicked(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, CommonProperties.get().startupErrorReportUrl)));
}

private void openLog(Button button) {
handleComponentClicked(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, console.scriptType.getLogFile().toAbsolutePath().toString())));
if (logFile != null) {
handleComponentClicked(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, logFile.toAbsolutePath().toString())));
}
}

@Override
public void render(GuiGraphics guiGraphics, int i, int j, float f) {
public void render(GuiGraphics guiGraphics, int mx, int my, float delta) {
this.renderBackground(guiGraphics);
this.multilineMessage.renderCentered(guiGraphics, this.width / 2, this.messageTop());
super.render(guiGraphics, i, j, f);
this.list.render(guiGraphics, mx, my, delta);
guiGraphics.drawCenteredString(this.font, "KubeJS " + scriptType.name + " script errors", this.width / 2, 12, 0xFFFFFF);
super.render(guiGraphics, mx, my, delta);
}

private int titleTop() {
int i = (this.height - this.messageHeight()) / 2;
int var10000 = i - 20;
Objects.requireNonNull(this.font);
return Mth.clamp(var10000 - 9, 10, 80);
@Override
public boolean shouldCloseOnEsc() {
return !scriptType.isStartup();
}

private int messageTop() {
return this.titleTop() + 20;
@Override
public void onClose() {
minecraft.setScreen(lastScreen);
}

private int messageHeight() {
int var10000 = this.multilineMessage.getLineCount();
Objects.requireNonNull(this.font);
return var10000 * 9;
public static class ErrorList extends ObjectSelectionList<Entry> {
public final KubeJSErrorScreen screen;

public ErrorList(KubeJSErrorScreen screen, Minecraft minecraft, int x1, int height, int y0, int y1) {
super(minecraft, x1, height, y0, y1, 48);
this.screen = screen;

setRenderBackground(false);

var calendar = Calendar.getInstance();

for (int i = 0; i < screen.errors.size(); i++) {
addEntry(new KubeJSErrorScreen.Entry(this, minecraft, i, screen.errors.get(i), calendar));
}

for (int i = 0; i < screen.warnings.size(); i++) {
addEntry(new KubeJSErrorScreen.Entry(this, minecraft, i, screen.warnings.get(i), calendar));
}
}

@Override
public boolean keyPressed(int i, int j, int k) {
if (CommonInputs.selected(i)) {
var sel = this.getSelected();
if (sel != null) {
sel.open();
return true;
}
}

return super.keyPressed(i, j, k);
}

@Override
public int getRowWidth() {
return (int) (this.width * 0.93D);
}
}

@Override
public boolean shouldCloseOnEsc() {
return false;
@Environment(EnvType.CLIENT)
public static class Entry extends ObjectSelectionList.Entry<Entry> {
private final ErrorList errorList;
private final Minecraft minecraft;
private final ConsoleLine line;
private long lastClickTime;
private final FormattedCharSequence indexText;
private final FormattedCharSequence scriptLineText;
private final FormattedCharSequence timestampText;
private final List<FormattedCharSequence> errorText;
private final List<FormattedCharSequence> stackTraceText;

public Entry(ErrorList errorList, Minecraft minecraft, int index, ConsoleLine line, Calendar calendar) {
this.errorList = errorList;
this.minecraft = minecraft;
this.line = line;

this.indexText = Component.literal("#" + (index + 1)).getVisualOrderText();

if (line.source.isEmpty()) {
if (line.externalFile != null) {
this.scriptLineText = Component.literal(line.externalFile.getFileName().toString()).getVisualOrderText();
} else {
this.scriptLineText = Component.literal("<unknown source>#" + line.line).getVisualOrderText();
}
} else {
this.scriptLineText = Component.literal((line.source + "#") + line.line).getVisualOrderText();
}

var sb = new StringBuilder();
calendar.setTimeInMillis(line.timestamp);
UtilsJS.appendTimestamp(sb, calendar);
this.timestampText = Component.literal(sb.toString()).getVisualOrderText();

this.errorText = new ArrayList<>(minecraft.font.split(Component.literal(line.message), errorList.getRowWidth()).stream().limit(3L).toList());
this.stackTraceText = line.stackTrace.isEmpty() ? List.of() : minecraft.font.split(Component.literal(String.join("\n", line.stackTrace)).setStyle(Style.EMPTY.withColor(ChatFormatting.GRAY)), Integer.MAX_VALUE);
}

@Override
public Component getNarration() {
return Component.empty();
}

@Override
public void render(GuiGraphics g, int idx, int y, int x, int w, int h, int mx, int my, boolean hovered, float delta) {
var col = line.type == LogType.ERROR ? 0xFF5B63 : 0xFFBB5B;

g.drawString(minecraft.font, indexText, x + 1, y + 1, col);
g.drawCenteredString(minecraft.font, scriptLineText, x + w / 2, y + 1, 0xFFFFFF);
g.drawString(minecraft.font, timestampText, x + w - minecraft.font.width(timestampText) - 4, y + 1, 0x666666);

for (int i = 0; i < errorText.size(); i++) {
g.drawString(minecraft.font, errorText.get(i), x + 1, y + 13 + i * 10, col);
}

if (hovered && !stackTraceText.isEmpty()) {
errorList.screen.setTooltipForNextRenderPass(Screen.hasShiftDown() ? stackTraceText : stackTraceText.stream().limit(4L).toList());
}
}

@Override
public boolean mouseClicked(double d, double e, int i) {
errorList.setSelected(this);

if (Util.getMillis() - this.lastClickTime < 250L) {
if (i == 1) {
minecraft.keyboardHandler.setClipboard(String.join("\n", line.stackTrace));
} else {
open();
}
return true;
} else {
this.lastClickTime = Util.getMillis();
return true;
}
}

public void open() {
var path = line.externalFile == null ? (line.source.isEmpty() ? null : line.console.scriptType.path.resolve(line.source)) : line.externalFile;

if (path != null && Files.exists(path)) {
try {
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE_FILE_DIR)) {
Desktop.getDesktop().browseFileDirectory(path.toFile());
} else {
throw new IllegalStateException("Error");
}
} catch (Exception ignored) {
if (Files.isRegularFile(path)) {
path = path.getParent();
}

errorList.screen.handleComponentClicked(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, path.toAbsolutePath().toString())));
}
}
}
}
}
Loading

0 comments on commit 0912c5b

Please sign in to comment.