summaryrefslogtreecommitdiffstats
path: root/platform/android/java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/java')
-rw-r--r--platform/android/java/app/config.gradle2
-rw-r--r--platform/android/java/build.gradle58
-rw-r--r--platform/android/java/editor/build.gradle92
-rw-r--r--platform/android/java/editor/src/debug/res/values/strings.xml4
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt3
-rw-r--r--platform/android/java/lib/build.gradle23
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java61
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt122
11 files changed, 303 insertions, 78 deletions
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 4bac6c814a..acc6b22c3a 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -135,7 +135,7 @@ ext.generateGodotLibraryVersion = { List<String> requiredKeys ->
String statusValue = map["status"]
if (statusValue == null) {
statusCode = 0
- } else if (statusValue.startsWith("alpha")) {
+ } else if (statusValue.startsWith("alpha") || statusValue.startsWith("dev")) {
statusCode = 1
} else if (statusValue.startsWith("beta")) {
statusCode = 2
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 10c28a00b2..f94454e2a7 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -9,7 +9,7 @@ buildscript {
dependencies {
classpath libraries.androidGradlePlugin
classpath libraries.kotlinGradlePlugin
- classpath 'io.github.gradle-nexus:publish-plugin:1.1.0'
+ classpath 'io.github.gradle-nexus:publish-plugin:1.3.0'
}
}
@@ -38,9 +38,7 @@ ext {
supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"]
supportedFlavors = ["editor", "template"]
supportedFlavorsBuildTypes = [
- // The editor can't be used with target=release as debugging tools are then not
- // included, and it would crash on errors instead of reporting them.
- "editor": ["dev", "debug"],
+ "editor": ["dev", "debug", "release"],
"template": ["dev", "debug", "release"]
]
@@ -54,6 +52,7 @@ ext {
def rootDir = "../../.."
def binDir = "$rootDir/bin/"
+def androidEditorBuildsDir = "$binDir/android_editor_builds/"
def getSconsTaskName(String flavor, String buildType, String abi) {
return "compileGodotNativeLibs" + flavor.capitalize() + buildType.capitalize() + abi.capitalize()
@@ -221,18 +220,46 @@ def isAndroidStudio() {
return sysProps != null && sysProps['idea.platform.prefix'] != null
}
-task copyEditorDebugBinaryToBin(type: Copy) {
+task copyEditorReleaseApkToBin(type: Copy) {
+ dependsOn ':editor:assembleRelease'
+ from('editor/build/outputs/apk/release')
+ into(androidEditorBuildsDir)
+ include('android_editor-release*.apk')
+}
+
+task copyEditorReleaseAabToBin(type: Copy) {
+ dependsOn ':editor:bundleRelease'
+ from('editor/build/outputs/bundle/release')
+ into(androidEditorBuildsDir)
+ include('android_editor-release*.aab')
+}
+
+task copyEditorDebugApkToBin(type: Copy) {
dependsOn ':editor:assembleDebug'
from('editor/build/outputs/apk/debug')
- into(binDir)
- include('android_editor.apk')
+ into(androidEditorBuildsDir)
+ include('android_editor-debug.apk')
}
-task copyEditorDevBinaryToBin(type: Copy) {
+task copyEditorDebugAabToBin(type: Copy) {
+ dependsOn ':editor:bundleDebug'
+ from('editor/build/outputs/bundle/debug')
+ into(androidEditorBuildsDir)
+ include('android_editor-debug.aab')
+}
+
+task copyEditorDevApkToBin(type: Copy) {
dependsOn ':editor:assembleDev'
from('editor/build/outputs/apk/dev')
- into(binDir)
- include('android_editor_dev.apk')
+ into(androidEditorBuildsDir)
+ include('android_editor-dev.apk')
+}
+
+task copyEditorDevAabToBin(type: Copy) {
+ dependsOn ':editor:bundleDev'
+ from('editor/build/outputs/bundle/dev')
+ into(androidEditorBuildsDir)
+ include('android_editor-dev.aab')
}
/**
@@ -253,7 +280,8 @@ task generateGodotEditor {
&& targetLibs.isDirectory()
&& targetLibs.listFiles() != null
&& targetLibs.listFiles().length > 0) {
- tasks += "copyEditor${target.capitalize()}BinaryToBin"
+ tasks += "copyEditor${target.capitalize()}ApkToBin"
+ tasks += "copyEditor${target.capitalize()}AabToBin"
}
}
@@ -301,9 +329,11 @@ task cleanGodotEditor(type: Delete) {
// Delete the generated binary apks
delete("editor/build/outputs/apk")
- // Delete the Godot editor apks in the Godot bin directory
- delete("$binDir/android_editor.apk")
- delete("$binDir/android_editor_dev.apk")
+ // Delete the generated aab binaries
+ delete("editor/build/outputs/bundle")
+
+ // Delete the Godot editor apks & aabs in the Godot bin directory
+ delete(androidEditorBuildsDir)
}
/**
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index 9152492e9d..38034aa47c 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -13,22 +13,67 @@ dependencies {
}
ext {
- // Build number added as a suffix to the version code, and incremented for each build/upload to
- // the Google Play store.
- // This should be reset on each stable release of Godot.
- editorBuildNumber = 0
+ // Retrieve the build number from the environment variable; default to 0 if none is specified.
+ // The build number is added as a suffix to the version code for upload to the Google Play store.
+ getEditorBuildNumber = { ->
+ int buildNumber = 0
+ String versionStatus = System.getenv("GODOT_VERSION_STATUS")
+ if (versionStatus != null && !versionStatus.isEmpty()) {
+ try {
+ buildNumber = Integer.parseInt(versionStatus.replaceAll("[^0-9]", ""));
+ } catch (NumberFormatException ignored) {
+ buildNumber = 0
+ }
+ }
+
+ return buildNumber
+ }
// Value by which the Godot version code should be offset by to make room for the build number
editorBuildNumberOffset = 100
+
+ // Return the keystore file used for signing the release build.
+ getGodotKeystoreFile = { ->
+ def keyStore = System.getenv("GODOT_ANDROID_SIGN_KEYSTORE")
+ if (keyStore == null) {
+ return null
+ }
+ return file(keyStore)
+ }
+
+ // Return the key alias used for signing the release build.
+ getGodotKeyAlias = { ->
+ def kAlias = System.getenv("GODOT_ANDROID_KEYSTORE_ALIAS")
+ return kAlias
+ }
+
+ // Return the password for the key used for signing the release build.
+ getGodotSigningPassword = { ->
+ def signingPassword = System.getenv("GODOT_ANDROID_SIGN_PASSWORD")
+ return signingPassword
+ }
+
+ // Returns true if the environment variables contains the configuration for signing the release
+ // build.
+ hasReleaseSigningConfigs = { ->
+ def keystoreFile = getGodotKeystoreFile()
+ def keyAlias = getGodotKeyAlias()
+ def signingPassword = getGodotSigningPassword()
+
+ return keystoreFile != null && keystoreFile.isFile()
+ && keyAlias != null && !keyAlias.isEmpty()
+ && signingPassword != null && !signingPassword.isEmpty()
+ }
}
def generateVersionCode() {
int libraryVersionCode = getGodotLibraryVersionCode()
- return (libraryVersionCode * editorBuildNumberOffset) + editorBuildNumber
+ return (libraryVersionCode * editorBuildNumberOffset) + getEditorBuildNumber()
}
def generateVersionName() {
String libraryVersionName = getGodotLibraryVersionName()
- return libraryVersionName + ".$editorBuildNumber"
+ int buildNumber = getEditorBuildNumber()
+ return buildNumber == 0 ? libraryVersionName : libraryVersionName + ".$buildNumber"
}
android {
@@ -45,6 +90,7 @@ android {
targetSdkVersion versions.targetSdk
missingDimensionStrategy 'products', 'editor'
+ setProperty("archivesBaseName", "android_editor")
}
compileOptions {
@@ -56,6 +102,15 @@ android {
jvmTarget = versions.javaVersion
}
+ signingConfigs {
+ release {
+ storeFile getGodotKeystoreFile()
+ storePassword getGodotSigningPassword()
+ keyAlias getGodotKeyAlias()
+ keyPassword getGodotSigningPassword()
+ }
+ }
+
buildTypes {
dev {
initWith debug
@@ -64,15 +119,14 @@ android {
debug {
initWith release
-
- // Need to swap with the release signing config when this is ready for public release.
+ applicationIdSuffix ".debug"
signingConfig signingConfigs.debug
}
release {
- // This buildtype is disabled below.
- // The editor can't be used with target=release only, as debugging tools are then not
- // included, and it would crash on errors instead of reporting them.
+ if (hasReleaseSigningConfigs()) {
+ signingConfig signingConfigs.release
+ }
}
}
@@ -82,20 +136,4 @@ android {
doNotStrip '**/*.so'
}
}
-
- // Disable 'release' buildtype.
- // The editor can't be used with target=release only, as debugging tools are then not
- // included, and it would crash on errors instead of reporting them.
- variantFilter { variant ->
- if (variant.buildType.name == "release") {
- setIgnore(true)
- }
- }
-
- applicationVariants.all { variant ->
- variant.outputs.all { output ->
- def suffix = variant.name == "dev" ? "_dev" : ""
- output.outputFileName = "android_editor${suffix}.apk"
- }
- }
}
diff --git a/platform/android/java/editor/src/debug/res/values/strings.xml b/platform/android/java/editor/src/debug/res/values/strings.xml
new file mode 100644
index 0000000000..09ee2d77e1
--- /dev/null
+++ b/platform/android/java/editor/src/debug/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="godot_editor_name_string">Godot Editor 4 (debug)</string>
+</resources>
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 42ef1436f3..8b6efd572f 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
@@ -113,6 +113,9 @@ open class GodotEditor : FullScreenGodotApp() {
if (args != null && args.isNotEmpty()) {
commandLineParams.addAll(listOf(*args))
}
+ if (BuildConfig.BUILD_TYPE == "dev") {
+ commandLineParams.add("--benchmark")
+ }
}
override fun getCommandLine() = commandLineParams
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 38133ddd51..4340250ad3 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -80,19 +80,11 @@ android {
release.jniLibs.srcDirs = ['libs/release']
// Editor jni library
+ editorRelease.jniLibs.srcDirs = ['libs/tools/release']
editorDebug.jniLibs.srcDirs = ['libs/tools/debug']
editorDev.jniLibs.srcDirs = ['libs/tools/dev']
}
- // Disable 'editorRelease'.
- // The editor can't be used with target=release as debugging tools are then not
- // included, and it would crash on errors instead of reporting them.
- variantFilter { variant ->
- if (variant.name == "editorRelease") {
- setIgnore(true)
- }
- }
-
libraryVariants.all { variant ->
def flavorName = variant.getFlavorName()
if (flavorName == null || flavorName == "") {
@@ -105,9 +97,14 @@ android {
}
boolean devBuild = buildType == "dev"
+ boolean runTests = devBuild
+ boolean productionBuild = !devBuild
+ boolean storeRelease = buildType == "release"
def sconsTarget = flavorName
if (sconsTarget == "template") {
+ // Tests are not supported on template builds
+ runTests = false
switch (buildType) {
case "release":
sconsTarget += "_release"
@@ -135,10 +132,10 @@ android {
def sconsExts = (org.gradle.internal.os.OperatingSystem.current().isWindows()
? [".bat", ".cmd", ".ps1", ".exe"]
: [""])
- logger.lifecycle("Looking for $sconsName executable path")
+ logger.debug("Looking for $sconsName executable path")
for (ext in sconsExts) {
String sconsNameExt = sconsName + ext
- logger.lifecycle("Checking $sconsNameExt")
+ logger.debug("Checking $sconsNameExt")
sconsExecutableFile = org.gradle.internal.os.OperatingSystem.current().findInPath(sconsNameExt)
if (sconsExecutableFile != null) {
// We're done!
@@ -155,7 +152,7 @@ android {
if (sconsExecutableFile == null) {
throw new GradleException("Unable to find executable path for the '$sconsName' command.")
} else {
- logger.lifecycle("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}")
+ logger.debug("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}")
}
for (String selectedAbi : selectedAbis) {
@@ -167,7 +164,7 @@ android {
def taskName = getSconsTaskName(flavorName, buildType, selectedAbi)
tasks.create(name: taskName, type: Exec) {
executable sconsExecutableFile.absolutePath
- args "--directory=${pathToRootDir}", "platform=android", "dev_mode=${devBuild}", "dev_build=${devBuild}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors()
+ args "--directory=${pathToRootDir}", "platform=android", "store_release=${storeRelease}", "production=${productionBuild}", "dev_mode=${devBuild}", "dev_build=${devBuild}", "tests=${runTests}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors()
}
// Schedule the tasks so the generated libs are present before the aar file is packaged.
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 4c47ca9760..748a1c41fd 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -39,6 +39,7 @@ import org.godotengine.godot.io.file.FileAccessHandler;
import org.godotengine.godot.plugin.GodotPlugin;
import org.godotengine.godot.plugin.GodotPluginRegistry;
import org.godotengine.godot.tts.GodotTTS;
+import org.godotengine.godot.utils.BenchmarkUtils;
import org.godotengine.godot.utils.GodotNetUtils;
import org.godotengine.godot.utils.PermissionsUtil;
import org.godotengine.godot.xr.XRMode;
@@ -180,7 +181,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
public GodotIO io;
public GodotNetUtils netUtils;
public GodotTTS tts;
- DirectoryAccessHandler directoryAccessHandler;
+ private DirectoryAccessHandler directoryAccessHandler;
+ private FileAccessHandler fileAccessHandler;
public interface ResultCallback {
void callback(int requestCode, int resultCode, Intent data);
@@ -522,7 +524,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
return cmdline;
} catch (Exception e) {
- e.printStackTrace();
+ // The _cl_ file can be missing with no adverse effect
return new String[0];
}
}
@@ -578,7 +580,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
netUtils = new GodotNetUtils(activity);
Context context = getContext();
directoryAccessHandler = new DirectoryAccessHandler(context);
- FileAccessHandler fileAccessHandler = new FileAccessHandler(context);
+ fileAccessHandler = new FileAccessHandler(context);
mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
@@ -605,6 +607,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
@Override
public void onCreate(Bundle icicle) {
+ BenchmarkUtils.beginBenchmarkMeasure("Godot::onCreate");
super.onCreate(icicle);
final Activity activity = getActivity();
@@ -653,6 +656,18 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
editor.apply();
i++;
+ } else if (command_line[i].equals("--benchmark")) {
+ BenchmarkUtils.setUseBenchmark(true);
+ new_args.add(command_line[i]);
+ } else if (has_extra && command_line[i].equals("--benchmark-file")) {
+ BenchmarkUtils.setUseBenchmark(true);
+ new_args.add(command_line[i]);
+
+ // Retrieve the filepath
+ BenchmarkUtils.setBenchmarkFile(command_line[i + 1]);
+ new_args.add(command_line[i + 1]);
+
+ i++;
} else if (command_line[i].trim().length() != 0) {
new_args.add(command_line[i]);
}
@@ -723,6 +738,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
mCurrentIntent = activity.getIntent();
initializeGodot();
+ BenchmarkUtils.endBenchmarkMeasure("Godot::onCreate");
}
@Override
@@ -928,20 +944,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
// Do something here if sensor accuracy changes.
}
- /*
- @Override public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getKeyCode()==KeyEvent.KEYCODE_BACK) {
- System.out.printf("** BACK REQUEST!\n");
-
- GodotLib.quit();
- return true;
- }
- System.out.printf("** OTHER KEY!\n");
-
- return false;
- }
- */
-
public void onBackPressed() {
boolean shouldQuit = true;
@@ -1153,10 +1155,35 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
@Keep
+ public DirectoryAccessHandler getDirectoryAccessHandler() {
+ return directoryAccessHandler;
+ }
+
+ @Keep
+ public FileAccessHandler getFileAccessHandler() {
+ return fileAccessHandler;
+ }
+
+ @Keep
private int createNewGodotInstance(String[] args) {
if (godotHost != null) {
return godotHost.onNewGodotInstanceRequested(args);
}
return 0;
}
+
+ @Keep
+ private void beginBenchmarkMeasure(String label) {
+ BenchmarkUtils.beginBenchmarkMeasure(label);
+ }
+
+ @Keep
+ private void endBenchmarkMeasure(String label) {
+ BenchmarkUtils.endBenchmarkMeasure(label);
+ }
+
+ @Keep
+ private void dumpBenchmark(String benchmarkFile) {
+ BenchmarkUtils.dumpBenchmark(fileAccessHandler, benchmarkFile);
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
index 330e2ede76..bc7234e2ad 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
@@ -188,10 +188,10 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
try {
Bitmap bitmap = null;
if (!TextUtils.isEmpty(imagePath)) {
- if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) {
+ if (godot.getDirectoryAccessHandler().filesystemFileExists(imagePath)) {
// Try to load the bitmap from the file system
bitmap = BitmapFactory.decodeFile(imagePath);
- } else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) {
+ } else if (godot.getDirectoryAccessHandler().assetsFileExists(imagePath)) {
// Try to load the bitmap from the assets directory
AssetManager am = getContext().getAssets();
InputStream imageInputStream = am.open(imagePath);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index 34490d4625..5439f55b25 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -162,10 +162,10 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
try {
Bitmap bitmap = null;
if (!TextUtils.isEmpty(imagePath)) {
- if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) {
+ if (godot.getDirectoryAccessHandler().filesystemFileExists(imagePath)) {
// Try to load the bitmap from the file system
bitmap = BitmapFactory.decodeFile(imagePath);
- } else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) {
+ } else if (godot.getDirectoryAccessHandler().assetsFileExists(imagePath)) {
// Try to load the bitmap from the assets directory
AssetManager am = getContext().getAssets();
InputStream imageInputStream = am.open(imagePath);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
index 357008ca66..984bf607d0 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
@@ -46,7 +46,7 @@ class FileAccessHandler(val context: Context) {
private val TAG = FileAccessHandler::class.java.simpleName
private const val FILE_NOT_FOUND_ERROR_ID = -1
- private const val INVALID_FILE_ID = 0
+ internal const val INVALID_FILE_ID = 0
private const val STARTING_FILE_ID = 1
internal fun fileExists(context: Context, storageScopeIdentifier: StorageScope.Identifier, path: String?): Boolean {
@@ -96,13 +96,17 @@ class FileAccessHandler(val context: Context) {
private fun hasFileId(fileId: Int) = files.indexOfKey(fileId) >= 0
fun fileOpen(path: String?, modeFlags: Int): Int {
+ val accessFlag = FileAccessFlags.fromNativeModeFlags(modeFlags) ?: return INVALID_FILE_ID
+ return fileOpen(path, accessFlag)
+ }
+
+ internal fun fileOpen(path: String?, accessFlag: FileAccessFlags): Int {
val storageScope = storageScopeIdentifier.identifyStorageScope(path)
if (storageScope == StorageScope.UNKNOWN) {
return INVALID_FILE_ID
}
try {
- val accessFlag = FileAccessFlags.fromNativeModeFlags(modeFlags) ?: return INVALID_FILE_ID
val dataAccess = DataAccess.generateDataAccess(storageScope, context, path!!, accessFlag) ?: return INVALID_FILE_ID
files.put(++lastFileId, dataAccess)
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
new file mode 100644
index 0000000000..1552c8f082
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
@@ -0,0 +1,122 @@
+/**************************************************************************/
+/* BenchmarkUtils.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+@file:JvmName("BenchmarkUtils")
+
+package org.godotengine.godot.utils
+
+import android.os.Build
+import android.os.SystemClock
+import android.os.Trace
+import android.util.Log
+import org.godotengine.godot.BuildConfig
+import org.godotengine.godot.io.file.FileAccessFlags
+import org.godotengine.godot.io.file.FileAccessHandler
+import org.json.JSONObject
+import java.nio.ByteBuffer
+import java.util.concurrent.ConcurrentSkipListMap
+
+/**
+ * Contains benchmark related utilities methods
+ */
+private const val TAG = "GodotBenchmark"
+
+var useBenchmark = false
+var benchmarkFile = ""
+
+private val startBenchmarkFrom = ConcurrentSkipListMap<String, Long>()
+private val benchmarkTracker = ConcurrentSkipListMap<String, Double>()
+
+/**
+ * Start measuring and tracing the execution of a given section of code using the given label.
+ *
+ * Must be followed by a call to [endBenchmarkMeasure].
+ *
+ * Note: Only enabled on 'editorDev' build variant.
+ */
+fun beginBenchmarkMeasure(label: String) {
+ if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
+ return
+ }
+ startBenchmarkFrom[label] = SystemClock.elapsedRealtime()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Trace.beginAsyncSection(label, 0)
+ }
+}
+
+/**
+ * End measuring and tracing of the section of code with the given label.
+ *
+ * Must be preceded by a call [beginBenchmarkMeasure]
+ *
+ * * Note: Only enabled on 'editorDev' build variant.
+ */
+fun endBenchmarkMeasure(label: String) {
+ if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
+ return
+ }
+ val startTime = startBenchmarkFrom[label] ?: return
+ val total = SystemClock.elapsedRealtime() - startTime
+ benchmarkTracker[label] = total / 1000.0
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Trace.endAsyncSection(label, 0)
+ }
+}
+
+/**
+ * Dump the benchmark measurements.
+ * If [filepath] is valid, the data is also written in json format to the specified file.
+ *
+ * * Note: Only enabled on 'editorDev' build variant.
+ */
+@JvmOverloads
+fun dumpBenchmark(fileAccessHandler: FileAccessHandler?, filepath: String? = benchmarkFile) {
+ if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
+ return
+ }
+ if (!useBenchmark) {
+ return
+ }
+
+ val printOut =
+ benchmarkTracker.map { "\t- ${it.key} : ${it.value} sec." }.joinToString("\n")
+ Log.i(TAG, "BENCHMARK:\n$printOut")
+
+ if (fileAccessHandler != null && !filepath.isNullOrBlank()) {
+ val fileId = fileAccessHandler.fileOpen(filepath, FileAccessFlags.WRITE)
+ if (fileId != FileAccessHandler.INVALID_FILE_ID) {
+ val jsonOutput = JSONObject(benchmarkTracker.toMap()).toString(4)
+ fileAccessHandler.fileWrite(fileId, ByteBuffer.wrap(jsonOutput.toByteArray()))
+ fileAccessHandler.fileClose(fileId)
+ }
+ }
+}