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

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]
}