aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/ca/allanwang/kau/kotlin/Debouncer.kt
blob: de5d1489b275e5ab7286d152adfd11a22fcb47ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package ca.allanwang.kau.kotlin

import ca.allanwang.kau.logging.KL
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

/**
 * Created by Allan Wang on 2017-08-05.
 *
 * Thread safe function wrapper to allow for debouncing
 * With reference to <a href="https://stackoverflow.com/a/20978973/4407321">Stack Overflow</a>
 */

/**
 * The debouncer base
 * Implements everything except for the callback,
 * as the number of variables is different between implementations
 * You may still use this without extending it, but you'll have to pass a callback each time
 */
open class Debouncer(var interval: Long) {
    private val sched = Executors.newScheduledThreadPool(1)
    private var task: DebounceTask? = null

    /**
     * Generic invocation to pass a callback to the new task
     * Pass a new callback for the task
     * If another task is pending, it will be invalidated
     */
    operator fun invoke(callback: () -> Unit) {
        synchronized(this) {
            task?.invalidate()
            val newTask = DebounceTask(callback)
            KL.v { "Debouncer task created: $newTask in $this" }
            sched.schedule(newTask, interval, TimeUnit.MILLISECONDS)
            task = newTask
        }
    }

    /**
     * Call to cancel all pending requests and shutdown the thread pool
     * The debouncer cannot be used after this
     */
    fun terminate() {
        task?.invalidate()
        task = null
        sched.shutdownNow()
    }

    /**
     * Invalidate any pending tasks
     */
    fun cancel() {
        synchronized(this) {
            if (task != null) KL.v { "Debouncer cancelled for $task in $this" }
            task?.invalidate()
            task = null
        }
    }

}

/*
 * Helper extensions for functions with 0 to 3 arguments
 */

/**
 * The debounced task
 * Holds a callback to execute if the time has come and it is still valid
 * All methods can be viewed as synchronous as the invocation is synchronous
 */
private class DebounceTask(inline val callback: () -> Unit) : Runnable {
    private var valid = true

    fun invalidate() {
        valid = false
    }

    override fun run() {
        if (!valid) return
        valid = false
        KL.v { "Debouncer task executed $this" }
        try {
            callback()
        } catch (e: Exception) {
            KL.e(e) { "DebouncerTask exception" }
        }
    }
}

/**
 * A zero input debouncer
 */
class Debouncer0 internal constructor(interval: Long, val callback: () -> Unit) : Debouncer(interval) {
    operator fun invoke() = invoke(callback)
}

fun debounce(interval: Long, callback: () -> Unit) = Debouncer0(interval, callback)
fun (() -> Unit).debounce(interval: Long) = debounce(interval, this)

/**
 * A one argument input debouncer
 */
class Debouncer1<T> internal constructor(interval: Long, val callback: (T) -> Unit) : Debouncer(interval) {
    operator fun invoke(key: T) = invoke { callback(key) }
}

fun <T> debounce(interval: Long, callback: (T) -> Unit) = Debouncer1(interval, callback)
fun <T> ((T) -> Unit).debounce(interval: Long) = debounce(interval, this)

/**
 * A two argument input debouncer
 */
class Debouncer2<T, V> internal constructor(interval: Long, val callback: (T, V) -> Unit) : Debouncer(interval) {
    operator fun invoke(arg0: T, arg1: V) = invoke { callback(arg0, arg1) }
}

fun <T, V> debounce(interval: Long, callback: (T, V) -> Unit) = Debouncer2(interval, callback)
fun <T, V> ((T, V) -> Unit).debounce(interval: Long) = debounce(interval, this)

/**
 * A three argument input debouncer
 */
class Debouncer3<T, U, V> internal constructor(interval: Long, val callback: (T, U, V) -> Unit) : Debouncer(interval) {
    operator fun invoke(arg0: T, arg1: U, arg2: V) = invoke { callback(arg0, arg1, arg2) }
}

fun <T, U, V> debounce(interval: Long, callback: ((T, U, V) -> Unit)) = Debouncer3(interval, callback)
fun <T, U, V> ((T, U, V) -> Unit).debounce(interval: Long) = debounce(interval, this)