diff --git a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs index ab47067411..ffb3d41d18 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -65,10 +64,9 @@ namespace osu.Game.Tests.Skins public new void TriggerSourceChanged() => base.TriggerSourceChanged(); - protected override void OnSourceChanged() + protected override void RefreshSources() { - ResetSources(); - sources.ForEach(AddSource); + SetSources(sources); } } diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index ba3e2bf6ad..a5ed0fc990 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -12,6 +12,9 @@ namespace osu.Game.Skinning /// public interface ISkinSource : ISkin { + /// + /// Fired whenever a source change occurs, signalling that consumers should re-query as required. + /// event Action SourceChanged; /// diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index f5a7788359..b884794739 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -58,10 +58,8 @@ namespace osu.Game.Skinning return base.CreateChildDependencies(parent); } - protected override void OnSourceChanged() + protected override void RefreshSources() { - ResetSources(); - // Populate a local list first so we can adjust the returned order as we go. var sources = new List(); @@ -91,8 +89,7 @@ namespace osu.Game.Skinning else sources.Add(rulesetResourcesSkin); - foreach (var skin in sources) - AddSource(skin); + SetSources(sources); } protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index ada6e4b788..c8e4c2c7b6 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -40,10 +41,12 @@ namespace osu.Game.Skinning protected virtual bool AllowColourLookup => true; + private readonly object sourceSetLock = new object(); + /// /// A dictionary mapping each source to a wrapper which handles lookup allowances. /// - private readonly List<(ISkin skin, DisableableSkinSource wrapped)> skinSources = new List<(ISkin, DisableableSkinSource)>(); + private (ISkin skin, DisableableSkinSource wrapped)[] skinSources = Array.Empty<(ISkin skin, DisableableSkinSource wrapped)>(); /// /// Constructs a new initialised with a single skin source. @@ -52,7 +55,7 @@ namespace osu.Game.Skinning : this() { if (skin != null) - AddSource(skin); + SetSources(new[] { skin }); } /// @@ -168,49 +171,42 @@ namespace osu.Game.Skinning } /// - /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. + /// Replace the sources used for lookups in this container. /// - /// The skin to add. - protected void AddSource(ISkin skin) + /// + /// This does not implicitly fire a event. Consider calling if required. + /// + /// The new sources. + protected void SetSources(IEnumerable sources) { - skinSources.Add((skin, new DisableableSkinSource(skin, this))); + lock (sourceSetLock) + { + foreach (var skin in skinSources) + { + if (skin.skin is ISkinSource source) + source.SourceChanged -= TriggerSourceChanged; + } - if (skin is ISkinSource source) - source.SourceChanged += TriggerSourceChanged; + skinSources = sources.Select(skin => (skin, new DisableableSkinSource(skin, this))).ToArray(); + + foreach (var skin in skinSources) + { + if (skin.skin is ISkinSource source) + source.SourceChanged += TriggerSourceChanged; + } + } } /// - /// Remove a skin from this provider. - /// - /// The skin to remove. - protected void RemoveSource(ISkin skin) - { - if (skinSources.RemoveAll(s => s.skin == skin) == 0) - return; - - if (skin is ISkinSource source) - source.SourceChanged -= TriggerSourceChanged; - } - - /// - /// Clears all skin sources. - /// - protected void ResetSources() - { - foreach (var i in skinSources.ToArray()) - RemoveSource(i.skin); - } - - /// - /// Invoked when any source has changed (either or a source registered via ). + /// Invoked after any consumed source change, before the external event is fired. /// This is also invoked once initially during to ensure sources are ready for children consumption. /// - protected virtual void OnSourceChanged() { } + protected virtual void RefreshSources() { } protected void TriggerSourceChanged() { // Expose to implementations, giving them a chance to react before notifying external consumers. - OnSourceChanged(); + RefreshSources(); SourceChanged?.Invoke(); }