Better mod crash logs
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package io.anuke.mindustry.game;
|
package io.anuke.mindustry.game;
|
||||||
|
|
||||||
|
import io.anuke.arc.files.*;
|
||||||
import io.anuke.arc.util.ArcAnnotate.*;
|
import io.anuke.arc.util.ArcAnnotate.*;
|
||||||
import io.anuke.mindustry.*;
|
import io.anuke.mindustry.*;
|
||||||
import io.anuke.mindustry.mod.Mods.*;
|
import io.anuke.mindustry.mod.Mods.*;
|
||||||
@@ -11,6 +12,8 @@ public abstract class Content implements Comparable<Content>{
|
|||||||
public final short id;
|
public final short id;
|
||||||
/** The mod that loaded this piece of content. */
|
/** The mod that loaded this piece of content. */
|
||||||
public @Nullable LoadedMod mod;
|
public @Nullable LoadedMod mod;
|
||||||
|
/** File that this content was loaded from. */
|
||||||
|
public @Nullable FileHandle sourceFile;
|
||||||
|
|
||||||
public Content(){
|
public Content(){
|
||||||
this.id = (short)Vars.content.getBy(getContentType()).size;
|
this.id = (short)Vars.content.getBy(getContentType()).size;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import io.anuke.arc.*;
|
|||||||
import io.anuke.arc.audio.*;
|
import io.anuke.arc.audio.*;
|
||||||
import io.anuke.arc.collection.Array;
|
import io.anuke.arc.collection.Array;
|
||||||
import io.anuke.arc.collection.*;
|
import io.anuke.arc.collection.*;
|
||||||
|
import io.anuke.arc.files.*;
|
||||||
import io.anuke.arc.function.*;
|
import io.anuke.arc.function.*;
|
||||||
import io.anuke.arc.graphics.*;
|
import io.anuke.arc.graphics.*;
|
||||||
import io.anuke.arc.util.ArcAnnotate.*;
|
import io.anuke.arc.util.ArcAnnotate.*;
|
||||||
@@ -229,9 +230,11 @@ public class ContentParser{
|
|||||||
|
|
||||||
/** Call to read a content's extra info later.*/
|
/** Call to read a content's extra info later.*/
|
||||||
private void read(Runnable run){
|
private void read(Runnable run){
|
||||||
|
Content cont = currentContent;
|
||||||
LoadedMod mod = currentMod;
|
LoadedMod mod = currentMod;
|
||||||
reads.add(() -> {
|
reads.add(() -> {
|
||||||
this.currentMod = mod;
|
this.currentMod = mod;
|
||||||
|
this.currentContent = cont;
|
||||||
run.run();
|
run.run();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -255,7 +258,7 @@ public class ContentParser{
|
|||||||
try{
|
try{
|
||||||
reads.each(Runnable::run);
|
reads.each(Runnable::run);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
throw new RuntimeException("Error occurred parsing content: " + currentContent, e);
|
Vars.mods.handleError(new ModLoadException("Error occurred parsing content: " + currentContent, currentContent, e), currentMod);
|
||||||
}
|
}
|
||||||
reads.clear();
|
reads.clear();
|
||||||
}
|
}
|
||||||
@@ -265,9 +268,10 @@ public class ContentParser{
|
|||||||
* @param name the name of the file without its extension
|
* @param name the name of the file without its extension
|
||||||
* @param json the json to parse
|
* @param json the json to parse
|
||||||
* @param type the type of content this is
|
* @param type the type of content this is
|
||||||
|
* @param file file that this content is being parsed from
|
||||||
* @return the content that was parsed
|
* @return the content that was parsed
|
||||||
*/
|
*/
|
||||||
public Content parse(LoadedMod mod, String name, String json, ContentType type) throws Exception{
|
public Content parse(LoadedMod mod, String name, String json, FileHandle file, ContentType type) throws Exception{
|
||||||
if(contentTypes.isEmpty()){
|
if(contentTypes.isEmpty()){
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@@ -279,6 +283,7 @@ public class ContentParser{
|
|||||||
|
|
||||||
currentMod = mod;
|
currentMod = mod;
|
||||||
Content c = parsers.get(type).parse(mod.name, name, value);
|
Content c = parsers.get(type).parse(mod.name, name, value);
|
||||||
|
c.sourceFile = file;
|
||||||
c.mod = mod;
|
c.mod = mod;
|
||||||
checkNulls(c);
|
checkNulls(c);
|
||||||
return c;
|
return c;
|
||||||
|
|||||||
@@ -268,25 +268,28 @@ public class Mods implements Loadable{
|
|||||||
/** Creates all the content found in mod files. */
|
/** Creates all the content found in mod files. */
|
||||||
public void loadContent(){
|
public void loadContent(){
|
||||||
for(LoadedMod mod : loaded){
|
for(LoadedMod mod : loaded){
|
||||||
if(mod.root.child("content").exists()){
|
safeRun(mod, () -> {
|
||||||
FileHandle contentRoot = mod.root.child("content");
|
if(mod.root.child("content").exists()){
|
||||||
for(ContentType type : ContentType.all){
|
FileHandle contentRoot = mod.root.child("content");
|
||||||
FileHandle folder = contentRoot.child(type.name().toLowerCase() + "s");
|
for(ContentType type : ContentType.all){
|
||||||
if(folder.exists()){
|
FileHandle folder = contentRoot.child(type.name().toLowerCase() + "s");
|
||||||
for(FileHandle file : folder.list()){
|
if(folder.exists()){
|
||||||
if(file.extension().equals("json")){
|
for(FileHandle file : folder.list()){
|
||||||
try{
|
if(file.extension().equals("json")){
|
||||||
//this binds the content but does not load it entirely
|
try{
|
||||||
Content loaded = parser.parse(mod, file.nameWithoutExtension(), file.readString("UTF-8"), type);
|
//this binds the content but does not load it entirely
|
||||||
Log.info("[{0}] Loaded '{1}'.", mod.meta.name, (loaded instanceof UnlockableContent ? ((UnlockableContent)loaded).localizedName : loaded));
|
Content loaded = parser.parse(mod, file.nameWithoutExtension(), file.readString("UTF-8"), file, type);
|
||||||
}catch(Exception e){
|
Log.info("[{0}] Loaded '{1}'.", mod.meta.name,
|
||||||
throw new RuntimeException("Failed to parse content file '" + file + "' for mod '" + mod.meta.name + "'.", e);
|
(loaded instanceof UnlockableContent ? ((UnlockableContent)loaded).localizedName : loaded));
|
||||||
|
}catch(Exception e){
|
||||||
|
throw new RuntimeException("Failed to parse content file '" + file + "' for mod '" + mod.meta.name + "'.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//this finishes parsing content fields
|
//this finishes parsing content fields
|
||||||
@@ -345,7 +348,40 @@ public class Mods implements Loadable{
|
|||||||
|
|
||||||
/** Iterates through each mod with a main class.*/
|
/** Iterates through each mod with a main class.*/
|
||||||
public void each(Consumer<Mod> cons){
|
public void each(Consumer<Mod> cons){
|
||||||
loaded.each(p -> p.mod != null, p -> cons.accept(p.mod));
|
loaded.each(p -> p.mod != null, p -> safeRun(p, () -> cons.accept(p.mod)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleError(Throwable t, LoadedMod mod){
|
||||||
|
Array<Throwable> causes = Strings.getCauses(t);
|
||||||
|
Content content = null;
|
||||||
|
for(Throwable e : causes){
|
||||||
|
if(e instanceof ModLoadException && ((ModLoadException) e).content != null){
|
||||||
|
content = ((ModLoadException) e).content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String realCause = "<???>";
|
||||||
|
for(int i = causes.size -1 ; i >= 0; i--){
|
||||||
|
if(causes.get(i).getMessage() != null){
|
||||||
|
realCause = causes.get(i).getMessage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(content != null){
|
||||||
|
throw new ModLoadException(Strings.format("Error loading '{0}' from mod '{1}' ({2}):\n{3}",
|
||||||
|
content, mod.meta.name, content.sourceFile.name(), realCause), content, t);
|
||||||
|
}else{
|
||||||
|
throw new ModLoadException("Error loading mod " + mod.meta.name, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void safeRun(LoadedMod mod, Runnable run){
|
||||||
|
try{
|
||||||
|
run.run();
|
||||||
|
}catch(Throwable t){
|
||||||
|
handleError(t, mod);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads a mod file+meta, but does not add it to the list.
|
/** Loads a mod file+meta, but does not add it to the list.
|
||||||
@@ -439,4 +475,22 @@ public class Mods implements Loadable{
|
|||||||
/** Hidden mods are only server-side or client-side, and do not support adding new content. */
|
/** Hidden mods are only server-side or client-side, and do not support adding new content. */
|
||||||
public boolean hidden;
|
public boolean hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Thrown when an error occurs while loading a mod.*/
|
||||||
|
public static class ModLoadException extends RuntimeException{
|
||||||
|
public Content content;
|
||||||
|
public LoadedMod mod;
|
||||||
|
|
||||||
|
public ModLoadException(String message, Throwable cause){
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModLoadException(String message, @Nullable Content content, Throwable cause){
|
||||||
|
super(message, cause);
|
||||||
|
this.content = content;
|
||||||
|
if(content != null){
|
||||||
|
this.mod = content.mod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class CrashSender{
|
|||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
|
|
||||||
//don't create crash logs for custom builds, as it's expected
|
//don't create crash logs for custom builds, as it's expected
|
||||||
if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))) return;
|
//if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))) return;
|
||||||
|
|
||||||
//attempt to load version regardless
|
//attempt to load version regardless
|
||||||
if(Version.number == 0){
|
if(Version.number == 0){
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class HostDialog extends FloatingDialog{
|
|||||||
player.isAdmin = true;
|
player.isAdmin = true;
|
||||||
|
|
||||||
if(steam){
|
if(steam){
|
||||||
Core.app.post(() -> Core.settings.getBoolOnce("steampublic", () -> {
|
Core.app.post(() -> Core.settings.getBoolOnce("steampublic2", () -> {
|
||||||
ui.showCustomConfirm("$setting.publichost.name", "$public.confirm", "$yes", "$no", () -> {
|
ui.showCustomConfirm("$setting.publichost.name", "$public.confirm", "$yes", "$no", () -> {
|
||||||
Core.settings.putSave("publichost", true);
|
Core.settings.putSave("publichost", true);
|
||||||
platform.updateLobby();
|
platform.updateLobby();
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import io.anuke.mindustry.desktop.steam.*;
|
|||||||
import io.anuke.mindustry.game.EventType.*;
|
import io.anuke.mindustry.game.EventType.*;
|
||||||
import io.anuke.mindustry.game.Version;
|
import io.anuke.mindustry.game.Version;
|
||||||
import io.anuke.mindustry.maps.Map;
|
import io.anuke.mindustry.maps.Map;
|
||||||
|
import io.anuke.mindustry.mod.Mods.*;
|
||||||
import io.anuke.mindustry.net.*;
|
import io.anuke.mindustry.net.*;
|
||||||
import io.anuke.mindustry.net.Net.*;
|
import io.anuke.mindustry.net.Net.*;
|
||||||
import io.anuke.mindustry.ui.*;
|
import io.anuke.mindustry.ui.*;
|
||||||
@@ -229,7 +230,10 @@ public class DesktopLauncher extends ClientLauncher{
|
|||||||
boolean fbgp = badGPU;
|
boolean fbgp = badGPU;
|
||||||
|
|
||||||
CrashSender.send(e, file -> {
|
CrashSender.send(e, file -> {
|
||||||
Throwable cause = Strings.getFinalCause(e);
|
Array<Throwable> causes = Strings.getCauses(e);
|
||||||
|
Throwable fc = causes.find(t -> t instanceof ModLoadException);
|
||||||
|
if(fc == null) fc = Strings.getFinalCause(e);
|
||||||
|
Throwable cause = fc;
|
||||||
if(!fbgp){
|
if(!fbgp){
|
||||||
dialog.accept(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + cause.getClass().getSimpleName().replace("Exception", "") + (cause.getMessage() == null ? "" : ":\n" + cause.getMessage())));
|
dialog.accept(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + cause.getClass().getSimpleName().replace("Exception", "") + (cause.getMessage() == null ? "" : ":\n" + cause.getMessage())));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
org.gradle.jvmargs=-Xms256m -Xmx1024m
|
org.gradle.jvmargs=-Xms256m -Xmx1024m
|
||||||
archash=0db0c0d660952fb57cda0f83d78daa9acbcd5439
|
archash=9e37a345d59b09e8677adbec29cb3981d26fe86b
|
||||||
|
|||||||
Reference in New Issue
Block a user