From 951deee459dbb09b64353dad475b9f80502ba143 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 31 Oct 2024 03:10:26 +0900 Subject: [PATCH] Add BeatmapLoadContext --- .../Beatmaps/OsuBeatmapConverter.cs | 65 +++++----- osu.Game/Beatmaps/BeatmapLoadContext.cs | 115 ++++++++++++++++++ .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +- 3 files changed, 151 insertions(+), 35 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapLoadContext.cs diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 3c051a6bb1aa..261c56d04f94 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -34,43 +34,42 @@ protected override IEnumerable ConvertHitObject(HitObject original switch (original) { case IHasPathWithRepeats curveData: - return new Slider - { - StartTime = original.StartTime, - Samples = original.Samples, - Path = curveData.Path, - NodeSamples = curveData.NodeSamples, - RepeatCount = curveData.RepeatCount, - Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, - // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. - // this results in more (or less) ticks being generated in (); + slider.StartTime = original.StartTime; + slider.Samples = original.Samples; + slider.Path = curveData.Path; + slider.NodeSamples = curveData.NodeSamples; + slider.RepeatCount = curveData.RepeatCount; + slider.Position = positionData?.Position ?? Vector2.Zero; + slider.NewCombo = comboData?.NewCombo ?? false; + slider.ComboOffset = comboData?.ComboOffset ?? 0; + // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. + // this results in more (or less) ticks being generated in (); + spinner.StartTime = original.StartTime; + spinner.Samples = original.Samples; + spinner.EndTime = endTimeData.EndTime; + spinner.Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2; + spinner.NewCombo = comboData?.NewCombo ?? false; + spinner.ComboOffset = comboData?.ComboOffset ?? 0; + return spinner.Yield(); default: - return new HitCircle - { - StartTime = original.StartTime, - Samples = original.Samples, - Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false, - ComboOffset = comboData?.ComboOffset ?? 0, - }.Yield(); + HitCircle circle = BeatmapLoadContext.Current.Rent(); + circle.StartTime = original.StartTime; + circle.Samples = original.Samples; + circle.Position = positionData?.Position ?? Vector2.Zero; + circle.NewCombo = comboData?.NewCombo ?? false; + circle.ComboOffset = comboData?.ComboOffset ?? 0; + return circle.Yield(); } } diff --git a/osu.Game/Beatmaps/BeatmapLoadContext.cs b/osu.Game/Beatmaps/BeatmapLoadContext.cs new file mode 100644 index 000000000000..8925021c024f --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapLoadContext.cs @@ -0,0 +1,115 @@ +// 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.Diagnostics; +using System.Threading; + +namespace osu.Game.Beatmaps +{ + public abstract class BeatmapLoadContext + { + public static BeatmapLoadContext Current => current.Value!; + private static readonly ThreadLocal current = new ThreadLocal(() => new DefaultBeatmapLoadContext()); + + public IDisposable Begin() + => new BeatmapLoadContextUsage(this); + + public abstract T Rent() where T : notnull, new(); + + public abstract void Return(T obj) where T : notnull, new(); + + protected abstract void UsageStarted(); + + protected abstract void UsageFinished(); + + private class BeatmapLoadContextUsage : IDisposable + { + private readonly BeatmapLoadContext context; + private readonly BeatmapLoadContext oldContext; + private bool isDisposed; + + public BeatmapLoadContextUsage(BeatmapLoadContext context) + { + this.context = context; + oldContext = current.Value!; + current.Value = context; + + context.UsageStarted(); + } + + public void Dispose() + { + if (isDisposed) + return; + + context.UsageFinished(); + current.Value = oldContext; + isDisposed = true; + } + } + } + + public class DefaultBeatmapLoadContext : BeatmapLoadContext + { + public override T Rent() + => new T(); + + public override void Return(T obj) + { + } + + protected override void UsageStarted() + { + } + + protected override void UsageFinished() + { + } + } + + public class PooledBeatmapLoadContext : BeatmapLoadContext + { + private readonly Dictionary> pools = new Dictionary>(); + private readonly List objectsInUse = new List(); + private uint isTracking; + + public override T Rent() + { + if (isTracking == 0) + return new T(); + + if (!getPool(typeof(T)).TryPop(out object? obj)) + obj = new T(); + + objectsInUse.Add(obj); + return (T)obj; + } + + public override void Return(T obj) + => getPool(typeof(T)).Push(obj); + + protected override void UsageStarted() + => isTracking++; + + protected override void UsageFinished() + { + Trace.Assert(isTracking > 0); + if (--isTracking > 0) + return; + + foreach (object obj in objectsInUse) + getPool(obj.GetType()).Push(obj); + objectsInUse.Clear(); + } + + private Stack getPool(Type type) + { + if (pools.TryGetValue(type, out Stack? pool)) + return pool; + + return pools[type] = []; + } + } +} diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b068c87fbb10..bca60aa3f6e4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -162,7 +162,8 @@ private void applySamples(HitObject hitObject) { if (hitObject is IHasRepeats hasRepeats) { - SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1) ?? SampleControlPoint.DEFAULT; + SampleControlPoint sampleControlPoint = + (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1) ?? SampleControlPoint.DEFAULT; hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) @@ -175,7 +176,8 @@ private void applySamples(HitObject hitObject) } else { - SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY) ?? SampleControlPoint.DEFAULT; + SampleControlPoint sampleControlPoint = + (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY) ?? SampleControlPoint.DEFAULT; hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); } }