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. // 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) { func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) {
return gamepaddb.Update([]byte(mappings)) return gamepaddb.Update([]byte(mappings))
} }

View File

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