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;
}