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.
213 lines
4.5 KiB
213 lines
4.5 KiB
3 years ago
|
package neo
|
||
|
|
||
|
import (
|
||
|
"sort"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// Timer abstracts a single event.
|
||
|
type Timer interface {
|
||
|
C() <-chan time.Time
|
||
|
Stop() bool
|
||
|
Reset(d time.Duration)
|
||
|
}
|
||
|
|
||
|
// Ticker abstracts a channel that delivers ``ticks'' of a clock at intervals.
|
||
|
type Ticker interface {
|
||
|
C() <-chan time.Time
|
||
|
Stop()
|
||
|
Reset(d time.Duration)
|
||
|
}
|
||
|
|
||
|
// NewTime returns new temporal simulator.
|
||
|
func NewTime(now time.Time) *Time {
|
||
|
return &Time{
|
||
|
now: now,
|
||
|
moments: map[int]moment{},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Time simulates temporal interactions.
|
||
|
//
|
||
|
// All methods are goroutine-safe.
|
||
|
type Time struct {
|
||
|
// mux guards internal state. Note that all methods without Unlocked
|
||
|
// suffix acquire mux.
|
||
|
mux sync.Mutex
|
||
|
now time.Time
|
||
|
momentID int
|
||
|
|
||
|
moments map[int]moment
|
||
|
observers []chan struct{}
|
||
|
}
|
||
|
|
||
|
func (t *Time) Timer(d time.Duration) Timer {
|
||
|
tt := &timer{
|
||
|
time: t,
|
||
|
ch: make(chan time.Time, 1),
|
||
|
}
|
||
|
tt.id = t.plan(t.When(d), tt.do)
|
||
|
return tt
|
||
|
}
|
||
|
|
||
|
func (t *Time) Ticker(d time.Duration) Ticker {
|
||
|
tt := &ticker{
|
||
|
time: t,
|
||
|
ch: make(chan time.Time, 1),
|
||
|
dur: d,
|
||
|
}
|
||
|
tt.id = t.plan(t.When(d), tt.do)
|
||
|
return tt
|
||
|
}
|
||
|
|
||
|
func (t *Time) planUnlocked(when time.Time, do func(now time.Time)) int {
|
||
|
id := t.momentID
|
||
|
t.momentID++
|
||
|
t.moments[id] = moment{
|
||
|
when: when,
|
||
|
do: do,
|
||
|
}
|
||
|
t.observeUnlocked()
|
||
|
return id
|
||
|
}
|
||
|
|
||
|
func (t *Time) plan(when time.Time, do func(now time.Time)) int {
|
||
|
t.mux.Lock()
|
||
|
defer t.mux.Unlock()
|
||
|
|
||
|
return t.planUnlocked(when, do)
|
||
|
}
|
||
|
|
||
|
// stop removes the moment with the given ID from the list of scheduled moments.
|
||
|
// It returns true if a moment existed for the given ID, otherwise it is no-op.
|
||
|
func (t *Time) stop(id int) bool {
|
||
|
t.mux.Lock()
|
||
|
defer t.mux.Unlock()
|
||
|
|
||
|
_, ok := t.moments[id]
|
||
|
delete(t.moments, id)
|
||
|
return ok
|
||
|
}
|
||
|
|
||
|
// reset adjusts the moment with the given ID to run after the d duration. It
|
||
|
// creates a new moment if the moment does not already exist. If durp pointer
|
||
|
// is not nil, it is updated with d value while reset is holding Time’s lock.
|
||
|
func (t *Time) reset(d time.Duration, id int, do func(now time.Time), durp *time.Duration) {
|
||
|
t.mux.Lock()
|
||
|
defer t.mux.Unlock()
|
||
|
t.resetUnlocked(d, id, do, durp)
|
||
|
}
|
||
|
|
||
|
// resetUnlocked is like reset but does not acquire the Time’s lock.
|
||
|
func (t *Time) resetUnlocked(d time.Duration, id int, do func(now time.Time), durp *time.Duration) {
|
||
|
if durp != nil {
|
||
|
*durp = d
|
||
|
}
|
||
|
|
||
|
m, ok := t.moments[id]
|
||
|
if !ok {
|
||
|
m = moment{do: do}
|
||
|
}
|
||
|
|
||
|
m.when = t.now.Add(d)
|
||
|
t.moments[id] = m
|
||
|
}
|
||
|
|
||
|
// tickUnlocked applies all scheduled temporal effects.
|
||
|
func (t *Time) tickUnlocked() moments {
|
||
|
var past moments
|
||
|
|
||
|
for id, m := range t.moments {
|
||
|
if m.when.After(t.now) {
|
||
|
continue
|
||
|
}
|
||
|
delete(t.moments, id)
|
||
|
past = append(past, m)
|
||
|
}
|
||
|
sort.Sort(past)
|
||
|
|
||
|
return past
|
||
|
}
|
||
|
|
||
|
// Now returns the current time.
|
||
|
func (t *Time) Now() time.Time {
|
||
|
t.mux.Lock()
|
||
|
defer t.mux.Unlock()
|
||
|
return t.now
|
||
|
}
|
||
|
|
||
|
// Set travels to specified time.
|
||
|
//
|
||
|
// Also triggers temporal effects.
|
||
|
func (t *Time) Set(now time.Time) {
|
||
|
t.mux.Lock()
|
||
|
defer t.mux.Unlock()
|
||
|
t.setUnlocked(now)
|
||
|
}
|
||
|
|
||
|
// Travel adds duration to current time and returns result.
|
||
|
//
|
||
|
// Also triggers temporal effects.
|
||
|
func (t *Time) Travel(d time.Duration) time.Time {
|
||
|
t.mux.Lock()
|
||
|
defer t.mux.Unlock()
|
||
|
now := t.now.Add(d)
|
||
|
t.setUnlocked(now)
|
||
|
return now
|
||
|
}
|
||
|
|
||
|
// TravelDate applies AddDate to current time and returns result.
|
||
|
//
|
||
|
// Also triggers temporal effects.
|
||
|
func (t *Time) TravelDate(years, months, days int) time.Time {
|
||
|
t.mux.Lock()
|
||
|
defer t.mux.Unlock()
|
||
|
now := t.now.AddDate(years, months, days)
|
||
|
t.setUnlocked(now)
|
||
|
return now
|
||
|
}
|
||
|
|
||
|
// setUnlocked sets the current time to the given now time and triggers temporal
|
||
|
// effects.
|
||
|
func (t *Time) setUnlocked(now time.Time) {
|
||
|
t.now = now
|
||
|
t.tickUnlocked().do(now)
|
||
|
}
|
||
|
|
||
|
// Sleep blocks until duration is elapsed.
|
||
|
func (t *Time) Sleep(d time.Duration) { <-t.After(d) }
|
||
|
|
||
|
// When returns relative time point.
|
||
|
func (t *Time) When(d time.Duration) time.Time {
|
||
|
return t.Now().Add(d)
|
||
|
}
|
||
|
|
||
|
// After returns new channel that will receive time.Time value with current tme after
|
||
|
// specified duration.
|
||
|
func (t *Time) After(d time.Duration) <-chan time.Time {
|
||
|
done := make(chan time.Time, 1)
|
||
|
t.plan(t.When(d), func(now time.Time) {
|
||
|
done <- now
|
||
|
})
|
||
|
return done
|
||
|
}
|
||
|
|
||
|
// Observe return channel that closes on clock calls. The current implementation
|
||
|
// also closes the channel on Ticker’s ticks.
|
||
|
func (t *Time) Observe() <-chan struct{} {
|
||
|
observer := make(chan struct{})
|
||
|
t.mux.Lock()
|
||
|
t.observers = append(t.observers, observer)
|
||
|
t.mux.Unlock()
|
||
|
|
||
|
return observer
|
||
|
}
|
||
|
|
||
|
func (t *Time) observeUnlocked() {
|
||
|
for _, observer := range t.observers {
|
||
|
close(observer)
|
||
|
}
|
||
|
t.observers = t.observers[:0]
|
||
|
}
|