{"record":{"id":"28521cc7903b508e","repo":"stride3d/stride","slug":"something-should-handle-controller-disconnect-gamepadxinput","errorCode":null,"errorMessage":"Something should handle controller disconnect","messagePattern":"Something should handle controller disconnect","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/engine/Stride.Input/Windows/GamePadXInput.cs","lineNumber":50,"sourceCode":"\n            // Used to create a product ID that matches SDL2's method of generating them\n            // Taken from SDL 2.0.5 (src\\joystick\\windows\\SDL_xinputjoystick.c:149)\n            var subType = controller.GetCapabilities(DeviceQueryType.Any).SubType;\n            var pidBytes = Encoding.ASCII.GetBytes(\"xinput\").ToList();\n            pidBytes.Add((byte)subType);\n\n            while (pidBytes.Count < 16)\n            {\n                pidBytes.Add(0);\n            }\n\n            ProductId = new Guid(pidBytes.ToArray());\n        }\n\n        public void Dispose()\n        {\n            if (Disconnected == null)\n                throw new InvalidOperationException(\"Something should handle controller disconnect\");\n        }\n\n        public override string Name => $\"XInput GamePad {Index}\";\n        public override Guid Id { get; }\n        public override Guid ProductId { get; }\n        public override GamePadState State => state;\n        public override IInputSource Source { get; }\n\n        public event EventHandler Disconnected;\n\n        public override void Update(List<InputEvent> inputEvents)\n        {\n            if ((int)controller.UserIndex != Index)\n            {\n                SetIndexInternal((int)controller.UserIndex);\n            }\n\n            ClearButtonStates();","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Input/Windows/GamePadXInput.cs#L32-L68","documentation":"GamePadXInput.Dispose uses Disconnected == null as a guard: if nobody subscribed to the Disconnected event, disposing the gamepad would orphan it, so Dispose throws InvalidOperationException with 'Something should handle controller disconnect'. It is an invariant enforcing that disconnect handling is wired before a device is torn down.","triggerScenarios":"Calling DisconnectAndDispose (or Dispose directly) on a GamePadXInput whose Disconnected event has no subscribers.","commonSituations":"Manually creating and disposing GamePadXInput instances outside InputSourceWindowsXInput; app shutdown ordering that disposes gamepads after the input source cleared its handlers.","solutions":["Use InputSourceWindowsXInput.OpenDevice to create gamepads so the Disconnected handler is attached automatically.","Subscribe to Disconnected before disposing the gamepad.","Dispose gamepads through the input source rather than calling Dispose directly."],"exampleFix":"// before\ngamePad.Dispose();\n// after\ngamePad.Disconnected += (s, e) => { /* remove device */ };\ngamePad.Dispose();","handlingStrategy":"validation","validationCode":"if (gamePad.Disconnected == null)\n    throw new InvalidOperationException(\"Attach a Disconnected handler before disposing the XInput gamepad.\");\ngamePad.Dispose();","typeGuard":"bool SafeToDispose(GamePadXInput g) => g.Disconnected != null;","tryCatchPattern":"try\n{\n    gamePad.Dispose();\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"controller disconnect\"))\n{\n    // Subscribe to Disconnected and retry disposal via the input source.\n}","preventionTips":["Create XInput gamepads only via InputSourceWindowsXInput.OpenDevice, which wires the handler.","Never call Dispose directly on GamePadXInput from game code.","Order shutdown so the input source disposes its devices."],"tags":["xinput","gamepad","dispose","windows"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}