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:
committed by
Facebook GitHub Bot
parent
868ae9e36a
commit
f5a5e1b19d
@@ -8,10 +8,10 @@
|
|||||||
package com.facebook.flipper.plugins.uidebugger.core
|
package com.facebook.flipper.plugins.uidebugger.core
|
||||||
|
|
||||||
import com.facebook.flipper.core.FlipperConnection
|
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.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.TreeObserverFactory
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverManager
|
||||||
|
|
||||||
data class Context(
|
data class Context(
|
||||||
val applicationRef: ApplicationRef,
|
val applicationRef: ApplicationRef,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface TreeObserverBuilder<T> {
|
|||||||
fun build(context: Context): TreeObserver<T>
|
fun build(context: Context): TreeObserver<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
class TreeObserverFactory() {
|
class TreeObserverFactory {
|
||||||
|
|
||||||
private val builders = mutableListOf<TreeObserverBuilder<*>>()
|
private val builders = mutableListOf<TreeObserverBuilder<*>>()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,94 +8,8 @@
|
|||||||
package com.facebook.flipper.plugins.uidebugger
|
package com.facebook.flipper.plugins.uidebugger
|
||||||
|
|
||||||
import android.util.Log
|
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.core.Context
|
||||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor
|
import com.facebook.flipper.plugins.uidebugger.observers.SubtreeUpdate
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Stateful class that manages some subtree in the UI Hierarchy.
|
Stateful class that manages some subtree in the UI Hierarchy.
|
||||||
@@ -153,7 +67,7 @@ abstract class TreeObserver<T> {
|
|||||||
children[childKey]!!.cleanUpRecursive()
|
children[childKey]!!.cleanUpRecursive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.treeObserverManager.emit(
|
context.treeObserverManager.send(
|
||||||
SubtreeUpdate(type, visitedNodes, start, System.currentTimeMillis()))
|
SubtreeUpdate(type, visitedNodes, start, System.currentTimeMillis()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,79 +84,3 @@ typealias HashCode = Int
|
|||||||
fun Any.identityHashCode(): HashCode {
|
fun Any.identityHashCode(): HashCode {
|
||||||
return System.identityHashCode(this)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user