// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable enable using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace osu.Game.Rulesets.Objects.Pooling { /// /// Manages a mapping between and /// internal class HitObjectEntryManager { /// /// All entries, including entries of the nested hit objects. /// public IEnumerable AllEntries => entryMap.Values; /// /// Invoked when a new is added to this .. /// The second parameter of the event is the parent hit object. /// public event Action? OnEntryAdded; /// /// Invoked when a is removed from this . /// The second parameter of the event is the parent hit object. /// public event Action? OnEntryRemoved; private readonly Func createLifetimeEntry; /// /// Provides the reverse mapping of for each entry. /// private readonly Dictionary entryMap = new Dictionary(); /// /// Stores the parent hit object for entries of the nested hit objects. /// A null is stored for entries of the top-level hit objects. /// /// /// The parent hit object of a pooled hit object may be non-pooled. /// In that case, no corresponding is stored in this . /// private readonly Dictionary parentMap = new Dictionary(); /// /// Stores the list of entries managed by this for each hit object managed by this . /// private readonly Dictionary> childrenMap = new Dictionary>(); public HitObjectEntryManager(Func createLifetimeEntry) { this.createLifetimeEntry = createLifetimeEntry; } public HitObjectLifetimeEntry Add(HitObject hitObject, HitObject? parent) { if (entryMap.ContainsKey(hitObject)) throw new InvalidOperationException($@"The {nameof(HitObject)} is already added to this {nameof(HitObjectEntryManager)}."); var entry = createLifetimeEntry(hitObject); entryMap[hitObject] = entry; parentMap[entry] = parent; if (parent != null && childrenMap.TryGetValue(parent, out var parentChildEntries)) parentChildEntries.Add(entry); hitObject.DefaultsApplied += onDefaultsApplied; childrenMap[entry.HitObject] = new List(); OnEntryAdded?.Invoke(entry, parent); return entry; } public bool Remove(HitObject hitObject) { if (!entryMap.Remove(hitObject, out var entry)) return false; parentMap.Remove(entry, out var parent); if (parent != null && childrenMap.TryGetValue(parent, out var parentChildEntries)) parentChildEntries.Remove(entry); hitObject.DefaultsApplied -= onDefaultsApplied; // Remove all entries of the nested hit objects if (childrenMap.Remove(entry.HitObject, out var childEntries)) { foreach (var childEntry in childEntries) Remove(childEntry.HitObject); } OnEntryRemoved?.Invoke(entry, parent); return true; } public bool TryGet(HitObject hitObject, [MaybeNullWhen(false)] out HitObjectLifetimeEntry entry) { return entryMap.TryGetValue(hitObject, out entry); } /// /// As nested hit objects are recreated, remove entries of the old nested hit objects. /// private void onDefaultsApplied(HitObject hitObject) { if (!childrenMap.Remove(hitObject, out var childEntries)) return; foreach (var entry in childEntries) Remove(entry.HitObject); childEntries.Clear(); childrenMap[hitObject] = childEntries; } } }