mirror of
https://github.com/ppy/osu.git
synced 2025-02-05 06:32:55 +08:00
Move hitobject pooling to Playfield
This commit is contained in:
parent
f093acc9d5
commit
36f1833f6e
@ -134,19 +134,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
RegisterPool<TestHitObject, DrawableTestHitObject>(PoolSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(TestHitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
protected override HitObjectLifetimeEntry CreateLifetimeEntry(TestHitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
||||||
|
|
||||||
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h) => null;
|
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h) => null;
|
||||||
|
|
||||||
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
|
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new TestPlayfield();
|
protected override Playfield CreatePlayfield() => new TestPlayfield(PoolSize);
|
||||||
|
|
||||||
private class TestHitObjectLifetimeEntry : HitObjectLifetimeEntry
|
private class TestHitObjectLifetimeEntry : HitObjectLifetimeEntry
|
||||||
{
|
{
|
||||||
@ -161,11 +155,20 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private class TestPlayfield : Playfield
|
private class TestPlayfield : Playfield
|
||||||
{
|
{
|
||||||
public TestPlayfield()
|
private readonly int poolSize;
|
||||||
|
|
||||||
|
public TestPlayfield(int poolSize)
|
||||||
{
|
{
|
||||||
|
this.poolSize = poolSize;
|
||||||
AddInternal(HitObjectContainer);
|
AddInternal(HitObjectContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
|
||||||
|
}
|
||||||
|
|
||||||
protected override GameplayCursorContainer CreateCursor() => null;
|
protected override GameplayCursorContainer CreateCursor() => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
private HitObjectLifetimeEntry lifetimeEntry;
|
private HitObjectLifetimeEntry lifetimeEntry;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private DrawableRuleset drawableRuleset { get; set; }
|
private HitObjectPoolProvider poolProvider { get; set; }
|
||||||
|
|
||||||
private Container<PausableSkinnableSound> samplesContainer;
|
private Container<PausableSkinnableSound> samplesContainer;
|
||||||
|
|
||||||
@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
foreach (var h in HitObject.NestedHitObjects)
|
foreach (var h in HitObject.NestedHitObjects)
|
||||||
{
|
{
|
||||||
var drawableNested = drawableRuleset?.GetPooledDrawableRepresentation(h)
|
var drawableNested = poolProvider?.GetPooledDrawableRepresentation(h)
|
||||||
?? CreateNestedHitObject(h)
|
?? CreateNestedHitObject(h)
|
||||||
?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
||||||
|
|
||||||
|
@ -15,10 +15,8 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Pooling;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -327,9 +325,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// Creates a <see cref="DrawableHitObject{TObject}"/> to represent a <see cref="HitObject"/>.
|
/// Creates a <see cref="DrawableHitObject{TObject}"/> to represent a <see cref="HitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If this method returns <c>null</c>, then this <see cref="DrawableRuleset"/> will assume the requested <see cref="HitObject"/> type is being pooled,
|
/// If this method returns <c>null</c>, then this <see cref="DrawableRuleset"/> will assume the requested <see cref="HitObject"/> type is being pooled inside the <see cref="Playfield"/>,
|
||||||
/// and will instead attempt to retrieve the <see cref="DrawableHitObject"/>s at the point they should become alive via pools registered through
|
/// and will instead attempt to retrieve the <see cref="DrawableHitObject"/>s at the point they should become alive via pools registered in the <see cref="Playfield"/>.
|
||||||
/// <see cref="DrawableRuleset.RegisterPool{TObject, TDrawable}(int, int?)"/> or <see cref="DrawableRuleset.RegisterPool{TObject, TDrawable}(DrawablePool{TDrawable})"/>.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="h">The <see cref="HitObject"/> to represent.</param>
|
/// <param name="h">The <see cref="HitObject"/> to represent.</param>
|
||||||
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
|
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
|
||||||
@ -550,68 +547,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void CancelResume();
|
public abstract void CancelResume();
|
||||||
|
|
||||||
private readonly Dictionary<Type, IDrawablePool> pools = new Dictionary<Type, IDrawablePool>();
|
|
||||||
private readonly Dictionary<HitObject, HitObjectLifetimeEntry> lifetimeEntries = new Dictionary<HitObject, HitObjectLifetimeEntry>();
|
private readonly Dictionary<HitObject, HitObjectLifetimeEntry> lifetimeEntries = new Dictionary<HitObject, HitObjectLifetimeEntry>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a default <see cref="DrawableHitObject"/> pool with this <see cref="DrawableRuleset"/> which is to be used whenever
|
|
||||||
/// <see cref="DrawableHitObject"/> representations are requested for the given <typeparamref name="TObject"/> type (via <see cref="GetPooledDrawableRepresentation"/>).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="initialSize">The number of <see cref="DrawableHitObject"/>s to be initially stored in the pool.</param>
|
|
||||||
/// <param name="maximumSize">
|
|
||||||
/// The maximum number of <see cref="DrawableHitObject"/>s that can be stored in the pool.
|
|
||||||
/// If this limit is exceeded, every subsequent <see cref="DrawableHitObject"/> will be created anew instead of being retrieved from the pool,
|
|
||||||
/// until some of the existing <see cref="DrawableHitObject"/>s are returned to the pool.
|
|
||||||
/// </param>
|
|
||||||
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
|
||||||
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
|
||||||
protected void RegisterPool<TObject, TDrawable>(int initialSize, int? maximumSize = null)
|
|
||||||
where TObject : HitObject
|
|
||||||
where TDrawable : DrawableHitObject, new()
|
|
||||||
=> RegisterPool<TObject, TDrawable>(new DrawablePool<TDrawable>(initialSize, maximumSize));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a custom <see cref="DrawableHitObject"/> pool with this <see cref="DrawableRuleset"/> which is to be used whenever
|
|
||||||
/// <see cref="DrawableHitObject"/> representations are requested for the given <typeparamref name="TObject"/> type (via <see cref="GetPooledDrawableRepresentation"/>).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pool">The <see cref="DrawablePool{T}"/> to register.</param>
|
|
||||||
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
|
||||||
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
|
||||||
protected void RegisterPool<TObject, TDrawable>([NotNull] DrawablePool<TDrawable> pool)
|
|
||||||
where TObject : HitObject
|
|
||||||
where TDrawable : DrawableHitObject, new()
|
|
||||||
{
|
|
||||||
pools[typeof(TObject)] = pool;
|
|
||||||
AddInternal(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to retrieve the poolable <see cref="DrawableHitObject"/> representation of a <see cref="HitObject"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve the <see cref="DrawableHitObject"/> representation of.</param>
|
|
||||||
/// <returns>The <see cref="DrawableHitObject"/> representing <see cref="HitObject"/>, or <c>null</c> if no poolable representation exists.</returns>
|
|
||||||
[CanBeNull]
|
|
||||||
public DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject)
|
|
||||||
{
|
|
||||||
if (!pools.TryGetValue(hitObject.GetType(), out var pool))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return (DrawableHitObject)pool.Get(d =>
|
|
||||||
{
|
|
||||||
var dho = (DrawableHitObject)d;
|
|
||||||
|
|
||||||
// If this is the first time this DHO is being used (not loaded), then apply the DHO mods.
|
|
||||||
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
|
||||||
if (!dho.IsLoaded)
|
|
||||||
{
|
|
||||||
foreach (var m in Mods.OfType<IApplicableToDrawableHitObjects>())
|
|
||||||
m.ApplyToDrawableHitObjects(dho.Yield());
|
|
||||||
}
|
|
||||||
|
|
||||||
dho.Apply(hitObject, GetLifetimeEntry(hitObject));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the <see cref="HitObjectLifetimeEntry"/> for a given <see cref="HitObject"/>.
|
/// Creates the <see cref="HitObjectLifetimeEntry"/> for a given <see cref="HitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -629,7 +566,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve or create the <see cref="HitObjectLifetimeEntry"/> for.</param>
|
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve or create the <see cref="HitObjectLifetimeEntry"/> for.</param>
|
||||||
/// <returns>The <see cref="HitObjectLifetimeEntry"/> for <paramref name="hitObject"/>.</returns>
|
/// <returns>The <see cref="HitObjectLifetimeEntry"/> for <paramref name="hitObject"/>.</returns>
|
||||||
[NotNull]
|
[NotNull]
|
||||||
protected HitObjectLifetimeEntry GetLifetimeEntry([NotNull] HitObject hitObject)
|
internal HitObjectLifetimeEntry GetLifetimeEntry([NotNull] HitObject hitObject)
|
||||||
{
|
{
|
||||||
if (lifetimeEntries.TryGetValue(hitObject, out var entry))
|
if (lifetimeEntries.TryGetValue(hitObject, out var entry))
|
||||||
return entry;
|
return entry;
|
||||||
|
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private DrawableRuleset drawableRuleset { get; set; }
|
private HitObjectPoolProvider poolProvider { get; set; }
|
||||||
|
|
||||||
public HitObjectContainer()
|
public HitObjectContainer()
|
||||||
{
|
{
|
||||||
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
Debug.Assert(!drawableMap.ContainsKey(entry));
|
Debug.Assert(!drawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
var drawable = drawableRuleset.GetPooledDrawableRepresentation(entry.HitObject);
|
var drawable = poolProvider.GetPooledDrawableRepresentation(entry.HitObject);
|
||||||
if (drawable == null)
|
if (drawable == null)
|
||||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||||
|
|
||||||
|
111
osu.Game/Rulesets/UI/HitObjectPoolProvider.cs
Normal file
111
osu.Game/Rulesets/UI/HitObjectPoolProvider.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// 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 System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="CompositeDrawable"/> that pools <see cref="DrawableHitObject"/>s and allows children to retrieve them via <see cref="GetPooledDrawableRepresentation"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Cached(typeof(HitObjectPoolProvider))]
|
||||||
|
public class HitObjectPoolProvider : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DrawableRuleset drawableRuleset { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IReadOnlyList<Mod> mods { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private HitObjectPoolProvider parentProvider { get; set; }
|
||||||
|
|
||||||
|
private readonly Dictionary<Type, IDrawablePool> pools = new Dictionary<Type, IDrawablePool>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a default <see cref="DrawableHitObject"/> pool with this <see cref="DrawableRuleset"/> which is to be used whenever
|
||||||
|
/// <see cref="DrawableHitObject"/> representations are requested for the given <typeparamref name="TObject"/> type (via <see cref="GetPooledDrawableRepresentation"/>).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="initialSize">The number of <see cref="DrawableHitObject"/>s to be initially stored in the pool.</param>
|
||||||
|
/// <param name="maximumSize">
|
||||||
|
/// The maximum number of <see cref="DrawableHitObject"/>s that can be stored in the pool.
|
||||||
|
/// If this limit is exceeded, every subsequent <see cref="DrawableHitObject"/> will be created anew instead of being retrieved from the pool,
|
||||||
|
/// until some of the existing <see cref="DrawableHitObject"/>s are returned to the pool.
|
||||||
|
/// </param>
|
||||||
|
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
||||||
|
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
||||||
|
protected void RegisterPool<TObject, TDrawable>(int initialSize, int? maximumSize = null)
|
||||||
|
where TObject : HitObject
|
||||||
|
where TDrawable : DrawableHitObject, new()
|
||||||
|
=> RegisterPool<TObject, TDrawable>(new DrawablePool<TDrawable>(initialSize, maximumSize));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a custom <see cref="DrawableHitObject"/> pool with this <see cref="DrawableRuleset"/> which is to be used whenever
|
||||||
|
/// <see cref="DrawableHitObject"/> representations are requested for the given <typeparamref name="TObject"/> type (via <see cref="GetPooledDrawableRepresentation"/>).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pool">The <see cref="DrawablePool{T}"/> to register.</param>
|
||||||
|
/// <typeparam name="TObject">The <see cref="HitObject"/> type.</typeparam>
|
||||||
|
/// <typeparam name="TDrawable">The <see cref="DrawableHitObject"/> receiver for <typeparamref name="TObject"/>s.</typeparam>
|
||||||
|
protected void RegisterPool<TObject, TDrawable>([NotNull] DrawablePool<TDrawable> pool)
|
||||||
|
where TObject : HitObject
|
||||||
|
where TDrawable : DrawableHitObject, new()
|
||||||
|
{
|
||||||
|
pools[typeof(TObject)] = pool;
|
||||||
|
AddInternal(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the poolable <see cref="DrawableHitObject"/> representation of a <see cref="HitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve the <see cref="DrawableHitObject"/> representation of.</param>
|
||||||
|
/// <returns>The <see cref="DrawableHitObject"/> representing <see cref="HitObject"/>, or <c>null</c> if no poolable representation exists.</returns>
|
||||||
|
[CanBeNull]
|
||||||
|
public DrawableHitObject GetPooledDrawableRepresentation([NotNull] 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pool == null)
|
||||||
|
return parentProvider?.GetPooledDrawableRepresentation(hitObject);
|
||||||
|
|
||||||
|
return (DrawableHitObject)pool.Get(d =>
|
||||||
|
{
|
||||||
|
var dho = (DrawableHitObject)d;
|
||||||
|
|
||||||
|
// If this is the first time this DHO is being used (not loaded), then apply the DHO mods.
|
||||||
|
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
||||||
|
if (!dho.IsLoaded)
|
||||||
|
{
|
||||||
|
foreach (var m in mods.OfType<IApplicableToDrawableHitObjects>())
|
||||||
|
m.ApplyToDrawableHitObjects(dho.Yield());
|
||||||
|
}
|
||||||
|
|
||||||
|
dho.Apply(hitObject, drawableRuleset.GetLifetimeEntry(hitObject));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,6 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -17,7 +16,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
public abstract class Playfield : CompositeDrawable
|
public abstract class Playfield : HitObjectPoolProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
||||||
|
Loading…
Reference in New Issue
Block a user