1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 17:43:05 +08:00

Merge pull request #12486 from ekrctb/unmanaged-hit-object-entry

Associate DrawableHitObject with HitObjectLifetimeEntry rather than HitObject
This commit is contained in:
Dan Balasescu 2021-04-21 17:08:21 +09:00 committed by GitHub
commit cf55383fff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 90 additions and 49 deletions

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
Position = new Vector2(128, 128), Position = new Vector2(128, 128),
ComboIndex = 1, ComboIndex = 1,
}), null)); })));
} }
private HitCircle prepareObject(HitCircle circle) private HitCircle prepareObject(HitCircle circle)

View File

@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(300, 0), new Vector2(300, 0),
}), }),
RepeatCount = 1 RepeatCount = 1
}), null)); })));
} }
[Test] [Test]

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Position = new Vector2(256, 192), Position = new Vector2(256, 192),
ComboIndex = 1, ComboIndex = 1,
Duration = 1000, Duration = 1000,
}), null)); })));
AddAssert("rotation is reset", () => dho.Result.RateAdjustedRotation == 0); AddAssert("rotation is reset", () => dho.Result.RateAdjustedRotation == 0);
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{ {
StartTime = 400, StartTime = 400,
Major = true Major = true
}), null)); })));
AddHitObject(barLine); AddHitObject(barLine);
RemoveHitObject(barLine); RemoveHitObject(barLine);
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{ {
StartTime = 200, StartTime = 200,
Major = false Major = false
}), null)); })));
AddHitObject(barLine); AddHitObject(barLine);
} }
} }

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Duration = 500, Duration = 500,
IsStrong = false, IsStrong = false,
TickRate = 2 TickRate = 2
}), null)); })));
AddHitObject(drumRoll); AddHitObject(drumRoll);
RemoveHitObject(drumRoll); RemoveHitObject(drumRoll);
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Duration = 400, Duration = 400,
IsStrong = true, IsStrong = true,
TickRate = 16 TickRate = 16
}), null)); })));
AddHitObject(drumRoll); AddHitObject(drumRoll);
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Type = HitType.Rim, Type = HitType.Rim,
IsStrong = false, IsStrong = false,
StartTime = 300 StartTime = 300
}), null)); })));
AddHitObject(hit); AddHitObject(hit);
RemoveHitObject(hit); RemoveHitObject(hit);
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Type = HitType.Centre, Type = HitType.Centre,
IsStrong = true, IsStrong = true,
StartTime = 500 StartTime = 500
}), null)); })));
AddHitObject(hit); AddHitObject(hit);
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <summary> /// <summary>
/// The <see cref="HitObject"/> currently represented by this <see cref="DrawableHitObject"/>. /// The <see cref="HitObject"/> currently represented by this <see cref="DrawableHitObject"/>.
/// </summary> /// </summary>
public HitObject HitObject { get; private set; } public HitObject HitObject => lifetimeEntry?.HitObject;
/// <summary> /// <summary>
/// The parenting <see cref="DrawableHitObject"/>, if any. /// The parenting <see cref="DrawableHitObject"/>, if any.
@ -108,7 +109,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <summary> /// <summary>
/// The scoring result of this <see cref="DrawableHitObject"/>. /// The scoring result of this <see cref="DrawableHitObject"/>.
/// </summary> /// </summary>
public JudgementResult Result { get; private set; } public JudgementResult Result => lifetimeEntry?.Result;
/// <summary> /// <summary>
/// The relative X position of this hit object for sample playback balance adjustment. /// 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<ArmedState> State => state; public IBindable<ArmedState> State => state;
/// <summary> /// <summary>
/// Whether <see cref="HitObject"/> is currently applied. /// Whether a <see cref="HitObjectLifetimeEntry"/> is currently applied.
/// </summary> /// </summary>
private bool hasHitObjectApplied; private bool hasEntryApplied;
/// <summary> /// <summary>
/// The <see cref="HitObjectLifetimeEntry"/> controlling the lifetime of the currently-attached <see cref="HitObject"/>. /// The <see cref="HitObjectLifetimeEntry"/> controlling the lifetime of the currently-attached <see cref="HitObject"/>.
/// </summary> /// </summary>
/// <remarks>Even if it is not null, it may not be fully applied until loaded (<see cref="hasEntryApplied"/> is false).</remarks>
[CanBeNull] [CanBeNull]
private HitObjectLifetimeEntry lifetimeEntry; private HitObjectLifetimeEntry lifetimeEntry;
@ -164,11 +166,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </summary> /// </summary>
/// <param name="initialHitObject"> /// <param name="initialHitObject">
/// The <see cref="HitObject"/> to be initially applied to this <see cref="DrawableHitObject"/>. /// The <see cref="HitObject"/> to be initially applied to this <see cref="DrawableHitObject"/>.
/// If <c>null</c>, a hitobject is expected to be later applied via <see cref="Apply"/> (or automatically via pooling). /// If <c>null</c>, a hitobject is expected to be later applied via <see cref="Apply(osu.Game.Rulesets.Objects.HitObjectLifetimeEntry)"/> (or automatically via pooling).
/// </param> /// </param>
protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null)
{ {
HitObject = initialHitObject; if (initialHitObject != null)
{
lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject);
ensureEntryHasResult();
}
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -184,8 +190,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
{ {
base.LoadAsyncComplete(); base.LoadAsyncComplete();
if (HitObject != null) if (lifetimeEntry != null && !hasEntryApplied)
Apply(HitObject, lifetimeEntry); Apply(lifetimeEntry);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -198,37 +204,47 @@ namespace osu.Game.Rulesets.Objects.Drawables
} }
/// <summary> /// <summary>
/// Applies a new <see cref="HitObject"/> to be represented by this <see cref="DrawableHitObject"/>. /// Applies a hit object to be represented by this <see cref="DrawableHitObject"/>.
/// </summary> /// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to apply.</param> [Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")]
/// <param name="lifetimeEntry">The <see cref="HitObjectLifetimeEntry"/> controlling the lifetime of <paramref name="hitObject"/>.</param>
public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry) public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
{
if (lifetimeEntry != null)
Apply(lifetimeEntry);
else
Apply(hitObject);
}
/// <summary>
/// Applies a new <see cref="HitObject"/> to be represented by this <see cref="DrawableHitObject"/>.
/// A new <see cref="HitObjectLifetimeEntry"/> is automatically created and applied to this <see cref="DrawableHitObject"/>.
/// </summary>
public void Apply([NotNull] HitObject hitObject)
{
if (hitObject == null)
throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}.");
Apply(new SyntheticHitObjectEntry(hitObject));
}
/// <summary>
/// Applies a new <see cref="HitObjectLifetimeEntry"/> to be represented by this <see cref="DrawableHitObject"/>.
/// </summary>
public void Apply([NotNull] HitObjectLifetimeEntry newEntry)
{ {
free(); 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) LifetimeStart = lifetimeEntry.LifetimeStart;
{ LifetimeEnd = lifetimeEntry.LifetimeEnd;
// Transfer lifetime from the entry.
LifetimeStart = lifetimeEntry.LifetimeStart;
LifetimeEnd = lifetimeEntry.LifetimeEnd;
// Copy any existing result from the entry (required for rewind / judgement revert). ensureEntryHasResult();
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;
foreach (var h in HitObject.NestedHitObjects) foreach (var h in HitObject.NestedHitObjects)
{ {
@ -278,16 +294,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
updateState(ArmedState.Idle, true); updateState(ArmedState.Idle, true);
} }
hasHitObjectApplied = true; hasEntryApplied = true;
} }
/// <summary> /// <summary>
/// Removes the currently applied <see cref="HitObject"/> /// Removes the currently applied <see cref="lifetimeEntry"/>
/// </summary> /// </summary>
private void free() private void free()
{ {
if (!hasHitObjectApplied) if (!hasEntryApplied) return;
return;
StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable);
if (HitObject is IHasComboInformation combo) if (HitObject is IHasComboInformation combo)
@ -319,14 +334,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
OnFree(); OnFree();
HitObject = null;
ParentHitObject = null; ParentHitObject = null;
Result = null;
lifetimeEntry = null; lifetimeEntry = null;
clearExistingStateTransforms(); clearExistingStateTransforms();
hasHitObjectApplied = false; hasEntryApplied = false;
} }
protected sealed override void FreeAfterUse() protected sealed override void FreeAfterUse()
@ -385,7 +398,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
private void onDefaultsApplied(HitObject hitObject) private void onDefaultsApplied(HitObject hitObject)
{ {
Apply(hitObject, lifetimeEntry); Debug.Assert(lifetimeEntry != null);
Apply(lifetimeEntry);
DefaultsApplied?.Invoke(this); DefaultsApplied?.Invoke(this);
} }
@ -783,6 +798,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <param name="judgement">The <see cref="Judgement"/> that provides the scoring information.</param> /// <param name="judgement">The <see cref="Judgement"/> that provides the scoring information.</param>
protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement); 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) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);

View File

@ -0,0 +1,19 @@
// 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 osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Objects
{
/// <summary>
/// Created for a <see cref="DrawableHitObject"/> when only <see cref="HitObject"/> is given
/// to make sure a <see cref="DrawableHitObject"/> is always associated with a <see cref="HitObjectLifetimeEntry"/>.
/// </summary>
internal class SyntheticHitObjectEntry : HitObjectLifetimeEntry
{
public SyntheticHitObjectEntry(HitObject hitObject)
: base(hitObject)
{
}
}
}

View File

@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.UI
lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject); lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject);
dho.ParentHitObject = parent; dho.ParentHitObject = parent;
dho.Apply(hitObject, entry); dho.Apply(entry);
}); });
} }