mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-28 12:36:30 +00:00
Add configurable debouncer for spammy Libretro callbacks
This commit is contained in:
parent
07f40351fa
commit
7f2f1d70b1
8 changed files with 131 additions and 86 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ type LibretroConfig struct {
|
|||
}
|
||||
List map[string]LibretroCoreConfig
|
||||
}
|
||||
DebounceMs int
|
||||
SaveCompression bool
|
||||
LogLevel int
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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() }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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")
|
||||
|
|
@ -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) }
|
||||
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
|
@ -47,13 +48,15 @@ type Nanoarch struct {
|
|||
enabled bool
|
||||
value C.unsigned
|
||||
}
|
||||
options *map[string]string
|
||||
reserved chan struct{} // limits concurrent use
|
||||
Rot uint
|
||||
serializeSize C.size_t
|
||||
stopped atomic.Bool
|
||||
sysAvInfo C.struct_retro_system_av_info
|
||||
sysInfo C.struct_retro_system_info
|
||||
options *map[string]string
|
||||
reserved chan struct{} // limits concurrent use
|
||||
Rot uint
|
||||
serializeSize C.size_t
|
||||
stopped atomic.Bool
|
||||
sys struct {
|
||||
av C.struct_retro_system_av_info
|
||||
i C.struct_retro_system_info
|
||||
}
|
||||
tickTime int64
|
||||
cSaveDirectory *C.char
|
||||
cSystemDirectory *C.char
|
||||
|
|
@ -69,6 +72,7 @@ type Nanoarch struct {
|
|||
vfr bool
|
||||
sdlCtx *graphics.SDL
|
||||
hackSkipHwContextDestroy bool
|
||||
limiter func(func())
|
||||
log *logger.Logger
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +123,7 @@ func (p PixFmt) String() string {
|
|||
var Nan0 = Nanoarch{
|
||||
reserved: make(chan struct{}, 1), // this thing forbids concurrent use of the emulator
|
||||
stopped: atomic.Bool{},
|
||||
limiter: func(fn func()) { fn() },
|
||||
Handlers: Handlers{
|
||||
OnDpad: func(uint, uint) int16 { return 0 },
|
||||
OnKeyPress: func(uint, int) int { return 0 },
|
||||
|
|
@ -139,18 +144,15 @@ func NewNano(localPath string) *Nanoarch {
|
|||
return nano
|
||||
}
|
||||
|
||||
func (n *Nanoarch) AudioSampleRate() int { return int(n.sysAvInfo.timing.sample_rate) }
|
||||
func (n *Nanoarch) VideoFramerate() int { return int(n.sysAvInfo.timing.fps) }
|
||||
func (n *Nanoarch) IsPortrait() bool { return n.Rot == 90 || n.Rot == 270 }
|
||||
func (n *Nanoarch) GeometryBase() (int, int) {
|
||||
return int(n.sysAvInfo.geometry.base_width), int(n.sysAvInfo.geometry.base_height)
|
||||
}
|
||||
func (n *Nanoarch) GeometryMax() (int, int) {
|
||||
return int(n.sysAvInfo.geometry.max_width), int(n.sysAvInfo.geometry.max_height)
|
||||
}
|
||||
func (n *Nanoarch) WaitReady() { <-n.reserved }
|
||||
func (n *Nanoarch) Close() { n.stopped.Store(true); n.reserved <- struct{}{} }
|
||||
func (n *Nanoarch) SetLogger(log *logger.Logger) { n.log = log }
|
||||
func (n *Nanoarch) AudioSampleRate() int { return int(n.sys.av.timing.sample_rate) }
|
||||
func (n *Nanoarch) VideoFramerate() int { return int(n.sys.av.timing.fps) }
|
||||
func (n *Nanoarch) IsPortrait() bool { return 90 == n.Rot%180 }
|
||||
func (n *Nanoarch) BaseWidth() int { return int(n.sys.av.geometry.base_width) }
|
||||
func (n *Nanoarch) BaseHeight() int { return int(n.sys.av.geometry.base_height) }
|
||||
func (n *Nanoarch) WaitReady() { <-n.reserved }
|
||||
func (n *Nanoarch) Close() { n.stopped.Store(true); n.reserved <- struct{}{} }
|
||||
func (n *Nanoarch) SetLogger(log *logger.Logger) { n.log = log }
|
||||
func (n *Nanoarch) SetVideoDebounce(t time.Duration) { n.limiter = NewLimit(t) }
|
||||
|
||||
func (n *Nanoarch) CoreLoad(meta Metadata) {
|
||||
var err error
|
||||
|
|
@ -219,16 +221,16 @@ func (n *Nanoarch) CoreLoad(meta Metadata) {
|
|||
C.bridge_retro_init(retroInit)
|
||||
}
|
||||
|
||||
C.bridge_retro_get_system_info(retroGetSystemInfo, &n.sysInfo)
|
||||
C.bridge_retro_get_system_info(retroGetSystemInfo, &n.sys.i)
|
||||
n.log.Debug().Msgf("System >>> %s (%s) [%s] nfp: %v",
|
||||
C.GoString(n.sysInfo.library_name), C.GoString(n.sysInfo.library_version),
|
||||
C.GoString(n.sysInfo.valid_extensions), bool(n.sysInfo.need_fullpath))
|
||||
C.GoString(n.sys.i.library_name), C.GoString(n.sys.i.library_version),
|
||||
C.GoString(n.sys.i.valid_extensions), bool(n.sys.i.need_fullpath))
|
||||
}
|
||||
|
||||
func (n *Nanoarch) LoadGame(path string) error {
|
||||
game := C.struct_retro_game_info{}
|
||||
|
||||
big := bool(n.sysInfo.need_fullpath) // big ROMs are loaded by cores later
|
||||
big := bool(n.sys.i.need_fullpath) // big ROMs are loaded by cores later
|
||||
if big {
|
||||
size, err := os.StatSize(path)
|
||||
if err != nil {
|
||||
|
|
@ -256,17 +258,17 @@ func (n *Nanoarch) LoadGame(path string) error {
|
|||
return fmt.Errorf("core failed to load ROM: %v", path)
|
||||
}
|
||||
|
||||
C.bridge_retro_get_system_av_info(retroGetSystemAVInfo, &n.sysAvInfo)
|
||||
C.bridge_retro_get_system_av_info(retroGetSystemAVInfo, &n.sys.av)
|
||||
n.log.Info().Msgf("System A/V >>> %vx%v (%vx%v), [%vfps], AR [%v], audio [%vHz]",
|
||||
n.sysAvInfo.geometry.base_width, n.sysAvInfo.geometry.base_height,
|
||||
n.sysAvInfo.geometry.max_width, n.sysAvInfo.geometry.max_height,
|
||||
n.sysAvInfo.timing.fps, n.sysAvInfo.geometry.aspect_ratio, n.sysAvInfo.timing.sample_rate,
|
||||
n.sys.av.geometry.base_width, n.sys.av.geometry.base_height,
|
||||
n.sys.av.geometry.max_width, n.sys.av.geometry.max_height,
|
||||
n.sys.av.timing.fps, n.sys.av.geometry.aspect_ratio, n.sys.av.timing.sample_rate,
|
||||
)
|
||||
|
||||
n.serializeSize = C.bridge_retro_serialize_size(retroSerializeSize)
|
||||
n.log.Info().Msgf("Save file size: %v", byteCountBinary(int64(n.serializeSize)))
|
||||
|
||||
Nan0.tickTime = int64(time.Second / time.Duration(n.sysAvInfo.timing.fps))
|
||||
Nan0.tickTime = int64(time.Second / time.Duration(n.sys.av.timing.fps))
|
||||
if n.vfr {
|
||||
n.log.Info().Msgf("variable framerate (VFR) is enabled")
|
||||
}
|
||||
|
|
@ -274,10 +276,9 @@ func (n *Nanoarch) LoadGame(path string) error {
|
|||
n.stopped.Store(false)
|
||||
|
||||
if n.Video.gl.enabled {
|
||||
//setRotation(image.F180) // flip Y coordinates of OpenGL
|
||||
bufS := uint(n.sysAvInfo.geometry.max_width*n.sysAvInfo.geometry.max_height) * n.Video.PixFmt.BPP
|
||||
bufS := uint(n.sys.av.geometry.max_width*n.sys.av.geometry.max_height) * n.Video.PixFmt.BPP
|
||||
graphics.SetBuffer(int(bufS))
|
||||
n.log.Info().Msgf("Set buffer: %v", byteCountBinary(int64(bufS)))
|
||||
n.log.Debug().Msgf("Set buffer: %v", byteCountBinary(int64(bufS)))
|
||||
if n.LibCo {
|
||||
C.same_thread(C.init_video_cgo)
|
||||
} else {
|
||||
|
|
@ -414,9 +415,6 @@ func printOpenGLDriverInfo() {
|
|||
openGLInfo.Grow(128)
|
||||
openGLInfo.WriteString(fmt.Sprintf("\n[OpenGL] Version: %v\n", graphics.GetGLVersionInfo()))
|
||||
openGLInfo.WriteString(fmt.Sprintf("[OpenGL] Vendor: %v\n", graphics.GetGLVendorInfo()))
|
||||
// This string is often the name of the GPU.
|
||||
// In the case of Mesa3d, it would be i.e "Gallium 0.4 on NVA8".
|
||||
// It might even say "Direct3D" if the Windows Direct3D wrapper is being used.
|
||||
openGLInfo.WriteString(fmt.Sprintf("[OpenGL] Renderer: %v\n", graphics.GetGLRendererInfo()))
|
||||
openGLInfo.WriteString(fmt.Sprintf("[OpenGL] GLSL Version: %v", graphics.GetGLSLInfo()))
|
||||
Nan0.log.Debug().Msg(openGLInfo.String())
|
||||
|
|
@ -655,7 +653,7 @@ func coreLog(level C.enum_retro_log_level, msg *C.char) {
|
|||
switch level {
|
||||
// with debug level cores have too much logs
|
||||
case C.RETRO_LOG_DEBUG:
|
||||
Nan0.log.Debug().MsgFunc(func() string { return m(msg) })
|
||||
Nan0.log.Trace().MsgFunc(func() string { return m(msg) })
|
||||
case C.RETRO_LOG_INFO:
|
||||
Nan0.log.Info().MsgFunc(func() string { return m(msg) })
|
||||
case C.RETRO_LOG_WARN:
|
||||
|
|
@ -688,18 +686,27 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool {
|
|||
|
||||
switch cmd {
|
||||
case C.RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
|
||||
av := *(*C.struct_retro_system_av_info)(data)
|
||||
Nan0.log.Info().Msgf(">>> SET SYS AV INFO: %v", av)
|
||||
Nan0.sysAvInfo = av
|
||||
go func() {
|
||||
if Nan0.OnSystemAvInfo != nil {
|
||||
Nan0.OnSystemAvInfo()
|
||||
}
|
||||
}()
|
||||
Nan0.sys.av = *(*C.struct_retro_system_av_info)(data)
|
||||
Nan0.log.Debug().Msgf(">>> system av change: %v", Nan0.sys.av)
|
||||
if Nan0.OnSystemAvInfo != nil {
|
||||
go Nan0.OnSystemAvInfo()
|
||||
}
|
||||
return true
|
||||
case C.RETRO_ENVIRONMENT_SET_GEOMETRY:
|
||||
geom := *(*C.struct_retro_game_geometry)(data)
|
||||
Nan0.log.Info().Msgf(">>> GEOMETRY: %v", geom)
|
||||
Nan0.log.Debug().Msgf(">>> geometry change: %v", geom)
|
||||
// some cores are eager to change resolution too many times
|
||||
// in a small period of time, thus we have some debouncer here
|
||||
Nan0.limiter(func() {
|
||||
lw := Nan0.sys.av.geometry.base_width
|
||||
lh := Nan0.sys.av.geometry.base_height
|
||||
if lw != geom.base_width || lh != geom.base_height {
|
||||
Nan0.sys.av.geometry = geom
|
||||
if Nan0.OnSystemAvInfo != nil {
|
||||
go Nan0.OnSystemAvInfo()
|
||||
}
|
||||
}
|
||||
})
|
||||
return true
|
||||
case C.RETRO_ENVIRONMENT_SET_ROTATION:
|
||||
setRotation((*(*uint)(data) % 4) * 90)
|
||||
|
|
@ -739,7 +746,7 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool {
|
|||
//window.SetShouldClose(true)
|
||||
return false
|
||||
case C.RETRO_ENVIRONMENT_GET_VARIABLE:
|
||||
if (*Nan0.options) == nil {
|
||||
if Nan0.options == nil || *Nan0.options == nil {
|
||||
return false
|
||||
}
|
||||
rv := (*C.struct_retro_variable)(data)
|
||||
|
|
@ -818,8 +825,8 @@ func initVideo() {
|
|||
|
||||
sdl, err := graphics.NewSDLContext(graphics.Config{
|
||||
Ctx: context,
|
||||
W: int(Nan0.sysAvInfo.geometry.max_width),
|
||||
H: int(Nan0.sysAvInfo.geometry.max_height),
|
||||
W: int(Nan0.sys.av.geometry.max_width),
|
||||
H: int(Nan0.sys.av.geometry.max_height),
|
||||
GLAutoContext: Nan0.Video.gl.autoCtx,
|
||||
GLVersionMajor: uint(Nan0.Video.hw.version_major),
|
||||
GLVersionMinor: uint(Nan0.Video.hw.version_minor),
|
||||
|
|
@ -849,3 +856,23 @@ func deinitVideo() {
|
|||
Nan0.Video.gl.autoCtx = false
|
||||
Nan0.hackSkipHwContextDestroy = false
|
||||
}
|
||||
|
||||
type limit struct {
|
||||
d time.Duration
|
||||
t *time.Timer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewLimit(d time.Duration) func(f func()) {
|
||||
l := &limit{d: d}
|
||||
return func(f func()) { l.push(f) }
|
||||
}
|
||||
|
||||
func (d *limit) push(f func()) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.t != nil {
|
||||
d.t.Stop()
|
||||
}
|
||||
d.t = time.AfterFunc(d.d, f)
|
||||
}
|
||||
|
|
|
|||
22
pkg/worker/caged/libretro/nanoarch/nanoarch_test.go
Normal file
22
pkg/worker/caged/libretro/nanoarch/nanoarch_test.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package nanoarch
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLimit(t *testing.T) {
|
||||
c := atomic.Int32{}
|
||||
lim := NewLimit(50 * time.Millisecond)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
lim(func() {
|
||||
c.Add(1)
|
||||
})
|
||||
}
|
||||
|
||||
if c.Load() > 1 {
|
||||
t.Errorf("should be just 1")
|
||||
}
|
||||
}
|
||||
|
|
@ -140,12 +140,13 @@ func (c *coordinator) HandleGameStart(rq api.StartGameRequest[com.Uid], w *Worke
|
|||
m.SetPixFmt(app.PixFormat())
|
||||
m.SetRot(app.Rotation())
|
||||
|
||||
app.HandleOnSystemAvInfo(func() {
|
||||
// recreate the video encoder
|
||||
app.VideoChangeCb(func() {
|
||||
app.ViewportRecalculate()
|
||||
m.VideoW, m.VideoH = app.ViewportSize()
|
||||
m.VideoScale = app.Scale()
|
||||
err := m.Reinit()
|
||||
if err != nil {
|
||||
c.log.Error().Err(err).Msgf("av reinit fail")
|
||||
if err := m.Reinit(); err != nil {
|
||||
c.log.Error().Err(err).Msgf("reinit fail")
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue