osu/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs

226 lines
8.9 KiB
C#
Raw Normal View History

// 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;
2019-12-13 10:00:28 +00:00
using System.Collections.Generic;
using System.IO;
using System.Linq;
2020-05-11 07:37:08 +00:00
using System.Text;
using NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
2020-08-12 04:38:05 +00:00
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Legacy;
using osu.Game.IO;
2019-12-13 10:00:28 +00:00
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
2021-04-06 05:33:46 +00:00
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
2021-04-06 05:33:46 +00:00
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Taiko;
2020-08-12 04:38:05 +00:00
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
2021-04-06 05:33:46 +00:00
using osuTK;
namespace osu.Game.Tests.Beatmaps.Formats
{
[TestFixture]
public class LegacyBeatmapEncoderTest
{
2020-08-23 13:08:02 +00:00
private static readonly DllResourceStore beatmaps_resource_store = TestResources.GetStore();
2020-10-16 03:58:34 +00:00
private static IEnumerable<string> allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu", StringComparison.Ordinal));
2019-12-13 10:00:28 +00:00
[TestCaseSource(nameof(allBeatmaps))]
public void TestEncodeDecodeStability(string name)
{
2020-08-30 14:08:13 +00:00
var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
2020-08-23 13:08:02 +00:00
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
sort(decoded.beatmap);
sort(decodedAfterEncode.beatmap);
2020-08-15 21:41:53 +00:00
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
}
[TestCaseSource(nameof(allBeatmaps))]
public void TestEncodeDecodeStabilityDoubleConvert(string name)
{
var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
// run an extra convert. this is expected to be stable.
decodedAfterEncode.beatmap = convert(decodedAfterEncode.beatmap);
sort(decoded.beatmap);
sort(decodedAfterEncode.beatmap);
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
}
2021-04-06 05:33:46 +00:00
[Test]
public void TestEncodeMultiSegmentSliderWithFloatingPointError()
{
var beatmap = new Beatmap
{
HitObjects =
{
new Slider
{
Position = new Vector2(0.6f),
Path = new SliderPath(new[]
{
new PathControlPoint(Vector2.Zero, PathType.Bezier),
new PathControlPoint(new Vector2(0.5f)),
new PathControlPoint(new Vector2(0.51f)), // This is actually on the same position as the previous one in legacy beatmaps (truncated to int).
new PathControlPoint(new Vector2(1f), PathType.Bezier),
new PathControlPoint(new Vector2(2f))
})
},
}
};
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty))), string.Empty);
var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0];
Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5));
}
[TestCaseSource(nameof(allBeatmaps))]
public void TestEncodeDecodeStabilityWithNonLegacyControlPoints(string name)
{
var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
sort(decoded.beatmap);
var originalSerialized = decoded.beatmap.Serialize();
var encoded = encodeToLegacy(decoded);
Assert.AreEqual(typeof(LegacyControlPointInfo), decoded.beatmap.ControlPointInfo.GetType());
// emulate non-legacy control points by cloning the non-legacy portion.
// the assertion is that the encoder can recreate this losslessly from hitobject data.
var controlPointInfo = new ControlPointInfo();
foreach (var point in decoded.beatmap.ControlPointInfo.AllControlPoints)
controlPointInfo.Add(point.Time, point.DeepClone());
decoded.beatmap.ControlPointInfo = controlPointInfo;
Assert.AreNotEqual(typeof(LegacyControlPointInfo), decoded.beatmap.ControlPointInfo.GetType());
var decodedAfterEncode = decodeFromLegacy(encoded, name);
sort(decodedAfterEncode.beatmap);
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(originalSerialized));
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
}
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
{
// equal to null, no need to SequenceEqual
if (a.ComboColours == null && b.ComboColours == null)
return true;
if (a.ComboColours == null || b.ComboColours == null)
return false;
return a.ComboColours.SequenceEqual(b.ComboColours);
}
private void sort(IBeatmap beatmap)
{
// Sort control points to ensure a sane ordering, as they may be parsed in different orders. This works because each group contains only uniquely-typed control points.
foreach (var g in beatmap.ControlPointInfo.Groups)
{
ArrayList.Adapter((IList)g.ControlPoints).Sort(
Comparer<ControlPoint>.Create((c1, c2) => string.Compare(c1.GetType().ToString(), c2.GetType().ToString(), StringComparison.Ordinal)));
}
}
private (IBeatmap beatmap, TestLegacySkin beatmapSkin) decodeFromLegacy(Stream stream, string name)
{
using (var reader = new LineBufferedReader(stream))
2020-08-15 21:41:53 +00:00
{
var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader);
var beatmapSkin = new TestLegacySkin(beatmaps_resource_store, name);
return (convert(beatmap), beatmapSkin);
2020-08-30 19:13:06 +00:00
}
}
2020-09-01 16:15:46 +00:00
private class TestLegacySkin : LegacySkin
{
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName)
2020-08-30 14:23:00 +00:00
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName)
{
}
}
private MemoryStream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap)
{
2020-08-15 21:41:53 +00:00
var (beatmap, beatmapSkin) = fullBeatmap;
var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
2020-08-15 20:06:26 +00:00
new LegacyBeatmapEncoder(beatmap, beatmapSkin).Encode(writer);
stream.Position = 0;
return stream;
}
private IBeatmap convert(IBeatmap beatmap)
{
switch (beatmap.BeatmapInfo.RulesetID)
{
case 0:
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
break;
case 1:
beatmap.BeatmapInfo.Ruleset = new TaikoRuleset().RulesetInfo;
break;
case 2:
beatmap.BeatmapInfo.Ruleset = new CatchRuleset().RulesetInfo;
break;
case 3:
beatmap.BeatmapInfo.Ruleset = new ManiaRuleset().RulesetInfo;
break;
}
return new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset);
}
private class TestWorkingBeatmap : WorkingBeatmap
{
private readonly IBeatmap beatmap;
public TestWorkingBeatmap(IBeatmap beatmap)
: base(beatmap.BeatmapInfo, null)
{
this.beatmap = beatmap;
}
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => throw new NotImplementedException();
2020-08-07 13:31:41 +00:00
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
protected internal override ISkin GetSkin() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
}
}