Fix GL error logging

This commit is contained in:
James Seibel
2025-03-14 10:17:52 -05:00
parent 7b5b8da0d2
commit 0cf5e6d594
9 changed files with 639 additions and 487 deletions
@@ -25,8 +25,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.GLMessage;
import com.seibel.distanthorizons.core.util.objects.GLMessageOutputStream;
import com.seibel.distanthorizons.core.util.objects.GLMessages.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
@@ -38,28 +37,17 @@ import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.GLUtil;
import java.io.PrintStream;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* A singleton that holds references to different openGL contexts
* and GPU capabilities.
*
* <p>
* Helpful OpenGL resources:
* <p>
* https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf <br>
* https://learnopengl.com/Advanced-OpenGL/Advanced-Data <br>
* https://www.slideshare.net/CassEveritt/approaching-zero-driver-overhead <br><br>
*
* https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one <br>
* https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br>
*/
public class GLProxy
{
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final ConfigBasedLogger GL_LOGGER = new ConfigBasedLogger(LogManager.getLogger(GLProxy.class),
() -> Config.Common.Logging.logRendererGLEvent.get());
@@ -79,7 +67,29 @@ public class GLProxy
private final EDhApiGpuUploadMethod preferredUploadMethod;
public final GLMessage.Builder vanillaDebugMessageBuilder = GLMessage.Builder.DEFAULT_MESSAGE_BUILDER;
public final GLMessageBuilder vanillaDebugMessageBuilder =
new GLMessageBuilder(
(type) ->
{
if (type == EGLMessageType.POP_GROUP)
return false;
else if (type == EGLMessageType.PUSH_GROUP)
return false;
else if (type == EGLMessageType.MARKER)
return false;
else
return true;
},
(severity) ->
{
// notifications can generally be ignored (if they are logged at all)
if (severity == EGLMessageSeverity.NOTIFICATION)
return false;
else
return true;
},
null
);
@@ -268,11 +278,11 @@ public class GLProxy
if (msg.type == GLMessage.EType.ERROR || msg.type == GLMessage.EType.UNDEFINED_BEHAVIOR)
if (msg.type == EGLMessageType.ERROR || msg.type == EGLMessageType.UNDEFINED_BEHAVIOR)
{
// critical error
GL_LOGGER.error("GL ERROR " + msg.id + " from " + msg.source + ": " + msg.message);
GL_LOGGER.error("GL ERROR [" + msg.id + "] from [" + msg.source + "]: [" + msg.message + "].");
if (errorHandlingMode == EDhApiGLErrorHandlingMode.LOG_THROW)
{
@@ -284,28 +294,28 @@ public class GLProxy
{
// non-critical log
GLMessage.ESeverity severity = msg.severity;
RuntimeException ex = new RuntimeException("GL MESSAGE: " + msg);
EGLMessageSeverity severity = msg.severity;
RuntimeException exception = new RuntimeException("GL MESSAGE: " + msg);
if (severity == null)
{
// just in case the message was malformed
severity = GLMessage.ESeverity.LOW;
severity = EGLMessageSeverity.LOW;
}
switch (severity)
{
case HIGH:
GL_LOGGER.error("{}", ex);
GL_LOGGER.error(exception.getMessage(), exception);
break;
case MEDIUM:
GL_LOGGER.warn("{}", ex);
GL_LOGGER.warn(exception.getMessage(), exception);
break;
case LOW:
GL_LOGGER.info("{}", ex);
GL_LOGGER.info(exception.getMessage(), exception);
break;
case NOTIFICATION:
GL_LOGGER.debug("{}", ex);
GL_LOGGER.debug(exception.getMessage(), exception);
break;
}
}
@@ -1,430 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.function.Function;
/**
* Handles parsing and creating string messages from OpenGL messages.
*
* @author Leetom
* @version 2022-10-1
*/
public final class GLMessage
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
static final String HEADER = "[LWJGL] OpenGL debug message";
public final EType type;
public final ESeverity severity;
public final ESource source;
public final String id;
public final String message;
/** This is needed since gl callback will not have the correct class loader set, which causes issues. */
static void initLoadClass()
{
Builder dummy = new Builder();
dummy.add(GLMessage.HEADER);
dummy.add("ID");
dummy.add(":");
dummy.add("dummyId");
dummy.add("Source");
dummy.add(":");
dummy.add(ESource.API.name);
dummy.add("Type");
dummy.add(":");
dummy.add(EType.OTHER.name);
dummy.add("Severity");
dummy.add(":");
dummy.add(ESeverity.LOW.name);
dummy.add("Message");
dummy.add(":");
dummy.add("dummyMessage");
}
static
{
initLoadClass();
}
GLMessage(EType type, ESeverity severity, ESource source, String id, String message)
{
this.type = type;
this.source = source;
this.severity = severity;
this.id = id;
this.message = message;
}
@Override
public String toString() { return "[level:" + severity + ", type:" + type + ", source:" + source + ", id:" + id + ", msg:{" + message + "}]"; }
//==============//
// helper enums //
//==============//
public enum EType
{
ERROR,
DEPRECATED_BEHAVIOR,
UNDEFINED_BEHAVIOR,
PORTABILITY,
PERFORMANCE,
MARKER,
PUSH_GROUP,
POP_GROUP,
OTHER;
private static final HashMap<String, EType> ENUM_BY_NAME = new HashMap<>();
private final String name;
static
{
for (EType type : EType.values())
{
ENUM_BY_NAME.put(type.name, type);
}
}
EType() { name = super.toString().toUpperCase(); }
@Override
public final String toString() { return name; }
public static EType get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
public enum ESource
{
API,
WINDOW_SYSTEM,
SHADER_COMPILER,
THIRD_PARTY,
APPLICATION,
OTHER;
private static final HashMap<String, ESource> ENUM_BY_NAME = new HashMap<>();
public final String name;
static
{
for (ESource source : ESource.values())
{
ENUM_BY_NAME.put(source.name, source);
}
}
ESource() { name = super.toString().toUpperCase(); }
@Override
public final String toString() { return name; }
public static ESource get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
public enum ESeverity
{
HIGH,
MEDIUM,
LOW,
NOTIFICATION;
public final String name;
static final HashMap<String, ESeverity> ENUM_BY_NAME = new HashMap<>();
static
{
for (ESeverity severity : ESeverity.values())
{
ENUM_BY_NAME.put(severity.name, severity);
}
}
ESeverity() { name = super.toString().toUpperCase(); }
@Override
public final String toString() { return name; }
public static ESeverity get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
//================//
// helper classes //
//================//
/**
* Expected message format: <br>
* <code>
* [LWJGL] OpenGL debug message <br>
* ID: 0x20071 <br>
* Source: API <br>
* Type: OTHER <br>
* Severity: NOTIFICATION <br>
* Message: Buffer detailed info: Buffer object 1014084 (bound to ...
* </code>
*/
public static class Builder
{
/** how many stages are present in the message parser */
private static final int FINAL_PARSER_STAGE_INDEX = 15;
public static final Builder DEFAULT_MESSAGE_BUILDER =
new Builder(
(type) ->
{ // type filter
if (type == GLMessage.EType.POP_GROUP)
return false;
if (type == GLMessage.EType.PUSH_GROUP)
return false;
if (type == GLMessage.EType.MARKER)
return false;
// if (type == GLMessage.Type.PERFORMANCE) return false;
return true;
},
(severity) ->
{ // severity filter
if (severity == GLMessage.ESeverity.NOTIFICATION)
return false;
return true;
},
null
);
private final StringBuilder inProgressMessageBuilder = new StringBuilder();
private EType type;
private ESeverity severity;
private ESource source;
/** if the function returns false the message will be allowed */
private final Function<EType, Boolean> typeFilter;
/** if the function returns false the message will be allowed */
private final Function<ESeverity, Boolean> severityFilter;
/** if the function returns false the message will be allowed */
private final Function<ESource, Boolean> sourceFilter;
private String id;
private String message;
/** how far into the message parser this builder is */
private int parserStage = 0;
static
{
initLoadClass();
}
public Builder() { this(null, null, null); }
public Builder(
Function<EType, Boolean> typeFilter,
Function<ESeverity, Boolean> severityFilter,
Function<ESource, Boolean> sourceFilter)
{
this.typeFilter = typeFilter;
this.severityFilter = severityFilter;
this.sourceFilter = sourceFilter;
}
/**
* Adds the given string to the message builder. <br> <br>
*
* Will log a warning if the string given wasn't expected
* for the next stage of the OpenGL message format.<br> <br>
*
* @return null if the message isn't complete
*/
public GLMessage add(String str)
{
// TODO fix implementation for MC 1.20.2 and newer
// please see the incomplete GLMessageTest for an example as to how the message formats differ
if (true)
return null;
str = str.trim();
if (str.isEmpty())
return null;
boolean parseSuccess = runNextParserStage(str);
if (parseSuccess && parserStage > FINAL_PARSER_STAGE_INDEX)
{
this.parserStage = 0;
GLMessage msg = new GLMessage(this.type, this.severity, this.source, this.id, this.message);
if (doesMessagePassFilters(msg))
{
return msg;
}
}
else if (!parseSuccess)
{
LOGGER.warn("Failed to parse GLMessage line '{}' at stage {}", str, parserStage);
}
// the message isn't finished yet
return null;
// TODO implement a method that works for both MC 1.20.2+ and 1.20.1-
//if (str.equals(HEADER) && inProgressMessageBuilder.length() != 0)
//{
// boolean parseSuccess = runNextParserStage(str);
// if (parseSuccess && parserStage > FINAL_PARSER_STAGE_INDEX)
// {
// this.parserStage = 0;
// GLMessage msg = new GLMessage(this.type, this.severity, this.source, this.id, this.message);
// if (doesMessagePassFilters(msg))
// {
// return msg;
// }
// else
// {
// inProgressMessageBuilder.setLength(0);
// return null;
// }
// }
// else
// {
// if (!parseSuccess)
// {
// LOGGER.warn("Failed to parse GLMessage line '{}' at stage {}", str, parserStage);
// inProgressMessageBuilder.setLength(0);
// }
//
// return null;
// }
//}
//else
//{
// inProgressMessageBuilder.append(str);
// return null;
//}
}
private boolean doesMessagePassFilters(GLMessage msg)
{
if (this.sourceFilter != null && !this.sourceFilter.apply(msg.source))
return false;
else if (this.typeFilter != null && !this.typeFilter.apply(msg.type))
return false;
else if (this.severityFilter != null && !this.severityFilter.apply(msg.severity))
return false;
else
return true;
}
/** @return true if the given string was expected next for the OpenGL message format */
private boolean runNextParserStage(String str)
{
switch (this.parserStage)
{
case 0:
return checkAndIncStage(str, GLMessage.HEADER);
case 1:
return checkAndIncStage(str, "ID");
case 2:
return checkAndIncStage(str, ":");
case 3:
this.id = str;
this.parserStage++;
return true;
case 4:
return checkAndIncStage(str, "Source");
case 5:
return checkAndIncStage(str, ":");
case 6:
this.source = ESource.get(str);
this.parserStage++;
return true;
case 7:
return checkAndIncStage(str, "Type");
case 8:
return checkAndIncStage(str, ":");
case 9:
this.type = EType.get(str);
this.parserStage++;
return true;
case 10:
return checkAndIncStage(str, "Severity");
case 11:
return checkAndIncStage(str, ":");
case 12:
this.severity = ESeverity.get(str);
this.parserStage++;
return true;
case 13:
return checkAndIncStage(str, "Message");
case 14:
return checkAndIncStage(str, ":");
case 15:
this.message = str;
this.parserStage++;
return true;
default:
return false;
}
}
/**
* Returns true and increments the parserStage
* if the given and expected strings are the same.
*/
private boolean checkAndIncStage(String givenString, String expectedString)
{
boolean equal = givenString.equals(expectedString);
//boolean equal = givenString.contains(expectedString);
if (equal)
this.parserStage++;
return equal;
}
} // builder class
}
@@ -0,0 +1,54 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
import java.util.HashMap;
public enum EGLMessageSeverity
{
HIGH,
MEDIUM,
LOW,
NOTIFICATION;
public final String name;
static final HashMap<String, EGLMessageSeverity> ENUM_BY_NAME = new HashMap<>();
static
{
for (EGLMessageSeverity severity : EGLMessageSeverity.values())
{
ENUM_BY_NAME.put(severity.name, severity);
}
}
EGLMessageSeverity() { this.name = super.toString().toUpperCase(); }
@Override
public final String toString() { return this.name; }
public static EGLMessageSeverity get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
@@ -0,0 +1,55 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
import java.util.HashMap;
public enum EGLMessageSource
{
API,
WINDOW_SYSTEM,
SHADER_COMPILER,
THIRD_PARTY,
APPLICATION,
OTHER;
private static final HashMap<String, EGLMessageSource> ENUM_BY_NAME = new HashMap<>();
public final String name;
static
{
for (EGLMessageSource source : EGLMessageSource.values())
{
ENUM_BY_NAME.put(source.name, source);
}
}
EGLMessageSource() { this.name = super.toString().toUpperCase(); }
@Override
public final String toString() { return this.name; }
public static EGLMessageSource get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
@@ -0,0 +1,58 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
import java.util.HashMap;
public enum EGLMessageType
{
ERROR,
DEPRECATED_BEHAVIOR,
UNDEFINED_BEHAVIOR,
PORTABILITY,
PERFORMANCE,
MARKER,
PUSH_GROUP,
POP_GROUP,
OTHER;
private static final HashMap<String, EGLMessageType> ENUM_BY_NAME = new HashMap<>();
public final String name;
static
{
for (EGLMessageType type : EGLMessageType.values())
{
ENUM_BY_NAME.put(type.name, type);
}
}
EGLMessageType() { this.name = super.toString().toUpperCase(); }
@Override
public final String toString() { return this.name; }
public static EGLMessageType get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
@@ -0,0 +1,56 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
public final class GLMessage
{
static final String HEADER = "[LWJGL] OpenGL debug message";
public final EGLMessageType type;
public final EGLMessageSeverity severity;
public final EGLMessageSource source;
public final String id;
public final String message;
GLMessage(EGLMessageType type, EGLMessageSeverity severity, EGLMessageSource source, String id, String message)
{
this.type = type;
this.source = source;
this.severity = severity;
this.id = id;
this.message = message;
}
@Override
public String toString()
{
return "level: [" + this.severity + "], " +
"type: [" + this.type + "], " +
"source: [" + this.source + "], " +
"id: [" + this.id + "], " +
"msg: [" + this.message + "]";
}
}
@@ -0,0 +1,319 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger;
import java.util.function.Consumer;
import java.util.function.Function;
/** Expected message formats can be found in GLMessageTest. */
public class GLMessageBuilder
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** how many stages are present in the message parser */
private static final int FINAL_LEGACY_PARSER_STAGE_INDEX = 15;
private static final int FINAL_NEW_PARSER_STAGE_INDEX = 5;
private EGLMessageType type;
private EGLMessageSeverity severity;
private EGLMessageSource source;
/** if the function returns false the message will be allowed */
private final Function<EGLMessageType, Boolean> typeFilter;
/** if the function returns false the message will be allowed */
private final Function<EGLMessageSeverity, Boolean> severityFilter;
/** if the function returns false the message will be allowed */
private final Function<EGLMessageSource, Boolean> sourceFilter;
private String id;
private String message;
/** how far into the message parser this builder is */
private int parserStage = 0;
private boolean legacyMessage = true;
//==============//
// constructors //
//==============//
public GLMessageBuilder() { this(null, null, null); }
public GLMessageBuilder(
Function<EGLMessageType, Boolean> typeFilter,
Function<EGLMessageSeverity, Boolean> severityFilter,
Function<EGLMessageSource, Boolean> sourceFilter)
{
this.typeFilter = typeFilter;
this.severityFilter = severityFilter;
this.sourceFilter = sourceFilter;
}
//=================//
// message parsing //
//=================//
/**
* Adds the given string to the message builder. <br> <br>
*
* Will log a warning if the string given wasn't expected
* for the next stage of the OpenGL message format.<br> <br>
*
* @return null if the message isn't complete
*/
public GLMessage add(String str)
{
str = str.trim();
if (str.isEmpty())
{
return null;
}
boolean messageFinished = false;
boolean parseSuccess = this.runNextParserStage(str);
if (this.legacyMessage
&& this.parserStage > FINAL_LEGACY_PARSER_STAGE_INDEX)
{
messageFinished = true;
}
else if (!this.legacyMessage
&& this.parserStage > FINAL_NEW_PARSER_STAGE_INDEX)
{
messageFinished = true;
}
if (parseSuccess && messageFinished)
{
this.parserStage = 0;
GLMessage msg = new GLMessage(this.type, this.severity, this.source, this.id, this.message);
if (this.doesMessagePassFilters(msg))
{
return msg;
}
}
else if (!parseSuccess && messageFinished)
{
LOGGER.warn("Failed to parse GLMessage line [" + str + "] at stage [" + this.parserStage + "]");
}
// the message isn't finished yet
return null;
}
private boolean doesMessagePassFilters(GLMessage msg)
{
if (this.sourceFilter != null && !this.sourceFilter.apply(msg.source))
return false;
else if (this.typeFilter != null && !this.typeFilter.apply(msg.type))
return false;
else if (this.severityFilter != null && !this.severityFilter.apply(msg.severity))
return false;
else
return true;
}
/** @return true if the given string was expected next for the OpenGL message format */
private boolean runNextParserStage(String str)
{
if (this.parserStage == 0)
{
return this.checkExactAndIncStage(str, GLMessage.HEADER);
}
else if (this.parserStage == 1)
{
// legacy message only contains "ID" (not the colon)
this.legacyMessage = !str.contains("ID: ");
}
if (this.legacyMessage)
{
return this.runNextLegacyParserStage(str);
}
else
{
return this.runNextNewParserStage(str);
}
}
/** MC 1.20.2 and older */
private boolean runNextLegacyParserStage(String str)
{
switch (this.parserStage)
{
case 0:
throw new IllegalStateException("Parser should be past stage ["+this.parserStage+"], next stage is [1].");
case 1:
return this.checkExactAndIncStage(str, "ID");
case 2:
return this.checkExactAndIncStage(str, ":");
case 3:
this.id = str;
this.parserStage++;
return true;
case 4:
return this.checkExactAndIncStage(str, "Source");
case 5:
return this.checkExactAndIncStage(str, ":");
case 6:
this.source = EGLMessageSource.get(str);
this.parserStage++;
return true;
case 7:
return this.checkExactAndIncStage(str, "Type");
case 8:
return this.checkExactAndIncStage(str, ":");
case 9:
this.type = EGLMessageType.get(str);
this.parserStage++;
return true;
case 10:
return this.checkExactAndIncStage(str, "Severity");
case 11:
return this.checkExactAndIncStage(str, ":");
case 12:
this.severity = EGLMessageSeverity.get(str);
this.parserStage++;
return true;
case 13:
return this.checkExactAndIncStage(str, "Message");
case 14:
return this.checkExactAndIncStage(str, ":");
case 15:
this.message = str;
this.parserStage++;
return true;
default:
return false;
}
}
/** after MC 1.20.2 */
private boolean runNextNewParserStage(String str)
{
switch (this.parserStage)
{
case 0:
throw new IllegalStateException("Parser should be past stage [" + this.parserStage + "], next stage is [1].");
case 1:
String idPrefix = "ID: ";
return this.checkPrefixAndRun(str, idPrefix,
(line) ->
{
this.id = trySubstring(str, idPrefix.length());
this.parserStage++;
});
case 2:
String sourcePrefix = "Source: ";
return this.checkPrefixAndRun(str, sourcePrefix,
(line) ->
{
String sourceString = trySubstring(str, sourcePrefix.length());
this.source = EGLMessageSource.get(sourceString);
this.parserStage++;
});
case 3:
String typePrefix = "Type: ";
return this.checkPrefixAndRun(str, typePrefix,
(line) ->
{
String sourceString = trySubstring(str, typePrefix.length());
this.type = EGLMessageType.get(sourceString);
this.parserStage++;
});
case 4:
String severityPrefix = "Severity: ";
return this.checkPrefixAndRun(str, severityPrefix,
(line) ->
{
String sourceString = trySubstring(str, severityPrefix.length());
this.severity = EGLMessageSeverity.get(sourceString);
this.parserStage++;
});
case 5:
String messagePrefix = "Message: ";
return this.checkPrefixAndRun(str, messagePrefix,
(line) ->
{
this.message = trySubstring(str, messagePrefix.length());
this.parserStage++;
});
default:
return false;
}
}
//================//
// helper methods //
//================//
/**
* Returns true and increments the parserStage
* if the message and expected strings are the same.
*/
private boolean checkExactAndIncStage(String message, String expectedString)
{
boolean equal = message.equals(expectedString);
if (equal)
{
this.parserStage++;
}
return equal;
}
/**
* Returns true and increments the parserStage
* if the message starts with the given prefix.
*/
private boolean checkPrefixAndRun(String message, String expectedPrefix, Consumer<String> successConsumer)
{
boolean equal = message.startsWith(expectedPrefix);
if (equal)
{
successConsumer.accept(message);
}
return equal;
}
/** returns "" if the string isn't long enough */
private static String trySubstring(String string, int beginIndex)
{
if (beginIndex > string.length())
{
// prevent index-out-of-bounds errors
// if the message isn't what we expected
return "";
}
return string.substring(beginIndex);
}
}
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects;
package com.seibel.distanthorizons.core.util.objects.GLMessages;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -27,12 +27,12 @@ import java.util.function.Consumer;
public final class GLMessageOutputStream extends OutputStream
{
final Consumer<GLMessage> func;
final GLMessage.Builder builder;
final GLMessageBuilder builder;
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
public GLMessageOutputStream(Consumer<GLMessage> func, GLMessage.Builder builder)
public GLMessageOutputStream(Consumer<GLMessage> func, GLMessageBuilder builder)
{
this.func = func;
this.builder = builder;
@@ -41,24 +41,30 @@ public final class GLMessageOutputStream extends OutputStream
@Override
public void write(int b)
{
buffer.write(b);
if (b == '\n') flush();
this.buffer.write(b);
if (b == '\n')
{
this.flush();
}
}
@Override
public void flush()
{
String str = buffer.toString();
GLMessage msg = builder.add(str);
if (msg != null) func.accept(msg);
buffer.reset();
String str = this.buffer.toString();
GLMessage msg = this.builder.add(str);
if (msg != null)
{
this.func.accept(msg);
}
this.buffer.reset();
}
@Override
public void close() throws IOException
{
flush();
buffer.close();
this.flush();
this.buffer.close();
}
}
+46 -22
View File
@@ -19,7 +19,7 @@
package tests;
import com.seibel.distanthorizons.core.util.objects.GLMessage;
import com.seibel.distanthorizons.core.util.objects.GLMessages.*;
import org.junit.Assert;
import org.junit.Test;
@@ -28,66 +28,88 @@ import java.util.ArrayList;
public class GLMessageTest
{
public static final String MESSAGE_ID = "0x20071";
public static final GLMessage.ESource MESSAGE_SOURCE = GLMessage.ESource.API;
public static final GLMessage.EType MESSAGE_TYPE = GLMessage.EType.OTHER;
public static final GLMessage.ESeverity MESSAGE_SEVERITY = GLMessage.ESeverity.NOTIFICATION;
public static final EGLMessageSource MESSAGE_SOURCE = EGLMessageSource.API;
public static final EGLMessageType MESSAGE_TYPE = EGLMessageType.OTHER;
public static final EGLMessageSeverity MESSAGE_SEVERITY = EGLMessageSeverity.NOTIFICATION;
public static final String MESSAGE = "Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations.";
/** This is how debug messages were sent prior to Minecraft 1.20.2 */
private static final String[] PRE_1_20_2_MESSAGE_ARRAY =
private static final String[] OLD_MESSAGE_ARRAY =
{
"[LWJGL] OpenGL debug message"
,"ID", ":", "0x20071"
,"Source", ":", "API"
,"Type", ":", "OTHER"
,"Severity", ":", "NOTIFICATION"
,"Message", ":", "Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations."
,"ID", ":", MESSAGE_ID
,"Source", ":", MESSAGE_SOURCE.name
,"Type", ":", MESSAGE_TYPE.name
,"Severity", ":", MESSAGE_SEVERITY.name
,"Message", ":", MESSAGE
// optional addition to force the builder into noticing the message ended, shouldn't be necessary
//,"[LWJGL] OpenGL debug message"
};
/** This is how debug messages were sent after (and including) Minecraft 1.20.2 */
private static final String[] POST_1_20_2_MESSAGE_ARRAY =
private static final String[] NEW_MESSAGE_ARRAY =
{
"[LWJGL] OpenGL debug message"
,"ID: 0x20071"
,"Source: API"
,"Type: OTHER"
,"Severity: NOTIFICATION"
,"Message: Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations."
,"ID: " + MESSAGE_ID
,"Source: " + MESSAGE_SOURCE.name
,"Type: " + MESSAGE_TYPE.name
,"Severity: " + MESSAGE_SEVERITY.name
,"Message: " + MESSAGE
// optional addition to force the builder into noticing the message ended, shouldn't be necessary
//,"[LWJGL] OpenGL debug message"
};
public final GLMessageBuilder messageBuilder = new GLMessageBuilder(null, null, null);
//=======//
// tests //
//=======//
@Test
public void preMc1_20_2()
{
ArrayList<GLMessage> messageList = new ArrayList<>();
for (String str : PRE_1_20_2_MESSAGE_ARRAY)
for (String str : OLD_MESSAGE_ARRAY)
{
GLMessage message = GLMessage.Builder.DEFAULT_MESSAGE_BUILDER.add(str);
GLMessage message = this.messageBuilder.add(str);
if (message != null)
{
messageList.add(message);
}
}
//Assert.assertEquals("Incorrect message parse count.", 1, messageList.size());
//testMessage(messageList.get(0));
Assert.assertEquals("Incorrect message parse count.", 1, messageList.size());
messageMatchesExpected(messageList.get(0));
}
@Test
public void mc1_20_2()
{
// TODO
ArrayList<GLMessage> messageList = new ArrayList<>();
for (String str : NEW_MESSAGE_ARRAY)
{
GLMessage message = this.messageBuilder.add(str);
if (message != null)
{
messageList.add(message);
}
}
Assert.assertEquals("Incorrect message parse count.", 1, messageList.size());
messageMatchesExpected(messageList.get(0));
}
//================//
// helper methods //
//================//
private static void messageMatchesExpected(GLMessage testMessage)
{
Assert.assertEquals(MESSAGE_ID, testMessage.id);
@@ -97,4 +119,6 @@ public class GLMessageTest
Assert.assertEquals(MESSAGE, testMessage.message);
}
}