diff --git a/.gitignore b/.gitignore index 961268360..2429b3ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ website/src/embedded-pages/docs/plugins/ # Logs **/*/flipper-server-log.out + +*.salive diff --git a/android/build.gradle b/android/build.gradle index 6f789dcbe..fcdab512a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -68,6 +68,7 @@ android { compileOnly deps.proguardAnnotations implementation deps.kotlinStdLibrary + implementation deps.kotlinCoroutinesAndroid implementation deps.openssl implementation deps.fbjni implementation deps.soloader diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt index ecdd2417e..7334bb458 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt @@ -8,24 +8,34 @@ package com.facebook.flipper.plugins.uidebugger import android.app.Application +import android.util.Log import com.facebook.flipper.core.FlipperConnection import com.facebook.flipper.core.FlipperPlugin -import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef -import com.facebook.flipper.plugins.uidebugger.core.ConnectionRef -import com.facebook.flipper.plugins.uidebugger.core.Context -import com.facebook.flipper.plugins.uidebugger.core.NativeScanScheduler +import com.facebook.flipper.plugins.uidebugger.core.* +import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister import com.facebook.flipper.plugins.uidebugger.model.InitEvent +import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory import com.facebook.flipper.plugins.uidebugger.scheduler.Scheduler +import kotlinx.coroutines.* import kotlinx.serialization.json.Json -val LogTag = "FlipperUIDebugger" +const val LogTag = "FlipperUIDebugger" class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin { - private val context: Context = Context(ApplicationRef(application), ConnectionRef(null)) + private val context: Context = + Context( + ApplicationRef(application), + ConnectionRef(null), + DescriptorRegister.withDefaults(), + TreeObserverFactory.withDefaults()) private val nativeScanScheduler = Scheduler(NativeScanScheduler(context)) + init { + Log.i(LogTag, "Initializing UI Debugger") + } + override fun getId(): String { return "ui-debugger" } @@ -42,16 +52,19 @@ class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin { Json.encodeToString( InitEvent.serializer(), InitEvent(rootDescriptor.getId(context.applicationRef)))) - nativeScanScheduler.start() + context.treeObserverManager.start() + // nativeScanScheduler.start() } @Throws(Exception::class) override fun onDisconnect() { this.context.connectionRef.connection = null + Log.e(LogTag, "disconnected") + this.nativeScanScheduler.stop() } override fun runInBackground(): Boolean { - return false + return true } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt index dbea0b0c9..64f2eb657 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt @@ -18,7 +18,7 @@ open class EnumMapping(val mapping: Map) { if (entry != null) { return entry.key } else { - Log.w( + Log.d( LogTag, "Could not convert enum value ${enumValue.toString()} to string, known values ${mapping.entries}") return NoMapping diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.kt index 04d66daf8..d3f09fd41 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationRef.kt @@ -15,23 +15,19 @@ import java.lang.ref.WeakReference class ApplicationRef(val application: Application) : Application.ActivityLifecycleCallbacks { interface ActivityStackChangedListener { + fun onActivityAdded(activity: Activity, stack: List) fun onActivityStackChanged(stack: List) - } - - interface ActivityDestroyedListener { - fun onActivityDestroyed(activity: Activity) + fun onActivityDestroyed(activity: Activity, stack: List) } private val rootsResolver: RootViewResolver private val activities: MutableList> private var activityStackChangedlistener: ActivityStackChangedListener? = null - private var activityDestroyedListener: ActivityDestroyedListener? = null override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { activities.add(WeakReference(activity)) - activityStackChangedlistener?.let { listener -> - listener.onActivityStackChanged(this.activitiesStack) - } + activityStackChangedlistener?.onActivityAdded(activity, this.activitiesStack) + activityStackChangedlistener?.onActivityStackChanged(this.activitiesStack) } override fun onActivityStarted(activity: Activity) {} override fun onActivityResumed(activity: Activity) {} @@ -47,21 +43,14 @@ class ApplicationRef(val application: Application) : Application.ActivityLifecyc } } - activityDestroyedListener?.let { listener -> listener.onActivityDestroyed(activity) } - - activityStackChangedlistener?.let { listener -> - listener.onActivityStackChanged(this.activitiesStack) - } + activityStackChangedlistener?.onActivityDestroyed(activity, this.activitiesStack) + activityStackChangedlistener?.onActivityStackChanged(this.activitiesStack) } fun setActivityStackChangedListener(listener: ActivityStackChangedListener?) { activityStackChangedlistener = listener } - fun setActivityDestroyedListener(listener: ActivityDestroyedListener?) { - activityDestroyedListener = listener - } - val activitiesStack: List get() { val stack: MutableList = ArrayList(activities.size) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt index fb795e4df..c74269116 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt @@ -8,12 +8,21 @@ package com.facebook.flipper.plugins.uidebugger.core import com.facebook.flipper.core.FlipperConnection +import com.facebook.flipper.plugins.uidebugger.PartialLayoutTraversal +import com.facebook.flipper.plugins.uidebugger.TreeObserverManager import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister +import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory data class Context( val applicationRef: ApplicationRef, val connectionRef: ConnectionRef, - val descriptorRegister: DescriptorRegister = DescriptorRegister.withDefaults() -) + val descriptorRegister: DescriptorRegister, + val observerFactory: TreeObserverFactory, +) { + val layoutTraversal: PartialLayoutTraversal = + PartialLayoutTraversal(descriptorRegister, observerFactory) + + val treeObserverManager = TreeObserverManager(this) +} data class ConnectionRef(var connection: FlipperConnection?) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt index 0406d2803..eb71128d5 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt @@ -59,6 +59,7 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task { result.txId, result.scanStart, result.scanEnd, + result.scanEnd, serializationEnd, socketEnd, result.nodes.size))) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt index 19d6e4b68..d76b8fbf7 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt @@ -10,10 +10,8 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.app.Activity import com.facebook.flipper.plugins.uidebugger.common.InspectableObject import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef -import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver class ApplicationRefDescriptor : AbstractChainedDescriptor() { - val rootResolver = RootViewResolver() override fun onInit() {} override fun onGetActiveChild(node: ApplicationRef): Any? { @@ -32,22 +30,8 @@ class ApplicationRefDescriptor : AbstractChainedDescriptor() { } override fun onGetChildren(applicationRef: ApplicationRef, children: MutableList) { - val activeRoots = rootResolver.listActiveRootViews() - - activeRoots?.let { roots -> - for (root: RootViewResolver.RootView in roots) { - var added = false - for (activity: Activity in applicationRef.activitiesStack) { - if (activity.window.decorView == root.view) { - children.add(activity) - added = true - break - } - } - if (!added) { - children.add(root.view) - } - } + for (activity: Activity in applicationRef.activitiesStack) { + children.add(activity) } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt index 873a7ca8f..54b91acef 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt @@ -14,6 +14,13 @@ data class InitEvent(val rootId: String) { } } +@kotlinx.serialization.Serializable +data class SubtreeUpdateEvent(val txId: Long, val observerType: String, val nodes: List) { + companion object { + const val name = "subtreeUpdate" + } +} + @kotlinx.serialization.Serializable data class NativeScanEvent(val txId: Long, val nodes: List) { companion object { @@ -26,7 +33,8 @@ data class NativeScanEvent(val txId: Long, val nodes: List) { data class PerfStatsEvent( val txId: Long, val start: Long, - val scanComplete: Long, + val traversalComplete: Long, + val queuingComplete: Long, val serializationComplete: Long, val socketComplete: Long, val nodesCount: Int diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt new file mode 100644 index 000000000..4374a2156 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ApplicationTreeObserver.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.flipper.plugins.uidebugger.observers + +import android.app.Activity +import android.content.ContextWrapper +import android.util.Log +import android.view.View +import com.facebook.flipper.plugins.uidebugger.LogTag +import com.facebook.flipper.plugins.uidebugger.SubtreeUpdate +import com.facebook.flipper.plugins.uidebugger.TreeObserver +import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef +import com.facebook.flipper.plugins.uidebugger.core.Context +import com.facebook.flipper.plugins.uidebugger.identityHashCode + +/** + * responsible for observing the activity stack and managing the subscription to the top most + * content view (decor view) + */ +class ApplicationTreeObserver(val context: Context) : TreeObserver() { + + override fun subscribe(node: Any) { + Log.i(LogTag, "subscribing to application / activity changes") + + val applicationRef = node as ApplicationRef + + val addRemoveListener = + object : ApplicationRef.ActivityStackChangedListener { + + override fun onActivityAdded(activity: Activity, stack: List) { + val start = System.currentTimeMillis() + val (nodes, skipped) = context.layoutTraversal.traverse(applicationRef) + val observer = + context.observerFactory.createObserver(activity.window.decorView, context)!! + observer.subscribe(activity.window.decorView) + children[activity.window.decorView.identityHashCode()] = observer + context.treeObserverManager.emit( + SubtreeUpdate("Application", nodes, start, System.currentTimeMillis())) + Log.i( + LogTag, + "Activity added,stack size ${stack.size} found ${nodes.size} skipped $skipped Listeners $children") + } + + override fun onActivityStackChanged(stack: List) {} + + override fun onActivityDestroyed(activity: Activity, stack: List) { + val start = System.currentTimeMillis() + + val (nodes, skipped) = context.layoutTraversal.traverse(applicationRef) + + val observer = children[activity.window.decorView.identityHashCode()] + children.remove(activity.window.decorView.identityHashCode()) + observer?.cleanUpRecursive() + + context.treeObserverManager.emit( + SubtreeUpdate("Application", nodes, start, System.currentTimeMillis())) + + Log.i( + LogTag, + "Activity removed,stack size ${stack.size} found ${nodes.size} skipped $skipped Listeners $children") + } + } + + context.applicationRef.setActivityStackChangedListener(addRemoveListener) + + Log.i(LogTag, "${context.applicationRef.rootViews.size} root views") + Log.i(LogTag, "${context.applicationRef.activitiesStack.size} activities") + + val stack = context.applicationRef.activitiesStack + for (activity in stack) { + addRemoveListener.onActivityAdded(activity, stack) + } + } + private fun getActivity(view: View): Activity? { + var context: android.content.Context? = view.context + while (context is ContextWrapper) { + if (context is Activity) { + return context + } + context = context.baseContext + } + return null + } + + override fun unsubscribe() { + context.applicationRef.setActivityStackChangedListener(null) + } +} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt new file mode 100644 index 000000000..cae214627 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.flipper.plugins.uidebugger.observers + +import android.util.Log +import android.view.View +import android.view.ViewTreeObserver +import com.facebook.flipper.plugins.uidebugger.LogTag +import com.facebook.flipper.plugins.uidebugger.SubtreeUpdate +import com.facebook.flipper.plugins.uidebugger.TreeObserver +import com.facebook.flipper.plugins.uidebugger.core.Context + +typealias DecorView = View + +/** Responsible for subscribing to updates to the content view of an activity */ +class DecorViewObserver(val context: Context) : TreeObserver() { + + val throttleTimeMs = 500 + + // maybe should be weak reference in ctor? + private var nodeRef: View? = null + private var listener: ViewTreeObserver.OnPreDrawListener? = null + + override fun subscribe(node: Any) { + + node as View + nodeRef = node + + Log.i(LogTag, "Subscribing to decor view changes") + + listener = + object : ViewTreeObserver.OnPreDrawListener { + var lastSend = 0L + override fun onPreDraw(): Boolean { + val start = System.currentTimeMillis() + if (start - lastSend > throttleTimeMs) { + val (nodes, skipped) = context.layoutTraversal.traverse(node) + val traversalComplete = System.currentTimeMillis() + context.treeObserverManager.emit( + SubtreeUpdate("DecorView", nodes, start, traversalComplete)) + lastSend = System.currentTimeMillis() + } + return true + } + } + + node.viewTreeObserver.addOnPreDrawListener(listener) + } + + override fun unsubscribe() { + Log.i(LogTag, "Try Unsubscribing to decor view changes") + + listener.let { + Log.i(LogTag, "Actually Unsubscribing to decor view changes") + + nodeRef?.viewTreeObserver?.removeOnPreDrawListener(it) + listener = null + nodeRef = null + } + } +} + +object DecorViewTreeObserverBuilder : TreeObserverBuilder { + override fun canBuildFor(node: Any): Boolean { + return node.javaClass.simpleName.contains("DecorView") + } + + override fun build(context: Context): TreeObserver { + Log.i(LogTag, "Building decor view observer") + return DecorViewObserver(context) + } +} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ObserverFactory.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ObserverFactory.kt new file mode 100644 index 000000000..9fd757423 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/ObserverFactory.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.flipper.plugins.uidebugger.observers + +import com.facebook.flipper.plugins.uidebugger.TreeObserver +import com.facebook.flipper.plugins.uidebugger.core.Context + +interface TreeObserverBuilder { + + fun canBuildFor(node: Any): Boolean + fun build(context: Context): TreeObserver +} + +class TreeObserverFactory() { + + private val builders = mutableListOf>() + + fun register(builder: TreeObserverBuilder) { + builders.add(builder) + } + + fun hasObserverFor(node: Any): Boolean { + return builders.any { it.canBuildFor(node) } + } + + fun createObserver(node: Any, context: Context): TreeObserver<*>? { + return builders.find { it.canBuildFor(node) }?.build(context) + } + + companion object { + fun withDefaults(): TreeObserverFactory { + val factory = TreeObserverFactory() + factory.register(DecorViewTreeObserverBuilder) + + return factory + } + } +} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt new file mode 100644 index 000000000..88ad2b3a8 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt @@ -0,0 +1,189 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.flipper.plugins.uidebugger + +import android.util.Log +import com.facebook.flipper.plugins.uidebugger.common.InspectableObject +import com.facebook.flipper.plugins.uidebugger.core.Context +import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor +import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister +import com.facebook.flipper.plugins.uidebugger.model.Node +import com.facebook.flipper.plugins.uidebugger.model.PerfStatsEvent +import com.facebook.flipper.plugins.uidebugger.model.SubtreeUpdateEvent +import com.facebook.flipper.plugins.uidebugger.observers.ApplicationTreeObserver +import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory +import java.util.concurrent.atomic.AtomicInteger +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.serialization.json.Json + +data class SubtreeUpdate( + val observerType: String, + val nodes: List, + val startTime: Long, + val traversalCompleteTime: Long +) + +/** Holds the instances of Tree observers */ +class TreeObserverManager(val context: Context) { + + private val rootObserver = ApplicationTreeObserver(context) + private val treeUpdates = Channel(Channel.UNLIMITED) + private val workerScope = CoroutineScope(Dispatchers.IO) + private val txId = AtomicInteger() + + fun emit(update: SubtreeUpdate) { + treeUpdates.trySend(update) + } + + /** + * 1. Sets up the root observer + * 2. Starts worker to listen to channel, which serializers and sends data over connection + */ + fun start() { + + rootObserver.subscribe(context.applicationRef) + + workerScope.launch { + while (isActive) { + try { + + val observation = treeUpdates.receive() + + val onWorkerThread = System.currentTimeMillis() + + val txId = txId.getAndIncrement().toLong() + val serialized = + Json.encodeToString( + SubtreeUpdateEvent.serializer(), + SubtreeUpdateEvent(txId, observation.observerType, observation.nodes)) + + val serializationEnd = System.currentTimeMillis() + + context.connectionRef.connection?.send(SubtreeUpdateEvent.name, serialized) + val socketEnd = System.currentTimeMillis() + Log.i( + LogTag, "Sent event for ${observation.observerType} nodes ${observation.nodes.size}") + + val perfStats = + PerfStatsEvent( + txId = txId, + start = observation.startTime, + traversalComplete = observation.traversalCompleteTime, + queuingComplete = onWorkerThread, + serializationComplete = serializationEnd, + socketComplete = socketEnd, + nodesCount = observation.nodes.size) + context.connectionRef.connection?.send( + PerfStatsEvent.name, Json.encodeToString(PerfStatsEvent.serializer(), perfStats)) + } catch (e: java.lang.Exception) { + Log.e(LogTag, "Error in channel ", e) + } + } + + Log.i(LogTag, "shutting down worker") + } + } + + fun stop() { + rootObserver.cleanUpRecursive() + treeUpdates.close() + workerScope.cancel() + } +} + +/* +Stateful class that manages some subtree in the UI Hierarchy. +It is responsible for: + 1. listening to the relevant framework events + 2. Traversing the hierarchy of the managed nodes + 3. Diffing to previous state (optional) + 4. Pushing out updates for its entire set of nodes + + If while traversing it encounters a node type which has its own TreeObserver, it + does not traverse that, instead it sets up a Tree observer responsible for that subtree + + The parent is responsible for detecting when a child observer needs to be cleaned up +*/ +abstract class TreeObserver { + + protected val children: MutableMap> = mutableMapOf() + + // todo try to pass T again? + abstract fun subscribe(node: Any) + + abstract fun unsubscribe() + + fun cleanUpRecursive() { + children.values.forEach { it.cleanUpRecursive() } + unsubscribe() + children.clear() + } +} + +typealias HashCode = Int + +fun Any.identityHashCode(): HashCode { + return System.identityHashCode(this) +} + +class PartialLayoutTraversal( + private val descriptorRegister: DescriptorRegister, + private val treeObserverfactory: TreeObserverFactory, +) { + + internal fun Descriptor<*>.asAny(): Descriptor = this as Descriptor + + fun traverse(root: Any): Pair, List> { + + val visited = mutableListOf() + val skipped = mutableListOf() + val stack = mutableListOf() + stack.add(root) + + while (stack.isNotEmpty()) { + + val node = stack.removeLast() + + try { + + // if we encounter a node that has it own observer, dont traverse + if (node != root && treeObserverfactory.hasObserverFor(node)) { + skipped.add(node) + continue + } + + val descriptor = descriptorRegister.descriptorForClassUnsafe(node::class.java).asAny() + + val children = mutableListOf() + descriptor.getChildren(node, children) + + val childrenIds = mutableListOf() + for (child in children) { + // it might make sense one day to remove id from the descriptor since its always the + // hash code + val childDescriptor = + descriptorRegister.descriptorForClassUnsafe(child::class.java).asAny() + childrenIds.add(childDescriptor.getId(child)) + stack.add(child) + } + + val attributes = mutableMapOf() + descriptor.getData(node, attributes) + + // NOTE active child null here + visited.add( + Node(descriptor.getId(node), descriptor.getName(node), attributes, childrenIds, null)) + } catch (exception: Exception) { + Log.e(LogTag, "Error while processing node ${node.javaClass.name} ${node} ", exception) + } + } + + return Pair(visited, skipped) + } +} diff --git a/build.gradle b/build.gradle index 9c929ffb8..77e5107eb 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,7 @@ ext { ext.deps = [ // Kotlin support kotlinStdLibrary : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION", + kotlinCoroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4", // Android support supportAnnotations : "androidx.annotation:annotation:$ANDROIDX_VERSION", supportAppCompat : "androidx.appcompat:appcompat:$ANDROIDX_VERSION", diff --git a/desktop/plugins/public/ui-debugger/components/main.tsx b/desktop/plugins/public/ui-debugger/components/main.tsx index f98f43c54..f4dcd23b9 100644 --- a/desktop/plugins/public/ui-debugger/components/main.tsx +++ b/desktop/plugins/public/ui-debugger/components/main.tsx @@ -71,17 +71,24 @@ export const columns: DataTableColumn[] = [ }, }, { - key: 'scanComplete', - title: 'Scan time', + key: 'traversalComplete', + title: 'Traversal time (Main thread)', onRender: (row: PerfStatsEvent) => { - return formatDiff(row.start, row.scanComplete); + return formatDiff(row.start, row.traversalComplete); + }, + }, + { + key: 'queuingComplete', + title: 'Queuing time', + onRender: (row: PerfStatsEvent) => { + return formatDiff(row.traversalComplete, row.queuingComplete); }, }, { key: 'serializationComplete', title: 'Serialization time', onRender: (row: PerfStatsEvent) => { - return formatDiff(row.scanComplete, row.serializationComplete); + return formatDiff(row.queuingComplete, row.serializationComplete); }, }, { diff --git a/desktop/plugins/public/ui-debugger/index.tsx b/desktop/plugins/public/ui-debugger/index.tsx index 9f030384d..c104332c2 100644 --- a/desktop/plugins/public/ui-debugger/index.tsx +++ b/desktop/plugins/public/ui-debugger/index.tsx @@ -13,8 +13,9 @@ import {Id, UINode} from './types'; export type PerfStatsEvent = { txId: number; start: number; - scanComplete: number; + traversalComplete: number; serializationComplete: number; + queuingComplete: number; socketComplete: number; nodesCount: number; }; @@ -22,6 +23,7 @@ export type PerfStatsEvent = { type Events = { init: {rootId: string}; nativeScan: {txId: number; nodes: UINode[]}; + subtreeUpdate: {txId: number; nodes: UINode[]}; perfStats: PerfStatsEvent; }; @@ -38,9 +40,17 @@ export function plugin(client: PluginClient) { }); const nodesAtom = createState>(new Map()); + client.onMessage('subtreeUpdate', ({nodes}) => { + nodesAtom.update((draft) => { + for (const node of nodes) { + draft.set(node.id, node); + } + }); + }); + client.onMessage('nativeScan', ({nodes}) => { + //Native scan is a full update so overwrite everything nodesAtom.set(new Map(nodes.map((node) => [node.id, node]))); - console.log(nodesAtom.get()); }); return {rootId, nodes: nodesAtom, perfEvents};