1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 07:27:25 +08:00
osu-lazer/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

167 lines
6.5 KiB
C#
Raw Normal View History

2020-04-09 19:48:59 +08:00
// 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;
2020-11-07 23:18:25 +08:00
using System.Diagnostics;
2020-04-09 19:48:59 +08:00
using System.IO;
using System.Text;
2020-04-09 19:48:59 +08:00
using DiffPlex;
2022-06-13 14:40:11 +08:00
using DiffPlex.Model;
2020-04-09 19:48:59 +08:00
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
2022-06-13 14:40:11 +08:00
using osu.Game.Beatmaps.Formats;
2020-04-09 19:48:59 +08:00
using osu.Game.IO;
using osu.Game.Skinning;
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
2020-04-09 19:48:59 +08:00
namespace osu.Game.Screens.Edit
{
2020-04-13 16:20:01 +08:00
/// <summary>
/// Patches an <see cref="EditorBeatmap"/> based on the difference between two legacy (.osu) states.
/// </summary>
public class LegacyEditorBeatmapPatcher
2020-04-09 19:48:59 +08:00
{
private readonly EditorBeatmap editorBeatmap;
2020-04-13 16:20:01 +08:00
public LegacyEditorBeatmapPatcher(EditorBeatmap editorBeatmap)
2020-04-09 19:48:59 +08:00
{
this.editorBeatmap = editorBeatmap;
}
public void Patch(byte[] currentState, byte[] newState)
2020-04-09 19:48:59 +08:00
{
// Diff the beatmaps
var result = new Differ().CreateLineDiffs(readString(currentState), readString(newState), true, false);
2022-06-13 14:40:11 +08:00
IBeatmap newBeatmap = null;
editorBeatmap.BeginChange();
processHitObjects(result, () => newBeatmap ??= readBeatmap(newState));
processTimingPoints(result, () => newBeatmap ??= readBeatmap(newState));
editorBeatmap.EndChange();
}
private void processTimingPoints(DiffResult result, Func<IBeatmap> getNewBeatmap)
{
findChangedIndices(result, LegacyDecoder<Beatmap>.Section.TimingPoints, out var removedIndices, out var addedIndices);
if (removedIndices.Count == 0 && addedIndices.Count == 0)
return;
// Due to conversion from legacy to non-legacy control points, it becomes difficult to diff control points correctly.
// So instead _all_ control points are reloaded if _any_ control point is changed.
var newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo);
editorBeatmap.ControlPointInfo.Clear();
foreach (var point in newControlPoints.AllControlPoints)
editorBeatmap.ControlPointInfo.Add(point.Time, point);
}
private void processHitObjects(DiffResult result, Func<IBeatmap> getNewBeatmap)
{
findChangedIndices(result, LegacyDecoder<Beatmap>.Section.HitObjects, out var removedIndices, out var addedIndices);
foreach (int removed in removedIndices)
editorBeatmap.RemoveAt(removed);
if (addedIndices.Count > 0)
{
var newBeatmap = getNewBeatmap();
foreach (int i in addedIndices)
editorBeatmap.Insert(i, newBeatmap.HitObjects[i]);
}
}
private void findChangedIndices(DiffResult result, LegacyDecoder<Beatmap>.Section section, out List<int> removedIndices, out List<int> addedIndices)
{
removedIndices = new List<int>();
addedIndices = new List<int>();
2020-04-09 19:48:59 +08:00
// Find the index of [HitObject] sections. Lines changed prior to this index are ignored.
2022-06-13 14:40:11 +08:00
int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]");
int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith('['));
2022-06-13 14:40:11 +08:00
if (oldSectionEndIndex == -1)
oldSectionEndIndex = result.PiecesOld.Length;
int newSectionStartIndex = Array.IndexOf(result.PiecesNew, $"[{section}]");
int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('['));
2020-04-09 19:48:59 +08:00
2022-06-13 14:40:11 +08:00
if (newSectionEndIndex == -1)
newSectionEndIndex = result.PiecesOld.Length;
2020-11-07 23:18:25 +08:00
2022-06-13 14:40:11 +08:00
Debug.Assert(oldSectionStartIndex >= 0);
Debug.Assert(newSectionStartIndex >= 0);
2020-04-09 19:48:59 +08:00
foreach (var block in result.DiffBlocks)
{
2022-06-13 14:40:11 +08:00
// Removed indices
2020-04-09 19:48:59 +08:00
for (int i = 0; i < block.DeleteCountA; i++)
{
2022-06-13 14:40:11 +08:00
int objectIndex = block.DeleteStartA + i;
2020-04-09 19:48:59 +08:00
2022-06-13 14:40:11 +08:00
if (objectIndex <= oldSectionStartIndex || objectIndex >= oldSectionEndIndex)
2020-04-09 19:48:59 +08:00
continue;
2022-06-13 14:40:11 +08:00
removedIndices.Add(objectIndex - oldSectionStartIndex - 1);
2020-04-09 19:48:59 +08:00
}
2022-06-13 14:40:11 +08:00
// Added indices
2020-04-09 19:48:59 +08:00
for (int i = 0; i < block.InsertCountB; i++)
{
2022-06-13 14:40:11 +08:00
int objectIndex = block.InsertStartB + i;
2020-04-09 19:48:59 +08:00
2022-06-13 14:40:11 +08:00
if (objectIndex <= newSectionStartIndex || objectIndex >= newSectionEndIndex)
2020-04-09 19:48:59 +08:00
continue;
2022-06-13 14:40:11 +08:00
addedIndices.Add(objectIndex - newSectionStartIndex - 1);
2020-04-09 19:48:59 +08:00
}
}
// Sort the indices to ensure that removal + insertion indices don't get jumbled up post-removal or post-insertion.
// This isn't strictly required, but the differ makes no guarantees about order.
2022-06-13 14:40:11 +08:00
removedIndices.Sort();
addedIndices.Sort();
2020-04-09 19:48:59 +08:00
2022-06-13 14:40:11 +08:00
removedIndices.Reverse();
2020-04-09 19:48:59 +08:00
}
private string readString(byte[] state) => Encoding.UTF8.GetString(state);
2020-04-09 19:48:59 +08:00
private IBeatmap readBeatmap(byte[] state)
2020-04-09 19:48:59 +08:00
{
using (var stream = new MemoryStream(state))
2020-04-09 19:48:59 +08:00
using (var reader = new LineBufferedReader(stream, true))
2020-04-30 19:03:46 +08:00
{
var decoded = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
decoded.BeatmapInfo.Ruleset = editorBeatmap.BeatmapInfo.Ruleset;
return new PassThroughWorkingBeatmap(decoded).GetPlayableBeatmap(editorBeatmap.BeatmapInfo.Ruleset);
}
2020-04-09 19:48:59 +08:00
}
private class PassThroughWorkingBeatmap : WorkingBeatmap
{
private readonly IBeatmap beatmap;
public PassThroughWorkingBeatmap(IBeatmap beatmap)
: base(beatmap.BeatmapInfo, null)
{
this.beatmap = beatmap;
}
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => throw new NotImplementedException();
2020-08-07 21:31:41 +08: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();
2020-04-09 19:48:59 +08:00
}
}
}