diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs
index 5fc1082743..8b3fead366 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
Position = new Vector2(128, 128),
ComboIndex = 1,
- }), null));
+ })));
}
private HitCircle prepareObject(HitCircle circle)
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
index aac6db60fe..e698766aac 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(300, 0),
}),
RepeatCount = 1
- }), null));
+ })));
}
[Test]
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs
index d7fbc7ac48..8c97c02049 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Position = new Vector2(256, 192),
ComboIndex = 1,
Duration = 1000,
- }), null));
+ })));
AddAssert("rotation is reset", () => dho.Result.RateAdjustedRotation == 0);
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs
index a970965141..f33c738b04 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
StartTime = 400,
Major = true
- }), null));
+ })));
AddHitObject(barLine);
RemoveHitObject(barLine);
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
StartTime = 200,
Major = false
- }), null));
+ })));
AddHitObject(barLine);
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs
index 54450e27db..c389a05566 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Duration = 500,
IsStrong = false,
TickRate = 2
- }), null));
+ })));
AddHitObject(drumRoll);
RemoveHitObject(drumRoll);
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Duration = 400,
IsStrong = true,
TickRate = 16
- }), null));
+ })));
AddHitObject(drumRoll);
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs
index 52fd440857..c2f251fcb6 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Type = HitType.Rim,
IsStrong = false,
StartTime = 300
- }), null));
+ })));
AddHitObject(hit);
RemoveHitObject(hit);
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Type = HitType.Centre,
IsStrong = true,
StartTime = 500
- }), null));
+ })));
AddHitObject(hit);
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 669e4cecbe..ba2b8423d0 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
@@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// The currently represented by this .
///
- public HitObject HitObject { get; private set; }
+ public HitObject HitObject => lifetimeEntry?.HitObject;
///
/// The parenting , if any.
@@ -108,7 +109,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// The scoring result of this .
///
- public JudgementResult Result { get; private set; }
+ public JudgementResult Result => lifetimeEntry?.Result;
///
/// The relative X position of this hit object for sample playback balance adjustment.
@@ -141,13 +142,14 @@ namespace osu.Game.Rulesets.Objects.Drawables
public IBindable State => state;
///
- /// Whether is currently applied.
+ /// Whether a is currently applied.
///
- private bool hasHitObjectApplied;
+ private bool hasEntryApplied;
///
/// The controlling the lifetime of the currently-attached .
///
+ /// Even if it is not null, it may not be fully applied until loaded ( is false).
[CanBeNull]
private HitObjectLifetimeEntry lifetimeEntry;
@@ -164,11 +166,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
///
/// The to be initially applied to this .
- /// If null, a hitobject is expected to be later applied via (or automatically via pooling).
+ /// If null, a hitobject is expected to be later applied via (or automatically via pooling).
///
protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null)
{
- HitObject = initialHitObject;
+ if (initialHitObject != null)
+ {
+ lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject);
+ ensureEntryHasResult();
+ }
}
[BackgroundDependencyLoader]
@@ -184,8 +190,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
base.LoadAsyncComplete();
- if (HitObject != null)
- Apply(HitObject, lifetimeEntry);
+ if (lifetimeEntry != null && !hasEntryApplied)
+ Apply(lifetimeEntry);
}
protected override void LoadComplete()
@@ -198,37 +204,47 @@ namespace osu.Game.Rulesets.Objects.Drawables
}
///
- /// Applies a new to be represented by this .
+ /// Applies a hit object to be represented by this .
///
- /// The to apply.
- /// The controlling the lifetime of .
+ [Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")]
public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
+ {
+ if (lifetimeEntry != null)
+ Apply(lifetimeEntry);
+ else
+ Apply(hitObject);
+ }
+
+ ///
+ /// Applies a new to be represented by this .
+ /// A new is automatically created and applied to this .
+ ///
+ public void Apply([NotNull] HitObject hitObject)
+ {
+ if (hitObject == null)
+ throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}.");
+
+ Apply(new SyntheticHitObjectEntry(hitObject));
+ }
+
+ ///
+ /// Applies a new to be represented by this .
+ ///
+ public void Apply([NotNull] HitObjectLifetimeEntry newEntry)
{
free();
- HitObject = hitObject ?? throw new InvalidOperationException($"Cannot apply a null {nameof(HitObject)}.");
+ lifetimeEntry = newEntry;
- this.lifetimeEntry = lifetimeEntry;
+ // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset.
+ // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO.
+ if (newEntry is SyntheticHitObjectEntry)
+ lifetimeEntry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
- if (lifetimeEntry != null)
- {
- // Transfer lifetime from the entry.
- LifetimeStart = lifetimeEntry.LifetimeStart;
- LifetimeEnd = lifetimeEntry.LifetimeEnd;
+ LifetimeStart = lifetimeEntry.LifetimeStart;
+ LifetimeEnd = lifetimeEntry.LifetimeEnd;
- // Copy any existing result from the entry (required for rewind / judgement revert).
- Result = lifetimeEntry.Result;
- }
- else
- LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
-
- // Ensure this DHO has a result.
- Result ??= CreateResult(HitObject.CreateJudgement())
- ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
-
- // Copy back the result to the entry for potential future retrieval.
- if (lifetimeEntry != null)
- lifetimeEntry.Result = Result;
+ ensureEntryHasResult();
foreach (var h in HitObject.NestedHitObjects)
{
@@ -278,16 +294,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
updateState(ArmedState.Idle, true);
}
- hasHitObjectApplied = true;
+ hasEntryApplied = true;
}
///
- /// Removes the currently applied
+ /// Removes the currently applied
///
private void free()
{
- if (!hasHitObjectApplied)
- return;
+ if (!hasEntryApplied) return;
StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable);
if (HitObject is IHasComboInformation combo)
@@ -319,14 +334,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
OnFree();
- HitObject = null;
ParentHitObject = null;
- Result = null;
lifetimeEntry = null;
clearExistingStateTransforms();
- hasHitObjectApplied = false;
+ hasEntryApplied = false;
}
protected sealed override void FreeAfterUse()
@@ -385,7 +398,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
private void onDefaultsApplied(HitObject hitObject)
{
- Apply(hitObject, lifetimeEntry);
+ Debug.Assert(lifetimeEntry != null);
+ Apply(lifetimeEntry);
+
DefaultsApplied?.Invoke(this);
}
@@ -783,6 +798,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// The that provides the scoring information.
protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement);
+ private void ensureEntryHasResult()
+ {
+ Debug.Assert(lifetimeEntry != null);
+ lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement())
+ ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
+ }
+
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
diff --git a/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs
new file mode 100644
index 0000000000..76f9eaf25a
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Objects
+{
+ ///
+ /// Created for a when only is given
+ /// to make sure a is always associated with a .
+ ///
+ internal class SyntheticHitObjectEntry : HitObjectLifetimeEntry
+ {
+ public SyntheticHitObjectEntry(HitObject hitObject)
+ : base(hitObject)
+ {
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs
index d55005363c..17d3cf01a4 100644
--- a/osu.Game/Rulesets/UI/Playfield.cs
+++ b/osu.Game/Rulesets/UI/Playfield.cs
@@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.UI
lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject);
dho.ParentHitObject = parent;
- dho.Apply(hitObject, entry);
+ dho.Apply(entry);
});
}