ppy/osu · error · InvalidOperationException

Cannot Commit a rotate operation without calling Begin…

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.

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.

Appendix: 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)