ErrorHandler

This commit is contained in:
yugecin 2017-01-11 20:41:13 +01:00
parent 90684c084a
commit 68ac7f3d10
7 changed files with 379 additions and 19 deletions

View File

@ -590,6 +590,8 @@ public class Utils {
if (isJarRunning()) if (isJarRunning())
return null; return null;
File f = new File(".git/refs/remotes/origin/master"); File f = new File(".git/refs/remotes/origin/master");
if (!f.isFile())
f = new File("../.git/refs/remotes/origin/master");
if (!f.isFile()) if (!f.isFile())
return null; return null;
try (BufferedReader in = new BufferedReader(new FileReader(f))) { try (BufferedReader in = new BufferedReader(new FileReader(f))) {

View File

@ -20,6 +20,7 @@ package yugecin.opsudance;
import com.google.inject.Inject; import com.google.inject.Inject;
import org.lwjgl.LWJGLException; import org.lwjgl.LWJGLException;
import yugecin.opsudance.core.DisplayContainer; import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.errorhandling.ErrorHandler;
import static yugecin.opsudance.kernel.Entrypoint.log; import static yugecin.opsudance.kernel.Entrypoint.log;
@ -34,11 +35,24 @@ public class OpsuDance {
public void start() { public void start() {
log("initialized"); log("initialized");
container.init();
while (rungame());
}
private boolean rungame() {
try {
container.setup();
} catch (LWJGLException e) {
ErrorHandler.error("could not initialize GL", e, container).showAndExit();
}
Exception caughtException = null;
try { try {
container.run(); container.run();
} catch (LWJGLException e) { } catch (Exception e) {
e.printStackTrace(); caughtException = e;
} }
container.teardown();
return caughtException != null && ErrorHandler.error("update/render error", caughtException, container).show().shouldIgnoreAndContinue();
} }
} }

View File

@ -23,6 +23,7 @@ import org.lwjgl.Sys;
import org.lwjgl.openal.AL; import org.lwjgl.openal.AL;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.Graphics; import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input; import org.newdawn.slick.Input;
import org.newdawn.slick.opengl.InternalTextureLoader; import org.newdawn.slick.opengl.InternalTextureLoader;
@ -30,8 +31,10 @@ import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL; import org.newdawn.slick.opengl.renderer.SGL;
import org.newdawn.slick.util.Log; import org.newdawn.slick.util.Log;
import yugecin.opsudance.core.state.OpsuState; import yugecin.opsudance.core.state.OpsuState;
import yugecin.opsudance.errorhandling.ErrorDumpable;
import yugecin.opsudance.utils.GLHelper; import yugecin.opsudance.utils.GLHelper;
import java.io.StringWriter;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -40,7 +43,7 @@ import static yugecin.opsudance.kernel.Entrypoint.log;
/** /**
* based on org.newdawn.slick.AppGameContainer * based on org.newdawn.slick.AppGameContainer
*/ */
public class DisplayContainer { public class DisplayContainer implements ErrorDumpable {
private static SGL GL = Renderer.get(); private static SGL GL = Renderer.get();
@ -64,6 +67,9 @@ public class DisplayContainer {
private long lastFrame; private long lastFrame;
private String glVersion;
private String glVendor;
@Inject @Inject
public DisplayContainer(Demux demux) { public DisplayContainer(Demux demux) {
this.demux = demux; this.demux = demux;
@ -86,10 +92,11 @@ public class DisplayContainer {
demux.switchState(newState); demux.switchState(newState);
} }
public void run() throws LWJGLException { public void init() {
demux.init(); demux.init();
setup(); }
log("GL ready");
public void run() throws LWJGLException {
while(!(Display.isCloseRequested() && demux.onCloseRequest())) { while(!(Display.isCloseRequested() && demux.onCloseRequest())) {
delta = getDelta(); delta = getDelta();
@ -130,23 +137,21 @@ public class DisplayContainer {
teardown(); teardown();
} }
private void setup() { public void setup() throws LWJGLException {
width = height = -1;
Input.disableControllers(); Input.disableControllers();
Display.setTitle("opsu!dance"); Display.setTitle("opsu!dance");
try { // temp displaymode to not flash the screen with a 1ms black window
// temp displaymode to not flash the screen with a 1ms black window Display.setDisplayMode(new DisplayMode(100, 100));
Display.setDisplayMode(new DisplayMode(100, 100)); Display.create();
Display.create(); GLHelper.setIcons(new String[] { "icon16.png", "icon32.png" });
GLHelper.setIcons(new String[] { "icon16.png", "icon32.png" }); setDisplayMode(640, 480, false);
setDisplayMode(640, 480, false); log("GL ready");
} catch (LWJGLException e) { glVersion = GL11.glGetString(GL11.GL_VERSION);
e.printStackTrace(); glVendor = GL11.glGetString(GL11.GL_VENDOR);
// TODO errorhandler dialog here
Log.error("could not initialize GL", e);
}
} }
private void teardown() { public void teardown() {
Display.destroy(); Display.destroy();
AL.destroy(); AL.destroy();
} }
@ -210,4 +215,10 @@ public class DisplayContainer {
return (Sys.getTime() * 1000) / Sys.getTimerResolution(); return (Sys.getTime() * 1000) / Sys.getTimerResolution();
} }
@Override
public void writeErrorDump(StringWriter dump) {
dump.append("> DisplayContainer dump").append('\n');
dump.append("OpenGL version: ").append(glVersion).append( "(").append(glVendor).append(")").append('\n');
}
} }

View File

@ -0,0 +1,26 @@
/*
* opsu!dance - fork of opsu! with cursordance auto
* Copyright (C) 2017 yugecin
*
* opsu!dance is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu!dance is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!dance. If not, see <http://www.gnu.org/licenses/>.
*/
package yugecin.opsudance.errorhandling;
import java.io.StringWriter;
public interface ErrorDumpable {
void writeErrorDump(StringWriter dump);
}

View File

@ -0,0 +1,224 @@
/*
* opsu!dance - fork of opsu! with cursordance auto
* Copyright (C) 2017 yugecin
*
* opsu!dance is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu!dance is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!dance. If not, see <http://www.gnu.org/licenses/>.
*/
package yugecin.opsudance.errorhandling;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import org.newdawn.slick.util.Log;
import yugecin.opsudance.utils.MiscUtils;
import javax.swing.*;
import java.awt.Desktop;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
/**
* based on itdelatrisu.opsu.ErrorHandler
*/
public class ErrorHandler {
private final String customMessage;
private final Throwable cause;
private final String errorDump;
private final String messageBody;
private boolean preventContinue;
private boolean preventReport;
private boolean ignoreAndContinue;
private ErrorHandler(String customMessage, Throwable cause, ErrorDumpable[] errorInfoProviders) {
this.customMessage = customMessage;
this.cause = cause;
StringWriter dump = new StringWriter();
for (ErrorDumpable infoProvider : errorInfoProviders) {
try {
infoProvider.writeErrorDump(dump);
} catch (Exception e) {
dump
.append("### ")
.append(e.getClass().getSimpleName())
.append(" while creating errordump for ")
.append(infoProvider.getClass().getSimpleName());
e.printStackTrace(new PrintWriter(dump));
}
}
errorDump = dump.toString();
dump = new StringWriter();
dump.append(customMessage).append("\n");
dump.append("unhandled ").append(cause.getClass().getSimpleName()).append("\n\n");
cause.printStackTrace(new PrintWriter(dump));
dump.append("\n\n").append(errorDump);
messageBody = dump.toString();
Log.error(messageBody);
}
public static ErrorHandler error(String message, Throwable cause, ErrorDumpable... errorInfoProviders) {
return new ErrorHandler(message, cause, errorInfoProviders);
}
public ErrorHandler preventReport() {
preventReport = true;
return this;
}
public ErrorHandler preventContinue() {
preventContinue = true;
return this;
}
public ErrorHandler show() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
Log.warn("Unable to set look and feel for error dialog");
}
String title = "opsu!dance error - " + customMessage;
String messageText = "opsu!dance has encountered an error.";
if (!preventReport) {
messageText += " Please report this!";
}
JLabel message = new JLabel(messageText);
JTextArea textArea = new JTextArea(15, 100);
textArea.setEditable(false);
textArea.setBackground(UIManager.getColor("Panel.background"));
textArea.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
textArea.setTabSize(2);
textArea.setLineWrap(false);
textArea.setWrapStyleWord(true);
textArea.setText(messageBody);
Object[] messageComponents = new Object[] { message, new JScrollPane(textArea), createViewLogButton(), createReportButton() };
String[] buttons;
if (preventContinue) {
buttons = new String[] { "Terminate" };
} else {
buttons = new String[] { "Terminate", "Ignore & continue" };
}
JFrame frame = new JFrame(title);
frame.setUndecorated(true);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
int result = JOptionPane.showOptionDialog(frame,
messageComponents,
title,
JOptionPane.DEFAULT_OPTION,
JOptionPane.ERROR_MESSAGE,
null,
buttons,
buttons[buttons.length - 1]);
ignoreAndContinue = result == 1;
frame.dispose();
return this;
}
private JComponent createViewLogButton() {
return createButton("View log", Desktop.Action.OPEN, new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
try {
Desktop.getDesktop().open(Options.LOG_FILE);
} catch (IOException e) {
Log.warn("Could not open log file", e);
JOptionPane.showMessageDialog(null, "whoops could not open log file", "errorception", JOptionPane.ERROR_MESSAGE);
}
}
});
}
private JComponent createReportButton() {
if (preventReport) {
return new JLabel();
}
return createButton("Report error", Desktop.Action.BROWSE, new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
try {
Desktop.getDesktop().browse(createGithubIssueUrl());
} catch (IOException e) {
Log.warn("Could not open browser to report issue", e);
JOptionPane.showMessageDialog(null, "whoops could not launch a browser", "errorception", JOptionPane.ERROR_MESSAGE);
}
}
});
}
private JButton createButton(String buttonText, Desktop.Action action, ActionListener listener) {
JButton button = new JButton(buttonText);
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(action)) {
button.addActionListener(listener);
return button;
}
button.setEnabled(false);
return button;
}
private URI createGithubIssueUrl() {
StringWriter dump = new StringWriter();
dump.append("**opsu!dance version:** ").append(MiscUtils.buildProperties.get().getProperty("version")).append('\n');
String gitHash = Utils.getGitHash();
if (gitHash != null) {
dump.append("**git hash:** ").append(gitHash.substring(0, 12)).append('\n');
}
dump.append("**os:** ").append(System.getProperty("os.name"))
.append(" (").append(System.getProperty("os.arch")).append(")\n");
dump.append("**jre:** ").append(System.getProperty("java.version")).append('\n');
dump.append("**info dump:**").append('\n');
dump.append("```\n").append(errorDump).append("```").append("\n\n");
dump.append("**trace:**").append("\n```\n");
cause.printStackTrace(new PrintWriter(dump));
dump.append("```");
String issueTitle = "";
String issueBody = "";
try {
issueTitle = URLEncoder.encode("*** Unhandled " + cause.getClass().getSimpleName() + " " + customMessage, "UTF-8");
issueBody = URLEncoder.encode(dump.toString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.warn("URLEncoder failed to encode the auto-filled issue report URL.", e);
}
return URI.create(String.format(Options.ISSUES_URL, issueTitle, issueBody));
}
public boolean shouldIgnoreAndContinue() {
return ignoreAndContinue;
}
public void showAndExit() {
show();
System.exit(1);
}
}

View File

@ -0,0 +1,41 @@
/*
* opsu!dance - fork of opsu! with cursordance auto
* Copyright (C) 2017 yugecin
*
* opsu!dance is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu!dance is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!dance. If not, see <http://www.gnu.org/licenses/>.
*/
package yugecin.opsudance.utils;
public class CachedVariable<T> {
private T value;
private Getter<T> getter;
public CachedVariable(Getter<T> getter) {
this.getter = getter;
}
public T get() {
if (getter != null) {
value = getter.get();
getter = null;
}
return value;
}
public interface Getter<T> {
T get();
}
}

View File

@ -0,0 +1,42 @@
/*
* opsu!dance - fork of opsu! with cursordance auto
* Copyright (C) 2017 yugecin
*
* opsu!dance is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu!dance is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!dance. If not, see <http://www.gnu.org/licenses/>.
*/
package yugecin.opsudance.utils;
import itdelatrisu.opsu.Options;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import java.io.IOException;
import java.util.Properties;
public class MiscUtils {
public static final CachedVariable<Properties> buildProperties = new CachedVariable<>(new CachedVariable.Getter<Properties>() {
@Override
public Properties get() {
Properties props = new Properties();
try {
props.load(ResourceLoader.getResourceAsStream(Options.VERSION_FILE));
} catch (IOException e) {
Log.error("Could not read version file", e);
}
return props;
}
});
}