diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 3acec4498d..1447f131c6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -2,23 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; -using osuTK; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; -using osuTK.Graphics; using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -126,18 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); - - var slidingSamples = new List(); - - var normalSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); - if (normalSample != null) - slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(normalSample).With("sliderslide")); - - var whistleSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); - if (whistleSample != null) - slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(whistleSample).With("sliderwhistle")); - - slidingSample.Samples = slidingSamples.ToArray(); + slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); } public override void StopAllSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index ec1387eb54..64964ed396 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -74,6 +74,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } + protected override void LoadSamples() + { + // Tail models don't actually get samples, as the playback is handled by DrawableSlider. + // This override is only here for visibility in explaining this weird flow. + } + + public override void PlaySamples() + { + // Tail models don't actually get samples, as the playback is handled by DrawableSlider. + // This override is only here for visibility in explaining this weird flow. + } + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index c6db02ee02..a904658a4c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -121,15 +121,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - var firstSample = HitObject.Samples.FirstOrDefault(); - - if (firstSample != null) - { - var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin"); - - spinningSample.Samples = new ISampleInfo[] { clone }; - spinningSample.Frequency.Value = spinning_sample_initial_frequency; - } + spinningSample.Samples = HitObject.CreateSpinningSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + spinningSample.Frequency.Value = spinning_sample_initial_frequency; } private void updateSpinningSample(ValueChangedEvent tracking) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 5c1c3fd253..776165cfb4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -29,6 +29,23 @@ namespace osu.Game.Rulesets.Osu.Objects set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } + public override IList AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray(); + + public IList CreateSlidingSamples() + { + var slidingSamples = new List(); + + var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); + if (normalSample != null) + slidingSamples.Add(normalSample.With("sliderslide")); + + var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); + if (whistleSample != null) + slidingSamples.Add(whistleSample.With("sliderwhistle")); + + return slidingSamples; + } + private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 1eddfb7fef..ddee4d3ebd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -1,7 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -73,5 +77,20 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + public override IList AuxiliarySamples => CreateSpinningSamples(); + + public HitSampleInfo[] CreateSpinningSamples() + { + var referenceSample = Samples.FirstOrDefault(); + + if (referenceSample == null) + return Array.Empty(); + + return new[] + { + SampleControlPoint.ApplyTo(referenceSample).With("spinnerspin") + }; + } } } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index c590cc302f..57b897e5b5 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using JetBrains.Annotations; using Newtonsoft.Json; @@ -67,6 +68,12 @@ namespace osu.Game.Rulesets.Objects } } + /// + /// Any samples which may be used by this hit object that are non-standard. + /// This is used only to preload these samples ahead of time. + /// + public virtual IList AuxiliarySamples => ImmutableList.Empty; + public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index b091803406..8dc037c7c8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -500,12 +500,12 @@ namespace osu.Game.Rulesets.Objects.Legacy => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered)); public bool Equals(LegacyHitSampleInfo? other) - => base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered; + => base.Equals(other) && CustomSampleBank == other.CustomSampleBank; public override bool Equals(object? obj) => obj is LegacyHitSampleInfo other && Equals(other); - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered); + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank); } private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 30e71dde1c..cd8f99db8b 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -3,22 +3,22 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; -using System.Diagnostics; namespace osu.Game.Rulesets.UI { @@ -264,10 +264,25 @@ namespace osu.Game.Rulesets.UI var entry = CreateLifetimeEntry(hitObject); lifetimeEntryMap[entry.HitObject] = entry; + preloadSamples(hitObject); + HitObjectContainer.Add(entry); OnHitObjectAdded(entry.HitObject); } + private void preloadSamples(HitObject hitObject) + { + // prepare sample pools ahead of time so we're not initialising at runtime. + foreach (var sample in hitObject.Samples) + prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + + foreach (var sample in hitObject.AuxiliarySamples) + prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + + foreach (var nestedObject in hitObject.NestedHitObjects) + preloadSamples(nestedObject); + } + /// /// Removes a for a pooled from this . /// @@ -330,22 +345,7 @@ namespace osu.Game.Rulesets.UI DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject, DrawableHitObject parent) { - var lookupType = hitObject.GetType(); - - IDrawablePool pool; - - // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. - if (!pools.TryGetValue(lookupType, out pool)) - { - foreach (var (t, p) in pools) - { - if (!t.IsInstanceOfType(hitObject)) - continue; - - pools[lookupType] = pool = p; - break; - } - } + var pool = prepareDrawableHitObjectPool(hitObject); return (DrawableHitObject)pool?.Get(d => { @@ -372,14 +372,39 @@ namespace osu.Game.Rulesets.UI }); } + private IDrawablePool prepareDrawableHitObjectPool(HitObject hitObject) + { + var lookupType = hitObject.GetType(); + + IDrawablePool pool; + + // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. + if (!pools.TryGetValue(lookupType, out pool)) + { + foreach (var (t, p) in pools) + { + if (!t.IsInstanceOfType(hitObject)) + continue; + + pools[lookupType] = pool = p; + break; + } + } + + return pool; + } + private readonly Dictionary> samplePools = new Dictionary>(); - public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) - { - if (!samplePools.TryGetValue(sampleInfo, out var existingPool)) - AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 1)); + public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) => prepareSamplePool(sampleInfo).Get(); - return existingPool.Get(); + private DrawablePool prepareSamplePool(ISampleInfo sampleInfo) + { + if (samplePools.TryGetValue(sampleInfo, out var pool)) return pool; + + AddInternal(samplePools[sampleInfo] = pool = new DrawableSamplePool(sampleInfo, 1)); + + return pool; } private class DrawableSamplePool : DrawablePool