Files
prevara/examples/timer/timer.go

123 lines
2.0 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"context"
"sync"
"time"
)
// Timer implements a timer with adjustable duration.
type Timer struct {
// Updated is used to notify UI about changes in the timer.
Updated chan struct{}
mu sync.Mutex
start time.Time
now time.Time
duration time.Duration
}
// NewTimer creates a new timer with the specified duration.
func NewTimer(initialDuration time.Duration) *Timer {
return &Timer{
Updated: make(chan struct{}),
duration: initialDuration,
}
}
// Start the timer goroutine and return a cancel func.
func (t *Timer) Start() context.CancelFunc {
now := time.Now()
t.now = now
t.start = now
done := make(chan struct{})
go t.run(done)
return func() { close(done) }
}
func (t *Timer) run(done chan struct{}) {
tick := time.NewTicker(50 * time.Millisecond)
defer tick.Stop()
for {
select {
case now := <-tick.C:
t.update(now)
case <-done:
return
}
}
}
func (t *Timer) invalidate() {
select {
case t.Updated <- struct{}{}:
default:
}
}
func (t *Timer) update(now time.Time) {
t.mu.Lock()
defer t.mu.Unlock()
previousNow := t.now
t.now = now
progressAfter := t.now.Sub(t.start)
if progressAfter <= t.duration {
t.invalidate()
return
}
progressBefore := previousNow.Sub(t.start)
if progressBefore <= t.duration {
t.invalidate()
return
}
}
// Reset resets the timer.
func (t *Timer) Reset() {
t.mu.Lock()
defer t.mu.Unlock()
t.start = t.now
t.invalidate()
}
// SetDuration changes the duration.
func (t *Timer) SetDuration(duration time.Duration) {
t.mu.Lock()
defer t.mu.Unlock()
if t.duration == duration {
return
}
t.duration = duration
t.invalidate()
}
// Info returns timer information.
type Info struct {
Progress time.Duration
Duration time.Duration
}
// Info returns the current timer state.
func (t *Timer) Info() Info {
t.mu.Lock()
defer t.mu.Unlock()
info := Info{
Progress: t.now.Sub(t.start),
Duration: t.duration,
}
if info.Progress > info.Duration {
info.Progress = info.Duration
}
return info
}