···41414242 host.Suspend();
43434444- completed.Reset();
4444+ // in single-threaded execution, the main thread may already be in the process of updating one last time.
4545+ int gameUpdates = 0;
4646+ game.Scheduler.AddDelayed(() => ++gameUpdates, 0, true);
4747+ Assert.That(() => gameUpdates, Is.LessThan(2).After(timeout / 10));
45484649 // check that scheduler doesn't process while suspended..
5050+ completed.Reset();
4751 game.Schedule(() => completed.Set());
4852 Assert.IsFalse(completed.Wait(timeout / 10));
49535454+ // ..and does after resume.
5055 host.Resume();
5151-5252- // ..and does after resume.
5356 Assert.IsTrue(completed.Wait(timeout / 10));
54575558 game.Exit();
5656-5759 Assert.IsTrue(task.Wait(timeout));
5860 }
5961
···242242 if (!device.IsEnabled)
243243 return false;
244244245245- // same device
246246- if (device.IsInitialized && deviceIndex == Bass.CurrentDevice)
247247- return true;
248248-249245 // initialize new device
250246 bool initSuccess = InitBass(deviceIndex);
251247 if (Bass.LastError != Errors.Already && BassUtils.CheckFaulted(false))
+3-4
osu.Framework/Audio/Sample/SampleChannelBass.cs
···159159 if (Played && Bass.ChannelIsActive(channel) == PlaybackState.Stopped)
160160 return;
161161162162- if (relativeFrequencyHandler.IsFrequencyZero)
163163- return;
162162+ playing = true;
164163165165- Bass.ChannelPlay(channel);
166166- playing = true;
164164+ if (!relativeFrequencyHandler.IsFrequencyZero)
165165+ Bass.ChannelPlay(channel);
167166 }
168167 finally
169168 {
+21-54
osu.Framework/Bindables/BindableMarginPadding.cs
···7788namespace osu.Framework.Bindables
99{
1010- public class BindableMarginPadding : Bindable<MarginPadding>
1010+ public class BindableMarginPadding : RangeConstrainedBindable<MarginPadding>
1111 {
1212- public BindableMarginPadding(MarginPadding value = default)
1313- : base(value)
1414- {
1515- MinValue = DefaultMinValue;
1616- MaxValue = DefaultMaxValue;
1717- }
1212+ protected override MarginPadding DefaultMinValue => new MarginPadding(float.MinValue);
1313+ protected override MarginPadding DefaultMaxValue => new MarginPadding(float.MaxValue);
18141919- public MarginPadding MinValue { get; set; }
2020- public MarginPadding MaxValue { get; set; }
2121-2222- protected MarginPadding DefaultMinValue => new MarginPadding(float.MinValue);
2323- protected MarginPadding DefaultMaxValue => new MarginPadding(float.MaxValue);
2424-2525- public override MarginPadding Value
1515+ public BindableMarginPadding(MarginPadding defaultValue = default)
1616+ : base(defaultValue)
2617 {
2727- get => base.Value;
2828- set => base.Value = clamp(value, MinValue, MaxValue);
2929- }
3030-3131- public override void BindTo(Bindable<MarginPadding> them)
3232- {
3333- if (them is BindableMarginPadding other)
3434- {
3535- MinValue = new MarginPadding
3636- {
3737- Top = Math.Max(MinValue.Top, other.MinValue.Top),
3838- Left = Math.Max(MinValue.Left, other.MinValue.Left),
3939- Bottom = Math.Max(MinValue.Bottom, other.MinValue.Bottom),
4040- Right = Math.Max(MinValue.Right, other.MinValue.Right)
4141- };
4242-4343- MaxValue = new MarginPadding
4444- {
4545- Top = Math.Min(MaxValue.Top, other.MaxValue.Top),
4646- Left = Math.Min(MaxValue.Left, other.MaxValue.Left),
4747- Bottom = Math.Min(MaxValue.Bottom, other.MaxValue.Bottom),
4848- Right = Math.Min(MaxValue.Right, other.MaxValue.Right)
4949- };
5050-5151- if (MinValue.Top > MaxValue.Top || MinValue.Left > MaxValue.Left || MinValue.Bottom > MaxValue.Bottom || MinValue.Right > MaxValue.Right)
5252- {
5353- throw new ArgumentOutOfRangeException(
5454- nameof(them),
5555- $"Can not weld BindableMarginPaddings with non-overlapping min/max-ranges. The ranges were [{MinValue} - {MaxValue}] and [{other.MinValue} - {other.MaxValue}]."
5656- );
5757- }
5858- }
5959-6060- base.BindTo(them);
6118 }
62196320 public override string ToString() => Value.ToString();
···8744 }
8845 }
89469090- private static MarginPadding clamp(MarginPadding value, MarginPadding minValue, MarginPadding maxValue) =>
9191- new MarginPadding
4747+ protected sealed override MarginPadding ClampValue(MarginPadding value, MarginPadding minValue, MarginPadding maxValue)
4848+ {
4949+ return new MarginPadding
9250 {
9393- Top = Math.Max(minValue.Top, Math.Min(maxValue.Top, value.Top)),
9494- Left = Math.Max(minValue.Left, Math.Min(maxValue.Left, value.Left)),
9595- Bottom = Math.Max(minValue.Bottom, Math.Min(maxValue.Bottom, value.Bottom)),
9696- Right = Math.Max(minValue.Right, Math.Min(maxValue.Right, value.Right))
5151+ Top = Math.Clamp(value.Top, minValue.Top, maxValue.Top),
5252+ Left = Math.Clamp(value.Left, minValue.Left, maxValue.Left),
5353+ Bottom = Math.Clamp(value.Bottom, minValue.Bottom, maxValue.Bottom),
5454+ Right = Math.Clamp(value.Right, minValue.Right, maxValue.Right)
9755 };
5656+ }
5757+5858+ protected sealed override bool IsValidRange(MarginPadding min, MarginPadding max)
5959+ {
6060+ return min.Top <= max.Top &&
6161+ min.Left <= max.Left &&
6262+ min.Bottom <= max.Bottom &&
6363+ min.Right <= max.Right;
6464+ }
9865 }
9966}
+14-146
osu.Framework/Bindables/BindableNumber.cs
···8899namespace osu.Framework.Bindables
1010{
1111- public class BindableNumber<T> : Bindable<T>, IBindableNumber<T>
1111+ public class BindableNumber<T> : RangeConstrainedBindable<T>, IBindableNumber<T>
1212 where T : struct, IComparable<T>, IConvertible, IEquatable<T>
1313 {
1414 public event Action<T> PrecisionChanged;
15151616- public event Action<T> MinValueChanged;
1717-1818- public event Action<T> MaxValueChanged;
1919-2016 public BindableNumber(T defaultValue = default)
2117 : base(defaultValue)
2218 {
···2925 $"{nameof(BindableNumber<T>)} only accepts the primitive numeric types (except for {typeof(decimal).FullName}) as type arguments. You provided {typeof(T).FullName}.");
3026 }
31273232- minValue = DefaultMinValue;
3333- maxValue = DefaultMaxValue;
3428 precision = DefaultPrecision;
35293636- // Re-apply the current value to apply the default min/max/precision values
3737- SetValue(Value);
3030+ // Re-apply the current value to apply the default precision value
3131+ setValue(Value);
3832 }
39334034 private T precision;
···6862 if (updateCurrentValue)
6963 {
7064 // Re-apply the current value to apply the new precision
7171- SetValue(Value);
6565+ setValue(Value);
7266 }
7367 }
74687569 public override T Value
7670 {
7771 get => base.Value;
7878- set => SetValue(value);
7272+ set => setValue(value);
7973 }
80748181- internal void SetValue(T value)
7575+ private void setValue(T value)
8276 {
8377 if (Precision.CompareTo(DefaultPrecision) > 0)
8478 {
8585- double doubleValue = clamp(value, MinValue, MaxValue).ToDouble(NumberFormatInfo.InvariantInfo);
7979+ double doubleValue = ClampValue(value, MinValue, MaxValue).ToDouble(NumberFormatInfo.InvariantInfo);
8680 doubleValue = Math.Round(doubleValue / Precision.ToDouble(NumberFormatInfo.InvariantInfo)) * Precision.ToDouble(NumberFormatInfo.InvariantInfo);
87818882 base.Value = (T)Convert.ChangeType(doubleValue, typeof(T), CultureInfo.InvariantCulture);
8983 }
9084 else
9191- base.Value = clamp(value, MinValue, MaxValue);
9292- }
9393-9494- private T minValue;
9595-9696- public T MinValue
9797- {
9898- get => minValue;
9999- set
100100- {
101101- if (minValue.Equals(value))
102102- return;
103103-104104- SetMinValue(value, true, this);
105105- }
106106- }
107107-108108- /// <summary>
109109- /// Sets the minimum value. This method does no equality comparisons.
110110- /// </summary>
111111- /// <param name="minValue">The new minimum value.</param>
112112- /// <param name="updateCurrentValue">Whether to update the current value after the minimum value is set.</param>
113113- /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param>
114114- internal void SetMinValue(T minValue, bool updateCurrentValue, BindableNumber<T> source)
115115- {
116116- this.minValue = minValue;
117117- TriggerMinValueChange(source);
118118-119119- if (updateCurrentValue)
120120- {
121121- // Re-apply the current value to apply the new minimum value
122122- SetValue(Value);
123123- }
124124- }
125125-126126- private T maxValue;
127127-128128- public T MaxValue
129129- {
130130- get => maxValue;
131131- set
132132- {
133133- if (maxValue.Equals(value))
134134- return;
135135-136136- SetMaxValue(value, true, this);
137137- }
138138- }
139139-140140- /// <summary>
141141- /// Sets the maximum value. This method does no equality comparisons.
142142- /// </summary>
143143- /// <param name="maxValue">The new maximum value.</param>
144144- /// <param name="updateCurrentValue">Whether to update the current value after the maximum value is set.</param>
145145- /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param>
146146- internal void SetMaxValue(T maxValue, bool updateCurrentValue, BindableNumber<T> source)
147147- {
148148- this.maxValue = maxValue;
149149- TriggerMaxValueChange(source);
150150-151151- if (updateCurrentValue)
152152- {
153153- // Re-apply the current value to apply the new maximum value
154154- SetValue(Value);
155155- }
8585+ base.Value = value;
15686 }
15787158158- /// <summary>
159159- /// The default <see cref="MinValue"/>. This should be equal to the minimum value of type <typeparamref name="T"/>.
160160- /// </summary>
161161- protected virtual T DefaultMinValue
8888+ protected override T DefaultMinValue
16289 {
16390 get
16491 {
···187114 }
188115 }
189116190190- /// <summary>
191191- /// The default <see cref="MaxValue"/>. This should be equal to the maximum value of type <typeparamref name="T"/>.
192192- /// </summary>
193193- protected virtual T DefaultMaxValue
117117+ protected override T DefaultMaxValue
194118 {
195119 get
196120 {
···254178 base.TriggerChange();
255179256180 TriggerPrecisionChange(this, false);
257257- TriggerMinValueChange(this, false);
258258- TriggerMaxValueChange(this, false);
259181 }
260182261183 protected void TriggerPrecisionChange(BindableNumber<T> source = null, bool propagateToBindings = true)
···278200 PrecisionChanged?.Invoke(precision);
279201 }
280202281281- protected void TriggerMinValueChange(BindableNumber<T> source = null, bool propagateToBindings = true)
282282- {
283283- // check a bound bindable hasn't changed the value again (it will fire its own event)
284284- T beforePropagation = minValue;
285285-286286- if (propagateToBindings && Bindings != null)
287287- {
288288- foreach (var b in Bindings)
289289- {
290290- if (b == source) continue;
291291-292292- if (b is BindableNumber<T> bn)
293293- bn.SetMinValue(minValue, false, this);
294294- }
295295- }
296296-297297- if (beforePropagation.Equals(minValue))
298298- MinValueChanged?.Invoke(minValue);
299299- }
300300-301301- protected void TriggerMaxValueChange(BindableNumber<T> source = null, bool propagateToBindings = true)
302302- {
303303- // check a bound bindable hasn't changed the value again (it will fire its own event)
304304- T beforePropagation = maxValue;
305305-306306- if (propagateToBindings && Bindings != null)
307307- {
308308- foreach (var b in Bindings)
309309- {
310310- if (b == source) continue;
311311-312312- if (b is BindableNumber<T> bn)
313313- bn.SetMaxValue(maxValue, false, this);
314314- }
315315- }
316316-317317- if (beforePropagation.Equals(maxValue))
318318- MaxValueChanged?.Invoke(maxValue);
319319- }
320320-321203 public override void BindTo(Bindable<T> them)
322204 {
323205 if (them is BindableNumber<T> other)
324324- {
325206 Precision = other.Precision;
326326- MinValue = other.MinValue;
327327- MaxValue = other.MaxValue;
328328-329329- if (MinValue.CompareTo(MaxValue) > 0)
330330- {
331331- throw new ArgumentOutOfRangeException(
332332- nameof(them), $"Can not weld bindable longs with non-overlapping min/max-ranges. The ranges were [{MinValue} - {MaxValue}] and [{other.MinValue} - {other.MaxValue}].");
333333- }
334334- }
335207336208 base.BindTo(them);
337209 }
338338-339339- /// <summary>
340340- /// Whether this bindable has a user-defined range that is not the full range of the <typeparamref name="T"/> type.
341341- /// </summary>
342342- public bool HasDefinedRange => !MinValue.Equals(DefaultMinValue) || !MaxValue.Equals(DefaultMaxValue);
343210344211 public bool IsInteger =>
345212 typeof(T) != typeof(float) &&
···446313 }
447314 }
448315316316+ protected sealed override T ClampValue(T value, T minValue, T maxValue) => max(minValue, min(maxValue, value));
317317+318318+ protected sealed override bool IsValidRange(T min, T max) => min.CompareTo(max) <= 0;
319319+449320 private static T max(T value1, T value2)
450321 {
451322 var comparison = value1.CompareTo(value2);
···457328 var comparison = value1.CompareTo(value2);
458329 return comparison > 0 ? value2 : value1;
459330 }
460460-461461- private static T clamp(T value, T minValue, T maxValue)
462462- => max(minValue, min(maxValue, value));
463331464332 [MethodImpl(MethodImplOptions.AggressiveInlining)]
465333 private static bool isSupportedType() =>
+18-40
osu.Framework/Bindables/BindableSize.cs
···6677namespace osu.Framework.Bindables
88{
99- public class BindableSize : Bindable<Size>
99+ /// <summary>
1010+ /// Represents a <see cref="Size"/> bindable with defined component-wise constraints applied to it.
1111+ /// </summary>
1212+ public class BindableSize : RangeConstrainedBindable<Size>
1013 {
1111- public BindableSize(Size value = default)
1212- : base(value)
1313- {
1414- MinValue = DefaultMinValue;
1515- MaxValue = DefaultMaxValue;
1616- }
1414+ protected override Size DefaultMinValue => new Size(int.MinValue, int.MinValue);
1515+ protected override Size DefaultMaxValue => new Size(int.MaxValue, int.MaxValue);
17161818- public Size MinValue { get; set; }
1919- public Size MaxValue { get; set; }
2020-2121- protected Size DefaultMinValue => new Size(int.MinValue, int.MinValue);
2222- protected Size DefaultMaxValue => new Size(int.MaxValue, int.MaxValue);
2323-2424- public override Size Value
1717+ public BindableSize(Size defaultValue = default)
1818+ : base(defaultValue)
2519 {
2626- get => base.Value;
2727- set => base.Value = clamp(value, MinValue, MaxValue);
2828- }
2929-3030- public override void BindTo(Bindable<Size> them)
3131- {
3232- if (them is BindableSize other)
3333- {
3434- MinValue = new Size(Math.Max(MinValue.Width, other.MinValue.Width), Math.Max(MinValue.Height, other.MinValue.Height));
3535- MaxValue = new Size(Math.Min(MaxValue.Width, other.MaxValue.Width), Math.Min(MaxValue.Height, other.MaxValue.Height));
3636-3737- if (MinValue.Width > MaxValue.Width || MinValue.Height > MaxValue.Height)
3838- {
3939- throw new ArgumentOutOfRangeException(
4040- nameof(them),
4141- $"Can not weld BindableSizes with non-overlapping min/max-ranges. The ranges were [{MinValue} - {MaxValue}] and [{other.MinValue} - {other.MaxValue}]."
4242- );
4343- }
4444- }
4545-4646- base.BindTo(them);
4720 }
48214922 public override string ToString() => $"{Value.Width}x{Value.Height}";
···6740 }
6841 }
69427070- private static Size clamp(Size value, Size minValue, Size maxValue) =>
7171- new Size(
7272- Math.Max(minValue.Width, Math.Min(value.Width, maxValue.Width)),
7373- Math.Max(minValue.Height, Math.Min(value.Height, maxValue.Height))
7474- );
4343+ protected sealed override Size ClampValue(Size value, Size minValue, Size maxValue)
4444+ {
4545+ return new Size
4646+ {
4747+ Width = Math.Clamp(value.Width, minValue.Width, maxValue.Width),
4848+ Height = Math.Clamp(value.Height, minValue.Height, maxValue.Height)
4949+ };
5050+ }
5151+5252+ protected sealed override bool IsValidRange(Size min, Size max) => min.Width <= max.Width && min.Height <= max.Height;
7553 }
7654}
···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;
55+using System.Collections.Generic;
66+77+namespace osu.Framework.Bindables
88+{
99+ public abstract class RangeConstrainedBindable<T> : Bindable<T>
1010+ {
1111+ public event Action<T> MinValueChanged;
1212+1313+ public event Action<T> MaxValueChanged;
1414+1515+ private T minValue;
1616+1717+ public T MinValue
1818+ {
1919+ get => minValue;
2020+ set
2121+ {
2222+ if (EqualityComparer<T>.Default.Equals(value, minValue))
2323+ return;
2424+2525+ SetMinValue(value, true, this);
2626+ }
2727+ }
2828+2929+ private T maxValue;
3030+3131+ public T MaxValue
3232+ {
3333+ get => maxValue;
3434+ set
3535+ {
3636+ if (EqualityComparer<T>.Default.Equals(value, maxValue))
3737+ return;
3838+3939+ SetMaxValue(value, true, this);
4040+ }
4141+ }
4242+4343+ public override T Value
4444+ {
4545+ get => base.Value;
4646+ set => setValue(value);
4747+ }
4848+4949+ /// <summary>
5050+ /// The default <see cref="MinValue"/>. This should be equal to the minimum value of type <typeparamref name="T"/>.
5151+ /// </summary>
5252+ protected abstract T DefaultMinValue { get; }
5353+5454+ /// <summary>
5555+ /// The default <see cref="MaxValue"/>. This should be equal to the maximum value of type <typeparamref name="T"/>.
5656+ /// </summary>
5757+ protected abstract T DefaultMaxValue { get; }
5858+5959+ /// <summary>
6060+ /// Whether this bindable has a user-defined range that is not the full range of the <typeparamref name="T"/> type.
6161+ /// </summary>
6262+ public bool HasDefinedRange => !EqualityComparer<T>.Default.Equals(MinValue, DefaultMinValue) ||
6363+ !EqualityComparer<T>.Default.Equals(MaxValue, DefaultMaxValue);
6464+6565+ protected RangeConstrainedBindable(T defaultValue = default)
6666+ : base(defaultValue)
6767+ {
6868+ minValue = DefaultMinValue;
6969+ maxValue = DefaultMaxValue;
7070+7171+ // Reapply the default value here for respecting the defined default min/max values.
7272+ setValue(defaultValue);
7373+ }
7474+7575+ /// <summary>
7676+ /// Sets the minimum value. This method does no equality comparisons.
7777+ /// </summary>
7878+ /// <param name="minValue">The new minimum value.</param>
7979+ /// <param name="updateCurrentValue">Whether to update the current value after the minimum value is set.</param>
8080+ /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param>
8181+ internal void SetMinValue(T minValue, bool updateCurrentValue, RangeConstrainedBindable<T> source)
8282+ {
8383+ this.minValue = minValue;
8484+ TriggerMinValueChange(source);
8585+8686+ if (updateCurrentValue)
8787+ {
8888+ // Reapply the current value to respect the new minimum value.
8989+ setValue(Value);
9090+ }
9191+ }
9292+9393+ /// <summary>
9494+ /// Sets the maximum value. This method does no equality comparisons.
9595+ /// </summary>
9696+ /// <param name="maxValue">The new maximum value.</param>
9797+ /// <param name="updateCurrentValue">Whether to update the current value after the maximum value is set.</param>
9898+ /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param>
9999+ internal void SetMaxValue(T maxValue, bool updateCurrentValue, RangeConstrainedBindable<T> source)
100100+ {
101101+ this.maxValue = maxValue;
102102+ TriggerMaxValueChange(source);
103103+104104+ if (updateCurrentValue)
105105+ {
106106+ // Reapply the current value to respect the new maximum value.
107107+ setValue(Value);
108108+ }
109109+ }
110110+111111+ public override void TriggerChange()
112112+ {
113113+ base.TriggerChange();
114114+115115+ TriggerMinValueChange(this, false);
116116+ TriggerMaxValueChange(this, false);
117117+ }
118118+119119+ protected void TriggerMinValueChange(RangeConstrainedBindable<T> source = null, bool propagateToBindings = true)
120120+ {
121121+ // check a bound bindable hasn't changed the value again (it will fire its own event)
122122+ T beforePropagation = minValue;
123123+124124+ if (propagateToBindings && Bindings != null)
125125+ {
126126+ foreach (var b in Bindings)
127127+ {
128128+ if (b == source) continue;
129129+130130+ if (b is RangeConstrainedBindable<T> cb)
131131+ cb.SetMinValue(minValue, false, this);
132132+ }
133133+ }
134134+135135+ if (EqualityComparer<T>.Default.Equals(beforePropagation, minValue))
136136+ MinValueChanged?.Invoke(minValue);
137137+ }
138138+139139+ protected void TriggerMaxValueChange(RangeConstrainedBindable<T> source = null, bool propagateToBindings = true)
140140+ {
141141+ // check a bound bindable hasn't changed the value again (it will fire its own event)
142142+ T beforePropagation = maxValue;
143143+144144+ if (propagateToBindings && Bindings != null)
145145+ {
146146+ foreach (var b in Bindings)
147147+ {
148148+ if (b == source) continue;
149149+150150+ if (b is RangeConstrainedBindable<T> cb)
151151+ cb.SetMaxValue(maxValue, false, this);
152152+ }
153153+ }
154154+155155+ if (EqualityComparer<T>.Default.Equals(beforePropagation, maxValue))
156156+ MaxValueChanged?.Invoke(maxValue);
157157+ }
158158+159159+ public override void BindTo(Bindable<T> them)
160160+ {
161161+ if (them is RangeConstrainedBindable<T> other)
162162+ {
163163+ if (!IsValidRange(other.MinValue, other.MaxValue))
164164+ {
165165+ throw new ArgumentOutOfRangeException(
166166+ nameof(them), $"The target bindable has specified an invalid range of [{other.MinValue} - {other.MaxValue}].");
167167+ }
168168+169169+ MinValue = other.MinValue;
170170+ MaxValue = other.MaxValue;
171171+ }
172172+173173+ base.BindTo(them);
174174+ }
175175+176176+ public new RangeConstrainedBindable<T> GetBoundCopy() => (RangeConstrainedBindable<T>)base.GetBoundCopy();
177177+178178+ public new RangeConstrainedBindable<T> GetUnboundCopy() => (RangeConstrainedBindable<T>)base.GetUnboundCopy();
179179+180180+ /// <summary>
181181+ /// Clamps <paramref name="value"/> to the range defined by <paramref name="minValue"/> and <paramref name="maxValue"/>.
182182+ /// </summary>
183183+ protected abstract T ClampValue(T value, T minValue, T maxValue);
184184+185185+ /// <summary>
186186+ /// Whether <paramref name="min"/> and <paramref name="max"/> constitute a valid range
187187+ /// (usually used to check that <paramref name="min"/> is indeed lesser than or equal to <paramref name="max"/>).
188188+ /// </summary>
189189+ /// <param name="min">The range's minimum value.</param>
190190+ /// <param name="max">The range's maximum value.</param>
191191+ protected abstract bool IsValidRange(T min, T max);
192192+193193+ private void setValue(T value) => base.Value = ClampValue(value, minValue, maxValue);
194194+ }
195195+}
···1616 /// Has the ability to delay the loading until it has been visible on-screen for a specified duration.
1717 /// In order to benefit from delayed load, we must be inside a <see cref="ScrollContainer{T}"/>.
1818 /// </summary>
1919- /// <remarks>
2020- /// <see cref="LifetimeStart"/> and <see cref="LifetimeEnd"/> are propagated from the wrapper to the content on content load.
2121- /// After load, the content's lifetime is preferred, meaning any changes to content's lifetime post-load will be respected.
2222- /// </remarks>
2319 public class DelayedLoadWrapper : CompositeDrawable
2420 {
2521 [Resolved]
···58545955 AddLayout(optimisingContainerCache);
6056 AddLayout(isIntersectingCache);
6161- }
6262-6363- private double lifetimeStart = double.MinValue;
6464-6565- public override double LifetimeStart
6666- {
6767- get => Content?.LifetimeStart ?? lifetimeStart;
6868- set
6969- {
7070- if (Content != null)
7171- Content.LifetimeStart = value;
7272- lifetimeStart = value;
7373- }
7474- }
7575-7676- private double lifetimeEnd = double.MaxValue;
7777-7878- public override double LifetimeEnd
7979- {
8080- get => Content?.LifetimeEnd ?? lifetimeEnd;
8181- set
8282- {
8383- if (Content != null)
8484- Content.LifetimeEnd = value;
8585- lifetimeEnd = value;
8686- }
8757 }
88588959 private Drawable content;
···160130 // This code is running on the game's scheduler, while this DLW may have been async disposed, so the addition is scheduled locally to prevent adding to disposed DLWs.
161131 scheduledAddition = Schedule(() =>
162132 {
163163- content.LifetimeStart = lifetimeStart;
164164- content.LifetimeEnd = lifetimeEnd;
165165-166133 AddInternal(content);
167134168135 DelayedLoadCompleted = true;
···583583584584 ExecutionState = ExecutionState.Running;
585585586586- initialiseInputHandlers();
586586+ populateInputHandlers();
587587588588 SetupConfig(game.GetFrameworkConfigDefaults() ?? new Dictionary<FrameworkSetting, object>());
589589+590590+ initialiseInputHandlers();
589591590592 if (Window != null)
591593 {
···704706 Logger.Storage = Storage.GetStorageForDirectory("logs");
705707 }
706708707707- private void initialiseInputHandlers()
709709+ private void populateInputHandlers()
708710 {
709711 AvailableInputHandlers = CreateAvailableInputHandlers().ToImmutableArray();
712712+ }
710713714714+ private void initialiseInputHandlers()
715715+ {
711716 foreach (var handler in AvailableInputHandlers)
712717 {
713713- (handler as IHasCursorSensitivity)?.Sensitivity.BindTo(cursorSensitivity);
714714-715718 if (!handler.Initialize(this))
716719 handler.Enabled.Value = false;
717720 }
···850853 };
851854852855#pragma warning disable 618
856856+ // pragma region can be removed 20210911
853857 ignoredInputHandlers = Config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers);
854858 ignoredInputHandlers.ValueChanged += e =>
855859 {
···864868865869 Config.BindWith(FrameworkSetting.CursorSensitivity, cursorSensitivity);
866870867867- // one way binding to preserve compatibility.
871871+ var cursorSensitivityHandlers = AvailableInputHandlers.OfType<IHasCursorSensitivity>();
872872+873873+ // one way bindings to preserve compatibility.
868874 cursorSensitivity.BindValueChanged(val =>
869875 {
870870- foreach (var h in AvailableInputHandlers.OfType<IHasCursorSensitivity>())
876876+ foreach (var h in cursorSensitivityHandlers)
871877 h.Sensitivity.Value = val.NewValue;
872878 }, true);
879879+880880+ foreach (var h in cursorSensitivityHandlers)
881881+ h.Sensitivity.BindValueChanged(s => cursorSensitivity.Value = s.NewValue);
873882#pragma warning restore 618
874883875884 PerformanceLogging.BindValueChanged(logging =>
···889898 CultureInfo.DefaultThreadCurrentCulture = culture;
890899 CultureInfo.DefaultThreadCurrentUICulture = culture;
891900892892- foreach (var t in Threads)
893893- {
894894- t.Scheduler.Add(() => { t.CurrentCulture = culture; });
895895- }
901901+ threadRunner.SetCulture(culture);
896902 }, true);
897903898904 // intentionally done after everything above to ensure the new configuration location has priority over obsoleted values.
+6
osu.Framework/Platform/HeadlessGameHost.cs
···3939 defaultOverrides[FrameworkSetting.AudioDevice] = "No sound";
40404141 base.SetupConfig(defaultOverrides);
4242+4343+ if (Enum.TryParse<ExecutionMode>(Environment.GetEnvironmentVariable("OSU_EXECUTION_MODE"), out var mode))
4444+ {
4545+ Config.SetValue(FrameworkSetting.ExecutionMode, mode);
4646+ Logger.Log($"Startup execution mode set to {mode} from envvar");
4747+ }
4248 }
43494450 protected override void SetupForRun()
+20
osu.Framework/Platform/ThreadRunner.cs
···77using System;
88using System.Collections.Generic;
99using System.Diagnostics;
1010+using System.Globalization;
1011using System.Linq;
1212+using System.Threading;
1113using osu.Framework.Development;
1214using osu.Framework.Extensions.IEnumerableExtensions;
1315using osu.Framework.Logging;
···161163162164 // if null, we have not yet got an execution mode, so set this early to allow usage in GameThread.Initialize overrides.
163165 activeExecutionMode ??= ThreadSafety.ExecutionMode = ExecutionMode;
166166+ Logger.Log($"Execution mode changed to {activeExecutionMode}");
164167165168 pauseAllThreads();
166169···217220 {
218221 mainThread.ActiveHz = GameThread.DEFAULT_ACTIVE_HZ;
219222 mainThread.InactiveHz = GameThread.DEFAULT_INACTIVE_HZ;
223223+ }
224224+ }
225225+226226+ /// <summary>
227227+ /// Sets the current culture of all threads to the supplied <paramref name="culture"/>.
228228+ /// </summary>
229229+ public void SetCulture(CultureInfo culture)
230230+ {
231231+ // for single-threaded mode, switch the current (assumed to be main) thread's culture, since it's actually the one that's running the frames.
232232+ Thread.CurrentThread.CurrentCulture = culture;
233233+234234+ // for multi-threaded mode, schedule the culture change on all threads.
235235+ // note that if the threads haven't been created yet (e.g. if the game started single-threaded), this will only store the culture in GameThread.CurrentCulture.
236236+ // in that case, the stored value will be set on the actual threads after the next Start() call.
237237+ foreach (var t in Threads)
238238+ {
239239+ t.Scheduler.Add(() => t.CurrentCulture = culture);
220240 }
221241 }
222242 }