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.
212 lines
4.5 KiB
212 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] |
|
}
|
|
|