···1212 /// </summary>
1313 public class AudioAdjustments : IAdjustableAudioComponent
1414 {
1515+ private static readonly AdjustableProperty[] all_adjustments = (AdjustableProperty[])Enum.GetValues(typeof(AdjustableProperty));
1616+1517 /// <summary>
1618 /// The volume of this component.
1719 /// </summary>
···59616062 public AudioAdjustments()
6163 {
6262- foreach (AdjustableProperty type in Enum.GetValues(typeof(AdjustableProperty)))
6464+ foreach (AdjustableProperty type in all_adjustments)
6365 {
6466 var aggregate = getAggregate(type) = new AggregateBindable<double>(getAggregateFunction(type), getProperty(type).GetUnboundCopy());
6567 aggregate.AddSource(getProperty(type));
···74767577 public void BindAdjustments(IAggregateAudioAdjustment component)
7678 {
7777- foreach (AdjustableProperty type in Enum.GetValues(typeof(AdjustableProperty)))
7979+ foreach (AdjustableProperty type in all_adjustments)
7880 getAggregate(type).AddSource(component.GetAggregate(type));
7981 }
80828183 public void UnbindAdjustments(IAggregateAudioAdjustment component)
8284 {
8383- foreach (AdjustableProperty type in Enum.GetValues(typeof(AdjustableProperty)))
8585+ foreach (AdjustableProperty type in all_adjustments)
8486 getAggregate(type).RemoveSource(component.GetAggregate(type));
8587 }
8688
+3-1
osu.Framework/Audio/BassAmplitudeProcessor.cs
···29293030 private float[]? frequencyData;
31313232+ private readonly float[] channelLevels = new float[2];
3333+3234 public void Update()
3335 {
3436 if (channel.Handle == 0)
···36383739 bool active = channel.Mixer.ChannelIsActive(channel) == PlaybackState.Playing;
38403939- float[] channelLevels = new float[2];
4041 channel.Mixer.ChannelGetLevel(channel, channelLevels, 1 / 60f, LevelRetrievalFlags.Stereo);
4242+4143 var leftChannel = active ? channelLevels[0] : -1;
4244 var rightChannel = active ? channelLevels[1] : -1;
4345
+2-1
osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs
···1212using ManagedBass;
1313using ManagedBass.Mix;
1414using osu.Framework.Bindables;
1515+using osu.Framework.Extensions.EnumExtensions;
1516using osu.Framework.Extensions.ObjectExtensions;
1617using osu.Framework.Statistics;
1718···140141141142 // The channel is always in a playing state unless stopped or stalled as it's a decoding channel. Retrieve the true playing state from the mixer channel.
142143 if (state == PlaybackState.Playing)
143143- state = BassMix.ChannelHasFlag(channel.Handle, BassFlags.MixerChanPause) ? PlaybackState.Paused : state;
144144+ state = BassMix.ChannelFlags(channel.Handle, BassFlags.Default, BassFlags.Default).HasFlagFast(BassFlags.MixerChanPause) ? PlaybackState.Paused : state;
144145145146 return state;
146147 }
+2-3
osu.Framework/Bindables/Bindable.cs
···77using System.Linq;
88using JetBrains.Annotations;
99using Newtonsoft.Json;
1010-using osu.Framework.Caching;
1110using osu.Framework.Extensions.TypeExtensions;
1211using osu.Framework.IO.Serialization;
1312using osu.Framework.Lists;
···129128 TriggerDefaultChange(previousValue, source ?? this, true, bypassChecks);
130129 }
131130132132- private readonly Cached<WeakReference<Bindable<T>>> weakReferenceCache = new Cached<WeakReference<Bindable<T>>>();
131131+ private WeakReference<Bindable<T>> weakReferenceInstance;
133132134134- private WeakReference<Bindable<T>> weakReference => weakReferenceCache.IsValid ? weakReferenceCache.Value : weakReferenceCache.Value = new WeakReference<Bindable<T>>(this);
133133+ private WeakReference<Bindable<T>> weakReference => weakReferenceInstance ??= new WeakReference<Bindable<T>>(this);
135134136135 /// <summary>
137136 /// Creates a new bindable instance. This is used for deserialization of bindables.
···4545 }
46464747 /// <summary>
4848- /// Hide this container by setting its visibility to <see cref="Visibility.Visible"/>.
4848+ /// Show this container by setting its visibility to <see cref="Visibility.Visible"/>.
4949 /// </summary>
5050 public override void Show() => State.Value = Visibility.Visible;
5151
+5-5
osu.Framework/Graphics/DrawNode.cs
···11// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22// See the LICENCE file in the repository root for full licence text.
3344-using osu.Framework.Graphics.OpenGL;
54using System;
65using System.Runtime.CompilerServices;
66+using System.Threading;
77using osu.Framework.Graphics.Batches;
88using osu.Framework.Graphics.Colour;
99+using osu.Framework.Graphics.OpenGL;
910using osu.Framework.Graphics.OpenGL.Buffers;
1011using osu.Framework.Graphics.OpenGL.Textures;
1112using osu.Framework.Graphics.OpenGL.Vertices;
1213using osu.Framework.Graphics.Primitives;
1314using osu.Framework.Graphics.Textures;
1414-using osu.Framework.Threading;
1515using osu.Framework.Utils;
1616using osuTK;
1717···4545 /// </summary>
4646 protected IDrawable Source { get; private set; }
47474848- private readonly AtomicCounter referenceCount = new AtomicCounter();
4848+ private long referenceCount;
49495050 /// <summary>
5151 /// The depth at which drawing should take place.
···282282 /// <remarks>
283283 /// All <see cref="DrawNode"/>s start with a reference count of 1.
284284 /// </remarks>
285285- internal void Reference() => referenceCount.Increment();
285285+ internal void Reference() => Interlocked.Increment(ref referenceCount);
286286287287 protected internal bool IsDisposed { get; private set; }
288288289289 public void Dispose()
290290 {
291291- if (referenceCount.Decrement() != 0)
291291+ if (Interlocked.Decrement(ref referenceCount) != 0)
292292 return;
293293294294 GLWrapper.ScheduleDisposal(() => Dispose(true));
+2-2
osu.Framework/Graphics/Drawable.cs
···151151 {
152152 a(target);
153153 }
154154- catch
154154+ catch (Exception e)
155155 {
156156- // Execution should continue regardless of whether an unbind failed
156156+ Logger.Error(e, $"Failed to unbind a local bindable in {type.ReadableName()}");
157157 }
158158 }
159159 };
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22+// See the LICENCE file in the repository root for full licence text.
33+44+namespace osu.Framework.Graphics.Transforms
55+{
66+ public interface ITransformSequence
77+ {
88+ internal void TransformAborted();
99+ internal void TransformCompleted();
1010+ }
1111+}
···1515 /// <typeparam name="T">
1616 /// The type of the <see cref="ITransformable"/> the <see cref="Transform"/>s in this sequence operate upon.
1717 /// </typeparam>
1818- public class TransformSequence<T> where T : class, ITransformable
1818+ public class TransformSequence<T> : ITransformSequence where T : class, ITransformable
1919 {
2020 /// <summary>
2121 /// A delegate that generates a new <see cref="TransformSequence{T}"/> on a given <paramref name="origin"/>.
···5757 // As soon as we have an infinitely looping transform,
5858 // completion no longer makes sense.
5959 if (last != null)
6060- last.OnComplete = null;
6060+ last.CompletionTargetSequence = null;
61616262 last = null;
6363 lastEndTime = double.PositiveInfinity;
···81818282 transforms.Add(transform);
83838484- transform.OnComplete = null;
8585- transform.OnAbort = onTransformAborted;
8484+ transform.CompletionTargetSequence = this;
8585+ transform.AbortTargetSequence = this;
86868787 if (transform.IsLooping)
8888 onLoopingTransform();
···9191 if (last == null || transform.EndTime > lastEndTime)
9292 {
9393 if (last != null)
9494- last.OnComplete = null;
9494+ last.CompletionTargetSequence = null;
95959696 last = transform;
9797- last.OnComplete = onTransformsComplete;
9797+ last.CompletionTargetSequence = this;
9898 lastEndTime = last.EndTime;
9999 hasCompleted = false;
100100 }
···175175 return this;
176176 }
177177178178- private void onTransformAborted()
179179- {
180180- if (transforms.Count == 0)
181181- return;
182182-183183- // No need for OnAbort events to trigger anymore, since
184184- // we are already aware of the abortion.
185185- foreach (var t in transforms)
186186- {
187187- t.OnAbort = null;
188188- t.OnComplete = null;
189189-190190- if (!t.HasStartValue)
191191- t.TargetTransformable.RemoveTransform(t);
192192- }
193193-194194- transforms.Clear();
195195- last = null;
196196-197197- onAbort?.Invoke();
198198- }
199199-200200- private void onTransformsComplete()
201201- {
202202- hasCompleted = true;
203203- onComplete?.Invoke();
204204- }
205205-206178 private void subscribeComplete(Action func)
207179 {
208180 if (onComplete != null)
···342314343315 foreach (var t in transforms)
344316 {
345345- Action tmpOnAbort = t.OnAbort;
346346- t.OnAbort = null;
317317+ var tmpOnAbort = t.AbortTargetSequence;
318318+ t.AbortTargetSequence = null;
347319 t.TargetTransformable.RemoveTransform(t);
348348- t.OnAbort = tmpOnAbort;
320320+ t.AbortTargetSequence = tmpOnAbort;
349321350322 // Update start and end times such that no transformations need to be instantly
351323 // looped right after they're added. This is required so that transforms can be
···457429 if (hasEnd)
458430 OnComplete(function);
459431 OnAbort(function);
432432+ }
433433+434434+ void ITransformSequence.TransformAborted()
435435+ {
436436+ if (transforms.Count == 0)
437437+ return;
438438+439439+ // No need for OnAbort events to trigger anymore, since
440440+ // we are already aware of the abortion.
441441+ foreach (var t in transforms)
442442+ {
443443+ t.AbortTargetSequence = null;
444444+ t.CompletionTargetSequence = null;
445445+446446+ if (!t.HasStartValue)
447447+ t.TargetTransformable.RemoveTransform(t);
448448+ }
449449+450450+ transforms.Clear();
451451+ last = null;
452452+453453+ onAbort?.Invoke();
454454+ }
455455+456456+ void ITransformSequence.TransformCompleted()
457457+ {
458458+ hasCompleted = true;
459459+ onComplete?.Invoke();
460460 }
461461 }
462462}
···6161 //expiry should happen either at the end of the last transform or using the current sequence delay (whichever is highest).
6262 double max = TransformStartTime;
63636464- foreach (Transform t in Transforms)
6464+ foreach (var tracker in targetGroupingTrackers)
6565 {
6666- if (t.EndTime > max)
6767- max = t.EndTime + 1; //adding 1ms here ensures we can expire on the current frame without issue.
6666+ for (int i = 0; i < tracker.Transforms.Count; i++)
6767+ {
6868+ var t = tracker.Transforms[i];
6969+ if (t.EndTime > max)
7070+ max = t.EndTime + 1; //adding 1ms here ensures we can expire on the current frame without issue.
7171+ }
6872 }
69737074 return max;
···142146143147 getTrackerForGrouping(toRemove.TargetGrouping, false)?.RemoveTransform(toRemove);
144148145145- toRemove.OnAbort?.Invoke();
149149+ toRemove.TriggerAbort();
146150 }
147151148152 /// <summary>
···294298 return createAbsoluteSequenceAction(newTransformStartTime);
295299 }
296300297297- internal virtual void CollectAbsoluteSequenceActionsFromSubTree(double newTransformStartTime, List<IDisposable> actions)
301301+ internal virtual void CollectAbsoluteSequenceActionsFromSubTree(double newTransformStartTime, List<AbsoluteSequenceSender> actions)
298302 {
299303 actions.Add(createAbsoluteSequenceAction(newTransformStartTime));
300304 }
301305302302- private IDisposable createAbsoluteSequenceAction(double newTransformStartTime)
306306+ private AbsoluteSequenceSender createAbsoluteSequenceAction(double newTransformStartTime)
303307 {
304308 double oldTransformDelay = TransformDelay;
305309 double newTransformDelay = TransformDelay = newTransformStartTime - (Clock?.CurrentTime ?? 0);
306310307307- return new ValueInvokeOnDisposal<AbsoluteSequenceSender>(new AbsoluteSequenceSender(this, oldTransformDelay, newTransformDelay), sender =>
308308- {
309309- if (!Precision.AlmostEquals(sender.NewTransformDelay, sender.Transformable.TransformDelay))
310310- {
311311- throw new InvalidOperationException(
312312- $"{nameof(sender.Transformable.TransformStartTime)} at the end of absolute sequence is not the same as at the beginning, but should be. " +
313313- $"(begin={sender.NewTransformDelay} end={sender.Transformable.TransformDelay})");
314314- }
315315-316316- sender.Transformable.TransformDelay = sender.OldTransformDelay;
317317- });
311311+ return new AbsoluteSequenceSender(this, oldTransformDelay, newTransformDelay);
318312 }
319313320314 /// An ad-hoc struct used as a closure environment in <see cref="BeginAbsoluteSequence" />.
321321- private readonly struct AbsoluteSequenceSender
315315+ internal readonly struct AbsoluteSequenceSender : IDisposable
322316 {
323323- public readonly Transformable Transformable;
317317+ public readonly Transformable Sender;
318318+324319 public readonly double OldTransformDelay;
325320 public readonly double NewTransformDelay;
326321327327- public AbsoluteSequenceSender(Transformable transformable, double oldTransformDelay, double newTransformDelay)
322322+ public AbsoluteSequenceSender(Transformable sender, double oldTransformDelay, double newTransformDelay)
328323 {
329329- Transformable = transformable;
330324 OldTransformDelay = oldTransformDelay;
331325 NewTransformDelay = newTransformDelay;
326326+327327+ Sender = sender;
328328+ }
329329+330330+ public void Dispose()
331331+ {
332332+ if (!Precision.AlmostEquals(NewTransformDelay, Sender.TransformDelay))
333333+ {
334334+ throw new InvalidOperationException(
335335+ $"{nameof(Sender.TransformStartTime)} at the end of absolute sequence is not the same as at the beginning, but should be. " +
336336+ $"(begin={NewTransformDelay} end={Sender.TransformDelay})");
337337+ }
338338+339339+ Sender.TransformDelay = OldTransformDelay;
332340 }
333341 }
334342···364372 }
365373366374 transform.Apply(transform.EndTime);
367367- transform.OnComplete?.Invoke();
375375+ transform.TriggerComplete();
376376+368377 return;
369378 }
370379
+1-9
osu.Framework/Lists/SortedList.cs
···94949595 public virtual void RemoveAt(int index) => list.RemoveAt(index);
96969797- public int RemoveAll(Predicate<T> match)
9898- {
9999- List<T> found = (List<T>)FindAll(match);
100100-101101- foreach (var i in found)
102102- Remove(i);
103103-104104- return found.Count;
105105- }
9797+ public int RemoveAll(Predicate<T> match) => list.RemoveAll(match);
1069810799 public virtual void Clear() => list.Clear();
108100
-26
osu.Framework/Threading/AtomicCounter.cs
···11-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22-// See the LICENCE file in the repository root for full licence text.
33-44-using System.Threading;
55-66-namespace osu.Framework.Threading
77-{
88- public class AtomicCounter
99- {
1010- private long count;
1111-1212- public long Increment() => Interlocked.Increment(ref count);
1313-1414- public long Decrement() => Interlocked.Decrement(ref count);
1515-1616- public long Add(long value) => Interlocked.Add(ref count, value);
1717-1818- public long Reset() => Interlocked.Exchange(ref count, 0);
1919-2020- public long Value
2121- {
2222- set => Interlocked.Exchange(ref count, value);
2323- get => Interlocked.Read(ref count);
2424- }
2525- }
2626-}