Преглед изворни кода

Merge pull request #94661 from m4gr3d/fix_android_render_thread_cleanup

Fix the cleanup logic for the Android render thread
Rémi Verschelde пре 1 година
родитељ
комит
ab80e564b2

+ 8 - 1
platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt

@@ -203,7 +203,14 @@ open class GodotEditor : GodotActivity() {
 		}
 		}
 		if (editorWindowInfo.windowClassName == javaClass.name) {
 		if (editorWindowInfo.windowClassName == javaClass.name) {
 			Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
 			Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
-			ProcessPhoenix.triggerRebirth(this, newInstance)
+			val godot = godot
+			if (godot != null) {
+				godot.destroyAndKillProcess {
+					ProcessPhoenix.triggerRebirth(this, newInstance)
+				}
+			} else {
+				ProcessPhoenix.triggerRebirth(this, newInstance)
+			}
 		} else {
 		} else {
 			Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
 			Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
 			newInstance.putExtra(EXTRA_NEW_LAUNCH, true)
 			newInstance.putExtra(EXTRA_NEW_LAUNCH, true)

+ 39 - 11
platform/android/java/lib/src/org/godotengine/godot/Godot.kt

@@ -73,6 +73,7 @@ import java.io.InputStream
 import java.lang.Exception
 import java.lang.Exception
 import java.security.MessageDigest
 import java.security.MessageDigest
 import java.util.*
 import java.util.*
+import java.util.concurrent.atomic.AtomicReference
 
 
 /**
 /**
  * Core component used to interface with the native layer of the engine.
  * Core component used to interface with the native layer of the engine.
@@ -127,6 +128,11 @@ class Godot(private val context: Context) : SensorEventListener {
 	val netUtils = GodotNetUtils(context)
 	val netUtils = GodotNetUtils(context)
 	private val commandLineFileParser = CommandLineFileParser()
 	private val commandLineFileParser = CommandLineFileParser()
 
 
+	/**
+	 * Task to run when the engine terminates.
+	 */
+	private val runOnTerminate = AtomicReference<Runnable>()
+
 	/**
 	/**
 	 * Tracks whether [onCreate] was completed successfully.
 	 * Tracks whether [onCreate] was completed successfully.
 	 */
 	 */
@@ -577,10 +583,7 @@ class Godot(private val context: Context) : SensorEventListener {
 			plugin.onMainDestroy()
 			plugin.onMainDestroy()
 		}
 		}
 
 
-		runOnRenderThread {
-			GodotLib.ondestroy()
-			forceQuit()
-		}
+		renderView?.onActivityDestroyed()
 	}
 	}
 
 
 	/**
 	/**
@@ -663,6 +666,15 @@ class Godot(private val context: Context) : SensorEventListener {
 		primaryHost?.onGodotMainLoopStarted()
 		primaryHost?.onGodotMainLoopStarted()
 	}
 	}
 
 
+	/**
+	 * Invoked on the render thread when the engine is about to terminate.
+	 */
+	@Keep
+	private fun onGodotTerminating() {
+		Log.v(TAG, "OnGodotTerminating")
+		runOnTerminate.get()?.run()
+	}
+
 	private fun restart() {
 	private fun restart() {
 		primaryHost?.onGodotRestartRequested(this)
 		primaryHost?.onGodotRestartRequested(this)
 	}
 	}
@@ -798,8 +810,28 @@ class Godot(private val context: Context) : SensorEventListener {
 		mClipboard.setPrimaryClip(clip)
 		mClipboard.setPrimaryClip(clip)
 	}
 	}
 
 
-	fun forceQuit() {
-		forceQuit(0)
+	/**
+	 * Destroys the Godot Engine and kill the process it's running in.
+	 */
+	@JvmOverloads
+	fun destroyAndKillProcess(destroyRunnable: Runnable? = null) {
+		val host = primaryHost
+		val activity = host?.activity
+		if (host == null || activity == null) {
+			// Run the destroyRunnable right away as we are about to force quit.
+			destroyRunnable?.run()
+
+			// Fallback to force quit
+			forceQuit(0)
+			return
+		}
+
+		// Store the destroyRunnable so it can be run when the engine is terminating
+		runOnTerminate.set(destroyRunnable)
+
+		runOnUiThread {
+			onDestroy(host)
+		}
 	}
 	}
 
 
 	@Keep
 	@Keep
@@ -814,11 +846,7 @@ class Godot(private val context: Context) : SensorEventListener {
 		} ?: return false
 		} ?: return false
 	}
 	}
 
 
-	fun onBackPressed(host: GodotHost) {
-		if (host != primaryHost) {
-			return
-		}
-
+	fun onBackPressed() {
 		var shouldQuit = true
 		var shouldQuit = true
 		for (plugin in pluginRegistry.allPlugins) {
 		for (plugin in pluginRegistry.allPlugins) {
 			if (plugin.onMainBackPressed()) {
 			if (plugin.onMainBackPressed()) {

+ 1 - 5
platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt

@@ -85,12 +85,8 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
 	protected open fun getGodotAppLayout() = R.layout.godot_app_layout
 	protected open fun getGodotAppLayout() = R.layout.godot_app_layout
 
 
 	override fun onDestroy() {
 	override fun onDestroy() {
-		Log.v(TAG, "Destroying Godot app...")
+		Log.v(TAG, "Destroying GodotActivity $this...")
 		super.onDestroy()
 		super.onDestroy()
-
-		godotFragment?.let {
-			terminateGodotInstance(it.godot)
-		}
 	}
 	}
 
 
 	override fun onGodotForceQuit(instance: Godot) {
 	override fun onGodotForceQuit(instance: Godot) {

+ 8 - 3
platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java

@@ -187,7 +187,12 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
 		final Activity activity = getActivity();
 		final Activity activity = getActivity();
 		mCurrentIntent = activity.getIntent();
 		mCurrentIntent = activity.getIntent();
 
 
-		godot = new Godot(requireContext());
+		if (parentHost != null) {
+			godot = parentHost.getGodot();
+		}
+		if (godot == null) {
+			godot = new Godot(requireContext());
+		}
 		performEngineInitialization();
 		performEngineInitialization();
 		BenchmarkUtils.endBenchmarkMeasure("Startup", "GodotFragment::onCreate");
 		BenchmarkUtils.endBenchmarkMeasure("Startup", "GodotFragment::onCreate");
 	}
 	}
@@ -209,7 +214,7 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
 			final String errorMessage = TextUtils.isEmpty(e.getMessage())
 			final String errorMessage = TextUtils.isEmpty(e.getMessage())
 					? getString(R.string.error_engine_setup_message)
 					? getString(R.string.error_engine_setup_message)
 					: e.getMessage();
 					: e.getMessage();
-			godot.alert(errorMessage, getString(R.string.text_error_title), godot::forceQuit);
+			godot.alert(errorMessage, getString(R.string.text_error_title), godot::destroyAndKillProcess);
 		} catch (IllegalArgumentException ignored) {
 		} catch (IllegalArgumentException ignored) {
 			final Activity activity = getActivity();
 			final Activity activity = getActivity();
 			Intent notifierIntent = new Intent(activity, activity.getClass());
 			Intent notifierIntent = new Intent(activity, activity.getClass());
@@ -325,7 +330,7 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
 	}
 	}
 
 
 	public void onBackPressed() {
 	public void onBackPressed() {
-		godot.onBackPressed(this);
+		godot.onBackPressed();
 	}
 	}
 
 
 	/**
 	/**

+ 7 - 3
platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java

@@ -42,7 +42,6 @@ import org.godotengine.godot.xr.regular.RegularContextFactory;
 import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser;
 import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser;
 
 
 import android.annotation.SuppressLint;
 import android.annotation.SuppressLint;
-import android.content.Context;
 import android.content.res.AssetManager;
 import android.content.res.AssetManager;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory;
@@ -77,7 +76,7 @@ import java.io.InputStream;
  *   that matches it exactly (with regards to red/green/blue/alpha channels
  *   that matches it exactly (with regards to red/green/blue/alpha channels
  *   bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
  *   bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
  */
  */
-public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
+class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
 	private final GodotHost host;
 	private final GodotHost host;
 	private final Godot godot;
 	private final Godot godot;
 	private final GodotInputHandler inputHandler;
 	private final GodotInputHandler inputHandler;
@@ -140,9 +139,14 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
 		resumeGLThread();
 		resumeGLThread();
 	}
 	}
 
 
+	@Override
+	public void onActivityDestroyed() {
+		requestRenderThreadExitAndWait();
+	}
+
 	@Override
 	@Override
 	public void onBackPressed() {
 	public void onBackPressed() {
-		godot.onBackPressed(host);
+		godot.onBackPressed();
 	}
 	}
 
 
 	@Override
 	@Override

+ 5 - 0
platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java

@@ -44,6 +44,9 @@ public interface GodotRenderView {
 	 */
 	 */
 	void startRenderer();
 	void startRenderer();
 
 
+	/**
+	 * Queues a runnable to be run on the rendering thread.
+	 */
 	void queueOnRenderThread(Runnable event);
 	void queueOnRenderThread(Runnable event);
 
 
 	void onActivityPaused();
 	void onActivityPaused();
@@ -54,6 +57,8 @@ public interface GodotRenderView {
 
 
 	void onActivityStarted();
 	void onActivityStarted();
 
 
+	void onActivityDestroyed();
+
 	void onBackPressed();
 	void onBackPressed();
 
 
 	GodotInputHandler getInputHandler();
 	GodotInputHandler getInputHandler();

+ 7 - 2
platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java

@@ -50,7 +50,7 @@ import androidx.annotation.Keep;
 
 
 import java.io.InputStream;
 import java.io.InputStream;
 
 
-public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
+class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
 	private final GodotHost host;
 	private final GodotHost host;
 	private final Godot godot;
 	private final Godot godot;
 	private final GodotInputHandler mInputHandler;
 	private final GodotInputHandler mInputHandler;
@@ -118,9 +118,14 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
 		});
 		});
 	}
 	}
 
 
+	@Override
+	public void onActivityDestroyed() {
+		requestRenderThreadExitAndWait();
+	}
+
 	@Override
 	@Override
 	public void onBackPressed() {
 	public void onBackPressed() {
-		godot.onBackPressed(host);
+		godot.onBackPressed();
 	}
 	}
 
 
 	@Override
 	@Override

+ 20 - 0
platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java

@@ -595,6 +595,15 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
 	protected final void resumeGLThread() {
 	protected final void resumeGLThread() {
 		mGLThread.onResume();
 		mGLThread.onResume();
 	}
 	}
+
+	/**
+	 * Requests the render thread to exit and block until it does.
+	 */
+	protected final void requestRenderThreadExitAndWait() {
+		if (mGLThread != null) {
+			mGLThread.requestExitAndWait();
+		}
+	}
 	// -- GODOT end --
 	// -- GODOT end --
 
 
 	/**
 	/**
@@ -783,6 +792,11 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
 		 * @return true if the buffers should be swapped, false otherwise.
 		 * @return true if the buffers should be swapped, false otherwise.
 		 */
 		 */
 		boolean onDrawFrame(GL10 gl);
 		boolean onDrawFrame(GL10 gl);
+
+		/**
+		 * Invoked when the render thread is in the process of shutting down.
+		 */
+		void onRenderThreadExiting();
 		// -- GODOT end --
 		// -- GODOT end --
 	}
 	}
 
 
@@ -1621,6 +1635,12 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
 				 * clean-up everything...
 				 * clean-up everything...
 				 */
 				 */
 				synchronized (sGLThreadManager) {
 				synchronized (sGLThreadManager) {
+					Log.d("GLThread", "Exiting render thread");
+					GLSurfaceView view = mGLSurfaceViewWeakRef.get();
+					if (view != null) {
+						view.mRenderer.onRenderThreadExiting();
+					}
+
 					stopEglSurfaceLocked();
 					stopEglSurfaceLocked();
 					stopEglContextLocked();
 					stopEglContextLocked();
 				}
 				}

+ 10 - 0
platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java

@@ -34,6 +34,8 @@ import org.godotengine.godot.GodotLib;
 import org.godotengine.godot.plugin.GodotPlugin;
 import org.godotengine.godot.plugin.GodotPlugin;
 import org.godotengine.godot.plugin.GodotPluginRegistry;
 import org.godotengine.godot.plugin.GodotPluginRegistry;
 
 
+import android.util.Log;
+
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
 import javax.microedition.khronos.opengles.GL10;
 
 
@@ -41,6 +43,8 @@ import javax.microedition.khronos.opengles.GL10;
  * Godot's GL renderer implementation.
  * Godot's GL renderer implementation.
  */
  */
 public class GodotRenderer implements GLSurfaceView.Renderer {
 public class GodotRenderer implements GLSurfaceView.Renderer {
+	private final String TAG = GodotRenderer.class.getSimpleName();
+
 	private final GodotPluginRegistry pluginRegistry;
 	private final GodotPluginRegistry pluginRegistry;
 	private boolean activityJustResumed = false;
 	private boolean activityJustResumed = false;
 
 
@@ -62,6 +66,12 @@ public class GodotRenderer implements GLSurfaceView.Renderer {
 		return swapBuffers;
 		return swapBuffers;
 	}
 	}
 
 
+	@Override
+	public void onRenderThreadExiting() {
+		Log.d(TAG, "Destroying Godot Engine");
+		GodotLib.ondestroy();
+	}
+
 	public void onSurfaceChanged(GL10 gl, int width, int height) {
 	public void onSurfaceChanged(GL10 gl, int width, int height) {
 		GodotLib.resize(null, width, height);
 		GodotLib.resize(null, width, height);
 		for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
 		for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {

+ 10 - 5
platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt

@@ -31,11 +31,9 @@
 @file:JvmName("VkRenderer")
 @file:JvmName("VkRenderer")
 package org.godotengine.godot.vulkan
 package org.godotengine.godot.vulkan
 
 
+import android.util.Log
 import android.view.Surface
 import android.view.Surface
-
-import org.godotengine.godot.Godot
 import org.godotengine.godot.GodotLib
 import org.godotengine.godot.GodotLib
-import org.godotengine.godot.plugin.GodotPlugin
 import org.godotengine.godot.plugin.GodotPluginRegistry
 import org.godotengine.godot.plugin.GodotPluginRegistry
 
 
 /**
 /**
@@ -52,6 +50,11 @@ import org.godotengine.godot.plugin.GodotPluginRegistry
  * @see [VkSurfaceView.startRenderer]
  * @see [VkSurfaceView.startRenderer]
  */
  */
 internal class VkRenderer {
 internal class VkRenderer {
+
+	companion object {
+		private val TAG = VkRenderer::class.java.simpleName
+	}
+
 	private val pluginRegistry: GodotPluginRegistry = GodotPluginRegistry.getPluginRegistry()
 	private val pluginRegistry: GodotPluginRegistry = GodotPluginRegistry.getPluginRegistry()
 
 
 	/**
 	/**
@@ -101,8 +104,10 @@ internal class VkRenderer {
 	}
 	}
 
 
 	/**
 	/**
-	 * Called when the rendering thread is destroyed and used as signal to tear down the Vulkan logic.
+	 * Invoked when the render thread is in the process of shutting down.
 	 */
 	 */
-	fun onVkDestroy() {
+	fun onRenderThreadExiting() {
+		Log.d(TAG, "Destroying Godot Engine")
+		GodotLib.ondestroy()
 	}
 	}
 }
 }

+ 3 - 5
platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt

@@ -113,12 +113,10 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf
 	}
 	}
 
 
 	/**
 	/**
-	 * Tear down the rendering thread.
-	 *
-	 * Must not be called before a [VkRenderer] has been set.
+	 * Requests the render thread to exit and block until it does.
 	 */
 	 */
-	fun onDestroy() {
-		vkThread.blockingExit()
+	fun requestRenderThreadExitAndWait() {
+		vkThread.requestExitAndWait()
 	}
 	}
 
 
 	override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
 	override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {

+ 4 - 2
platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt

@@ -75,6 +75,9 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
 
 
 	private fun threadExiting() {
 	private fun threadExiting() {
 		lock.withLock {
 		lock.withLock {
+			Log.d(TAG, "Exiting render thread")
+			vkRenderer.onRenderThreadExiting()
+
 			exited = true
 			exited = true
 			lockCondition.signalAll()
 			lockCondition.signalAll()
 		}
 		}
@@ -93,7 +96,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
 	/**
 	/**
 	 * Request the thread to exit and block until it's done.
 	 * Request the thread to exit and block until it's done.
 	 */
 	 */
-	fun blockingExit() {
+	fun requestExitAndWait() {
 		lock.withLock {
 		lock.withLock {
 			shouldExit = true
 			shouldExit = true
 			lockCondition.signalAll()
 			lockCondition.signalAll()
@@ -171,7 +174,6 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
 					while (true) {
 					while (true) {
 						// Code path for exiting the thread loop.
 						// Code path for exiting the thread loop.
 						if (shouldExit) {
 						if (shouldExit) {
-							vkRenderer.onVkDestroy()
 							return
 							return
 						}
 						}
 
 

+ 1 - 0
platform/android/java_godot_lib_jni.cpp

@@ -114,6 +114,7 @@ static void _terminate(JNIEnv *env, bool p_restart = false) {
 	NetSocketAndroid::terminate();
 	NetSocketAndroid::terminate();
 
 
 	if (godot_java) {
 	if (godot_java) {
+		godot_java->on_godot_terminating(env);
 		if (!restart_on_cleanup) {
 		if (!restart_on_cleanup) {
 			if (p_restart) {
 			if (p_restart) {
 				godot_java->restart(env);
 				godot_java->restart(env);

+ 11 - 0
platform/android/java_godot_wrapper.cpp

@@ -76,6 +76,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
 	_get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;");
 	_get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;");
 	_on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
 	_on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
 	_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
 	_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
+	_on_godot_terminating = p_env->GetMethodID(godot_class, "onGodotTerminating", "()V");
 	_create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I");
 	_create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I");
 	_get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;");
 	_get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;");
 	_begin_benchmark_measure = p_env->GetMethodID(godot_class, "nativeBeginBenchmarkMeasure", "(Ljava/lang/String;Ljava/lang/String;)V");
 	_begin_benchmark_measure = p_env->GetMethodID(godot_class, "nativeBeginBenchmarkMeasure", "(Ljava/lang/String;Ljava/lang/String;)V");
@@ -136,6 +137,16 @@ void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) {
 	}
 	}
 }
 }
 
 
+void GodotJavaWrapper::on_godot_terminating(JNIEnv *p_env) {
+	if (_on_godot_terminating) {
+		if (p_env == nullptr) {
+			p_env = get_jni_env();
+		}
+		ERR_FAIL_NULL(p_env);
+		p_env->CallVoidMethod(godot_instance, _on_godot_terminating);
+	}
+}
+
 void GodotJavaWrapper::restart(JNIEnv *p_env) {
 void GodotJavaWrapper::restart(JNIEnv *p_env) {
 	if (_restart) {
 	if (_restart) {
 		if (p_env == nullptr) {
 		if (p_env == nullptr) {

+ 2 - 0
platform/android/java_godot_wrapper.h

@@ -68,6 +68,7 @@ private:
 	jmethodID _get_input_fallback_mapping = nullptr;
 	jmethodID _get_input_fallback_mapping = nullptr;
 	jmethodID _on_godot_setup_completed = nullptr;
 	jmethodID _on_godot_setup_completed = nullptr;
 	jmethodID _on_godot_main_loop_started = nullptr;
 	jmethodID _on_godot_main_loop_started = nullptr;
+	jmethodID _on_godot_terminating = nullptr;
 	jmethodID _create_new_godot_instance = nullptr;
 	jmethodID _create_new_godot_instance = nullptr;
 	jmethodID _get_render_view = nullptr;
 	jmethodID _get_render_view = nullptr;
 	jmethodID _begin_benchmark_measure = nullptr;
 	jmethodID _begin_benchmark_measure = nullptr;
@@ -85,6 +86,7 @@ public:
 
 
 	void on_godot_setup_completed(JNIEnv *p_env = nullptr);
 	void on_godot_setup_completed(JNIEnv *p_env = nullptr);
 	void on_godot_main_loop_started(JNIEnv *p_env = nullptr);
 	void on_godot_main_loop_started(JNIEnv *p_env = nullptr);
+	void on_godot_terminating(JNIEnv *p_env = nullptr);
 	void restart(JNIEnv *p_env = nullptr);
 	void restart(JNIEnv *p_env = nullptr);
 	bool force_quit(JNIEnv *p_env = nullptr, int p_instance_id = 0);
 	bool force_quit(JNIEnv *p_env = nullptr, int p_instance_id = 0);
 	void set_keep_screen_on(bool p_enabled);
 	void set_keep_screen_on(bool p_enabled);