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
This commit is contained in:
Luke De Feo
2022-09-13 11:05:42 -07:00
committed by Facebook GitHub Bot
parent 868ae9e36a
commit f5a5e1b19d
5 changed files with 190 additions and 167 deletions

View File

@@ -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,

View File

@@ -16,7 +16,7 @@ interface TreeObserverBuilder<T> {
fun build(context: Context): TreeObserver<T>
}
class TreeObserverFactory() {
class TreeObserverFactory {
private val builders = mutableListOf<TreeObserverBuilder<*>>()

View File

@@ -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<Any> = this as Descriptor<Any>
fun traverse(root: Any): Pair<MutableList<Node>, List<Any>> {
val visited = mutableListOf<Node>()
val observableRoots = mutableListOf<Any>()
val stack = mutableListOf<Any>()
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<Any>()
descriptor.getChildren(node, children)
val childrenIds = mutableListOf<String>()
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<String, InspectableObject>()
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)
}
}

View File

@@ -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<Node>,
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<SubtreeUpdate>(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<T> {
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<Any> = this as Descriptor<Any>
fun traverse(root: Any): Pair<MutableList<Node>, List<Any>> {
val visited = mutableListOf<Node>()
val observableRoots = mutableListOf<Any>()
val stack = mutableListOf<Any>()
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<Any>()
descriptor.getChildren(node, children)
val childrenIds = mutableListOf<String>()
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<String, InspectableObject>()
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)
}
}

View File

@@ -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<Node>,
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<SubtreeUpdate>(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()
}
}