diff --git a/osu.Android.props b/osu.Android.props
index e0b07549f4..5aee9e15cc 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs
new file mode 100644
index 0000000000..2e3f192f1b
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs
@@ -0,0 +1,94 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneDrawableHitObject : OsuTestScene
+ {
+ [Test]
+ public void TestEntryLifetime()
+ {
+ TestDrawableHitObject dho = null;
+ var initialHitObject = new HitObject
+ {
+ StartTime = 1000
+ };
+ var entry = new TestLifetimeEntry(new HitObject
+ {
+ StartTime = 2000
+ });
+
+ AddStep("Create DHO", () => Child = dho = new TestDrawableHitObject(initialHitObject));
+
+ AddAssert("Correct initial lifetime", () => dho.LifetimeStart == initialHitObject.StartTime - TestDrawableHitObject.INITIAL_LIFETIME_OFFSET);
+
+ AddStep("Apply entry", () => dho.Apply(entry));
+
+ AddAssert("Correct initial lifetime", () => dho.LifetimeStart == entry.HitObject.StartTime - TestLifetimeEntry.INITIAL_LIFETIME_OFFSET);
+
+ AddStep("Set lifetime", () => dho.LifetimeEnd = 3000);
+ AddAssert("Entry lifetime is updated", () => entry.LifetimeEnd == 3000);
+ }
+
+ [Test]
+ public void TestKeepAlive()
+ {
+ TestDrawableHitObject dho = null;
+ TestLifetimeEntry entry = null;
+ AddStep("Create DHO", () =>
+ {
+ dho = new TestDrawableHitObject(null);
+ dho.Apply(entry = new TestLifetimeEntry(new HitObject())
+ {
+ LifetimeStart = 0,
+ LifetimeEnd = 1000,
+ });
+ Child = dho;
+ });
+
+ AddStep("KeepAlive = true", () => entry.KeepAlive = true);
+ AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue);
+
+ AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500);
+ AddStep("KeepAlive = false", () => entry.KeepAlive = false);
+ AddAssert("Lifetime is correct", () => entry.LifetimeStart == 500 && entry.LifetimeEnd == 1000);
+
+ AddStep("Set LifetimeStart while KeepAlive", () =>
+ {
+ entry.KeepAlive = true;
+ dho.LifetimeStart = double.MinValue;
+ entry.KeepAlive = false;
+ });
+ AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000);
+ }
+
+ private class TestDrawableHitObject : DrawableHitObject
+ {
+ public const double INITIAL_LIFETIME_OFFSET = 100;
+ protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
+
+ public TestDrawableHitObject(HitObject hitObject)
+ : base(hitObject)
+ {
+ }
+ }
+
+ private class TestLifetimeEntry : HitObjectLifetimeEntry
+ {
+ public const double INITIAL_LIFETIME_OFFSET = 200;
+ protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
+
+ public TestLifetimeEntry(HitObject hitObject)
+ : base(hitObject)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index e715e4aac6..86c733c392 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -11,6 +11,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Performance;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Threading;
using osu.Game.Audio;
@@ -436,7 +437,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Apply (generally fade-in) transforms leading into the start time.
- /// The local drawable hierarchy is recursively delayed to for convenience.
+ /// The local drawable hierarchy is recursively delayed to for convenience.
///
/// By default this will fade in the object from zero with no duration.
///
@@ -618,7 +619,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required.
/// It is indirectly used to decide the automatic transform offset provided to .
- /// A more accurate should be set for further optimisation (in , for example).
+ /// A more accurate should be set for further optimisation (in , for example).
///
/// Only has an effect if this is not being pooled.
/// For pooled s, use instead.
diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs
index 1954d7e6d2..0d1eb68f07 100644
--- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs
+++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs
@@ -38,40 +38,23 @@ namespace osu.Game.Rulesets.Objects
startTimeBindable.BindValueChanged(onStartTimeChanged, true);
}
- // The lifetime start, as set by the hitobject.
+ // The lifetime, as set by the hitobject.
private double realLifetimeStart = double.MinValue;
-
- ///
- /// The time at which the should become alive.
- ///
- public new double LifetimeStart
- {
- get => realLifetimeStart;
- set => setLifetime(realLifetimeStart = value, LifetimeEnd);
- }
-
- // The lifetime end, as set by the hitobject.
private double realLifetimeEnd = double.MaxValue;
- ///
- /// The time at which the should become dead.
- ///
- public new double LifetimeEnd
+ // This method is called even if `start == LifetimeStart` when `KeepAlive` is true (necessary to update `realLifetimeStart`).
+ protected override void SetLifetimeStart(double start)
{
- get => realLifetimeEnd;
- set => setLifetime(LifetimeStart, realLifetimeEnd = value);
+ realLifetimeStart = start;
+ if (!keepAlive)
+ base.SetLifetimeStart(start);
}
- private void setLifetime(double start, double end)
+ protected override void SetLifetimeEnd(double end)
{
- if (keepAlive)
- {
- start = double.MinValue;
- end = double.MaxValue;
- }
-
- base.LifetimeStart = start;
- base.LifetimeEnd = end;
+ realLifetimeEnd = end;
+ if (!keepAlive)
+ base.SetLifetimeEnd(end);
}
private bool keepAlive;
@@ -87,7 +70,10 @@ namespace osu.Game.Rulesets.Objects
return;
keepAlive = value;
- setLifetime(realLifetimeStart, realLifetimeEnd);
+ if (keepAlive)
+ SetLifetime(double.MinValue, double.MaxValue);
+ else
+ SetLifetime(realLifetimeStart, realLifetimeEnd);
}
}
@@ -98,12 +84,12 @@ namespace osu.Game.Rulesets.Objects
///
/// This is only used as an optimisation to delay the initial update of the and may be tuned more aggressively if required.
/// It is indirectly used to decide the automatic transform offset provided to .
- /// A more accurate should be set for further optimisation (in , for example).
+ /// A more accurate should be set for further optimisation (in , for example).
///
protected virtual double InitialLifetimeOffset => 10000;
///
- /// Resets according to the change in start time of the .
+ /// Resets according to the change in start time of the .
///
private void onStartTimeChanged(ValueChangedEvent startTime) => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 9a43d5f031..986bd8e7ba 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -29,7 +29,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 2c41b3ef26..c32109d6db 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+