From c3bbc8661d5f8facd6c3054d81502817359d2cb5 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Fri, 14 Jan 2022 00:28:52 +0300 Subject: [PATCH] Added GLFW error handling and fixed a bug - GLFW errors are now detected - Added IntConstantsMap - Removed window maximization on init --- .../jputil/ConstantsMapException.java | 45 +++ .../ru/windcorp/jputil/IntConstantsMap.java | 307 ++++++++++++++++++ .../graphics/backend/GLFWErrorHandler.java | 56 ++++ .../graphics/backend/LWJGLInitializer.java | 30 +- 4 files changed, 424 insertions(+), 14 deletions(-) create mode 100644 src/main/java/ru/windcorp/jputil/ConstantsMapException.java create mode 100644 src/main/java/ru/windcorp/jputil/IntConstantsMap.java create mode 100644 src/main/java/ru/windcorp/progressia/client/graphics/backend/GLFWErrorHandler.java diff --git a/src/main/java/ru/windcorp/jputil/ConstantsMapException.java b/src/main/java/ru/windcorp/jputil/ConstantsMapException.java new file mode 100644 index 0000000..3c50204 --- /dev/null +++ b/src/main/java/ru/windcorp/jputil/ConstantsMapException.java @@ -0,0 +1,45 @@ +/* + * JPUtil + * Copyright (C) 2019-2021 OLEGSHA/Javapony and contributors + * + * This program 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. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package ru.windcorp.jputil; + +public class ConstantsMapException extends RuntimeException { + + private static final long serialVersionUID = -4298704891780063127L; + + public ConstantsMapException() { + + } + + public ConstantsMapException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public ConstantsMapException(String message, Throwable cause) { + super(message, cause); + } + + public ConstantsMapException(String message) { + super(message); + } + + public ConstantsMapException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/ru/windcorp/jputil/IntConstantsMap.java b/src/main/java/ru/windcorp/jputil/IntConstantsMap.java new file mode 100644 index 0000000..08f8101 --- /dev/null +++ b/src/main/java/ru/windcorp/jputil/IntConstantsMap.java @@ -0,0 +1,307 @@ +/* + * JPUtil + * Copyright (C) 2019-2022 OLEGSHA/Javapony and contributors + * + * This program 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. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package ru.windcorp.jputil; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntPredicate; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +public class IntConstantsMap { + + private final Map namesByValue; + private final Map valuesByName; + + protected IntConstantsMap(Map namesByValue, Map valuesByName) { + this.namesByValue = namesByValue; + this.valuesByName = valuesByName; + } + + public int getValue(String name) { + Integer value = valuesByName.get(name); + if (value == null) { + throw new NoSuchElementException("No constant with name " + name); + } + return value.intValue(); + } + + public boolean hasConstant(String name) { + return valuesByName.containsKey(name); + } + + public String getName(int value) { + String name = namesByValue.get(value); + if (name == null) { + throw new NoSuchElementException("No constant with value " + value); + } + return name; + } + + public boolean hasConstant(int value) { + return namesByValue.containsKey(value); + } + + public Map getAll() { + return valuesByName; + } + + @Override + public String toString() { + return valuesByName.toString(); + } + + public static Builder from(Class clazz) { + return new Builder(clazz); + } + + public static class Builder { + + @FunctionalInterface + public static interface Filter { + boolean test(String name, int value); + } + + public class ConstantSpec { + public String name; + public int value; + + public void drop() { + if (!extra.contains(name)) { + name = null; + } + } + } + + private final List> transforms = new ArrayList<>(); + private final Set extra = new HashSet<>(); + + private final Class source; + + public Builder(Class source) { + this.source = source; + } + + public Builder apply(Consumer transform) { + transforms.add(transform); + return this; + } + + public Builder only(Filter filter) { + return apply(s -> { + if (!filter.test(s.name, s.value)) { + s.drop(); + } + }); + } + + public Builder only(Predicate nameFilter) { + return apply(s -> { + if (!nameFilter.test(s.name)) { + s.drop(); + } + }); + } + + public Builder onlyValued(IntPredicate valueFilter) { + return apply(s -> { + if (!valueFilter.test(s.value)) { + s.drop(); + } + }); + } + + public Builder regex(String regex) { + return only(Pattern.compile(regex).asPredicate()); + } + + public Builder prefix(String prefix) { + return only(n -> n.startsWith(prefix) && n.length() > prefix.length()); + } + + public Builder exclude(Filter filter) { + return only((n, v) -> !filter.test(n, v)); + } + + public Builder exclude(Predicate nameFilter) { + return only(nameFilter.negate()); + } + + public Builder exclude(String... names) { + Set excluded = new HashSet<>(); + for (String name : names) { + excluded.add(name); + } + return exclude(excluded::contains); + } + + public Builder excludeRegex(String... nameRegexes) { + List> tests = new ArrayList<>(); + for (String regex : nameRegexes) { + tests.add(Pattern.compile(regex).asPredicate()); + } + return only((n, v) -> { + for (Predicate test : tests) { + if (test.test(n)) { + return false; + } + } + + return true; + }); + } + + public Builder extra(String... names) { + for (String name : names) { + extra.add(name); + } + return this; + } + + public Builder rename(Function renamer) { + apply(s -> { + s.name = renamer.apply(s.name); + }); + return this; + } + + public Builder stripPrefix(String prefix) { + return apply(s -> { + if (s.name.startsWith(prefix)) { + s.name = s.name.substring(prefix.length()); + } else if (extra.contains(s.name)) { + return; + } else { + s.drop(); + } + }); + } + + public IntConstantsMap scan() { + return build(true); + } + + public IntConstantsMap scanAll() { + return build(false); + } + + private IntConstantsMap build(boolean onlyPublic) { + Map namesByValue = new HashMap<>(); + Map valuesByName = new HashMap<>(); + + BiConsumer putter = (name, value) -> { + if (namesByValue.containsKey(value)) { + throw newDuplicateException("value", value, name, namesByValue.get(value)); + } + if (valuesByName.containsKey(name)) { + throw newDuplicateException("name", name, value, valuesByName.get(name)); + } + namesByValue.put(value, name); + valuesByName.put(name, value); + }; + + try { + for (Field field : source.getDeclaredFields()) { + processField(field, putter, onlyPublic); + } + } catch (IllegalAccessException e) { + throw new ConstantsMapException(e); + } + + return new IntConstantsMap( + Collections.unmodifiableMap(namesByValue), + Collections.unmodifiableMap(valuesByName) + ); + } + + private void processField(Field field, BiConsumer putter, boolean onlyPublic) + throws IllegalAccessException { + if (!Modifier.isStatic(field.getModifiers())) { + return; + } + if (!Modifier.isFinal(field.getModifiers())) { + return; + } + + boolean clearAccessible = false; + if (!Modifier.isPublic(field.getModifiers())) { + if (onlyPublic) { + return; + } else if (!isAccessibleFlagSet(field)) { + field.setAccessible(true); + clearAccessible = true; + } + } + + try { + + ConstantSpec spec = new ConstantSpec(); + spec.name = field.getName(); + spec.value = field.getInt(null); + + for (Consumer t : transforms) { + t.accept(spec); + if (spec.name == null) { + return; + } + } + + putter.accept(spec.name, spec.value); + + } finally { + if (clearAccessible) { + field.setAccessible(false); + } + } + } + + /* + * Yes, this method exists only so that neither Java 8 nor Java 9 complain about deprecation. + */ + @Deprecated + private boolean isAccessibleFlagSet(Field f) { + return f.isAccessible(); + } + + private ConstantsMapException newDuplicateException(String what, Object common, Object current, Object old) { + return new ConstantsMapException( + String.format( + "Duplicate %1$s: %2$s -> %3$s and %2$s -> %4$s", + what, + common, + current, + old + ) + ); + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/GLFWErrorHandler.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/GLFWErrorHandler.java new file mode 100644 index 0000000..7f6364d --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/GLFWErrorHandler.java @@ -0,0 +1,56 @@ +/* + * Progressia + * Copyright (C) 2020-2022 Wind Corporation and contributors + * + * This program 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. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.client.graphics.backend; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWErrorCallback; + +import ru.windcorp.jputil.ConstantsMapException; +import ru.windcorp.jputil.IntConstantsMap; +import ru.windcorp.progressia.common.util.crash.CrashReports; + +public class GLFWErrorHandler { + + private static final IntConstantsMap ERROR_CODES; + + static { + try { + ERROR_CODES = IntConstantsMap.from(GLFW.class) + .stripPrefix("GLFW_") + .onlyValued(i -> i >= 0x10000 && i <= 0x1FFFF) + .extra("GLFW_NO_ERROR") + .scan(); + } catch (ConstantsMapException e) { + throw CrashReports.report(e, "Could not analyze GLFW error codes"); + } + } + + public void onError(int errorCode, long descriptionPointer) { + String description = GLFWErrorCallback.getDescription(descriptionPointer); + + String errorCodeName; + if (ERROR_CODES.hasConstant(errorCode)) { + errorCodeName = ERROR_CODES.getName(errorCode); + } else { + errorCodeName = ""; + } + + throw CrashReports.report(null, "GLFW error detected: " + errorCodeName + " %s", description); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java index fded2be..e7d5845 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/LWJGLInitializer.java @@ -24,6 +24,7 @@ import static org.lwjgl.system.MemoryUtil.*; import java.io.IOException; +import org.lwjgl.glfw.GLFWErrorCallback; import org.lwjgl.glfw.GLFWImage; import org.lwjgl.opengl.GL; @@ -55,6 +56,7 @@ class LWJGLInitializer { setupWindowCallbacks(); glfwShowWindow(GraphicsBackend.getWindowHandle()); + GraphicsBackend.onFrameResized(GraphicsBackend.getWindowHandle(), 800, 600); } private static void checkEnvironment() { @@ -62,8 +64,12 @@ class LWJGLInitializer { } private static void initializeGLFW() { - // TODO Do GLFW error handling: check glfwInit, setup error callback - glfwInit(); + GLFWErrorCallback.create(new GLFWErrorHandler()::onError).set(); + + if (!glfwInit()) { + throw CrashReports.report(null, "GLFW could not be initialized: glfwInit() has failed"); + } + GraphicsBackend.setGLFWInitialized(true); } @@ -71,17 +77,13 @@ class LWJGLInitializer { glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); - glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); - long handle = glfwCreateWindow( - 800, - 600, - Progressia.getName() + " " + Progressia.getFullerVersion(), - NULL, - NULL - ); - - // TODO Check that handle != NULL + String windowTitle = Progressia.getName() + " " + Progressia.getFullerVersion(); + long handle = glfwCreateWindow(800, 600, windowTitle, NULL, NULL); + + if (handle == 0) { + throw CrashReports.report(null, "Could not create game window"); + } GraphicsBackend.setWindowHandle(handle); @@ -95,8 +97,8 @@ class LWJGLInitializer { } private static void createWindowIcons() { - if (glfwGetVersionString().toLowerCase().contains("wayland")) { - // glfwSetWindowIcon is not supported on Wayland + if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { + // Wayland does not support changing window icons return; }