Fix GL error logging
This commit is contained in:
@@ -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
|
||||
|
||||
}
|
||||
+54
@@ -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()); }
|
||||
|
||||
}
|
||||
|
||||
+55
@@ -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()); }
|
||||
|
||||
}
|
||||
+58
@@ -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()); }
|
||||
|
||||
}
|
||||
+56
@@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+319
@@ -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
-11
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user