stride3d/stride · error · InvalidOperationException

Device with Id ( ) already registered to

Error message

Device with Id {device.Id}({device.Name}) already registered to {devicesById[device.Id].Name}

What it means

When a new input device is registered, its unique Device.Id is stored in devicesById. If a device with the same Id already exists, TryAdd fails and Stride throws InvalidOperationException reporting both device names, because duplicate device IDs would break device lookup and event routing.

Solutions

  1. Ensure every IInputDevice returns a unique, stable Id.
  2. Remove the stale device (raise Remove on the source's Devices collection) before re-adding.
  3. Fix duplicate/constant Id generation in custom sources.

Example fix

// before
public Guid Id => Guid.Empty; // collides
// after
public Guid Id => id; // unique per device instance, assigned at construction
Defensive patterns

Strategy: validation

Validate before calling

if (existingDevicesById.TryGetValue(device.Id, out var dup) && !ReferenceEquals(dup, device)) throw new InvalidOperationException($"Duplicate device Id {device.Id}");

Try / catch

try { /* register device */ }
catch (InvalidOperationException ex) when (ex.Message.Contains("already registered"))
{ logger.Error(ex, "Duplicate device Id; removing stale device"); }

Prevention

When it happens

Trigger: An input source (re)adding a device whose Id collides with an existing one — e.g. re-adding a device without a stable unique Id, or a custom source generating constant/zero Ids.

Common situations: Custom input sources hard-coding Device.Id; platform backends recreating a device after reconnect without freeing the old registration; two source instances emitting devices with identical Ids.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/b9f29c32e507d4b1. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Input/InputManager.cs:795

        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    OnInputDeviceAdded(source, (IInputDevice)e.Item);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    OnInputDeviceRemoved((IInputDevice)e.Item);
                    break;
                default:
                    throw new InvalidOperationException("Unsupported collection operation");
            }
        }

        private void OnInputDeviceAdded(IInputSource source, IInputDevice device)
        {
            devices.Add(device);
            if (!devicesById.TryAdd(device.Id, device))
                throw new InvalidOperationException($"Device with Id {device.Id}({device.Name}) already registered to {devicesById[device.Id].Name}");

            if (device is IKeyboardDevice)
            {
                RegisterKeyboard((IKeyboardDevice)device);
                keyboards.Sort((l, r) => -l.Priority.CompareTo(r.Priority));
            }
            else if (device is IPointerDevice)
            {
                RegisterPointer((IPointerDevice)device);
                pointers.Sort((l, r) => -l.Priority.CompareTo(r.Priority));
            }
            else if (device is IGameControllerDevice)
            {
                RegisterGameController((IGameControllerDevice)device);
                gameControllers.Sort((l, r) => -l.Priority.CompareTo(r.Priority));
            }
            else if (device is IGamePadDevice)
            {

View on GitHub (pinned to 96fad776d2)