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
/// <summary>
/// Stores the list of entries managed by this <see cref="HitObjectEntryManager"/> for each hit object managed by this <see cref="HitObjectEntryManager"/>.
/// </summary>
2021-06-15 19:48:00 +08:00
private readonly Dictionary < HitObject , List < HitObjectLifetimeEntry > > childrenMap = new Dictionary < HitObject , List < HitObjectLifetimeEntry > > ( ) ;
2021-06-15 18:41:37 +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 ;
2022-09-15 14:38:44 +08:00
childrenMap [ hitObject ] = new List < HitObjectLifetimeEntry > ( ) ;
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 ;
if ( childrenMap . TryGetValue ( parent , out var parentChildEntries ) )
parentChildEntries . Add ( entry ) ;
}
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.
2022-09-15 14:38:44 +08:00
if ( parentMap . Remove ( entry , out var parent ) & & childrenMap . TryGetValue ( parent , out var parentChildEntries ) )
2021-06-15 19:48:00 +08:00
parentChildEntries . Remove ( entry ) ;
2022-09-15 15:00:35 +08:00
// Remove all the entries' children.
2022-09-15 14:40:03 +08:00
if ( childrenMap . Remove ( hitObject , out var childEntries ) )
2021-06-15 19:48:00 +08:00
{
foreach ( var childEntry in childEntries )
2021-06-16 14:15:33 +08:00
Remove ( childEntry ) ;
2021-06-15 19:48:00 +08:00
}
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 )
{
if ( ! childrenMap . Remove ( hitObject , out var childEntries ) )
return ;
2022-09-15 15:00:35 +08:00
// Remove all the entries' children. At this point the parents' (this entries') children list has been removed from the map, so this does not cause upwards traversal.
2021-06-15 19:48:00 +08:00
foreach ( var entry in childEntries )
2021-06-16 14:15:33 +08:00
Remove ( entry ) ;
2021-06-15 19:48:00 +08:00
2022-09-15 15:00:35 +08:00
// The removed children list needs to be added back to the map for the entry to potentially receive children.
2021-06-15 19:48:00 +08:00
childEntries . Clear ( ) ;
childrenMap [ hitObject ] = childEntries ;
}
2021-06-15 18:41:37 +08:00
}
}