<commit_before>﻿// Copyright (c) ppy Pty Ltd <<EMAIL>>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Layout;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;

namespace osu.Game.Rulesets.UI.Scrolling
{
    public class ScrollingHitObjectContainer : HitObjectContainer
    {
        private readonly IBindable<double> timeRange = new BindableDouble();
        private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
        private readonly Dictionary<DrawableHitObject, Cached> hitObjectInitialStateCache = new Dictionary<DrawableHitObject, Cached>();

        [Resolved]
        private IScrollingInfo scrollingInfo { get; set; }

        // Responds to changes in the layout. When the layout is changes, all hit object states must be recomputed.
        private readonly LayoutValue layoutCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);

        // A combined cache across all hit object states to reduce per-update iterations.
        // When invalidated, one or more (but not necessarily all) hitobject states must be re-validated.
        private readonly Cached combinedObjCache = new Cached();

        public ScrollingHitObjectContainer()
        {
            RelativeSizeAxes = Axes.Both;

            AddLayout(layoutCache);
        }

        [BackgroundDependencyLoader]
        private void load()
        {
            direction.BindTo(scrollingInfo.Direction);
            timeRange.BindTo(scrollingInfo.TimeRange);

            direction.ValueChanged += _ => layoutCache.Invalidate();
            timeRange.ValueChanged += _ => layoutCache.Invalidate();
        }

        public override void Add(DrawableHitObject hitObject)
        {
            combinedObjCache.Invalidate();
            hitObject.DefaultsApplied += onDefaultsApplied;
            base.Add(hitObject);
        }

        public override bool Remove(DrawableHitObject hitObject)
        {
            var result = base.Remove(hitObject);

            if (result)
            {
                combinedObjCache.Invalidate();
                hitObjectInitialStateCache.Remove(hitObject);

                hitObject.DefaultsApplied -= onDefaultsApplied;
            }

            return result;
        }

        public override void Clear(bool disposeChildren = true)
        {
            foreach (var h in Objects)
                h.DefaultsApplied -= onDefaultsApplied;

            base.Clear(disposeChildren);

            combinedObjCache.Invalidate();
            hitObjectInitialStateCache.Clear();
        }

        private void onDefaultsApplied(DrawableHitObject drawableObject)
        {
            // The cache may not exist if the hitobject state hasn't been computed yet (e.g. if the hitobject was added + defaults applied in the same frame).
            // In such a case, combinedObjCache will take care of updating the hitobject.
            if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var objCache))
            {
                combinedObjCache.Invalidate();
                objCache.Invalidate();
            }
        }

        private float scrollLength;

        protected override void Update()
        {
            base.Update();

            if (!layoutCache.IsValid)
            {
                foreach (var cached in hitObjectInitialStateCache.Values)
                    cached.Invalidate();
                combinedObjCache.Invalidate();

                scrollingInfo.Algorithm.Reset();

                layoutCache.Validate();
            }

            if (!combinedObjCache.IsValid)
            {
                switch (direction.Value)
                {
                    case ScrollingDirection.Up:
                    case ScrollingDirection.Down:
                        scrollLength = DrawSize.Y;
                        break;

                    default:
                        scrollLength = DrawSize.X;
                        break;
                }

                foreach (var obj in Objects)
                {
                    if (!hitObjectInitialStateCache.TryGetValue(obj, out var objCache))
                        objCache = hitObjectInitialStateCache[obj] = new Cached();

                    if (objCache.IsValid)
                        return;

                    computeLifetimeStartRecursive(obj);
                    computeInitialStateRecursive(obj);

                    objCache.Validate();
                }

                combinedObjCache.Validate();<commit_msg>Fix potentially skipping hitobject updates<commit_after>﻿// Copyright (c) p<NAME> <<EMAIL>>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Layout;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;

namespace osu.Game.Rulesets.UI.Scrolling
{
    public class ScrollingHitObjectContainer : HitObjectContainer
    {
        private readonly IBindable<double> timeRange = new BindableDouble();
        private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
        private readonly Dictionary<DrawableHitObject, Cached> hitObjectInitialStateCache = new Dictionary<DrawableHitObject, Cached>();

        [Resolved]
        private IScrollingInfo scrollingInfo { get; set; }

        // Responds to changes in the layout. When the layout is changes, all hit object states must be recomputed.
        private readonly LayoutValue layoutCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);

        // A combined cache across all hit object states to reduce per-update iterations.
        // When invalidated, one or more (but not necessarily all) hitobject states must be re-validated.
        private readonly Cached combinedObjCache = new Cached();

        public ScrollingHitObjectContainer()
        {
            RelativeSizeAxes = Axes.Both;

            AddLayout(layoutCache);
        }

        [BackgroundDependencyLoader]
        private void load()
        {
            direction.BindTo(scrollingInfo.Direction);
            timeRange.BindTo(scrollingInfo.TimeRange);

            direction.ValueChanged += _ => layoutCache.Invalidate();
            timeRange.ValueChanged += _ => layoutCache.Invalidate();
        }

        public override void Add(DrawableHitObject hitObject)
        {
            combinedObjCache.Invalidate();
            hitObject.DefaultsApplied += onDefaultsApplied;
            base.Add(hitObject);
        }

        public override bool Remove(DrawableHitObject hitObject)
        {
            var result = base.Remove(hitObject);

            if (result)
            {
                combinedObjCache.Invalidate();
                hitObjectInitialStateCache.Remove(hitObject);

                hitObject.DefaultsApplied -= onDefaultsApplied;
            }

            return result;
        }

        public override void Clear(bool disposeChildren = true)
        {
            foreach (var h in Objects)
                h.DefaultsApplied -= onDefaultsApplied;

            base.Clear(disposeChildren);

            combinedObjCache.Invalidate();
            hitObjectInitialStateCache.Clear();
        }

        private void onDefaultsApplied(DrawableHitObject drawableObject)
        {
            // The cache may not exist if the hitobject state hasn't been computed yet (e.g. if the hitobject was added + defaults applied in the same frame).
            // In such a case, combinedObjCache will take care of updating the hitobject.
            if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var objCache))
            {
                combinedObjCache.Invalidate();
                objCache.Invalidate();
            }
        }

        private float scrollLength;

        protected override void Update()
        {
            base.Update();

            if (!layoutCache.IsValid)
            {
                foreach (var cached in hitObjectInitialStateCache.Values)
                    cached.Invalidate();
                combinedObjCache.Invalidate();

                scrollingInfo.Algorithm.Reset();

                layoutCache.Validate();
            }

            if (!combinedObjCache.IsValid)
            {
                switch (direction.Value)
                {
                    case ScrollingDirection.Up:
                    case ScrollingDirection.Down:
                        scrollLength = DrawSize.Y;
                        break;

                    default:
                        scrollLength = DrawSize.X;
                        break;
                }

                foreach (var obj in Objects)
                {
                    if (!hitObjectInitialStateCache.TryGetValue(obj, out var objCache))
                        objCache = hitObjectInitialStateCache[obj] = new Cached();

                    if (objCache.IsValid)
                        continue;

                    computeLifetimeStartRecursive(obj);
                    computeInitialStateRecursive(obj);

                    objCache.Validate();
                }

                combinedObjCache.Validate();
{'fraction_non_alphanumeric': 0.07170321105684298, 'fraction_numerical': 0.0, 'mean_word_length': 1.8634335019339483, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4228773', 'n_tokens_mistral': 2338, 'n_tokens_neox': 2188, 'n_words': 577}