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:
Lorenzo Blasa
2022-09-21 13:28:17 -07:00
committed by Facebook GitHub Bot
parent fa9ba6f2d0
commit cae15276f5
2 changed files with 64 additions and 16 deletions

View File

@@ -12,45 +12,47 @@ import android.view.View
import android.view.ViewTreeObserver
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.scheduler.throttleLatest
import java.lang.ref.WeakReference
import kotlinx.coroutines.*
typealias DecorView = View
/** Responsible for subscribing to updates to the content view of an activity */
class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
val throttleTimeMs = 500
private val throttleTimeMs = 500L
private var nodeRef: WeakReference<View>? = null
private var listener: ViewTreeObserver.OnPreDrawListener? = null
override val type = "DecorView"
private val waitScope = CoroutineScope(Dispatchers.IO)
private val mainScope = CoroutineScope(Dispatchers.Main)
override fun subscribe(node: Any) {
node as View
nodeRef = WeakReference(node)
Log.i(LogTag, "Subscribing to decor view changes")
// TODO: there's a problem with this. Some future changes may have been
// ignored and not sent. Need to keep track of the last one, always and react
// accordingly.
listener =
object : ViewTreeObserver.OnPreDrawListener {
var lastSend = 0L
override fun onPreDraw(): Boolean {
if (System.currentTimeMillis() - lastSend > throttleTimeMs) {
traverseAndSend(context, node)
val throttleSend =
throttleLatest<WeakReference<View>?>(throttleTimeMs, waitScope, mainScope) { weakView ->
weakView?.get()?.let { view -> traverseAndSend(context, view) }
}
lastSend = System.currentTimeMillis()
}
return true
}
listener =
ViewTreeObserver.OnPreDrawListener {
throttleSend(nodeRef)
true
}
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() {

View File

@@ -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) }
}
}
}
}