Skip to content

Commit

Permalink
Merge pull request #469 from player-ui/async-node-android
Browse files Browse the repository at this point in the history
Async node android
  • Loading branch information
sakuntala-motukuri authored Aug 6, 2024
2 parents 0c12e2a + a1bd8f6 commit 28d0e47
Show file tree
Hide file tree
Showing 9 changed files with 388 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.intuit.playerui.core.bridge.hooks

import com.intuit.hooks.AsyncParallelBailHook
import com.intuit.hooks.BailResult
import com.intuit.hooks.HookContext
import com.intuit.playerui.core.bridge.Node
import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable

@OptIn(ExperimentalCoroutinesApi::class)
@Serializable(with = NodeAsyncParallelBailHook1.Serializer::class)
public class NodeAsyncParallelBailHook1<T1, R : Any>(override val node: Node, serializer1: KSerializer<T1>) : AsyncParallelBailHook<(HookContext, T1) -> BailResult<R>, R>(), AsyncNodeHook<R> {

init {
init(serializer1)
}

override suspend fun callAsync(context: HookContext, serializedArgs: Array<Any?>): R {
require(serializedArgs.size == 1) { "Expected exactly one argument, but got ${serializedArgs.size}" }
val (p1) = serializedArgs
val result = call(10) { f, _ ->
f(context, p1 as T1)
} as R
return result
}

/** The return type serializer is never used, but in order to generate the serializer with @Serializable for [NodeAsyncParallelBailHook1], it's needed */
internal class Serializer<T1, R : Any>(private val serializer1: KSerializer<T1>, `_`: KSerializer<R>) : NodeWrapperSerializer<NodeAsyncParallelBailHook1<T1, R>>({
NodeAsyncParallelBailHook1(it, serializer1)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.intuit.hooks.HookContext
import com.intuit.playerui.core.bridge.Invokable
import com.intuit.playerui.core.bridge.Node
import com.intuit.playerui.core.bridge.NodeWrapper
import com.intuit.playerui.core.bridge.Promise
import com.intuit.playerui.core.bridge.getInvokable
import com.intuit.playerui.core.utils.InternalPlayerApi
import kotlinx.serialization.ExperimentalSerializationApi
Expand Down Expand Up @@ -38,5 +39,14 @@ internal interface NodeHook<R> : NodeWrapper {
fun call(context: HookContext, serializedArgs: Array<Any?>): R
}

internal interface AsyncNodeHook<R : Any> : NodeHook<Promise> {
override fun call(context: HookContext, serializedArgs: Array<Any?>): Promise = node.runtime.Promise { resolve, reject ->
val result = callAsync(context, serializedArgs)
resolve(result)
}

suspend fun callAsync(context: HookContext, serializedArgs: Array<Any?>): R
}

@InternalPlayerApi
public inline val callingStackTraceElement: StackTraceElement get() = Thread.currentThread().stackTrace[1]
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.intuit.playerui.core.bridge

import com.intuit.playerui.core.bridge.runtime.add
import com.intuit.playerui.core.bridge.runtime.serialize
import com.intuit.playerui.core.player.PlayerException
import com.intuit.playerui.core.player.PlayerFlowStatus
Expand All @@ -8,8 +9,10 @@ import com.intuit.playerui.core.player.state.PlayerFlowState
import com.intuit.playerui.utils.normalizeStackTraceElements
import com.intuit.playerui.utils.test.PromiseUtils
import com.intuit.playerui.utils.test.RuntimeTest
import com.intuit.playerui.utils.test.runBlockingTest
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
Expand Down Expand Up @@ -219,4 +222,27 @@ internal class PromiseTest : RuntimeTest(), PromiseUtils {
assertEquals(exception.message, caught.message)
assertEquals(exception.stackTrace.normalizeStackTraceElements(), caught.stackTrace.normalizeStackTraceElements())
}

@TestTemplate
fun testPromiseConstructor() = runBlockingTest {
runtime.execute(
"""
class TestClass {
name = 'foo';
constructor(promise) {
promise.then((value) => {
this.name = value;
});
};
}""",
)
val promise = runtime.Promise<String> { jsRes, _ ->
val result = "bar"
jsRes(result)
}
runtime.add("myPromise", promise)
val value = runtime.execute("new TestClass(myPromise)") as Node
promise.toCompletable(String.serializer()).await()
assertEquals("bar", value["name"])
}
}
1 change: 1 addition & 0 deletions jvm/hermes/src/main/jni/JHermesRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class JHermesConfig : public HybridClass<JHermesConfig> {
static local_ref<jhybridobject> create(alias_ref<jclass>, bool intl = true, bool microtaskQueue = false) {
auto config = hermes::vm::RuntimeConfig::Builder()
.withIntl(intl)
.withES6Class(true)
.withMicrotaskQueue(microtaskQueue)
.build();

Expand Down
10 changes: 10 additions & 0 deletions plugins/async-node/jvm/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
load("//plugins:defs.bzl", "kt_player_plugin")
load(":deps.bzl", "main_deps", "main_exports", "main_resources")

kt_player_plugin(
name = "async-node",
main_deps = main_deps,
main_exports = main_exports,
main_resources = main_resources,
test_package = "com.intuit.playerui.plugins.asyncnode",
)
21 changes: 21 additions & 0 deletions plugins/async-node/jvm/api/async-node.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
public final class com/intuit/player/jvm/plugins/asyncnode/AsyncNodePlugin : com/intuit/player/jvm/core/plugins/JSScriptPluginWrapper {
public field hooks Lcom/intuit/player/jvm/plugins/asyncnode/AsyncNodePlugin$Hooks;
public fun <init> ()V
public fun apply (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)V
public final fun getHooks ()Lcom/intuit/player/jvm/plugins/asyncnode/AsyncNodePlugin$Hooks;
public final fun setHooks (Lcom/intuit/player/jvm/plugins/asyncnode/AsyncNodePlugin$Hooks;)V
}

public final class com/intuit/player/jvm/plugins/asyncnode/AsyncNodePlugin$Hooks : com/intuit/player/jvm/core/bridge/NodeWrapper {
public static final field Companion Lcom/intuit/player/jvm/plugins/asyncnode/AsyncNodePlugin$Hooks$Companion;
public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node;
public final fun getOnAsyncNode ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeAsyncParallelBailHook1;
}

public final class com/intuit/player/jvm/plugins/asyncnode/AsyncNodePlugin$Hooks$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class com/intuit/player/jvm/plugins/asyncnode/AsyncNodePluginKt {
public static final fun getAsyncNodePlugin (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/plugins/asyncnode/AsyncNodePlugin;
}
11 changes: 11 additions & 0 deletions plugins/async-node/jvm/deps.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
main_exports = [
"//jvm/core",
]

main_deps = main_exports + [
"//jvm:kotlin_serialization",
]

main_resources = [
"//plugins/async-node/core:core_native_bundle",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.intuit.playerui.plugins.asyncnode

import com.intuit.playerui.core.bridge.Node
import com.intuit.playerui.core.bridge.NodeWrapper
import com.intuit.playerui.core.bridge.hooks.NodeAsyncParallelBailHook1
import com.intuit.playerui.core.bridge.runtime.Runtime
import com.intuit.playerui.core.bridge.runtime.ScriptContext
import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer
import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField
import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializer
import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer
import com.intuit.playerui.core.player.Player
import com.intuit.playerui.core.player.PlayerException
import com.intuit.playerui.core.plugins.JSScriptPluginWrapper
import com.intuit.playerui.core.plugins.findPlugin
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer

public class AsyncNodePlugin : JSScriptPluginWrapper(pluginName, sourcePath = bundledSourcePath) {

public lateinit var hooks: Hooks

override fun apply(runtime: Runtime<*>) {
runtime.load(ScriptContext(script, bundledSourcePath))
instance = runtime.buildInstance("(new $pluginName({plugins: [new AsyncNodePlugin.AsyncNodePluginPlugin()]}))")
hooks = instance.getSerializable("hooks", Hooks.serializer()) ?: throw PlayerException("AsyncNodePlugin is not loaded correctly")
}

@Serializable(with = Hooks.Serializer::class)
public class Hooks internal constructor(override val node: Node) : NodeWrapper {
/** The hook right before the View starts resolving. Attach anything custom here */
public val onAsyncNode: NodeAsyncParallelBailHook1<Node, List<Map<String, Any?>>> by NodeSerializableField(NodeAsyncParallelBailHook1.serializer(NodeSerializer(), ListSerializer(MapSerializer(String.serializer(), GenericSerializer()))))
internal object Serializer : NodeWrapperSerializer<Hooks>(AsyncNodePlugin::Hooks)
}

private companion object {
private const val bundledSourcePath = "plugins/async-node/core/dist/AsyncNodePlugin.native.js"
private const val pluginName = "AsyncNodePlugin.AsyncNodePlugin"
}
}

/** Convenience getter to find the first [AsyncNodePlugin] registered to the [Player] */
public val Player.asyncNodePlugin: AsyncNodePlugin? get() = findPlugin()
Loading

0 comments on commit 28d0e47

Please sign in to comment.