From 33078c764c05cd0dfe535259506dc720a079c50c Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 2 Apr 2022 20:19:02 +0900 Subject: [PATCH] internal/gamepaddb: make Update atomic Closes #1978 --- input.go | 4 +- internal/gamepaddb/gamepaddb.go | 75 +++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/input.go b/input.go index e9e2ed0ce..451f6b197 100644 --- a/input.go +++ b/input.go @@ -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)) } diff --git a/internal/gamepaddb/gamepaddb.go b/internal/gamepaddb/gamepaddb.go index dfcfdfcfc..0da8bc71d 100644 --- a/internal/gamepaddb/gamepaddb.go +++ b/internal/gamepaddb/gamepaddb.go @@ -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 }