Change serialisation format of skin layouts to allow more flexibility

Also adds per-ruleset storage for each container type.
This commit is contained in:
Dean Herbert 2023-02-16 19:58:04 +09:00
parent 9685fb2114
commit 6b3652f567
4 changed files with 85 additions and 31 deletions

View File

@ -66,7 +66,7 @@ namespace osu.Game.Tests.Skins
{
var skin = new TestSkin(new SkinInfo(), null, storage);
foreach (var target in skin.DrawableComponentInfo)
foreach (var target in skin.LayoutInfos)
{
foreach (var info in target.Value)
instantiatedTypes.Add(info.Type);
@ -87,8 +87,8 @@ namespace osu.Game.Tests.Skins
{
var skin = new TestSkin(new SkinInfo(), null, storage);
Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(9));
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(9));
}
}
@ -100,11 +100,11 @@ namespace osu.Game.Tests.Skins
{
var skin = new TestSkin(new SkinInfo(), null, storage);
Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(6));
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.SongSelect], Has.Length.EqualTo(1));
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(6));
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect], Has.Length.EqualTo(1));
var skinnableInfo = skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.SongSelect].First();
var skinnableInfo = skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].First();
Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
@ -115,10 +115,10 @@ namespace osu.Game.Tests.Skins
using (var storage = new ZipArchiveReader(stream))
{
var skin = new TestSkin(new SkinInfo(), null, storage);
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(8));
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(8));
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
}
}

View File

@ -37,9 +37,10 @@ namespace osu.Game.Skinning
public SkinConfiguration Configuration { get; set; }
public IDictionary<SkinComponentsContainerLookup.TargetArea, SerialisedDrawableInfo[]> DrawableComponentInfo => drawableComponentInfo;
public IDictionary<SkinComponentsContainerLookup.TargetArea, SkinLayoutInfo> LayoutInfos => layoutInfos;
private readonly Dictionary<SkinComponentsContainerLookup.TargetArea, SerialisedDrawableInfo[]> drawableComponentInfo = new Dictionary<SkinComponentsContainerLookup.TargetArea, SerialisedDrawableInfo[]>();
private readonly Dictionary<SkinComponentsContainerLookup.TargetArea, SkinLayoutInfo> layoutInfos =
new Dictionary<SkinComponentsContainerLookup.TargetArea, SkinLayoutInfo>();
public abstract ISample? GetSample(ISampleInfo sampleInfo);
@ -113,18 +114,37 @@ namespace osu.Game.Skinning
{
string jsonContent = Encoding.UTF8.GetString(bytes);
// handle namespace changes...
SkinLayoutInfo? layoutInfo = null;
// can be removed 2023-01-31
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
try
{
// First attempt to deserialise using the new SkinLayoutInfo format
layoutInfo = JsonConvert.DeserializeObject<SkinLayoutInfo>(jsonContent);
}
catch
{
}
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SerialisedDrawableInfo>>(jsonContent);
// if that fails, attempt to deserialise using the old naked list.
if (layoutInfo == null)
{
// handle namespace changes...
// can be removed 2023-01-31
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
if (deserializedContent == null)
continue;
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SerialisedDrawableInfo>>(jsonContent);
DrawableComponentInfo[skinnableTarget] = deserializedContent.ToArray();
if (deserializedContent == null)
continue;
layoutInfo = new SkinLayoutInfo();
layoutInfo.Update(null, deserializedContent.ToArray());
Logger.Log($"Ferrying {deserializedContent.Count()} components in {skinnableTarget} to global section of new {nameof(SkinLayoutInfo)} format");
}
LayoutInfos[skinnableTarget] = layoutInfo;
}
catch (Exception ex)
{
@ -145,7 +165,7 @@ namespace osu.Game.Skinning
/// <param name="targetContainer">The target container to reset.</param>
public void ResetDrawableTarget(SkinComponentsContainer targetContainer)
{
DrawableComponentInfo.Remove(targetContainer.Lookup.Target);
LayoutInfos.Remove(targetContainer.Lookup.Target);
}
/// <summary>
@ -154,7 +174,10 @@ namespace osu.Game.Skinning
/// <param name="targetContainer">The target container to serialise to this skin.</param>
public void UpdateDrawableTarget(SkinComponentsContainer targetContainer)
{
DrawableComponentInfo[targetContainer.Lookup.Target] = ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray();
if (!LayoutInfos.TryGetValue(targetContainer.Lookup.Target, out var layoutInfo))
layoutInfos[targetContainer.Lookup.Target] = layoutInfo = new SkinLayoutInfo();
layoutInfo.Update(targetContainer.Lookup.Ruleset, ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray());
}
public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
@ -166,18 +189,16 @@ namespace osu.Game.Skinning
return this.GetAnimation(sprite.LookupName, false, false);
case SkinComponentsContainerLookup containerLookup:
if (!DrawableComponentInfo.TryGetValue(containerLookup.Target, out var skinnableInfo))
return null;
var components = new List<Drawable>();
foreach (var i in skinnableInfo)
components.Add(i.CreateInstance());
// It is important to return null if the user has not configured this yet.
// This allows skin transformers the opportunity to provide default components.
if (!LayoutInfos.TryGetValue(containerLookup.Target, out var layoutInfo)) return null;
if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null;
return new Container
{
RelativeSizeAxes = Axes.Both,
Children = components,
ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance())
};
}

View File

@ -201,7 +201,7 @@ namespace osu.Game.Skinning
}
// Then serialise each of the drawable component groups into respective files.
foreach (var drawableInfo in skin.DrawableComponentInfo)
foreach (var drawableInfo in skin.LayoutInfos)
{
string json = JsonConvert.SerializeObject(drawableInfo.Value, new JsonSerializerSettings { Formatting = Formatting.Indented });

View File

@ -0,0 +1,33 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using osu.Game.Rulesets;
namespace osu.Game.Skinning
{
/// <summary>
/// A serialisable model describing layout of a <see cref="SkinComponentsContainer"/>.
/// May contain multiple configurations for different rulesets, each of which should manifest their own <see cref="SkinComponentsContainer"/> as required.
/// </summary>
[Serializable]
public class SkinLayoutInfo
{
private const string global_identifier = "global";
[JsonProperty]
public Dictionary<string, SerialisedDrawableInfo[]> DrawableInfo { get; set; } = new Dictionary<string, SerialisedDrawableInfo[]>();
public bool TryGetDrawableInfo(Ruleset? ruleset, [NotNullWhen(true)] out SerialisedDrawableInfo[]? components) =>
DrawableInfo.TryGetValue(ruleset?.ShortName ?? global_identifier, out components);
public void Reset(Ruleset? ruleset) =>
DrawableInfo.Remove(ruleset?.ShortName ?? global_identifier);
public void Update(Ruleset? ruleset, SerialisedDrawableInfo[] components) =>
DrawableInfo[ruleset?.ShortName ?? global_identifier] = components;
}
}