{"record":{"id":"371be55429868085","repo":"ppy/osu","slug":"cannot-update-a-rotate-operation-without-calling-b","errorCode":null,"errorMessage":"Cannot Update a rotate operation without calling Begin first!","messagePattern":"Cannot Update a rotate operation without calling Begin first!","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs","lineNumber":73,"sourceCode":"            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\n            Debug.Assert(objectsInRotation != null && originalPositions != null && originalPathControlPointPositions != null && DefaultOrigin != null);\r\n\r\n            Vector2 actualOrigin = origin ?? DefaultOrigin.Value;\r\n\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","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs#L55-L91","documentation":"OsuSelectionRotationHandler.Update() applies a rotation delta using originalPositions and originalPathControlPointPositions, which are only populated by Begin(). Without Begin, these fields are null and the Debug.Assert would fire after the exception. The guard throws before reaching the null dereference.","triggerScenarios":"Calling Update(rotation, origin) before Begin() has been called — for example, a rotation delta event firing before the operation was started, or the operation state was cleared/reset without restarting.","commonSituations":"Input event ordering issues where the rotation delta handler runs before initialization; state desync between the UI rotation control and the handler's OperationInProgress; a cancelled operation that nulls the state but the input pipeline still delivers Update.","solutions":["Ensure Begin() is called before any Update() in the rotation lifecycle.","Guard Update calls behind an OperationInProgress.Value check and ignore deltas when no operation is active."],"exampleFix":"// before\nrotationHandler.Update(deltaAngle); // called before Begin\n\n// after\nif (rotationHandler.OperationInProgress.Value)\n    rotationHandler.Update(deltaAngle);","handlingStrategy":"validation","validationCode":"// Guard Update against calls before Begin\nif (rotationHandler.OperationInProgress.Value)\n    rotationHandler.Update(deltaAngle, origin);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Route all Update calls through a guard that checks OperationInProgress.Value.","Ensure the input pipeline cannot deliver Update events after an operation is committed or cancelled.","Null out event subscriptions on Commit to prevent stale callbacks from delivering Update."],"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"}