ppy/osu · error · InvalidOperationException
Cannot Update a rotate operation without calling Begin first
Error message
Cannot Update a rotate operation without calling Begin first!
What it means
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.
Source
Thrown at osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs:73
if (OperationInProgress.Value)
throw new InvalidOperationException($"Cannot {nameof(Begin)} a rotate operation while another is in progress!");
base.Begin();
changeHandler?.BeginChange();
objectsInRotation = selectedMovableObjects.ToArray();
DefaultOrigin = GeometryUtils.MinimumEnclosingCircle(objectsInRotation).Item1;
originalPositions = objectsInRotation.ToDictionary(obj => obj, obj => obj.Position);
originalPathControlPointPositions = objectsInRotation.OfType<IHasPath>().ToDictionary(
obj => obj,
obj => obj.Path.ControlPoints.Select(point => point.Position).ToArray());
}
public override void Update(float rotation, Vector2? origin = null)
{
if (!OperationInProgress.Value)
throw new InvalidOperationException($"Cannot {nameof(Update)} a rotate operation without calling {nameof(Begin)} first!");
Debug.Assert(objectsInRotation != null && originalPositions != null && originalPathControlPointPositions != null && DefaultOrigin != null);
Vector2 actualOrigin = origin ?? DefaultOrigin.Value;
foreach (var ho in objectsInRotation)
{
ho.Position = GeometryUtils.RotatePointAroundOrigin(originalPositions[ho], actualOrigin, rotation);
if (ho is IHasPath withPath)
{
var originalPath = originalPathControlPointPositions[withPath];
for (int i = 0; i < withPath.Path.ControlPoints.Count; ++i)
withPath.Path.ControlPoints[i].Position = GeometryUtils.RotatePointAroundOrigin(originalPath[i], Vector2.Zero, rotation);
}
}
}
View on GitHub (pinned to d9c73e12ad)
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.
Example fix
// before
rotationHandler.Update(deltaAngle); // called before Begin
// after
if (rotationHandler.OperationInProgress.Value)
rotationHandler.Update(deltaAngle); Defensive patterns
Strategy: validation
Validate before calling
// Guard Update against calls before Begin
if (rotationHandler.OperationInProgress.Value)
rotationHandler.Update(deltaAngle, origin); Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot Begin a rotate operation while another is in progress
- Cannot Commit a rotate operation without calling Begin first
- Cannot Begin a scale operation while another is in progress!
- Cannot Update a scale operation without calling Begin first!
- Cannot Commit a rotate operation without calling Begin first
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/371be55429868085.
Report an issue: GitHub.