Allow Kotlin coroutines
This method allows clients to use coroutines for async methods by
supplying a helper suspend method for each function.
This provides (IMHO) the ideal API for Kotlin apps and retains the
option to switch to an alternative method using Futures at a later
date.
Bug: 330292651
Change-Id: Iaf9b464e80ea7f3fcf2dfae8c211c722e4a0e311
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/191380
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: dan sinclair <dsinclair@google.com>
Commit-Queue: Jim Blackler <jimblackler@google.com>
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index 90bf762..eb5d614 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -837,6 +837,17 @@
def jni_name(type):
return kt_file_path + '/' + type.name.CamelCase()
+ # We assume that if the final two parameters are named 'userdata' and 'callback' respectively
+ # that this is an async method that uses function pointer based callbacks.
+ def is_async_method(method):
+ if len(method.arguments) < 3:
+ return False # Not enough parameters to be an async method.
+ if method.arguments[-1].name.get() != 'userdata':
+ return False
+ if method.arguments[-2].name.get() != 'callback':
+ return False
+ return True
+
# A structure may need to know which other structures listed it as a chain root, e.g.
# to know whether to mark the generated class 'open'.
chain_children = defaultdict(list)
@@ -847,6 +858,7 @@
params_kotlin['include_structure_member'] = include_structure_member
params_kotlin['include_method'] = include_method
params_kotlin['jni_name'] = jni_name
+ params_kotlin['is_async_method'] = is_async_method
return params_kotlin
@@ -1504,6 +1516,10 @@
'java/' + kt_file_path + '/Functions.kt',
[RENDER_PARAMS_BASE, params_kotlin]))
renders.append(
+ FileRender('art/api_kotlin_async_helpers.kt',
+ 'java/' + kt_file_path + '/AsyncHelpers.kt',
+ [RENDER_PARAMS_BASE, params_kotlin]))
+ renders.append(
FileRender('art/structures.h', 'cpp/structures.h',
[RENDER_PARAMS_BASE, params_kotlin]))
renders.append(
diff --git a/generator/templates/art/api_kotlin_async_helpers.kt b/generator/templates/art/api_kotlin_async_helpers.kt
new file mode 100644
index 0000000..1e1ad89
--- /dev/null
+++ b/generator/templates/art/api_kotlin_async_helpers.kt
@@ -0,0 +1,70 @@
+//* Copyright 2024 The Dawn & Tint Authors
+//*
+//* Redistribution and use in source and binary forms, with or without
+//* modification, are permitted provided that the following conditions are met:
+//*
+//* 1. Redistributions of source code must retain the above copyright notice, this
+//* list of conditions and the following disclaimer.
+//*
+//* 2. Redistributions in binary form must reproduce the above copyright notice,
+//* this list of conditions and the following disclaimer in the documentation
+//* and/or other materials provided with the distribution.
+//*
+//* 3. Neither the name of the copyright holder nor the names of its
+//* contributors may be used to endorse or promote products derived from
+//* this software without specific prior written permission.
+//*
+//* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+package {{ kotlin_package }}
+
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+{% from 'art/api_kotlin_types.kt' import kotlin_declaration, kotlin_definition with context %}
+
+//* We make a return class for every function pointer so that usage of callback-using methods can be
+// replaced with suspend (async) function that returns the same data.
+{% for function_pointer
+ in by_category['function pointer'] if len(function_pointer.name.chunks) > 1 %}
+ //* Function pointers generally end in Callback which we replace with Return.
+ {% set return_name = function_pointer.name.chunks[:-1] | map('title') | join + 'Return' %}
+ data class {{ return_name }}(
+ //* Kotlin doesn't need userdata because of captures, so we omit it.
+ {% for arg in function_pointer.arguments if arg.name.get() != 'userdata' %}
+ val {{ as_varName(arg.name) }}: {{ kotlin_declaration(arg) }},
+ {% endfor %})
+{% endfor %}
+
+//* Every method that is identified as using callbacks is given a helper method that wraps the
+//* call with a suspend function.
+{% for obj in by_category['object'] %}
+ {% for method in obj.methods if is_async_method(method) %}
+ {% set function_pointer = method.arguments[-2].type %}
+ {% set return_name = function_pointer.name.chunks[:-1] | map('title') | join + 'Return' %}
+ suspend fun {{ obj.name.CamelCase() }}.{{ method.name.camelCase() }}(
+ {%- for arg in method.arguments[:-2] %}
+ {{- as_varName(arg.name) }}: {{ kotlin_definition(arg) }},
+ {%- endfor %}) = suspendCoroutine {
+ {{ method.name.camelCase() }}(
+ {%- for arg in method.arguments[:-2] %}
+ {{- as_varName(arg.name) }},
+ {% endfor %}) {
+ {%- for arg in function_pointer.arguments if arg.name.get() != 'userdata' %}
+ {{- as_varName(arg.name) }},
+ {%- endfor %} -> it.resume({{ return_name }}(
+ {%- for arg in function_pointer.arguments if arg.name.get() != 'userdata' %}
+ {{- as_varName(arg.name) }},
+ {%- endfor %})
+ )
+ }
+ }
+ {% endfor %}
+{% endfor %}
diff --git a/tools/android/BUILD.gn b/tools/android/BUILD.gn
index b7caaae..05fb81c 100644
--- a/tools/android/BUILD.gn
+++ b/tools/android/BUILD.gn
@@ -38,6 +38,7 @@
"java/android/dawn/AdapterProperties.kt",
"java/android/dawn/AdapterType.kt",
"java/android/dawn/AddressMode.kt",
+ "java/android/dawn/AsyncHelpers.kt",
"java/android/dawn/BackendType.kt",
"java/android/dawn/BindGroup.kt",
"java/android/dawn/BindGroupDescriptor.kt",