From f5a5e1b19d991a135fd8b3d0f842d3814bd6d39b Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Tue, 13 Sep 2022 11:05:42 -0700 Subject: [PATCH] Split Tree Observer into multiple files Summary: Tree observer had multiple components, now their usage is clearer they are moved to separate files Reviewed By: lblasa Differential Revision: D39469078 fbshipit-source-id: 4d4c03aff229fd2cc0eace216144b37694637691 --- .../plugins/uidebugger/core/Context.kt | 4 +- .../uidebugger/observers/ObserverFactory.kt | 2 +- .../observers/PartialLayoutTraversal.kt | 91 ++++++++++ .../uidebugger/observers/TreeObserver.kt | 166 +----------------- .../observers/TreeObserverManager.kt | 94 ++++++++++ 5 files changed, 190 insertions(+), 167 deletions(-) create mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/PartialLayoutTraversal.kt create mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt 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 c74269116..724b3711c 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,10 +8,10 @@ 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.PartialLayoutTraversal import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory +import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverManager data class Context( val applicationRef: ApplicationRef, 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 index 9fd757423..0fa779bef 100644 --- 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 @@ -16,7 +16,7 @@ interface TreeObserverBuilder { fun build(context: Context): TreeObserver } -class TreeObserverFactory() { +class TreeObserverFactory { private val builders = mutableListOf>() diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/PartialLayoutTraversal.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/PartialLayoutTraversal.kt new file mode 100644 index 000000000..b8bacee8b --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/PartialLayoutTraversal.kt @@ -0,0 +1,91 @@ +/* + * 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 com.facebook.flipper.plugins.uidebugger.LogTag +import com.facebook.flipper.plugins.uidebugger.common.InspectableObject +import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor +import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister +import com.facebook.flipper.plugins.uidebugger.model.Node + +/** + * This will traverse the layout hierarchy untill it sees a node that has an observer registered for + * it. The first item in the pair is the visited nodes The second item are any observable roots + * discovered + */ +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 observableRoots = 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)) { + observableRoots.add(node) + continue + } + + val descriptor = descriptorRegister.descriptorForClassUnsafe(node::class.java).asAny() + + val children = mutableListOf() + descriptor.getChildren(node, children) + + val childrenIds = mutableListOf() + val activeChild = descriptor.getActiveChild(node) + + 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)) + // if there is an active child then dont traverse it + if (activeChild == null) { + stack.add(child) + } + } + var activeChildId: String? = null + if (activeChild != null) { + stack.add(activeChild) + activeChildId = + descriptorRegister.descriptorForClassUnsafe(activeChild.javaClass).getId(activeChild) + } + + val attributes = mutableMapOf() + descriptor.getData(node, attributes) + + visited.add( + Node( + descriptor.getId(node), + descriptor.getName(node), + attributes, + childrenIds, + activeChildId)) + } catch (exception: Exception) { + Log.e(LogTag, "Error while processing node ${node.javaClass.name} ${node} ", exception) + } + } + + return Pair(visited, observableRoots) + } +} 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 index d7960a908..cfc6f0cd3 100644 --- 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 @@ -8,94 +8,8 @@ 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 treeUpdate = treeUpdates.receive() - - val onWorkerThread = System.currentTimeMillis() - - val txId = txId.getAndIncrement().toLong() - val serialized = - Json.encodeToString( - SubtreeUpdateEvent.serializer(), - SubtreeUpdateEvent(txId, treeUpdate.observerType, treeUpdate.nodes)) - - val serializationEnd = System.currentTimeMillis() - - context.connectionRef.connection?.send(SubtreeUpdateEvent.name, serialized) - val socketEnd = System.currentTimeMillis() - Log.i(LogTag, "Sent event for ${treeUpdate.observerType} nodes ${treeUpdate.nodes.size}") - - val perfStats = - PerfStatsEvent( - txId = txId, - observerType = treeUpdate.observerType, - start = treeUpdate.startTime, - traversalComplete = treeUpdate.traversalCompleteTime, - queuingComplete = onWorkerThread, - serializationComplete = serializationEnd, - socketComplete = socketEnd, - nodesCount = treeUpdate.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() - } -} +import com.facebook.flipper.plugins.uidebugger.observers.SubtreeUpdate /* Stateful class that manages some subtree in the UI Hierarchy. @@ -153,7 +67,7 @@ abstract class TreeObserver { children[childKey]!!.cleanUpRecursive() } } - context.treeObserverManager.emit( + context.treeObserverManager.send( SubtreeUpdate(type, visitedNodes, start, System.currentTimeMillis())) } @@ -170,79 +84,3 @@ typealias HashCode = Int fun Any.identityHashCode(): HashCode { return System.identityHashCode(this) } - -/** - * This will traverse the layout hierarchy untill it sees a node that has an observer registered for - * it. The first item in the pair is the visited nodes The second item are any observable roots - * discovered - */ -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 observableRoots = 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)) { - observableRoots.add(node) - continue - } - - val descriptor = descriptorRegister.descriptorForClassUnsafe(node::class.java).asAny() - - val children = mutableListOf() - descriptor.getChildren(node, children) - - val childrenIds = mutableListOf() - val activeChild = descriptor.getActiveChild(node) - - 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)) - // if there is an active child then dont traverse it - if (activeChild == null) { - stack.add(child) - } - } - var activeChildId: String? = null - if (activeChild != null) { - stack.add(activeChild) - activeChildId = - descriptorRegister.descriptorForClassUnsafe(activeChild.javaClass).getId(activeChild) - } - - val attributes = mutableMapOf() - descriptor.getData(node, attributes) - - visited.add( - Node( - descriptor.getId(node), - descriptor.getName(node), - attributes, - childrenIds, - activeChildId)) - } catch (exception: Exception) { - Log.e(LogTag, "Error while processing node ${node.javaClass.name} ${node} ", exception) - } - } - - return Pair(visited, observableRoots) - } -} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt new file mode 100644 index 000000000..5d6abe69b --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt @@ -0,0 +1,94 @@ +/* + * 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 com.facebook.flipper.plugins.uidebugger.LogTag +import com.facebook.flipper.plugins.uidebugger.core.Context +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 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 root observer and manages sending updates to desktop */ +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 send(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 treeUpdate = treeUpdates.receive() + + val onWorkerThread = System.currentTimeMillis() + + val txId = txId.getAndIncrement().toLong() + val serialized = + Json.encodeToString( + SubtreeUpdateEvent.serializer(), + SubtreeUpdateEvent(txId, treeUpdate.observerType, treeUpdate.nodes)) + + val serializationEnd = System.currentTimeMillis() + + context.connectionRef.connection?.send(SubtreeUpdateEvent.name, serialized) + val socketEnd = System.currentTimeMillis() + Log.i(LogTag, "Sent event for ${treeUpdate.observerType} nodes ${treeUpdate.nodes.size}") + + val perfStats = + PerfStatsEvent( + txId = txId, + observerType = treeUpdate.observerType, + start = treeUpdate.startTime, + traversalComplete = treeUpdate.traversalCompleteTime, + queuingComplete = onWorkerThread, + serializationComplete = serializationEnd, + socketComplete = socketEnd, + nodesCount = treeUpdate.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() + } +}