Throttle with wait
Summary: Throttle events whilst keeping track of latest Reviewed By: LukeDefeo Differential Revision: D39652348 fbshipit-source-id: 9c8fb5a1bb92872985f46a62d79c6594a37e8340
This commit is contained in:
committed by
Facebook GitHub Bot
parent
fa9ba6f2d0
commit
cae15276f5
@@ -12,45 +12,47 @@ import android.view.View
|
|||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||||
import com.facebook.flipper.plugins.uidebugger.core.Context
|
import com.facebook.flipper.plugins.uidebugger.core.Context
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.scheduler.throttleLatest
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
typealias DecorView = View
|
typealias DecorView = View
|
||||||
|
|
||||||
/** Responsible for subscribing to updates to the content view of an activity */
|
/** Responsible for subscribing to updates to the content view of an activity */
|
||||||
class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
|
class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
|
||||||
|
|
||||||
val throttleTimeMs = 500
|
private val throttleTimeMs = 500L
|
||||||
|
|
||||||
private var nodeRef: WeakReference<View>? = null
|
private var nodeRef: WeakReference<View>? = null
|
||||||
private var listener: ViewTreeObserver.OnPreDrawListener? = null
|
private var listener: ViewTreeObserver.OnPreDrawListener? = null
|
||||||
|
|
||||||
override val type = "DecorView"
|
override val type = "DecorView"
|
||||||
|
|
||||||
|
private val waitScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
private val mainScope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
override fun subscribe(node: Any) {
|
override fun subscribe(node: Any) {
|
||||||
node as View
|
node as View
|
||||||
nodeRef = WeakReference(node)
|
nodeRef = WeakReference(node)
|
||||||
|
|
||||||
Log.i(LogTag, "Subscribing to decor view changes")
|
Log.i(LogTag, "Subscribing to decor view changes")
|
||||||
|
|
||||||
// TODO: there's a problem with this. Some future changes may have been
|
val throttleSend =
|
||||||
// ignored and not sent. Need to keep track of the last one, always and react
|
throttleLatest<WeakReference<View>?>(throttleTimeMs, waitScope, mainScope) { weakView ->
|
||||||
// accordingly.
|
weakView?.get()?.let { view -> traverseAndSend(context, view) }
|
||||||
listener =
|
}
|
||||||
object : ViewTreeObserver.OnPreDrawListener {
|
|
||||||
var lastSend = 0L
|
|
||||||
override fun onPreDraw(): Boolean {
|
|
||||||
if (System.currentTimeMillis() - lastSend > throttleTimeMs) {
|
|
||||||
traverseAndSend(context, node)
|
|
||||||
|
|
||||||
lastSend = System.currentTimeMillis()
|
listener =
|
||||||
}
|
ViewTreeObserver.OnPreDrawListener {
|
||||||
return true
|
throttleSend(nodeRef)
|
||||||
}
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
node.viewTreeObserver.addOnPreDrawListener(listener)
|
node.viewTreeObserver.addOnPreDrawListener(listener)
|
||||||
// sometimes we are too late to the party and we miss the first draw
|
|
||||||
listener?.onPreDraw()
|
// It can be the case that the DecorView the current observer owns has already
|
||||||
|
// drawn. In this case, manually trigger an update.
|
||||||
|
throttleSend(nodeRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unsubscribe() {
|
override fun unsubscribe() {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.scheduler
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throttle the execution of an executable for the specified interval.
|
||||||
|
*
|
||||||
|
* How does it work?
|
||||||
|
*
|
||||||
|
* The function `throttleLatest` returns a proxy for the given executable. This proxy captures the
|
||||||
|
* latest argument/param that was used on the last invocation. If the throttle job does not exist or
|
||||||
|
* has already completed, then create a new one.
|
||||||
|
*
|
||||||
|
* The job will wait on the waiting scope for the given amount of specified ms. Once it finishes
|
||||||
|
* waiting, then it will execute the given executable on the main scope with the latest captured
|
||||||
|
* param.
|
||||||
|
*/
|
||||||
|
fun <T> throttleLatest(
|
||||||
|
intervalMs: Long,
|
||||||
|
waitScope: CoroutineScope,
|
||||||
|
mainScope: CoroutineScope,
|
||||||
|
executable: (T) -> Unit
|
||||||
|
): (T) -> Unit {
|
||||||
|
var throttleJob: Job? = null
|
||||||
|
var latestParam: T
|
||||||
|
return { param: T ->
|
||||||
|
latestParam = param
|
||||||
|
if (throttleJob == null || throttleJob?.isCompleted == true) {
|
||||||
|
throttleJob =
|
||||||
|
waitScope.launch {
|
||||||
|
delay(intervalMs)
|
||||||
|
mainScope.launch { executable(latestParam) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user