Package github.com/matjam/bunyip/timer
timer
Package timer schedules callbacks on game time, after a delay or on every interval. Timers run when the game calls Update, so they pause with the game and stay deterministic.
Advance a Scheduler each update with the step. After runs a function once, Every runs it repeatedly, and both return a handle to Cancel. Countdown is a simpler alternative. Start it, update it, and ask whether it is still Running. Because the time is the game's own, a paused game stops its timers by not calling Update, a replay reruns them identically, and a fast-forward advances them by a larger step. Schedulers are not goroutine-safe and fire on the goroutine that calls Update, the game loop's, so callbacks may touch game state freely.
Index
- type Countdown
- type Handle
- type Scheduler
- type Sequence
func NewSequence() *Sequencefunc (s *Sequence) Do(fn func()) *Sequencefunc (s *Sequence) Done() boolfunc (s *Sequence) Loop() *Sequencefunc (s *Sequence) Reset()func (s *Sequence) Run(fn func(dt float32) bool) *Sequencefunc (s *Sequence) Skip()func (s *Sequence) Until(cond func() bool) *Sequencefunc (s *Sequence) Update(dt float32) boolfunc (s *Sequence) Wait(seconds float32) *Sequence
Types
type Countdown source
type Countdown struct {
Left float64 // seconds remaining; may become negative on the final Update
}
Countdown is a simple timer a game polls rather than a callback: set
it and ask each frame whether it has run out.
Example
package main
import (
"fmt"
"github.com/matjam/bunyip/timer"
)
func main() {
var c timer.Countdown
c.Start(1)
for i := 1; ; i++ {
if c.Update(0.4) {
fmt.Println("ran out on update", i)
break
}
}
}
ran out on update 3
type Scheduler source
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler runs timers in game time. Its zero value is ready to use.
Example
package main
import (
"fmt"
"github.com/matjam/bunyip/timer"
)
func main() {
var s timer.Scheduler
s.After(1, func() { fmt.Println("door opens") })
tick := s.Every(0.5, func() { fmt.Println("tick") })
// Update from the game loop with the frame's delta; here two big
// steps. Timers fire in time order, and at the same moment in the
// order they were scheduled.
s.Update(0.6)
s.Update(0.6)
s.Cancel(tick)
s.Update(5)
}
tick
door opens
tick
After source
func (s *Scheduler) After(seconds float64, fn func()) Handle
After runs a non-nil fn once, seconds from now. A nonpositive delay becomes due on the next Update, or in the current Update if scheduled by a callback. It does not run fn during After itself.
Cancel source
func (s *Scheduler) Cancel(h Handle)
Cancel stops a timer; cancelling one that already fired is harmless.
Every source
func (s *Scheduler) Every(seconds float64, fn func()) Handle
Every runs fn every interval seconds, starting one interval from now, until cancelled. Use a positive interval for repetition; a nonpositive interval fires once on the next Update.
Pending source
func (s *Scheduler) Pending() int
Pending counts scheduled timers that have not fired or been cancelled.
Update source
func (s *Scheduler) Update(dt float64)
Update advances time by dt seconds and fires what is due, earliest first; timers due at the same moment fire in the order they were scheduled. Callbacks may schedule or cancel timers. Use finite, nonnegative dt. Repeating timers fire once for every elapsed interval, including multiple times in a large step. Callbacks must return and must not recursively call Update.
type Sequence source
type Sequence struct {
// contains filtered or unexported fields
}
Sequence runs steps one after another on game time, the way a coroutine would: wait a while, do something, wait until a condition holds, run a function every update until it says it is done. It is how a cutscene, a turn's animation, a boss's attack pattern or a tutorial is written as a list rather than a state machine:
seq := timer.NewSequence().
Do(func() { camera.PanTo(door) }).
Wait(1).
Until(func() bool { return camera.Arrived() }).
Do(func() { door.Open() }).
Run(func(dt float32) bool { return hero.WalkTowards(door, dt) })
The game calls Update each step; Done reports when the last step has finished. Steps run on the goroutine that calls Update.
NewSequence source
func NewSequence() *Sequence
NewSequence starts an empty sequence; add steps with the builder methods, which return the sequence for chaining.
Do source
func (s *Sequence) Do(fn func()) *Sequence
Do runs a function once and moves on in the same update.
Done source
func (s *Sequence) Done() bool
Done reports whether every step has finished; a looping sequence is never done unless Skip was called or it has no steps.
Loop source
func (s *Sequence) Loop() *Sequence
Loop makes the sequence start over when it ends, for patrols and idle behaviours.
Run source
func (s *Sequence) Run(fn func(dt float32) bool) *Sequence
Run calls the function every update with the step until it returns true: a movement to carry out, an animation to play through.
Skip source
func (s *Sequence) Skip()
Skip jumps to the end, for a player who presses through a cutscene; Do steps that have not run yet do not run.
Until source
func (s *Sequence) Until(cond func() bool) *Sequence
Until waits, checking each update, until the condition holds.
Update source
func (s *Sequence) Update(dt float32) bool
Update advances the sequence by dt seconds, running as many steps as finish within it. It reports whether the sequence is done. Wait carries leftover time into following steps. A completed Run consumes the entire remaining step, so later Run steps in the same call receive zero dt. Use finite, nonnegative dt and do not call Update recursively from a step.
Source files
bench_test.go example_test.go review_test.go sequence.go sequence_test.go timer.go timer_test.go