Shift fetching litho attributes to background thread
Summary: Due to litho component instances being immutable we are able to process them later if we hold on to the instance. We have added a Maybe deferred type which sort of resembles a Monad. It wraps a value which may or may not be calculated later. Reviewed By: lblasa Differential Revision: D41474251 fbshipit-source-id: 2ba6e688518dba55cf4aa5ba53f390a92cf0921f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
c95c59342e
commit
7ec09b4f95
@@ -15,6 +15,8 @@ import com.facebook.flipper.plugins.uidebugger.litho.descriptors.props.LayoutPro
|
||||
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||
import com.facebook.flipper.plugins.uidebugger.util.Deferred
|
||||
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||
import com.facebook.litho.DebugComponent
|
||||
|
||||
class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescriptor<DebugComponent> {
|
||||
@@ -67,19 +69,20 @@ class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescripto
|
||||
private val UserPropsId =
|
||||
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Litho Props")
|
||||
|
||||
override fun getData(node: DebugComponent): Map<MetadataId, InspectableObject> {
|
||||
override fun getData(node: DebugComponent): MaybeDeferred<Map<MetadataId, InspectableObject>> {
|
||||
return Deferred {
|
||||
val attributeSections = mutableMapOf<MetadataId, InspectableObject>()
|
||||
|
||||
val attributeSections = mutableMapOf<MetadataId, InspectableObject>()
|
||||
val layoutProps = LayoutPropExtractor.getProps(node)
|
||||
attributeSections[LayoutId] = InspectableObject(layoutProps.toMap())
|
||||
|
||||
val layoutProps = LayoutPropExtractor.getProps(node)
|
||||
attributeSections[LayoutId] = InspectableObject(layoutProps.toMap())
|
||||
if (!node.canResolve()) {
|
||||
val props = ComponentPropExtractor.getProps(node.component)
|
||||
attributeSections[UserPropsId] = InspectableObject(props.toMap())
|
||||
}
|
||||
|
||||
if (!node.canResolve()) {
|
||||
val props = ComponentPropExtractor.getProps(node.component)
|
||||
attributeSections[UserPropsId] = InspectableObject(props.toMap())
|
||||
attributeSections
|
||||
}
|
||||
|
||||
return attributeSections
|
||||
}
|
||||
|
||||
override fun getBounds(node: DebugComponent): Bounds =
|
||||
|
||||
@@ -19,13 +19,14 @@ import com.facebook.flipper.plugins.uidebugger.model.SubtreeUpdateEvent
|
||||
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
|
||||
import com.facebook.flipper.plugins.uidebugger.scheduler.Scheduler
|
||||
import com.facebook.flipper.plugins.uidebugger.traversal.PartialLayoutTraversal
|
||||
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
data class ScanResult(
|
||||
val txId: Long,
|
||||
val scanStart: Long,
|
||||
val scanEnd: Long,
|
||||
val nodes: List<Node>
|
||||
val nodes: List<MaybeDeferred<Node>>
|
||||
)
|
||||
|
||||
const val observerType = "FullScan"
|
||||
@@ -62,6 +63,9 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task<ScanResult> {
|
||||
}
|
||||
|
||||
private fun sendSubtreeUpdate(input: ScanResult) {
|
||||
val nodes = input.nodes.map { it.value() }
|
||||
val deferredComputationComplete = System.currentTimeMillis()
|
||||
|
||||
val serialized =
|
||||
Json.encodeToString(
|
||||
SubtreeUpdateEvent.serializer(),
|
||||
@@ -69,7 +73,7 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task<ScanResult> {
|
||||
input.txId,
|
||||
observerType,
|
||||
ApplicationRefDescriptor.getId(context.applicationRef),
|
||||
input.nodes))
|
||||
nodes))
|
||||
val serializationEnd = System.currentTimeMillis()
|
||||
|
||||
context.connectionRef.connection?.send(
|
||||
@@ -89,6 +93,7 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task<ScanResult> {
|
||||
input.scanStart,
|
||||
input.scanEnd,
|
||||
input.scanEnd,
|
||||
deferredComputationComplete,
|
||||
serializationEnd,
|
||||
socketEnd,
|
||||
input.nodes.size)))
|
||||
|
||||
@@ -11,6 +11,8 @@ import android.graphics.Bitmap
|
||||
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||
import com.facebook.flipper.plugins.uidebugger.util.Immediate
|
||||
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||
|
||||
/**
|
||||
* A chained descriptor is a special type of descriptor that models the inheritance hierarchy in
|
||||
@@ -81,7 +83,7 @@ abstract class ChainedDescriptor<T> : NodeDescriptor<T> {
|
||||
|
||||
open fun onGetChildren(node: T): List<Any>? = null
|
||||
|
||||
final override fun getData(node: T): Map<MetadataId, InspectableObject> {
|
||||
final override fun getData(node: T): MaybeDeferred<Map<MetadataId, InspectableObject>> {
|
||||
val builder = mutableMapOf<MetadataId, InspectableObject>()
|
||||
onGetData(node, builder)
|
||||
|
||||
@@ -92,7 +94,7 @@ abstract class ChainedDescriptor<T> : NodeDescriptor<T> {
|
||||
curDescriptor = curDescriptor.mSuper
|
||||
}
|
||||
|
||||
return builder
|
||||
return Immediate(builder)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.graphics.Bitmap
|
||||
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||
|
||||
/*
|
||||
Descriptors are an extension point used during traversal to extract data out of arbitrary
|
||||
@@ -73,7 +74,7 @@ interface NodeDescriptor<T> {
|
||||
* Get the data to show for this node in the sidebar of the inspector. The object will be shown in
|
||||
* order and with a header matching the given name.
|
||||
*/
|
||||
fun getData(node: T): Map<MetadataId, InspectableObject>
|
||||
fun getData(node: T): MaybeDeferred<Map<MetadataId, InspectableObject>>
|
||||
|
||||
/**
|
||||
* Set of tags to describe this node in an abstract way for the UI Unfortunately this can't be an
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.graphics.Bitmap
|
||||
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||
import com.facebook.flipper.plugins.uidebugger.util.Immediate
|
||||
|
||||
object ObjectDescriptor : NodeDescriptor<Any> {
|
||||
|
||||
@@ -26,7 +27,7 @@ object ObjectDescriptor : NodeDescriptor<Any> {
|
||||
|
||||
override fun getChildren(node: Any) = listOf<Any>()
|
||||
|
||||
override fun getData(node: Any) = mutableMapOf<MetadataId, InspectableObject>()
|
||||
override fun getData(node: Any) = Immediate(mapOf<MetadataId, InspectableObject>())
|
||||
|
||||
override fun getBounds(node: Any): Bounds = Bounds(0, 0, 0, 0)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.graphics.Bitmap
|
||||
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||
|
||||
/** a drawable or view that is mounted, along with the correct descriptor */
|
||||
class OffsetChild(val child: Any, val descriptor: NodeDescriptor<Any>, val x: Int, val y: Int) {
|
||||
@@ -37,7 +38,7 @@ object OffsetChildDescriptor : NodeDescriptor<OffsetChild> {
|
||||
|
||||
override fun getActiveChild(node: OffsetChild): Any? = node.descriptor.getActiveChild(node.child)
|
||||
|
||||
override fun getData(node: OffsetChild): Map<MetadataId, InspectableObject> =
|
||||
override fun getData(node: OffsetChild): MaybeDeferred<Map<MetadataId, InspectableObject>> =
|
||||
node.descriptor.getData(node.child)
|
||||
|
||||
override fun getTags(node: OffsetChild): Set<String> = node.descriptor.getTags(node.child)
|
||||
|
||||
@@ -58,6 +58,7 @@ data class PerfStatsEvent(
|
||||
val traversalComplete: Long,
|
||||
val snapshotComplete: Long,
|
||||
val queuingComplete: Long,
|
||||
val deferredComputationComplete: Long,
|
||||
val serializationComplete: Long,
|
||||
val socketComplete: Long,
|
||||
val nodesCount: Int
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent
|
||||
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.util.MaybeDeferred
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlinx.coroutines.*
|
||||
@@ -37,7 +38,7 @@ data class CoordinateUpdate(val observerType: String, val nodeId: Id, val coordi
|
||||
data class SubtreeUpdate(
|
||||
val observerType: String,
|
||||
val rootId: Id,
|
||||
val nodes: List<Node>,
|
||||
val deferredNodes: List<MaybeDeferred<Node>>,
|
||||
val startTime: Long,
|
||||
val traversalCompleteTime: Long,
|
||||
val snapshotComplete: Long,
|
||||
@@ -104,12 +105,13 @@ class TreeObserverManager(val context: Context) {
|
||||
sendMetadata()
|
||||
|
||||
val serialized: String?
|
||||
val nodes = treeUpdate.deferredNodes.map { it.value() }
|
||||
val deferredComptationComplete = System.currentTimeMillis()
|
||||
if (treeUpdate.snapshot == null) {
|
||||
serialized =
|
||||
Json.encodeToString(
|
||||
SubtreeUpdateEvent.serializer(),
|
||||
SubtreeUpdateEvent(
|
||||
txId, treeUpdate.observerType, treeUpdate.rootId, treeUpdate.nodes))
|
||||
SubtreeUpdateEvent(txId, treeUpdate.observerType, treeUpdate.rootId, nodes))
|
||||
} else {
|
||||
val stream = ByteArrayOutputStream()
|
||||
val base64Stream = Base64OutputStream(stream, Base64.DEFAULT)
|
||||
@@ -118,8 +120,7 @@ class TreeObserverManager(val context: Context) {
|
||||
serialized =
|
||||
Json.encodeToString(
|
||||
SubtreeUpdateEvent.serializer(),
|
||||
SubtreeUpdateEvent(
|
||||
txId, treeUpdate.observerType, treeUpdate.rootId, treeUpdate.nodes, snapshot))
|
||||
SubtreeUpdateEvent(txId, treeUpdate.observerType, treeUpdate.rootId, nodes, snapshot))
|
||||
|
||||
treeUpdate.snapshot.readyForReuse()
|
||||
}
|
||||
@@ -130,7 +131,7 @@ class TreeObserverManager(val context: Context) {
|
||||
val socketEnd = System.currentTimeMillis()
|
||||
Log.i(
|
||||
LogTag,
|
||||
"Sent event for ${treeUpdate.observerType} root ID ${treeUpdate.rootId} nodes ${treeUpdate.nodes.size}")
|
||||
"Sent event for ${treeUpdate.observerType} root ID ${treeUpdate.rootId} nodes ${nodes.size}")
|
||||
|
||||
val perfStats =
|
||||
PerfStatsEvent(
|
||||
@@ -140,9 +141,10 @@ class TreeObserverManager(val context: Context) {
|
||||
traversalComplete = treeUpdate.traversalCompleteTime,
|
||||
snapshotComplete = treeUpdate.snapshotComplete,
|
||||
queuingComplete = onWorkerThread,
|
||||
deferredComputationComplete = deferredComptationComplete,
|
||||
serializationComplete = serializationEnd,
|
||||
socketComplete = socketEnd,
|
||||
nodesCount = treeUpdate.nodes.size)
|
||||
nodesCount = nodes.size)
|
||||
|
||||
context.connectionRef.connection?.send(
|
||||
PerfStatsEvent.name, Json.encodeToString(PerfStatsEvent.serializer(), perfStats))
|
||||
|
||||
@@ -14,6 +14,8 @@ import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
|
||||
import com.facebook.flipper.plugins.uidebugger.model.Node
|
||||
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
|
||||
import com.facebook.flipper.plugins.uidebugger.util.Immediate
|
||||
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||
|
||||
/**
|
||||
* This will traverse the layout hierarchy until it sees a node that has an observer registered for
|
||||
@@ -29,9 +31,9 @@ class PartialLayoutTraversal(
|
||||
@Suppress("unchecked_cast")
|
||||
internal fun NodeDescriptor<*>.asAny(): NodeDescriptor<Any> = this as NodeDescriptor<Any>
|
||||
|
||||
fun traverse(root: Any): Pair<List<Node>, List<Any>> {
|
||||
fun traverse(root: Any): Pair<List<MaybeDeferred<Node>>, List<Any>> {
|
||||
|
||||
val visited = mutableListOf<Node>()
|
||||
val visited = mutableListOf<MaybeDeferred<Node>>()
|
||||
val observableRoots = mutableListOf<Any>()
|
||||
|
||||
val stack = mutableListOf<Any>()
|
||||
@@ -53,15 +55,16 @@ class PartialLayoutTraversal(
|
||||
|
||||
if (shallow.contains(node)) {
|
||||
visited.add(
|
||||
Node(
|
||||
descriptor.getId(node),
|
||||
descriptor.getQualifiedName(node),
|
||||
descriptor.getName(node),
|
||||
emptyMap(),
|
||||
descriptor.getBounds(node),
|
||||
emptySet(),
|
||||
emptyList(),
|
||||
null))
|
||||
Immediate(
|
||||
Node(
|
||||
descriptor.getId(node),
|
||||
descriptor.getQualifiedName(node),
|
||||
descriptor.getName(node),
|
||||
emptyMap(),
|
||||
descriptor.getBounds(node),
|
||||
emptySet(),
|
||||
emptyList(),
|
||||
null)))
|
||||
|
||||
shallow.remove(node)
|
||||
continue
|
||||
@@ -93,15 +96,17 @@ class PartialLayoutTraversal(
|
||||
val bounds = descriptor.getBounds(node)
|
||||
val tags = descriptor.getTags(node)
|
||||
visited.add(
|
||||
Node(
|
||||
descriptor.getId(node),
|
||||
descriptor.getQualifiedName(node),
|
||||
descriptor.getName(node),
|
||||
attributes,
|
||||
bounds,
|
||||
tags,
|
||||
childrenIds,
|
||||
activeChildId))
|
||||
attributes.map { attrs ->
|
||||
Node(
|
||||
descriptor.getId(node),
|
||||
descriptor.getQualifiedName(node),
|
||||
descriptor.getName(node),
|
||||
attrs,
|
||||
bounds,
|
||||
tags,
|
||||
childrenIds,
|
||||
activeChildId)
|
||||
})
|
||||
} catch (exception: Exception) {
|
||||
Log.e(LogTag, "Error while processing node ${node.javaClass.name} $node", exception)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
/**
|
||||
* This abstracts over a value which could be available immediately or calculated later. The use
|
||||
* case is for shifting computation to a background thread.
|
||||
*/
|
||||
abstract class MaybeDeferred<T> {
|
||||
abstract fun value(): T
|
||||
|
||||
/** similar to map on an Option or Functor in a functional language. */
|
||||
abstract fun <U> map(fn: (T) -> U): MaybeDeferred<U>
|
||||
}
|
||||
|
||||
class Immediate<T>(private val value: T) : MaybeDeferred<T>() {
|
||||
override fun value(): T = value
|
||||
|
||||
override fun <U> map(fn: (T) -> U): MaybeDeferred<U> = Immediate(fn(value))
|
||||
}
|
||||
|
||||
class Deferred<T>(private val lazyLoader: () -> T) : MaybeDeferred<T>() {
|
||||
override fun value(): T = lazyLoader()
|
||||
override fun <U> map(fn: (T) -> U): MaybeDeferred<U> {
|
||||
return Deferred { fn(lazyLoader()) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user