From cd50c426082a01ab7de4a0d7e32a487283a1865c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 28 May 2023 00:20:33 +0800 Subject: [PATCH 1/4] Game side bindable nullablity for easy cases --- osu.Desktop/DiscordRichPresence.cs | 2 +- osu.Game/Graphics/UserInterface/StatefulMenuItem.cs | 11 +++++++---- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- osu.Game/Overlays/Chat/ChatTextBar.cs | 4 ++-- .../Settings/Sections/Graphics/LayoutSettings.cs | 2 +- .../Screens/Edit/Components/BottomBarContainer.cs | 2 +- osu.Game/Screens/Edit/Timing/GroupSection.cs | 2 +- osu.Game/Screens/Edit/Timing/Section.cs | 4 +++- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 2 +- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 3 ++- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- osu.Game/Skinning/SkinnableSprite.cs | 2 +- 12 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index fe3e08537e62..299950071751 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -35,7 +35,7 @@ internal partial class DiscordRichPresence : Component private IAPIProvider api { get; set; } = null!; private readonly IBindable status = new Bindable(); - private readonly IBindable activity = new Bindable(); + private readonly IBindable activity = new Bindable(); private readonly Bindable privacyMode = new Bindable(); diff --git a/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs index 85efd75a60f3..9111cfb3aada 100644 --- a/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -16,7 +18,7 @@ public abstract class StatefulMenuItem : OsuMenuItem /// /// The current state that should be displayed. /// - public readonly Bindable State = new Bindable(); + public readonly Bindable State = new Bindable(); /// /// Creates a new . @@ -24,7 +26,7 @@ public abstract class StatefulMenuItem : OsuMenuItem /// The text to display. /// A function that mutates a state to another state after this is pressed. /// The type of action which this performs. - protected StatefulMenuItem(LocalisableString text, Func changeStateFunc, MenuItemType type = MenuItemType.Standard) + protected StatefulMenuItem(LocalisableString text, Func changeStateFunc, MenuItemType type = MenuItemType.Standard) : this(text, changeStateFunc, type, null) { } @@ -36,7 +38,7 @@ protected StatefulMenuItem(LocalisableString text, Func changeSt /// A function that mutates a state to another state after this is pressed. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - protected StatefulMenuItem(LocalisableString text, Func? changeStateFunc, MenuItemType type, Action? action) + protected StatefulMenuItem(LocalisableString text, Func? changeStateFunc, MenuItemType type, Action? action) : base(text, type) { Action.Value = () => @@ -81,13 +83,14 @@ protected StatefulMenuItem(LocalisableString text, Func? changeStateFunc, /// The type of action which this performs. /// A delegate to be invoked when this is pressed. protected StatefulMenuItem(LocalisableString text, Func? changeStateFunc, MenuItemType type, Action? action) - : base(text, o => changeStateFunc?.Invoke((T)o) ?? o, type, o => action?.Invoke((T)o)) + : base(text, o => changeStateFunc?.Invoke((T)o.AsNonNull()) ?? o, type, o => action?.Invoke((T)o.AsNonNull())) { base.State.BindValueChanged(state => { if (state.NewValue == null) base.State.Value = default(T); + Debug.Assert(base.State.Value != null); State.Value = (T)base.State.Value; }, true); diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 46449fea7307..05929b345a45 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -104,7 +104,7 @@ private void initialiseUserStatistics(GetUsersResponse response) => Schedule(() private void userScoreProcessed(int userId, long scoreId) { - if (userId != api.LocalUser.Value?.OnlineID) + if (userId != api.LocalUser.Value.OnlineID) return; lastProcessedScoreId = scoreId; diff --git a/osu.Game/Overlays/Chat/ChatTextBar.cs b/osu.Game/Overlays/Chat/ChatTextBar.cs index fd5e0e983649..c81c95c75190 100644 --- a/osu.Game/Overlays/Chat/ChatTextBar.cs +++ b/osu.Game/Overlays/Chat/ChatTextBar.cs @@ -30,7 +30,7 @@ public partial class ChatTextBar : Container public void TextBoxKillFocus() => chatTextBox.KillFocus(); [Resolved] - private Bindable currentChannel { get; set; } = null!; + private Bindable currentChannel { get; set; } = null!; private Container chattingTextContainer = null!; private OsuSpriteText chattingText = null!; @@ -136,7 +136,7 @@ protected override void LoadComplete() currentChannel.BindValueChanged(change => { - Channel newChannel = change.NewValue; + Channel? newChannel = change.NewValue; switch (newChannel?.Type) { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index a3290bc81c2a..9956ee3d9178 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -32,7 +32,7 @@ public partial class LayoutSettings : SettingsSubsection private FillFlowContainer> scalingSettings = null!; private SettingsSlider dimSlider = null!; - private readonly Bindable currentDisplay = new Bindable(); + private readonly Bindable currentDisplay = new Bindable(); private Bindable scalingMode = null!; private Bindable sizeFullscreen = null!; diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index 0ba1ab925840..8f6896692f4d 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs @@ -18,7 +18,7 @@ public partial class BottomBarContainer : Container protected readonly IBindable Beatmap = new Bindable(); - protected readonly IBindable Track = new Bindable(); + protected readonly IBindable Track = new Bindable(); protected readonly Drawable Background; private readonly Container content; diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 487a871881e7..4fa8567283eb 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -20,7 +20,7 @@ internal partial class GroupSection : CompositeDrawable private OsuButton button = null!; [Resolved] - protected Bindable SelectedGroup { get; private set; } = null!; + protected Bindable SelectedGroup { get; private set; } = null!; [Resolved] protected EditorBeatmap Beatmap { get; private set; } = null!; diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index ba3874dcee91..6e48f8655856 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -30,7 +31,7 @@ internal abstract partial class Section : CompositeDrawable protected EditorBeatmap Beatmap { get; private set; } = null!; [Resolved] - protected Bindable SelectedGroup { get; private set; } = null!; + protected Bindable SelectedGroup { get; private set; } = null!; [Resolved] protected IEditorChangeHandler? ChangeHandler { get; private set; } @@ -109,6 +110,7 @@ protected override void LoadComplete() { if (ControlPoint.Value != null) { + Debug.Assert(SelectedGroup.Value != null); SelectedGroup.Value.Remove(ControlPoint.Value); ControlPoint.Value = null; } diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index bb7a3b8be3bc..39dc8db87667 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -27,7 +27,7 @@ public partial class TapTimingControl : CompositeDrawable private EditorBeatmap beatmap { get; set; } = null!; [Resolved] - private Bindable selectedGroup { get; set; } = null!; + private Bindable selectedGroup { get; set; } = null!; private readonly BindableBool isHandlingTapping = new BindableBool(); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 2b40b9faf89d..dd2d5892e73f 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -71,7 +71,7 @@ public bool FilterMods private ScoreManager scoreManager { get; set; } = null!; [Resolved] - private IBindable ruleset { get; set; } = null!; + private IBindable ruleset { get; set; } = null!; [Resolved] private IBindable> mods { get; set; } = null!; @@ -189,6 +189,7 @@ private void subscribeToLocalScores(BeatmapInfo beatmapInfo, CancellationToken c scoreSubscription?.Dispose(); scoreSubscription = null; + Debug.Assert(ruleset.Value != null); scoreSubscription = realm.RegisterForNotifications(r => r.All().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0" + $" AND {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}" diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4d6a5398c5aa..3f8baa348d47 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -127,7 +127,7 @@ public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBind private FooterButtonOptions beatmapOptionsButton = null!; - private readonly Bindable decoupledRuleset = new Bindable(); + private readonly Bindable decoupledRuleset = new Bindable(); private double audioFeedbackLastPlaybackTime; @@ -372,7 +372,7 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl dependencies.CacheAs(this); dependencies.CacheAs(decoupledRuleset); - dependencies.CacheAs>(decoupledRuleset); + dependencies.CacheAs>(decoupledRuleset); return dependencies; } diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 1d975664706a..164f9e605dde 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -48,7 +48,7 @@ public SkinnableSprite() SpriteName.BindValueChanged(name => { - ((SpriteComponentLookup)ComponentLookup).LookupName = name.NewValue ?? string.Empty; + ((SpriteComponentLookup)ComponentLookup).LookupName = name.NewValue; if (IsLoaded) SkinChanged(CurrentSkin); }); From cb0a7d9bc18a402f9d6707970c073bd824afcdf0 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 28 May 2023 00:29:58 +0800 Subject: [PATCH 2/4] Pass time to Section.CreatePoint --- osu.Game/Screens/Edit/Timing/EffectSection.cs | 4 ++-- osu.Game/Screens/Edit/Timing/Section.cs | 4 ++-- osu.Game/Screens/Edit/Timing/TimingSection.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index 7e484433f733..c4742fe60082 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -63,9 +63,9 @@ protected override void OnControlPointChanged(ValueChangedEvent point); - protected abstract T CreatePoint(); + protected abstract T CreatePointFrom(double time); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 2757753b0729..ad99b5d41bf2 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -60,9 +60,9 @@ protected override void OnControlPointChanged(ValueChangedEvent Date: Sun, 28 May 2023 01:00:45 +0800 Subject: [PATCH 3/4] Remaining cases of bindable nullability --- .../Skinning/Legacy/LegacyTaikoScroller.cs | 2 +- .../Settings/Sections/Graphics/LayoutSettings.cs | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs index 2964473f8903..030ec9190e48 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public partial class LegacyTaikoScroller : CompositeDrawable { - public Bindable LastResult = new Bindable(); + public Bindable LastResult = new Bindable(); public LegacyTaikoScroller() { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 9956ee3d9178..ccb525121c93 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Linq; using osu.Framework; @@ -32,7 +33,7 @@ public partial class LayoutSettings : SettingsSubsection private FillFlowContainer> scalingSettings = null!; private SettingsSlider dimSlider = null!; - private readonly Bindable currentDisplay = new Bindable(); + private readonly Bindable currentDisplay = new Bindable(); private Bindable scalingMode = null!; private Bindable sizeFullscreen = null!; @@ -193,11 +194,7 @@ protected override void LoadComplete() currentDisplay.BindValueChanged(display => Schedule(() => { - if (display.NewValue == null) - { - resolutions.Clear(); - return; - } + Debug.Assert(display.NewValue != null); resolutions.ReplaceRange(1, resolutions.Count - 1, display.NewValue.DisplayModes .Where(m => m.Size.Width >= 800 && m.Size.Height >= 600) From 938428d2641705fbb2f7cba5861c2b5d36a49907 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 27 Nov 2024 21:37:25 +0800 Subject: [PATCH 4/4] Resolve new nullability warnings --- osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs | 4 ++-- osu.Game/Graphics/UserInterface/StatefulMenuItem.cs | 4 ++-- osu.Game/Screens/Edit/Setup/ColoursSection.cs | 2 +- osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs | 5 +++++ osu.Game/Screens/Select/SongSelect.cs | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index 4206f77c9865..f917203b6b0f 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -37,7 +37,7 @@ protected override bool OnMouseDown(MouseDownEvent e) private partial class ToggleTextContainer : TextContainer { private readonly StatefulMenuItem menuItem; - private readonly Bindable state; + private readonly Bindable state; private readonly SpriteIcon stateIcon; public ToggleTextContainer(StatefulMenuItem menuItem) @@ -61,7 +61,7 @@ protected override void LoadComplete() state.BindValueChanged(updateState, true); } - private void updateState(ValueChangedEvent state) + private void updateState(ValueChangedEvent state) { var icon = menuItem.GetIconForState(state.NewValue); diff --git a/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs index 9111cfb3aada..c3d8877ede53 100644 --- a/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/StatefulMenuItem.cs @@ -53,7 +53,7 @@ protected StatefulMenuItem(LocalisableString text, Func? chang /// /// The state to retrieve the relevant icon for. /// The icon to be displayed for . - public abstract IconUsage? GetIconForState(object state); + public abstract IconUsage? GetIconForState(object? state); } public abstract class StatefulMenuItem : StatefulMenuItem @@ -97,7 +97,7 @@ protected StatefulMenuItem(LocalisableString text, Func? changeStateFunc, State.BindValueChanged(state => base.State.Value = state.NewValue); } - public sealed override IconUsage? GetIconForState(object state) => GetIconForState((T)state); + public sealed override IconUsage? GetIconForState(object? state) => GetIconForState((T)state!); /// /// Retrieves the icon to be displayed for a state. diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs index 8de7f86523a7..f6c61daa69b4 100644 --- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -66,7 +66,7 @@ protected override void LoadComplete() syncingColours = true; comboColours.Colours.Clear(); - comboColours.Colours.AddRange(Beatmap.BeatmapSkin?.ComboColours); + comboColours.Colours.AddRange(Beatmap.BeatmapSkin?.ComboColours ?? []); syncingColours = false; }); diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index 0c993f4abf32..3ceaf187e26d 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -38,6 +39,10 @@ public DrawableMatchRoom(Room room, bool allowEdit = true) { this.allowEdit = allowEdit; + // Roslyn sees required non-nullable property as nullable in constructor, + // as it can't see the implementation provides a fallback. + Debug.Assert(SelectedItem != null); + base.SelectedItem.BindTo(SelectedItem); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 1c431b1cd484..e30ef761cde9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -994,7 +994,7 @@ private void bindBindables() selectedMods.BindValueChanged(_ => { - if (decoupledRuleset.Value.Equals(rulesetNoDebounce)) + if (decoupledRuleset.Value?.Equals(rulesetNoDebounce) ?? false) advancedStats.Mods.Value = selectedMods.Value; }, true);