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();
}