{"record":{"id":"274cc68bb3bec187","repo":"ppy/osu","slug":"cannot-commit-a-rotate-operation-without-calling-b","errorCode":null,"errorMessage":"Cannot Commit a rotate operation without calling Begin first!","messagePattern":"Cannot Commit a rotate operation without calling Begin first!","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs","lineNumber":96,"sourceCode":"\r\n            foreach (var ho in objectsInRotation)\r\n            {\r\n                ho.Position = GeometryUtils.RotatePointAroundOrigin(originalPositions[ho], actualOrigin, rotation);\r\n\r\n                if (ho is IHasPath withPath)\r\n                {\r\n                    var originalPath = originalPathControlPointPositions[withPath];\r\n\r\n                    for (int i = 0; i < withPath.Path.ControlPoints.Count; ++i)\r\n                        withPath.Path.ControlPoints[i].Position = GeometryUtils.RotatePointAroundOrigin(originalPath[i], Vector2.Zero, rotation);\r\n                }\r\n            }\r\n        }\r\n\r\n        public override void Commit()\r\n        {\r\n            if (!OperationInProgress.Value)\r\n                throw new InvalidOperationException($\"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!\");\r\n\r\n            changeHandler?.EndChange();\r\n\r\n            base.Commit();\r\n\r\n            objectsInRotation = null;\r\n            originalPositions = null;\r\n            originalPathControlPointPositions = null;\r\n            DefaultOrigin = null;\r\n        }\r\n\r\n        private IEnumerable<OsuHitObject> selectedMovableObjects => selectedItems.Cast<OsuHitObject>()\r\n                                                                                 .Where(h => h is not Spinner);\r\n    }\r\n}\r\n","sourceCodeStart":78,"sourceCodeEnd":112,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs#L78-L112","documentation":"OsuSelectionRotationHandler.Commit() ends the change-handler transaction (changeHandler.EndChange()) and nulls the rotation state. Without a preceding Begin(), there is no open transaction to end and no state to clean up. The guard prevents calling EndChange on an unopened transaction.","triggerScenarios":"Calling Commit() when OperationInProgress.Value is false — e.g., a mouse-up/commit event firing without a prior Begin, or the operation was already committed and Commit fires again.","commonSituations":"Double-commit from duplicate event handlers; state machine lifecycle mismatch between the UI control and the rotation handler.","solutions":["Ensure Begin() is called before Commit().","Guard Commit() behind an OperationInProgress.Value check to skip if no operation is active."],"exampleFix":"// before\nrotationHandler.Commit(); // no prior Begin\n\n// after\nif (rotationHandler.OperationInProgress.Value)\n    rotationHandler.Commit();","handlingStrategy":"validation","validationCode":"// Guard Commit against calls without a prior Begin\nif (rotationHandler.OperationInProgress.Value)\n    rotationHandler.Commit();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pair Begin and Commit in a using/try-finally pattern to guarantee lifecycle correctness.","Guard Commit behind OperationInProgress.Value to make it idempotent."],"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"}