// Copyright (c) ppy Pty Ltd . 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 { /// /// 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; /// /// 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. /// /// /// 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 void Add(HitObjectLifetimeEntry entry, HitObject? parent) { HitObject hitObject = entry.HitObject; if (entryMap.ContainsKey(hitObject)) throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is already added to this {nameof(HitObjectEntryManager)}."); entryMap[hitObject] = entry; childrenMap[hitObject] = new List(); if (parent != null) { parentMap[entry] = parent; if (childrenMap.TryGetValue(parent, out var parentChildEntries)) parentChildEntries.Add(entry); } hitObject.DefaultsApplied += onDefaultsApplied; OnEntryAdded?.Invoke(entry, parent); } public void Remove(HitObjectLifetimeEntry entry) { HitObject hitObject = entry.HitObject; if (!entryMap.ContainsKey(hitObject)) throw new InvalidOperationException($@"The {nameof(HitObjectLifetimeEntry)} is not contained in this {nameof(HitObjectLifetimeEntry)}."); entryMap.Remove(hitObject); if (parentMap.Remove(entry, out var parent) && childrenMap.TryGetValue(parent, out var parentChildEntries)) parentChildEntries.Remove(entry); // Remove all entries of the nested hit objects if (childrenMap.Remove(hitObject, out var childEntries)) { foreach (var childEntry in childEntries) Remove(childEntry); } hitObject.DefaultsApplied -= onDefaultsApplied; OnEntryRemoved?.Invoke(entry, parent); } 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); childEntries.Clear(); childrenMap[hitObject] = childEntries; } } }