mumble-discord-bridge/pkg/sleepct/sleepct.go

93 lines
2.1 KiB
Go

package sleepct
import (
"context"
"fmt"
"time"
)
// SleepCT - Sleep constant time step crates a sleep based ticker.
// designed to maintain a consistent sleep/tick interval.
// The sleeper can be paused waiting to be signaled from another go routine.
// This allows for the pausing of loops that do not have work to complete
type SleepCT struct {
d time.Duration // desired duration between targets
t time.Time // last time target
resume chan bool
wake time.Time // last wake time
drift int64 // last wake drift microseconds
}
func (s *SleepCT) Start(d time.Duration) {
s.resume = make(chan bool, 2)
if s.t.IsZero() {
s.d = d
s.t = time.Now()
} else {
panic("SleepCT already started")
}
}
// Sleep to the next target duration.
// If pause it set to true will sleep the duration and wait to be notified.
// The notification channel will be cleared when the thread wakes.
// SleepNextTarget should not be call more than once concurrently.
func (s *SleepCT) SleepNextTarget(ctx context.Context, pause bool) int64 {
now := time.Now()
// if target is zero safety net
if s.t.IsZero() {
fmt.Println("SleepCT reset")
s.t = now.Add(-s.d)
}
// Sleep to Next Target
s.t = s.t.Add(s.d)
// Compute the desired sleep time to reach the target
d := time.Until(s.t)
// Sleep
time.Sleep(d)
// record the wake time
s.wake = time.Now()
s.drift = s.wake.Sub(s.t).Microseconds()
// fmt.Println(s.t.UnixMilli(), d.Milliseconds(), wake.UnixMilli(), drift, pause, len(s.resume))
// external pause control
if pause {
// don't pause if the notification channel has something
if len(s.resume) == 0 {
// fmt.Println("pause")
select {
case <-s.resume:
case <-ctx.Done():
// fmt.Println("sleepct ctx exit")
}
// if we did pause set the last sleep target to now
s.t = time.Now()
}
}
// Drain the resume channel
select {
case <-s.resume:
default:
}
// return the drift for monitoring purposes
return s.drift
}
// Notify attempts to resume a paused sleeper.
// It is safe to call notify from other processes and as often as desired.
func (s *SleepCT) Notify() {
select {
case s.resume <- true:
default:
}
}