// 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; using System.Linq; using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Edit.Compose { /// /// A queue which processes events from the many s in a nested hierarchy. /// internal class HitObjectContainerEventQueue : Component { /// /// Invoked when a becomes used by a . /// /// /// If the ruleset uses pooled objects, this represents the time when the s become alive. /// public event Action HitObjectUsageBegan; /// /// Invoked when a becomes unused by a . /// /// /// If the ruleset uses pooled objects, this represents the time when the s become dead. /// public event Action HitObjectUsageFinished; /// /// Invoked when a has been transferred to another . /// public event Action HitObjectUsageTransferred; private readonly Playfield playfield; /// /// Creates a new . /// /// The most top-level . public HitObjectContainerEventQueue([NotNull] Playfield playfield) { this.playfield = playfield; playfield.HitObjectUsageBegan += onHitObjectUsageBegan; playfield.HitObjectUsageFinished += onHitObjectUsageFinished; } private readonly Dictionary pendingUsagesBegan = new Dictionary(); private readonly Dictionary pendingUsagesFinished = new Dictionary(); private void onHitObjectUsageBegan(HitObject hitObject) => pendingUsagesBegan[hitObject] = pendingUsagesBegan.GetValueOrDefault(hitObject, 0) + 1; private void onHitObjectUsageFinished(HitObject hitObject) => pendingUsagesFinished[hitObject] = pendingUsagesFinished.GetValueOrDefault(hitObject, 0) + 1; protected override void Update() { base.Update(); foreach (var (hitObject, countBegan) in pendingUsagesBegan) { if (pendingUsagesFinished.TryGetValue(hitObject, out int countFinished)) { Debug.Assert(countFinished > 0); if (countBegan > countFinished) { // The hitobject is still in use, but transferred to a different HOC. HitObjectUsageTransferred?.Invoke(hitObject, playfield.AllHitObjects.Single(d => d.HitObject == hitObject)); pendingUsagesFinished.Remove(hitObject); } } else { // This is a new usage of the hitobject. HitObjectUsageBegan?.Invoke(hitObject); } } // Go through any remaining pending finished usages. foreach (var (hitObject, _) in pendingUsagesFinished) HitObjectUsageFinished?.Invoke(hitObject); pendingUsagesBegan.Clear(); pendingUsagesFinished.Clear(); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); playfield.HitObjectUsageBegan -= onHitObjectUsageBegan; playfield.HitObjectUsageFinished -= onHitObjectUsageFinished; } } }