mirror of
https://github.com/osukey/osukey.git
synced 2025-08-03 14:46:38 +09:00
Merge branch 'master' into refactor-combo-colour-retrieval
This commit is contained in:
@ -11,7 +11,6 @@ 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;
|
||||
@ -157,10 +156,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// If <c>null</c>, a hitobject is expected to be later applied via <see cref="PoolableDrawableWithLifetime{TEntry}.Apply"/> (or automatically via pooling).
|
||||
/// </param>
|
||||
protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null)
|
||||
: base(initialHitObject != null ? new SyntheticHitObjectEntry(initialHitObject) : null)
|
||||
{
|
||||
if (Entry != null)
|
||||
ensureEntryHasResult();
|
||||
if (initialHitObject == null) return;
|
||||
|
||||
Entry = new SyntheticHitObjectEntry(initialHitObject);
|
||||
ensureEntryHasResult();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -172,7 +172,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
base.AddInternal(Samples = new PausableSkinnableSound());
|
||||
|
||||
CurrentSkin = skinSource;
|
||||
CurrentSkin.SourceChanged += onSkinSourceChanged;
|
||||
CurrentSkin.SourceChanged += skinSourceChanged;
|
||||
}
|
||||
|
||||
protected override void LoadAsyncComplete()
|
||||
{
|
||||
base.LoadAsyncComplete();
|
||||
skinChanged();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -187,7 +193,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// <summary>
|
||||
/// Applies a hit object to be represented by this <see cref="DrawableHitObject"/>.
|
||||
/// </summary>
|
||||
[Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")]
|
||||
[Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")] // Can be removed 20211021.
|
||||
public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
|
||||
{
|
||||
if (lifetimeEntry != null)
|
||||
@ -305,6 +311,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
/// <summary>
|
||||
/// Invoked for this <see cref="DrawableHitObject"/> to take on any values from a newly-applied <see cref="HitObject"/>.
|
||||
/// This is also fired after any changes which occurred via an <see cref="osu.Game.Rulesets.Objects.HitObject.ApplyDefaults"/> call.
|
||||
/// </summary>
|
||||
protected virtual void OnApply()
|
||||
{
|
||||
@ -312,6 +319,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
/// <summary>
|
||||
/// Invoked for this <see cref="DrawableHitObject"/> to revert any values previously taken on from the currently-applied <see cref="HitObject"/>.
|
||||
/// This is also fired after any changes which occurred via an <see cref="osu.Game.Rulesets.Objects.HitObject.ApplyDefaults"/> call.
|
||||
/// </summary>
|
||||
protected virtual void OnFree()
|
||||
{
|
||||
@ -396,18 +404,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
clearExistingStateTransforms();
|
||||
|
||||
using (BeginAbsoluteSequence(transformTime, true))
|
||||
using (BeginAbsoluteSequence(transformTime))
|
||||
UpdateInitialTransforms();
|
||||
|
||||
using (BeginAbsoluteSequence(StateUpdateTime, true))
|
||||
using (BeginAbsoluteSequence(StateUpdateTime))
|
||||
UpdateStartTimeStateTransforms();
|
||||
|
||||
#pragma warning disable 618
|
||||
using (BeginAbsoluteSequence(StateUpdateTime + (Result?.TimeOffset ?? 0), true))
|
||||
UpdateStateTransforms(newState);
|
||||
#pragma warning restore 618
|
||||
|
||||
using (BeginAbsoluteSequence(HitStateUpdateTime, true))
|
||||
using (BeginAbsoluteSequence(HitStateUpdateTime))
|
||||
UpdateHitStateTransforms(newState);
|
||||
|
||||
state.Value = newState;
|
||||
@ -437,12 +440,10 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
/// <summary>
|
||||
/// Apply (generally fade-in) transforms leading into the <see cref="HitObject"/> start time.
|
||||
/// The local drawable hierarchy is recursively delayed to <see cref="LifetimeEntry.LifetimeStart"/> for convenience.
|
||||
///
|
||||
/// By default this will fade in the object from zero with no duration.
|
||||
/// By default, this will fade in the object from zero with no duration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is called once before every <see cref="UpdateStateTransforms"/>. This is to ensure a good state in the case
|
||||
/// This is called once before every <see cref="UpdateHitStateTransforms"/>. This is to ensure a good state in the case
|
||||
/// the <see cref="JudgementResult.TimeOffset"/> was negative and potentially altered the pre-hit transforms.
|
||||
/// </remarks>
|
||||
protected virtual void UpdateInitialTransforms()
|
||||
@ -450,16 +451,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
this.FadeInFromZero();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply transforms based on the current <see cref="ArmedState"/>. Previous states are automatically cleared.
|
||||
/// In the case of a non-idle <see cref="ArmedState"/>, and if <see cref="Drawable.LifetimeEnd"/> was not set during this call, <see cref="Drawable.Expire"/> will be invoked.
|
||||
/// </summary>
|
||||
/// <param name="state">The new armed state.</param>
|
||||
[Obsolete("Use UpdateStartTimeStateTransforms and UpdateHitStateTransforms instead")] // Can be removed 20210504
|
||||
protected virtual void UpdateStateTransforms(ArmedState state)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply passive transforms at the <see cref="HitObject"/>'s StartTime.
|
||||
/// This is called each time <see cref="State"/> changes.
|
||||
@ -495,7 +486,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
protected ISkinSource CurrentSkin { get; private set; }
|
||||
|
||||
private void onSkinSourceChanged() => Scheduler.AddOnce(() =>
|
||||
private void skinSourceChanged() => Scheduler.AddOnce(skinChanged);
|
||||
|
||||
private void skinChanged()
|
||||
{
|
||||
UpdateComboColour();
|
||||
|
||||
@ -503,7 +496,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
if (IsLoaded)
|
||||
updateState(State.Value, true);
|
||||
});
|
||||
}
|
||||
|
||||
protected void UpdateComboColour()
|
||||
{
|
||||
@ -512,23 +505,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
AccentColour.Value = combo.GetComboColour(CurrentSkin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to retrieve the combo colour. Automatically assigned to <see cref="AccentColour"/>.
|
||||
/// Defaults to using <see cref="IHasComboInformation.ComboIndex"/> to decide on a colour.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will only be called if the <see cref="HitObject"/> implements <see cref="IHasComboInformation"/>.
|
||||
/// </remarks>
|
||||
/// <param name="comboColours">A list of combo colours provided by the beatmap or skin. Can be null if not available.</param>
|
||||
[Obsolete("Unused. Implement IHasComboInformation and IHasComboInformation.GetComboColour() on the HitObject model instead.")] // Can be removed 20210527
|
||||
protected virtual Color4 GetComboColour(IReadOnlyList<Color4> comboColours)
|
||||
{
|
||||
if (!(HitObject is IHasComboInformation combo))
|
||||
throw new InvalidOperationException($"{nameof(HitObject)} must implement {nameof(IHasComboInformation)}");
|
||||
|
||||
return comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a change is made to the skin.
|
||||
/// </summary>
|
||||
@ -612,23 +588,17 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action);
|
||||
|
||||
/// <summary>
|
||||
/// A safe offset prior to the start time of <see cref="HitObject"/> at which this <see cref="DrawableHitObject"/> may begin displaying contents.
|
||||
/// An offset prior to the start time of <see cref="HitObject"/> at which this <see cref="DrawableHitObject"/> may begin displaying contents.
|
||||
/// By default, <see cref="DrawableHitObject"/>s are assumed to display their contents within 10 seconds prior to the start time of <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used as an optimisation to delay the initial update of this <see cref="DrawableHitObject"/> and may be tuned more aggressively if required.
|
||||
/// It is indirectly used to decide the automatic transform offset provided to <see cref="UpdateInitialTransforms"/>.
|
||||
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set for further optimisation (in <see cref="LoadComplete"/>, for example).
|
||||
/// <para>
|
||||
/// Only has an effect if this <see cref="DrawableHitObject"/> is not being pooled.
|
||||
/// For pooled <see cref="DrawableHitObject"/>s, use <see cref="HitObjectLifetimeEntry.InitialLifetimeOffset"/> instead.
|
||||
/// </para>
|
||||
/// The initial transformation (<see cref="UpdateInitialTransforms"/>) starts at this offset before the start time of <see cref="HitObject"/>.
|
||||
/// </remarks>
|
||||
protected virtual double InitialLifetimeOffset => 10000;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which state transforms should be applied that line up to <see cref="HitObject"/>'s StartTime.
|
||||
/// This is used to offset calls to <see cref="UpdateStateTransforms"/>.
|
||||
/// This is used to offset calls to <see cref="UpdateStartTimeStateTransforms"/>.
|
||||
/// </summary>
|
||||
public double StateUpdateTime => HitObject.StartTime;
|
||||
|
||||
@ -746,7 +716,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
if (HitObject != null)
|
||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||
|
||||
CurrentSkin.SourceChanged -= onSkinSourceChanged;
|
||||
if (CurrentSkin != null)
|
||||
CurrentSkin.SourceChanged -= skinSourceChanged;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
// 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.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Drawables
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface that exposes properties required for scrolling hit objects to be properly displayed.
|
||||
/// </summary>
|
||||
internal interface IScrollingHitObject : IDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// Time offset before the hit object start time at which this <see cref="IScrollingHitObject"/> becomes visible and the time offset
|
||||
/// after the hit object's end time after which it expires.
|
||||
///
|
||||
/// <para>
|
||||
/// This provides only a default life time range, however classes inheriting from <see cref="IScrollingHitObject"/> should override
|
||||
/// their life times if more tight control is desired.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
BindableDouble LifetimeOffset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Axes which this <see cref="IScrollingHitObject"/> will scroll through.
|
||||
/// This is set by the container which this scrolls through.
|
||||
/// </summary>
|
||||
Axes ScrollingAxes { set; }
|
||||
}
|
||||
}
|
@ -35,7 +35,11 @@ namespace osu.Game.Rulesets.Objects
|
||||
HitObject = hitObject;
|
||||
|
||||
startTimeBindable.BindTo(HitObject.StartTimeBindable);
|
||||
startTimeBindable.BindValueChanged(onStartTimeChanged, true);
|
||||
startTimeBindable.BindValueChanged(_ => SetInitialLifetime(), true);
|
||||
|
||||
// Subscribe to this event before the DrawableHitObject so that the local callback is invoked before the entry is re-applied as a result of DefaultsApplied.
|
||||
// This way, the DrawableHitObject can use OnApply() to overwrite the LifetimeStart that was set inside setInitialLifetime().
|
||||
HitObject.DefaultsApplied += _ => SetInitialLifetime();
|
||||
}
|
||||
|
||||
// The lifetime, as set by the hitobject.
|
||||
@ -82,15 +86,14 @@ namespace osu.Game.Rulesets.Objects
|
||||
/// By default, <see cref="HitObject"/>s are assumed to display their contents within 10 seconds prior to their start time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only used as an optimisation to delay the initial update of the <see cref="HitObject"/> and may be tuned more aggressively if required.
|
||||
/// It is indirectly used to decide the automatic transform offset provided to <see cref="DrawableHitObject.UpdateInitialTransforms"/>.
|
||||
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set for further optimisation (in <see cref="DrawableHitObject.LoadComplete"/>, for example).
|
||||
/// This is only used as an optimisation to delay the initial application of the <see cref="HitObject"/> to a <see cref="DrawableHitObject"/>.
|
||||
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set on the hit object application, for further optimisation.
|
||||
/// </remarks>
|
||||
protected virtual double InitialLifetimeOffset => 10000;
|
||||
|
||||
/// <summary>
|
||||
/// Resets <see cref="LifetimeEntry.LifetimeStart"/> according to the change in start time of the <see cref="HitObject"/>.
|
||||
/// Set <see cref="LifetimeEntry.LifetimeStart"/> using <see cref="InitialLifetimeOffset"/>.
|
||||
/// </summary>
|
||||
private void onStartTimeChanged(ValueChangedEvent<double> startTime) => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
|
||||
internal void SetInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,20 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!catch Hit-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertHit : ConvertHitObject, IHasCombo, IHasXPosition
|
||||
internal sealed class ConvertHit : ConvertHitObject, IHasPosition, IHasCombo
|
||||
{
|
||||
public float X { get; set; }
|
||||
public float X => Position.X;
|
||||
|
||||
public float Y => Position.Y;
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
|
||||
return new ConvertHit
|
||||
{
|
||||
X = position.X,
|
||||
Position = position,
|
||||
NewCombo = newCombo,
|
||||
ComboOffset = comboOffset
|
||||
};
|
||||
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
|
||||
return new ConvertSlider
|
||||
{
|
||||
X = position.X,
|
||||
Position = position,
|
||||
NewCombo = FirstObject || newCombo,
|
||||
ComboOffset = comboOffset,
|
||||
Path = new SliderPath(controlPoints, length),
|
||||
|
@ -2,15 +2,20 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!catch Slider-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo
|
||||
{
|
||||
public float X { get; set; }
|
||||
public float X => Position.X;
|
||||
|
||||
public float Y => Position.Y;
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
|
||||
@ -15,27 +17,59 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
||||
/// <typeparam name="TEntry">The <see cref="LifetimeEntry"/> type storing state and controlling this drawable.</typeparam>
|
||||
public abstract class PoolableDrawableWithLifetime<TEntry> : PoolableDrawable where TEntry : LifetimeEntry
|
||||
{
|
||||
private TEntry? entry;
|
||||
|
||||
/// <summary>
|
||||
/// The entry holding essential state of this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
||||
/// </summary>
|
||||
protected TEntry? Entry { get; private set; }
|
||||
/// <remarks>
|
||||
/// If a non-null value is set before loading is started, the entry is applied when the loading is completed.
|
||||
/// It is not valid to set an entry while this <see cref="PoolableDrawableWithLifetime{TEntry}"/> is loading.
|
||||
/// </remarks>
|
||||
public TEntry? Entry
|
||||
{
|
||||
get => entry;
|
||||
set
|
||||
{
|
||||
if (LoadState == LoadState.NotLoaded)
|
||||
entry = value;
|
||||
else if (value != null)
|
||||
Apply(value);
|
||||
else if (HasEntryApplied)
|
||||
free();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether <see cref="Entry"/> is applied to this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
||||
/// When an initial entry is specified in the constructor, <see cref="Entry"/> is set but not applied until loading is completed.
|
||||
/// When an <see cref="Entry"/> is set during initialization, it is not applied until loading is completed.
|
||||
/// </summary>
|
||||
protected bool HasEntryApplied { get; private set; }
|
||||
|
||||
public override double LifetimeStart
|
||||
{
|
||||
get => base.LifetimeStart;
|
||||
set => setLifetime(value, LifetimeEnd);
|
||||
set
|
||||
{
|
||||
if (Entry == null && LifetimeStart != value)
|
||||
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
||||
|
||||
if (Entry != null)
|
||||
Entry.LifetimeStart = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override double LifetimeEnd
|
||||
{
|
||||
get => base.LifetimeEnd;
|
||||
set => setLifetime(LifetimeStart, value);
|
||||
set
|
||||
{
|
||||
if (Entry == null && LifetimeEnd != value)
|
||||
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
||||
|
||||
if (Entry != null)
|
||||
Entry.LifetimeEnd = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
@ -50,9 +84,9 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
||||
{
|
||||
base.LoadAsyncComplete();
|
||||
|
||||
// Apply the initial entry given in the constructor.
|
||||
// Apply the initial entry.
|
||||
if (Entry != null && !HasEntryApplied)
|
||||
Apply(Entry);
|
||||
apply(Entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -61,15 +95,10 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
||||
/// </summary>
|
||||
public void Apply(TEntry entry)
|
||||
{
|
||||
if (HasEntryApplied)
|
||||
free();
|
||||
if (LoadState == LoadState.Loading)
|
||||
throw new InvalidOperationException($"Cannot apply a new {nameof(TEntry)} while currently loading.");
|
||||
|
||||
setLifetime(entry.LifetimeStart, entry.LifetimeEnd);
|
||||
Entry = entry;
|
||||
|
||||
OnApply(entry);
|
||||
|
||||
HasEntryApplied = true;
|
||||
apply(entry);
|
||||
}
|
||||
|
||||
protected sealed override void FreeAfterUse()
|
||||
@ -95,16 +124,18 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
||||
{
|
||||
}
|
||||
|
||||
private void setLifetime(double start, double end)
|
||||
private void apply(TEntry entry)
|
||||
{
|
||||
base.LifetimeStart = start;
|
||||
base.LifetimeEnd = end;
|
||||
if (HasEntryApplied)
|
||||
free();
|
||||
|
||||
if (Entry != null)
|
||||
{
|
||||
Entry.LifetimeStart = start;
|
||||
Entry.LifetimeEnd = end;
|
||||
}
|
||||
this.entry = entry;
|
||||
entry.LifetimeChanged += setLifetimeFromEntry;
|
||||
setLifetimeFromEntry(entry);
|
||||
|
||||
OnApply(entry);
|
||||
|
||||
HasEntryApplied = true;
|
||||
}
|
||||
|
||||
private void free()
|
||||
@ -113,10 +144,19 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
||||
|
||||
OnFree(Entry);
|
||||
|
||||
Entry = null;
|
||||
setLifetime(double.MaxValue, double.MaxValue);
|
||||
Entry.LifetimeChanged -= setLifetimeFromEntry;
|
||||
entry = null;
|
||||
base.LifetimeStart = double.MinValue;
|
||||
base.LifetimeEnd = double.MaxValue;
|
||||
|
||||
HasEntryApplied = false;
|
||||
}
|
||||
|
||||
private void setLifetimeFromEntry(LifetimeEntry entry)
|
||||
{
|
||||
Debug.Assert(entry == Entry);
|
||||
base.LifetimeStart = entry.LifetimeStart;
|
||||
base.LifetimeEnd = entry.LifetimeEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,163 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Pooling
|
||||
{
|
||||
/// <summary>
|
||||
/// A container of <typeparamref name="TDrawable"/>s dynamically added/removed by model <typeparamref name="TEntry"/>s.
|
||||
/// When an entry became alive, a drawable corresponding to the entry is obtained (potentially pooled), and added to this container.
|
||||
/// The drawable is removed when the entry became dead.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntry">The type of entries managed by this container.</typeparam>
|
||||
/// <typeparam name="TDrawable">The type of drawables corresponding to the entries.</typeparam>
|
||||
public abstract class PooledDrawableWithLifetimeContainer<TEntry, TDrawable> : CompositeDrawable
|
||||
where TEntry : LifetimeEntry
|
||||
where TDrawable : Drawable
|
||||
{
|
||||
/// <summary>
|
||||
/// All entries added to this container, including dead entries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The enumeration order is undefined.
|
||||
/// </remarks>
|
||||
public IEnumerable<TEntry> Entries => allEntries;
|
||||
|
||||
/// <summary>
|
||||
/// All alive entries and drawables corresponding to the entries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The enumeration order is undefined.
|
||||
/// </remarks>
|
||||
public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value));
|
||||
|
||||
/// <summary>
|
||||
/// Whether to remove an entry when clock goes backward and crossed its <see cref="LifetimeEntry.LifetimeStart"/>.
|
||||
/// Used when entries are dynamically added at its <see cref="LifetimeEntry.LifetimeStart"/> to prevent duplicated entries.
|
||||
/// </summary>
|
||||
protected virtual bool RemoveRewoundEntry => false;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time prior to the current time within which entries should be considered alive.
|
||||
/// </summary>
|
||||
internal double PastLifetimeExtension { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time after the current time within which entries should be considered alive.
|
||||
/// </summary>
|
||||
internal double FutureLifetimeExtension { get; set; }
|
||||
|
||||
private readonly Dictionary<TEntry, TDrawable> aliveDrawableMap = new Dictionary<TEntry, TDrawable>();
|
||||
private readonly HashSet<TEntry> allEntries = new HashSet<TEntry>();
|
||||
|
||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||
|
||||
protected PooledDrawableWithLifetimeContainer()
|
||||
{
|
||||
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
||||
lifetimeManager.EntryBecameDead += entryBecameDead;
|
||||
lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a <typeparamref name="TEntry"/> to be managed by this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The aliveness of the entry is not updated until <see cref="CheckChildrenLife"/>.
|
||||
/// </remarks>
|
||||
public virtual void Add(TEntry entry)
|
||||
{
|
||||
allEntries.Add(entry);
|
||||
lifetimeManager.AddEntry(entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a <typeparamref name="TEntry"/> from this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the entry was alive, the corresponding drawable is removed.
|
||||
/// </remarks>
|
||||
/// <returns>Whether the entry was in this container.</returns>
|
||||
public virtual bool Remove(TEntry entry)
|
||||
{
|
||||
if (!lifetimeManager.RemoveEntry(entry)) return false;
|
||||
|
||||
allEntries.Remove(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize new <typeparamref name="TDrawable"/> corresponding <paramref name="entry"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <typeparamref name="TDrawable"/> corresponding to the entry.</returns>
|
||||
protected abstract TDrawable GetDrawable(TEntry entry);
|
||||
|
||||
private void entryBecameAlive(LifetimeEntry lifetimeEntry)
|
||||
{
|
||||
var entry = (TEntry)lifetimeEntry;
|
||||
Debug.Assert(!aliveDrawableMap.ContainsKey(entry));
|
||||
|
||||
TDrawable drawable = GetDrawable(entry);
|
||||
aliveDrawableMap[entry] = drawable;
|
||||
AddDrawable(entry, drawable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a <typeparamref name="TDrawable"/> corresponding to <paramref name="entry"/> to this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Invoked when the entry became alive and a <typeparamref name="TDrawable"/> is obtained by <see cref="GetDrawable"/>.
|
||||
/// </remarks>
|
||||
protected virtual void AddDrawable(TEntry entry, TDrawable drawable) => AddInternal(drawable);
|
||||
|
||||
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
||||
{
|
||||
var entry = (TEntry)lifetimeEntry;
|
||||
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
||||
|
||||
TDrawable drawable = aliveDrawableMap[entry];
|
||||
aliveDrawableMap.Remove(entry);
|
||||
RemoveDrawable(entry, drawable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a <typeparamref name="TDrawable"/> corresponding to <paramref name="entry"/> from this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Invoked when the entry became dead.
|
||||
/// </remarks>
|
||||
protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable);
|
||||
|
||||
private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
|
||||
{
|
||||
if (RemoveRewoundEntry && kind == LifetimeBoundaryKind.Start && direction == LifetimeBoundaryCrossingDirection.Backward)
|
||||
Remove((TEntry)lifetimeEntry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all <typeparamref name="TEntry"/>s.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var entry in Entries.ToArray())
|
||||
Remove(entry);
|
||||
|
||||
Debug.Assert(aliveDrawableMap.Count == 0);
|
||||
}
|
||||
|
||||
protected override bool CheckChildrenLife()
|
||||
{
|
||||
bool aliveChanged = base.CheckChildrenLife();
|
||||
aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
|
||||
return aliveChanged;
|
||||
}
|
||||
}
|
||||
}
|
19
osu.Game/Rulesets/Objects/Types/IHasDisplayColour.cs
Normal file
19
osu.Game/Rulesets/Objects/Types/IHasDisplayColour.cs
Normal 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.Framework.Bindables;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObject which has a preferred display colour. Will be used for editor timeline display.
|
||||
/// </summary>
|
||||
public interface IHasDisplayColour
|
||||
{
|
||||
/// <summary>
|
||||
/// The current display colour of this hit object.
|
||||
/// </summary>
|
||||
Bindable<Color4> DisplayColour { get; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user