diff --git a/YARG.Core/IO/Ini/IniModifierCreator.cs b/YARG.Core/IO/Ini/IniModifierCreator.cs index bf74750ed..c083e1377 100644 --- a/YARG.Core/IO/Ini/IniModifierCreator.cs +++ b/YARG.Core/IO/Ini/IniModifierCreator.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; using YARG.Core.Song; using YARG.Core.Utility; @@ -38,63 +35,62 @@ public IniModifierCreator(string outputName, ModifierCreatorType type) this.type = type; } - public IniModifier CreateModifier(YARGTextReader reader) + public unsafe IniModifier CreateModifier(ref YARGTextContainer container, in delegate* decoder) where TChar : unmanaged, IConvertible - where TDecoder : IStringDecoder, new() { return type switch { - ModifierCreatorType.SortString => new IniModifier(SortString.Convert(ExtractIniString(reader, false))), - ModifierCreatorType.SortString_Chart => new IniModifier(SortString.Convert(ExtractIniString(reader, true))), - ModifierCreatorType.String => new IniModifier(ExtractIniString(reader, false)), - ModifierCreatorType.String_Chart => new IniModifier(ExtractIniString(reader, true)), - _ => CreateNumberModifier(reader.Container), + ModifierCreatorType.SortString => new IniModifier(SortString.Convert(ExtractIniString(ref container, decoder, false))), + ModifierCreatorType.SortString_Chart => new IniModifier(SortString.Convert(ExtractIniString(ref container, decoder, true))), + ModifierCreatorType.String => new IniModifier(ExtractIniString(ref container, decoder, false)), + ModifierCreatorType.String_Chart => new IniModifier(ExtractIniString(ref container, decoder, true)), + _ => CreateNumberModifier(ref container), }; } - public IniModifier CreateSngModifier(YARGTextContainer sngContainer, int length) + public IniModifier CreateSngModifier(ref YARGTextContainer sngContainer, int length) { return type switch { - ModifierCreatorType.SortString => new IniModifier(SortString.Convert(ExtractSngString(sngContainer, length))), - ModifierCreatorType.String => new IniModifier(ExtractSngString(sngContainer, length)), - _ => CreateNumberModifier(sngContainer), + ModifierCreatorType.SortString => new IniModifier(SortString.Convert(ExtractSngString(ref sngContainer, length))), + ModifierCreatorType.String => new IniModifier(ExtractSngString(ref sngContainer, length)), + _ => CreateNumberModifier(ref sngContainer), }; } - private IniModifier CreateNumberModifier(YARGTextContainer container) + private IniModifier CreateNumberModifier(ref YARGTextContainer container) where TChar : unmanaged, IConvertible { switch (type) { case ModifierCreatorType.UInt64: { - container.ExtractUInt64(out ulong value); + container.TryExtractUInt64(out ulong value); return new IniModifier(value); } case ModifierCreatorType.Int64: { - container.ExtractInt64(out long value); + container.TryExtractInt64(out long value); return new IniModifier(value); } case ModifierCreatorType.UInt32: { - container.ExtractUInt32(out uint value); + container.TryExtractUInt32(out uint value); return new IniModifier(value); } case ModifierCreatorType.Int32: { - container.ExtractInt32(out int value); + container.TryExtractInt32(out int value); return new IniModifier(value); } case ModifierCreatorType.UInt16: { - container.ExtractUInt16(out ushort value); + container.TryExtractUInt16(out ushort value); return new IniModifier(value); } case ModifierCreatorType.Int16: { - container.ExtractInt16(out short value); + container.TryExtractInt16(out short value); return new IniModifier(value); } case ModifierCreatorType.Bool: @@ -103,21 +99,21 @@ private IniModifier CreateNumberModifier(YARGTextContainer contain } case ModifierCreatorType.Float: { - container.ExtractFloat(out float value); + container.TryExtractFloat(out float value); return new IniModifier(value); } case ModifierCreatorType.Double: { - container.ExtractDouble(out double value); + container.TryExtractDouble(out double value); return new IniModifier(value); } case ModifierCreatorType.UInt64Array: { long l2 = -1; - if (container.ExtractInt64(out long l1)) + if (container.TryExtractInt64(out long l1)) { - YARGTextReader.SkipWhitespace(container); - if (!container.ExtractInt64(out l2)) + YARGTextReader.SkipWhitespace(ref container); + if (!container.TryExtractInt64(out l2)) { l2 = -1; } @@ -133,16 +129,15 @@ private IniModifier CreateNumberModifier(YARGTextContainer contain } } - private static string ExtractIniString(YARGTextReader reader, bool isChartFile) + private static unsafe string ExtractIniString(ref YARGTextContainer container, in delegate* decoder, bool isChartFile) where TChar : unmanaged, IConvertible - where TDecoder : IStringDecoder, new() { - return RichTextUtils.ReplaceColorNames(reader.ExtractText(isChartFile)); + return RichTextUtils.ReplaceColorNames(YARGTextReader.ExtractText(ref container, decoder, isChartFile)); } - private static unsafe string ExtractSngString(YARGTextContainer sngContainer, int length) + private static unsafe string ExtractSngString(ref YARGTextContainer sngContainer, int length) { - return RichTextUtils.ReplaceColorNames(Encoding.UTF8.GetString(sngContainer.Data.Ptr + sngContainer.Position, length)); + return RichTextUtils.ReplaceColorNames(Encoding.UTF8.GetString(sngContainer.Position, length)); } } -} +} \ No newline at end of file diff --git a/YARG.Core/IO/Ini/YARGIniReader.cs b/YARG.Core/IO/Ini/YARGIniReader.cs index 521ee06a7..1fc7849b5 100644 --- a/YARG.Core/IO/Ini/YARGIniReader.cs +++ b/YARG.Core/IO/Ini/YARGIniReader.cs @@ -7,20 +7,21 @@ namespace YARG.Core.IO.Ini { - public static class YARGIniReader + public static unsafe class YARGIniReader { public static Dictionary ReadIniFile(FileInfo iniFile, Dictionary> sections) { try { using var bytes = MemoryMappedArray.Load(iniFile); - var byteReader = YARGTextLoader.TryLoadByteText(bytes); - if (byteReader != null) - return ProcessIni(byteReader, sections); + if (YARGTextReader.TryLoadByteText(bytes, out var byteContainer)) + { + return ProcessIni(ref byteContainer, &StringDecoder.Decode, sections); + } - using var chars = YARGTextLoader.ConvertToChar(bytes); - var charReader = new YARGTextReader(chars, 0); - return ProcessIni(charReader, sections); + using var chars = YARGTextReader.ConvertToChar(bytes); + var charContainer = new YARGTextContainer(chars, 0); + return ProcessIni(ref charContainer, &StringDecoder.Decode, sections); } catch (Exception ex) @@ -30,79 +31,64 @@ public static Dictionary ReadIniFile(FileInfo iniFile, Dicti } } - private static Dictionary ProcessIni(YARGTextReader reader, Dictionary> sections) + private static Dictionary ProcessIni(ref YARGTextContainer container, in delegate* decoder, Dictionary> sections) where TChar : unmanaged, IConvertible - where TDecoder : IStringDecoder, new() { Dictionary modifierMap = new(); - while (TrySection(reader, out string section)) + while (TrySection(ref container, decoder, out string section)) { if (sections.TryGetValue(section, out var nodes)) - modifierMap[section] = ExtractModifiers(reader, ref nodes); + modifierMap[section] = ExtractModifiers(ref container, decoder, ref nodes); else - reader.SkipLinesUntil('['); + YARGTextReader.SkipLinesUntil(ref container, '['); } return modifierMap; } - private static bool TrySection(YARGTextReader reader, out string section) + private static bool TrySection(ref YARGTextContainer container, in delegate* decoder, out string section) where TChar : unmanaged, IConvertible - where TDecoder : IStringDecoder, new() { section = string.Empty; - if (reader.Container.IsEndOfFile()) + if (container.IsAtEnd()) + { return false; + } - if (!reader.Container.IsCurrentCharacter('[')) + if (!container.IsCurrentCharacter('[')) { - reader.SkipLinesUntil('['); - if (reader.Container.IsEndOfFile()) + if (!YARGTextReader.SkipLinesUntil(ref container, '[')) + { return false; + } } - section = reader.PeekLine().ToLower(); + section = YARGTextReader.PeekLine(ref container, decoder).ToLower(); return true; } - private static IniSection ExtractModifiers(YARGTextReader reader, ref Dictionary validNodes) + private static IniSection ExtractModifiers(ref YARGTextContainer container, in delegate* decoder, ref Dictionary validNodes) where TChar : unmanaged, IConvertible - where TDecoder : IStringDecoder, new() { Dictionary> modifiers = new(); - reader.GotoNextLine(); - while (IsStillCurrentSection(reader)) + while (IsStillCurrentSection(ref container)) { - string name = reader.ExtractModifierName().ToLower(); + string name = YARGTextReader.ExtractModifierName(ref container, decoder).ToLower(); if (validNodes.TryGetValue(name, out var node)) { - var mod = node.CreateModifier(reader); + var mod = node.CreateModifier(ref container, decoder); if (modifiers.TryGetValue(node.outputName, out var list)) list.Add(mod); else modifiers.Add(node.outputName, new() { mod }); } - reader.GotoNextLine(); } return new IniSection(modifiers); } - private static bool IsStillCurrentSection(YARGTextReader reader) + private static bool IsStillCurrentSection(ref YARGTextContainer container) where TChar : unmanaged, IConvertible - where TDecoder : IStringDecoder, new() { - return !reader.Container.IsEndOfFile() && !reader.Container.IsCurrentCharacter('['); - } - - private static bool FindNextTrack(YARGTextReader reader) - where TChar : unmanaged, IConvertible - where TDecoder : IStringDecoder, new() - { - while (reader.Container.Position < reader.Container.Length) - { - if (reader.Container.Data[reader.Container.Position].ToChar(null) == '[') - return true; - ++reader.Container.Position; - } - return false; + YARGTextReader.GotoNextLine(ref container); + return !container.IsAtEnd() && !container.IsCurrentCharacter('['); } } } diff --git a/YARG.Core/IO/Midi/YARGMidiFile.cs b/YARG.Core/IO/Midi/YARGMidiFile.cs index 2a032325b..085b4f90c 100644 --- a/YARG.Core/IO/Midi/YARGMidiFile.cs +++ b/YARG.Core/IO/Midi/YARGMidiFile.cs @@ -83,6 +83,7 @@ public MidiFileEnumerator(YARGMidiFile file) public bool MoveNext() { + _current?.Dispose(); _current = file.LoadNextTrack(); return _current != null; } @@ -94,7 +95,7 @@ public void Reset() public void Dispose() { - //throw new NotImplementedException(); + _current?.Dispose(); } } } diff --git a/YARG.Core/IO/Midi/YARGMidiTrack.cs b/YARG.Core/IO/Midi/YARGMidiTrack.cs index eff4c3f92..79daac9f9 100644 --- a/YARG.Core/IO/Midi/YARGMidiTrack.cs +++ b/YARG.Core/IO/Midi/YARGMidiTrack.cs @@ -3,10 +3,11 @@ using System.IO; using System.Text; using YARG.Core.Extensions; +using YARG.Core.IO.Disposables; namespace YARG.Core.IO { - public sealed class YARGMidiTrack + public sealed unsafe class YARGMidiTrack : IDisposable { public static readonly Dictionary TRACKNAMES = new() { @@ -51,8 +52,9 @@ private struct MidiEvent private MidiEvent _event; private MidiEvent _running; - private readonly ReadOnlyMemory _data; - private int _trackPos; + private readonly AllocatedArray? _buffer; + private readonly unsafe byte* _end; + private unsafe byte* _position; public long Position => _tickPosition; public MidiEventType Type => _event.Type; @@ -60,22 +62,25 @@ private struct MidiEvent public YARGMidiTrack(Stream stream) { - int count = stream.Read(Endianness.Big); - if (stream is MemoryStream mem) + int length = stream.Read(Endianness.Big); + if (stream is UnmanagedMemoryStream unmem) { - _data = new ReadOnlyMemory(mem.GetBuffer(), (int) mem.Position, count); - mem.Position += count; + _position = unmem.PositionPointer; + unmem.Position += length; } else { - _data = stream.ReadBytes(count); + _buffer = AllocatedArray.Read(stream, length); + _position = _buffer.Ptr; } + _end = _position + length; _event.Type = _running.Type = MidiEventType.Reset_Or_Meta; } public string? FindTrackName(Encoding encoding) { string trackname = string.Empty; + var start = _position; while (ParseEvent(true) && _tickPosition == 0) { if (_event.Type == MidiEventType.Text_TrackName) @@ -86,7 +91,11 @@ public YARGMidiTrack(Stream stream) trackname = ev; } } - Reset(); + + _position = start; + _tickPosition = 0; + _event.Length = 0; + _event.Type = _running.Type = MidiEventType.Reset_Or_Meta; return trackname; } @@ -95,14 +104,13 @@ public YARGMidiTrack(Stream stream) public bool ParseEvent(bool parseVLQ) { - _trackPos += _event.Length; + _position += _event.Length; if (!parseVLQ) AbsorbVLQ(); else _tickPosition += ReadVLQ(); - var span = _data.Span; - byte tmp = span[_trackPos]; + byte tmp = PeekByte(); var type = (MidiEventType) tmp; if (type < MidiEventType.Note_Off) { @@ -112,7 +120,7 @@ public bool ParseEvent(bool parseVLQ) } else { - _trackPos++; + ++_position; if (type < MidiEventType.SysEx) { _event.Channel = _running.Channel = (byte) (tmp & CHANNEL_MASK); @@ -132,7 +140,7 @@ MidiEventType.Key_Pressure or switch (type) { case MidiEventType.Reset_Or_Meta: - type = (MidiEventType) span[_trackPos++]; + type = (MidiEventType) ReadByte(); goto case MidiEventType.SysEx_End; case MidiEventType.SysEx: case MidiEventType.SysEx_End: @@ -154,29 +162,34 @@ MidiEventType.Key_Pressure or } } - if (_trackPos + _event.Length > _data.Length) + if (_position + _event.Length > _end) throw new EndOfStreamException(); return true; } - public ReadOnlySpan ExtractTextOrSysEx() + private byte PeekByte() { - return _data.Slice(_trackPos, _event.Length).Span; + if (_position < _end) + return *_position; + throw new InvalidOperationException(); } - public void ExtractMidiNote(ref MidiNote note) + private byte ReadByte() { - var span = _data.Span; - note.value = span[_trackPos]; - note.velocity = span[_trackPos + 1]; + if (_position < _end) + return *_position++; + throw new InvalidOperationException(); } - public void Reset() + public ReadOnlySpan ExtractTextOrSysEx() { - _trackPos = 0; - _tickPosition = 0; - _event.Length = 0; - _event.Type = _running.Type = MidiEventType.Reset_Or_Meta; + return new ReadOnlySpan(_position, _event.Length); + } + + public void ExtractMidiNote(ref MidiNote note) + { + note.value = _position[0]; + note.velocity = _position[1]; } private const uint EXTENDED_VLQ_FLAG = 0x80; @@ -189,15 +202,14 @@ public void Reset() private const uint VLQ_SHIFTLIMIT = 1 << (VLQ_SHIFT * MAX_SHIFTCOUNT); private uint ReadVLQ() { - var span = _data.Span; - uint curr = span[_trackPos++]; + uint curr = ReadByte(); uint value = curr & VLQ_MASK; while (curr >= EXTENDED_VLQ_FLAG) { if (value < VLQ_SHIFTLIMIT) { value <<= VLQ_SHIFT; - curr = span[_trackPos++]; + curr = ReadByte(); value |= curr & VLQ_MASK; } else @@ -208,21 +220,25 @@ private uint ReadVLQ() private unsafe void AbsorbVLQ() { - var span = _data.Span; - uint b = span[_trackPos++]; + uint b = ReadByte(); // Skip zeroes while (b == EXTENDED_VLQ_FLAG) - b = span[_trackPos++]; + b = ReadByte(); - int maxPos = _trackPos + MAX_SHIFTCOUNT; + var maxPos = _position + MAX_SHIFTCOUNT; while (b >= EXTENDED_VLQ_FLAG) { - if (_trackPos < maxPos) - b = span[_trackPos++]; + if (_position < maxPos) + b = ReadByte(); else throw new Exception("Invalid variable length quantity"); } } + + public void Dispose() + { + _buffer?.Dispose(); + } } public enum MidiTrackType diff --git a/YARG.Core/IO/SngHandler/SngFile.cs b/YARG.Core/IO/SngHandler/SngFile.cs index 8e0fcb148..6cb048592 100644 --- a/YARG.Core/IO/SngHandler/SngFile.cs +++ b/YARG.Core/IO/SngHandler/SngFile.cs @@ -125,15 +125,15 @@ private static unsafe IniSection ReadMetadata(Stream stream) for (ulong i = 0; i < numPairs; i++) { - int strLength = GetLength(container); - var key = Encoding.UTF8.GetString(container.Data.Ptr + container.Position, strLength); + int strLength = GetLength(ref container); + var key = Encoding.UTF8.GetString(container.Position, strLength); container.Position += strLength; - strLength = GetLength(container); + strLength = GetLength(ref container); var next = container.Position + strLength; if (validNodes.TryGetValue(key, out var node)) { - var mod = node.CreateSngModifier(container, strLength); + var mod = node.CreateSngModifier(ref container, strLength); if (modifiers.TryGetValue(node.outputName, out var list)) { list.Add(mod); @@ -170,11 +170,11 @@ private static Dictionary ReadListings(Stream stream) return listings; } - private static unsafe int GetLength(YARGTextContainer container) + private static unsafe int GetLength(ref YARGTextContainer container) { - int length = *(int*)(container.Data.Ptr + container.Position); + int length = *(int*)(container.Position); container.Position += sizeof(int); - if (container.Position + length > container.Length) + if (container.Position + length > container.End) { throw new EndOfStreamException(); } diff --git a/YARG.Core/IO/TextReader/StringDecoder.cs b/YARG.Core/IO/TextReader/StringDecoder.cs index 3e86c477f..e33ee55ec 100644 --- a/YARG.Core/IO/TextReader/StringDecoder.cs +++ b/YARG.Core/IO/TextReader/StringDecoder.cs @@ -1,40 +1,25 @@ -using System; -using System.Text; -using YARG.Core.Extensions; -using YARG.Core.IO.Disposables; +using System.Text; namespace YARG.Core.IO { - public sealed class ByteStringDecoder : IStringDecoder + public static unsafe class StringDecoder { private static readonly UTF8Encoding UTF8 = new(true, true); - private Encoding encoding = UTF8; - - public unsafe string Decode(FixedArray data, long index, long count) + public static string Decode(byte* data, long count) { try { - return encoding.GetString(data.Ptr + index, (int)count); + return UTF8.GetString(data, (int) count); } catch { - encoding = YARGTextContainer.Latin1; - return encoding.GetString(data.Ptr + index, (int)count); + return YARGTextContainer.Latin1.GetString(data, (int) count); } } - } - public struct CharStringDecoder : IStringDecoder - { - public readonly unsafe string Decode(FixedArray data, long index, long count) + public static string Decode(char* data, long count) { - return new string(data.Ptr, (int)index, (int) count); + return new string(data, 0, (int) count); } } - - public interface IStringDecoder - where TChar : unmanaged, IConvertible - { - public string Decode(FixedArray data, long index, long count); - } -} +} \ No newline at end of file diff --git a/YARG.Core/IO/TextReader/YARGTextContainer.cs b/YARG.Core/IO/TextReader/YARGTextContainer.cs index 44908f664..c434d7324 100644 --- a/YARG.Core/IO/TextReader/YARGTextContainer.cs +++ b/YARG.Core/IO/TextReader/YARGTextContainer.cs @@ -11,42 +11,45 @@ public static class YARGTextContainer public static readonly Encoding UTF8Strict = new UTF8Encoding(false, true); } - public sealed class YARGTextContainer + public unsafe struct YARGTextContainer where TChar : unmanaged, IConvertible { - public readonly FixedArray Data; - public readonly long Length; - public long Position; + public readonly TChar* End; + public TChar* Position; public YARGTextContainer(FixedArray data, long position) { - Data = data; - Length = data.Length; - Position = position; + Position = data.Ptr + position; + End = data.Ptr + data.Length; } - public YARGTextContainer(YARGTextContainer other) + public TChar CurrentValue { - Data = other.Data; - Length = other.Length; - Position = other.Position; + get + { + if (Position < End) + { + return *Position; + } + throw new InvalidOperationException("End of file reached"); + } } - public bool IsCurrentCharacter(char cmp) + public readonly bool IsCurrentCharacter(char cmp) { - return Data[Position].ToChar(null).Equals(cmp); + return Position->ToChar(null).Equals(cmp); } - public bool IsEndOfFile() + public readonly bool IsAtEnd() { - return Position >= Length; + return Position >= End; } private const char LAST_DIGIT_SIGNED = '7'; private const char LAST_DIGIT_UNSIGNED = '5'; private const short SHORT_MAX = short.MaxValue / 10; - public bool ExtractInt16(out short value) + public bool TryExtractInt16(out short value) { bool result = InternalExtractSigned(out long tmp, short.MaxValue, short.MinValue, SHORT_MAX); value = (short)tmp; @@ -54,7 +57,7 @@ public bool ExtractInt16(out short value) } private const int INT_MAX = int.MaxValue / 10; - public bool ExtractInt32(out int value) + public bool TryExtractInt32(out int value) { bool result = InternalExtractSigned(out long tmp, int.MaxValue, int.MinValue, INT_MAX); value = (int)tmp; @@ -62,13 +65,13 @@ public bool ExtractInt32(out int value) } private const long LONG_MAX = long.MaxValue / 10; - public bool ExtractInt64(out long value) + public bool TryExtractInt64(out long value) { return InternalExtractSigned(out value, long.MaxValue, long.MinValue, LONG_MAX); } private const ushort USHORT_MAX = ushort.MaxValue / 10; - public bool ExtractUInt16(out ushort value) + public bool TryExtractUInt16(out ushort value) { bool result = InternalExtractUnsigned(out ulong tmp, ushort.MaxValue, USHORT_MAX); value = (ushort) tmp; @@ -76,7 +79,7 @@ public bool ExtractUInt16(out ushort value) } private const uint UINT_MAX = uint.MaxValue / 10; - public bool ExtractUInt32(out uint value) + public bool TryExtractUInt32(out uint value) { bool result = InternalExtractUnsigned(out ulong tmp, uint.MaxValue, UINT_MAX); value = (uint) tmp; @@ -84,37 +87,37 @@ public bool ExtractUInt32(out uint value) } private const ulong ULONG_MAX = ulong.MaxValue / 10; - public bool ExtractUInt64(out ulong value) + public bool TryExtractUInt64(out ulong value) { return InternalExtractUnsigned(out value, ulong.MaxValue, ULONG_MAX); } - public bool ExtractFloat(out float value) + public bool TryExtractFloat(out float value) { - bool result = ExtractDouble(out double tmp); + bool result = TryExtractDouble(out double tmp); value = (float) tmp; return result; } - public bool ExtractDouble(out double value) + public bool TryExtractDouble(out double value) { value = 0; - if (Position >= Length) + if (Position >= End) { return false; } - char ch = Data[Position].ToChar(null); + char ch = Position->ToChar(null); double sign = ch == '-' ? -1 : 1; if (ch == '-' || ch == '+') { ++Position; - if (Position >= Length) + if (Position >= End) { return false; } - ch = Data[Position].ToChar(null); + ch = Position->ToChar(null); } if (ch < '0' || '9' < ch && ch != '.') @@ -127,31 +130,31 @@ public bool ExtractDouble(out double value) value *= 10; value += ch - '0'; ++Position; - if (Position == Length) + if (Position == End) { break; } - ch = Data[Position].ToChar(null); + ch = Position->ToChar(null); } if (ch == '.') { ++Position; - if (Position < Length) + if (Position < End) { double divisor = 1; - ch = Data[Position].ToChar(null); + ch = Position->ToChar(null); while ('0' <= ch && ch <= '9') { divisor *= 10; value += (ch - '0') / divisor; ++Position; - if (Position == Length) + if (Position == End) { break; } - ch = Data[Position].ToChar(null); + ch = Position->ToChar(null); } } } @@ -162,79 +165,23 @@ public bool ExtractDouble(out double value) public bool ExtractBoolean() { - return Position < Length && Data[Position].ToChar(null) switch + return Position < End && Position->ToChar(null) switch { '0' => false, '1' => true, - _ => Position + 4 <= Length && - (Data[Position].ToChar(null).ToAsciiLower() == 't') && - (Data[Position + 1].ToChar(null).ToAsciiLower() == 'r') && - (Data[Position + 2].ToChar(null).ToAsciiLower() == 'u') && - (Data[Position + 3].ToChar(null).ToAsciiLower() == 'e'), + _ => Position + 4 <= End && + (Position[0].ToChar(null).ToAsciiLower() == 't') && + (Position[1].ToChar(null).ToAsciiLower() == 'r') && + (Position[2].ToChar(null).ToAsciiLower() == 'u') && + (Position[3].ToChar(null).ToAsciiLower() == 'e'), }; } - public short ExtractInt16() - { - if (ExtractInt16(out short value)) - return value; - throw new Exception("Data for Int16 not present"); - } - - public ushort ExtractUInt16() - { - if (ExtractUInt16(out ushort value)) - return value; - throw new Exception("Data for UInt16 not present"); - } - - public int ExtractInt32() - { - if (ExtractInt32(out int value)) - return value; - throw new Exception("Data for Int32 not present"); - } - - public uint ExtractUInt32() - { - if (ExtractUInt32(out uint value)) - return value; - throw new Exception("Data for UInt32 not present"); - } - - public long ExtractInt64() - { - if (ExtractInt64(out long value)) - return value; - throw new Exception("Data for Int64 not present"); - } - - public ulong ExtractUInt64() - { - if (ExtractUInt64(out ulong value)) - return value; - throw new Exception("Data for UInt64 not present"); - } - - public float ExtractFloat() - { - if (ExtractFloat(out float value)) - return value; - throw new Exception("Data for Float not present"); - } - - public double ExtractDouble() - { - if (ExtractDouble(out double value)) - return value; - throw new Exception("Data for Double not present"); - } - private void SkipDigits() { - while (Position < Length) + while (Position < End) { - char ch = Data[Position].ToChar(null); + char ch = Position->ToChar(null); if (ch < '0' || '9' < ch) { break; @@ -246,12 +193,12 @@ private void SkipDigits() private bool InternalExtractSigned(out long value, long hardMax, long hardMin, long softMax) { value = 0; - if (Position >= Length) + if (Position >= End) { return false; } - char ch = Data[Position].ToChar(null); + char ch = Position->ToChar(null); long sign = 1; switch (ch) @@ -261,11 +208,11 @@ private bool InternalExtractSigned(out long value, long hardMax, long hardMin, l goto case '+'; case '+': ++Position; - if (Position >= Length) + if (Position >= End) { return false; } - ch = Data[Position].ToChar(null); + ch = Position->ToChar(null); break; } @@ -279,9 +226,9 @@ private bool InternalExtractSigned(out long value, long hardMax, long hardMin, l value += ch - '0'; ++Position; - if (Position < Length) + if (Position < End) { - ch = Data[Position].ToChar(null); + ch = Position->ToChar(null); if ('0' <= ch && ch <= '9') { if (value < softMax || value == softMax && ch <= LAST_DIGIT_SIGNED) @@ -304,20 +251,20 @@ private bool InternalExtractSigned(out long value, long hardMax, long hardMin, l private bool InternalExtractUnsigned(out ulong value, ulong hardMax, ulong softMax) { value = 0; - if (Position >= Length) + if (Position >= End) { return false; } - char ch = Data[Position].ToChar(null); + char ch = Position->ToChar(null); if (ch == '+') { ++Position; - if (Position >= Length) + if (Position >= End) { return false; } - ch = Data[Position].ToChar(null); + ch = Position->ToChar(null); } if (ch < '0' || '9' < ch) @@ -328,9 +275,9 @@ private bool InternalExtractUnsigned(out ulong value, ulong hardMax, ulong softM value += (ulong)(ch - '0'); ++Position; - if (Position < Length) + if (Position < End) { - ch = Data[Position].ToChar(null); + ch = Position->ToChar(null); if ('0' <= ch && ch <= '9') { if (value < softMax || value == softMax && ch <= LAST_DIGIT_UNSIGNED) diff --git a/YARG.Core/IO/TextReader/YARGTextReader.cs b/YARG.Core/IO/TextReader/YARGTextReader.cs index 73aa040f9..a1cd9b97f 100644 --- a/YARG.Core/IO/TextReader/YARGTextReader.cs +++ b/YARG.Core/IO/TextReader/YARGTextReader.cs @@ -4,17 +4,21 @@ namespace YARG.Core.IO { - public static class YARGTextLoader + public static unsafe class YARGTextReader { private static readonly UTF32Encoding UTF32BE = new(true, false); - public static YARGTextReader? TryLoadByteText(FixedArray data) + public static bool TryLoadByteText(FixedArray data, out YARGTextContainer container) { if ((data[0] == 0xFF && data[1] == 0xFE) || (data[0] == 0xFE && data[1] == 0xFF)) - return null; + { + container = default; + return false; + } int position = data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF ? 3 : 0; - return new YARGTextReader(data, position); + container = new YARGTextContainer(data, position); + return true; } public static FixedArray ConvertToChar(FixedArray data) @@ -44,322 +48,318 @@ public static FixedArray ConvertToChar(FixedArray data) var charData = AllocatedArray.Alloc(length); unsafe { - encoding.GetChars(data.Ptr + offset, (int)(data.Length - offset), charData.Ptr, (int)charData.Length); + encoding.GetChars(data.Ptr + offset, (int) (data.Length - offset), charData.Ptr, (int) charData.Length); } return charData; } - } - public static class YARGTextReader - { - public static char SkipWhitespace(YARGTextContainer container) + public static void SkipPureWhitespace(ref YARGTextContainer container) where TChar : unmanaged, IConvertible { - while (container.Position < container.Length) + while (container.Position < container.End && container.Position->ToChar(null) <= 32) { - char ch = container.Data[container.Position].ToChar(null); - if (ch <= 32) - { - if (ch == '\n') - return ch; - } - else if (ch != '=') - return ch; ++container.Position; } - return (char) 0; } - } - - public sealed class YARGTextReader - where TChar : unmanaged, IConvertible - where TDecoder : IStringDecoder, new() - { - private readonly TDecoder decoder = new(); - public readonly YARGTextContainer Container; - public YARGTextReader(FixedArray data, long position) + /// + /// Skips all whitespace starting at the current position of the provided container, + /// until the end of the current line. + /// + /// "\n" is not included as whitespace in this version + /// Type of data contained + /// Buffer of data + /// The current character that halted skipping, or 0 if at EoF + public static char SkipWhitespace(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - Container = new YARGTextContainer(data, position); - while (Container.Position < Container.Length) + while (container.Position < container.End) { - char curr = Container.Data[Container.Position].ToChar(null); - if (curr > 32 && curr != '{' && curr != '=') + char ch = container.Position->ToChar(null); + if (ch > 32 || ch == '\n') { - break; + return ch; } - ++Container.Position; + ++container.Position; } + return (char) 0; } - public char SkipWhitespace() + public static void SkipWhitespaceAndEquals(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - return YARGTextReader.SkipWhitespace(Container); + if (SkipWhitespace(ref container) == '=') + { + ++container.Position; + SkipWhitespace(ref container); + } } - public void GotoNextLine() + public static void GotoNextLine(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - char curr = default; - while (Container.Position < Container.Length) + while (container.Position < container.End) { - curr = Container.Data[Container.Position].ToChar(null); - ++Container.Position; + char curr = container.Position->ToChar(null); + ++container.Position; if (curr == '\n') { + SkipPureWhitespace(ref container); break; } } - - while (Container.Position < Container.Length) - { - curr = Container.Data[Container.Position].ToChar(null); - if (curr > 32 && curr != '{' && curr != '=') - { - break; - - } - ++Container.Position; - } } - public void SkipLinesUntil(char stopCharacter) + public static bool SkipLinesUntil(ref YARGTextContainer container, char stopCharacter) + where TChar : unmanaged, IConvertible { - GotoNextLine(); - while (Container.Position < Container.Length) + GotoNextLine(ref container); + var pivot = container.Position; + while (container.Position < container.End) { - if (Container.Data[Container.Position].ToChar(null) == stopCharacter) + if (container.Position->ToChar(null) == stopCharacter) { // Runs a check to ensure that the character is the start of the line - long test = Container.Position - 1; - char character = Container.Data[test].ToChar(null); - while (test > 0 && character <= 32 && character != '\n') + var test = container.Position - 1; + char character = test->ToChar(null); + while (test > pivot && character <= 32 && character != '\n') { --test; - character = Container.Data[test].ToChar(null); + character = test->ToChar(null); } if (character == '\n') - break; + { + return true; + } + pivot = container.Position; } - ++Container.Position; + ++container.Position; } + return false; } - public string ExtractModifierName() + public static unsafe string ExtractModifierName(ref YARGTextContainer container, in delegate* decoder) + where TChar : unmanaged, IConvertible { - long curr = Container.Position; - while (curr < Container.Length) + var curr = container.Position; + while (curr < container.End) { - char b = Container.Data[curr].ToChar(null); + char b = curr->ToChar(null); if (b <= 32 || b == '=') + { break; + } ++curr; } - string name = decoder.Decode(Container.Data, Container.Position, curr - Container.Position); - Container.Position = curr; - SkipWhitespace(); + string name = decoder(container.Position, curr - container.Position); + container.Position = curr; + SkipWhitespaceAndEquals(ref container); return name; } - public string PeekLine() + public static unsafe string PeekLine(ref YARGTextContainer container, in delegate* decoder) + where TChar : unmanaged, IConvertible { - var curr = Container.Position; - while (curr < Container.Length && Container.Data[curr].ToChar(null) != '\n') + var curr = container.Position; + while (curr < container.End && curr->ToChar(null) != '\n') { ++curr; } - return decoder.Decode(Container.Data, Container.Position, curr - Container.Position).TrimEnd(); + return decoder(container.Position, curr - container.Position).TrimEnd(); } - public string ExtractText(bool isChartFile) + public static unsafe string ExtractText(ref YARGTextContainer container, in delegate* decoder, bool isChartFile) + where TChar : unmanaged, IConvertible { - var stringBegin = Container.Position; - long stringEnd = -1; - if (isChartFile && Container.Position < Container.Length && Container.Data[Container.Position].ToChar(null) == '\"') + var stringBegin = container.Position; + TChar* stringEnd = null; + if (isChartFile && container.Position < container.End && container.Position->ToChar(null) == '\"') { while (true) { - ++Container.Position; - if (Container.Position == Container.Length) + ++container.Position; + if (container.Position == container.End) { break; } - char ch = Container.Data[Container.Position].ToChar(null); + char ch = container.Position->ToChar(null); if (ch == '\n') { break; } - if (stringEnd == -1) + if (stringEnd == null) { - if (ch == '\"' && Container.Data[Container.Position - 1].ToChar(null) != '\\') + if (ch == '\"' && container.Position[-1].ToChar(null) != '\\') { ++stringBegin; - stringEnd = Container.Position; + stringEnd = container.Position; } else if (ch == '\r') { - stringEnd = Container.Position; + stringEnd = container.Position; } } } } else { - while (Container.Position < Container.Length) + while (container.Position < container.End) { - char ch = Container.Data[Container.Position].ToChar(null); + char ch = container.Position->ToChar(null); if (ch == '\n') { break; } - if (ch == '\r' && stringEnd == -1) + if (ch == '\r' && stringEnd == null) { - stringEnd = Container.Position; + stringEnd = container.Position; } - ++Container.Position; + ++container.Position; } } - if (stringEnd == -1) + if (stringEnd == null) { - stringEnd = Container.Position; + stringEnd = container.Position; } - while (stringBegin < stringEnd && Container.Data[stringEnd - 1].ToChar(null) <= 32) + while (stringBegin < stringEnd && stringEnd[-1].ToChar(null) <= 32) --stringEnd; - return decoder.Decode(Container.Data, stringBegin, stringEnd - stringBegin); + return decoder(stringBegin, stringEnd - stringBegin); } - public bool ExtractBoolean() - { - bool result = Container.ExtractBoolean(); - SkipWhitespace(); - return result; - } - - public short ExtractInt16() - { - short result = Container.ExtractInt16(); - SkipWhitespace(); - return result; - } - - public ushort ExtractUInt16() - { - ushort result = Container.ExtractUInt16(); - SkipWhitespace(); - return result; - } - - public int ExtractInt32() - { - int result = Container.ExtractInt32(); - SkipWhitespace(); - return result; - } - - public uint ExtractUInt32() - { - uint result = Container.ExtractUInt32(); - SkipWhitespace(); - return result; - } - - public long ExtractInt64() - { - long result = Container.ExtractInt64(); - SkipWhitespace(); - return result; - } - - public ulong ExtractUInt64() - { - ulong result = Container.ExtractUInt64(); - SkipWhitespace(); - return result; - } - - public float ExtractFloat() - { - float result = Container.ExtractFloat(); - SkipWhitespace(); - return result; - } - - public double ExtractDouble() - { - double result = Container.ExtractDouble(); - SkipWhitespace(); - return result; - } - - public bool ExtractInt16(out short value) + /// + /// Extracts a short and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The short + public static short ExtractInt16(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - if (!Container.ExtractInt16(out value)) - return false; - SkipWhitespace(); - return true; + if (!container.TryExtractInt16(out short value)) + { + throw new Exception("Data for Int16 not present"); + } + SkipWhitespace(ref container); + return value; } - public bool ExtractUInt16(out ushort value) + /// + /// Extracts a ushort and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The ushort + public static ushort ExtractUInt16(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - if (!Container.ExtractUInt16(out value)) - return false; - SkipWhitespace(); - return true; + if (!container.TryExtractUInt16(out ushort value)) + { + throw new Exception("Data for UInt16 not present"); + } + SkipWhitespace(ref container); + return value; } - public bool ExtractInt32(out int value) + /// + /// Extracts a int and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The int + public static int ExtractInt32(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - if (!Container.ExtractInt32(out value)) - return false; - SkipWhitespace(); - return true; + if (!container.TryExtractInt32(out int value)) + { + throw new Exception("Data for Int32 not present"); + } + SkipWhitespace(ref container); + return value; } - public bool ExtractUInt32(out uint value) + /// + /// Extracts a uint and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The uint + public static uint ExtractUInt32(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - if (!Container.ExtractUInt32(out value)) - return false; - SkipWhitespace(); - return true; + if (!container.TryExtractUInt32(out uint value)) + { + throw new Exception("Data for UInt32 not present"); + } + SkipWhitespace(ref container); + return value; } - public bool ExtractInt64(out long value) + /// + /// Extracts a long and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The long + public static long ExtractInt64(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - if (!Container.ExtractInt64(out value)) - return false; - SkipWhitespace(); - return true; + if (!container.TryExtractInt64(out long value)) + { + throw new Exception("Data for Int64 not present"); + } + SkipWhitespace(ref container); + return value; } - public bool ExtractUInt64(out ulong value) + /// + /// Extracts a ulong and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The ulong + public static ulong ExtractUInt64(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - if (!Container.ExtractUInt64(out value)) - return false; - SkipWhitespace(); - return true; + if (!container.TryExtractUInt64(out ulong value)) + { + throw new Exception("Data for UInt64 not present"); + } + SkipWhitespace(ref container); + return value; } - public bool ExtractFloat(out float value) + /// + /// Extracts a float and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The float + public static float ExtractFloat(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - if (!Container.ExtractFloat(out value)) - return false; - SkipWhitespace(); - return true; + if (!container.TryExtractFloat(out float value)) + { + throw new Exception("Data for Int16 not present"); + } + SkipWhitespace(ref container); + return value; } - public bool ExtractDouble(out double value) + /// + /// Extracts a double and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The double + public static double ExtractDouble(ref YARGTextContainer container) + where TChar : unmanaged, IConvertible { - if (!Container.ExtractDouble(out value)) - return false; - SkipWhitespace(); - return true; + if (!container.TryExtractDouble(out double value)) + { + throw new Exception("Data for Int16 not present"); + } + SkipWhitespace(ref container); + return value; } } } diff --git a/YARG.Core/IO/YARGChartFileReader.cs b/YARG.Core/IO/YARGChartFileReader.cs index 88188670f..64dc4843b 100644 --- a/YARG.Core/IO/YARGChartFileReader.cs +++ b/YARG.Core/IO/YARGChartFileReader.cs @@ -35,303 +35,155 @@ public enum NoteTracks_Chart public struct DotChartEvent { - private long _position; - + public long Position; public ChartEventType Type; - public long Position - { - get { return _position; } - set - { - if (_position <= value) - _position = value; - else - throw new Exception($".chart position out of order (previous: {_position})"); - } - } } - public struct DotChartNote + public static class YARGChartFileReader { - public int Lane; - public long Duration; - } + public const string HEADERTRACK = "[Song]"; + public const string SYNCTRACK = "[SyncTrack]"; + public const string EVENTTRACK = "[Events]"; - public readonly struct DotChartEventCombo - where T : unmanaged, IEquatable - { - public readonly T[] descriptor; - public readonly ChartEventType eventType; - public DotChartEventCombo(ReadOnlySpan descriptor, ChartEventType eventType) + internal static readonly (string name, Difficulty difficulty)[] DIFFICULTIES = { - this.descriptor = descriptor.ToArray(); - this.eventType = eventType; - } - - public bool DoesEventMatch(ReadOnlySpan span) - { - if (span.Length != descriptor.Length) - return false; - - for (int i = 0; i < descriptor.Length; i++) - if (!span[i].Equals(descriptor[i])) - return false; - return true; - } - } - - public interface IDotChartBases - where T : unmanaged, IEquatable - { - public ReadOnlySpan HEADERTRACK { get; } - public ReadOnlySpan SYNCTRACK { get; } - public ReadOnlySpan EVENTTRACK { get; } - public DotChartEventCombo TEMPO { get; } - public DotChartEventCombo TIMESIG { get; } - public DotChartEventCombo ANCHOR { get; } - public DotChartEventCombo TEXT { get; } - public DotChartEventCombo NOTE { get; } - public DotChartEventCombo SPECIAL { get; } - - public (T[], Difficulty)[] DIFFICULTIES { get; } - public (T[], NoteTracks_Chart)[] NOTETRACKS { get; } - public DotChartEventCombo[] EVENTS_SYNC { get; } - public DotChartEventCombo[] EVENTS_EVENTS { get; } - public DotChartEventCombo[] EVENTS_DIFF { get; } - } - - public readonly struct DotChartByte : IDotChartBases - { - private static readonly byte[] _HEADERTRACK = Encoding.ASCII.GetBytes("[Song]"); - private static readonly byte[] _SYNCTRACK = Encoding.ASCII.GetBytes("[SyncTrack]"); - private static readonly byte[] _EVENTTRACK = Encoding.ASCII.GetBytes("[Events]"); - private static readonly DotChartEventCombo _TEMPO = new(Encoding.ASCII.GetBytes("B"), ChartEventType.Bpm); - private static readonly DotChartEventCombo _TIMESIG = new(Encoding.ASCII.GetBytes("TS"), ChartEventType.Time_Sig); - private static readonly DotChartEventCombo _ANCHOR = new(Encoding.ASCII.GetBytes("A"), ChartEventType.Anchor); - private static readonly DotChartEventCombo _TEXT = new(Encoding.ASCII.GetBytes("E"), ChartEventType.Text); - private static readonly DotChartEventCombo _NOTE = new(Encoding.ASCII.GetBytes("N"), ChartEventType.Note); - private static readonly DotChartEventCombo _SPECIAL = new(Encoding.ASCII.GetBytes("S"), ChartEventType.Special); - - private static readonly (byte[] name, Difficulty difficulty)[] _DIFFICULTIES = - { - (Encoding.ASCII.GetBytes("[Easy"), Difficulty.Easy), - (Encoding.ASCII.GetBytes("[Medium"), Difficulty.Medium), - (Encoding.ASCII.GetBytes("[Hard"), Difficulty.Hard), - (Encoding.ASCII.GetBytes("[Expert"), Difficulty.Expert), + ("[Easy", Difficulty.Easy), + ("[Medium", Difficulty.Medium), + ("[Hard", Difficulty.Hard), + ("[Expert", Difficulty.Expert), }; - private static readonly (byte[], NoteTracks_Chart)[] _NOTETRACKS = - { - new(Encoding.ASCII.GetBytes("Single]"), NoteTracks_Chart.Single ), - new(Encoding.ASCII.GetBytes("DoubleGuitar]"), NoteTracks_Chart.DoubleGuitar ), - new(Encoding.ASCII.GetBytes("DoubleBass]"), NoteTracks_Chart.DoubleBass ), - new(Encoding.ASCII.GetBytes("DoubleRhythm]"), NoteTracks_Chart.DoubleRhythm ), - new(Encoding.ASCII.GetBytes("Drums]"), NoteTracks_Chart.Drums ), - new(Encoding.ASCII.GetBytes("Keyboard]"), NoteTracks_Chart.Keys ), - new(Encoding.ASCII.GetBytes("GHLGuitar]"), NoteTracks_Chart.GHLGuitar ), - new(Encoding.ASCII.GetBytes("GHLBass]"), NoteTracks_Chart.GHLBass ), - new(Encoding.ASCII.GetBytes("GHLRhythm]"), NoteTracks_Chart.GHLGuitar ), - new(Encoding.ASCII.GetBytes("GHLCoop]"), NoteTracks_Chart.GHLBass ), + internal static readonly (string, Instrument)[] NOTETRACKS = + { + new("Single]", Instrument.FiveFretGuitar), + new("DoubleGuitar]", Instrument.FiveFretCoopGuitar), + new("DoubleBass]", Instrument.FiveFretBass), + new("DoubleRhythm]", Instrument.FiveFretRhythm), + new("Drums]", Instrument.FourLaneDrums), + new("Keyboard]", Instrument.Keys), + new("GHLGuitar]", Instrument.SixFretGuitar), + new("GHLBass]", Instrument.SixFretBass), + new("GHLRhythm]", Instrument.SixFretRhythm), + new("GHLCoop]", Instrument.SixFretCoopGuitar), }; - private static readonly DotChartEventCombo[] _EVENTS_SYNC = { _TEMPO, _TIMESIG, _ANCHOR }; - private static readonly DotChartEventCombo[] _EVENTS_EVENTS = { _TEXT, }; - private static readonly DotChartEventCombo[] _EVENTS_DIFF = { _NOTE, _SPECIAL, _TEXT, }; - - public ReadOnlySpan HEADERTRACK => _HEADERTRACK; - public ReadOnlySpan SYNCTRACK => _SYNCTRACK; - public ReadOnlySpan EVENTTRACK => _EVENTTRACK; - public DotChartEventCombo TEMPO => _TEMPO; - public DotChartEventCombo TIMESIG => _TIMESIG; - public DotChartEventCombo ANCHOR => _ANCHOR; - public DotChartEventCombo TEXT => _TEXT; - public DotChartEventCombo NOTE => _NOTE; - public DotChartEventCombo SPECIAL => _SPECIAL; - - public (byte[], Difficulty)[] DIFFICULTIES => _DIFFICULTIES; - public (byte[], NoteTracks_Chart)[] NOTETRACKS => _NOTETRACKS; - public DotChartEventCombo[] EVENTS_SYNC => _EVENTS_SYNC; - public DotChartEventCombo[] EVENTS_EVENTS => _EVENTS_EVENTS; - public DotChartEventCombo[] EVENTS_DIFF => _EVENTS_DIFF; - } - - public readonly struct DotChartChar : IDotChartBases - { - private static readonly string _HEADERTRACK = "[Song]"; - private static readonly string _SYNCTRACK = "[SyncTrack]"; - private static readonly string _EVENTTRACK = "[Events]"; - private static readonly DotChartEventCombo _TEMPO = new("B", ChartEventType.Bpm); - private static readonly DotChartEventCombo _TIMESIG = new("TS", ChartEventType.Time_Sig); - private static readonly DotChartEventCombo _ANCHOR = new("A", ChartEventType.Anchor); - private static readonly DotChartEventCombo _TEXT = new("E", ChartEventType.Text); - private static readonly DotChartEventCombo _NOTE = new("N", ChartEventType.Note); - private static readonly DotChartEventCombo _SPECIAL = new("S", ChartEventType.Special); - - private static readonly (char[] name, Difficulty difficulty)[] _DIFFICULTIES = - { - ("[Easy".ToCharArray(), Difficulty.Easy), - ("[Medium".ToCharArray(), Difficulty.Medium), - ("[Hard".ToCharArray(), Difficulty.Hard), - ("[Expert".ToCharArray(), Difficulty.Expert), - }; - - private static readonly (char[], NoteTracks_Chart)[] _NOTETRACKS = + internal static readonly (string Descriptor, ChartEventType Type)[] EVENTS = { - new("Single]".ToCharArray(), NoteTracks_Chart.Single ), - new("DoubleGuitar]".ToCharArray(), NoteTracks_Chart.DoubleGuitar ), - new("DoubleBass]".ToCharArray(), NoteTracks_Chart.DoubleBass ), - new("DoubleRhythm]".ToCharArray(), NoteTracks_Chart.DoubleRhythm ), - new("Drums]".ToCharArray(), NoteTracks_Chart.Drums ), - new("Keyboard]".ToCharArray(), NoteTracks_Chart.Keys ), - new("GHLGuitar]".ToCharArray(), NoteTracks_Chart.GHLGuitar ), - new("GHLBass]".ToCharArray(), NoteTracks_Chart.GHLBass ), - new("GHLRhythm]".ToCharArray(), NoteTracks_Chart.GHLGuitar ), - new("GHLCoop]".ToCharArray(), NoteTracks_Chart.GHLBass ), + new("B", ChartEventType.Bpm), + new("TS", ChartEventType.Time_Sig), + new("A", ChartEventType.Anchor), + new("E", ChartEventType.Text), + new("N", ChartEventType.Note), + new("S", ChartEventType.Special), }; - private static readonly DotChartEventCombo[] _EVENTS_SYNC = { _TEMPO, _TIMESIG, _ANCHOR }; - private static readonly DotChartEventCombo[] _EVENTS_EVENTS = { _TEXT, }; - private static readonly DotChartEventCombo[] _EVENTS_DIFF = { _NOTE, _SPECIAL, _TEXT, }; - - public ReadOnlySpan HEADERTRACK => _HEADERTRACK; - public ReadOnlySpan SYNCTRACK => _SYNCTRACK; - public ReadOnlySpan EVENTTRACK => _EVENTTRACK; - public DotChartEventCombo TEMPO => _TEMPO; - public DotChartEventCombo TIMESIG => _TIMESIG; - public DotChartEventCombo ANCHOR => _ANCHOR; - public DotChartEventCombo TEXT => _TEXT; - public DotChartEventCombo NOTE => _NOTE; - public DotChartEventCombo SPECIAL => _SPECIAL; - public (char[], Difficulty)[] DIFFICULTIES => _DIFFICULTIES; - public (char[], NoteTracks_Chart)[] NOTETRACKS => _NOTETRACKS; - public DotChartEventCombo[] EVENTS_SYNC => _EVENTS_SYNC; - public DotChartEventCombo[] EVENTS_EVENTS => _EVENTS_EVENTS; - public DotChartEventCombo[] EVENTS_DIFF => _EVENTS_DIFF; - } - - public sealed class YARGChartFileReader - where TChar : unmanaged, IEquatable, IConvertible - where TDecoder : IStringDecoder, new() - where TBase : unmanaged, IDotChartBases - { - private static readonly TBase CONFIG = default; - private readonly YARGTextReader reader; - - private DotChartEventCombo[] eventSet = Array.Empty>(); - private NoteTracks_Chart _instrument; - private Difficulty _difficulty; - - public NoteTracks_Chart Instrument => _instrument; - public Difficulty Difficulty => _difficulty; - - public YARGChartFileReader(YARGTextReader reader) - { - this.reader = reader; - } - - public bool IsStartOfTrack() + public static bool IsStartOfTrack(in YARGTextContainer container) + where TChar : unmanaged, IEquatable, IConvertible { - return !reader.Container.IsEndOfFile() && reader.Container.IsCurrentCharacter('['); + return !container.IsAtEnd() && container.IsCurrentCharacter('['); } - public bool ValidateHeaderTrack() + public static bool ValidateTrack(ref YARGTextContainer container, string track) + where TChar : unmanaged, IEquatable, IConvertible { - return ValidateTrack(CONFIG.HEADERTRACK); - } - - public bool ValidateSyncTrack() - { - if (!ValidateTrack(CONFIG.SYNCTRACK)) + if (!DoesStringMatch(ref container, track)) return false; - eventSet = CONFIG.EVENTS_SYNC; + YARGTextReader.GotoNextLine(ref container); return true; } - public bool ValidateEventsTrack() + public static bool ValidateInstrument(ref YARGTextContainer container, out Instrument instrument, out Difficulty difficulty) + where TChar : unmanaged, IEquatable, IConvertible { - if (!ValidateTrack(CONFIG.EVENTTRACK)) - return false; - - eventSet = CONFIG.EVENTS_EVENTS; - return true; - } - - public bool ValidateDifficulty() - { - for (int diff = 3; diff >= 0; --diff) + if (ValidateDifficulty(ref container, out difficulty)) { - var (name, difficulty) = CONFIG.DIFFICULTIES[diff]; - if (DoesStringMatch(name)) + foreach (var (name, inst) in YARGChartFileReader.NOTETRACKS) { - _difficulty = difficulty; - eventSet = CONFIG.EVENTS_DIFF; - reader.Container.Position += name.Length; - return true; + if (ValidateTrack(ref container, name)) + { + instrument = inst; + return true; + } } } + instrument = default; return false; } - public bool ValidateInstrument() + private static unsafe bool ValidateDifficulty(ref YARGTextContainer container, out Difficulty difficulty) + where TChar : unmanaged, IEquatable, IConvertible { - foreach (var track in CONFIG.NOTETRACKS) + for (int diffIndex = 3; diffIndex >= 0; --diffIndex) { - if (ValidateTrack(track.Item1)) + var (name, diff) = YARGChartFileReader.DIFFICULTIES[diffIndex]; + if (DoesStringMatch(ref container, name)) { - _instrument = track.Item2; + difficulty = diff; + container.Position += name.Length; return true; } } + difficulty = default; return false; } - private bool ValidateTrack(ReadOnlySpan track) - { - if (!DoesStringMatch(track)) - return false; - - reader.GotoNextLine(); - return true; - } - - private bool DoesStringMatch(ReadOnlySpan str) + private static unsafe bool DoesStringMatch(ref YARGTextContainer container, string str) + where TChar : unmanaged, IEquatable, IConvertible { int index = 0; + var position = container.Position; while (index < str.Length - && reader.Container.Position + index < reader.Container.Length - && reader.Container.Data[reader.Container.Position + index].Equals(str[index])) + && position < container.End + && position->ToChar(null) == str[index]) { ++index; + ++position; } return index == str.Length; } - public bool IsStillCurrentTrack() + public static bool IsStillCurrentTrack(ref YARGTextContainer container) + where TChar : unmanaged, IEquatable, IConvertible { - if (reader.Container.Position == reader.Container.Length) + YARGTextReader.GotoNextLine(ref container); + if (container.IsAtEnd()) + { return false; + } - if (reader.Container.IsCurrentCharacter('}')) + if (container.IsCurrentCharacter('}')) { - reader.GotoNextLine(); + YARGTextReader.GotoNextLine(ref container); return false; } - return true; } - public bool TryParseEvent(ref DotChartEvent ev) + public static unsafe bool TryParseEvent(ref YARGTextContainer container, ref DotChartEvent ev) + where TChar : unmanaged, IEquatable, IConvertible { - if (!IsStillCurrentTrack()) + if (!IsStillCurrentTrack(ref container)) + { return false; + } - ev.Position = reader.ExtractInt64(); + if (!container.TryExtractInt64(out long position)) + { + throw new Exception("Could not parse event position"); + } + + if (position < ev.Position) + { + throw new Exception($".chart position out of order (previous: {ev.Position})"); + } + ev.Position = position; + YARGTextReader.SkipWhitespaceAndEquals(ref container); - var curr = reader.Container.Position; - while (curr < reader.Container.Length) + var curr = container.Position; + while (curr < container.End) { - int c = reader.Container.Data[curr].ToChar(null) | CharacterExtensions.ASCII_LOWERCASE_FLAG; + int c = curr->ToChar(null) | CharacterExtensions.ASCII_LOWERCASE_FLAG; if (c < 'a' || 'z' < c) { break; @@ -339,65 +191,49 @@ public bool TryParseEvent(ref DotChartEvent ev) ++curr; } - unsafe + var span = new ReadOnlySpan(container.Position, (int) (curr - container.Position)); + container.Position = curr; + ev.Type = ChartEventType.Unknown; + foreach (var combo in EVENTS) { - var span = new ReadOnlySpan(reader.Container.Data.Ptr + reader.Container.Position, (int)(curr - reader.Container.Position)); - reader.Container.Position = curr; - foreach (var combo in eventSet) + if (span.Length != combo.Descriptor.Length) { - if (combo.DoesEventMatch(span)) - { - reader.SkipWhitespace(); - ev.Type = combo.eventType; - return true; - } + continue; } - } - - ev.Type = ChartEventType.Unknown; - return true; - } - - public void SkipEvent() - { - reader.GotoNextLine(); - } - - public void NextEvent() - { - reader.GotoNextLine(); - } - public void ExtractLaneAndSustain(ref DotChartNote note) - { - note.Lane = reader.ExtractInt32(); - note.Duration = reader.ExtractInt64(); - } + int index = 0; + while (index < span.Length && span[index].ToChar(null) == combo.Descriptor[index]) + { + ++index; + } - public void SkipTrack() - { - reader.SkipLinesUntil('}'); - if (!reader.Container.IsEndOfFile()) - reader.GotoNextLine(); + if (index == span.Length) + { + YARGTextReader.SkipWhitespace(ref container); + ev.Type = combo.Type; + break; + } + } + return true; } - public Dictionary> ExtractModifiers(Dictionary validNodes) + public unsafe static Dictionary> ExtractModifiers(ref YARGTextContainer container, in delegate* decoder, Dictionary validNodes) + where TChar : unmanaged, IEquatable, IConvertible { Dictionary> modifiers = new(); - while (IsStillCurrentTrack()) + while (IsStillCurrentTrack(ref container)) { - string name = reader.ExtractModifierName(); + string name = YARGTextReader.ExtractModifierName(ref container, decoder); if (validNodes.TryGetValue(name, out var node)) { - var mod = node.CreateModifier(reader); + var mod = node.CreateModifier(ref container, decoder); if (modifiers.TryGetValue(node.outputName, out var list)) list.Add(mod); else modifiers.Add(node.outputName, new() { mod }); } - reader.GotoNextLine(); } return modifiers; } } -} +} \ No newline at end of file diff --git a/YARG.Core/IO/YARGDTAReader.cs b/YARG.Core/IO/YARGDTAReader.cs index 899809dde..78f070fc3 100644 --- a/YARG.Core/IO/YARGDTAReader.cs +++ b/YARG.Core/IO/YARGDTAReader.cs @@ -8,58 +8,50 @@ namespace YARG.Core.IO { - public sealed class YARGDTAReader + public unsafe struct YARGDTAReader { - public static YARGDTAReader? TryCreate(FixedArray data) + public static bool TryCreate(FixedArray data, out YARGDTAReader reader) { if ((data[0] == 0xFF && data[1] == 0xFE) || (data[0] == 0xFE && data[1] == 0xFF)) { YargLogger.LogError("UTF-16 & UTF-32 are not supported for .dta files"); - return null; + reader = default; + return false; } - int position = data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF ? 3 : 0; - return new YARGDTAReader(data, position); + reader = data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF + ? new YARGDTAReader(data, 3, Encoding.UTF8) + : new YARGDTAReader(data, 0, YARGTextContainer.Latin1); + return true; } - private readonly YARGTextContainer container; - public Encoding encoding; + private YARGTextContainer _container; + public Encoding Encoding; - private YARGDTAReader(FixedArray data, int position) + private YARGDTAReader(FixedArray data, int position, Encoding encoding) { - container = new YARGTextContainer(data, position); - encoding = position == 3 ? Encoding.UTF8 : YARGTextContainer.Latin1; + _container = new YARGTextContainer(data, position); + Encoding = encoding; SkipWhitespace(); } - public YARGDTAReader Clone() - { - return new YARGDTAReader(this); - } - - private YARGDTAReader(YARGDTAReader reader) - { - container = new(reader.container); - encoding = reader.encoding; - } - public char SkipWhitespace() { - while (container.Position < container.Length) + while (_container.Position < _container.End) { - char ch = (char) container.Data[container.Position]; + char ch = (char) *_container.Position; if (ch > 32 && ch != ';') { return ch; } - ++container.Position; + ++_container.Position; if (ch > 32) { // In comment - while (container.Position < container.Length) + while (_container.Position < _container.End) { - if (container.Data[container.Position++] == '\n') + if (*_container.Position++ == '\n') { break; } @@ -69,9 +61,9 @@ public char SkipWhitespace() return (char) 0; } - public string GetNameOfNode(bool allowNonAlphetical) + public unsafe string GetNameOfNode(bool allowNonAlphetical) { - char ch = (char)container.Data[container.Position]; + char ch = (char) _container.CurrentValue; if (ch == '(') { return string.Empty; @@ -80,16 +72,12 @@ public string GetNameOfNode(bool allowNonAlphetical) bool hasApostrophe = ch == '\''; if (hasApostrophe) { - ++container.Position; - if (container.Position >= container.Length) - { - throw new EndOfStreamException(); - } - ch = (char) container.Data[container.Position]; + ++_container.Position; + ch = (char) _container.CurrentValue; } - long start = container.Position; - long end = container.Position; + var start = _container.Position; + var end = _container.Position; while (true) { if (ch == '\'') @@ -98,7 +86,7 @@ public string GetNameOfNode(bool allowNonAlphetical) { throw new Exception("Invalid name format"); } - container.Position = end + 1; + _container.Position = end + 1; break; } @@ -108,23 +96,27 @@ public string GetNameOfNode(bool allowNonAlphetical) { throw new Exception("Invalid name format"); } - container.Position = end + 1; + _container.Position = end + 1; break; } if (!allowNonAlphetical && !ch.IsAsciiLetter() && ch != '_') { - container.Position = end; + _container.Position = end; + break; + } + + ++end; + if (end >= _container.End) + { + _container.Position = end; break; } - ch = (char) container.Data[++end]; + ch = (char) *end; } SkipWhitespace(); - unsafe - { - return Encoding.UTF8.GetString(container.Data.Ptr + start,(int)(end - start)); - } + return Encoding.UTF8.GetString(start, (int) (end - start)); } private enum TextScopeState @@ -135,33 +127,24 @@ private enum TextScopeState Apostrophes } - public string ExtractText() + public unsafe string ExtractText() { - if (container.Position >= container.Length) - { - throw new EndOfStreamException(); - } - - char ch = (char)container.Data[container.Position]; + char ch = (char) _container.CurrentValue; var state = ch switch { - '{' => TextScopeState.Squirlies, + '{' => TextScopeState.Squirlies, '\"' => TextScopeState.Quotes, '\'' => TextScopeState.Apostrophes, - _ => TextScopeState.None + _ => TextScopeState.None }; if (state != TextScopeState.None) { - ++container.Position; - if (container.Position >= container.Length) - { - throw new EndOfStreamException(); - } - ch = (char) container.Data[container.Position]; + ++_container.Position; + ch = (char) _container.CurrentValue; } - var start = container.Position; + var start = _container.Position; // Loop til the end of the text is found while (true) { @@ -195,32 +178,25 @@ public string ExtractText() if (state == TextScopeState.Apostrophes) throw new Exception("Text error - no whitespace allowed"); } - ++container.Position; - if (container.Position >= container.Length) - { - throw new EndOfStreamException(); - } - ch = (char) container.Data[container.Position]; + ++_container.Position; + ch = (char) _container.CurrentValue; } - unsafe + string txt = Encoding.GetString(start, (int) (_container.Position - start)).Replace("\\q", "\""); + if (ch != ')') { - string txt = encoding.GetString(container.Data.Ptr + start, (int)(container.Position - start)).Replace("\\q", "\""); - if (ch != ')') - { - ++container.Position; + ++_container.Position; - } - SkipWhitespace(); - return txt; } + SkipWhitespace(); + return txt; } public int[] ExtractArray_Int() { bool doEnd = StartNode(); List values = new(); - while (container.Data[container.Position] != ')') + while (_container.CurrentValue != ')') { values.Add(ExtractInt32()); } @@ -236,7 +212,7 @@ public float[] ExtractArray_Float() { bool doEnd = StartNode(); List values = new(); - while (container.Data[container.Position] != ')') + while (_container.CurrentValue != ')') { values.Add(ExtractFloat()); } @@ -252,7 +228,7 @@ public string[] ExtractArray_String() { bool doEnd = StartNode(); List strings = new(); - while (container.Data[container.Position] != ')') + while (_container.CurrentValue != ')') { strings.Add(ExtractText()); } @@ -266,12 +242,12 @@ public string[] ExtractArray_String() public bool StartNode() { - if (container.Position >= container.Length || container.Data[container.Position] != '(') + if (_container.Position >= _container.End || !_container.IsCurrentCharacter('(')) { return false; } - ++container.Position; + ++_container.Position; SkipWhitespace(); return true; } @@ -282,10 +258,10 @@ public void EndNode() bool inApostropes = false; bool inQuotes = false; bool inComment = false; - while (container.Position < container.Length && scopeLevel >= 0) + while (_container.Position < _container.End && scopeLevel >= 0) { - char curr = (char) container.Data[container.Position]; - ++container.Position; + char curr = (char) *_container.Position; + ++_container.Position; if (inComment) { if (curr == '\n') @@ -322,83 +298,158 @@ public void EndNode() SkipWhitespace(); } + /// + /// Extracts a boolean and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The boolean or `false` on failed extraction public bool ExtractBoolean() { - bool result = container.ExtractBoolean(); + bool result = _container.ExtractBoolean(); SkipWhitespace(); return result; } - + + /// + /// Extracts a boolean and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The boolean or `true` on failed extraction public bool ExtractBoolean_FlippedDefault() { - bool result = container.Position >= container.Length || (char)container.Data[container.Position] switch + bool result = _container.Position >= _container.End || (char)*_container.Position switch { '0' => false, '1' => true, - _ => container.Position + 5 > container.Length || - char.ToLowerInvariant((char)container.Data[container.Position] ) != 'f' || - char.ToLowerInvariant((char)container.Data[container.Position + 1]) != 'a' || - char.ToLowerInvariant((char)container.Data[container.Position + 2]) != 'l' || - char.ToLowerInvariant((char)container.Data[container.Position + 3]) != 's' || - char.ToLowerInvariant((char)container.Data[container.Position + 4]) != 'e', + _ => _container.Position + 5 > _container.End || + char.ToLowerInvariant((char)_container.Position[0]) != 'f' || + char.ToLowerInvariant((char)_container.Position[1]) != 'a' || + char.ToLowerInvariant((char)_container.Position[2]) != 'l' || + char.ToLowerInvariant((char)_container.Position[3]) != 's' || + char.ToLowerInvariant((char)_container.Position[4]) != 'e', }; SkipWhitespace(); return result; } + /// + /// Extracts a short and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The short public short ExtractInt16() { - short result = container.ExtractInt16(); + if (!_container.TryExtractInt16(out short value)) + { + throw new Exception("Data for Int16 not present"); + } SkipWhitespace(); - return result; + return value; } + /// + /// Extracts a ushort and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The ushort public ushort ExtractUInt16() { - ushort result = container.ExtractUInt16(); + if (!_container.TryExtractUInt16(out ushort value)) + { + throw new Exception("Data for UInt16 not present"); + } SkipWhitespace(); - return result; + return value; } + /// + /// Extracts a int and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The int public int ExtractInt32() { - int result = container.ExtractInt32(); + if (!_container.TryExtractInt32(out int value)) + { + throw new Exception("Data for Int32 not present"); + } SkipWhitespace(); - return result; + return value; } + + /// + /// Extracts a uint and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The uint public uint ExtractUInt32() { - uint result = container.ExtractUInt32(); + if (!_container.TryExtractUInt32(out uint value)) + { + throw new Exception("Data for UInt32 not present"); + } SkipWhitespace(); - return result; + return value; } + /// + /// Extracts a long and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The long public long ExtractInt64() { - long result = container.ExtractInt64(); + if (!_container.TryExtractInt64(out long value)) + { + throw new Exception("Data for Int64 not present"); + } SkipWhitespace(); - return result; + return value; } + /// + /// Extracts a ulong and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The ulong public ulong ExtractUInt64() { - ulong result = container.ExtractUInt64(); + if (!_container.TryExtractUInt64(out ulong value)) + { + throw new Exception("Data for UInt64 not present"); + } SkipWhitespace(); - return result; + return value; } + /// + /// Extracts a float and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The float public float ExtractFloat() { - float result = container.ExtractFloat(); + if (!_container.TryExtractFloat(out float value)) + { + throw new Exception("Data for float not present"); + } SkipWhitespace(); - return result; + return value; } + /// + /// Extracts a double and skips the following whitespace + /// + /// Throws if no value could be parsed + /// The double public double ExtractDouble() { - double result = container.ExtractDouble(); + if (!_container.TryExtractDouble(out double value)) + { + throw new Exception("Data for double not present"); + } SkipWhitespace(); - return result; + return value; } }; } diff --git a/YARG.Core/Song/Cache/CacheGroups/ConGroup.cs b/YARG.Core/Song/Cache/CacheGroups/ConGroup.cs index 20a613987..9a12c68f5 100644 --- a/YARG.Core/Song/Cache/CacheGroups/ConGroup.cs +++ b/YARG.Core/Song/Cache/CacheGroups/ConGroup.cs @@ -20,7 +20,7 @@ protected CONGroup(string location, string defaultPlaylist) DefaultPlaylist = defaultPlaylist; } - public abstract void ReadEntry(string nodeName, int index, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings); + public abstract void ReadEntry(string nodeName, int index, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings); public abstract byte[] SerializeEntries(Dictionary nodes); public void AddEntry(string name, int index, RBCONEntry entry) diff --git a/YARG.Core/Song/Cache/CacheGroups/PackedCONGroup.cs b/YARG.Core/Song/Cache/CacheGroups/PackedCONGroup.cs index f83dd4520..5488a4570 100644 --- a/YARG.Core/Song/Cache/CacheGroups/PackedCONGroup.cs +++ b/YARG.Core/Song/Cache/CacheGroups/PackedCONGroup.cs @@ -32,7 +32,7 @@ public PackedCONGroup(CONFile conFile, AbridgedFileInfo info, string defaultPlay conFile.TryGetListing(SONGSFILEPATH, out SongDTA); } - public override void ReadEntry(string nodeName, int index, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) + public override void ReadEntry(string nodeName, int index, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) { var song = PackedRBCONEntry.TryLoadFromCache(in ConFile, nodeName, upgrades, reader, strings); if (song != null) @@ -54,43 +54,47 @@ public override byte[] SerializeEntries(Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) + public override void ReadEntry(string nodeName, int index, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) { var song = UnpackedRBCONEntry.TryLoadFromCache(Location, DTA, nodeName, upgrades, reader, strings); if (song != null) diff --git a/YARG.Core/Song/Cache/CacheGroups/UpdateGroup.cs b/YARG.Core/Song/Cache/CacheGroups/UpdateGroup.cs index db1461537..91f533376 100644 --- a/YARG.Core/Song/Cache/CacheGroups/UpdateGroup.cs +++ b/YARG.Core/Song/Cache/CacheGroups/UpdateGroup.cs @@ -54,32 +54,19 @@ public void Dispose() public sealed class SongUpdate : IComparable { private readonly DateTime _dtaLastWrite; - private readonly YARGDTAReader[] _readers; public readonly string BaseDirectory; public readonly AbridgedFileInfo_Length? Midi; public readonly AbridgedFileInfo? Mogg; public readonly AbridgedFileInfo_Length? Milo; public readonly AbridgedFileInfo_Length? Image; - - public YARGDTAReader[] Readers - { - get - { - var readers = new YARGDTAReader[_readers.Length]; - for (int i = 0; i < readers.Length; i++) - { - readers[i] = _readers[i].Clone(); - } - return readers; - } - } + public readonly YARGDTAReader[] Readers; public SongUpdate(UpdateGroup group, string name, DateTime dtaLastWrite, YARGDTAReader[] readers) { BaseDirectory = group.Directory.FullName; + Readers = readers; _dtaLastWrite = dtaLastWrite; - _readers = readers; string subname = name.ToLowerInvariant(); if (group.SubDirectories.TryGetValue(subname, out var subDirectory)) diff --git a/YARG.Core/Song/Cache/CacheHandler.Parallel.cs b/YARG.Core/Song/Cache/CacheHandler.Parallel.cs index 821a84b80..c5926ac38 100644 --- a/YARG.Core/Song/Cache/CacheHandler.Parallel.cs +++ b/YARG.Core/Song/Cache/CacheHandler.Parallel.cs @@ -77,23 +77,9 @@ protected override void FindNewEntries() { conTasks[con++] = Task.Run(() => { - var reader = group.LoadSongs(); - if (reader != null) + if (group.LoadSongs(out var reader)) { - try - { - List tasks = new(); - TraverseCONGroup(reader, (string name, int index) => - { - var node = reader.Clone(); - tasks.Add(Task.Run(() => ScanPackedCONNode(group, name, index, node))); - }); - Task.WaitAll(tasks.ToArray()); - } - catch (Exception e) - { - YargLogger.LogException(e, $"Error while scanning CON group {group.Location}!"); - } + TraverseCONGroup(group, ref reader, ScanPackedCONNode); } group.DisposeStreamAndSongDTA(); } @@ -104,23 +90,9 @@ protected override void FindNewEntries() { conTasks[con++] = Task.Run(() => { - var reader = group.LoadDTA(); - if (reader != null) + if (group.LoadDTA(out var reader)) { - try - { - List tasks = new(); - TraverseCONGroup(reader, (string name, int index) => - { - var node = reader.Clone(); - tasks.Add(Task.Run(() => ScanUnpackedCONNode(group, name, index, node))); - }); - Task.WaitAll(tasks.ToArray()); - } - catch (Exception e) - { - YargLogger.LogException(e, $"Error while scanning CON group {group.Location}!"); - } + TraverseCONGroup(group, ref reader, ScanUnpackedCONNode); } group.Dispose(); } @@ -151,6 +123,34 @@ protected override void TraverseDirectory(FileCollector collector, IniGroup grou foreach (var task in tasks) task.Dispose(); } + private void TraverseCONGroup(TGroup group, ref YARGDTAReader reader, Action func) + where TGroup : CONGroup + { + List tasks = new(); + try + { + Dictionary indices = new(); + while (reader.StartNode()) + { + string name = reader.GetNameOfNode(true); + if (indices.TryGetValue(name, out int index)) + { + ++index; + } + indices[name] = index; + + var node = reader; + tasks.Add(Task.Run(() => func(group, name, index, node))); + reader.EndNode(); + } + } + catch (Exception e) + { + YargLogger.LogException(e, $"Error while scanning CON group {group.Location}!"); + } + Task.WaitAll(tasks.ToArray()); + } + protected override bool AddEntry(SongEntry entry) { lock (cache.Entries) @@ -190,12 +190,6 @@ protected override void AddUpdates(UpdateGroup group, Dictionary(TGroup group, YARGDTAReader reader, Action func) - where TGroup : CONGroup - { - - } - protected override void SortEntries() { Parallel.ForEach(cache.Entries, node => @@ -286,7 +280,7 @@ protected override void Deserialize_Quick(FileStream stream) } } - protected override void AddUpgrade(string name, YARGDTAReader? reader, IRBProUpgrade upgrade) + protected override void AddUpgrade(string name, YARGDTAReader reader, IRBProUpgrade upgrade) { lock (upgrades) { diff --git a/YARG.Core/Song/Cache/CacheHandler.Sequential.cs b/YARG.Core/Song/Cache/CacheHandler.Sequential.cs index 74ec1d52f..60188bb65 100644 --- a/YARG.Core/Song/Cache/CacheHandler.Sequential.cs +++ b/YARG.Core/Song/Cache/CacheHandler.Sequential.cs @@ -30,34 +30,18 @@ protected override void FindNewEntries() foreach (var group in conGroups) { - var reader = group.LoadSongs(); - if (reader != null) + if (group.LoadSongs(out var reader)) { - try - { - TraverseCONGroup(reader, (string name, int index) => ScanPackedCONNode(group, name, index, reader)); - } - catch (Exception e) - { - YargLogger.LogException(e, $"Error while scanning packed CON group {group.Location}!"); - } + TraverseCONGroup(group, ref reader, ScanPackedCONNode); } group.DisposeStreamAndSongDTA(); } foreach (var group in extractedConGroups) { - var reader = group.LoadDTA(); - if (reader != null) + if (group.LoadDTA(out var reader)) { - try - { - TraverseCONGroup(reader, (string name, int index) => ScanUnpackedCONNode(group, name, index, reader)); - } - catch (Exception e) - { - YargLogger.LogException(e, $"Error while scanning unpacked CON group {group.Location}!"); - } + TraverseCONGroup(group, ref reader, ScanUnpackedCONNode); } group.Dispose(); } @@ -102,6 +86,32 @@ protected override void TraverseDirectory(FileCollector collector, IniGroup grou } } + private void TraverseCONGroup(TGroup group, ref YARGDTAReader reader, Action func) + where TGroup : CONGroup + { + try + { + Dictionary indices = new(); + while (reader.StartNode()) + { + string name = reader.GetNameOfNode(true); + if (indices.TryGetValue(name, out int index)) + { + ++index; + } + indices[name] = index; + + func(group, name, index, reader); + reader.EndNode(); + } + } + catch (Exception e) + { + YargLogger.LogException(e, $"Error while scanning unpacked CON group {group.Location}!"); + } + } + + protected override void SortEntries() { foreach (var node in cache.Entries) @@ -153,7 +163,7 @@ protected override void Deserialize_Quick(FileStream stream) RunEntryTasks(stream, strings, QuickReadExtractedCONGroup); } - protected override void AddUpgrade(string name, YARGDTAReader? reader, IRBProUpgrade upgrade) + protected override void AddUpgrade(string name, YARGDTAReader reader, IRBProUpgrade upgrade) { upgrades[name] = new(reader, upgrade); } diff --git a/YARG.Core/Song/Cache/CacheHandler.Serialization.cs b/YARG.Core/Song/Cache/CacheHandler.Serialization.cs index bdac9d23d..1c821e994 100644 --- a/YARG.Core/Song/Cache/CacheHandler.Serialization.cs +++ b/YARG.Core/Song/Cache/CacheHandler.Serialization.cs @@ -368,7 +368,7 @@ protected void QuickReadUpgradeDirectory(BinaryReader reader) string filename = Path.Combine(directory, $"{name}_plus.mid"); var info = new AbridgedFileInfo_Length(filename, reader); - AddUpgrade(name, null, new UnpackedRBProUpgrade(info)); + AddUpgrade(name, default, new UnpackedRBProUpgrade(info)); } } @@ -392,7 +392,7 @@ protected void QuickReadUpgradeCON(BinaryReader reader) group?.ConFile.TryGetListing($"songs_upgrades/{name}_plus.mid", out listing); var upgrade = new PackedRBProUpgrade(listing, lastWrite); - AddUpgrade(name, null, upgrade); + AddUpgrade(name, default, upgrade); } } diff --git a/YARG.Core/Song/Cache/CacheHandler.cs b/YARG.Core/Song/Cache/CacheHandler.cs index 78708b22e..59b27bcea 100644 --- a/YARG.Core/Song/Cache/CacheHandler.cs +++ b/YARG.Core/Song/Cache/CacheHandler.cs @@ -144,7 +144,7 @@ private static void FullScan(CacheHandler handler, bool loadCache, string cacheL /// Format is YY_MM_DD_RR: Y = year, M = month, D = day, R = revision (reset across dates, only increment /// if multiple cache version changes happen in a single day). /// - public const int CACHE_VERSION = 24_06_13_02; + public const int CACHE_VERSION = 24_06_13_03; protected readonly SongCache cache = new(); @@ -155,7 +155,7 @@ private static void FullScan(CacheHandler handler, bool loadCache, string cacheL protected readonly List extractedConGroups = new(); protected readonly Dictionary> updates = new(); - protected readonly Dictionary upgrades = new(); + protected readonly Dictionary upgrades = new(); protected readonly HashSet preScannedDirectories = new(); protected readonly HashSet preScannedFiles = new(); @@ -197,7 +197,7 @@ private void DisposeDTAData() protected abstract void SortEntries(); protected abstract void AddUpdates(UpdateGroup group, Dictionary> nodes, bool removeEntries); - protected abstract void AddUpgrade(string name, YARGDTAReader? reader, IRBProUpgrade upgrade); + protected abstract void AddUpgrade(string name, YARGDTAReader reader, IRBProUpgrade upgrade); protected abstract void AddPackedCONGroup(PackedCONGroup group); protected abstract void AddUnpackedCONGroup(UnpackedCONGroup group); protected abstract void AddUpgradeGroup(UpgradeGroup group); @@ -363,19 +363,19 @@ private void WriteBadSongs(string badSongsLocation) private UpgradeGroup? CreateUpgradeGroup(string directory, FileInfo dta, bool removeEntries) { MemoryMappedArray? fileData = null; - YARGDTAReader? reader = null; + YARGDTAReader reader; try { fileData = MemoryMappedArray.Load(dta); - reader = YARGDTAReader.TryCreate(fileData); + if (!YARGDTAReader.TryCreate(fileData, out reader)) + { + fileData!.Dispose(); + return null; + } } catch (Exception ex) { YargLogger.LogException(ex, $"Error while loading {dta.FullName}"); - } - - if (reader == null) - { fileData?.Dispose(); return null; } @@ -394,7 +394,7 @@ private void WriteBadSongs(string badSongsLocation) { var upgrade = new UnpackedRBProUpgrade(abridged); group.Upgrades[name] = upgrade; - AddUpgrade(name, reader.Clone(), upgrade); + AddUpgrade(name, reader, upgrade); if (removeEntries) { @@ -423,19 +423,19 @@ private void WriteBadSongs(string badSongsLocation) private UpdateGroup? CreateUpdateGroup(DirectoryInfo dirInfo, FileInfo dta, bool removeEntries) { MemoryMappedArray? fileData = null; - YARGDTAReader? reader = null; + YARGDTAReader reader; try { fileData = MemoryMappedArray.Load(dta); - reader = YARGDTAReader.TryCreate(fileData); + if (!YARGDTAReader.TryCreate(fileData, out reader)) + { + fileData!.Dispose(); + return null; + } } catch (Exception ex) { YargLogger.LogException(ex, $"Error while loading {dta.FullName}"); - } - - if (reader == null) - { fileData?.Dispose(); return null; } @@ -450,7 +450,7 @@ private void WriteBadSongs(string badSongsLocation) { nodes.Add(name, list = new List()); } - list.Add(reader.Clone()); + list.Add(reader); reader.EndNode(); } @@ -474,9 +474,10 @@ private void WriteBadSongs(string badSongsLocation) private bool TryParseUpgrades(string filename, PackedCONGroup group) { - var reader = group.LoadUpgrades(); - if (reader == null) + if (!group.LoadUpgrades(out var reader)) + { return false; + } try { @@ -489,7 +490,7 @@ private bool TryParseUpgrades(string filename, PackedCONGroup group) { IRBProUpgrade upgrade = new PackedRBProUpgrade(listing, listing.LastWrite); group.Upgrades[name] = upgrade; - AddUpgrade(name, reader.Clone(), upgrade); + AddUpgrade(name, reader, upgrade); RemoveCONEntry(name); } } diff --git a/YARG.Core/Song/Entries/Ini/SongEntry.IniBase.cs b/YARG.Core/Song/Entries/Ini/SongEntry.IniBase.cs index fb8ae71fe..c4a2d1d65 100644 --- a/YARG.Core/Song/Entries/Ini/SongEntry.IniBase.cs +++ b/YARG.Core/Song/Entries/Ini/SongEntry.IniBase.cs @@ -179,14 +179,18 @@ protected static (ScanResult Result, AvailableParts Parts) ScanIniChartFile(Fixe var parts = AvailableParts.Default; if (chartType == ChartType.Chart) { - var byteReader = YARGTextLoader.TryLoadByteText(file); - if (byteReader != null) - ParseDotChart(byteReader, modifiers, ref parts, drums); - else + unsafe { - using var chars = YARGTextLoader.ConvertToChar(file); - var charReader = new YARGTextReader(chars, 0); - ParseDotChart(charReader, modifiers, ref parts, drums); + if (YARGTextReader.TryLoadByteText(file, out var byteContainter)) + { + ParseDotChart(ref byteContainter, &StringDecoder.Decode, modifiers, ref parts, drums); + } + else + { + using var chars = YARGTextReader.ConvertToChar(file); + var charContainer = new YARGTextContainer(chars, 0); + ParseDotChart(ref charContainer, &StringDecoder.Decode, modifiers, ref parts, drums); + } } } else // if (chartType == ChartType.Mid || chartType == ChartType.Midi) // Uncomment for any future file type @@ -209,18 +213,25 @@ protected static (ScanResult Result, AvailableParts Parts) ScanIniChartFile(Fixe return (ScanResult.Success, parts); } - private static void ParseDotChart(YARGTextReader textReader, IniSection modifiers, ref AvailableParts parts, DrumPreparseHandler drums) + private static unsafe void ParseDotChart(ref YARGTextContainer reader, in delegate* decoder, IniSection modifiers, ref AvailableParts parts, DrumPreparseHandler drums) where TChar : unmanaged, IEquatable, IConvertible - where TDecoder : IStringDecoder, new() - where TBase : unmanaged, IDotChartBases { - YARGChartFileReader chartReader = new(textReader); - if (chartReader.ValidateHeaderTrack()) + if (YARGChartFileReader.ValidateTrack(ref reader, YARGChartFileReader.HEADERTRACK)) { - var chartMods = chartReader.ExtractModifiers(CHART_MODIFIER_LIST); + var chartMods = YARGChartFileReader.ExtractModifiers(ref reader, decoder, CHART_MODIFIER_LIST); modifiers.Append(chartMods); } - ParseChart(chartReader, drums, ref parts); + + while (YARGChartFileReader.IsStartOfTrack(in reader)) + { + if (!ParseChartTrack(ref reader, drums, ref parts)) + { + if (YARGTextReader.SkipLinesUntil(ref reader, '}')) + { + YARGTextReader.GotoNextLine(ref reader); + } + } + } if (drums.Type == DrumsType.Unknown && drums.ValidatedDiffs > 0) drums.Type = DrumsType.FourLane; @@ -240,6 +251,30 @@ private static bool ParseDotMidi(FixedArray file, IniSection modifiers, re return ParseMidi(file, drums, ref parts); } + private static bool ParseChartTrack(ref YARGTextContainer reader, DrumPreparseHandler drums, ref AvailableParts parts) + where TChar : unmanaged, IEquatable, IConvertible + { + if (!YARGChartFileReader.ValidateInstrument(ref reader, out var instrument, out var difficulty)) + { + return false; + } + + return instrument switch + { + Instrument.FiveFretGuitar => ChartPreparser.Preparse(ref reader, difficulty, ref parts.FiveFretGuitar, ChartPreparser.ValidateFiveFret), + Instrument.FiveFretBass => ChartPreparser.Preparse(ref reader, difficulty, ref parts.FiveFretBass, ChartPreparser.ValidateFiveFret), + Instrument.FiveFretRhythm => ChartPreparser.Preparse(ref reader, difficulty, ref parts.FiveFretRhythm, ChartPreparser.ValidateFiveFret), + Instrument.FiveFretCoopGuitar => ChartPreparser.Preparse(ref reader, difficulty, ref parts.FiveFretCoopGuitar, ChartPreparser.ValidateFiveFret), + Instrument.SixFretGuitar => ChartPreparser.Preparse(ref reader, difficulty, ref parts.SixFretGuitar, ChartPreparser.ValidateSixFret), + Instrument.SixFretBass => ChartPreparser.Preparse(ref reader, difficulty, ref parts.SixFretBass, ChartPreparser.ValidateSixFret), + Instrument.SixFretRhythm => ChartPreparser.Preparse(ref reader, difficulty, ref parts.SixFretRhythm, ChartPreparser.ValidateSixFret), + Instrument.SixFretCoopGuitar => ChartPreparser.Preparse(ref reader, difficulty, ref parts.SixFretCoopGuitar, ChartPreparser.ValidateSixFret), + Instrument.Keys => ChartPreparser.Preparse(ref reader, difficulty, ref parts.Keys, ChartPreparser.ValidateFiveFret), + Instrument.FourLaneDrums => drums.ParseChart(ref reader, difficulty), + _ => false, + }; + } + private static DrumsType GetDrumTypeFromModifier(IniSection modifiers) { if (!modifiers.TryGet("five_lane_drums", out bool fivelane)) diff --git a/YARG.Core/Song/Entries/RBCON/SongEntry.PackedRBCON.cs b/YARG.Core/Song/Entries/RBCON/SongEntry.PackedRBCON.cs index 6c96ea978..9551b1ef3 100644 --- a/YARG.Core/Song/Entries/RBCON/SongEntry.PackedRBCON.cs +++ b/YARG.Core/Song/Entries/RBCON/SongEntry.PackedRBCON.cs @@ -23,7 +23,7 @@ public sealed class PackedRBCONEntry : RBCONEntry public override string Directory { get; } = string.Empty; public override EntryType SubType => EntryType.CON; - public static (ScanResult, PackedRBCONEntry?) ProcessNewEntry(PackedCONGroup group, string nodename, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades) + public static (ScanResult, PackedRBCONEntry?) ProcessNewEntry(PackedCONGroup group, string nodename, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades) { try { @@ -48,7 +48,7 @@ public static (ScanResult, PackedRBCONEntry?) ProcessNewEntry(PackedCONGroup gro } } - public static PackedRBCONEntry? TryLoadFromCache(in CONFile conFile, string nodename, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) + public static PackedRBCONEntry? TryLoadFromCache(in CONFile conFile, string nodename, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) { var psuedoDirectory = reader.ReadString(); @@ -87,7 +87,7 @@ public static (ScanResult, PackedRBCONEntry?) ProcessNewEntry(PackedCONGroup gro return new PackedRBCONEntry(midiListing, lastMidiWrite, moggListing, miloListing, imgListing, psuedoDirectory, updateMidi, upgrade, reader, strings); } - public static PackedRBCONEntry LoadFromCache_Quick(in CONFile conFile, string nodename, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) + public static PackedRBCONEntry LoadFromCache_Quick(in CONFile conFile, string nodename, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) { var psuedoDirectory = reader.ReadString(); @@ -109,7 +109,7 @@ public static PackedRBCONEntry LoadFromCache_Quick(in CONFile conFile, string no return new PackedRBCONEntry(midiListing, lastMidiWrite, moggListing, miloListing, imgListing, psuedoDirectory, updateMidi, upgrade, reader, strings); } - private PackedRBCONEntry(PackedCONGroup group, string nodename, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades) + private PackedRBCONEntry(PackedCONGroup group, string nodename, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades) : base() { var results = Init(nodename, reader, updates, upgrades, group.DefaultPlaylist); diff --git a/YARG.Core/Song/Entries/RBCON/SongEntry.RBCON.cs b/YARG.Core/Song/Entries/RBCON/SongEntry.RBCON.cs index a93d78344..a7dc581d2 100644 --- a/YARG.Core/Song/Entries/RBCON/SongEntry.RBCON.cs +++ b/YARG.Core/Song/Entries/RBCON/SongEntry.RBCON.cs @@ -309,7 +309,7 @@ protected RBCONEntry(AbridgedFileInfo_Length? updateMidi, IRBProUpgrade? upgrade } } - protected DTAResult Init(string nodeName, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades, string defaultPlaylist) + protected DTAResult Init(string nodeName, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades, string defaultPlaylist) { var dtaResults = ParseDTA(nodeName, reader); ApplyRBCONUpdates(ref dtaResults, nodeName, updates); @@ -485,13 +485,13 @@ private void ApplyRBCONUpdates(ref DTAResult mainResult, string nodeName, Dictio } } - private void ApplyRBProUpgrade(string nodeName, Dictionary upgrades) + private void ApplyRBProUpgrade(string nodeName, Dictionary upgrades) { if (upgrades.TryGetValue(nodeName, out var upgrade)) { try { - ParseDTA(nodeName, upgrade.Item1!.Clone()); + ParseDTA(nodeName, upgrade.Item1); _upgrade = upgrade.Item2; } catch (Exception ex) @@ -504,8 +504,9 @@ private void ApplyRBProUpgrade(string nodeName, Dictionary YARGTextContainer.Latin1, "utf-8" or "utf8" => Encoding.UTF8, - _ => reader.encoding + _ => reader.Encoding }; - if (reader.encoding != encoding) + if (reader.Encoding != encoding) { string Convert(string str) { - byte[] bytes = reader.encoding.GetBytes(str); + byte[] bytes = reader.Encoding.GetBytes(str); return encoding.GetString(bytes); } @@ -634,7 +635,7 @@ string Convert(string str) if (_metadata.Playlist.Str.Length != 0) _metadata.Playlist = Convert(_metadata.Playlist); - reader.encoding = encoding; + reader.Encoding = encoding; } break; diff --git a/YARG.Core/Song/Entries/RBCON/SongEntry.UnpackedRBCON.cs b/YARG.Core/Song/Entries/RBCON/SongEntry.UnpackedRBCON.cs index 351b07197..d54cabb85 100644 --- a/YARG.Core/Song/Entries/RBCON/SongEntry.UnpackedRBCON.cs +++ b/YARG.Core/Song/Entries/RBCON/SongEntry.UnpackedRBCON.cs @@ -21,7 +21,7 @@ public sealed class UnpackedRBCONEntry : RBCONEntry public override string Directory { get; } = string.Empty; public override EntryType SubType => EntryType.ExCON; - public static (ScanResult, UnpackedRBCONEntry?) ProcessNewEntry(UnpackedCONGroup group, string nodename, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades) + public static (ScanResult, UnpackedRBCONEntry?) ProcessNewEntry(UnpackedCONGroup group, string nodename, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades) { try { @@ -45,7 +45,7 @@ public static (ScanResult, UnpackedRBCONEntry?) ProcessNewEntry(UnpackedCONGroup } } - public static UnpackedRBCONEntry? TryLoadFromCache(string directory, in AbridgedFileInfo_Length dta, string nodename, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) + public static UnpackedRBCONEntry? TryLoadFromCache(string directory, in AbridgedFileInfo_Length dta, string nodename, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) { string subname = reader.ReadString(); string songDirectory = Path.Combine(directory, subname); @@ -71,7 +71,7 @@ public static (ScanResult, UnpackedRBCONEntry?) ProcessNewEntry(UnpackedCONGroup return new UnpackedRBCONEntry(midiInfo.Value, dta, songDirectory, subname, updateMidi, upgrade, reader, strings); } - public static UnpackedRBCONEntry LoadFromCache_Quick(string directory, in AbridgedFileInfo_Length? dta, string nodename, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) + public static UnpackedRBCONEntry LoadFromCache_Quick(string directory, in AbridgedFileInfo_Length? dta, string nodename, Dictionary upgrades, BinaryReader reader, CategoryCacheStrings strings) { string subname = reader.ReadString(); string songDirectory = Path.Combine(directory, subname); @@ -96,7 +96,7 @@ private UnpackedRBCONEntry(AbridgedFileInfo_Length midi, AbridgedFileInfo_Length _nodename = nodename; } - private UnpackedRBCONEntry(UnpackedCONGroup group, string nodename, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades) + private UnpackedRBCONEntry(UnpackedCONGroup group, string nodename, YARGDTAReader reader, Dictionary> updates, Dictionary upgrades) : base() { var results = Init(nodename, reader, updates, upgrades, group.DefaultPlaylist); diff --git a/YARG.Core/Song/Entries/SongEntry.Scanning.cs b/YARG.Core/Song/Entries/SongEntry.Scanning.cs index 54b00f696..df42bf52a 100644 --- a/YARG.Core/Song/Entries/SongEntry.Scanning.cs +++ b/YARG.Core/Song/Entries/SongEntry.Scanning.cs @@ -60,45 +60,6 @@ protected static bool ParseMidi(FixedArray file, DrumPreparseHandler drums return true; } - protected static void ParseChart(YARGChartFileReader reader, DrumPreparseHandler drums, ref AvailableParts parts) - where TChar : unmanaged, IEquatable, IConvertible - where TDecoder : IStringDecoder, new() - where TBase : unmanaged, IDotChartBases - { - while (reader.IsStartOfTrack()) - { - if (!reader.ValidateDifficulty() || !reader.ValidateInstrument()) - reader.SkipTrack(); - else if (reader.Instrument != NoteTracks_Chart.Drums) - ParseChartTrack(reader, ref parts); - else - drums.ParseChart(reader); - } - } - - private static void ParseChartTrack(YARGChartFileReader reader, ref AvailableParts parts) - where TChar : unmanaged, IEquatable, IConvertible - where TDecoder : IStringDecoder, new() - where TBase : unmanaged, IDotChartBases - { - bool skip = reader.Instrument switch - { - NoteTracks_Chart.Single => ChartPreparser.Preparse(reader, ref parts.FiveFretGuitar, ChartPreparser.ValidateFiveFret), - NoteTracks_Chart.DoubleBass => ChartPreparser.Preparse(reader, ref parts.FiveFretBass, ChartPreparser.ValidateFiveFret), - NoteTracks_Chart.DoubleRhythm => ChartPreparser.Preparse(reader, ref parts.FiveFretRhythm, ChartPreparser.ValidateFiveFret), - NoteTracks_Chart.DoubleGuitar => ChartPreparser.Preparse(reader, ref parts.FiveFretCoopGuitar, ChartPreparser.ValidateFiveFret), - NoteTracks_Chart.GHLGuitar => ChartPreparser.Preparse(reader, ref parts.SixFretGuitar, ChartPreparser.ValidateSixFret), - NoteTracks_Chart.GHLBass => ChartPreparser.Preparse(reader, ref parts.SixFretBass, ChartPreparser.ValidateSixFret), - NoteTracks_Chart.GHLRhythm => ChartPreparser.Preparse(reader, ref parts.SixFretRhythm, ChartPreparser.ValidateSixFret), - NoteTracks_Chart.GHLCoop => ChartPreparser.Preparse(reader, ref parts.SixFretCoopGuitar, ChartPreparser.ValidateSixFret), - NoteTracks_Chart.Keys => ChartPreparser.Preparse(reader, ref parts.Keys, ChartPreparser.ValidateFiveFret), - _ => true, - }; - - if (skip) - reader.SkipTrack(); - } - protected static void SetDrums(ref AvailableParts parts, DrumPreparseHandler drumTracker) { if (drumTracker.Type == DrumsType.FiveLane) diff --git a/YARG.Core/Song/Preparsers/Chart/ChartPreparser.cs b/YARG.Core/Song/Preparsers/Chart/ChartPreparser.cs index 1881a32f8..8b0fd4c16 100644 --- a/YARG.Core/Song/Preparsers/Chart/ChartPreparser.cs +++ b/YARG.Core/Song/Preparsers/Chart/ChartPreparser.cs @@ -1,36 +1,29 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; using YARG.Core.IO; namespace YARG.Core.Song { public static class ChartPreparser { - public static bool Preparse(YARGChartFileReader reader, ref PartValues scan, Func func) + public static bool Preparse(ref YARGTextContainer container, Difficulty difficulty, ref PartValues scan, Func func) where TChar : unmanaged, IEquatable, IConvertible - where TDecoder : IStringDecoder, new() - where TBase : unmanaged, IDotChartBases { - var difficulty = reader.Difficulty; if (scan[difficulty]) return true; DotChartEvent ev = default; - DotChartNote note = default; - while (reader.TryParseEvent(ref ev)) + while (YARGChartFileReader.TryParseEvent(ref container, ref ev)) { if (ev.Type == ChartEventType.Note) { - reader.ExtractLaneAndSustain(ref note); - if (func(note.Lane)) + int lane = YARGTextReader.ExtractInt32(ref container); + long _ = YARGTextReader.ExtractInt64(ref container); + if (func(lane)) { scan.SetDifficulty(difficulty); return true; } } - reader.NextEvent(); } return false; } @@ -56,4 +49,4 @@ public static bool ValidateFiveFret(int lane) return lane < GUITAR_FIVEFRET_MAX || lane == OPEN_NOTE; } } -} +} \ No newline at end of file diff --git a/YARG.Core/Song/Preparsers/DrumPreparseHandler.cs b/YARG.Core/Song/Preparsers/DrumPreparseHandler.cs index 865dcd70a..fa1ad575d 100644 --- a/YARG.Core/Song/Preparsers/DrumPreparseHandler.cs +++ b/YARG.Core/Song/Preparsers/DrumPreparseHandler.cs @@ -32,26 +32,21 @@ public void ParseMidi(YARGMidiTrack track) } } - public void ParseChart(YARGChartFileReader reader) + public bool ParseChart(ref YARGTextContainer reader, Difficulty difficulty) where TChar : unmanaged, IEquatable, IConvertible - where TDecoder : IStringDecoder, new() - where TBase : unmanaged, IDotChartBases { - var difficulty = reader.Difficulty.ToDifficultyMask(); - - bool skip = true; - if ((_validations & difficulty) == 0) + var diffMask = difficulty.ToDifficultyMask(); + if ((_validations & diffMask) > 0) { - skip = Type switch - { - DrumsType.Unknown => ParseChartUnknown(reader, difficulty), - DrumsType.FourLane => ParseChartFourLane(reader, difficulty), - _ => ParseChartCommon(reader, difficulty), - }; + return false; } - if (skip) - reader.SkipTrack(); + return Type switch + { + DrumsType.Unknown => ParseChartUnknown(ref reader, diffMask), + DrumsType.FourLane => ParseChartFourLane(ref reader, diffMask), + _ => ParseChartCommon(ref reader, diffMask), + }; } private const int FOUR_LANE_COUNT = 4; @@ -60,34 +55,32 @@ public void ParseChart(YARGChartFileReader(YARGChartFileReader reader, DifficultyMask difficulty) + private bool ParseChartUnknown(ref YARGTextContainer container, DifficultyMask difficulty) where TChar : unmanaged, IEquatable, IConvertible - where TDecoder : IStringDecoder, new() - where TBase : unmanaged, IDotChartBases { bool found = false; bool checkExpertPlus = difficulty == DifficultyMask.Expert; DotChartEvent ev = default; - DotChartNote note = default; - while (reader.TryParseEvent(ref ev)) + while (YARGChartFileReader.TryParseEvent(ref container, ref ev)) { if (ev.Type == ChartEventType.Note) { - reader.ExtractLaneAndSustain(ref note); - if (note.Lane <= FIVE_LANE_COUNT) + int lane = YARGTextReader.ExtractInt32(ref container); + long _ = YARGTextReader.ExtractInt64(ref container); + if (lane <= FIVE_LANE_COUNT) { _validations |= difficulty; found = true; - if (note.Lane == FIVE_LANE_COUNT) + if (lane == FIVE_LANE_COUNT) Type = DrumsType.FiveLane; } - else if (YELLOW_CYMBAL <= note.Lane && note.Lane <= GREEN_CYMBAL) + else if (YELLOW_CYMBAL <= lane && lane <= GREEN_CYMBAL) { Type = DrumsType.ProDrums; } - else if (checkExpertPlus && note.Lane == DOUBLE_BASS_MODIFIER) + else if (checkExpertPlus && lane == DOUBLE_BASS_MODIFIER) { checkExpertPlus = false; _validations |= DifficultyMask.ExpertPlus; @@ -96,36 +89,33 @@ private bool ParseChartUnknown(YARGChartFileReader(YARGChartFileReader reader, DifficultyMask difficulty) + private bool ParseChartFourLane(ref YARGTextContainer container, DifficultyMask difficulty) where TChar : unmanaged, IEquatable, IConvertible - where TDecoder : IStringDecoder, new() - where TBase : unmanaged, IDotChartBases { bool found = false; bool checkExpertPlus = difficulty == DifficultyMask.Expert; DotChartEvent ev = default; - DotChartNote note = default; - while (reader.TryParseEvent(ref ev)) + while (YARGChartFileReader.TryParseEvent(ref container, ref ev)) { if (ev.Type == ChartEventType.Note) { - reader.ExtractLaneAndSustain(ref note); - if (note.Lane <= FOUR_LANE_COUNT) + int lane = YARGTextReader.ExtractInt32(ref container); + long _ = YARGTextReader.ExtractInt64(ref container); + if (lane <= FOUR_LANE_COUNT) { found = true; _validations |= difficulty; } - else if (YELLOW_CYMBAL <= note.Lane && note.Lane <= GREEN_CYMBAL) + else if (YELLOW_CYMBAL <= lane && lane <= GREEN_CYMBAL) { Type = DrumsType.ProDrums; } - else if (checkExpertPlus && note.Lane == DOUBLE_BASS_MODIFIER) + else if (checkExpertPlus && lane == DOUBLE_BASS_MODIFIER) { checkExpertPlus = false; _validations |= DifficultyMask.ExpertPlus; @@ -134,33 +124,30 @@ private bool ParseChartFourLane(YARGChartFileReader(YARGChartFileReader reader, DifficultyMask difficulty) + private bool ParseChartCommon(ref YARGTextContainer container, DifficultyMask difficulty) where TChar : unmanaged, IEquatable, IConvertible - where TDecoder : IStringDecoder, new() - where TBase : unmanaged, IDotChartBases { bool found = false; bool checkExpertPlus = difficulty == DifficultyMask.Expert; int numPads = Type == DrumsType.ProDrums ? FOUR_LANE_COUNT : FIVE_LANE_COUNT; DotChartEvent ev = default; - DotChartNote note = default; - while (reader.TryParseEvent(ref ev)) + while (YARGChartFileReader.TryParseEvent(ref container, ref ev)) { if (ev.Type == ChartEventType.Note) { - reader.ExtractLaneAndSustain(ref note); - if (note.Lane <= numPads) + int lane = YARGTextReader.ExtractInt32(ref container); + long _ = YARGTextReader.ExtractInt64(ref container); + if (lane <= numPads) { found = true; _validations |= difficulty; } - else if (checkExpertPlus && note.Lane == DOUBLE_BASS_MODIFIER) + else if (checkExpertPlus && lane == DOUBLE_BASS_MODIFIER) { checkExpertPlus = false; _validations |= DifficultyMask.ExpertPlus; @@ -169,9 +156,8 @@ private bool ParseChartCommon(YARGChartFileReader