Deterministic JavaScript intervals + Code Cleanup
* Remove several unused functions in doodad.Drawing (velocity, acceleration, grounded, etc.) - uix.Actor is where these are actually managed. * In the JavaScript API, setTimeout() and setInterval() will translate the milliseconds from wallclock time into a fixed number of game ticks to match the target frame rate for better deterministic timing.
This commit is contained in:
parent
c4456ac51b
commit
866e5e7fd8
|
@ -15,6 +15,12 @@ const (
|
|||
FormatZipfile // v2: zip archive with external chunks
|
||||
)
|
||||
|
||||
// Target frame rate for the game (also ticks per second for logic).
|
||||
const (
|
||||
TargetFPS = 60
|
||||
TargetClockRate = 1000 / 60 // wallclock milliseconds per tick
|
||||
)
|
||||
|
||||
// Numbers.
|
||||
var (
|
||||
// Window dimensions.
|
||||
|
|
|
@ -41,52 +41,14 @@ func (d *Drawing) Position() render.Point {
|
|||
return d.point
|
||||
}
|
||||
|
||||
// Velocity returns the Drawing's velocity.
|
||||
func (d *Drawing) Velocity() render.Point {
|
||||
return d.velocity
|
||||
}
|
||||
|
||||
// SetVelocity to set the speed.
|
||||
func (d *Drawing) SetVelocity(v render.Point) {
|
||||
d.velocity = v
|
||||
}
|
||||
|
||||
// Acceleration returns the Drawing's acceleration.
|
||||
func (d *Drawing) Acceleration() int {
|
||||
return d.accel
|
||||
}
|
||||
|
||||
// SetAcceleration to set the acceleration.
|
||||
func (d *Drawing) SetAcceleration(v int) {
|
||||
d.accel = v
|
||||
}
|
||||
|
||||
// Size returns the Drawing's size.
|
||||
func (d *Drawing) Size() render.Rect {
|
||||
return d.size
|
||||
}
|
||||
|
||||
// Grounded returns whether the Drawing is standing on solid ground.
|
||||
func (d *Drawing) Grounded() bool {
|
||||
return d.grounded
|
||||
}
|
||||
|
||||
// SetGrounded sets the grounded state.
|
||||
func (d *Drawing) SetGrounded(v bool) {
|
||||
d.grounded = v
|
||||
}
|
||||
|
||||
// MoveBy a relative value.
|
||||
func (d *Drawing) MoveBy(by render.Point) {
|
||||
d.point.Add(by)
|
||||
}
|
||||
|
||||
// MoveTo an absolute world value.
|
||||
//
|
||||
// NOTE: used only by unit test.
|
||||
func (d *Drawing) MoveTo(to render.Point) {
|
||||
d.point = to
|
||||
}
|
||||
|
||||
// Draw the drawing.
|
||||
func (d *Drawing) Draw(e render.Engine) {
|
||||
|
||||
}
|
||||
|
|
|
@ -29,9 +29,6 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// TargetFPS is the frame rate to cap the game to.
|
||||
TargetFPS = 1000 / 60 // 60 FPS
|
||||
|
||||
// Millisecond64 is a time.Millisecond casted to float64.
|
||||
Millisecond64 = float64(time.Millisecond)
|
||||
)
|
||||
|
@ -230,8 +227,8 @@ func (d *Doodle) Run() error {
|
|||
if !fpsDoNotCap {
|
||||
elapsed := time.Now().Sub(start)
|
||||
tmp := elapsed / time.Millisecond
|
||||
if TargetFPS-int(tmp) > 0 { // make sure it won't roll under
|
||||
delay = uint32(TargetFPS - int(tmp))
|
||||
if balance.TargetClockRate-int(tmp) > 0 { // make sure it won't roll under
|
||||
delay = uint32(balance.TargetClockRate - int(tmp))
|
||||
}
|
||||
d.Engine.Delay(delay)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package scripting
|
|||
import (
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
||||
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
|
@ -10,8 +12,8 @@ import (
|
|||
type Timer struct {
|
||||
id int
|
||||
callback goja.Value
|
||||
interval time.Duration // milliseconds delay for timeout
|
||||
next time.Time // scheduled time for next invocation
|
||||
ticks uint64 // interval (milliseconds) converted into game ticks
|
||||
nextTick uint64 // next tick to trigger the callback
|
||||
repeat bool // for setInterval
|
||||
}
|
||||
|
||||
|
@ -47,12 +49,16 @@ AddTimer loads timeouts and intervals into the VM's memory and returns the ID.
|
|||
func (vm *VM) AddTimer(callback goja.Value, interval int, repeat bool) int {
|
||||
// Get the next timer ID. The first timer has ID 1.
|
||||
vm.timerLastID++
|
||||
id := vm.timerLastID
|
||||
|
||||
var (
|
||||
id = vm.timerLastID
|
||||
ticks = float64(interval) * (float64(balance.TargetFPS) / 1000)
|
||||
)
|
||||
|
||||
t := &Timer{
|
||||
id: id,
|
||||
callback: callback,
|
||||
interval: time.Duration(interval),
|
||||
ticks: uint64(ticks),
|
||||
repeat: repeat,
|
||||
}
|
||||
t.Schedule()
|
||||
|
@ -71,7 +77,7 @@ func (vm *VM) TickTimer(now time.Time) {
|
|||
var clear []int
|
||||
|
||||
for id, timer := range vm.timers {
|
||||
if now.After(timer.next) {
|
||||
if shmem.Tick > timer.nextTick {
|
||||
if function, ok := goja.AssertFunction(timer.callback); ok {
|
||||
function(goja.Undefined())
|
||||
}
|
||||
|
@ -104,5 +110,5 @@ func (vm *VM) ClearTimer(id int) {
|
|||
|
||||
// Schedule the callback to be run in the future.
|
||||
func (t *Timer) Schedule() {
|
||||
t.next = time.Now().Add(t.interval * time.Millisecond)
|
||||
t.nextTick = shmem.Tick + t.ticks
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user