diff options
Diffstat (limited to 'platform')
33 files changed, 441 insertions, 258 deletions
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 4abc6548bf..0cc929d226 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -24,6 +24,10 @@ android:hasFragileUserData="false" android:requestLegacyExternalStorage="false" tools:ignore="GoogleAppIndexingWarning" > + <profileable + android:shell="true" + android:enabled="true" + tools:targetApi="29" /> <!-- Records the version of the Godot editor used for building --> <meta-data diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 01759a1b2f..eb9ad9de05 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -7,7 +7,7 @@ ext.versions = [ targetSdk : 34, buildTools : '34.0.0', kotlinVersion : '1.9.20', - fragmentVersion : '1.6.2', + fragmentVersion : '1.7.1', nexusPublishVersion: '1.3.0', javaVersion : JavaVersion.VERSION_17, // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index 55fe2a22fe..37f68d295a 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -9,7 +9,7 @@ dependencies { implementation "androidx.fragment:fragment:$versions.fragmentVersion" implementation project(":lib") - implementation "androidx.window:window:1.2.0" + implementation "androidx.window:window:1.3.0" implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion" implementation "androidx.constraintlayout:constraintlayout:2.1.4" } diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 5515347bd6..dad397de61 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -117,6 +117,10 @@ open class GodotEditor : GodotActivity() { val longPressEnabled = enableLongPressGestures() val panScaleEnabled = enablePanAndScaleGestures() + val useInputBuffering = useInputBuffering() + val useAccumulatedInput = useAccumulatedInput() + GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering) + checkForProjectPermissionsToEnable() runOnUiThread { @@ -124,6 +128,7 @@ open class GodotEditor : GodotActivity() { godotFragment?.godot?.renderView?.inputHandler?.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) + enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput) } } } @@ -275,6 +280,13 @@ open class GodotEditor : GodotActivity() { java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures")) /** + * Use input buffering for the Godot Android editor. + */ + protected open fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_input_buffering")) + + protected open fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_accumulated_input")) + + /** * Whether we should launch the new godot instance in an adjacent window * @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT */ diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt index 8e4e089211..f50b5577c3 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt @@ -30,6 +30,8 @@ package org.godotengine.editor +import org.godotengine.godot.GodotLib + /** * Drives the 'run project' window of the Godot Editor. */ @@ -39,9 +41,13 @@ class GodotGame : GodotEditor() { override fun overrideOrientationRequest() = false - override fun enableLongPressGestures() = false + override fun enableLongPressGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) + + override fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) + + override fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering")) - override fun enablePanAndScaleGestures() = false + override fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input")) override fun checkForProjectPermissionsToEnable() { // Nothing to do.. by the time we get here, the project permissions will have already diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 290be727ab..c188a97ca5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -84,17 +84,20 @@ class Godot(private val context: Context) : SensorEventListener { private companion object { private val TAG = Godot::class.java.simpleName - } - private val windowManager: WindowManager by lazy { - requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager + // Supported build flavors + const val EDITOR_FLAVOR = "editor" + const val TEMPLATE_FLAVOR = "template" } + + private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + private val mSensorManager: SensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager + private val mClipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + private val vibratorService: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + private val pluginRegistry: GodotPluginRegistry by lazy { GodotPluginRegistry.getPluginRegistry() } - private val mSensorManager: SensorManager by lazy { - requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager - } private val mAccelerometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) } @@ -107,9 +110,6 @@ class Godot(private val context: Context) : SensorEventListener { private val mGyroscope: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) } - private val mClipboard: ClipboardManager by lazy { - requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - } private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int -> if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { @@ -192,6 +192,8 @@ class Godot(private val context: Context) : SensorEventListener { return } + Log.v(TAG, "OnCreate: $primaryHost") + darkMode = context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES beginBenchmarkMeasure("Startup", "Godot::onCreate") @@ -200,6 +202,8 @@ class Godot(private val context: Context) : SensorEventListener { val activity = requireActivity() val window = activity.window window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) + + Log.v(TAG, "Initializing Godot plugin registry") GodotPluginRegistry.initializePluginRegistry(this, primaryHost.getHostPlugins(this)) if (io == null) { io = GodotIO(activity) @@ -323,13 +327,17 @@ class Godot(private val context: Context) : SensorEventListener { return false } - if (expansionPackPath.isNotEmpty()) { - commandLine.add("--main-pack") - commandLine.add(expansionPackPath) - } - val activity = requireActivity() - if (!nativeLayerInitializeCompleted) { - nativeLayerInitializeCompleted = GodotLib.initialize( + Log.v(TAG, "OnInitNativeLayer: $host") + + beginBenchmarkMeasure("Startup", "Godot::onInitNativeLayer") + try { + if (expansionPackPath.isNotEmpty()) { + commandLine.add("--main-pack") + commandLine.add(expansionPackPath) + } + val activity = requireActivity() + if (!nativeLayerInitializeCompleted) { + nativeLayerInitializeCompleted = GodotLib.initialize( activity, this, activity.assets, @@ -338,15 +346,20 @@ class Godot(private val context: Context) : SensorEventListener { directoryAccessHandler, fileAccessHandler, useApkExpansion, - ) - } + ) + Log.v(TAG, "Godot native layer initialization completed: $nativeLayerInitializeCompleted") + } - if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) { - nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts) - if (!nativeLayerSetupCompleted) { - Log.e(TAG, "Unable to setup the Godot engine! Aborting...") - alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit) + if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) { + nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts) + if (!nativeLayerSetupCompleted) { + throw IllegalStateException("Unable to setup the Godot engine! Aborting...") + } else { + Log.v(TAG, "Godot native layer setup completed") + } } + } finally { + endBenchmarkMeasure("Startup", "Godot::onInitNativeLayer") } return isNativeInitialized() } @@ -370,6 +383,9 @@ class Godot(private val context: Context) : SensorEventListener { throw IllegalStateException("onInitNativeLayer() must be invoked successfully prior to initializing the render view") } + Log.v(TAG, "OnInitRenderView: $host") + + beginBenchmarkMeasure("Startup", "Godot::onInitRenderView") try { val activity: Activity = host.activity containerLayout = providedContainerLayout @@ -392,8 +408,7 @@ class Godot(private val context: Context) : SensorEventListener { containerLayout?.addView(editText) renderView = if (usesVulkan()) { if (!meetsVulkanRequirements(activity.packageManager)) { - alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit) - return null + throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message)) } GodotVulkanRenderView(host, this) } else { @@ -482,11 +497,14 @@ class Godot(private val context: Context) : SensorEventListener { containerLayout?.removeAllViews() containerLayout = null } + + endBenchmarkMeasure("Startup", "Godot::onInitRenderView") } return containerLayout } fun onStart(host: GodotHost) { + Log.v(TAG, "OnStart: $host") if (host != primaryHost) { return } @@ -495,6 +513,7 @@ class Godot(private val context: Context) : SensorEventListener { } fun onResume(host: GodotHost) { + Log.v(TAG, "OnResume: $host") if (host != primaryHost) { return } @@ -527,6 +546,7 @@ class Godot(private val context: Context) : SensorEventListener { } fun onPause(host: GodotHost) { + Log.v(TAG, "OnPause: $host") if (host != primaryHost) { return } @@ -539,6 +559,7 @@ class Godot(private val context: Context) : SensorEventListener { } fun onStop(host: GodotHost) { + Log.v(TAG, "OnStop: $host") if (host != primaryHost) { return } @@ -547,6 +568,7 @@ class Godot(private val context: Context) : SensorEventListener { } fun onDestroy(primaryHost: GodotHost) { + Log.v(TAG, "OnDestroy: $primaryHost") if (this.primaryHost != primaryHost) { return } @@ -604,18 +626,29 @@ class Godot(private val context: Context) : SensorEventListener { * Invoked on the render thread when the Godot setup is complete. */ private fun onGodotSetupCompleted() { - Log.d(TAG, "OnGodotSetupCompleted") - - // These properties are defined after Godot setup completion, so we retrieve them here. - val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) - val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) - val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")) - - runOnUiThread { - renderView?.inputHandler?.apply { - enableLongPress(longPressEnabled) - enablePanningAndScalingGestures(panScaleEnabled) - setRotaryInputAxis(rotaryInputAxis) + Log.v(TAG, "OnGodotSetupCompleted") + + if (!isEditorBuild()) { + // These properties are defined after Godot setup completion, so we retrieve them here. + val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) + val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) + val rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis") + + val useInputBuffering = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering")) + val useAccumulatedInput = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input")) + GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering) + + runOnUiThread { + renderView?.inputHandler?.apply { + enableLongPress(longPressEnabled) + enablePanningAndScalingGestures(panScaleEnabled) + enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput) + try { + setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue)) + } catch (e: NumberFormatException) { + Log.w(TAG, e) + } + } } } @@ -629,7 +662,7 @@ class Godot(private val context: Context) : SensorEventListener { * Invoked on the render thread when the Godot main loop has started. */ private fun onGodotMainLoopStarted() { - Log.d(TAG, "OnGodotMainLoopStarted") + Log.v(TAG, "OnGodotMainLoopStarted") for (plugin in pluginRegistry.allPlugins) { plugin.onGodotMainLoopStarted() @@ -646,12 +679,7 @@ class Godot(private val context: Context) : SensorEventListener { decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener) } - @Keep - private fun alert(message: String, title: String) { - alert(message, title, null) - } - - private fun alert( + fun alert( @StringRes messageResId: Int, @StringRes titleResId: Int, okCallback: Runnable? @@ -660,7 +688,9 @@ class Godot(private val context: Context) : SensorEventListener { alert(res.getString(messageResId), res.getString(titleResId), okCallback) } - private fun alert(message: String, title: String, okCallback: Runnable?) { + @JvmOverloads + @Keep + fun alert(message: String, title: String, okCallback: Runnable? = null) { val activity: Activity = getActivity() ?: return runOnUiThread { val builder = AlertDialog.Builder(activity) @@ -759,6 +789,11 @@ class Godot(private val context: Context) : SensorEventListener { return mClipboard.hasPrimaryClip() } + /** + * @return true if this is an editor build, false if this is a template build + */ + fun isEditorBuild() = BuildConfig.FLAVOR == EDITOR_FLAVOR + fun getClipboard(): String { val clipData = mClipboard.primaryClip ?: return "" val text = clipData.getItemAt(0).text ?: return "" @@ -770,7 +805,7 @@ class Godot(private val context: Context) : SensorEventListener { mClipboard.setPrimaryClip(clip) } - private fun forceQuit() { + fun forceQuit() { forceQuit(0) } @@ -881,7 +916,6 @@ class Godot(private val context: Context) : SensorEventListener { @Keep private fun vibrate(durationMs: Int, amplitude: Int) { if (durationMs > 0 && requestPermission("VIBRATE")) { - val vibratorService = getActivity()?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? ?: return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (amplitude <= -1) { vibratorService.vibrate( diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index a323045e1b..1612ddd0b3 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -42,6 +42,7 @@ import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.os.Messenger; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -203,6 +204,12 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH if (godotContainerLayout == null) { throw new IllegalStateException("Unable to initialize engine render view"); } + } catch (IllegalStateException e) { + Log.e(TAG, "Engine initialization failed", e); + final String errorMessage = TextUtils.isEmpty(e.getMessage()) + ? getString(R.string.error_engine_setup_message) + : e.getMessage(); + godot.alert(errorMessage, getString(R.string.text_error_title), godot::forceQuit); } catch (IllegalArgumentException ignored) { final Activity activity = getActivity(); Intent notifierIntent = new Intent(activity, activity.getClass()); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index 4b51bd778d..219631284a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -121,7 +121,7 @@ public class GodotIO { activity.startActivity(intent); return 0; - } catch (ActivityNotFoundException e) { + } catch (Exception e) { Log.e(TAG, "Unable to open uri " + uriString, e); return 1; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index d0c3d4a687..37e889daf7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -240,4 +240,11 @@ public class GodotLib { * @see GodotRenderer#onActivityPaused() */ public static native void onRendererPaused(); + + /** + * Invoked on the GL thread to update the input dispatch settings + * @param useAccumulatedInput True to use accumulated input, false otherwise + * @param useInputBuffering True to use input buffering, false otherwise + */ + public static native void updateInputDispatchSettings(boolean useAccumulatedInput, boolean useInputBuffering); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java index c316812404..c9421a3257 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java @@ -1704,15 +1704,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback mHasSurface = true; mFinishedCreatingEglSurface = false; sGLThreadManager.notifyAll(); - while (mWaitingForSurface - && !mFinishedCreatingEglSurface - && !mExited) { - try { - sGLThreadManager.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } } } @@ -1723,13 +1714,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } mHasSurface = false; sGLThreadManager.notifyAll(); - while((!mWaitingForSurface) && (!mExited)) { - try { - sGLThreadManager.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } } } @@ -1740,16 +1724,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } mRequestPaused = true; sGLThreadManager.notifyAll(); - while ((! mExited) && (! mPaused)) { - if (LOG_PAUSE_RESUME) { - Log.i("Main thread", "onPause waiting for mPaused."); - } - try { - sGLThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } } } @@ -1762,16 +1736,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback mRequestRender = true; mRenderComplete = false; sGLThreadManager.notifyAll(); - while ((! mExited) && mPaused && (!mRenderComplete)) { - if (LOG_PAUSE_RESUME) { - Log.i("Main thread", "onResume waiting for !mPaused."); - } - try { - sGLThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } } } @@ -1793,19 +1757,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } sGLThreadManager.notifyAll(); - - // Wait for thread to react to resize and render a frame - while (! mExited && !mPaused && !mRenderComplete - && ableToDraw()) { - if (LOG_SURFACE) { - Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId()); - } - try { - sGLThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt index 49b34a5229..4cd3bd8db9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt @@ -44,7 +44,7 @@ import org.godotengine.godot.GodotLib * @See https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener * @See https://developer.android.com/reference/android/view/ScaleGestureDetector.OnScaleGestureListener */ -internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureListener { +internal class GodotGestureHandler(private val inputHandler: GodotInputHandler) : SimpleOnGestureListener(), OnScaleGestureListener { companion object { private val TAG = GodotGestureHandler::class.java.simpleName @@ -65,13 +65,13 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi private var lastDragY: Float = 0.0f override fun onDown(event: MotionEvent): Boolean { - GodotInputHandler.handleMotionEvent(event, MotionEvent.ACTION_DOWN, nextDownIsDoubleTap) + inputHandler.handleMotionEvent(event, MotionEvent.ACTION_DOWN, nextDownIsDoubleTap) nextDownIsDoubleTap = false return true } override fun onSingleTapUp(event: MotionEvent): Boolean { - GodotInputHandler.handleMotionEvent(event) + inputHandler.handleMotionEvent(event) return true } @@ -85,10 +85,10 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi } // Cancel the previous down event - GodotInputHandler.handleMotionEvent(event, MotionEvent.ACTION_CANCEL) + inputHandler.handleMotionEvent(event, MotionEvent.ACTION_CANCEL) // Turn a context click into a single tap right mouse button click. - GodotInputHandler.handleMouseEvent( + inputHandler.handleMouseEvent( event, MotionEvent.ACTION_DOWN, MotionEvent.BUTTON_SECONDARY, @@ -104,7 +104,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi if (!hasCapture) { // Dispatch a mouse relative ACTION_UP event to signal the end of the capture - GodotInputHandler.handleMouseEvent(MotionEvent.ACTION_UP, true) + inputHandler.handleMouseEvent(MotionEvent.ACTION_UP, true) } pointerCaptureInProgress = hasCapture } @@ -131,9 +131,9 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi if (contextClickInProgress || GodotInputHandler.isMouseEvent(event)) { // This may be an ACTION_BUTTON_RELEASE event which we don't handle, // so we convert it to an ACTION_UP event. - GodotInputHandler.handleMouseEvent(event, MotionEvent.ACTION_UP) + inputHandler.handleMouseEvent(event, MotionEvent.ACTION_UP) } else { - GodotInputHandler.handleTouchEvent(event) + inputHandler.handleTouchEvent(event) } pointerCaptureInProgress = false dragInProgress = false @@ -148,7 +148,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi private fun onActionMove(event: MotionEvent): Boolean { if (contextClickInProgress) { - GodotInputHandler.handleMouseEvent(event, event.actionMasked, MotionEvent.BUTTON_SECONDARY, false) + inputHandler.handleMouseEvent(event, event.actionMasked, MotionEvent.BUTTON_SECONDARY, false) return true } else if (!scaleInProgress) { // The 'onScroll' event is triggered with a long delay. @@ -158,7 +158,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi if (lastDragX != event.getX(0) || lastDragY != event.getY(0)) { lastDragX = event.getX(0) lastDragY = event.getY(0) - GodotInputHandler.handleMotionEvent(event) + inputHandler.handleMotionEvent(event) return true } } @@ -168,9 +168,9 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi override fun onDoubleTapEvent(event: MotionEvent): Boolean { if (event.actionMasked == MotionEvent.ACTION_UP) { nextDownIsDoubleTap = false - GodotInputHandler.handleMotionEvent(event) + inputHandler.handleMotionEvent(event) } else if (event.actionMasked == MotionEvent.ACTION_MOVE && !panningAndScalingEnabled) { - GodotInputHandler.handleMotionEvent(event) + inputHandler.handleMotionEvent(event) } return true @@ -191,7 +191,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi if (dragInProgress || lastDragX != 0.0f || lastDragY != 0.0f) { if (originEvent != null) { // Cancel the drag - GodotInputHandler.handleMotionEvent(originEvent, MotionEvent.ACTION_CANCEL) + inputHandler.handleMotionEvent(originEvent, MotionEvent.ACTION_CANCEL) } dragInProgress = false lastDragX = 0.0f @@ -202,12 +202,12 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi val x = terminusEvent.x val y = terminusEvent.y if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress && !dragInProgress) { - GodotLib.pan(x, y, distanceX / 5f, distanceY / 5f) + inputHandler.handlePanEvent(x, y, distanceX / 5f, distanceY / 5f) } else if (!scaleInProgress) { dragInProgress = true lastDragX = terminusEvent.getX(0) lastDragY = terminusEvent.getY(0) - GodotInputHandler.handleMotionEvent(terminusEvent) + inputHandler.handleMotionEvent(terminusEvent) } return true } @@ -218,11 +218,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi } if (detector.scaleFactor >= 0.8f && detector.scaleFactor != 1f && detector.scaleFactor <= 1.2f) { - GodotLib.magnify( - detector.focusX, - detector.focusY, - detector.scaleFactor - ) + inputHandler.handleMagnifyEvent(detector.focusX, detector.focusY, detector.scaleFactor) } return true } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index 83e76e49c9..889618914d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -75,7 +75,9 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { */ private int lastSeenToolType = MotionEvent.TOOL_TYPE_UNKNOWN; - private static int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS; + private int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS; + + private boolean dispatchInputToRenderThread = false; public GodotInputHandler(GodotRenderView godotView) { final Context context = godotView.getView().getContext(); @@ -83,7 +85,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { mInputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE); mInputManager.registerInputDeviceListener(this, null); - this.godotGestureHandler = new GodotGestureHandler(); + this.godotGestureHandler = new GodotGestureHandler(this); this.gestureDetector = new GestureDetector(context, godotGestureHandler); this.gestureDetector.setIsLongpressEnabled(false); this.scaleGestureDetector = new ScaleGestureDetector(context, godotGestureHandler); @@ -109,6 +111,22 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { } /** + * Specifies whether input should be dispatch on the UI thread or on the Render thread. + * @param enable true to dispatch input on the Render thread, false to dispatch input on the UI thread + */ + public void enableInputDispatchToRenderThread(boolean enable) { + this.dispatchInputToRenderThread = enable; + } + + /** + * @return true if input must be dispatched from the render thread. If false, input is + * dispatched from the UI thread. + */ + private boolean shouldDispatchInputToRenderThread() { + return dispatchInputToRenderThread; + } + + /** * On Wear OS devices, sets which axis of the mouse wheel rotary input is mapped to. This is 1 (vertical axis) by default. */ public void setRotaryInputAxis(int axis) { @@ -151,14 +169,14 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { if (mJoystickIds.indexOfKey(deviceId) >= 0) { final int button = getGodotButton(keyCode); final int godotJoyId = mJoystickIds.get(deviceId); - GodotLib.joybutton(godotJoyId, button, false); + handleJoystickButtonEvent(godotJoyId, button, false); } } else { // getKeyCode(): The physical key that was pressed. final int physical_keycode = event.getKeyCode(); final int unicode = event.getUnicodeChar(); final int key_label = event.getDisplayLabel(); - GodotLib.key(physical_keycode, unicode, key_label, false, event.getRepeatCount() > 0); + handleKeyEvent(physical_keycode, unicode, key_label, false, event.getRepeatCount() > 0); }; return true; @@ -187,13 +205,13 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { if (mJoystickIds.indexOfKey(deviceId) >= 0) { final int button = getGodotButton(keyCode); final int godotJoyId = mJoystickIds.get(deviceId); - GodotLib.joybutton(godotJoyId, button, true); + handleJoystickButtonEvent(godotJoyId, button, true); } } else { final int physical_keycode = event.getKeyCode(); final int unicode = event.getUnicodeChar(); final int key_label = event.getDisplayLabel(); - GodotLib.key(physical_keycode, unicode, key_label, true, event.getRepeatCount() > 0); + handleKeyEvent(physical_keycode, unicode, key_label, true, event.getRepeatCount() > 0); } return true; @@ -248,7 +266,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { if (joystick.axesValues.indexOfKey(axis) < 0 || (float)joystick.axesValues.get(axis) != value) { // save value to prevent repeats joystick.axesValues.put(axis, value); - GodotLib.joyaxis(godotJoyId, i, value); + handleJoystickAxisEvent(godotJoyId, i, value); } } @@ -258,7 +276,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { if (joystick.hatX != hatX || joystick.hatY != hatY) { joystick.hatX = hatX; joystick.hatY = hatY; - GodotLib.joyhat(godotJoyId, hatX, hatY); + handleJoystickHatEvent(godotJoyId, hatX, hatY); } } return true; @@ -284,10 +302,12 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { int[] deviceIds = mInputManager.getInputDeviceIds(); for (int deviceId : deviceIds) { InputDevice device = mInputManager.getInputDevice(deviceId); - if (DEBUG) { - Log.v(TAG, String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); + if (device != null) { + if (DEBUG) { + Log.v(TAG, String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); + } + onInputDeviceAdded(deviceId); } - onInputDeviceAdded(deviceId); } } @@ -364,7 +384,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { } mJoysticksDevices.put(deviceId, joystick); - GodotLib.joyconnectionchanged(id, true, joystick.name); + handleJoystickConnectionChangedEvent(id, true, joystick.name); } @Override @@ -378,7 +398,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { final int godotJoyId = mJoystickIds.get(deviceId); mJoystickIds.delete(deviceId); mJoysticksDevices.delete(deviceId); - GodotLib.joyconnectionchanged(godotJoyId, false, ""); + handleJoystickConnectionChangedEvent(godotJoyId, false, ""); } @Override @@ -482,15 +502,15 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { } } - static boolean handleMotionEvent(final MotionEvent event) { + boolean handleMotionEvent(final MotionEvent event) { return handleMotionEvent(event, event.getActionMasked()); } - static boolean handleMotionEvent(final MotionEvent event, int eventActionOverride) { + boolean handleMotionEvent(final MotionEvent event, int eventActionOverride) { return handleMotionEvent(event, eventActionOverride, false); } - static boolean handleMotionEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) { + boolean handleMotionEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) { if (isMouseEvent(event)) { return handleMouseEvent(event, eventActionOverride, doubleTap); } @@ -523,19 +543,19 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { return (float)Math.cos(orientation) * tiltMult; } - static boolean handleMouseEvent(final MotionEvent event) { + boolean handleMouseEvent(final MotionEvent event) { return handleMouseEvent(event, event.getActionMasked()); } - static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride) { + boolean handleMouseEvent(final MotionEvent event, int eventActionOverride) { return handleMouseEvent(event, eventActionOverride, false); } - static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) { + boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) { return handleMouseEvent(event, eventActionOverride, event.getButtonState(), doubleTap); } - static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, int buttonMaskOverride, boolean doubleTap) { + boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, int buttonMaskOverride, boolean doubleTap) { final float x = event.getX(); final float y = event.getY(); @@ -564,11 +584,11 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { return handleMouseEvent(eventActionOverride, buttonMaskOverride, x, y, horizontalFactor, verticalFactor, doubleTap, sourceMouseRelative, pressure, getEventTiltX(event), getEventTiltY(event)); } - static boolean handleMouseEvent(int eventAction, boolean sourceMouseRelative) { + boolean handleMouseEvent(int eventAction, boolean sourceMouseRelative) { return handleMouseEvent(eventAction, 0, 0f, 0f, 0f, 0f, false, sourceMouseRelative, 1f, 0f, 0f); } - static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) { + boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) { // Fix the buttonsMask switch (eventAction) { case MotionEvent.ACTION_CANCEL: @@ -584,6 +604,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { break; } + final int updatedButtonsMask = buttonsMask; // We don't handle ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE events as they typically // follow ACTION_DOWN and ACTION_UP events. As such, handling them would result in duplicate // stream of events to the engine. @@ -596,22 +617,26 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { case MotionEvent.ACTION_HOVER_MOVE: case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_SCROLL: { - GodotLib.dispatchMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY); + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY)); + } else { + GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY); + } return true; } } return false; } - static boolean handleTouchEvent(final MotionEvent event) { + boolean handleTouchEvent(final MotionEvent event) { return handleTouchEvent(event, event.getActionMasked()); } - static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride) { + boolean handleTouchEvent(final MotionEvent event, int eventActionOverride) { return handleTouchEvent(event, eventActionOverride, false); } - static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) { + boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) { final int pointerCount = event.getPointerCount(); if (pointerCount == 0) { return true; @@ -636,10 +661,70 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_DOWN: { - GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap); + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap)); + } else { + GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap); + } return true; } } return false; } + + void handleMagnifyEvent(float x, float y, float factor) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.magnify(x, y, factor)); + } else { + GodotLib.magnify(x, y, factor); + } + } + + void handlePanEvent(float x, float y, float deltaX, float deltaY) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.pan(x, y, deltaX, deltaY)); + } else { + GodotLib.pan(x, y, deltaX, deltaY); + } + } + + private void handleJoystickButtonEvent(int device, int button, boolean pressed) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.joybutton(device, button, pressed)); + } else { + GodotLib.joybutton(device, button, pressed); + } + } + + private void handleJoystickAxisEvent(int device, int axis, float value) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.joyaxis(device, axis, value)); + } else { + GodotLib.joyaxis(device, axis, value); + } + } + + private void handleJoystickHatEvent(int device, int hatX, int hatY) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.joyhat(device, hatX, hatY)); + } else { + GodotLib.joyhat(device, hatX, hatY); + } + } + + private void handleJoystickConnectionChangedEvent(int device, boolean connected, String name) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.joyconnectionchanged(device, connected, name)); + } else { + GodotLib.joyconnectionchanged(device, connected, name); + } + } + + void handleKeyEvent(int physicalKeycode, int unicode, int keyLabel, boolean pressed, boolean echo) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo)); + } else { + GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo); + } + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java index 06b565c30f..e545669970 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java @@ -93,8 +93,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene @Override public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) { for (int i = 0; i < count; ++i) { - GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, true, false); - GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, false, false); + mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_DEL, 0, 0, true, false); + mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_DEL, 0, 0, false, false); if (mHasSelection) { mHasSelection = false; @@ -115,8 +115,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene // Return keys are handled through action events continue; } - GodotLib.key(0, character, 0, true, false); - GodotLib.key(0, character, 0, false, false); + mRenderView.getInputHandler().handleKeyEvent(0, character, 0, true, false); + mRenderView.getInputHandler().handleKeyEvent(0, character, 0, false, false); } } @@ -127,18 +127,16 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene if (characters != null) { for (int i = 0; i < characters.length(); i++) { final int character = characters.codePointAt(i); - GodotLib.key(0, character, 0, true, false); - GodotLib.key(0, character, 0, false, false); + mRenderView.getInputHandler().handleKeyEvent(0, character, 0, true, false); + mRenderView.getInputHandler().handleKeyEvent(0, character, 0, false, false); } } } if (pActionID == EditorInfo.IME_ACTION_DONE) { // Enter key has been pressed - mRenderView.queueOnRenderThread(() -> { - GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, true, false); - GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, false, false); - }); + mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_ENTER, 0, 0, true, false); + mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_ENTER, 0, 0, false, false); mRenderView.getView().requestFocus(); return true; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt index 69748c0a8d..d39f2309b8 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt @@ -81,7 +81,8 @@ fun beginBenchmarkMeasure(scope: String, label: String) { * * * Note: Only enabled on 'editorDev' build variant. */ -fun endBenchmarkMeasure(scope: String, label: String) { +@JvmOverloads +fun endBenchmarkMeasure(scope: String, label: String, dumpBenchmark: Boolean = false) { if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { return } @@ -93,6 +94,10 @@ fun endBenchmarkMeasure(scope: String, label: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Trace.endAsyncSection("[$scope] $label", 0) } + + if (dumpBenchmark) { + dumpBenchmark() + } } /** @@ -102,11 +107,11 @@ fun endBenchmarkMeasure(scope: String, label: String) { * * Note: Only enabled on 'editorDev' build variant. */ @JvmOverloads -fun dumpBenchmark(fileAccessHandler: FileAccessHandler?, filepath: String? = benchmarkFile) { +fun dumpBenchmark(fileAccessHandler: FileAccessHandler? = null, filepath: String? = benchmarkFile) { if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { return } - if (!useBenchmark) { + if (!useBenchmark || benchmarkTracker.isEmpty()) { return } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 40068745d6..87d4281c5a 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -67,6 +67,13 @@ static AndroidInputHandler *input_handler = nullptr; static GodotJavaWrapper *godot_java = nullptr; static GodotIOJavaWrapper *godot_io_java = nullptr; +enum StartupStep { + STEP_TERMINATED = -1, + STEP_SETUP, + STEP_SHOW_LOGO, + STEP_STARTED +}; + static SafeNumeric<int> step; // Shared between UI and render threads static Size2 new_size; @@ -76,7 +83,7 @@ static Vector3 magnetometer; static Vector3 gyroscope; static void _terminate(JNIEnv *env, bool p_restart = false) { - step.set(-1); // Ensure no further steps are attempted and no further events are sent + step.set(STEP_TERMINATED); // Ensure no further steps are attempted and no further events are sent // lets cleanup // Unregister android plugins @@ -203,7 +210,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j os_android->set_display_size(Size2i(p_width, p_height)); // No need to reset the surface during startup - if (step.get() > 0) { + if (step.get() > STEP_SETUP) { if (p_surface) { ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface); os_android->set_native_window(native_window); @@ -216,7 +223,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface) { if (os_android) { - if (step.get() == 0) { + if (step.get() == STEP_SETUP) { // During startup if (p_surface) { ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface); @@ -230,7 +237,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) { - if (step.get() == 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -244,20 +251,26 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ttsCallback(JNIEnv *e } JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) { - if (step.get() == -1) { + if (step.get() == STEP_TERMINATED) { return true; } - if (step.get() == 0) { + if (step.get() == STEP_SETUP) { // Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id, // but for Godot purposes, the main thread is the one running the game loop - Main::setup2(); + Main::setup2(false); // The logo is shown in the next frame otherwise we run into rendering issues input_handler = new AndroidInputHandler(); step.increment(); return true; } - if (step.get() == 1) { + if (step.get() == STEP_SHOW_LOGO) { + Main::setup_boot_logo(); + step.increment(); + return true; + } + + if (step.get() == STEP_STARTED) { if (Main::start() != EXIT_SUCCESS) { return true; // should exit instead and print the error } @@ -283,7 +296,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, // Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JNIEnv *env, jclass clazz, jint p_event_type, jint p_button_mask, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y, jboolean p_double_click, jboolean p_source_mouse_relative, jfloat p_pressure, jfloat p_tilt_x, jfloat p_tilt_y) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -292,7 +305,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JN // Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchTouchEvent(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint pointer_count, jfloatArray position, jboolean p_double_tap) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -313,7 +326,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchTouchEvent(JN // Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnify(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_factor) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } input_handler->process_magnify(Point2(p_x, p_y), p_factor); @@ -321,7 +334,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnify(JNIEnv *env, // Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_pan(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } input_handler->process_pan(Point2(p_x, p_y), Vector2(p_delta_x, p_delta_y)); @@ -329,7 +342,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_pan(JNIEnv *env, jcla // Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -344,7 +357,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env // Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -359,7 +372,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, // Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -396,7 +409,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged( // Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_physical_keycode, jint p_unicode, jint p_key_label, jboolean p_pressed, jboolean p_echo) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } input_handler->process_key_event(p_physical_keycode, p_unicode, p_key_label, p_pressed, p_echo); @@ -419,7 +432,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -427,7 +440,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -516,7 +529,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -528,7 +541,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz) { - if (step.get() <= 0) { + if (step.get() <= STEP_SETUP) { return; } @@ -536,4 +549,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED); } } + +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering) { + if (Input::get_singleton()) { + Input::get_singleton()->set_use_accumulated_input(p_use_accumulated_input); + Input::get_singleton()->set_use_input_buffering(p_use_input_buffering); + } +} } diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index f32ffc291a..852c475e7e 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -69,6 +69,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering); } #endif // JAVA_GODOT_LIB_JNI_H diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 341cc517e3..7bdc75db29 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -2049,9 +2049,14 @@ void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct z PointerData &pd = ss->pointer_data_buffer; + WindowState *ws = wl_surface_get_window_state(ss->pointed_surface); + ERR_FAIL_NULL(ws); + pd.relative_motion.x = wl_fixed_to_double(dx); pd.relative_motion.y = wl_fixed_to_double(dy); + pd.relative_motion *= window_state_get_scale_factor(ws); + pd.relative_motion_time = uptime_lo; } diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index a1a91345ac..da45391995 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -568,23 +568,7 @@ void DisplayServerMacOS::menu_callback(id p_sender) { } GodotMenuItem *value = [p_sender representedObject]; - if (value) { - if (value->max_states > 0) { - value->state++; - if (value->state >= value->max_states) { - value->state = 0; - } - } - - if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) { - if ([p_sender state] == NSControlStateValueOff) { - [p_sender setState:NSControlStateValueOn]; - } else { - [p_sender setState:NSControlStateValueOff]; - } - } - if (value->callback.is_valid()) { MenuCall mc; mc.tag = value->meta; diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm index 942c351ac0..eebaed0eaf 100644 --- a/platform/macos/godot_main_macos.mm +++ b/platform/macos/godot_main_macos.mm @@ -41,8 +41,8 @@ int main(int argc, char **argv) { #if defined(VULKAN_ENABLED) - // MoltenVK - enable full component swizzling support. - setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); + setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); // MoltenVK - enable full component swizzling support. + setenv("MVK_CONFIG_SWAPCHAIN_MIN_MAG_FILTER_USE_NEAREST", "0", 1); // MoltenVK - use linear surface scaling. TODO: remove when full DPI scaling is implemented. #endif #if defined(SANITIZERS_ENABLED) diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h index b6e2d41c08..e1af317259 100644 --- a/platform/macos/godot_menu_item.h +++ b/platform/macos/godot_menu_item.h @@ -52,6 +52,7 @@ enum GlobalMenuCheckType { Callable hover_callback; Variant meta; GlobalMenuCheckType checkable_type; + bool checked; int max_states; int state; Ref<Image> img; diff --git a/platform/macos/godot_menu_item.mm b/platform/macos/godot_menu_item.mm index 30dac9be9b..479542113a 100644 --- a/platform/macos/godot_menu_item.mm +++ b/platform/macos/godot_menu_item.mm @@ -31,4 +31,18 @@ #include "godot_menu_item.h" @implementation GodotMenuItem + +- (id)init { + self = [super init]; + + self->callback = Callable(); + self->key_callback = Callable(); + self->checkable_type = GlobalMenuCheckType::CHECKABLE_TYPE_NONE; + self->checked = false; + self->max_states = 0; + self->state = 0; + + return self; +} + @end diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm index 1ae1137ca0..802d58dc26 100644 --- a/platform/macos/native_menu_macos.mm +++ b/platform/macos/native_menu_macos.mm @@ -373,12 +373,7 @@ int NativeMenuMacOS::add_submenu_item(const RID &p_rid, const String &p_label, c menu_item = [md->menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; GodotMenuItem *obj = [[GodotMenuItem alloc] init]; - obj->callback = Callable(); - obj->key_callback = Callable(); obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; - obj->max_states = 0; - obj->state = 0; [menu_item setRepresentedObject:obj]; [md_sub->menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]]; @@ -417,9 +412,6 @@ int NativeMenuMacOS::add_item(const RID &p_rid, const String &p_label, const Cal obj->callback = p_callback; obj->key_callback = p_key_callback; obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; - obj->max_states = 0; - obj->state = 0; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } @@ -438,8 +430,6 @@ int NativeMenuMacOS::add_check_item(const RID &p_rid, const String &p_label, con obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; - obj->max_states = 0; - obj->state = 0; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } @@ -457,9 +447,6 @@ int NativeMenuMacOS::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_ico obj->callback = p_callback; obj->key_callback = p_key_callback; obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; - obj->max_states = 0; - obj->state = 0; DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); @@ -489,8 +476,6 @@ int NativeMenuMacOS::add_icon_check_item(const RID &p_rid, const Ref<Texture2D> obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; - obj->max_states = 0; - obj->state = 0; DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); @@ -520,8 +505,6 @@ int NativeMenuMacOS::add_radio_check_item(const RID &p_rid, const String &p_labe obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; - obj->max_states = 0; - obj->state = 0; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } @@ -540,8 +523,6 @@ int NativeMenuMacOS::add_icon_radio_check_item(const RID &p_rid, const Ref<Textu obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; - obj->max_states = 0; - obj->state = 0; DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); @@ -570,7 +551,6 @@ int NativeMenuMacOS::add_multistate_item(const RID &p_rid, const String &p_label obj->callback = p_callback; obj->key_callback = p_key_callback; obj->meta = p_tag; - obj->checkable_type = CHECKABLE_TYPE_NONE; obj->max_states = p_max_states; obj->state = p_default_state; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; @@ -640,7 +620,10 @@ bool NativeMenuMacOS::is_item_checked(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx >= item_start + item_count, false); const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx]; if (menu_item) { - return ([menu_item state] == NSControlStateValueOn); + const GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->checked; + } } return false; } @@ -958,10 +941,14 @@ void NativeMenuMacOS::set_item_checked(const RID &p_rid, int p_idx, bool p_check ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx]; if (menu_item) { - if (p_checked) { - [menu_item setState:NSControlStateValueOn]; - } else { - [menu_item setState:NSControlStateValueOff]; + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + obj->checked = p_checked; + if (p_checked) { + [menu_item setState:NSControlStateValueOn]; + } else { + [menu_item setState:NSControlStateValueOff]; + } } } } diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp index dd986e650c..b24c6cb1fd 100644 --- a/platform/web/audio_driver_web.cpp +++ b/platform/web/audio_driver_web.cpp @@ -33,6 +33,8 @@ #include "godot_audio.h" #include "core/config/project_settings.h" +#include "core/object/object.h" +#include "scene/main/node.h" #include "servers/audio/audio_stream.h" #include <emscripten.h> @@ -51,6 +53,33 @@ void AudioDriverWeb::_latency_update_callback(float p_latency) { AudioDriverWeb::audio_context.output_latency = p_latency; } +void AudioDriverWeb::_sample_playback_finished_callback(const char *p_playback_object_id) { + const ObjectID playback_id = ObjectID(String::to_int(p_playback_object_id)); + + Object *playback_object = ObjectDB::get_instance(playback_id); + if (playback_object == nullptr) { + return; + } + Ref<AudioSamplePlayback> playback = Object::cast_to<AudioSamplePlayback>(playback_object); + if (playback.is_null()) { + return; + } + + Object *player_object = ObjectDB::get_instance(playback->player_id); + if (player_object == nullptr) { + return; + } + Node *player = Object::cast_to<Node>(player_object); + if (player == nullptr) { + return; + } + + const StringName finished = SNAME("finished"); + if (player->has_signal(finished)) { + player->emit_signal(finished); + } +} + void AudioDriverWeb::_audio_driver_process(int p_from, int p_samples) { int32_t *stream_buffer = reinterpret_cast<int32_t *>(output_rb); const int max_samples = memarr_len(output_rb); @@ -132,6 +161,9 @@ Error AudioDriverWeb::init() { if (!input_rb) { return ERR_OUT_OF_MEMORY; } + + godot_audio_sample_set_finished_callback(&_sample_playback_finished_callback); + return OK; } diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h index 298ad90fae..46c5ce4de1 100644 --- a/platform/web/audio_driver_web.h +++ b/platform/web/audio_driver_web.h @@ -58,6 +58,7 @@ private: WASM_EXPORT static void _state_change_callback(int p_state); WASM_EXPORT static void _latency_update_callback(float p_latency); + WASM_EXPORT static void _sample_playback_finished_callback(const char *p_playback_object_id); static AudioDriverWeb *singleton; diff --git a/platform/web/godot_audio.h b/platform/web/godot_audio.h index 8bebbcf7de..dd5bec00cf 100644 --- a/platform/web/godot_audio.h +++ b/platform/web/godot_audio.h @@ -57,6 +57,7 @@ extern void godot_audio_sample_set_pause(const char *p_playback_object_id, bool extern int godot_audio_sample_is_active(const char *p_playback_object_id); extern void godot_audio_sample_update_pitch_scale(const char *p_playback_object_id, float p_pitch_scale); extern void godot_audio_sample_set_volumes_linear(const char *p_playback_object_id, int *p_buses_buf, int p_buses_size, float *p_volumes_buf, int p_volumes_size); +extern void godot_audio_sample_set_finished_callback(void (*p_callback)(const char *)); extern void godot_audio_sample_bus_set_count(int p_count); extern void godot_audio_sample_bus_remove(int p_index); diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js index 531dbdaeab..0b16b07261 100644 --- a/platform/web/js/libs/library_godot_audio.js +++ b/platform/web/js/libs/library_godot_audio.js @@ -687,9 +687,15 @@ class SampleNode { } switch (self.getSample().loopMode) { - case 'disabled': + case 'disabled': { + const id = this.id; self.stop(); - break; + if (GodotAudio.sampleFinishedCallback != null) { + const idCharPtr = GodotRuntime.allocString(id); + GodotAudio.sampleFinishedCallback(idCharPtr); + GodotRuntime.free(idCharPtr); + } + } break; case 'forward': case 'backward': self.restart(); @@ -1090,6 +1096,12 @@ const _GodotAudio = { busSolo: null, Bus, + /** + * Callback to signal that a sample has finished. + * @type {(playbackObjectIdPtr: number) => void | null} + */ + sampleFinishedCallback: null, + /** @type {AudioContext} */ ctx: null, input: null, @@ -1764,6 +1776,17 @@ const _GodotAudio = { godot_audio_sample_bus_set_mute: function (bus, enable) { GodotAudio.set_sample_bus_mute(bus, Boolean(enable)); }, + + godot_audio_sample_set_finished_callback__proxy: 'sync', + godot_audio_sample_set_finished_callback__sig: 'vi', + /** + * Sets the finished callback + * @param {Number} callbackPtr Finished callback pointer + * @returns {void} + */ + godot_audio_sample_set_finished_callback: function (callbackPtr) { + GodotAudio.sampleFinishedCallback = GodotRuntime.get_func(callbackPtr); + }, }; autoAddDeps(_GodotAudio, '$GodotAudio'); diff --git a/platform/web/js/libs/library_godot_input.js b/platform/web/js/libs/library_godot_input.js index 7ea89d553f..6e3b97023d 100644 --- a/platform/web/js/libs/library_godot_input.js +++ b/platform/web/js/libs/library_godot_input.js @@ -112,6 +112,7 @@ const GodotIME = { ime.style.top = '0px'; ime.style.width = '100%'; ime.style.height = '40px'; + ime.style.pointerEvents = 'none'; ime.style.display = 'none'; ime.contentEditable = 'true'; diff --git a/platform/web/js/libs/library_godot_javascript_singleton.js b/platform/web/js/libs/library_godot_javascript_singleton.js index b17fde1544..6bb69bca95 100644 --- a/platform/web/js/libs/library_godot_javascript_singleton.js +++ b/platform/web/js/libs/library_godot_javascript_singleton.js @@ -81,11 +81,16 @@ const GodotJSWrapper = { case 0: return null; case 1: - return !!GodotRuntime.getHeapValue(val, 'i64'); - case 2: - return GodotRuntime.getHeapValue(val, 'i64'); + return Boolean(GodotRuntime.getHeapValue(val, 'i64')); + case 2: { + // `heap_value` may be a bigint. + const heap_value = GodotRuntime.getHeapValue(val, 'i64'); + return heap_value >= Number.MIN_SAFE_INTEGER && heap_value <= Number.MAX_SAFE_INTEGER + ? Number(heap_value) + : heap_value; + } case 3: - return GodotRuntime.getHeapValue(val, 'double'); + return Number(GodotRuntime.getHeapValue(val, 'double')); case 4: return GodotRuntime.parseString(GodotRuntime.getHeapValue(val, '*')); case 24: // OBJECT @@ -110,6 +115,9 @@ const GodotJSWrapper = { } GodotRuntime.setHeapValue(p_exchange, p_val, 'double'); return 3; // FLOAT + } else if (type === 'bigint') { + GodotRuntime.setHeapValue(p_exchange, p_val, 'i64'); + return 2; // INT } else if (type === 'string') { const c_str = GodotRuntime.allocString(p_val); GodotRuntime.setHeapValue(p_exchange, c_str, '*'); diff --git a/platform/web/web_main.cpp b/platform/web/web_main.cpp index 04513f6d57..d0c3bd7c0e 100644 --- a/platform/web/web_main.cpp +++ b/platform/web/web_main.cpp @@ -35,6 +35,8 @@ #include "core/config/engine.h" #include "core/io/resource_loader.h" #include "main/main.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" // SceneTree only forward declares it. #include <emscripten/emscripten.h> #include <stdlib.h> @@ -130,7 +132,7 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) { if (Engine::get_singleton()->is_project_manager_hint() && FileAccess::exists("/tmp/preload.zip")) { PackedStringArray ps; ps.push_back("/tmp/preload.zip"); - os->get_main_loop()->emit_signal(SNAME("files_dropped"), ps, -1); + SceneTree::get_singleton()->get_root()->emit_signal(SNAME("files_dropped"), ps); } #endif emscripten_set_main_loop(main_loop_callback, -1, false); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 8d26a705a9..c1b3540f68 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -38,6 +38,7 @@ #include "core/version.h" #include "drivers/png/png_driver_common.h" #include "main/main.h" +#include "scene/resources/texture.h" #if defined(VULKAN_ENABLED) #include "rendering_context_driver_vulkan_windows.h" @@ -3807,9 +3808,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_ACTIVATE: { // Activation can happen just after the window has been created, even before the callbacks are set. // Therefore, it's safer to defer the delivery of the event. - if (!windows[window_id].activate_timer_id) { - windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); - } + // It's important to set an nIDEvent different from the SetTimer for move_timer_id because + // if the same nIDEvent is passed, the timer is replaced and the same timer_id is returned. + windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam); return 0; } break; @@ -4727,7 +4728,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_ENTERSIZEMOVE: { Input::get_singleton()->release_pressed_events(); - windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); + windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_MOVE_REDRAW, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); } break; case WM_EXITSIZEMOVE: { KillTimer(windows[window_id].hWnd, windows[window_id].move_timer_id); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 382f18c239..c2f4de7d81 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -332,6 +332,11 @@ class DisplayServerWindows : public DisplayServer { String tablet_driver; Vector<String> tablet_drivers; + enum TimerID { + TIMER_ID_MOVE_REDRAW = 1, + TIMER_ID_WINDOW_ACTIVATION = 2, + }; + enum { KEY_EVENT_BUFFER_SIZE = 512 }; diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp index d9dc28e9d9..fde55918e4 100644 --- a/platform/windows/native_menu_windows.cpp +++ b/platform/windows/native_menu_windows.cpp @@ -81,22 +81,6 @@ void NativeMenuWindows::_menu_activate(HMENU p_menu, int p_index) const { if (GetMenuItemInfoW(md->menu, p_index, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { - if (item_data->max_states > 0) { - item_data->state++; - if (item_data->state >= item_data->max_states) { - item_data->state = 0; - } - } - - if (item_data->checkable_type == CHECKABLE_TYPE_CHECK_BOX) { - if ((item.fState & MFS_CHECKED) == MFS_CHECKED) { - item.fState &= ~MFS_CHECKED; - } else { - item.fState |= MFS_CHECKED; - } - SetMenuItemInfoW(md->menu, p_index, true, &item); - } - if (item_data->callback.is_valid()) { Variant ret; Callable::CallError ce; @@ -619,9 +603,12 @@ bool NativeMenuWindows::is_item_checked(const RID &p_rid, int p_idx) const { MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); - item.fMask = MIIM_STATE; + item.fMask = MIIM_STATE | MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { - return (item.fState & MFS_CHECKED) == MFS_CHECKED; + MenuItemData *item_data = (MenuItemData *)item.dwItemData; + if (item_data) { + return item_data->checked; + } } return false; } @@ -861,12 +848,16 @@ void NativeMenuWindows::set_item_checked(const RID &p_rid, int p_idx, bool p_che MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); - item.fMask = MIIM_STATE; + item.fMask = MIIM_STATE | MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { - if (p_checked) { - item.fState |= MFS_CHECKED; - } else { - item.fState &= ~MFS_CHECKED; + MenuItemData *item_data = (MenuItemData *)item.dwItemData; + if (item_data) { + item_data->checked = p_checked; + if (p_checked) { + item.fState |= MFS_CHECKED; + } else { + item.fState &= ~MFS_CHECKED; + } } SetMenuItemInfoW(md->menu, p_idx, true, &item); } diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h index 5c4aaa52c8..235a4b332a 100644 --- a/platform/windows/native_menu_windows.h +++ b/platform/windows/native_menu_windows.h @@ -51,6 +51,7 @@ class NativeMenuWindows : public NativeMenu { Callable callback; Variant meta; GlobalMenuCheckType checkable_type; + bool checked = false; int max_states = 0; int state = 0; Ref<Image> img; |
