Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
rovn208 committed Jun 23, 2023
1 parent f1527dd commit cdb4632
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@

# Dependency directories (remove the comment below to include it)
# vendor/
.idea
94 changes: 91 additions & 3 deletions debouncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,68 @@ type Debouncer struct {
triggeredFunc func()
mu sync.Mutex
done chan struct{}
options Options
}
type Options struct {
Leading, Trailing bool
}

type DebouncerOptions func(*Debouncer)

type DebouncerType string

const (
TRAILING DebouncerType = "trailing"
LEADING = "leading"
OVERLAPPED = "overlapped"
INACTIVE = "inactive"
)

// New creates a new instance of debouncer. Each instance of debouncer works independent, concurrency with different wait duration.
func New(duration time.Duration) *Debouncer {
return &Debouncer{timeDuration: duration, triggeredFunc: func() {}}
return &Debouncer{timeDuration: duration, triggeredFunc: func() {}, options: Options{false, true}}
}

func NewWithOptions(opts ...DebouncerOptions) *Debouncer {
var (
defaultDuration = 1 * time.Minute
defaultOptions = Options{false, true}
defaultTriggeredFunc = func() {}
)

d := &Debouncer{
timeDuration: defaultDuration,
triggeredFunc: defaultTriggeredFunc,
options: defaultOptions,
done: make(chan struct{}),
}

for _, opt := range opts {
opt(d)
}

return d
}

// WithOptions sets the options of debouncer instance.
func WithOptions(options Options) DebouncerOptions {
return func(d *Debouncer) {
d.options = options
}
}

// WithTriggered sets the triggered function of debouncer instance.
func WithTriggered(triggeredFunc func()) DebouncerOptions {
return func(d *Debouncer) {
d.triggeredFunc = triggeredFunc
}
}

// WithTimeDuration sets the time duration of debouncer instance.
func WithTimeDuration(timeDuration time.Duration) DebouncerOptions {
return func(d *Debouncer) {
d.timeDuration = timeDuration
}
}

// WithTriggered attached a triggered function to debouncer instance and return the same instance of debouncer to use.
Expand All @@ -29,14 +86,45 @@ func (d *Debouncer) SendSignal() {
d.mu.Lock()
defer d.mu.Unlock()

d.Cancel()
d.timer = time.AfterFunc(d.timeDuration, func() {
switch d.getDebounceType() {
case TRAILING:
d.Cancel()
d.timer = d.invokeTriggeredFunc()
case LEADING:
if d.timer != nil {
d.timer = d.invokeTriggeredFunc()
}
case OVERLAPPED:
d.timer = d.invokeTriggeredFunc()
default:
}

}

func (d *Debouncer) invokeTriggeredFunc() *time.Timer {
return time.AfterFunc(d.timeDuration, func() {
d.triggeredFunc()
close(d.done)
d.done = make(chan struct{})
})
}

func (d *Debouncer) getDebounceType() DebouncerType {
if !d.options.Leading && d.options.Trailing {
return TRAILING
}

if d.options.Leading && !d.options.Trailing {
return LEADING
}

if d.options.Leading && d.options.Trailing {
return OVERLAPPED
}

return INACTIVE
}

// Do run the signalFunc() and call SendSignal() after all. The signalFunc() and SendSignal() function run sequentially.
func (d *Debouncer) Do(signalFunc func()) {
signalFunc()
Expand Down
47 changes: 37 additions & 10 deletions debouncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,20 @@ import (
)

func Example() {
wait := 5 * time.Second
debouncer := godebouncer.New(wait).WithTriggered(func() {
fmt.Println("Trigger") // Triggered func will be called after 5 seconds from last SendSignal().
})

fmt.Println("Action 1")
duration := 5 * time.Second
debouncer := godebouncer.NewWithOptions(
godebouncer.WithTimeDuration(duration),
godebouncer.WithTriggered(func() {
// Triggered func will be called after 5 seconds from last SendSignal()/Do().
fmt.Println("Trigger")
}),
)
debouncer.SendSignal()

time.Sleep(1 * time.Second)

fmt.Println("Action 2")
debouncer.SendSignal()

// After 5 seconds, the trigger will be called.
//Previous `SendSignal()` will be ignore to trigger the triggered function.
// Previous `SendSignal()` will be ignored to trigger the triggered function.
<-debouncer.Done()
}

Expand Down Expand Up @@ -211,6 +210,34 @@ func TestDebounceUpdateDurationAfterSendSignal(t *testing.T) {
}
}

// Test Debounce Leading
func TestDebounceLeading(t *testing.T) {
countPtr, incrementCount := createIncrementCount(0)
debouncer := godebouncer.NewWithOptions(
godebouncer.WithTimeDuration(300*time.Millisecond),
godebouncer.WithTriggered(incrementCount),
godebouncer.WithOptions(godebouncer.Options{Leading: true, Trailing: false}),
)

expectedCounter := 1

debouncer.SendSignal()
debouncer.SendSignal()
//debouncer.SendSignal()
<-debouncer.Done()
//time.Sleep(300 * time.Millisecond)
//debouncer.SendSignal()
//
//debouncer.SendSignal()
//debouncer.SendSignal()
//
//<-debouncer.Done()

if *countPtr != expectedCounter {
t.Errorf("Expected count %d, was %d", expectedCounter, *countPtr)
}
}

func TestDone(t *testing.T) {
countPtr, incrementCount := createIncrementCount(0)
debouncer := godebouncer.New(200 * time.Millisecond).WithTriggered(incrementCount)
Expand Down

0 comments on commit cdb4632

Please sign in to comment.