2021-06-15 18:41:37 +08:00
// 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.Diagnostics.CodeAnalysis ;
namespace osu.Game.Rulesets.Objects.Pooling
{
/// <summary>
/// Manages a mapping between <see cref="HitObject"/> and <see cref="HitObjectLifetimeEntry"/>
/// </summary>
internal class HitObjectEntryManager
{
/// <summary>
/// All entries, including entries of the nested hit objects.
/// </summary>
public IEnumerable < HitObjectLifetimeEntry > AllEntries = > entryMap . Values ;
2021-06-16 14:09:01 +08:00
/// <summary>
/// Invoked when a new <see cref="HitObjectLifetimeEntry"/> is added to this <see cref="HitObjectEntryManager"/>..
/// The second parameter of the event is the parent hit object.
/// </summary>
2021-06-15 18:41:37 +08:00
public event Action < HitObjectLifetimeEntry , HitObject ? > ? OnEntryAdded ;
2021-06-16 14:09:01 +08:00
/// <summary>
/// Invoked when a <see cref="HitObjectLifetimeEntry"/> is removed from this <see cref="HitObjectEntryManager"/>.
/// The second parameter of the event is the parent hit object.
/// </summary>
2021-06-15 18:41:37 +08:00
public event Action < HitObjectLifetimeEntry , HitObject ? > ? OnEntryRemoved ;
2021-06-16 14:09:01 +08:00
/// <summary>
/// Provides the reverse mapping of <see cref="HitObjectLifetimeEntry.HitObject"/> for each entry.
/// </summary>
2021-06-15 18:41:37 +08:00
private readonly Dictionary < HitObject , HitObjectLifetimeEntry > entryMap = new Dictionary < HitObject , HitObjectLifetimeEntry > ( ) ;
2021-06-16 14:09:01 +08:00
/// <summary>
/// Stores the parent hit object for entries of the nested hit objects.
/// </summary>
/// <remarks>
/// The parent hit object of a pooled hit object may be non-pooled.
/// In that case, no corresponding <see cref="HitObjectLifetimeEntry"/> is stored in this <see cref="HitObjectEntryManager"/>.
/// </remarks>
2022-09-15 14:38:44 +08:00
private readonly Dictionary < HitObjectLifetimeEntry , HitObject > parentMap = new Dictionary < HitObjectLifetimeEntry , HitObject > ( ) ;
2021-06-16 14:09:01 +08:00
2021-06-16 14:15:33 +08:00
public void Add ( HitObjectLifetimeEntry entry , HitObject ? parent )
2021-06-15 18:41:37 +08:00
{
2022-09-15 14:40:03 +08:00
HitObject hitObject = entry . HitObject ;
if ( entryMap . ContainsKey ( hitObject ) )
2021-06-16 14:15:33 +08:00
throw new InvalidOperationException ( $@"The {nameof(HitObjectLifetimeEntry)} is already added to this {nameof(HitObjectEntryManager)}." ) ;
2021-06-15 18:41:37 +08:00
2022-09-15 15:00:35 +08:00
// Add the entry.
2021-06-16 14:15:33 +08:00
entryMap [ hitObject ] = entry ;
2021-06-15 19:48:00 +08:00
2022-09-15 15:00:35 +08:00
// If the entry has a parent, set it and add the entry to the parent's children.
2022-09-15 14:38:44 +08:00
if ( parent ! = null )
{
parentMap [ entry ] = parent ;
2023-07-04 04:33:11 +08:00
if ( entryMap . TryGetValue ( parent , out var parentEntry ) )
parentEntry . NestedEntries . Add ( entry ) ;
2022-09-15 14:38:44 +08:00
}
2021-06-15 19:48:00 +08:00
hitObject . DefaultsApplied + = onDefaultsApplied ;
OnEntryAdded ? . Invoke ( entry , parent ) ;
2021-06-15 18:41:37 +08:00
}
2021-06-16 14:15:33 +08:00
public void Remove ( HitObjectLifetimeEntry entry )
2021-06-15 18:41:37 +08:00
{
2022-09-15 14:40:03 +08:00
HitObject hitObject = entry . HitObject ;
if ( ! entryMap . ContainsKey ( hitObject ) )
2022-09-15 15:00:35 +08:00
throw new InvalidOperationException ( $@"The {nameof(HitObjectLifetimeEntry)} is not contained in this {nameof(HitObjectEntryManager)}." ) ;
2021-06-15 18:41:37 +08:00
2021-06-16 14:15:33 +08:00
entryMap . Remove ( hitObject ) ;
2021-06-15 19:48:00 +08:00
2022-09-15 15:00:35 +08:00
// If the entry has a parent, unset it and remove the entry from the parents' children.
2023-07-04 04:33:11 +08:00
if ( parentMap . Remove ( entry , out var parent ) & & entryMap . TryGetValue ( parent , out var parentEntry ) )
parentEntry . NestedEntries . Remove ( entry ) ;
2021-06-15 19:48:00 +08:00
2022-09-15 15:00:35 +08:00
// Remove all the entries' children.
2023-07-04 04:33:11 +08:00
foreach ( var childEntry in entry . NestedEntries )
Remove ( childEntry ) ;
2021-06-15 18:41:37 +08:00
2022-09-15 14:38:44 +08:00
hitObject . DefaultsApplied - = onDefaultsApplied ;
2021-06-15 19:48:00 +08:00
OnEntryRemoved ? . Invoke ( entry , parent ) ;
2021-06-15 18:41:37 +08:00
}
public bool TryGet ( HitObject hitObject , [ MaybeNullWhen ( false ) ] out HitObjectLifetimeEntry entry )
{
return entryMap . TryGetValue ( hitObject , out entry ) ;
}
2021-06-15 19:48:00 +08:00
/// <summary>
/// As nested hit objects are recreated, remove entries of the old nested hit objects.
/// </summary>
private void onDefaultsApplied ( HitObject hitObject )
{
2023-07-04 04:33:11 +08:00
if ( ! entryMap . TryGetValue ( hitObject , out var entry ) )
2021-06-15 19:48:00 +08:00
return ;
2023-07-04 04:33:11 +08:00
// Replace the entire list rather than clearing to prevent circular traversal later.
var previousEntries = entry . NestedEntries ;
entry . NestedEntries = new List < HitObjectLifetimeEntry > ( ) ;
2021-06-15 19:48:00 +08:00
2023-07-04 04:33:11 +08:00
// Remove all the entries' children. At this point the parents' (this entries') children list has been reconstructed, so this does not cause upwards traversal.
foreach ( var nested in previousEntries )
Remove ( nested ) ;
2021-06-15 19:48:00 +08:00
}
2021-06-15 18:41:37 +08:00
}
}