Skip to content

Commit

Permalink
Move input into nanoarch
Browse files Browse the repository at this point in the history
  • Loading branch information
sergystepanov committed Feb 25, 2024
1 parent 38c896f commit 59e96c9
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 80 deletions.
91 changes: 26 additions & 65 deletions pkg/worker/caged/libretro/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"path/filepath"
"sync"
"sync/atomic"
"time"
"unsafe"

Expand Down Expand Up @@ -51,18 +50,18 @@ type Emulator interface {
}

type Frontend struct {
conf config.Emulator
done chan struct{}
input InputState
log *logger.Logger
nano *nanoarch.Nanoarch
onAudio func(app.Audio)
onData func([]byte)
onVideo func(app.Video)
storage Storage
scale float64
th int // draw threads
vw, vh int // out frame size
conf config.Emulator
done chan struct{}
retropad nanoarch.InputState
log *logger.Logger
nano *nanoarch.Nanoarch
onAudio func(app.Audio)
onData func([]byte)
onVideo func(app.Video)
storage Storage
scale float64
th int // draw threads
vw, vh int // out frame size

mu sync.Mutex
mui sync.Mutex
Expand All @@ -71,23 +70,6 @@ type Frontend struct {
SaveOnClose bool
}

// InputState stores full controller state.
// It consists of:
// - uint16 button values
// - int16 analog stick values
type (
InputState [maxPort]State
State struct {
keys uint32
axes [dpadAxes]int32
}
)

const (
maxPort = 8
dpadAxes = 4
)

var (
audioPool sync.Pool
noAudio = func(app.Audio) {}
Expand Down Expand Up @@ -132,15 +114,15 @@ func NewFrontend(conf config.Emulator, log *logger.Logger) (*Frontend, error) {

// set global link to the Libretro
f := &Frontend{
conf: conf,
done: make(chan struct{}),
input: NewGameSessionInput(),
log: log,
onAudio: noAudio,
onData: noData,
onVideo: noVideo,
storage: store,
th: conf.Threads,
conf: conf,
done: make(chan struct{}),
retropad: nanoarch.NewRetroPadState(),
log: log,
onAudio: noAudio,
onData: noData,
onVideo: noVideo,
storage: store,
th: conf.Threads,
}
f.linkNano(nano)

Expand Down Expand Up @@ -221,8 +203,8 @@ func (f *Frontend) linkNano(nano *nanoarch.Nanoarch) {
}
f.nano.WaitReady() // start only when nano is available

f.nano.OnKeyPress = f.input.isKeyPressed
f.nano.OnDpad = f.input.isDpadTouched
f.nano.OnKeyPress = f.retropad.IsKeyPressed
f.nano.OnDpad = f.retropad.IsDpadTouched
f.nano.OnVideo = f.handleVideo
f.nano.OnAudio = f.handleAudio
}
Expand Down Expand Up @@ -293,7 +275,7 @@ func (f *Frontend) Flipped() bool { return f.nano.IsGL() }
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) }
func (f *Frontend) Input(player int, data []byte) { f.retropad.SetInput(player, data) }
func (f *Frontend) IsPortrait() bool { return f.nano.IsPortrait() }
func (f *Frontend) LoadGame(path string) error { return f.nano.LoadGame(path) }
func (f *Frontend) PixFormat() uint32 { return f.nano.Video.PixFmt.C }
Expand Down Expand Up @@ -402,27 +384,6 @@ func (f *Frontend) autosave(periodSec int) {
}
}

func (f *Frontend) KeyboardEvent(down bool, keycode uint, character uint32, keyModifiers uint16) {
f.nano.KeyboardEvent(down, keycode, character, keyModifiers)
}

func NewGameSessionInput() InputState { return [maxPort]State{} }

// setInput sets input state for some player in a game session.
func (s *InputState) setInput(player int, data []byte) {
atomic.StoreUint32(&s[player].keys, uint32(uint16(data[1])<<8+uint16(data[0])))
for i, axes := 0, len(data); i < dpadAxes && i<<1+3 < axes; i++ {
axis := i<<1 + 2
atomic.StoreInt32(&s[player].axes[i], int32(data[axis+1])<<8+int32(data[axis]))
}
}

// isKeyPressed checks if some button is pressed by any player.
func (s *InputState) isKeyPressed(port uint, key int) int {
return int((atomic.LoadUint32(&s[port].keys) >> uint(key)) & 1)
}

// isDpadTouched checks if D-pad is used by any player.
func (s *InputState) isDpadTouched(port uint, axis uint) (shift int16) {
return int16(atomic.LoadInt32(&s[port].axes[axis]))
func (f *Frontend) KeyboardEvent(down bool, key uint, char uint32, mod uint16) {
f.nano.KeyboardEvent(down, key, char, mod)
}
16 changes: 1 addition & 15 deletions pkg/worker/caged/libretro/frontend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func EmulatorMock(room string, system string) *TestFrontend {
Path: os.TempDir(),
MainSave: room,
},
input: NewGameSessionInput(),
retropad: nanoarch.NewRetroPadState(),
done: make(chan struct{}),
th: conf.Emulator.Threads,
log: l2,
Expand Down Expand Up @@ -335,20 +335,6 @@ func TestStateConcurrency(t *testing.T) {
}
}

func TestConcurrentInput(t *testing.T) {
var wg sync.WaitGroup
state := NewGameSessionInput()
events := 1000
wg.Add(2 * events)

for i := 0; i < events; i++ {
player := rand.Intn(maxPort)
go func() { state.setInput(player, []byte{0, 1}); wg.Done() }()
go func() { state.isKeyPressed(uint(player), 100); wg.Done() }()
}
wg.Wait()
}

func TestStartStop(t *testing.T) {
f1 := DefaultFrontend("sushi", sushi.system, sushi.rom)
go f1.Start()
Expand Down
40 changes: 40 additions & 0 deletions pkg/worker/caged/libretro/nanoarch/input.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
package nanoarch

import "sync/atomic"

const RetroDeviceTypeShift = 8

// InputState stores full controller state.
// It consists of:
// - uint16 button values
// - int16 analog stick values
type (
InputState [maxPort]RetroPadState
RetroPadState struct {
keys uint32
axes [dpadAxes]int32
}
)

const (
maxPort = 8
dpadAxes = 4
)

func NewRetroPadState() InputState { return [maxPort]RetroPadState{} }

// SetInput sets input state for some player in a game session.
func (s *InputState) SetInput(player int, data []byte) {
atomic.StoreUint32(&s[player].keys, uint32(uint16(data[1])<<8+uint16(data[0])))
for i, axes := 0, len(data); i < dpadAxes && i<<1+3 < axes; i++ {
axis := i<<1 + 2
atomic.StoreInt32(&s[player].axes[i], int32(data[axis+1])<<8+int32(data[axis]))
}
}

// IsKeyPressed checks if some button is pressed by any player.
func (s *InputState) IsKeyPressed(port uint, key int) int {
return int((atomic.LoadUint32(&s[port].keys) >> uint(key)) & 1)
}

// IsDpadTouched checks if D-pad is used by any player.
func (s *InputState) IsDpadTouched(port uint, axis uint) (shift int16) {
return int16(atomic.LoadInt32(&s[port].axes[axis]))
}

func RetroDeviceSubclass(base, id int) int { return ((id + 1) << RetroDeviceTypeShift) | base }
21 changes: 21 additions & 0 deletions pkg/worker/caged/libretro/nanoarch/input_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package nanoarch

import (
"math/rand"
"sync"
"testing"
)

func TestConcurrentInput(t *testing.T) {
var wg sync.WaitGroup
state := NewRetroPadState()
events := 1000
wg.Add(2 * events)

for i := 0; i < events; i++ {
player := rand.Intn(maxPort)
go func() { state.SetInput(player, []byte{0, 1}); wg.Done() }()
go func() { state.IsKeyPressed(uint(player), 100); wg.Done() }()
}
wg.Wait()
}

0 comments on commit 59e96c9

Please sign in to comment.