|
@@ -0,0 +1,179 @@
|
|
|
+/*
|
|
|
+ * Copyright 2020 damios
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at:
|
|
|
+ * https://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+//Note, the above license and copyright applies to this file only.
|
|
|
+
|
|
|
+package xyz.prismix.MiningGame.lwjgl3;
|
|
|
+
|
|
|
+import org.lwjgl.system.macosx.LibC;
|
|
|
+
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.File;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.lang.management.ManagementFactory;
|
|
|
+import java.util.ArrayList;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Adds some utilities to ensure that the JVM was started with the
|
|
|
+ * {@code -XstartOnFirstThread} argument, which is required on macOS for LWJGL 3
|
|
|
+ * to function. Also helps on Windows when users have names with characters from
|
|
|
+ * outside the Latin alphabet, a common cause of startup crashes.
|
|
|
+ * <br>
|
|
|
+ * <a href="https://jvm-gaming.org/t/starting-jvm-on-mac-with-xstartonfirstthread-programmatically/57547">Based on this java-gaming.org post by kappa</a>
|
|
|
+ * @author damios
|
|
|
+ */
|
|
|
+public class StartupHelper {
|
|
|
+
|
|
|
+ private static final String JVM_RESTARTED_ARG = "jvmIsRestarted";
|
|
|
+
|
|
|
+ private StartupHelper() {
|
|
|
+ throw new UnsupportedOperationException();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Starts a new JVM if the application was started on macOS without the
|
|
|
+ * {@code -XstartOnFirstThread} argument. This also includes some code for
|
|
|
+ * Windows, for the case where the user's home directory includes certain
|
|
|
+ * non-Latin-alphabet characters (without this code, most LWJGL3 apps fail
|
|
|
+ * immediately for those users). Returns whether a new JVM was started and
|
|
|
+ * thus no code should be executed.
|
|
|
+ * <p>
|
|
|
+ * <u>Usage:</u>
|
|
|
+ *
|
|
|
+ * <pre><code>
|
|
|
+ * public static void main(String... args) {
|
|
|
+ * if (StartupHelper.startNewJvmIfRequired(true)) return; // This handles macOS support and helps on Windows.
|
|
|
+ * // after this is the actual main method code
|
|
|
+ * }
|
|
|
+ * </code></pre>
|
|
|
+ *
|
|
|
+ * @param redirectOutput
|
|
|
+ * whether the output of the new JVM should be rerouted to the
|
|
|
+ * old JVM, so it can be accessed in the same place; keeps the
|
|
|
+ * old JVM running if enabled
|
|
|
+ * @return whether a new JVM was started and thus no code should be executed
|
|
|
+ * in this one
|
|
|
+ */
|
|
|
+ public static boolean startNewJvmIfRequired(boolean redirectOutput) {
|
|
|
+ String osName = System.getProperty("os.name").toLowerCase();
|
|
|
+ if (!osName.contains("mac")) {
|
|
|
+ if (osName.contains("windows")) {
|
|
|
+// Here, we are trying to work around an issue with how LWJGL3 loads its extracted .dll files.
|
|
|
+// By default, LWJGL3 extracts to the directory specified by "java.io.tmpdir", which is usually the user's home.
|
|
|
+// If the user's name has non-ASCII (or some non-alphanumeric) characters in it, that would fail.
|
|
|
+// By extracting to the relevant "ProgramData" folder, which is usually "C:\ProgramData", we avoid this.
|
|
|
+ System.setProperty("java.io.tmpdir", System.getenv("ProgramData") + "/libGDX-temp");
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // There is no need for -XstartOnFirstThread on Graal native image
|
|
|
+ if (!System.getProperty("org.graalvm.nativeimage.imagecode", "").isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ long pid = LibC.getpid();
|
|
|
+
|
|
|
+ // check whether -XstartOnFirstThread is enabled
|
|
|
+ if ("1".equals(System.getenv("JAVA_STARTED_ON_FIRST_THREAD_" + pid))) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // check whether the JVM was previously restarted
|
|
|
+ // avoids looping, but most certainly leads to a crash
|
|
|
+ if ("true".equals(System.getProperty(JVM_RESTARTED_ARG))) {
|
|
|
+ System.err.println(
|
|
|
+ "There was a problem evaluating whether the JVM was started with the -XstartOnFirstThread argument.");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Restart the JVM with -XstartOnFirstThread
|
|
|
+ ArrayList<String> jvmArgs = new ArrayList<>();
|
|
|
+ String separator = System.getProperty("file.separator");
|
|
|
+ // The following line is used assuming you target Java 8, the minimum for LWJGL3.
|
|
|
+ String javaExecPath = System.getProperty("java.home") + separator + "bin" + separator + "java";
|
|
|
+ // If targeting Java 9 or higher, you could use the following instead of the above line:
|
|
|
+ //String javaExecPath = ProcessHandle.current().info().command().orElseThrow();
|
|
|
+
|
|
|
+ if (!(new File(javaExecPath)).exists()) {
|
|
|
+ System.err.println(
|
|
|
+ "A Java installation could not be found. If you are distributing this app with a bundled JRE, be sure to set the -XstartOnFirstThread argument manually!");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ jvmArgs.add(javaExecPath);
|
|
|
+ jvmArgs.add("-XstartOnFirstThread");
|
|
|
+ jvmArgs.add("-D" + JVM_RESTARTED_ARG + "=true");
|
|
|
+ jvmArgs.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
|
|
+ jvmArgs.add("-cp");
|
|
|
+ jvmArgs.add(System.getProperty("java.class.path"));
|
|
|
+ String mainClass = System.getenv("JAVA_MAIN_CLASS_" + pid);
|
|
|
+ if (mainClass == null) {
|
|
|
+ StackTraceElement[] trace = Thread.currentThread().getStackTrace();
|
|
|
+ if (trace.length > 0) {
|
|
|
+ mainClass = trace[trace.length - 1].getClassName();
|
|
|
+ } else {
|
|
|
+ System.err.println("The main class could not be determined.");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ jvmArgs.add(mainClass);
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (!redirectOutput) {
|
|
|
+ ProcessBuilder processBuilder = new ProcessBuilder(jvmArgs);
|
|
|
+ processBuilder.start();
|
|
|
+ } else {
|
|
|
+ Process process = (new ProcessBuilder(jvmArgs))
|
|
|
+ .redirectErrorStream(true).start();
|
|
|
+ BufferedReader processOutput = new BufferedReader(
|
|
|
+ new InputStreamReader(process.getInputStream()));
|
|
|
+ String line;
|
|
|
+
|
|
|
+ while ((line = processOutput.readLine()) != null) {
|
|
|
+ System.out.println(line);
|
|
|
+ }
|
|
|
+
|
|
|
+ process.waitFor();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.err.println("There was a problem restarting the JVM");
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Starts a new JVM if the application was started on macOS without the
|
|
|
+ * {@code -XstartOnFirstThread} argument. Returns whether a new JVM was
|
|
|
+ * started and thus no code should be executed. Redirects the output of the
|
|
|
+ * new JVM to the old one.
|
|
|
+ * <p>
|
|
|
+ * <u>Usage:</u>
|
|
|
+ *
|
|
|
+ * <pre>
|
|
|
+ * public static void main(String... args) {
|
|
|
+ * if (StartupHelper.startNewJvmIfRequired()) return; // This handles macOS support and helps on Windows.
|
|
|
+ * // the actual main method code
|
|
|
+ * }
|
|
|
+ * </pre>
|
|
|
+ *
|
|
|
+ * @return whether a new JVM was started and thus no code should be executed
|
|
|
+ * in this one
|
|
|
+ */
|
|
|
+ public static boolean startNewJvmIfRequired() {
|
|
|
+ return startNewJvmIfRequired(true);
|
|
|
+ }
|
|
|
+}
|