ppy/osu · error · InvalidOperationException
Cannot Update a scale operation without calling Begin first!
Error message
Cannot Update a scale operation without calling Begin first!
What it means
OsuSelectionScaleHandler.Update() applies a scale delta using objectsInScale, defaultOrigin, and OriginalSurroundingQuad, which are populated by Begin(). Without Begin, these are null. The guard throws before the Debug.Assert on nulls would fire.
Solutions
- Ensure Begin() is called before any Update() in the scale lifecycle.
- Guard Update calls behind an OperationInProgress.Value check.
Example fix
// before
scaleHandler.Update(deltaScale); // called before Begin
// after
if (scaleHandler.OperationInProgress.Value)
scaleHandler.Update(deltaScale); Defensive patterns
Strategy: validation
Validate before calling
// Guard Update against calls before Begin
if (scaleHandler.OperationInProgress.Value)
scaleHandler.Update(deltaScale, origin, adjustAxis, axisRotation); Prevention
- Route all Update calls through an OperationInProgress.Value guard.
- Ensure event subscriptions are cleared on Commit to prevent stale callbacks.
When it happens
Trigger: Calling Update(scale, origin, adjustAxis, axisRotation) before Begin() has been called.
Common situations: Input event ordering issues where the scale delta handler runs before initialization; state desync between the UI scale control and the handler's OperationInProgress.
Related errors
- Cannot Begin a scale operation while another is in progress!
- Cannot Commit a rotate operation without calling Begin…
- Cannot Begin a rotate operation while another is in…
- Cannot Commit a rotate operation without calling Begin…
- Cannot Update a rotate operation without calling Begin…
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/df4168be64794d5d.
Report an issue: GitHub.
Appendix: source
Thrown at osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs:103
public override void Begin()
{
if (OperationInProgress.Value)
throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!");
base.Begin();
changeHandler?.BeginChange();
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
originalConvexHull = GeometryUtils.GetConvexHull(objectsInScale.Keys);
defaultOrigin = GeometryUtils.MinimumEnclosingCircle(originalConvexHull).Item1;
}
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
{
if (!OperationInProgress.Value)
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
Debug.Assert(objectsInScale != null && defaultOrigin != null && OriginalSurroundingQuad != null);
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
scale = clampScaleToAdjustAxis(scale, adjustAxis);
// for the time being, allow resizing of slider paths only if the slider is
// the only hit object selected. with a group selection, it's likely the user
// is not looking to change the duration of the slider but expand the whole pattern.
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
{
scaleSlider(slider, scale, actualOrigin, objectsInScale[slider], axisRotation);
}
else
{
scale = ClampScaleToPlayfieldBounds(scale, actualOrigin, adjustAxis, axisRotation);
foreach (var (ho, originalState) in objectsInScale)
View on GitHub (pinned to d9c73e12ad)