From 68ac6afab4c2708dd9ec1182fd936ff6ffcee24f Mon Sep 17 00:00:00 2001 From: sergystepanov Date: Thu, 2 Jul 2026 00:11:27 +0300 Subject: [PATCH] Switch to the more precise runtime.nanotime for frame timing --- pkg/os/nanotime.go | 9 ++++++++ pkg/worker/caged/libretro/frontend.go | 31 ++++++++++++++------------- 2 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 pkg/os/nanotime.go diff --git a/pkg/os/nanotime.go b/pkg/os/nanotime.go new file mode 100644 index 00000000..22af88e4 --- /dev/null +++ b/pkg/os/nanotime.go @@ -0,0 +1,9 @@ +package os + +import _ "unsafe" + +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + +// Nanotime returns a monotonic timestamp in nanoseconds. +func Nanotime() int64 { return nanotime() } diff --git a/pkg/worker/caged/libretro/frontend.go b/pkg/worker/caged/libretro/frontend.go index b9d86abe..6974d590 100644 --- a/pkg/worker/caged/libretro/frontend.go +++ b/pkg/worker/caged/libretro/frontend.go @@ -320,17 +320,17 @@ func (f *Frontend) Start() { // The main loop of Libretro - targetFrameTime := time.Duration(float64(time.Second) / f.nano.VideoFramerate()) + targetFrameTimeNs := int64(float64(time.Second) / f.nano.VideoFramerate()) // stop sleeping and start spinning in the remaining 1ms - const spinThreshold = 1 * time.Millisecond + const spinThresholdNs = int64(1 * time.Millisecond) // how many frames will be considered not normal const lateFramesThreshold = 3 batch := max(f.SpinBatch, 1) - lastFrameStart := time.Now() + lastFrameStart := os.Nanotime() for { select { @@ -339,27 +339,28 @@ func (f *Frontend) Start() { default: f.Tick() - elapsed := time.Since(lastFrameStart) - sleepTime := targetFrameTime - elapsed - deadline := lastFrameStart.Add(targetFrameTime) + now := os.Nanotime() + elapsed := now - lastFrameStart + sleepTimeNs := targetFrameTimeNs - elapsed + deadline := lastFrameStart + targetFrameTimeNs - if sleepTime > 0 { + if sleepTimeNs > 0 { // SLEEP // if we have plenty of time, sleep to save CPU and // wake up slightly before the target time - if sleepTime > spinThreshold { - time.Sleep(sleepTime - spinThreshold) + if sleepTimeNs > spinThresholdNs { + time.Sleep(time.Duration(sleepTimeNs - spinThresholdNs)) } // SPIN // if we are close to the target, // burn CPU and check the clock with ns resolution. // SpinBatch batches the time check to reduce - // syscall overhead while keeping sub-us precision. + // overhead while keeping sub-us precision. for { for range batch - 1 { } - if !time.Now().Before(deadline) { + if os.Nanotime() >= deadline { break } } @@ -368,7 +369,7 @@ func (f *Frontend) Start() { // lagging behind the target framerate so we don't sleep if f.conf.LogDroppedFrames { // !to make some stats counter instead - f.log.Debug().Msgf("[] Frame drop: %v", elapsed) + f.log.Debug().Msgf("[] Frame drop: %v", time.Duration(elapsed)) } f.skipVideo = true } @@ -378,13 +379,13 @@ func (f *Frontend) Start() { // adding targetFrameTime to the previous start // prevents drift, if one frame was late, // we try to catch up in the next frame - lastFrameStart = lastFrameStart.Add(targetFrameTime) + lastFrameStart = lastFrameStart + targetFrameTimeNs // if execution was paused or heavily delayed, // reset lastFrameStart so we don't try to run // a bunch of frames instantly to catch up - if time.Since(lastFrameStart) > targetFrameTime*lateFramesThreshold { - lastFrameStart = time.Now() + if os.Nanotime()-lastFrameStart > targetFrameTimeNs*lateFramesThreshold { + lastFrameStart = os.Nanotime() } } }