mirror of
https://github.com/osukey/osukey.git
synced 2025-07-03 01:09:57 +09:00
Initial Apply()/FreeAfterUse() DHO implementation
This commit is contained in:
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
@ -27,7 +28,10 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
{
|
||||
public event Action<DrawableHitObject> DefaultsApplied;
|
||||
|
||||
public readonly HitObject HitObject;
|
||||
/// <summary>
|
||||
/// The <see cref="HitObject"/> currently represented by this <see cref="DrawableHitObject"/>.
|
||||
/// </summary>
|
||||
public HitObject HitObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The colour used for various elements of this DrawableHitObject.
|
||||
@ -96,10 +100,10 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// </remarks>
|
||||
protected virtual float SamplePlaybackPosition => 0.5f;
|
||||
|
||||
private BindableList<HitSampleInfo> samplesBindable;
|
||||
private Bindable<double> startTimeBindable;
|
||||
private Bindable<bool> userPositionalHitSounds;
|
||||
private Bindable<int> comboIndexBindable;
|
||||
public readonly Bindable<double> StartTimeBindable = new Bindable<double>();
|
||||
private readonly BindableList<HitSampleInfo> samplesBindable = new BindableList<HitSampleInfo>();
|
||||
private readonly Bindable<bool> userPositionalHitSounds = new Bindable<bool>();
|
||||
private readonly Bindable<int> comboIndexBindable = new Bindable<int>();
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
public override bool RemoveCompletedTransforms => false;
|
||||
@ -111,52 +115,120 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
public IBindable<ArmedState> State => state;
|
||||
|
||||
protected DrawableHitObject([NotNull] HitObject hitObject)
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DrawableHitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="initialHitObject">
|
||||
/// 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).
|
||||
/// </param>
|
||||
protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null)
|
||||
{
|
||||
HitObject = hitObject ?? throw new ArgumentNullException(nameof(hitObject));
|
||||
HitObject = initialHitObject;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
userPositionalHitSounds = config.GetBindable<bool>(OsuSetting.PositionalHitSounds);
|
||||
var judgement = HitObject.CreateJudgement();
|
||||
|
||||
Result = CreateResult(judgement);
|
||||
if (Result == null)
|
||||
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
||||
config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds);
|
||||
}
|
||||
|
||||
protected override void LoadAsyncComplete()
|
||||
{
|
||||
base.LoadAsyncComplete();
|
||||
|
||||
LoadSamples();
|
||||
|
||||
HitObject.DefaultsApplied += onDefaultsApplied;
|
||||
|
||||
startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
|
||||
startTimeBindable.BindValueChanged(_ => updateState(State.Value, true));
|
||||
|
||||
if (HitObject is IHasComboInformation combo)
|
||||
{
|
||||
comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy();
|
||||
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
||||
}
|
||||
|
||||
samplesBindable = HitObject.SamplesBindable.GetBoundCopy();
|
||||
samplesBindable.CollectionChanged += (_, __) => LoadSamples();
|
||||
|
||||
apply(HitObject);
|
||||
if (HitObject != null)
|
||||
Apply(HitObject);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
StartTimeBindable.BindValueChanged(_ => updateState(State.Value, true));
|
||||
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
||||
|
||||
updateState(ArmedState.Idle, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the <see cref="HitObject"/> currently applied to this <see cref="DrawableHitObject"/>,
|
||||
/// </summary>
|
||||
protected override void FreeAfterUse()
|
||||
{
|
||||
StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable);
|
||||
if (HitObject is IHasComboInformation combo)
|
||||
comboIndexBindable.UnbindFrom(combo.ComboIndexBindable);
|
||||
|
||||
samplesBindable.UnbindFrom(HitObject.SamplesBindable);
|
||||
|
||||
// When a new hitobject is applied, the samples will be cleared before re-populating.
|
||||
// In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply().
|
||||
samplesBindable.CollectionChanged -= onSamplesChanged;
|
||||
|
||||
if (nestedHitObjects.IsValueCreated)
|
||||
{
|
||||
foreach (var obj in nestedHitObjects.Value)
|
||||
{
|
||||
obj.OnNewResult -= onNewResult;
|
||||
obj.OnRevertResult -= onRevertResult;
|
||||
obj.ApplyCustomUpdateState -= onApplyCustomUpdateState;
|
||||
}
|
||||
|
||||
nestedHitObjects.Value.Clear();
|
||||
ClearNestedHitObjects();
|
||||
}
|
||||
|
||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||
HitObject = null;
|
||||
|
||||
base.FreeAfterUse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a new <see cref="HitObject"/> to be represented by this <see cref="DrawableHitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject"></param>
|
||||
public virtual void Apply(HitObject hitObject)
|
||||
{
|
||||
HitObject = hitObject;
|
||||
|
||||
// Copy any existing result from the hitobject (required for rewind / judgement revert).
|
||||
Result = HitObject.Result;
|
||||
|
||||
// Ensure this DHO has a result.
|
||||
Result ??= CreateResult(HitObject.CreateJudgement())
|
||||
?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
||||
|
||||
// Ensure the hitobject has a result.
|
||||
HitObject.Result = Result;
|
||||
|
||||
foreach (var h in HitObject.NestedHitObjects)
|
||||
{
|
||||
var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
||||
|
||||
drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r);
|
||||
drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r);
|
||||
drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j);
|
||||
|
||||
nestedHitObjects.Value.Add(drawableNested);
|
||||
AddNestedHitObject(drawableNested);
|
||||
}
|
||||
|
||||
StartTimeBindable.BindTo(HitObject.StartTimeBindable);
|
||||
if (HitObject is IHasComboInformation combo)
|
||||
comboIndexBindable.BindTo(combo.ComboIndexBindable);
|
||||
|
||||
samplesBindable.BindTo(HitObject.SamplesBindable);
|
||||
samplesBindable.BindCollectionChanged(onSamplesChanged, true);
|
||||
|
||||
HitObject.DefaultsApplied += onDefaultsApplied;
|
||||
|
||||
// If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates.
|
||||
if (IsLoaded)
|
||||
Schedule(() => updateState(ArmedState.Idle, true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked by the base <see cref="DrawableHitObject"/> to populate samples, once on initial load and potentially again on any change to the samples collection.
|
||||
/// </summary>
|
||||
@ -183,34 +255,21 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
AddInternal(Samples);
|
||||
}
|
||||
|
||||
private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples();
|
||||
|
||||
private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result);
|
||||
|
||||
private void onRevertResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnRevertResult?.Invoke(drawableHitObject, result);
|
||||
|
||||
private void onApplyCustomUpdateState(DrawableHitObject drawableHitObject, ArmedState state) => ApplyCustomUpdateState?.Invoke(drawableHitObject, state);
|
||||
|
||||
private void onDefaultsApplied(HitObject hitObject)
|
||||
{
|
||||
apply(hitObject);
|
||||
updateState(state.Value, true);
|
||||
FreeAfterUse();
|
||||
Apply(hitObject);
|
||||
DefaultsApplied?.Invoke(this);
|
||||
}
|
||||
|
||||
private void apply(HitObject hitObject)
|
||||
{
|
||||
if (nestedHitObjects.IsValueCreated)
|
||||
{
|
||||
nestedHitObjects.Value.Clear();
|
||||
ClearNestedHitObjects();
|
||||
}
|
||||
|
||||
foreach (var h in hitObject.NestedHitObjects)
|
||||
{
|
||||
var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
||||
|
||||
drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r);
|
||||
drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r);
|
||||
drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j);
|
||||
|
||||
nestedHitObjects.Value.Add(drawableNested);
|
||||
AddNestedHitObject(drawableNested);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked by the base <see cref="DrawableHitObject"/> to add nested <see cref="DrawableHitObject"/>s to the hierarchy.
|
||||
/// </summary>
|
||||
@ -600,7 +659,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||
|
||||
if (HitObject != null)
|
||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user