243 lines
3.8 KiB
Go
243 lines
3.8 KiB
Go
package clock
|
|
|
|
import "math"
|
|
|
|
type Domain uint8
|
|
|
|
const (
|
|
Real Domain = iota
|
|
Game
|
|
Fixed
|
|
Editor
|
|
Audio
|
|
DomainCount
|
|
)
|
|
|
|
const MaxScale = 8
|
|
|
|
type Span struct {
|
|
Delta float64
|
|
Elapsed float64
|
|
Scale float64
|
|
Paused bool
|
|
}
|
|
|
|
type Config struct {
|
|
FixedDelta float64
|
|
MaxFrameDelta float64
|
|
MaxFixedSteps int
|
|
}
|
|
|
|
func (c Config) WithDefaults() Config {
|
|
if c.FixedDelta <= 0 {
|
|
c.FixedDelta = DefaultFixedDelta
|
|
}
|
|
if c.MaxFrameDelta <= 0 {
|
|
c.MaxFrameDelta = DefaultMaxFrameDelta
|
|
}
|
|
if c.MaxFixedSteps <= 0 {
|
|
c.MaxFixedSteps = DefaultMaxSteps
|
|
}
|
|
return c
|
|
}
|
|
|
|
type Plan struct {
|
|
Steps int
|
|
Update bool
|
|
Paused bool
|
|
}
|
|
|
|
type step uint8
|
|
|
|
const (
|
|
stepNone step = iota
|
|
stepFixed
|
|
stepFrame
|
|
)
|
|
|
|
type Time struct {
|
|
cfg Config
|
|
spans [DomainCount]Span
|
|
scale float64
|
|
paused bool
|
|
pending step
|
|
acc float64
|
|
alpha float64
|
|
frame uint64
|
|
updates uint64
|
|
tick uint64
|
|
dropped uint64
|
|
clamped float64
|
|
lastGame float64
|
|
}
|
|
|
|
func NewTime(cfg Config) *Time {
|
|
t := &Time{scale: 1}
|
|
t.Configure(cfg)
|
|
return t
|
|
}
|
|
|
|
func (t *Time) Configure(cfg Config) {
|
|
t.cfg = cfg.WithDefaults()
|
|
}
|
|
|
|
func (t *Time) Config() Config {
|
|
return t.cfg
|
|
}
|
|
|
|
func (t *Time) FixedDelta() float64 {
|
|
return t.cfg.FixedDelta
|
|
}
|
|
|
|
func (t *Time) Span(d Domain) Span {
|
|
if d >= DomainCount {
|
|
return Span{}
|
|
}
|
|
return t.spans[d]
|
|
}
|
|
|
|
func (t *Time) Frame() uint64 {
|
|
return t.frame
|
|
}
|
|
|
|
func (t *Time) Updates() uint64 {
|
|
return t.updates
|
|
}
|
|
|
|
func (t *Time) Tick() uint64 {
|
|
return t.tick
|
|
}
|
|
|
|
func (t *Time) Alpha() float64 {
|
|
return t.alpha
|
|
}
|
|
|
|
func (t *Time) Scale() float64 {
|
|
return t.scale
|
|
}
|
|
|
|
func (t *Time) Paused() bool {
|
|
return t.paused
|
|
}
|
|
|
|
func (t *Time) DroppedSteps() uint64 {
|
|
return t.dropped
|
|
}
|
|
|
|
func (t *Time) ClampedTime() float64 {
|
|
return t.clamped
|
|
}
|
|
|
|
func (t *Time) SetScale(s float64) {
|
|
if math.IsNaN(s) || s < 0 {
|
|
s = 0
|
|
}
|
|
t.scale = min(s, MaxScale)
|
|
}
|
|
|
|
func (t *Time) SetPaused(on bool) {
|
|
t.paused = on
|
|
t.pending = stepNone
|
|
}
|
|
|
|
func (t *Time) StepFixed() {
|
|
if t.paused {
|
|
t.pending = stepFixed
|
|
}
|
|
}
|
|
|
|
func (t *Time) StepFrame() {
|
|
if t.paused {
|
|
t.pending = stepFrame
|
|
}
|
|
}
|
|
|
|
func (t *Time) BeginTick() uint64 {
|
|
t.tick++
|
|
return t.tick
|
|
}
|
|
|
|
func (t *Time) drain(limit int) int {
|
|
fd := t.cfg.FixedDelta
|
|
steps := int((t.acc + stepEpsilon) / fd)
|
|
if steps > limit {
|
|
t.dropped += uint64(steps - limit)
|
|
steps = limit
|
|
}
|
|
t.acc -= float64(steps) * fd
|
|
if t.acc < 0 {
|
|
t.acc = 0
|
|
}
|
|
if t.acc >= fd {
|
|
t.acc = math.Mod(t.acc, fd)
|
|
}
|
|
return steps
|
|
}
|
|
|
|
func (t *Time) set(d Domain, delta float64) {
|
|
s := &t.spans[d]
|
|
s.Delta, s.Scale, s.Paused = delta, 1, false
|
|
s.Elapsed += delta
|
|
}
|
|
|
|
func (t *Time) Begin(real float64) Plan {
|
|
if real < 0 || math.IsNaN(real) || math.IsInf(real, 0) {
|
|
real = 0
|
|
}
|
|
if real > t.cfg.MaxFrameDelta {
|
|
t.clamped += real - t.cfg.MaxFrameDelta
|
|
real = t.cfg.MaxFrameDelta
|
|
}
|
|
t.frame++
|
|
fd := t.cfg.FixedDelta
|
|
var plan Plan
|
|
var game float64
|
|
switch {
|
|
case !t.paused:
|
|
game = real * t.scale
|
|
t.acc += game
|
|
plan.Update = true
|
|
plan.Steps = t.drain(t.cfg.MaxFixedSteps)
|
|
if game > 0 {
|
|
t.lastGame = game
|
|
}
|
|
case t.pending == stepFixed:
|
|
plan.Steps = 1
|
|
case t.pending == stepFrame:
|
|
game = t.lastGame
|
|
if game <= 0 {
|
|
game = fd
|
|
}
|
|
t.acc += game
|
|
plan.Update = true
|
|
if t.acc+stepEpsilon >= fd {
|
|
plan.Steps = 1
|
|
t.acc = max(t.acc-fd, 0)
|
|
}
|
|
if t.acc >= fd {
|
|
t.acc = math.Mod(t.acc, fd)
|
|
}
|
|
}
|
|
t.pending = stepNone
|
|
if plan.Update || plan.Steps > 0 {
|
|
t.updates++
|
|
}
|
|
plan.Paused = t.paused && !plan.Update
|
|
fixed := float64(plan.Steps) * fd
|
|
t.set(Real, real)
|
|
t.set(Editor, real)
|
|
t.set(Audio, real)
|
|
g := &t.spans[Game]
|
|
g.Delta, g.Scale, g.Paused = game, t.scale, t.paused
|
|
if plan.Update {
|
|
g.Elapsed += game
|
|
} else {
|
|
g.Elapsed += fixed
|
|
}
|
|
f := &t.spans[Fixed]
|
|
f.Delta, f.Scale, f.Paused = fd, t.scale, t.paused
|
|
f.Elapsed += fixed
|
|
t.alpha = t.acc / fd
|
|
return plan
|
|
}
|