mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 16:52:55 +08:00
Merge branch 'master' into fix-slider-sample-parsing
This commit is contained in:
commit
146b15371d
@ -5,24 +5,24 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
namespace osu.Desktop.Windows
|
namespace osu.Desktop.Windows
|
||||||
{
|
{
|
||||||
public class GameplayWinKeyBlocker : Component
|
public class GameplayWinKeyBlocker : Component
|
||||||
{
|
{
|
||||||
private Bindable<bool> allowScreenSuspension;
|
|
||||||
private Bindable<bool> disableWinKey;
|
private Bindable<bool> disableWinKey;
|
||||||
|
private Bindable<bool> localUserPlaying;
|
||||||
|
|
||||||
private GameHost host;
|
[Resolved]
|
||||||
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, OsuConfigManager config)
|
private void load(OsuGame game, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
this.host = host;
|
localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
|
||||||
|
localUserPlaying.BindValueChanged(_ => updateBlocking());
|
||||||
allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy();
|
|
||||||
allowScreenSuspension.BindValueChanged(_ => updateBlocking());
|
|
||||||
|
|
||||||
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
|
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
|
||||||
disableWinKey.BindValueChanged(_ => updateBlocking(), true);
|
disableWinKey.BindValueChanged(_ => updateBlocking(), true);
|
||||||
@ -30,7 +30,7 @@ namespace osu.Desktop.Windows
|
|||||||
|
|
||||||
private void updateBlocking()
|
private void updateBlocking()
|
||||||
{
|
{
|
||||||
bool shouldDisable = disableWinKey.Value && !allowScreenSuspension.Value;
|
bool shouldDisable = disableWinKey.Value && localUserPlaying.Value;
|
||||||
|
|
||||||
if (shouldDisable)
|
if (shouldDisable)
|
||||||
host.InputThread.Scheduler.Add(WindowsKey.Disable);
|
host.InputThread.Scheduler.Add(WindowsKey.Disable);
|
||||||
|
@ -83,11 +83,17 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
RandomZ = snapshot.RandomZ;
|
RandomZ = snapshot.RandomZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void PostProcess()
|
||||||
|
{
|
||||||
|
base.PostProcess();
|
||||||
|
Objects.Sort();
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
|
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
|
||||||
public override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
|
public override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ConvertValue : IEquatable<ConvertValue>
|
public struct ConvertValue : IEquatable<ConvertValue>, IComparable<ConvertValue>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A sane value to account for osu!stable using ints everwhere.
|
/// A sane value to account for osu!stable using ints everwhere.
|
||||||
@ -102,5 +108,15 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
|
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
|
||||||
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
|
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
|
||||||
&& Column == other.Column;
|
&& Column == other.Column;
|
||||||
|
|
||||||
|
public int CompareTo(ConvertValue other)
|
||||||
|
{
|
||||||
|
var result = StartTime.CompareTo(other.StartTime);
|
||||||
|
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return Column.CompareTo(other.Column);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
int minColumn = int.MaxValue;
|
int minColumn = int.MaxValue;
|
||||||
int maxColumn = int.MinValue;
|
int maxColumn = int.MinValue;
|
||||||
|
|
||||||
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
|
||||||
{
|
{
|
||||||
if (obj.Column < minColumn)
|
if (obj.Column < minColumn)
|
||||||
minColumn = obj.Column;
|
minColumn = obj.Column;
|
||||||
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
|
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
|
||||||
|
|
||||||
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
|
||||||
obj.Column += columnDelta;
|
obj.Column += columnDelta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private void updatePath()
|
private void updatePath()
|
||||||
{
|
{
|
||||||
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
editorBeatmap?.UpdateHitObject(HitObject);
|
editorBeatmap?.Update(HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
base.OnSelectionChanged();
|
base.OnSelectionChanged();
|
||||||
|
|
||||||
bool canOperate = SelectedHitObjects.Count() > 1 || SelectedHitObjects.Any(s => s is Slider);
|
bool canOperate = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
|
||||||
|
|
||||||
SelectionBox.CanRotate = canOperate;
|
SelectionBox.CanRotate = canOperate;
|
||||||
SelectionBox.CanScaleX = canOperate;
|
SelectionBox.CanScaleX = canOperate;
|
||||||
@ -232,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// <param name="points">The points to calculate a quad for.</param>
|
/// <param name="points">The points to calculate a quad for.</param>
|
||||||
private Quad getSurroundingQuad(IEnumerable<Vector2> points)
|
private Quad getSurroundingQuad(IEnumerable<Vector2> points)
|
||||||
{
|
{
|
||||||
if (!SelectedHitObjects.Any())
|
if (!EditorBeatmap.SelectedHitObjects.Any())
|
||||||
return new Quad();
|
return new Quad();
|
||||||
|
|
||||||
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
|
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
|
||||||
@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// All osu! hitobjects which can be moved/rotated/scaled.
|
/// All osu! hitobjects which can be moved/rotated/scaled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private OsuHitObject[] selectedMovableObjects => SelectedHitObjects
|
private OsuHitObject[] selectedMovableObjects => EditorBeatmap.SelectedHitObjects
|
||||||
.OfType<OsuHitObject>()
|
.OfType<OsuHitObject>()
|
||||||
.Where(h => !(h is Spinner))
|
.Where(h => !(h is Spinner))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
@ -52,32 +52,32 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
public void SetStrongState(bool state)
|
public void SetStrongState(bool state)
|
||||||
{
|
{
|
||||||
var hits = SelectedHitObjects.OfType<Hit>();
|
var hits = EditorBeatmap.SelectedHitObjects.OfType<Hit>();
|
||||||
|
|
||||||
ChangeHandler.BeginChange();
|
EditorBeatmap.BeginChange();
|
||||||
|
|
||||||
foreach (var h in hits)
|
foreach (var h in hits)
|
||||||
{
|
{
|
||||||
if (h.IsStrong != state)
|
if (h.IsStrong != state)
|
||||||
{
|
{
|
||||||
h.IsStrong = state;
|
h.IsStrong = state;
|
||||||
EditorBeatmap.UpdateHitObject(h);
|
EditorBeatmap.Update(h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeHandler.EndChange();
|
EditorBeatmap.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRimState(bool state)
|
public void SetRimState(bool state)
|
||||||
{
|
{
|
||||||
var hits = SelectedHitObjects.OfType<Hit>();
|
var hits = EditorBeatmap.SelectedHitObjects.OfType<Hit>();
|
||||||
|
|
||||||
ChangeHandler.BeginChange();
|
EditorBeatmap.BeginChange();
|
||||||
|
|
||||||
foreach (var h in hits)
|
foreach (var h in hits)
|
||||||
h.Type = state ? HitType.Rim : HitType.Centre;
|
h.Type = state ? HitType.Rim : HitType.Centre;
|
||||||
|
|
||||||
ChangeHandler.EndChange();
|
EditorBeatmap.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection)
|
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection)
|
||||||
@ -95,8 +95,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
base.UpdateTernaryStates();
|
base.UpdateTernaryStates();
|
||||||
|
|
||||||
selectionRimState.Value = GetStateFromSelection(SelectedHitObjects.OfType<Hit>(), h => h.Type == HitType.Rim);
|
selectionRimState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType<Hit>(), h => h.Type == HitType.Rim);
|
||||||
selectionStrongState.Value = GetStateFromSelection(SelectedHitObjects.OfType<TaikoHitObject>(), h => h.IsStrong);
|
selectionStrongState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType<TaikoHitObject>(), h => h.IsStrong);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
100
osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs
Normal file
100
osu.Game.Tests/Editing/TransactionalCommitComponentTest.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TransactionalCommitComponentTest
|
||||||
|
{
|
||||||
|
private TestHandler handler;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
handler = new TestHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCommitTransaction()
|
||||||
|
{
|
||||||
|
Assert.That(handler.StateUpdateCount, Is.EqualTo(0));
|
||||||
|
|
||||||
|
handler.BeginChange();
|
||||||
|
Assert.That(handler.StateUpdateCount, Is.EqualTo(0));
|
||||||
|
handler.EndChange();
|
||||||
|
|
||||||
|
Assert.That(handler.StateUpdateCount, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSaveOutsideOfTransactionTriggersUpdates()
|
||||||
|
{
|
||||||
|
Assert.That(handler.StateUpdateCount, Is.EqualTo(0));
|
||||||
|
|
||||||
|
handler.SaveState();
|
||||||
|
Assert.That(handler.StateUpdateCount, Is.EqualTo(1));
|
||||||
|
|
||||||
|
handler.SaveState();
|
||||||
|
Assert.That(handler.StateUpdateCount, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEventsFire()
|
||||||
|
{
|
||||||
|
int transactionBegan = 0;
|
||||||
|
int transactionEnded = 0;
|
||||||
|
int stateSaved = 0;
|
||||||
|
|
||||||
|
handler.TransactionBegan += () => transactionBegan++;
|
||||||
|
handler.TransactionEnded += () => transactionEnded++;
|
||||||
|
handler.SaveStateTriggered += () => stateSaved++;
|
||||||
|
|
||||||
|
handler.BeginChange();
|
||||||
|
Assert.That(transactionBegan, Is.EqualTo(1));
|
||||||
|
|
||||||
|
handler.EndChange();
|
||||||
|
Assert.That(transactionEnded, Is.EqualTo(1));
|
||||||
|
|
||||||
|
Assert.That(stateSaved, Is.EqualTo(0));
|
||||||
|
handler.SaveState();
|
||||||
|
Assert.That(stateSaved, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSaveDuringTransactionDoesntTriggerUpdate()
|
||||||
|
{
|
||||||
|
Assert.That(handler.StateUpdateCount, Is.EqualTo(0));
|
||||||
|
|
||||||
|
handler.BeginChange();
|
||||||
|
|
||||||
|
handler.SaveState();
|
||||||
|
Assert.That(handler.StateUpdateCount, Is.EqualTo(0));
|
||||||
|
|
||||||
|
handler.EndChange();
|
||||||
|
|
||||||
|
Assert.That(handler.StateUpdateCount, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEndWithoutBeginThrows()
|
||||||
|
{
|
||||||
|
handler.BeginChange();
|
||||||
|
handler.EndChange();
|
||||||
|
Assert.That(() => handler.EndChange(), Throws.TypeOf<InvalidOperationException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestHandler : TransactionalCommitComponent
|
||||||
|
{
|
||||||
|
public int StateUpdateCount { get; private set; }
|
||||||
|
|
||||||
|
protected override void UpdateState()
|
||||||
|
{
|
||||||
|
StateUpdateCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -203,7 +203,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
// handle positional change etc.
|
// handle positional change etc.
|
||||||
foreach (var obj in selectedHitObjects)
|
foreach (var obj in selectedHitObjects)
|
||||||
Beatmap.UpdateHitObject(obj);
|
Beatmap.Update(obj);
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
changeHandler?.EndChange();
|
||||||
isDraggingBlueprint = false;
|
isDraggingBlueprint = false;
|
||||||
@ -436,8 +436,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
// Apply the start time at the newly snapped-to position
|
// Apply the start time at the newly snapped-to position
|
||||||
double offset = result.Time.Value - draggedObject.StartTime;
|
double offset = result.Time.Value - draggedObject.StartTime;
|
||||||
foreach (HitObject obj in SelectionHandler.SelectedHitObjects)
|
|
||||||
|
foreach (HitObject obj in Beatmap.SelectedHitObjects)
|
||||||
|
{
|
||||||
obj.StartTime += offset;
|
obj.StartTime += offset;
|
||||||
|
Beatmap.Update(obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -37,15 +37,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
public int SelectedCount => selectedBlueprints.Count;
|
public int SelectedCount => selectedBlueprints.Count;
|
||||||
|
|
||||||
public IEnumerable<HitObject> SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject);
|
|
||||||
|
|
||||||
private Drawable content;
|
private Drawable content;
|
||||||
|
|
||||||
private OsuSpriteText selectionDetailsText;
|
private OsuSpriteText selectionDetailsText;
|
||||||
|
|
||||||
protected SelectionBox SelectionBox { get; private set; }
|
protected SelectionBox SelectionBox { get; private set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
protected EditorBeatmap EditorBeatmap { get; private set; }
|
protected EditorBeatmap EditorBeatmap { get; private set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
@ -245,9 +243,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private void deleteSelected()
|
private void deleteSelected()
|
||||||
{
|
{
|
||||||
ChangeHandler?.BeginChange();
|
EditorBeatmap.RemoveRange(selectedBlueprints.Select(b => b.HitObject));
|
||||||
EditorBeatmap?.RemoveRange(selectedBlueprints.Select(b => b.HitObject));
|
|
||||||
ChangeHandler?.EndChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -314,9 +310,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <param name="sampleName">The name of the hit sample.</param>
|
/// <param name="sampleName">The name of the hit sample.</param>
|
||||||
public void AddHitSample(string sampleName)
|
public void AddHitSample(string sampleName)
|
||||||
{
|
{
|
||||||
ChangeHandler?.BeginChange();
|
EditorBeatmap.BeginChange();
|
||||||
|
|
||||||
foreach (var h in SelectedHitObjects)
|
foreach (var h in EditorBeatmap.SelectedHitObjects)
|
||||||
{
|
{
|
||||||
// Make sure there isn't already an existing sample
|
// Make sure there isn't already an existing sample
|
||||||
if (h.Samples.Any(s => s.Name == sampleName))
|
if (h.Samples.Any(s => s.Name == sampleName))
|
||||||
@ -325,7 +321,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
h.Samples.Add(new HitSampleInfo { Name = sampleName });
|
h.Samples.Add(new HitSampleInfo { Name = sampleName });
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeHandler?.EndChange();
|
EditorBeatmap.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -335,19 +331,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <exception cref="InvalidOperationException">Throws if any selected object doesn't implement <see cref="IHasComboInformation"/></exception>
|
/// <exception cref="InvalidOperationException">Throws if any selected object doesn't implement <see cref="IHasComboInformation"/></exception>
|
||||||
public void SetNewCombo(bool state)
|
public void SetNewCombo(bool state)
|
||||||
{
|
{
|
||||||
ChangeHandler?.BeginChange();
|
EditorBeatmap.BeginChange();
|
||||||
|
|
||||||
foreach (var h in SelectedHitObjects)
|
foreach (var h in EditorBeatmap.SelectedHitObjects)
|
||||||
{
|
{
|
||||||
var comboInfo = h as IHasComboInformation;
|
var comboInfo = h as IHasComboInformation;
|
||||||
|
|
||||||
if (comboInfo == null || comboInfo.NewCombo == state) continue;
|
if (comboInfo == null || comboInfo.NewCombo == state) continue;
|
||||||
|
|
||||||
comboInfo.NewCombo = state;
|
comboInfo.NewCombo = state;
|
||||||
EditorBeatmap?.UpdateHitObject(h);
|
EditorBeatmap.Update(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeHandler?.EndChange();
|
EditorBeatmap.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -356,12 +352,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <param name="sampleName">The name of the hit sample.</param>
|
/// <param name="sampleName">The name of the hit sample.</param>
|
||||||
public void RemoveHitSample(string sampleName)
|
public void RemoveHitSample(string sampleName)
|
||||||
{
|
{
|
||||||
ChangeHandler?.BeginChange();
|
EditorBeatmap.BeginChange();
|
||||||
|
|
||||||
foreach (var h in SelectedHitObjects)
|
foreach (var h in EditorBeatmap.SelectedHitObjects)
|
||||||
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
||||||
|
|
||||||
ChangeHandler?.EndChange();
|
EditorBeatmap.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -432,11 +428,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void UpdateTernaryStates()
|
protected virtual void UpdateTernaryStates()
|
||||||
{
|
{
|
||||||
SelectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType<IHasComboInformation>(), h => h.NewCombo);
|
SelectionNewComboState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType<IHasComboInformation>(), h => h.NewCombo);
|
||||||
|
|
||||||
foreach (var (sampleName, bindable) in SelectionSampleStates)
|
foreach (var (sampleName, bindable) in SelectionSampleStates)
|
||||||
{
|
{
|
||||||
bindable.Value = GetStateFromSelection(SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName));
|
bindable.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,6 +392,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
repeatHitObject.RepeatCount = proposedCount;
|
repeatHitObject.RepeatCount = proposedCount;
|
||||||
|
beatmap.Update(hitObject);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IHasDuration endTimeHitObject:
|
case IHasDuration endTimeHitObject:
|
||||||
@ -401,10 +402,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
endTimeHitObject.Duration = snappedTime - hitObject.StartTime;
|
endTimeHitObject.Duration = snappedTime - hitObject.StartTime;
|
||||||
|
beatmap.Update(hitObject);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
beatmap.UpdateHitObject(hitObject);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,14 +516,14 @@ namespace osu.Game.Screens.Edit
|
|||||||
foreach (var h in objects)
|
foreach (var h in objects)
|
||||||
h.StartTime += timeOffset;
|
h.StartTime += timeOffset;
|
||||||
|
|
||||||
changeHandler.BeginChange();
|
editorBeatmap.BeginChange();
|
||||||
|
|
||||||
editorBeatmap.SelectedHitObjects.Clear();
|
editorBeatmap.SelectedHitObjects.Clear();
|
||||||
|
|
||||||
editorBeatmap.AddRange(objects);
|
editorBeatmap.AddRange(objects);
|
||||||
editorBeatmap.SelectedHitObjects.AddRange(objects);
|
editorBeatmap.SelectedHitObjects.AddRange(objects);
|
||||||
|
|
||||||
changeHandler.EndChange();
|
editorBeatmap.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void Undo() => changeHandler.RestoreState(-1);
|
protected void Undo() => changeHandler.RestoreState(-1);
|
||||||
|
@ -8,7 +8,6 @@ using System.Linq;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
@ -18,7 +17,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
{
|
{
|
||||||
public class EditorBeatmap : Component, IBeatmap, IBeatSnapProvider
|
public class EditorBeatmap : TransactionalCommitComponent, IBeatmap, IBeatSnapProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="HitObject"/> is added to this <see cref="EditorBeatmap"/>.
|
/// Invoked when a <see cref="HitObject"/> is added to this <see cref="EditorBeatmap"/>.
|
||||||
@ -89,9 +88,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects;
|
private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects;
|
||||||
|
|
||||||
private readonly HashSet<HitObject> pendingUpdates = new HashSet<HitObject>();
|
private readonly List<HitObject> batchPendingInserts = new List<HitObject>();
|
||||||
|
|
||||||
private bool isBatchApplying;
|
private readonly List<HitObject> batchPendingDeletes = new List<HitObject>();
|
||||||
|
|
||||||
|
private readonly HashSet<HitObject> batchPendingUpdates = new HashSet<HitObject>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a collection of <see cref="HitObject"/>s to this <see cref="EditorBeatmap"/>.
|
/// Adds a collection of <see cref="HitObject"/>s to this <see cref="EditorBeatmap"/>.
|
||||||
@ -99,11 +100,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// <param name="hitObjects">The <see cref="HitObject"/>s to add.</param>
|
/// <param name="hitObjects">The <see cref="HitObject"/>s to add.</param>
|
||||||
public void AddRange(IEnumerable<HitObject> hitObjects)
|
public void AddRange(IEnumerable<HitObject> hitObjects)
|
||||||
{
|
{
|
||||||
ApplyBatchChanges(_ =>
|
BeginChange();
|
||||||
{
|
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
Add(h);
|
Add(h);
|
||||||
});
|
EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -131,26 +131,28 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
mutableHitObjects.Insert(index, hitObject);
|
mutableHitObjects.Insert(index, hitObject);
|
||||||
|
|
||||||
if (isBatchApplying)
|
BeginChange();
|
||||||
batchPendingInserts.Add(hitObject);
|
batchPendingInserts.Add(hitObject);
|
||||||
else
|
EndChange();
|
||||||
{
|
|
||||||
// must be run after any change to hitobject ordering
|
|
||||||
beatmapProcessor?.PreProcess();
|
|
||||||
processHitObject(hitObject);
|
|
||||||
beatmapProcessor?.PostProcess();
|
|
||||||
|
|
||||||
HitObjectAdded?.Invoke(hitObject);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates a <see cref="HitObject"/>, invoking <see cref="HitObject.ApplyDefaults"/> and re-processing the beatmap.
|
/// Updates a <see cref="HitObject"/>, invoking <see cref="HitObject.ApplyDefaults"/> and re-processing the beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hitObject">The <see cref="HitObject"/> to update.</param>
|
/// <param name="hitObject">The <see cref="HitObject"/> to update.</param>
|
||||||
public void UpdateHitObject([NotNull] HitObject hitObject)
|
public void Update([NotNull] HitObject hitObject)
|
||||||
{
|
{
|
||||||
pendingUpdates.Add(hitObject);
|
// updates are debounced regardless of whether a batch is active.
|
||||||
|
batchPendingUpdates.Add(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update all hit objects with potentially changed difficulty or control point data.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateAllHitObjects()
|
||||||
|
{
|
||||||
|
foreach (var h in HitObjects)
|
||||||
|
batchPendingUpdates.Add(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -175,11 +177,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// <param name="hitObjects">The <see cref="HitObject"/>s to remove.</param>
|
/// <param name="hitObjects">The <see cref="HitObject"/>s to remove.</param>
|
||||||
public void RemoveRange(IEnumerable<HitObject> hitObjects)
|
public void RemoveRange(IEnumerable<HitObject> hitObjects)
|
||||||
{
|
{
|
||||||
ApplyBatchChanges(_ =>
|
BeginChange();
|
||||||
{
|
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
Remove(h);
|
Remove(h);
|
||||||
});
|
EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -203,50 +204,45 @@ namespace osu.Game.Screens.Edit
|
|||||||
bindable.UnbindAll();
|
bindable.UnbindAll();
|
||||||
startTimeBindables.Remove(hitObject);
|
startTimeBindables.Remove(hitObject);
|
||||||
|
|
||||||
if (isBatchApplying)
|
BeginChange();
|
||||||
batchPendingDeletes.Add(hitObject);
|
batchPendingDeletes.Add(hitObject);
|
||||||
else
|
EndChange();
|
||||||
{
|
|
||||||
// must be run after any change to hitobject ordering
|
|
||||||
beatmapProcessor?.PreProcess();
|
|
||||||
processHitObject(hitObject);
|
|
||||||
beatmapProcessor?.PostProcess();
|
|
||||||
|
|
||||||
HitObjectRemoved?.Invoke(hitObject);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<HitObject> batchPendingInserts = new List<HitObject>();
|
protected override void Update()
|
||||||
|
|
||||||
private readonly List<HitObject> batchPendingDeletes = new List<HitObject>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Apply a batch of operations in one go, without performing Pre/Postprocessing each time.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="applyFunction">The function which will apply the batch changes.</param>
|
|
||||||
public void ApplyBatchChanges(Action<EditorBeatmap> applyFunction)
|
|
||||||
{
|
{
|
||||||
if (isBatchApplying)
|
base.Update();
|
||||||
throw new InvalidOperationException("Attempting to perform a batch application from within an existing batch");
|
|
||||||
|
|
||||||
isBatchApplying = true;
|
if (batchPendingUpdates.Count > 0)
|
||||||
|
UpdateState();
|
||||||
|
}
|
||||||
|
|
||||||
applyFunction(this);
|
protected override void UpdateState()
|
||||||
|
{
|
||||||
|
if (batchPendingUpdates.Count == 0 && batchPendingDeletes.Count == 0 && batchPendingInserts.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
beatmapProcessor?.PreProcess();
|
beatmapProcessor?.PreProcess();
|
||||||
|
|
||||||
foreach (var h in batchPendingDeletes) processHitObject(h);
|
foreach (var h in batchPendingDeletes) processHitObject(h);
|
||||||
foreach (var h in batchPendingInserts) processHitObject(h);
|
foreach (var h in batchPendingInserts) processHitObject(h);
|
||||||
|
foreach (var h in batchPendingUpdates) processHitObject(h);
|
||||||
|
|
||||||
beatmapProcessor?.PostProcess();
|
beatmapProcessor?.PostProcess();
|
||||||
|
|
||||||
foreach (var h in batchPendingDeletes) HitObjectRemoved?.Invoke(h);
|
// callbacks may modify the lists so let's be safe about it
|
||||||
foreach (var h in batchPendingInserts) HitObjectAdded?.Invoke(h);
|
var deletes = batchPendingDeletes.ToArray();
|
||||||
|
|
||||||
batchPendingDeletes.Clear();
|
batchPendingDeletes.Clear();
|
||||||
|
|
||||||
|
var inserts = batchPendingInserts.ToArray();
|
||||||
batchPendingInserts.Clear();
|
batchPendingInserts.Clear();
|
||||||
|
|
||||||
isBatchApplying = false;
|
var updates = batchPendingUpdates.ToArray();
|
||||||
|
batchPendingUpdates.Clear();
|
||||||
|
|
||||||
|
foreach (var h in deletes) HitObjectRemoved?.Invoke(h);
|
||||||
|
foreach (var h in inserts) HitObjectAdded?.Invoke(h);
|
||||||
|
foreach (var h in updates) HitObjectUpdated?.Invoke(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -254,28 +250,6 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Clear() => RemoveRange(HitObjects.ToArray());
|
public void Clear() => RemoveRange(HitObjects.ToArray());
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
// debounce updates as they are common and may come from input events, which can run needlessly many times per update frame.
|
|
||||||
if (pendingUpdates.Count > 0)
|
|
||||||
{
|
|
||||||
beatmapProcessor?.PreProcess();
|
|
||||||
|
|
||||||
foreach (var hitObject in pendingUpdates)
|
|
||||||
processHitObject(hitObject);
|
|
||||||
|
|
||||||
beatmapProcessor?.PostProcess();
|
|
||||||
|
|
||||||
// explicitly needs to be fired after PostProcess
|
|
||||||
foreach (var hitObject in pendingUpdates)
|
|
||||||
HitObjectUpdated?.Invoke(hitObject);
|
|
||||||
|
|
||||||
pendingUpdates.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty);
|
private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty);
|
||||||
|
|
||||||
private void trackStartTime(HitObject hitObject)
|
private void trackStartTime(HitObject hitObject)
|
||||||
@ -289,7 +263,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime);
|
var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime);
|
||||||
mutableHitObjects.Insert(insertionIndex + 1, hitObject);
|
mutableHitObjects.Insert(insertionIndex + 1, hitObject);
|
||||||
|
|
||||||
UpdateHitObject(hitObject);
|
Update(hitObject);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,14 +289,5 @@ namespace osu.Game.Screens.Edit
|
|||||||
public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor;
|
public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor;
|
||||||
|
|
||||||
public int BeatDivisor => beatDivisor?.Value ?? 1;
|
public int BeatDivisor => beatDivisor?.Value ?? 1;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update all hit objects with potentially changed difficulty or control point data.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateBeatmap()
|
|
||||||
{
|
|
||||||
foreach (var h in HitObjects)
|
|
||||||
pendingUpdates.Add(h);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracks changes to the <see cref="Editor"/>.
|
/// Tracks changes to the <see cref="Editor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EditorChangeHandler : IEditorChangeHandler
|
public class EditorChangeHandler : TransactionalCommitComponent, IEditorChangeHandler
|
||||||
{
|
{
|
||||||
public readonly Bindable<bool> CanUndo = new Bindable<bool>();
|
public readonly Bindable<bool> CanUndo = new Bindable<bool>();
|
||||||
public readonly Bindable<bool> CanRedo = new Bindable<bool>();
|
public readonly Bindable<bool> CanRedo = new Bindable<bool>();
|
||||||
@ -41,7 +41,6 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly EditorBeatmap editorBeatmap;
|
private readonly EditorBeatmap editorBeatmap;
|
||||||
private int bulkChangesStarted;
|
|
||||||
private bool isRestoring;
|
private bool isRestoring;
|
||||||
|
|
||||||
public const int MAX_SAVED_STATES = 50;
|
public const int MAX_SAVED_STATES = 50;
|
||||||
@ -54,9 +53,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
{
|
{
|
||||||
this.editorBeatmap = editorBeatmap;
|
this.editorBeatmap = editorBeatmap;
|
||||||
|
|
||||||
editorBeatmap.HitObjectAdded += hitObjectAdded;
|
editorBeatmap.TransactionBegan += BeginChange;
|
||||||
editorBeatmap.HitObjectRemoved += hitObjectRemoved;
|
editorBeatmap.TransactionEnded += EndChange;
|
||||||
editorBeatmap.HitObjectUpdated += hitObjectUpdated;
|
editorBeatmap.SaveStateTriggered += SaveState;
|
||||||
|
|
||||||
patcher = new LegacyEditorBeatmapPatcher(editorBeatmap);
|
patcher = new LegacyEditorBeatmapPatcher(editorBeatmap);
|
||||||
|
|
||||||
@ -64,28 +63,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
SaveState();
|
SaveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hitObjectAdded(HitObject obj) => SaveState();
|
protected override void UpdateState()
|
||||||
|
|
||||||
private void hitObjectRemoved(HitObject obj) => SaveState();
|
|
||||||
|
|
||||||
private void hitObjectUpdated(HitObject obj) => SaveState();
|
|
||||||
|
|
||||||
public void BeginChange() => bulkChangesStarted++;
|
|
||||||
|
|
||||||
public void EndChange()
|
|
||||||
{
|
{
|
||||||
if (bulkChangesStarted == 0)
|
|
||||||
throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}.");
|
|
||||||
|
|
||||||
if (--bulkChangesStarted == 0)
|
|
||||||
SaveState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveState()
|
|
||||||
{
|
|
||||||
if (bulkChangesStarted > 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (isRestoring)
|
if (isRestoring)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -120,7 +99,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// <param name="direction">The direction to restore in. If less than 0, an older state will be used. If greater than 0, a newer state will be used.</param>
|
/// <param name="direction">The direction to restore in. If less than 0, an older state will be used. If greater than 0, a newer state will be used.</param>
|
||||||
public void RestoreState(int direction)
|
public void RestoreState(int direction)
|
||||||
{
|
{
|
||||||
if (bulkChangesStarted > 0)
|
if (TransactionActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (savedStates.Count == 0)
|
if (savedStates.Count == 0)
|
||||||
|
@ -68,19 +68,20 @@ namespace osu.Game.Screens.Edit
|
|||||||
toRemove.Sort();
|
toRemove.Sort();
|
||||||
toAdd.Sort();
|
toAdd.Sort();
|
||||||
|
|
||||||
editorBeatmap.ApplyBatchChanges(eb =>
|
editorBeatmap.BeginChange();
|
||||||
{
|
|
||||||
// Apply the changes.
|
// Apply the changes.
|
||||||
for (int i = toRemove.Count - 1; i >= 0; i--)
|
for (int i = toRemove.Count - 1; i >= 0; i--)
|
||||||
eb.RemoveAt(toRemove[i]);
|
editorBeatmap.RemoveAt(toRemove[i]);
|
||||||
|
|
||||||
if (toAdd.Count > 0)
|
if (toAdd.Count > 0)
|
||||||
{
|
{
|
||||||
IBeatmap newBeatmap = readBeatmap(newState);
|
IBeatmap newBeatmap = readBeatmap(newState);
|
||||||
foreach (var i in toAdd)
|
foreach (var i in toAdd)
|
||||||
eb.Insert(i, newBeatmap.HitObjects[i]);
|
editorBeatmap.Insert(i, newBeatmap.HitObjects[i]);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
editorBeatmap.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string readString(byte[] state) => Encoding.UTF8.GetString(state);
|
private string readString(byte[] state) => Encoding.UTF8.GetString(state);
|
||||||
|
@ -93,7 +93,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value;
|
Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value;
|
||||||
Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||||
|
|
||||||
editorBeatmap.UpdateBeatmap();
|
editorBeatmap.UpdateAllHitObjects();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
73
osu.Game/Screens/Edit/TransactionalCommitComponent.cs
Normal file
73
osu.Game/Screens/Edit/TransactionalCommitComponent.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A component that tracks a batch change, only applying after all active changes are completed.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class TransactionalCommitComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fires whenever a transaction begins. Will not fire on nested transactions.
|
||||||
|
/// </summary>
|
||||||
|
public event Action TransactionBegan;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when the last transaction completes.
|
||||||
|
/// </summary>
|
||||||
|
public event Action TransactionEnded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when <see cref="SaveState"/> is called and results in a non-transactional state save.
|
||||||
|
/// </summary>
|
||||||
|
public event Action SaveStateTriggered;
|
||||||
|
|
||||||
|
public bool TransactionActive => bulkChangesStarted > 0;
|
||||||
|
|
||||||
|
private int bulkChangesStarted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signal the beginning of a change.
|
||||||
|
/// </summary>
|
||||||
|
public void BeginChange()
|
||||||
|
{
|
||||||
|
if (bulkChangesStarted++ == 0)
|
||||||
|
TransactionBegan?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signal the end of a change.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Throws if <see cref="BeginChange"/> was not first called.</exception>
|
||||||
|
public void EndChange()
|
||||||
|
{
|
||||||
|
if (bulkChangesStarted == 0)
|
||||||
|
throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}.");
|
||||||
|
|
||||||
|
if (--bulkChangesStarted == 0)
|
||||||
|
{
|
||||||
|
UpdateState();
|
||||||
|
TransactionEnded?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Force an update of the state with no attached transaction.
|
||||||
|
/// This is a no-op if a transaction is already active. Should generally be used as a safety measure to ensure granular changes are not left outside a transaction.
|
||||||
|
/// </summary>
|
||||||
|
public void SaveState()
|
||||||
|
{
|
||||||
|
if (bulkChangesStarted > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SaveStateTriggered?.Invoke();
|
||||||
|
UpdateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void UpdateState();
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,12 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
var ourResult = convert(name, mods.Select(m => (Mod)Activator.CreateInstance(m)).ToArray());
|
var ourResult = convert(name, mods.Select(m => (Mod)Activator.CreateInstance(m)).ToArray());
|
||||||
var expectedResult = read(name);
|
var expectedResult = read(name);
|
||||||
|
|
||||||
|
foreach (var m in ourResult.Mappings)
|
||||||
|
m.PostProcess();
|
||||||
|
|
||||||
|
foreach (var m in expectedResult.Mappings)
|
||||||
|
m.PostProcess();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
int mappingCounter = 0;
|
int mappingCounter = 0;
|
||||||
@ -239,6 +245,13 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
set => Objects = value;
|
set => Objects = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked after this <see cref="ConvertMapping{TConvertValue}"/> is populated to post-process the contained data.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void PostProcess()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public virtual bool Equals(ConvertMapping<TConvertValue> other) => StartTime == other?.StartTime;
|
public virtual bool Equals(ConvertMapping<TConvertValue> other) => StartTime == other?.StartTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user