// 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 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(Playfield playfield) { this.playfield = playfield; bindPlayfieldRecursive(playfield); } private void bindPlayfieldRecursive(Playfield p) { p.HitObjectContainer.HitObjectUsageBegan += onHitObjectUsageBegan; p.HitObjectContainer.HitObjectUsageFinished += onHitObjectUsageFinished; foreach (var nested in p.NestedPlayfields) bindPlayfieldRecursive(nested); } 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, playfield.AllHitObjects.Single(d => d.HitObject == hitObject)); } } // Go through any remaining pending finished usages. foreach (var (hitObject, _) in pendingUsagesFinished) HitObjectUsageFinished?.Invoke(hitObject); pendingUsagesBegan.Clear(); pendingUsagesFinished.Clear(); } } }