diff --git a/.gitignore b/.gitignore
index 8697ce0f..567eaa20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,35 +4,8 @@
# Ignore Gradle build output directory
build
-# IntelliJ IDEA (based on https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore#L2)
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-.idea/misc.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-*.iml
-.idea/modules.xml
-.idea/compiler.xml
-.idea/jarRepositories.xml
-
+build-root/.idea/*
+!build-root/.idea/codeStyles/*
/samples/mini-games/.import/
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index e7e9d11d..00000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# Default ignored files
-/workspace.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index 1bec35e5..00000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123c..00000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 246c3e89..00000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/build-root/build.gradle.kts b/build-root/build.gradle.kts
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/build-root/build.gradle.kts
@@ -0,0 +1 @@
+
diff --git a/build-root/gradle/wrapper/gradle-wrapper.jar b/build-root/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..62d4c053
Binary files /dev/null and b/build-root/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/build-root/gradle/wrapper/gradle-wrapper.properties b/build-root/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..ac33e994
--- /dev/null
+++ b/build-root/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/build-root/gradlew b/build-root/gradlew
new file mode 100755
index 00000000..fbd7c515
--- /dev/null
+++ b/build-root/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/build-root/gradlew.bat b/build-root/gradlew.bat
new file mode 100644
index 00000000..5093609d
--- /dev/null
+++ b/build-root/gradlew.bat
@@ -0,0 +1,104 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/build-root/settings.gradle.kts b/build-root/settings.gradle.kts
new file mode 100644
index 00000000..13181057
--- /dev/null
+++ b/build-root/settings.gradle.kts
@@ -0,0 +1,27 @@
+includeBuild("../") {
+ dependencySubstitution {
+ substitute(module("com.utopia-rise:godot-build-props")).with(project(":godot-build-props"))
+ substitute(module("com.utopia-rise:godot-annotation-processor")).with(project(":godot-annotation-processor"))
+ substitute(module("com.utopia-rise:godot-compiler-plugin")).with(project(":godot-compiler-plugin"))
+ substitute(module("com.utopia-rise:godot-compiler-plugin-common")).with(project(":godot-compiler-plugin-common"))
+ substitute(module("com.utopia-rise:godot-entry-generator")).with(project(":godot-entry-generator"))
+ substitute(module("com.utopia-rise:godot-gradle-plugin")).with(project(":godot-gradle-plugin"))
+ substitute(module("com.utopia-rise:godot-library")).with(project(":godot-library"))
+ // gradle doesn't support targeting a specific configuration (i.e shadow) when substituting a dependency in a composite build.
+ // composite-build-support depends on project(":godot-compiler-native-plugin", configuration = "shadow")
+ substitute(module("com.utopia-rise:godot-compiler-native-plugin")).with(project(":composite-build-support"))
+ }
+}
+
+pluginManagement {
+ resolutionStrategy.eachPlugin {
+ when (requested.id.id) {
+ "com.utopia-rise.godot-kotlin" -> useModule("com.utopia-rise:godot-gradle-plugin:${requested.version}")
+ }
+ }
+}
+
+includeBuild("../samples/3d-platformer")
+includeBuild("../samples/mini-games")
+includeBuild("../samples/benchmarks/bunnymark")
+includeBuild("../samples/benchmarks/gamelife")
diff --git a/samples/benchmarks/bunnymark/.gitignore b/samples/benchmarks/bunnymark/.gitignore
new file mode 100644
index 00000000..fe2fe926
--- /dev/null
+++ b/samples/benchmarks/bunnymark/.gitignore
@@ -0,0 +1,63 @@
+.idea/
+# Created by https://www.toptal.com/developers/gitignore/api/godot,kotlin,gradle
+# Edit at https://www.toptal.com/developers/gitignore?templates=godot,kotlin,gradle
+
+### Godot ###
+
+# Godot-specific ignores
+.import/
+export.cfg
+export_presets.cfg
+
+# Imported translations (automatically generated from CSV files)
+*.translation
+
+# Mono-specific ignores
+.mono/
+data_*/
+
+### Kotlin ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### Gradle ###
+.gradle
+build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+### Gradle Patch ###
+**/build/
+
+# End of https://www.toptal.com/developers/gitignore/api/godot,kotlin,gradle
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/Benchmarker.gd b/samples/benchmarks/bunnymark/Benchmarker.gd
new file mode 100644
index 00000000..4aa1a97a
--- /dev/null
+++ b/samples/benchmarks/bunnymark/Benchmarker.gd
@@ -0,0 +1,119 @@
+extends Control
+
+var fps_update_interval = 1.0
+var elapsed_time = 0.0
+var fps_label = null
+var benchmark_container = null
+var benchmark_node = null
+var output_path = "user://benchmark_results.json"
+var arg_bench = "--bench="
+var arg_lang = "--lang="
+
+# bunnymark
+var bunnymark_target = 60.0
+var bunnymark_target_error = 0.5
+var benchmark_is_bunnymark = false
+var bunnymark_update_interval = 2.0
+var stable_updates_required = 3
+var bunnymark_update_elapsed_time = 0.0
+var stable_updates = 0
+
+var nativescript_languages = {
+ "kot": true
+}
+
+export(String, "BunnymarkV2", "BunnymarkV1Sprites", "BunnymarkV1DrawTexture") var benchmark: String = "BunnymarkV2"
+export(String, "gd", "kot") var language: String = "gd"
+
+func _ready():
+ set_process(false)
+ fps_label = get_node("Panel/FPS")
+ benchmark_container = get_node("BenchmarkContainer")
+
+ var args = OS.get_cmdline_args()
+ for arg in args:
+ if arg.substr(0, arg_bench.length()) == arg_bench:
+ benchmark = arg.split("=")[1]
+ elif arg.substr(0, arg_lang.length()) == arg_lang:
+ language = arg.split("=")[1]
+
+ start_benchmark(benchmark, language)
+
+func _process(delta):
+ elapsed_time += delta
+ if elapsed_time >= fps_update_interval:
+ fps_label.text = "FPS: " + str(Engine.get_frames_per_second())
+ elapsed_time = 0.0
+ if benchmark_is_bunnymark:
+ update_bunnymark(delta)
+
+func start_benchmark(benchmark_name, language):
+ var language_extension = language
+ if nativescript_languages.has(language) and nativescript_languages[language]:
+ language_extension = "gdns"
+ var script_path = "res://benchmarks/" + benchmark_name + "/" + language + "/" + benchmark_name + "." + language_extension
+ benchmark_is_bunnymark = benchmark_name.begins_with("Bunnymark")
+ bunnymark_update_elapsed_time = bunnymark_update_interval
+ var script = load(script_path)
+ benchmark_node = Node2D.new()
+ benchmark_node.set_script(script)
+ benchmark_node.add_user_signal("benchmark_finished", ["output"])
+ benchmark_node.connect("benchmark_finished", self, "benchmark_finished")
+ benchmark_container.add_child(benchmark_node)
+ if benchmark_node.has_method("add_bunny"):
+ set_process(true)
+ else:
+ benchmark_finished(0)
+
+func benchmark_finished(output):
+ print("benchmark output: ", output)
+ benchmark_container.remove_child(benchmark_node)
+ write_result(output)
+ get_tree().quit()
+
+func write_result(output):
+ print("written ", output)
+ var file = File.new()
+ file.open(output_path, File.READ)
+ var parse_result = JSON.parse(file.get_as_text())
+ var benchmark_file = null
+ if parse_result.get_error() == 0:
+ benchmark_file = parse_result.get_result()
+ if benchmark_file == null or typeof(benchmark_file) != TYPE_DICTIONARY:
+ benchmark_file = {
+ "benchmark_results": {}
+ }
+ file.close()
+ benchmark_file["benchmark_results"][benchmark + "_" + language] = output
+ var dir = Directory.new()
+ dir.remove(output_path)
+ file = File.new()
+ file.open(output_path, File.WRITE)
+ benchmark_file["run_date"] = OS.get_datetime()
+ file.store_string(JSON.print(benchmark_file))
+
+func update_bunnymark(delta):
+ bunnymark_update_elapsed_time += delta
+ if bunnymark_update_elapsed_time > bunnymark_update_interval:
+ var fps = Engine.get_frames_per_second()
+ var difference = fps - bunnymark_target
+ var bunny_difference = 0
+ if difference > bunnymark_target_error:
+ bunny_difference = min(1000, max(1, floor(20 * difference)))
+ elif difference < -bunnymark_target_error:
+ bunny_difference = max(-1000, min(-1, -1*ceil(20 * difference)))
+ if abs(difference) < bunnymark_target_error:
+ stable_updates += 1
+ if stable_updates == stable_updates_required:
+ benchmark_node.finish()
+ else:
+ if bunny_difference > 0:
+ for i in range(bunny_difference):
+ benchmark_node.add_bunny()
+ else:
+ for i in range(-1*bunny_difference):
+ benchmark_node.remove_bunny()
+
+ stable_updates = 0
+
+ bunnymark_update_elapsed_time = 0.0
diff --git a/samples/benchmarks/bunnymark/Benchmarker.tscn b/samples/benchmarks/bunnymark/Benchmarker.tscn
new file mode 100644
index 00000000..948638ce
--- /dev/null
+++ b/samples/benchmarks/bunnymark/Benchmarker.tscn
@@ -0,0 +1,23 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://Benchmarker.gd" type="Script" id=1]
+
+[node name="Benchmarker" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+language = "kot"
+
+[node name="BenchmarkContainer" type="Node2D" parent="."]
+
+[node name="Panel" type="Panel" parent="."]
+margin_right = 83.0
+margin_bottom = 14.0
+
+[node name="FPS" type="Label" parent="Panel"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+text = "FPS:"
diff --git a/samples/benchmarks/bunnymark/LICENSE b/samples/benchmarks/bunnymark/LICENSE
new file mode 100644
index 00000000..992a1071
--- /dev/null
+++ b/samples/benchmarks/bunnymark/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Michael "Carter" Anderson
+
+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.
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/README.md b/samples/benchmarks/bunnymark/README.md
new file mode 100644
index 00000000..abd8ec15
--- /dev/null
+++ b/samples/benchmarks/bunnymark/README.md
@@ -0,0 +1,54 @@
+# Games sample
+
+Go in the kotlin directory and run `gradlew build` to build this sample.
+You can add a platform variable inside kotlin/gradle.properties (platform=windows/linux/macos/android/ios)
+Open the project in Godot and inspect the root node.
+You can choose between GdScript or Kotlin and the benchmark as well.
+
+![Godot Bunnymark](images/banner.png)
+
+Renders an increasing number of bunny sprites until a stable 60fps is hit. This is a decent test of real world usage as it combines Godot api usage with raw computation.
+
+## Benchmark Run - March 05, 2020
+
+### BunnymarkV2
+
+Attempts to draw as many sprites as possible using Sprite nodes. It calls GetChildren() to iterate over a list of Sprites and sets their positions. It also updates a Label's text once per frame. This test aims to be a better emulation of real world api usage than the V1 tests.
+
+| Language | Bunnies Rendered |
+|----------------------|------------------|
+| Kotlin | 35 |
+| GDScript | 9600 |
+
+### BunnymarkV1 - DrawTexture
+
+Attempts to draw as many sprites to the screen as possible by drawing textures directly with VisualServer. This test focuses on compute / render performance and avoids making godot api calls.
+
+| Language | Bunnies Rendered |
+|----------------------|------------------|
+| Kotlin | 1103 |
+| GDScript | 10986 |
+
+### BunnymarkV1 - Sprites
+
+Attempts to draw as many sprites to the screen as possible by adding Sprite nodes. This test focuses on compute / render performance and avoids making godot api calls.
+
+| Language | Bunnies Rendered |
+|----------------------|------------------|
+| Kotlin | 1564 |
+| GDScript | 9015 |
+
+### Hardware:
+
+* CPU: Intel i5 4690k 3.9GHz
+* GPU: Nvidia GeForce GTX 970
+* RAM: 16GB DDR3
+
+### Build Info:
+* OS: Windows 10
+* Official Godot 3.2 release
+* Kotlin 1.3
+
+
+Original work: [@Carter Anderson](https://github.com/cart/godot3-bunnymark). Thanks to him.
+
diff --git a/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1DrawTexture/Description.md b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1DrawTexture/Description.md
new file mode 100644
index 00000000..3323dab1
--- /dev/null
+++ b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1DrawTexture/Description.md
@@ -0,0 +1,3 @@
+# BunnymarkV1DrawTexture
+
+Attempts to draw as many sprites to the screen as possible by drawing textures directly with VisualServer. This test focuses on compute / render performance and avoids making godot api calls.
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1DrawTexture/gd/BunnymarkV1DrawTexture.gd b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1DrawTexture/gd/BunnymarkV1DrawTexture.gd
new file mode 100644
index 00000000..4be579bf
--- /dev/null
+++ b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1DrawTexture/gd/BunnymarkV1DrawTexture.gd
@@ -0,0 +1,57 @@
+extends Node2D
+
+var bunnies = []
+var bunny_texture = load("res://images/godot_bunny.png")
+var grav = 500
+var screen_size
+
+func _draw():
+ for bunny in bunnies:
+ draw_texture(bunny_texture, bunny[0])
+
+func _process(delta):
+ screen_size = get_viewport_rect().size
+
+ for bunny in bunnies:
+ var pos = bunny[0]
+ var newPosition = bunny[1]
+
+ pos.x += newPosition.x * delta
+ pos.y += newPosition.y * delta
+
+ newPosition.y += grav * delta
+
+ if pos.x > screen_size.x:
+ newPosition.x *= -1
+ pos.x = screen_size.x
+
+ if pos.x < 0:
+ newPosition.x *= -1
+ pos.x = 0
+
+ if pos.y > screen_size.y:
+ pos.y = screen_size.y
+ if randf() > 0.5:
+ newPosition.y = -(randi() % 1100 + 50)
+ else:
+ newPosition.y *= -0.85
+
+ if pos.y < 0:
+ newPosition.y = 0
+ pos.y = 0
+
+ bunny[0] = pos
+ bunny[1] = newPosition
+ update()
+
+func add_bunny():
+ bunnies.append([Vector2(screen_size.x / 2, screen_size.y / 2), Vector2(randi() % 200 + 50, randi() % 200 + 50)])
+
+func remove_bunny():
+ if bunnies.size() == 0:
+ return
+
+ bunnies.pop_back()
+
+func finish():
+ emit_signal("benchmark_finished", bunnies.size())
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1DrawTexture/kot/BunnymarkV1DrawTexture.gdns b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1DrawTexture/kot/BunnymarkV1DrawTexture.gdns
new file mode 100644
index 00000000..f544c461
--- /dev/null
+++ b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1DrawTexture/kot/BunnymarkV1DrawTexture.gdns
@@ -0,0 +1,8 @@
+[gd_resource type="NativeScript" load_steps=2 format=2]
+
+[ext_resource path="res://bunnymark.gdnlib" type="GDNativeLibrary" id=1]
+
+[resource]
+resource_name = "BunnymarkV1DrawTexture"
+class_name = "godot.samples.benchmark.bunnymark.BunnymarkV1DrawTexture"
+library = ExtResource( 1 )
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1Sprites/Description.md b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1Sprites/Description.md
new file mode 100644
index 00000000..1763fde5
--- /dev/null
+++ b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1Sprites/Description.md
@@ -0,0 +1,3 @@
+# BunnymarkV1DrawTexture
+
+Attempts to draw as many sprites to the screen as possible by adding Sprite nodes. This test focuses on compute / render performance and avoids making godot api calls.
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1Sprites/gd/BunnymarkV1Sprites.gd b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1Sprites/gd/BunnymarkV1Sprites.gd
new file mode 100644
index 00000000..5a5010ee
--- /dev/null
+++ b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1Sprites/gd/BunnymarkV1Sprites.gd
@@ -0,0 +1,57 @@
+extends Node2D
+
+var bunnies = []
+var grav = 500
+var bunny_texture = load("res://images/godot_bunny.png")
+var screen_size
+
+func _process(delta):
+ screen_size = get_viewport_rect().size
+
+ for bunny in bunnies:
+ var pos = bunny[0].position
+ var newPosition = bunny[1]
+
+ pos.x += newPosition.x * delta
+ pos.y += newPosition.y * delta
+
+ newPosition.y += grav * delta
+
+ if pos.x > screen_size.x:
+ newPosition.x *= -1
+ pos.x = screen_size.x
+
+ if pos.x < 0:
+ newPosition.x *= -1
+ pos.x = 0
+
+ if pos.y > screen_size.y:
+ pos.y = screen_size.y
+ if randf() > 0.5:
+ newPosition.y = -(randi() % 1100 + 50)
+ else:
+ newPosition.y *= -0.85
+
+ if pos.y < 0:
+ newPosition.y = 0
+ pos.y = 0
+
+ bunny[0].position = pos
+ bunny[1] = newPosition
+
+func add_bunny():
+ var bunny = Sprite.new()
+ bunny.set_texture(bunny_texture)
+ add_child(bunny)
+ bunny.position = Vector2(screen_size.x / 2, screen_size.y / 2)
+ bunnies.append([bunny, Vector2(randi() % 200 + 50, randi() % 200 + 50)])
+
+func remove_bunny():
+ if bunnies.size() == 0:
+ return
+ var bunny = bunnies[bunnies.size() - 1]
+ remove_child(bunny[0])
+ bunnies.pop_back()
+
+func finish():
+ emit_signal("benchmark_finished", bunnies.size())
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1Sprites/kot/BunnymarkV1Sprites.gdns b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1Sprites/kot/BunnymarkV1Sprites.gdns
new file mode 100644
index 00000000..ef015c08
--- /dev/null
+++ b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV1Sprites/kot/BunnymarkV1Sprites.gdns
@@ -0,0 +1,8 @@
+[gd_resource type="NativeScript" load_steps=2 format=2]
+
+[ext_resource path="res://bunnymark.gdnlib" type="GDNativeLibrary" id=1]
+
+[resource]
+resource_name = "BunnymarkV1Sprites"
+class_name = "godot.samples.benchmark.bunnymark.BunnymarkV1Sprites"
+library = ExtResource( 1 )
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/benchmarks/BunnymarkV2/Description.md b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV2/Description.md
new file mode 100644
index 00000000..f42136f1
--- /dev/null
+++ b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV2/Description.md
@@ -0,0 +1,3 @@
+# BunnymarkV2
+
+Attempts to draw as many sprites as possible using Sprite nodes. It calls GetChildren() to iterate over a list of Sprites and sets their positions. It also updates a Label's text once per frame. This test aims to be a better emulation of real world api usage than the V1 tests.
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/benchmarks/BunnymarkV2/gd/BunnymarkV2.gd b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV2/gd/BunnymarkV2.gd
new file mode 100644
index 00000000..feb4215a
--- /dev/null
+++ b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV2/gd/BunnymarkV2.gd
@@ -0,0 +1,69 @@
+extends Node2D
+
+var grav = 500
+var bunny_texture = load("res://images/godot_bunny.png")
+var bunny_speeds = []
+var label = Label.new()
+var bunnies = Node2D.new()
+var screen_size
+
+func _ready():
+ add_child(bunnies)
+
+ label.rect_position = Vector2(0, 20)
+ add_child(label)
+
+func _process(delta):
+ screen_size = get_viewport_rect().size
+ label.text = "Bunnies: " + str(bunnies.get_child_count())
+
+ var bunny_children = bunnies.get_children()
+ for i in range(0, bunny_children.size()):
+ var bunny = bunny_children[i]
+ var pos = bunny.position
+ var speed = bunny_speeds[i]
+
+ pos.x += speed.x * delta
+ pos.y += speed.y * delta
+
+ speed.y += grav * delta
+
+ if pos.x > screen_size.x:
+ speed.x *= -1
+ pos.x = screen_size.x
+
+ if pos.x < 0:
+ speed.x *= -1
+ pos.x = 0
+
+ if pos.y > screen_size.y:
+ pos.y = screen_size.y
+ if randf() > 0.5:
+ speed.y = -(randi() % 1100 + 50)
+ else:
+ speed.y *= -0.85
+
+ if pos.y < 0:
+ speed.y = 0
+ pos.y = 0
+
+ bunny.position = pos
+ bunny_speeds[i] = speed
+
+func add_bunny():
+ var bunny = Sprite.new()
+ bunny.set_texture(bunny_texture)
+ bunnies.add_child(bunny)
+ bunny.position = Vector2(screen_size.x / 2, screen_size.y / 2)
+ bunny_speeds.push_back(Vector2(randi() % 200 + 50, randi() % 200 + 50))
+
+func remove_bunny():
+ var child_count = bunnies.get_child_count()
+ if child_count == 0:
+ return
+ var bunny = bunnies.get_child(child_count - 1)
+ bunny_speeds.pop_back()
+ bunnies.remove_child(bunny)
+
+func finish():
+ emit_signal("benchmark_finished", bunnies.get_child_count())
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/benchmarks/BunnymarkV2/kot/BunnymarkV2.gdns b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV2/kot/BunnymarkV2.gdns
new file mode 100644
index 00000000..f2633c45
--- /dev/null
+++ b/samples/benchmarks/bunnymark/benchmarks/BunnymarkV2/kot/BunnymarkV2.gdns
@@ -0,0 +1,8 @@
+[gd_resource type="NativeScript" load_steps=2 format=2]
+
+[ext_resource path="res://bunnymark.gdnlib" type="GDNativeLibrary" id=1]
+
+[resource]
+resource_name = "BunnymarkV2"
+class_name = "godot.samples.benchmark.bunnymark.BunnymarkV2"
+library = ExtResource( 1 )
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/build.gradle.kts b/samples/benchmarks/bunnymark/build.gradle.kts
new file mode 100644
index 00000000..f886424c
--- /dev/null
+++ b/samples/benchmarks/bunnymark/build.gradle.kts
@@ -0,0 +1,14 @@
+plugins {
+ kotlin("multiplatform") version "1.3.72"
+ id("com.utopia-rise.godot-kotlin") version "0.1.0-3.2"
+}
+
+repositories {
+ jcenter()
+}
+
+
+godot {
+ debug.set(true)
+ defaultPlatforms()
+}
diff --git a/samples/benchmarks/bunnymark/bunnymark.gdnlib b/samples/benchmarks/bunnymark/bunnymark.gdnlib
new file mode 100644
index 00000000..63ebef5a
--- /dev/null
+++ b/samples/benchmarks/bunnymark/bunnymark.gdnlib
@@ -0,0 +1,18 @@
+[entry]
+
+X11.64="res://build/bin/linuxX64/debugShared/libbunnymark.so"
+Windows.64="res://build/bin/windowsX64/debugShared/bunnymark.dll"
+OSX.64="res://build/bin/macosX64/debugShared/libbunnymark.dylib"
+
+[dependencies]
+
+X11.64=[ ]
+Windows.64=[ ]
+OSX.64=[ ]
+
+[general]
+
+singleton=false
+load_once=true
+symbol_prefix="godot_"
+reloadable=true
diff --git a/samples/benchmarks/bunnymark/gradle.properties b/samples/benchmarks/bunnymark/gradle.properties
new file mode 100644
index 00000000..659b74c0
--- /dev/null
+++ b/samples/benchmarks/bunnymark/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx3G
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/gradle/wrapper/gradle-wrapper.jar b/samples/benchmarks/bunnymark/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..f3d88b1c
Binary files /dev/null and b/samples/benchmarks/bunnymark/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/samples/benchmarks/bunnymark/gradle/wrapper/gradle-wrapper.properties b/samples/benchmarks/bunnymark/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..ac33e994
--- /dev/null
+++ b/samples/benchmarks/bunnymark/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/samples/benchmarks/bunnymark/gradlew b/samples/benchmarks/bunnymark/gradlew
new file mode 100644
index 00000000..2fe81a7d
--- /dev/null
+++ b/samples/benchmarks/bunnymark/gradlew
@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/samples/benchmarks/bunnymark/gradlew.bat b/samples/benchmarks/bunnymark/gradlew.bat
new file mode 100644
index 00000000..62bd9b9c
--- /dev/null
+++ b/samples/benchmarks/bunnymark/gradlew.bat
@@ -0,0 +1,103 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/samples/benchmarks/bunnymark/icon.png b/samples/benchmarks/bunnymark/icon.png
new file mode 100644
index 00000000..6e343676
Binary files /dev/null and b/samples/benchmarks/bunnymark/icon.png differ
diff --git a/samples/benchmarks/bunnymark/images/banner.png b/samples/benchmarks/bunnymark/images/banner.png
new file mode 100644
index 00000000..9037506c
Binary files /dev/null and b/samples/benchmarks/bunnymark/images/banner.png differ
diff --git a/samples/benchmarks/bunnymark/images/bunny.svg b/samples/benchmarks/bunnymark/images/bunny.svg
new file mode 100644
index 00000000..72e6a986
--- /dev/null
+++ b/samples/benchmarks/bunnymark/images/bunny.svg
@@ -0,0 +1,217 @@
+
+
+
+
diff --git a/samples/benchmarks/bunnymark/images/godot_bunny.png b/samples/benchmarks/bunnymark/images/godot_bunny.png
new file mode 100644
index 00000000..8cafbf9a
Binary files /dev/null and b/samples/benchmarks/bunnymark/images/godot_bunny.png differ
diff --git a/samples/benchmarks/bunnymark/project.godot b/samples/benchmarks/bunnymark/project.godot
new file mode 100644
index 00000000..23b9f1ed
--- /dev/null
+++ b/samples/benchmarks/bunnymark/project.godot
@@ -0,0 +1,32 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=4
+
+_global_script_classes=[ ]
+_global_script_class_icons={
+
+}
+
+[application]
+
+config/name="Bunnymark"
+run/main_scene="res://Benchmarker.tscn"
+config/icon="res://icon.png"
+
+[display]
+
+window/vsync/use_vsync=false
+
+[gdnative]
+
+singletons=[ ]
+
+[rendering]
+
+environment/default_environment="res://default_env.tres"
diff --git a/samples/benchmarks/bunnymark/settings.gradle.kts b/samples/benchmarks/bunnymark/settings.gradle.kts
new file mode 100644
index 00000000..7f3666f5
--- /dev/null
+++ b/samples/benchmarks/bunnymark/settings.gradle.kts
@@ -0,0 +1,24 @@
+// The following is used for configuring composite builds, which we use for developing this binding.
+// Normally as a user, you should not need it.
+includeBuild("../../../") {
+ dependencySubstitution {
+ substitute(module("com.utopia-rise:godot-build-props")).with(project(":godot-build-props"))
+ substitute(module("com.utopia-rise:godot-annotation-processor")).with(project(":godot-annotation-processor"))
+ substitute(module("com.utopia-rise:godot-compiler-plugin")).with(project(":godot-compiler-plugin"))
+ substitute(module("com.utopia-rise:godot-compiler-plugin-common")).with(project(":godot-compiler-plugin-common"))
+ substitute(module("com.utopia-rise:godot-entry-generator")).with(project(":godot-entry-generator"))
+ substitute(module("com.utopia-rise:godot-gradle-plugin")).with(project(":godot-gradle-plugin"))
+ substitute(module("com.utopia-rise:godot-library")).with(project(":godot-library"))
+ // gradle doesn't support targeting a specific configuration (i.e shadow) when substituting a dependency in a composite build.
+ // composite-build-support depends on project(":godot-compiler-native-plugin", configuration = "shadow")
+ substitute(module("com.utopia-rise:godot-compiler-native-plugin")).with(project(":composite-build-support"))
+ }
+}
+
+pluginManagement {
+ resolutionStrategy.eachPlugin {
+ when (requested.id.id) {
+ "com.utopia-rise.godot-kotlin" -> useModule("com.utopia-rise:godot-gradle-plugin:${requested.version}")
+ }
+ }
+}
diff --git a/samples/benchmarks/bunnymark/src/godotMain/kotlin/godot/samples/benchmark/bunnymark/BunnymarkV1DrawTexture.kt b/samples/benchmarks/bunnymark/src/godotMain/kotlin/godot/samples/benchmark/bunnymark/BunnymarkV1DrawTexture.kt
new file mode 100644
index 00000000..4eee8f3e
--- /dev/null
+++ b/samples/benchmarks/bunnymark/src/godotMain/kotlin/godot/samples/benchmark/bunnymark/BunnymarkV1DrawTexture.kt
@@ -0,0 +1,91 @@
+package godot.samples.benchmark.bunnymark
+
+@RegisterClass("benchmarks/BunnymarkV1DrawTexture/kot")
+class BunnymarkV1DrawTexture : Node2D() {
+
+ data class Bunny(var position: Vector2, var speed: Vector2)
+
+ private var bunnies = ArrayList()
+ private var gravity = 500
+ private var bunnyTexture = Texture from ResourceLoader.load("res://images/godot_bunny.png")
+ private val randomNumberGenerator = RandomNumberGenerator()
+
+ lateinit var screenSize: Vector2
+
+ @RegisterFunction
+ override fun _ready() {
+ randomNumberGenerator.randomize()
+ }
+
+ @RegisterFunction
+ override fun _draw() {
+ for (bunny in bunnies) {
+ drawTexture(bunnyTexture, bunny.position)
+ }
+ }
+
+ @RegisterFunction
+ override fun _process(delta: Double) {
+ screenSize = getViewportRect().size
+
+ for (bunny in bunnies) {
+ val pos = bunny.position
+ val speed = bunny.speed
+
+ pos.x += speed.x * delta
+ pos.y += speed.y * delta
+
+ speed.y += gravity * delta
+
+ if (pos.x > screenSize.x) {
+ speed.x *= -1
+ pos.x = screenSize.x
+ }
+
+ if (pos.x < 0) {
+ speed.x *= -1.0
+ pos.x = 0.0
+ }
+
+ if (pos.y > screenSize.y) {
+ pos.y = screenSize.y
+ if (randomNumberGenerator.randf() > 0.5) {
+ speed.y = -(randomNumberGenerator.randi() % 1100 + 50).toDouble()
+ } else {
+ speed.y *= -0.85
+ }
+ }
+
+
+
+ if (pos.y < 0) {
+ speed.y = 0.0
+ pos.y = 0.0
+ }
+
+ bunny.position = pos
+ bunny.speed = speed
+ }
+ update()
+ }
+
+ @RegisterFunction
+ fun add_bunny() {
+ bunnies.add(Bunny(
+ Vector2(screenSize.x / 2, screenSize.y / 2),
+ Vector2(randomNumberGenerator.randi() % 200 + 50, randomNumberGenerator.randi() % 200 + 50)
+
+ ))
+ }
+
+ @RegisterFunction
+ fun remove_bunny() {
+ if (bunnies.size == 0) return
+ bunnies.removeAt(bunnies.size - 1)
+ }
+
+ @RegisterFunction
+ fun finish() {
+ emitSignal("benchmark_finished", bunnies.size)
+ }
+}
diff --git a/samples/benchmarks/bunnymark/src/godotMain/kotlin/godot/samples/benchmark/bunnymark/BunnymarkV1Sprites.kt b/samples/benchmarks/bunnymark/src/godotMain/kotlin/godot/samples/benchmark/bunnymark/BunnymarkV1Sprites.kt
new file mode 100644
index 00000000..1acce884
--- /dev/null
+++ b/samples/benchmarks/bunnymark/src/godotMain/kotlin/godot/samples/benchmark/bunnymark/BunnymarkV1Sprites.kt
@@ -0,0 +1,90 @@
+package godot.samples.benchmark.bunnymark
+
+import godot.core.*
+import godot.*
+import org.godotengine.kotlin.annotation.RegisterClass
+import org.godotengine.kotlin.annotation.RegisterFunction
+
+@RegisterClass("benchmarks/BunnymarkV1Sprites/kot")
+class BunnymarkV1Sprites : Node2D() {
+
+ private data class Bunny(var sprite: Sprite, var speed: Vector2)
+
+ private var bunnies = ArrayList()
+ private var gravity = 500
+ private var bunnyTexture = Texture from ResourceLoader.load("res://images/godot_bunny.png")
+ private val randomNumberGenerator = RandomNumberGenerator()
+
+ lateinit var screenSize: Vector2
+
+ @RegisterFunction
+ override fun _process(delta: Double) {
+ screenSize = getViewportRect().size
+
+ for (bunny in bunnies) {
+ val pos = bunny.sprite.position
+ val speed = bunny.speed
+
+ pos.x += speed.x * delta
+ pos.y += speed.y * delta
+
+ speed.y += gravity * delta
+
+ if (pos.x > screenSize.x) {
+ speed.x *= -1
+ pos.x = screenSize.x
+ }
+
+ if (pos.x < 0) {
+ speed.x *= -1.0
+ pos.x = 0.0
+ }
+
+ if (pos.y > screenSize.y) {
+ pos.y = screenSize.y
+ if (randomNumberGenerator.randf() > 0.5) {
+ speed.y = -(randomNumberGenerator.randi() % 1100 + 50).toDouble()
+ } else {
+ speed.y *= -0.85
+ }
+ }
+
+
+
+ if (pos.y < 0) {
+ speed.y = 0.0
+ pos.y = 0.0
+ }
+
+ bunny.sprite.position = pos
+ bunny.speed = speed
+ }
+ }
+
+ @RegisterFunction
+ fun add_bunny() {
+ val bunny = Sprite()
+ bunny.setTexture(bunnyTexture)
+ addChild(bunny)
+ bunny.position = Vector2(screenSize.x / 2, screenSize.y / 2)
+ bunnies.add(
+ Bunny(
+ bunny,
+ Vector2(randomNumberGenerator.randi() % 200 + 50, randomNumberGenerator.randi() % 200 + 50)
+ )
+ )
+ }
+
+ @RegisterFunction
+ fun remove_bunny() {
+ if (bunnies.size == 0) return
+ val bunny = bunnies[bunnies.size - 1]
+ removeChild(bunny.sprite)
+ bunnies.removeAt(bunnies.size - 1)
+ }
+
+ @RegisterFunction
+ fun finish() {
+ emitSignal("benchmark_finished", bunnies.size)
+ }
+}
\ No newline at end of file
diff --git a/samples/benchmarks/bunnymark/src/godotMain/kotlin/godot/samples/benchmark/bunnymark/BunnymarkV2.kt b/samples/benchmarks/bunnymark/src/godotMain/kotlin/godot/samples/benchmark/bunnymark/BunnymarkV2.kt
new file mode 100644
index 00000000..11f0e587
--- /dev/null
+++ b/samples/benchmarks/bunnymark/src/godotMain/kotlin/godot/samples/benchmark/bunnymark/BunnymarkV2.kt
@@ -0,0 +1,97 @@
+package godot.samples.benchmark.bunnymark
+
+import godot.core.*
+import godot.*
+import org.godotengine.kotlin.annotation.RegisterClass
+import org.godotengine.kotlin.annotation.RegisterFunction
+
+@RegisterClass("benchmarks/BunnymarkV2/kot")
+class BunnymarkV2 : Node2D() {
+
+
+ private var gravity = 500
+ private var bunnySpeeds = ArrayList()
+ private var label = Label()
+ private var bunnies = Node2D()
+ private var bunnyTexture = Texture from ResourceLoader.load("res://images/godot_bunny.png")
+ private val randomNumberGenerator = RandomNumberGenerator()
+
+ lateinit var screenSize: Vector2
+
+ @RegisterFunction
+ override fun _ready() {
+ addChild(bunnies)
+ label.setPosition(Vector2(0, 20))
+ addChild(label)
+ }
+
+ @RegisterFunction
+ override fun _process(delta: Double) {
+ screenSize = getViewportRect().size
+ label.text = "Bunnies: " + bunnies.getChildCount().toString()
+
+ val bunny_children = bunnies.getChildren()
+ for (i in 0 until bunny_children.size()) {
+ val bunny = Sprite from bunny_children[i]!!
+ val pos = bunny.position
+ val speed = bunnySpeeds[i]
+
+ pos.x += speed.x * delta
+ pos.y += speed.y * delta
+
+ speed.y += gravity * delta
+
+ if (pos.x > screenSize.x) {
+ speed.x *= -1
+ pos.x = screenSize.x
+ }
+
+ if (pos.x < 0) {
+ speed.x *= -1.0
+ pos.x = 0.0
+ }
+
+ if (pos.y > screenSize.y) {
+ pos.y = screenSize.y
+ if (randomNumberGenerator.randf() > 0.5) {
+ speed.y = -(randomNumberGenerator.randi() % 1100 + 50).toDouble()
+ } else {
+ speed.y *= -0.85
+ }
+ }
+
+ if (pos.y < 0) {
+ speed.y = 0.0
+ pos.y = 0.0
+ }
+
+ bunny.position = pos
+ bunnySpeeds[i] = speed
+ }
+ }
+
+ @RegisterFunction
+ fun add_bunny() {
+ val bunny = Sprite()
+ bunny.setTexture(bunnyTexture)
+ bunnies.addChild(bunny)
+ bunny.position = Vector2(screenSize.x / 2, screenSize.y / 2)
+ bunnySpeeds.add(
+ Vector2(randomNumberGenerator.randi() % 200 + 50, randomNumberGenerator.randi() % 200 + 50)
+ )
+ }
+
+ @RegisterFunction
+ fun remove_bunny() {
+ val child_count = bunnies.getChildCount()
+ if (child_count == 0L) return
+ val bunny = bunnies.getChild(child_count - 1)
+ bunnies.removeChild(bunny)
+ bunnySpeeds.removeAt(child_count.toInt() - 1)
+ }
+
+ @RegisterFunction
+ fun finish() {
+ emitSignal("benchmark_finished", bunnySpeeds.size)
+ }
+}
\ No newline at end of file
diff --git a/samples/benchmarks/gamelife/.gitignore b/samples/benchmarks/gamelife/.gitignore
new file mode 100644
index 00000000..fe2fe926
--- /dev/null
+++ b/samples/benchmarks/gamelife/.gitignore
@@ -0,0 +1,63 @@
+.idea/
+# Created by https://www.toptal.com/developers/gitignore/api/godot,kotlin,gradle
+# Edit at https://www.toptal.com/developers/gitignore?templates=godot,kotlin,gradle
+
+### Godot ###
+
+# Godot-specific ignores
+.import/
+export.cfg
+export_presets.cfg
+
+# Imported translations (automatically generated from CSV files)
+*.translation
+
+# Mono-specific ignores
+.mono/
+data_*/
+
+### Kotlin ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### Gradle ###
+.gradle
+build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+### Gradle Patch ###
+**/build/
+
+# End of https://www.toptal.com/developers/gitignore/api/godot,kotlin,gradle
\ No newline at end of file
diff --git a/samples/benchmarks/gamelife/LICENSE b/samples/benchmarks/gamelife/LICENSE
new file mode 100644
index 00000000..af20dbd5
--- /dev/null
+++ b/samples/benchmarks/gamelife/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Marcin Zawiejski
+
+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.
diff --git a/samples/benchmarks/gamelife/README.md b/samples/benchmarks/gamelife/README.md
new file mode 100644
index 00000000..703fd136
--- /dev/null
+++ b/samples/benchmarks/gamelife/README.md
@@ -0,0 +1,37 @@
+# Games sample
+
+Go in the kotlin directory and run `gradlew build` to build this sample.
+You can add a platform variable inside kotlin/gradle.properties (platform=windows/linux/macos/android/ios)
+Open the project in Godot and inspect the root node.
+You can choose between GdScript or Kotlin, the size of the grid and the length of the benchmark (Number of cycles).
+
+
+## Benchmark Run - March 05, 2020
+
+### Game of life
+
+A Game of life benchmark where each cell is a 3D Cube. Their colors are updated based on their state.
+The default grid size is 50x50 = 2500 cells.
+Each cell checks the state of its surrounding (8 others cells) to update its own. 2500 x 8 = 20000 checks each cycle.
+The benchmark measure the time it takes for the script to complete 1000 cycles.
+
+| Language | Elapsed seconds |
+|----------------------|-----------------------|
+| Kotlin | 458 |
+| GDScript | 13 |
+
+
+### Hardware:
+
+* CPU: Intel i5 4690k 3.9GHz
+* GPU: Nvidia GeForce GTX 970
+* RAM: 16GB DDR3
+
+### Build Info:
+* OS: Windows 10
+* Official Godot 3.2 release
+* Kotlin 1.3
+
+
+Original work: [@Marcin Zawiejski](https://github.com/dragmz/gdlife). Thanks to him.
+
diff --git a/samples/benchmarks/gamelife/build.gradle.kts b/samples/benchmarks/gamelife/build.gradle.kts
new file mode 100644
index 00000000..f886424c
--- /dev/null
+++ b/samples/benchmarks/gamelife/build.gradle.kts
@@ -0,0 +1,14 @@
+plugins {
+ kotlin("multiplatform") version "1.3.72"
+ id("com.utopia-rise.godot-kotlin") version "0.1.0-3.2"
+}
+
+repositories {
+ jcenter()
+}
+
+
+godot {
+ debug.set(true)
+ defaultPlatforms()
+}
diff --git a/samples/benchmarks/gamelife/default_env.tres b/samples/benchmarks/gamelife/default_env.tres
new file mode 100644
index 00000000..20207a4a
--- /dev/null
+++ b/samples/benchmarks/gamelife/default_env.tres
@@ -0,0 +1,7 @@
+[gd_resource type="Environment" load_steps=2 format=2]
+
+[sub_resource type="ProceduralSky" id=1]
+
+[resource]
+background_mode = 2
+background_sky = SubResource( 1 )
diff --git a/samples/benchmarks/gamelife/game.tscn b/samples/benchmarks/gamelife/game.tscn
new file mode 100644
index 00000000..951a7ec4
--- /dev/null
+++ b/samples/benchmarks/gamelife/game.tscn
@@ -0,0 +1,19 @@
+[gd_scene load_steps=4 format=2]
+
+[sub_resource type="SpatialMaterial" id=1]
+vertex_color_use_as_albedo = true
+
+[sub_resource type="CubeMesh" id=2]
+material = SubResource( 1 )
+size = Vector3( 1, 1, 1 )
+
+[sub_resource type="MultiMesh" id=3]
+color_format = 2
+transform_format = 1
+instance_count = 1
+mesh = SubResource( 2 )
+transform_array = PoolVector3Array( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )
+color_array = PoolColorArray( 1, 1, 1, 1 )
+
+[node name="game" type="MultiMeshInstance"]
+multimesh = SubResource( 3 )
diff --git a/samples/benchmarks/gamelife/gameoflife.gdnlib b/samples/benchmarks/gamelife/gameoflife.gdnlib
new file mode 100644
index 00000000..bb053485
--- /dev/null
+++ b/samples/benchmarks/gamelife/gameoflife.gdnlib
@@ -0,0 +1,19 @@
+[general]
+
+singleton=false
+load_once=true
+symbol_prefix="godot_"
+reloadable=true
+
+[entry]
+
+OSX.64="res://kotlin/build/bin/macos/debugShared/libkotlin.dylib"
+Windows.64="res://kotlin/build/bin/windows/debugShared/kotlin.dll"
+X11.64="res://kotlin/build/bin/linux/debugShared/libkotlin.so"
+Android.arm64-v8a="res://kotlin/build/bin/androidArm64/debugShared/libkotlin.so"
+
+[dependencies]
+
+OSX.64=[ ]
+Windows.64=[ ]
+X11.64=[ ]
diff --git a/samples/benchmarks/gamelife/gradle.properties b/samples/benchmarks/gamelife/gradle.properties
new file mode 100644
index 00000000..659b74c0
--- /dev/null
+++ b/samples/benchmarks/gamelife/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx3G
\ No newline at end of file
diff --git a/samples/benchmarks/gamelife/gradle/wrapper/gradle-wrapper.jar b/samples/benchmarks/gamelife/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..f3d88b1c
Binary files /dev/null and b/samples/benchmarks/gamelife/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/samples/benchmarks/gamelife/gradle/wrapper/gradle-wrapper.properties b/samples/benchmarks/gamelife/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..ac33e994
--- /dev/null
+++ b/samples/benchmarks/gamelife/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/samples/benchmarks/gamelife/gradlew b/samples/benchmarks/gamelife/gradlew
new file mode 100644
index 00000000..2fe81a7d
--- /dev/null
+++ b/samples/benchmarks/gamelife/gradlew
@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/samples/benchmarks/gamelife/gradlew.bat b/samples/benchmarks/gamelife/gradlew.bat
new file mode 100644
index 00000000..62bd9b9c
--- /dev/null
+++ b/samples/benchmarks/gamelife/gradlew.bat
@@ -0,0 +1,103 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/samples/benchmarks/gamelife/icon.png b/samples/benchmarks/gamelife/icon.png
new file mode 100644
index 00000000..2e42096c
Binary files /dev/null and b/samples/benchmarks/gamelife/icon.png differ
diff --git a/samples/benchmarks/gamelife/logo.png b/samples/benchmarks/gamelife/logo.png
new file mode 100644
index 00000000..98ad1c6c
Binary files /dev/null and b/samples/benchmarks/gamelife/logo.png differ
diff --git a/samples/benchmarks/gamelife/main.gd b/samples/benchmarks/gamelife/main.gd
new file mode 100644
index 00000000..4f036ed9
--- /dev/null
+++ b/samples/benchmarks/gamelife/main.gd
@@ -0,0 +1,19 @@
+extends Spatial
+
+export(String, "GdScript", "Kotlin") var language: String = "GdScript"
+export(int, 100, 10000, 100) var limit = 1000
+export(int, 10, 200, 10) var size = 50
+
+var game = preload("res://game.tscn").instance()
+
+# Called when the node enters the scene tree for the first time.
+func _ready():
+ if language == "GdScript":
+ var script = load("res://scripts/gamelife/game.gd")
+ game.set_script(script)
+ else:
+ var script = load("res://scripts/gamelife/Game.gdns")
+ game.set_script(script)
+ game.limit = limit
+ game.size = size
+ add_child(game)
diff --git a/samples/benchmarks/gamelife/main.tscn b/samples/benchmarks/gamelife/main.tscn
new file mode 100644
index 00000000..312a0d7a
--- /dev/null
+++ b/samples/benchmarks/gamelife/main.tscn
@@ -0,0 +1,9 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://main.gd" type="Script" id=1]
+
+[node name="main" type="Spatial"]
+script = ExtResource( 1 )
+
+[node name="camera" type="Camera" parent="."]
+transform = Transform( 1, 0, 0, 0, -1.62921e-07, 1, 0, -1, -1.62921e-07, 25, 50, 25 )
diff --git a/samples/benchmarks/gamelife/project.godot b/samples/benchmarks/gamelife/project.godot
new file mode 100644
index 00000000..aece1b5c
--- /dev/null
+++ b/samples/benchmarks/gamelife/project.godot
@@ -0,0 +1,30 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=4
+
+_global_script_classes=[ ]
+_global_script_class_icons={
+
+}
+
+[application]
+
+config/name="gdlife"
+run/main_scene="res://main.tscn"
+config/icon="res://icon.png"
+
+[display]
+
+window/vsync/use_vsync=false
+
+[rendering]
+
+vram_compression/import_etc=true
+vram_compression/import_etc2=false
+environment/default_environment="res://default_env.tres"
diff --git a/samples/benchmarks/gamelife/scripts/gamelife/Game.gdns b/samples/benchmarks/gamelife/scripts/gamelife/Game.gdns
new file mode 100644
index 00000000..eb473ad8
--- /dev/null
+++ b/samples/benchmarks/gamelife/scripts/gamelife/Game.gdns
@@ -0,0 +1,8 @@
+[gd_resource type="NativeScript" load_steps=2 format=2]
+
+[ext_resource path="res://gameoflife.gdnlib" type="GDNativeLibrary" id=1]
+
+[resource]
+resource_name = "Game"
+class_name = "godot.samples.benchmark.gamelife.Game"
+library = ExtResource( 1 )
\ No newline at end of file
diff --git a/samples/benchmarks/gamelife/scripts/gamelife/game.gd b/samples/benchmarks/gamelife/scripts/gamelife/game.gd
new file mode 100644
index 00000000..0c4048bd
--- /dev/null
+++ b/samples/benchmarks/gamelife/scripts/gamelife/game.gd
@@ -0,0 +1,143 @@
+extends MultiMeshInstance
+
+class Tile:
+ var v : bool
+ var life : int
+ var others : Array
+
+ func _init():
+ self.v = false
+ self.life = 0
+ self.others = Array()
+
+ func get_total() -> int:
+ var result : int = 0
+
+ for other in others:
+ result += other.get_value()
+
+ return result
+
+ func get_value():
+ if v:
+ return 1
+
+ return 0
+
+class Map:
+ var data : Array
+ var next : Array
+ var empty : Tile
+
+ var w : int
+ var h : int
+
+ func _init(width : int, height : int):
+ self.empty = Tile.new()
+
+ self.w = width
+ self.h = height
+
+ data = Array()
+ next = Array()
+
+ for i in self.w * self.h:
+ data.append(Tile.new())
+ next.append(false)
+
+ var i = 0
+ for y in self.h:
+ for x in self.w:
+ var t = data[i]
+ t.others = [
+ at(x - 1, y - 1), at(x, y - 1), at(x + 1, y - 1),
+ at(x - 1, y), at(x + 1, y),
+ at(x - 1, y + 1), at(x, y + 1), at(x + 1, y + 1)]
+ i += 1
+
+ func get_index(x, y):
+ if x < 0 || x >= w || y < 0 || y >= h:
+ return -1
+
+ return x + y * w
+
+ func put(x, y, v):
+ var index = get_index(x, y)
+ if index == -1:
+ return
+
+ data[index].v = v
+
+ func at(x, y):
+ var index = get_index(x, y)
+ if index == -1:
+ return empty
+
+ return data[index]
+
+ func process():
+ for i in w * h:
+ var t = data[i]
+ var v = t.get_total()
+
+ if data[i].v:
+ next[i] = v == 2 || v == 3
+ else:
+ next[i] = v == 3
+
+ for i in w * h:
+ data[i].v = next[i]
+
+ if data[i].v:
+ data[i].life = 20
+
+ data[i].life = max(0, data[i].life - 1)
+
+var map : Map
+var started
+var size = 50
+
+func _ready():
+ started = OS.get_system_time_msecs()
+ map = Map.new(size, size)
+ self.multimesh.instance_count = size*size
+
+ var index : int = 0
+
+ var offset = (size - 50) * 0.5
+ for y in size:
+ for x in size:
+ self.multimesh.set_instance_transform(index, Transform(Basis(), Vector3(x - offset, randi() % 10 * 0.1, y - offset)))
+ self.multimesh.set_instance_color(index, Color(255, x, y))
+
+ index += 1
+
+var counter : int
+var frame : int
+var limit = 1000
+
+func _process(_delta):
+ frame += 1
+ if frame == limit:
+ var elapsed = OS.get_system_time_msecs() - started
+ print("Result: ", elapsed)
+ get_tree().quit()
+ return
+
+ counter += 1
+ if counter == 10:
+ var center = size/2
+ map.put(center, center, true)
+ map.put(center-1, center, true)
+ map.put(center+1, center, true)
+ map.put(center, center-1, true)
+ counter = 0
+
+ map.process()
+
+ for i in self.multimesh.instance_count:
+ var tile = map.data[i]
+ if tile.v:
+ self.multimesh.set_instance_color(i, Color(1.0, 0, 0))
+ else:
+ self.multimesh.set_instance_color(i, Color(0, tile.life / 50.0, 0))
diff --git a/samples/benchmarks/gamelife/settings.gradle.kts b/samples/benchmarks/gamelife/settings.gradle.kts
new file mode 100644
index 00000000..7f3666f5
--- /dev/null
+++ b/samples/benchmarks/gamelife/settings.gradle.kts
@@ -0,0 +1,24 @@
+// The following is used for configuring composite builds, which we use for developing this binding.
+// Normally as a user, you should not need it.
+includeBuild("../../../") {
+ dependencySubstitution {
+ substitute(module("com.utopia-rise:godot-build-props")).with(project(":godot-build-props"))
+ substitute(module("com.utopia-rise:godot-annotation-processor")).with(project(":godot-annotation-processor"))
+ substitute(module("com.utopia-rise:godot-compiler-plugin")).with(project(":godot-compiler-plugin"))
+ substitute(module("com.utopia-rise:godot-compiler-plugin-common")).with(project(":godot-compiler-plugin-common"))
+ substitute(module("com.utopia-rise:godot-entry-generator")).with(project(":godot-entry-generator"))
+ substitute(module("com.utopia-rise:godot-gradle-plugin")).with(project(":godot-gradle-plugin"))
+ substitute(module("com.utopia-rise:godot-library")).with(project(":godot-library"))
+ // gradle doesn't support targeting a specific configuration (i.e shadow) when substituting a dependency in a composite build.
+ // composite-build-support depends on project(":godot-compiler-native-plugin", configuration = "shadow")
+ substitute(module("com.utopia-rise:godot-compiler-native-plugin")).with(project(":composite-build-support"))
+ }
+}
+
+pluginManagement {
+ resolutionStrategy.eachPlugin {
+ when (requested.id.id) {
+ "com.utopia-rise.godot-kotlin" -> useModule("com.utopia-rise:godot-gradle-plugin:${requested.version}")
+ }
+ }
+}
diff --git a/samples/benchmarks/gamelife/src/godotMain/kotlin/godot/samples/benchmark/gamelife/Game.kt b/samples/benchmarks/gamelife/src/godotMain/kotlin/godot/samples/benchmark/gamelife/Game.kt
new file mode 100644
index 00000000..856c8276
--- /dev/null
+++ b/samples/benchmarks/gamelife/src/godotMain/kotlin/godot/samples/benchmark/gamelife/Game.kt
@@ -0,0 +1,73 @@
+package godot.samples.benchmark.gamelife
+
+import godot.*
+import godot.core.*
+import org.godotengine.kotlin.annotation.RegisterClass
+import org.godotengine.kotlin.annotation.RegisterFunction
+import org.godotengine.kotlin.annotation.RegisterProperty
+import kotlin.math.sqrt
+
+@RegisterClass("scripts/gamelife")
+class Game : MultiMeshInstance() {
+
+ private lateinit var map: Map
+ private var started: Long = 0
+ private var counter: Int = 0
+ private var frame: Int = 0
+ private val randomNumberGenerator = RandomNumberGenerator()
+
+ @RegisterProperty(false, "50")
+ var size = 50
+ @RegisterProperty(false, "1000")
+ var limit = 1000
+
+ @RegisterFunction
+ override fun _ready() {
+ started = OS.getSystemTimeMsecs()
+ map = Map(size, size)
+ multimesh.instanceCount = (size * size).toLong()
+
+ var index = 0L
+
+ val offset = (size - 50) * 0.5
+ for (y in 0 until size) {
+ for (x in 0 until size) {
+ multimesh.setInstanceTransform(index, Transform(Basis(), Vector3(x - offset, randomNumberGenerator.randi() % 10 * 0.1, y - offset)))
+ multimesh.setInstanceColor(index, Color(255, x, y))
+ index += 1
+ }
+ }
+ }
+
+ @RegisterFunction
+ override fun _process(delta: Double) {
+ frame += 1
+ if (frame == limit) {
+ val elapsed = OS.getSystemTimeMsecs() - started
+ GD.print("Result: $elapsed")
+ getTree().quit()
+ return
+ }
+
+ counter += 1
+ if (counter == 10) {
+ val center = size / 2
+ map.put(center, center, true)
+ map.put(center - 1, center, true)
+ map.put(center + 1, center, true)
+ map.put(center, center - 1, true)
+ counter = 0
+ }
+
+ map.process()
+
+ for (i in 0 until multimesh.instanceCount) {
+ val tile = map.tileData[i.toInt()]
+ if (tile.isAlive)
+ multimesh.setInstanceColor(i, Color(1.0, 0, 0))
+ else
+ multimesh.setInstanceColor(i, Color(0, tile.life / 50.0, 0))
+ }
+ }
+}
+
diff --git a/samples/benchmarks/gamelife/src/godotMain/kotlin/godot/samples/benchmark/gamelife/Map.kt b/samples/benchmarks/gamelife/src/godotMain/kotlin/godot/samples/benchmark/gamelife/Map.kt
new file mode 100644
index 00000000..4315dedc
--- /dev/null
+++ b/samples/benchmarks/gamelife/src/godotMain/kotlin/godot/samples/benchmark/gamelife/Map.kt
@@ -0,0 +1,76 @@
+package godot.samples.benchmark.gamelife
+
+import kotlin.math.max
+
+class Map(var width: Int,
+ var height: Int) {
+
+ private var nextState: MutableList = ArrayList()
+ private var empty: Tile = Tile()
+
+ var tileData: MutableList = ArrayList()
+
+ init {
+ for (i in 0 until width * height) {
+ tileData.add(Tile())
+ nextState.add(false)
+ }
+
+ var i = 0
+ for (y in 0 until height) {
+ for (x in 0 until width) {
+ val t = tileData[i]
+ t.neighbors.addAll(listOf(
+ at(x - 1, y - 1), at(x, y - 1), at(x + 1, y - 1),
+ at(x - 1, y), at(x + 1, y),
+ at(x - 1, y + 1), at(x, y + 1), at(x + 1, y + 1)))
+ i += 1
+ }
+ }
+ }
+
+ private fun at(x: Int, y: Int): Tile {
+ val index = getIndex(x, y)
+ if (index == -1)
+ return empty
+
+ return tileData[index]
+ }
+
+ private fun getIndex(x: Int, y: Int): Int {
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ return -1
+
+ return x + y * width
+ }
+
+ fun put(x: Int, y: Int, v: Boolean) {
+ val index = getIndex(x, y)
+ if (index == -1)
+ return
+
+ tileData[index].isAlive = v
+ }
+
+ fun process() {
+ for (i in 0 until width * height) {
+ val tile = tileData[i]
+ val value = tile.getTotal()
+
+ if (tileData[i].isAlive) {
+ nextState[i] = value == 2 || value == 3
+ } else {
+ nextState[i] = value == 3
+ }
+ }
+
+ for (i in 0 until width * height) {
+ tileData[i].isAlive = nextState[i]
+
+ if (tileData[i].isAlive)
+ tileData[i].life = 20
+
+ tileData[i].life = max(0, tileData[i].life - 1)
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/benchmarks/gamelife/src/godotMain/kotlin/godot/samples/benchmark/gamelife/Tile.kt b/samples/benchmarks/gamelife/src/godotMain/kotlin/godot/samples/benchmark/gamelife/Tile.kt
new file mode 100644
index 00000000..9b289a0e
--- /dev/null
+++ b/samples/benchmarks/gamelife/src/godotMain/kotlin/godot/samples/benchmark/gamelife/Tile.kt
@@ -0,0 +1,22 @@
+package godot.samples.benchmark.gamelife
+
+data class Tile(var isAlive: Boolean = false,
+ var life: Int = 0,
+ var neighbors: MutableList = ArrayList(8)) {
+
+ fun getTotal(): Int {
+ var total = 0
+
+ for (neighbor in neighbors) {
+ total += neighbor.getValue()
+ }
+
+ return total
+ }
+
+ fun getValue(): Int {
+ if (isAlive)
+ return 1
+ return 0
+ }
+}
\ No newline at end of file