Add debouncer
This commit is contained in:
53
debouncer/debouncer.go
Normal file
53
debouncer/debouncer.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package debouncer
|
||||
|
||||
import "time"
|
||||
|
||||
type Debouncer struct {
|
||||
input <-chan bool
|
||||
C <-chan bool
|
||||
}
|
||||
|
||||
func New(ch <-chan bool) *Debouncer {
|
||||
output := make(chan bool)
|
||||
var currentState bool
|
||||
var lastState bool
|
||||
falseTimer := time.NewTimer(0)
|
||||
falseTimer.Stop()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-falseTimer.C:
|
||||
if !currentState && lastState {
|
||||
output <- false
|
||||
lastState = false
|
||||
} else {
|
||||
falseTimer.Reset(200 * time.Millisecond)
|
||||
}
|
||||
case v, ok := <-ch:
|
||||
if !ok {
|
||||
falseTimer.Reset(200 * time.Millisecond)
|
||||
<-falseTimer.C
|
||||
output <- false
|
||||
close(output)
|
||||
return
|
||||
}
|
||||
if v {
|
||||
if !lastState {
|
||||
output <- true
|
||||
lastState = true
|
||||
falseTimer.Reset(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
currentState = true
|
||||
} else {
|
||||
falseTimer.Reset(200 * time.Millisecond)
|
||||
|
||||
currentState = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
d := &Debouncer{input: ch, C: output}
|
||||
|
||||
return d
|
||||
}
|
89
debouncer/debouncer_test.go
Normal file
89
debouncer/debouncer_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package debouncer_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.t-juice.club/torjus/ghettoptt/debouncer"
|
||||
)
|
||||
|
||||
func TestDebouncer(t *testing.T) {
|
||||
t.Run("Simple", func(t *testing.T) {
|
||||
ch := make(chan bool)
|
||||
d := debouncer.New(ch)
|
||||
|
||||
go func() {
|
||||
ch <- true
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
if v := <-d.C; v != true {
|
||||
t.Errorf("Expected true, got %v", v)
|
||||
}
|
||||
})
|
||||
t.Run("TrueTrueTrueFalse", func(t *testing.T) {
|
||||
ch := make(chan bool)
|
||||
d := debouncer.New(ch)
|
||||
go func() {
|
||||
ch <- true
|
||||
ch <- true
|
||||
ch <- true
|
||||
ch <- false
|
||||
close(ch)
|
||||
}()
|
||||
if v := <-d.C; v != true {
|
||||
t.Errorf("Expected true, got %v", v)
|
||||
}
|
||||
if v := <-d.C; v != false {
|
||||
t.Errorf("Expected false, got %v", v)
|
||||
}
|
||||
})
|
||||
t.Run("Debounce", func(t *testing.T) {
|
||||
ch := make(chan bool)
|
||||
d := debouncer.New(ch)
|
||||
go func() {
|
||||
ch <- true
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ch <- false
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
ch <- true
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
ch <- false
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
close(ch)
|
||||
}()
|
||||
if v := <-d.C; v != true {
|
||||
t.Errorf("Expected first value to be true, got %v", v)
|
||||
}
|
||||
if v := <-d.C; v != false {
|
||||
t.Errorf("Expected second value to be false, got %v", v)
|
||||
}
|
||||
|
||||
if v, ok := <-d.C; ok {
|
||||
t.Errorf("Expected closed channel, got %v", v)
|
||||
}
|
||||
})
|
||||
t.Run("DebounceDelay", func(t *testing.T) {
|
||||
ch := make(chan bool)
|
||||
d := debouncer.New(ch)
|
||||
go func() {
|
||||
ch <- true
|
||||
ch <- false
|
||||
close(ch)
|
||||
}()
|
||||
start := time.Now()
|
||||
if v := <-d.C; v != true {
|
||||
t.Errorf("Expected first value to be true, got %v", v)
|
||||
}
|
||||
if v, ok := <-d.C; v != false || !ok {
|
||||
if ok {
|
||||
t.Errorf("Expected second value to be false, got %v", v)
|
||||
} else {
|
||||
t.Error("Unexpected closed channel")
|
||||
}
|
||||
}
|
||||
if duration := time.Since(start); duration < 200*time.Millisecond {
|
||||
t.Errorf("Got false too soon: %s", duration)
|
||||
}
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user