-
Notifications
You must be signed in to change notification settings - Fork 1
/
debouncer.go
138 lines (127 loc) · 3.58 KB
/
debouncer.go
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
129
130
131
132
133
134
135
136
137
138
/*
© 2021–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
ISC License
*/
package parl
import (
"sync/atomic"
"time"
)
// Debouncer debounces event stream values.
// T values are received from the in channel.
// Once d time has elapsed with no further incoming Ts,
// a slice of read Ts are provided to the send function.
// - the debouncer may be held up indefinitely for an uninterrupted stream of Ts
// - two threads are launched per debouncer
// - errFn receives any panics in the threads
// - sender and errFn functions must be thread-safe.
// - Debouncer is shutdown gracefully by input channel close or
// immediately using Shutdown method
type Debouncer[T any] struct {
duration time.Duration
inputCh <-chan T
buffer NBChan[T] // non-blocking unbound buffer
timer *time.Timer
sender func([]T)
errFn func(err error)
inputEndCh, outputEndCh, shutdownCh chan struct{}
isShutdown atomic.Bool
}
// NewDebouncer returns a channel debouncer
func NewDebouncer[T any](
duration time.Duration,
inputCh <-chan T,
sender func([]T),
errFn func(err error)) (debouncer *Debouncer[T]) {
db := Debouncer[T]{
duration: duration,
inputCh: inputCh,
sender: sender,
errFn: errFn,
timer: time.NewTimer(time.Second),
inputEndCh: make(chan struct{}),
outputEndCh: make(chan struct{}),
shutdownCh: make(chan struct{}),
}
db.timer.Stop()
if len(db.timer.C) > 0 {
<-db.timer.C
}
return &db
}
// Go launches the debouncer thread
func (d *Debouncer[T]) Go() (debouncer *Debouncer[T]) {
debouncer = d
go d.inputThread()
go d.outputThread()
return
}
func (d *Debouncer[T]) Shutdown() {
if d.isShutdown.CompareAndSwap(false, true) {
close(d.shutdownCh)
}
d.Wait()
}
// Wait blocks until the debouncer exits
// - the debouncer exits from in channel close or context cancel
func (d *Debouncer[T]) Wait() {
<-d.inputEndCh
<-d.outputEndCh
}
// debouncerThread debounces the in channel until it closes or context cancel
func (d *Debouncer[T]) inputThread() {
defer close(d.inputEndCh)
defer Recover(Annotation(), nil, d.errFn)
// read input channel save in buffer and reset timer
var noShutdown = true
for {
var value T
var ok bool
select {
case value, ok = <-d.inputCh:
case _, noShutdown = <-d.shutdownCh:
}
if !ok || !noShutdown {
d.buffer.Close()
return // input channel closed return
}
d.buffer.Send(value)
// Stop prevents the Timer from firing
d.timer.Stop()
// drain the channel without blocking
if len(d.timer.C) > 0 {
select {
case <-d.timer.C:
default:
}
}
// Reset should be invoked only on:
// - stopped or expired timers
// - with drained channels
d.timer.Reset(d.duration)
}
}
// debouncerThread debounces the in channel until it closes or context cancel
func (d *Debouncer[T]) outputThread() {
defer close(d.outputEndCh)
defer Recover(Annotation(), nil, d.errFn)
// wait for timer to elapse or input thread to exit
var inputThreadOK = true
var noShutdown = true
for {
select {
case <-d.timer.C: // wait for debounce time
case _, inputThreadOK = <-d.inputEndCh: // wait for input thread to exit
case _, noShutdown = <-d.shutdownCh:
}
if !noShutdown {
return // shutdown received
}
if values := d.buffer.Get(); len(values) > 0 {
d.sender(values)
}
if !inputThreadOK {
return // input thread ended and buffer is empty return
}
}
}