{"record":{"id":"76f0fd0e27ad143e","repo":"ppy/osu","slug":"cannot-begin-a-rotate-operation-while-another-is-i","errorCode":null,"errorMessage":"Cannot Begin a rotate operation while another is in progress!","messagePattern":"Cannot Begin a rotate operation while another is in progress!","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs","lineNumber":56,"sourceCode":"            updateState();\r\n        }\r\n\r\n        private void updateState()\r\n        {\r\n            var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects);\r\n            CanRotateAroundSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0;\r\n            CanRotateAroundPlayfieldOrigin.Value = selectedMovableObjects.Any();\r\n        }\r\n\r\n        private OsuHitObject[]? objectsInRotation;\r\n\r\n        private Dictionary<OsuHitObject, Vector2>? originalPositions;\r\n        private Dictionary<IHasPath, Vector2[]>? originalPathControlPointPositions;\r\n\r\n        public override void Begin()\r\n        {\r\n            if (OperationInProgress.Value)\r\n                throw new InvalidOperationException($\"Cannot {nameof(Begin)} a rotate operation while another is in progress!\");\r\n\r\n            base.Begin();\r\n\r\n            changeHandler?.BeginChange();\r\n\r\n            objectsInRotation = selectedMovableObjects.ToArray();\r\n            DefaultOrigin = GeometryUtils.MinimumEnclosingCircle(objectsInRotation).Item1;\r\n            originalPositions = objectsInRotation.ToDictionary(obj => obj, obj => obj.Position);\r\n            originalPathControlPointPositions = objectsInRotation.OfType<IHasPath>().ToDictionary(\r\n                obj => obj,\r\n                obj => obj.Path.ControlPoints.Select(point => point.Position).ToArray());\r\n        }\r\n\r\n        public override void Update(float rotation, Vector2? origin = null)\r\n        {\r\n            if (!OperationInProgress.Value)\r\n                throw new InvalidOperationException($\"Cannot {nameof(Update)} a rotate operation without calling {nameof(Begin)} first!\");\r\n\r","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs#L38-L74","documentation":"OsuSelectionRotationHandler.Begin() initiates a rotation operation: it snapshots original positions and control points, computes the default origin, and starts a change-handler transaction via changeHandler.BeginChange(). Calling Begin when OperationInProgress.Value is already true would overwrite the original position snapshots (losing the ability to revert) and start a nested change transaction.","triggerScenarios":"Calling Begin() when OperationInProgress.Value is already true — for example, a rotation handle's mouse-down event firing twice without an intervening mouse-up/Commit, or two independent input sources (keyboard + mouse) both calling Begin.","commonSituations":"Double-binding of a rotation gesture handler; concurrent input from multiple sources; state machine reset that clears OperationInProgress without calling Commit on the handler.","solutions":["Check OperationInProgress.Value before calling Begin(), and only begin if no operation is in progress.","Ensure every Begin() has a matching Commit() in the same interaction lifecycle (e.g., mouse-down begins, mouse-up commits).","Use a single input handler for rotation gestures to prevent concurrent Begin calls."],"exampleFix":"// before\nrotationHandler.Begin(); // may double-fire\n\n// after\nif (!rotationHandler.OperationInProgress.Value)\n    rotationHandler.Begin();","handlingStrategy":"validation","validationCode":"// Guard Begin against double-entry\nif (rotationHandler.OperationInProgress.Value)\n    return; // or log a warning\nrotationHandler.Begin();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always check OperationInProgress.Value before calling Begin() in input handlers.","Design input handlers so that Begin maps to a single discrete event (e.g., mouse-down) and Commit to its counterpart (mouse-up).","Add integration tests that simulate rapid double-clicks or concurrent input to verify no double-Begin."],"tags":["editor","osu-ruleset","selection","state-machine","rotation"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}