199 lines
3.4 KiB
Go
199 lines
3.4 KiB
Go
package play
|
|
|
|
import (
|
|
"time"
|
|
"unsafe"
|
|
|
|
"aego/core/clock"
|
|
"aego/core/rewind"
|
|
"aego/engine"
|
|
"aego/scene/sceneio"
|
|
)
|
|
|
|
const (
|
|
DefaultKeyEvery = 300
|
|
spareKeys = 2
|
|
)
|
|
|
|
type snapshot struct {
|
|
frame int
|
|
tick uint64
|
|
time clock.TimeState
|
|
rng uint64
|
|
keys []rewind.Key
|
|
size int
|
|
}
|
|
|
|
func (c *Controller) SetKeyEvery(ticks uint64) {
|
|
c.keyEvery = ticks
|
|
}
|
|
|
|
func (c *Controller) every() uint64 {
|
|
if c.keyEvery == 0 {
|
|
return DefaultKeyEvery
|
|
}
|
|
return c.keyEvery
|
|
}
|
|
|
|
func (c *Controller) keysOn() bool {
|
|
return c.kfOff == "" && len(c.kf) > 0
|
|
}
|
|
|
|
func (c *Controller) bindKeys() {
|
|
c.dropKeys(-1)
|
|
c.kf, c.kfOff, c.floor, c.lastTick = c.kf[:0], "", 0, 0
|
|
if c.world == nil {
|
|
return
|
|
}
|
|
c.world.EachOwner(func(id string, v any) {
|
|
k, ok := rewind.Of(v)
|
|
switch {
|
|
case ok:
|
|
c.kf = append(c.kf, k)
|
|
case c.kfOff == "":
|
|
c.kfOff = id
|
|
}
|
|
})
|
|
}
|
|
|
|
func (c *Controller) capture(frame int) {
|
|
start := time.Now()
|
|
var k *snapshot
|
|
if n := len(c.spare); n > 0 {
|
|
k, c.spare = c.spare[n-1], c.spare[:n-1]
|
|
} else {
|
|
k = &snapshot{}
|
|
}
|
|
w := c.world
|
|
k.frame, k.tick = frame, w.Time.Tick()
|
|
w.Time.Capture(&k.time)
|
|
k.rng = w.Random.State()
|
|
for len(k.keys) < len(c.kf) {
|
|
k.keys = append(k.keys, nil)
|
|
}
|
|
k.keys = k.keys[:len(c.kf)]
|
|
k.size = int(unsafe.Sizeof(*k))
|
|
for i, f := range c.kf {
|
|
k.keys[i] = f.Capture(k.keys[i])
|
|
k.size += k.keys[i].Size()
|
|
}
|
|
c.keys = append(c.keys, k)
|
|
c.keyBytes += k.size
|
|
c.lastTick = k.tick
|
|
c.keyTime = time.Since(start)
|
|
c.trim()
|
|
}
|
|
|
|
func (c *Controller) releaseKey(k *snapshot) {
|
|
c.keyBytes -= k.size
|
|
if len(c.spare) < spareKeys {
|
|
c.spare = append(c.spare, k)
|
|
}
|
|
}
|
|
|
|
func (c *Controller) trim() {
|
|
h := c.hist
|
|
n := 0
|
|
for len(c.keys)-n > 1 && h.bytes+c.keyBytes > h.limit {
|
|
c.releaseKey(c.keys[n])
|
|
n++
|
|
}
|
|
if n > 0 {
|
|
m := copy(c.keys, c.keys[n:])
|
|
clear(c.keys[m:])
|
|
c.keys = c.keys[:m]
|
|
}
|
|
if len(c.keys) > 0 {
|
|
c.floor = c.keys[0].frame
|
|
}
|
|
}
|
|
|
|
func (c *Controller) dropKeys(after int) {
|
|
n := len(c.keys)
|
|
for n > 0 && c.keys[n-1].frame > after {
|
|
n--
|
|
c.releaseKey(c.keys[n])
|
|
}
|
|
clear(c.keys[n:])
|
|
c.keys = c.keys[:n]
|
|
c.lastTick = 0
|
|
if n > 0 {
|
|
c.lastTick = c.keys[n-1].tick
|
|
return
|
|
}
|
|
c.floor = 0
|
|
}
|
|
|
|
func (c *Controller) keyAt(n int) *snapshot {
|
|
for i := len(c.keys) - 1; i >= 0; i-- {
|
|
if c.keys[i].frame <= n {
|
|
return c.keys[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) restore(k *snapshot) {
|
|
w := c.world
|
|
w.Time.Restore(&k.time)
|
|
w.Random.SetState(k.rng)
|
|
for i, f := range c.kf {
|
|
f.Restore(k.keys[i])
|
|
}
|
|
for _, f := range c.kf {
|
|
if a, ok := f.(rewind.AfterRestorer); ok {
|
|
a.AfterRestore()
|
|
}
|
|
}
|
|
c.cursor, c.beyond = k.frame, false
|
|
}
|
|
|
|
func (c *Controller) advance(dt float64) clock.Plan {
|
|
plan := c.ctx.Engine.TickWorld(c.world, engine.ScopePlay, dt)
|
|
if c.scene != nil {
|
|
c.scene.Flush()
|
|
}
|
|
return plan
|
|
}
|
|
|
|
func (c *Controller) setReplaying(on bool) {
|
|
if w := c.world; w != nil {
|
|
w.SetReplaying(on)
|
|
w.Recorder.SetReplaying(on)
|
|
}
|
|
}
|
|
|
|
func (c *Controller) applyRebase() {
|
|
if !c.rebase || c.hist == nil || c.scene == nil {
|
|
return
|
|
}
|
|
c.rebase = false
|
|
h := c.hist
|
|
c.scene.Flush()
|
|
h.data = sceneio.Save(c.scene)
|
|
h.truncate(0)
|
|
h.end, h.why = -1, ""
|
|
c.cursor, c.beyond = 0, false
|
|
c.dropKeys(-1)
|
|
if c.keysOn() {
|
|
c.capture(0)
|
|
}
|
|
}
|
|
|
|
func (c *Controller) StateHash() [32]byte {
|
|
if c.world == nil {
|
|
return [32]byte{}
|
|
}
|
|
if c.hasher == nil {
|
|
c.hasher = rewind.NewHasher()
|
|
}
|
|
h := c.hasher
|
|
h.Reset()
|
|
c.world.Time.Hash(h)
|
|
h.U64(c.world.Random.State())
|
|
for _, f := range c.kf {
|
|
f.Hash(h)
|
|
}
|
|
return h.Sum()
|
|
}
|