ppy/osu · error · InvalidOperationException

Cannot Commit a rotate operation without calling Begin first

Error message

Cannot Commit a rotate operation without calling Begin first!

What it means

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.

Source

Thrown at osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs:96

            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);
                }
            }
        }

        public override void Commit()
        {
            if (!OperationInProgress.Value)
                throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!");

            changeHandler?.EndChange();

            base.Commit();

            objectsInRotation = null;
            originalPositions = null;
            originalPathControlPointPositions = null;
            DefaultOrigin = null;
        }

        private IEnumerable<OsuHitObject> selectedMovableObjects => selectedItems.Cast<OsuHitObject>()
                                                                                 .Where(h => h is not Spinner);
    }
}

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Ensure Begin() is called before Commit().
  2. Guard Commit() behind an OperationInProgress.Value check to skip if no operation is active.

Example fix

// before
rotationHandler.Commit(); // no prior Begin

// after
if (rotationHandler.OperationInProgress.Value)
    rotationHandler.Commit();
Defensive patterns

Strategy: validation

Validate before calling

// Guard Commit against calls without a prior Begin
if (rotationHandler.OperationInProgress.Value)
    rotationHandler.Commit();

Prevention

When it happens

Trigger: 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.

Common situations: Double-commit from duplicate event handlers; state machine lifecycle mismatch between the UI control and the rotation handler.

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/274cc68bb3bec187. Report an issue: GitHub.