Skip to content

Commit

Permalink
Add configurable debouncer for spammy Libretro callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
sergystepanov authored Oct 25, 2023
1 parent 07f4035 commit 7f2f1d7
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 86 deletions.
3 changes: 3 additions & 0 deletions pkg/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ emulator:
libretro:
# use zip compression for emulator save states
saveCompression: true
# Sets a limiter function for some spammy core callbacks.
# 0 - disabled, otherwise -- time in milliseconds for ignoring repeated calls except the last.
debounceMs: 0
# Libretro cores logging level: DEBUG = 0, INFO, WARN, ERROR, DUMMY = INT_MAX
logLevel: 1
cores:
Expand Down
1 change: 1 addition & 0 deletions pkg/config/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type LibretroConfig struct {
}
List map[string]LibretroCoreConfig
}
DebounceMs int
SaveCompression bool
LogLevel int
}
Expand Down
14 changes: 4 additions & 10 deletions pkg/worker/caged/libretro/caged.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,15 @@ func (c *Caged) ReloadFrontend() {
c.base = frontend
}

func (c *Caged) HandleOnSystemAvInfo(fn func()) {
c.base.SetOnAV(func() {
w, h := c.ViewportCalc()
c.SetViewport(w, h)
fn()
})
}
// VideoChangeCb adds a callback when video params are changed by the app.
func (c *Caged) VideoChangeCb(fn func()) { c.base.SetVideoChangeCb(fn) }

func (c *Caged) Load(game games.GameMetadata, path string) error {
c.Emulator.LoadCore(game.System)
if err := c.Emulator.LoadGame(game.FullPath(path)); err != nil {
return err
}
w, h := c.ViewportCalc()
c.SetViewport(w, h)
c.ViewportRecalculate()
return nil
}

Expand All @@ -87,7 +81,7 @@ func (c *Caged) EnableCloudStorage(uid string, storage cloud.Storage) {
func (c *Caged) PixFormat() uint32 { return c.Emulator.PixFormat() }
func (c *Caged) Rotation() uint { return c.Emulator.Rotation() }
func (c *Caged) AudioSampleRate() int { return c.Emulator.AudioSampleRate() }
func (c *Caged) ViewportSize() (int, int) { return c.Emulator.ViewportSize() }
func (c *Caged) ViewportSize() (int, int) { return c.base.ViewportSize() }
func (c *Caged) Scale() float64 { return c.Emulator.Scale() }
func (c *Caged) SendControl(port int, data []byte) { c.base.Input(port, data) }
func (c *Caged) Start() { go c.Emulator.Start() }
Expand Down
39 changes: 21 additions & 18 deletions pkg/worker/caged/libretro/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@ type Emulator interface {
IsPortrait() bool
// Start is called after LoadGame
Start()
// SetViewport sets viewport size
SetViewport(width int, height int)
// ViewportCalc calculates the viewport size with the aspect ratio and scale
ViewportCalc() (nw int, nh int)
ViewportSize() (w, h int)
// ViewportRecalculate calculates output resolution with aspect and scale
ViewportRecalculate()
RestoreGameState() error
// SetSessionId sets distinct name for the game session (in order to save/load it later)
SetSessionId(name string)
Expand Down Expand Up @@ -112,8 +109,13 @@ func NewFrontend(conf config.Emulator, log *logger.Logger) (*Frontend, error) {
nano := nanoarch.NewNano(path)

log = log.Extend(log.With().Str("m", "Libretro"))
ll := log.Extend(log.Level(logger.Level(conf.Libretro.LogLevel)).With())
nano.SetLogger(ll)
level := logger.Level(conf.Libretro.LogLevel)
if level == logger.DebugLevel {
level = logger.TraceLevel
nano.SetLogger(log.Extend(log.Level(level).With()))
} else {
nano.SetLogger(log)
}

// Check if room is on local storage, if not, pull from GCS to local storage
log.Info().Msgf("Local storage path: %v", conf.Storage)
Expand All @@ -139,6 +141,12 @@ func NewFrontend(conf config.Emulator, log *logger.Logger) (*Frontend, error) {
}
f.linkNano(nano)

if conf.Libretro.DebounceMs > 0 {
t := time.Duration(conf.Libretro.DebounceMs) * time.Millisecond
f.nano.SetVideoDebounce(t)
f.log.Debug().Msgf("set debounce time: %v", t)
}

return f, nil
}

Expand Down Expand Up @@ -212,7 +220,7 @@ func (f *Frontend) linkNano(nano *nanoarch.Nanoarch) {
f.nano.OnAudio = f.handleAudio
}

func (f *Frontend) SetOnAV(fn func()) { f.nano.OnSystemAvInfo = fn }
func (f *Frontend) SetVideoChangeCb(fn func()) { f.nano.OnSystemAvInfo = fn }

func (f *Frontend) Start() {
f.log.Debug().Msgf("frontend start")
Expand Down Expand Up @@ -264,7 +272,7 @@ func (f *Frontend) Start() {
func (f *Frontend) AudioSampleRate() int { return f.nano.AudioSampleRate() }
func (f *Frontend) FPS() int { return f.nano.VideoFramerate() }
func (f *Frontend) Flipped() bool { return f.nano.IsGL() }
func (f *Frontend) FrameSize() (int, int) { return f.nano.GeometryBase() }
func (f *Frontend) FrameSize() (int, int) { return f.nano.BaseWidth(), f.nano.BaseHeight() }
func (f *Frontend) HasSave() bool { return os.Exists(f.HashPath()) }
func (f *Frontend) HashPath() string { return f.storage.GetSavePath() }
func (f *Frontend) Input(player int, data []byte) { f.input.setInput(player, data) }
Expand All @@ -279,14 +287,14 @@ func (f *Frontend) Scale() float64 { return f.scale }
func (f *Frontend) SetAudioCb(cb func(app.Audio)) { f.onAudio = cb }
func (f *Frontend) SetSessionId(name string) { f.storage.SetMainSaveName(name) }
func (f *Frontend) SetVideoCb(ff func(app.Video)) { f.onVideo = ff }
func (f *Frontend) SetViewport(w, h int) { f.mu.Lock(); f.vw, f.vh = w, h; f.mu.Unlock() }
func (f *Frontend) Tick() { f.mu.Lock(); f.nano.Run(); f.mu.Unlock() }
func (f *Frontend) ToggleMultitap() { f.nano.ToggleMultitap() }
func (f *Frontend) ViewportRecalculate() { f.mu.Lock(); f.vw, f.vh = f.ViewportCalc(); f.mu.Unlock() }
func (f *Frontend) ViewportSize() (int, int) { return f.vw, f.vh }

func (f *Frontend) ViewportCalc() (nw int, nh int) {
w, h := f.FrameSize()
f.log.Debug().Msgf("Viewport source size: %dx%d", w, h)
nw, nh = w, h

aspect, aw, ah := f.conf.AspectRatio.Keep, f.conf.AspectRatio.Width, f.conf.AspectRatio.Height
// calc the aspect ratio
Expand All @@ -298,29 +306,24 @@ func (f *Frontend) ViewportCalc() (nw int, nh int) {
nw = aw
nh = int(math.Round(float64(aw)/ratio/2) * 2)
}
f.log.Debug().Msgf("Viewport aspect change: %dx%d (%f) -> %dx%d", aw, ah, ratio, nw, nh)
} else {
nw, nh = w, h
}

if f.IsPortrait() {
nw, nh = nh, nw
f.log.Debug().Msgf("Set portrait mode")
}

f.log.Info().Msgf("Viewport final size: %dx%d", nw, nh)
f.log.Debug().Msgf("viewport: %dx%d -> %dx%d", w, h, nw, nh)

return
}

func (f *Frontend) Close() {
f.log.Debug().Msgf("frontend close")

close(f.done)

f.mui.Lock()
defer f.mui.Unlock()
f.nano.Close()
f.mui.Unlock()
f.log.Debug().Msgf("frontend closed")
}

Expand Down
8 changes: 1 addition & 7 deletions pkg/worker/caged/libretro/frontend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ func (emu *TestFrontend) loadRom(game string) {
if err != nil {
log.Fatal(err)
}
w, h := emu.FrameSize()
emu.SetViewport(w, h)
emu.ViewportRecalculate()
}

// Shutdown closes the emulator and cleans its resources.
Expand Down Expand Up @@ -228,31 +227,26 @@ func TestLoad(t *testing.T) {

mock := DefaultFrontend(test.room, test.system, test.rom)

fmt.Printf("[%-14v] ", "initial")
mock.dumpState()

for ticks := test.frames; ticks > 0; ticks-- {
mock.Tick()
}
fmt.Printf("[%-14v] ", fmt.Sprintf("emulated %d", test.frames))
mock.dumpState()

if err := mock.Save(); err != nil {
t.Errorf("Save fail %v", err)
}
fmt.Printf("[%-14v] ", "saved")
snapshot1, _ := mock.dumpState()

for ticks := test.frames; ticks > 0; ticks-- {
mock.Tick()
}
fmt.Printf("[%-14v] ", fmt.Sprintf("emulated %d", test.frames))
mock.dumpState()

if err := mock.Load(); err != nil {
t.Errorf("Load fail %v", err)
}
fmt.Printf("[%-14v] ", "restored")
snapshot2, _ := mock.dumpState()

if snapshot1 != snapshot2 {
Expand Down
Loading

0 comments on commit 7f2f1d7

Please sign in to comment.