forked from go/golangs_learn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
3.1 KiB
143 lines
3.1 KiB
3 years ago
|
package main
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// Timer implements an
|
||
|
type Timer struct {
|
||
|
// Updated is used to notify UI about changes in the timer.
|
||
|
Updated chan struct{}
|
||
|
|
||
|
// mu locks the state such that it can be modified and accessed
|
||
|
// from multiple goroutines.
|
||
|
mu sync.Mutex
|
||
|
start time.Time // start corresponds to when the timer was started.
|
||
|
now time.Time // now corresponds to the last updated time.
|
||
|
duration time.Duration // duration is the maximum progress.
|
||
|
}
|
||
|
|
||
|
// NewTimer creates a new timer with the specified timer.
|
||
|
func NewTimer(initialDuration time.Duration) *Timer {
|
||
|
return &Timer{
|
||
|
Updated: make(chan struct{}),
|
||
|
duration: initialDuration,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Start the timer goroutine and return a cancel func that
|
||
|
// that can be used to stop it.
|
||
|
func (t *Timer) Start() context.CancelFunc {
|
||
|
// initialize the timer state.
|
||
|
now := time.Now()
|
||
|
t.now = now
|
||
|
t.start = now
|
||
|
|
||
|
// we use done to signal stopping the goroutine.
|
||
|
// a context.Context could be also used.
|
||
|
done := make(chan struct{})
|
||
|
go t.run(done)
|
||
|
return func() { close(done) }
|
||
|
}
|
||
|
|
||
|
// run is the main loop for the timer.
|
||
|
func (t *Timer) run(done chan struct{}) {
|
||
|
// we use a time.Ticker to update the state,
|
||
|
// in many cases, this could be a network access instead.
|
||
|
tick := time.NewTicker(50 * time.Millisecond)
|
||
|
defer tick.Stop()
|
||
|
|
||
|
for {
|
||
|
select {
|
||
|
case now := <-tick.C:
|
||
|
t.update(now)
|
||
|
case <-done:
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// invalidate sends a signal to the UI that
|
||
|
// the internal state has changed.
|
||
|
func (t *Timer) invalidate() {
|
||
|
// we use a non-blocking send, that way the Timer
|
||
|
// can continue updating internally.
|
||
|
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
|
||
|
|
||
|
// first check whether we have not exceeded the duration.
|
||
|
// in that case the progress advanced and we need to notify
|
||
|
// about a change.
|
||
|
progressAfter := t.now.Sub(t.start)
|
||
|
if progressAfter <= t.duration {
|
||
|
t.invalidate()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// when we had progressed beyond the duration we also
|
||
|
// need to update the first time it happens.
|
||
|
progressBefore := previousNow.Sub(t.start)
|
||
|
if progressBefore <= t.duration {
|
||
|
t.invalidate()
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Reset resets timer to the last know time.
|
||
|
func (t *Timer) Reset() {
|
||
|
t.mu.Lock()
|
||
|
defer t.mu.Unlock()
|
||
|
|
||
|
t.start = t.now
|
||
|
t.invalidate()
|
||
|
}
|
||
|
|
||
|
// SetDuration changes the duration of the timer.
|
||
|
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 the latest know info about the timer.
|
||
|
func (t *Timer) Info() (info Info) {
|
||
|
t.mu.Lock()
|
||
|
defer t.mu.Unlock()
|
||
|
|
||
|
info.Progress = t.now.Sub(t.start)
|
||
|
info.Duration = t.duration
|
||
|
if info.Progress > info.Duration {
|
||
|
info.Progress = info.Duration
|
||
|
}
|
||
|
return info
|
||
|
}
|
||
|
|
||
|
// Info is the information about the timer.
|
||
|
type Info struct {
|
||
|
Progress time.Duration
|
||
|
Duration time.Duration
|
||
|
}
|
||
|
|
||
|
// ProgressString returns the progress formatted as seconds.
|
||
|
func (info *Info) ProgressString() string {
|
||
|
return fmt.Sprintf("%.1fs", info.Progress.Seconds())
|
||
|
}
|