Skip to content

Commit

Permalink
Add BeatmapLoadContext
Browse files Browse the repository at this point in the history
  • Loading branch information
smoogipoo committed Oct 31, 2024
1 parent 50be7fb commit 951deee
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 35 deletions.
65 changes: 32 additions & 33 deletions osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,43 +34,42 @@ protected override IEnumerable<OsuHitObject> 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 <v8 maps for the same time duration.
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1,
GenerateTicks = generateTicksData?.GenerateTicks ?? true,
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1,
}.Yield();
Slider slider = BeatmapLoadContext.Current.Rent<Slider>();
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 <v8 maps for the same time duration.
slider.TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8
? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity
: 1;
slider.GenerateTicks = generateTicksData?.GenerateTicks ?? true;
slider.SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1;
return slider.Yield();

case IHasDuration endTimeData:
return new Spinner
{
StartTime = original.StartTime,
Samples = original.Samples,
EndTime = endTimeData.EndTime,
Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2,
NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
}.Yield();
Spinner spinner = BeatmapLoadContext.Current.Rent<Spinner>();
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<HitCircle>();
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();
}
}

Expand Down
115 changes: 115 additions & 0 deletions osu.Game/Beatmaps/BeatmapLoadContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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<BeatmapLoadContext> current = new ThreadLocal<BeatmapLoadContext>(() => new DefaultBeatmapLoadContext());

public IDisposable Begin()
=> new BeatmapLoadContextUsage(this);

public abstract T Rent<T>() where T : notnull, new();

public abstract void Return<T>(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<T>()
=> new T();

public override void Return<T>(T obj)
{
}

protected override void UsageStarted()
{
}

protected override void UsageFinished()
{
}
}

public class PooledBeatmapLoadContext : BeatmapLoadContext
{
private readonly Dictionary<Type, Stack<object>> pools = new Dictionary<Type, Stack<object>>();
private readonly List<object> objectsInUse = new List<object>();
private uint isTracking;

public override T Rent<T>()
{
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>(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<object> getPool(Type type)
{
if (pools.TryGetValue(type, out Stack<object>? pool))
return pool;

return pools[type] = [];
}
}
}
6 changes: 4 additions & 2 deletions osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
Expand All @@ -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();
}
}
Expand Down

0 comments on commit 951deee

Please sign in to comment.