internal/gamepaddb: make Update atomic

Closes #1978
This commit is contained in:
Hajime Hoshi 2022-04-02 20:19:02 +09:00
parent 98b8fbe2df
commit 33078c764c
2 changed files with 50 additions and 29 deletions

View File

@ -317,7 +317,9 @@ func IsStandardGamepadLayoutAvailable(id GamepadID) bool {
//
// UpdateStandardGamepadLayoutMappings is concurrent-safe.
//
// Updated mappings take effect immediately even for already connected gamepads.
// UpdateStandardGamepadLayoutMappings mappings take effect immediately even for already connected gamepads.
//
// UpdateStandardGamepadLayoutMappings works atomically. If an error happens, nothing is updated.
func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) {
return gamepaddb.Update([]byte(mappings))
}

View File

@ -130,16 +130,15 @@ var (
mappingsM sync.RWMutex
)
func processLine(line string, platform platform) error {
func parseLine(line string, platform platform) (id string, name string, buttons map[StandardButton]*mapping, axes map[StandardAxis]*mapping, err error) {
line = strings.TrimSpace(line)
if len(line) == 0 {
return nil
return "", "", nil, nil, nil
}
if line[0] == '#' {
return nil
return "", "", nil, nil, nil
}
tokens := strings.Split(line, ",")
id := tokens[0]
for _, token := range tokens[2:] {
if len(token) == 0 {
continue
@ -151,54 +150,50 @@ func processLine(line string, platform platform) error {
switch tks[1] {
case "Windows":
if platform != platformWindows {
return nil
return "", "", nil, nil, nil
}
case "Mac OS X":
if platform != platformMacOS {
return nil
return "", "", nil, nil, nil
}
case "Linux":
if platform != platformUnix {
return nil
return "", "", nil, nil, nil
}
case "Android":
if platform != platformAndroid {
return nil
return "", "", nil, nil, nil
}
case "iOS":
if platform != platformIOS {
return nil
return "", "", nil, nil, nil
}
case "":
// Allow any platforms
default:
return fmt.Errorf("gamepaddb: unexpected platform: %s", tks[1])
return "", "", nil, nil, fmt.Errorf("gamepaddb: unexpected platform: %s", tks[1])
}
continue
}
gb, err := parseMappingElement(tks[1])
if err != nil {
return err
return "", "", nil, nil, err
}
if b, ok := toStandardGamepadButton(tks[0]); ok {
m, ok := gamepadButtonMappings[id]
if !ok {
m = map[StandardButton]*mapping{}
gamepadButtonMappings[id] = m
if buttons == nil {
buttons = map[StandardButton]*mapping{}
}
m[b] = gb
buttons[b] = gb
continue
}
if a, ok := toStandardGamepadAxis(tks[0]); ok {
m, ok := gamepadAxisMappings[id]
if !ok {
m = map[StandardAxis]*mapping{}
gamepadAxisMappings[id] = m
if axes == nil {
axes = map[StandardAxis]*mapping{}
}
m[a] = gb
axes[a] = gb
continue
}
@ -206,9 +201,7 @@ func processLine(line string, platform platform) error {
// There is no corresponding button in the Web standard gamepad layout.
}
gamepadNames[id] = tokens[1]
return nil
return tokens[0], tokens[1], buttons, axes, nil
}
func parseMappingElement(str string) (*mapping, error) {
@ -506,25 +499,51 @@ func IsButtonPressed(id string, button StandardButton, state GamepadState) bool
// Update adds new gamepad mappings.
// The string must be in the format of SDL_GameControllerDB.
func Update(mapping []byte) (bool, error) {
//
// Update works atomically. If an error happens, nothing is updated.
func Update(mappingData []byte) (bool, error) {
mappingsM.Lock()
defer mappingsM.Unlock()
buf := bytes.NewBuffer(mapping)
buf := bytes.NewBuffer(mappingData)
r := bufio.NewReader(buf)
type parsedLine struct {
id string
name string
buttons map[StandardButton]*mapping
axes map[StandardAxis]*mapping
}
var lines []parsedLine
for {
line, err := r.ReadString('\n')
if err != nil && err != io.EOF {
return false, err
}
if err := processLine(line, currentPlatform); err != nil {
return false, err
id, name, buttons, axes, err1 := parseLine(line, currentPlatform)
if err1 != nil {
return false, err1
}
if id != "" {
lines = append(lines, parsedLine{
id: id,
name: name,
buttons: buttons,
axes: axes,
})
}
if err == io.EOF {
break
}
}
for _, l := range lines {
gamepadNames[l.id] = l.name
gamepadButtonMappings[l.id] = l.buttons
gamepadAxisMappings[l.id] = l.axes
}
return true, nil
}