From 6c0735560146967339f3e75e5df607e68568b6f5 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Wed, 11 Mar 2026 13:56:49 +0800 Subject: [PATCH] refactor: refactor editor --- .../{Utils => Backend}/CountableEnumerable.cs | 8 +- .../BallanceTasEditor/Backend/FpsConverter.cs | 50 +++ .../Backend/NullableExtension.cs | 15 + .../{Utils => Backend}/TasFrame.cs | 11 +- .../BallanceTasEditor/Backend/TasOperation.cs | 146 +++++++ .../Backend/TasOperationStack.cs | 10 + .../BallanceTasEditor/Backend/TasSequence.cs | 361 ++++++++++++++++++ .../BallanceTasEditor/Backend/TasStorage.cs | 136 +++++++ .../BallanceTasEditor.csproj | 2 +- .../BallanceTasEditor/Utils/FpsConverter.cs | 31 -- .../BallanceTasEditor/Utils/TasOperation.cs | 100 ----- .../BallanceTasEditor/Utils/TasStorage.cs | 299 --------------- 12 files changed, 732 insertions(+), 437 deletions(-) rename BallanceTasEditor/BallanceTasEditor/{Utils => Backend}/CountableEnumerable.cs (93%) create mode 100644 BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs create mode 100644 BallanceTasEditor/BallanceTasEditor/Backend/NullableExtension.cs rename BallanceTasEditor/BallanceTasEditor/{Utils => Backend}/TasFrame.cs (95%) create mode 100644 BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs create mode 100644 BallanceTasEditor/BallanceTasEditor/Backend/TasOperationStack.cs create mode 100644 BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs create mode 100644 BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs delete mode 100644 BallanceTasEditor/BallanceTasEditor/Utils/FpsConverter.cs delete mode 100644 BallanceTasEditor/BallanceTasEditor/Utils/TasOperation.cs delete mode 100644 BallanceTasEditor/BallanceTasEditor/Utils/TasStorage.cs diff --git a/BallanceTasEditor/BallanceTasEditor/Utils/CountableEnumerable.cs b/BallanceTasEditor/BallanceTasEditor/Backend/CountableEnumerable.cs similarity index 93% rename from BallanceTasEditor/BallanceTasEditor/Utils/CountableEnumerable.cs rename to BallanceTasEditor/BallanceTasEditor/Backend/CountableEnumerable.cs index 9549dfb..96d283c 100644 --- a/BallanceTasEditor/BallanceTasEditor/Utils/CountableEnumerable.cs +++ b/BallanceTasEditor/BallanceTasEditor/Backend/CountableEnumerable.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace BallanceTasEditor.Utils { +namespace BallanceTasEditor.Backend { /// /// 一种提前给定元素个数的的IEnumerable。 /// @@ -28,8 +28,8 @@ namespace BallanceTasEditor.Utils { m_Count = array.Length; } - private IEnumerable m_Inner; - private int m_Count; + private readonly IEnumerable m_Inner; + private readonly int m_Count; /// /// 获取迭代器对象。 diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs b/BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs new file mode 100644 index 0000000..4349251 --- /dev/null +++ b/BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BallanceTasEditor.Backend { + /// + /// FPS converter + /// + public static class FpsConverter { + /// + /// Convert float point delta time to float point FPS + /// + /// Delta time in float point + /// FPS in float point + public static float ToFps(float delta) { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(delta, nameof(delta)); + return 1f / delta; + } + + /// + /// Convert float point delta time to integer FPS + /// + /// Delta time in float point + /// FPS in floor integer + public static uint ToFloorFps(float delta) { + return (uint)Math.Floor(ToFps(delta)); + } + + /// + /// Convert integer FPS to float point delta time + /// + /// FPS in integer + /// Delta time in float point + public static float ToDelta(uint fps) { + return ToDelta((float)fps); + } + + /// + /// Convert float point FPS to float point delta time + /// + /// FPS in float point + /// Delta time in float point + public static float ToDelta(float fps) { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(fps, nameof(fps)); + return 1f / fps; + } + } +} diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/NullableExtension.cs b/BallanceTasEditor/BallanceTasEditor/Backend/NullableExtension.cs new file mode 100644 index 0000000..b2bfd21 --- /dev/null +++ b/BallanceTasEditor/BallanceTasEditor/Backend/NullableExtension.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace BallanceTasEditor.Backend { + public static class NullableExtensions { + public static T Unwrap(this T? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) where T : class { + ArgumentNullException.ThrowIfNull(value, paramName); + return value; + } + } +} diff --git a/BallanceTasEditor/BallanceTasEditor/Utils/TasFrame.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasFrame.cs similarity index 95% rename from BallanceTasEditor/BallanceTasEditor/Utils/TasFrame.cs rename to BallanceTasEditor/BallanceTasEditor/Backend/TasFrame.cs index bfb54e4..189d9a6 100644 --- a/BallanceTasEditor/BallanceTasEditor/Utils/TasFrame.cs +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasFrame.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -namespace BallanceTasEditor.Utils { +namespace BallanceTasEditor.Backend { /// /// 原始的TAS帧结构,与二进制结构保持一致。 @@ -149,6 +149,13 @@ namespace BallanceTasEditor.Utils { /// public bool KeyEnterPressed { get { return IsKeyPressed(TasKey.KeyEnter); } set { SetKeyPressed(TasKey.KeyEnter, value); } } + /// + /// 清除所有按键,将所有按键设置为不按下。 + /// + public void ClearKeyPressed() { + m_KeyFlags = 0; + } + } /// diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs new file mode 100644 index 0000000..13bfc5c --- /dev/null +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BallanceTasEditor.Backend { + + /// + /// TAS操作接口。所有TAS操作均需要支持此接口。 + /// + public interface ITasOperation { + /// + /// 执行对应的TAS操作。 + /// + /// 所要操作的TAS存储容器。 + void Execute(ITasSequence storage); + } + + /// + /// 可撤销的TAS操作接口,所有可撤销的TAS操作均需支持此接口。 + /// + public interface ITasRevocableOperation : ITasOperation { + /// + /// 撤销对应TAS操作。 + /// + /// 所要撤销操作的TAS存储容器。 + void Revoke(ITasSequence storage); + /// + /// 返回该TAS操作占用的内存大小。 + /// + /// + /// 可撤销的TAS操作会在内存中存储一定数据,用于撤销对应操作。 + /// 该函数返回的占用用于衡量该操作的开销。 + /// 我们应当基于大小,而非写死的个数决定撤销栈中的最大操作次数, + /// 例如对于小型操作我们可以存储100个,对于大型操作则只能存储5个等。 + /// 用于解决编辑者目前认为撤销栈大小不足的情况。 + /// + /// 该函数返回的大小可以不是特别精确,但要准确反映空间复杂度。 + /// + /// 占用的内存大小(以byte为单位)。 + int Occupation(); + } + + public enum CellKeysOperationKind { + Set, Unset, Flip + } + + public class CellKeysOperation : ITasRevocableOperation { + + private CellKeysOperationKind m_Kind; + + public void Execute(ITasSequence storage) { + throw new NotImplementedException(); + } + + public void Revoke(ITasSequence storage) { + throw new NotImplementedException(); + } + + public int Occupation() { + throw new NotImplementedException(); + } + } + + public class CellFpsOperation : ITasRevocableOperation { + public void Execute(ITasSequence storage) { + throw new NotImplementedException(); + } + + public void Revoke(ITasSequence storage) { + throw new NotImplementedException(); + } + + public int Occupation() { + throw new NotImplementedException(); + } + } + + public class RemoveFrameOperation : ITasRevocableOperation { + public void Execute(ITasSequence storage) { + throw new NotImplementedException(); + } + + public void Revoke(ITasSequence storage) { + throw new NotImplementedException(); + } + + public int Occupation() { + throw new NotImplementedException(); + } + } + + public class AddFrameOperation : ITasRevocableOperation { + public void Execute(ITasSequence storage) { + throw new NotImplementedException(); + } + + public void Revoke(ITasSequence storage) { + throw new NotImplementedException(); + } + + public int Occupation() { + throw new NotImplementedException(); + } + } + + public class InsertFrameOperation : ITasRevocableOperation { + public void Execute(ITasSequence storage) { + throw new NotImplementedException(); + } + + public void Revoke(ITasSequence storage) { + throw new NotImplementedException(); + } + + public int Occupation() { + throw new NotImplementedException(); + } + } + + public class ClearKeysOperation : ITasOperation { + public ClearKeysOperation() { } + + public void Execute(ITasSequence storage) { + foreach (var frame in storage) { + frame.ClearKeyPressed(); + } + } + } + + public class UniformFpsOperation : ITasOperation { + public UniformFpsOperation(float deltaTime) { + m_DeltaTime = deltaTime; + } + + private float m_DeltaTime; + + public void Execute(ITasSequence storage) { + foreach (var frame in storage) { + frame.SetTimeDelta(m_DeltaTime); + } + } + } + +} diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasOperationStack.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasOperationStack.cs new file mode 100644 index 0000000..5bec10a --- /dev/null +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasOperationStack.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BallanceTasEditor.Backend { + internal class TasOperationStack { + } +} diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs new file mode 100644 index 0000000..5f65ace --- /dev/null +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace BallanceTasEditor.Backend { + + /// + /// 所有用于在内存中存储TAS帧的结构都必须实现此interface。 + /// + public interface ITasSequence : IEnumerable { + /// + /// 访问给定索引的帧的值。 + /// + /// + /// 实现此函数时需要格外注意以下事项: + /// + /// 该函数应当保证在访问临近项时有较高的效率。 + /// + /// 该函数理论上的复杂度应为O(1)。 + /// + /// 要访问的单元的索引。 + /// 被访问的单元。 + /// 给定的索引无效。 + TasFrame Visit(int index); + /// + /// 在给定的帧索引之前插入给定的项目。 + /// + /// + /// 实现此函数时需要格外注意以下事项: + /// + /// 按照函数约定,如果要在头部插入数据,则可以通过指定0来实现。 + /// 然而对于在尾部插入数据,或在空的存储中插入数据,可以指定存储结构的长度来实现。 + /// 即指定(最大Index + 1)作为参数来实现。 + /// + /// 该函数理论上的复杂度应为O(1)。 + /// + /// 要在前方插入数据的元素的索引。 + /// 要插入的元素的迭代器。 + /// 给定的索引无效。 + void Insert(int index, CountableEnumerable items); + /// + /// 从序列中移出给定帧区间的元素。 + /// + /// + /// 实现此函数时需要格外注意以下事项: + /// + /// 该函数理论上的复杂度应为O(1)。 + /// + /// 要移除的帧区间的起始索引(包含)。 + /// 要移除的帧区间的终止索引(包含) + /// 给定的索引无效。 + void Remove(int startIndex, int endIndex); + + /// + /// 清空存储结构。 + /// + void Clear(); + /// + /// 获取当前存储的TAS帧的个数。 + /// + /// 存储的TAS帧的个数。 + int GetCount(); + /// + /// 获取当前存储结构是不是空的。 + /// + /// 如果是空的就返回true,否则返回false。 + bool IsEmpty(); + } + + /// + /// 基于Gap Buffer思想的TAS存储器。 + /// + /// + /// 其实就是把List的InsertRange的复杂度从O(n*m)修正为O(n)。 + /// + public class GapBufferSequence : ITasSequence, IEnumerable { + public GapBufferSequence() { + // Do nothing. + } + + public TasFrame Visit(int index) { + throw new NotImplementedException(); + } + + public void Insert(int index, CountableEnumerable items) { + throw new NotImplementedException(); + } + + public void Remove(int startIndex, int endIndex) { + throw new NotImplementedException(); + } + + public void Clear() { + throw new NotImplementedException(); + } + + public int GetCount() { + throw new NotImplementedException(); + } + + public bool IsEmpty() { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + } + + /// + /// 基于简单的List的TAS存储器。 + /// + /// + /// 由于List的InsertRange的复杂度是O(n*m),可能不符合要求。 + /// + public class ListTasSequence : ITasSequence, IEnumerable { + public ListTasSequence() { + m_Container = new List(); + } + + private List m_Container; + + public TasFrame Visit(int index) { + if (index >= m_Container.Count || index < 0) { + throw new IndexOutOfRangeException("Invalid index for frame."); + } else { + return m_Container[index]; + } + } + + public void Insert(int index, CountableEnumerable items) { + if (index == m_Container.Count) { + m_Container.AddRange(items.GetInner()); + } else { + if (index > m_Container.Count || index < 0) { + throw new IndexOutOfRangeException("Invalid index for frame."); + } else { + m_Container.InsertRange(index, items.GetInner()); + } + } + } + + public void Remove(int startIndex, int endIndex) { + if (endIndex < startIndex || startIndex < 0 || endIndex >= m_Container.Count) { + throw new IndexOutOfRangeException("Invalid index for frame."); + } else { + m_Container.RemoveRange(startIndex, endIndex - startIndex); + } + } + + public void Clear() { + m_Container.Clear(); + } + + public int GetCount() { + return m_Container.Count; + } + + public bool IsEmpty() { + return GetCount() == 0; + } + + public IEnumerator GetEnumerator() { + return m_Container.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + } + + /// + /// 传统的基于LinkedList的TAS存储器。 + /// + public class LegacyTasSequence : ITasSequence, IEnumerable { + public LegacyTasSequence() { + m_Container = new LinkedList(); + m_Cursor = null; + } + + private class LinkedListCursor { + public LinkedListCursor(LinkedListNode node, int index) { + this.Node = node; + this.Index = index; + } + + public LinkedListNode Node; + public int Index; + } + + private LinkedList m_Container; + private LinkedListCursor? m_Cursor; + + private enum NodeSeekOrigin { + Head, + Cursor, + Tail, + } + + private struct NodeSeekInfo : IComparable { + public required NodeSeekOrigin Origin; + public required int Offset; + + public int CompareTo(NodeSeekInfo other) { + return Math.Abs(this.Offset).CompareTo(Math.Abs(other.Offset)); + } + } + + /// + /// 快速将内部游标移动到指定Index,并更新与之匹配的Index。 + /// + /// + /// + [MemberNotNull(nameof(m_Cursor))] + private void MoveToIndex(int desiredIndex) { + // 检查基本环境 + if (desiredIndex < 0 || desiredIndex >= GetCount()) + throw new IndexOutOfRangeException("Invalid index for frame."); + if (m_Cursor is null || IsEmpty()) + throw new InvalidOperationException("Can not move cursor when container is empty."); + + // 创建三个候选方案。 + var candidates = new NodeSeekInfo[3] { + new NodeSeekInfo() { Origin = NodeSeekOrigin.Head, Offset = desiredIndex }, + new NodeSeekInfo() { Origin = NodeSeekOrigin.Tail, Offset = desiredIndex - (GetCount() - 1) }, + new NodeSeekInfo() { Origin = NodeSeekOrigin.Cursor, Offset = desiredIndex - m_Cursor.Index }, + }; + // 确定哪个候选方案最短。 + var bestCandidate = candidates.Min(); + // 用最短候选方案移动。 + int pickedOffset = bestCandidate.Offset; + LinkedListNode pickedNode = bestCandidate.Origin switch { + NodeSeekOrigin.Head => m_Container.First.Unwrap(), + NodeSeekOrigin.Cursor => m_Cursor.Node, + NodeSeekOrigin.Tail => m_Container.Last.Unwrap(), + _ => throw new UnreachableException("Unknown NodeSeekOrigin"), + }; + int alreadyMoved = 0; + if (pickedOffset < 0) { + while (alreadyMoved != pickedOffset) { + pickedNode = pickedNode.Previous.Unwrap(); + alreadyMoved--; + } + } else if (pickedOffset > 0) { + while (alreadyMoved != pickedOffset) { + pickedNode = pickedNode.Next.Unwrap(); + alreadyMoved++; + } + } + + // 设置Cursor + m_Cursor = new LinkedListCursor(pickedNode, desiredIndex); + } + + public TasFrame Visit(int index) { + if (index >= m_Container.Count || index < 0) { + throw new IndexOutOfRangeException("Invalid index for frame."); + } else { + MoveToIndex(index); + return m_Cursor.Node.Value; + } + } + + public void Insert(int index, CountableEnumerable items) { + if (index >= m_Container.Count || index < 0) { + throw new IndexOutOfRangeException("Invalid index for frame."); + } else { + if (index == m_Container.Count) { + foreach (TasFrame item in items.GetInner()) { + m_Container.AddLast(item); + } + + var pendingCursor = m_Container.First; + if (pendingCursor is null) { + m_Cursor = null; + } else { + m_Cursor = new LinkedListCursor(pendingCursor, 0); + } + } else { + MoveToIndex(index); + + foreach (TasFrame item in items.GetInner()) { + m_Container.AddBefore(m_Cursor.Node, item); + } + m_Cursor.Index += items.GetCount(); + } + } + } + + public void Remove(int startIndex, int endIndex) { + if (endIndex < startIndex || startIndex < 0 || endIndex >= m_Container.Count) { + throw new IndexOutOfRangeException("Invalid index for frame."); + } + + // Compute count and move to index. + var count = endIndex - startIndex; + MoveToIndex(startIndex); + + // 我们总是获取要删除的项目的前一项来作为参照。 + // 如果获取到的是null,则说明是正在删第一项,从m_Container里获取First来删除就行, + // 否则就继续用这个Node的Next来删除。 + var prevNode = m_Cursor.Node.Previous; + if (prevNode is null) { + for (int i = 0; i < count; ++i) { + m_Container.RemoveFirst(); + } + } else { + for (int i = 0; i < count; ++i) { + m_Container.Remove(prevNode.Next.Unwrap()); + } + } + + // 然后设置Cursor和Index + if (IsEmpty()) { + // 如果全部删完了,就清除这两个的设置。 + m_Cursor = null; + } else { + if (prevNode is null) { + // 如果是按头部删除的,则直接获取头部及其Index。 + m_Cursor = new LinkedListCursor(m_Container.First.Unwrap(), 0); + } else { + // 否则就以prevNode为当前Cursor,Index--为对应Index。 + m_Cursor.Node = prevNode; + --m_Cursor.Index; + } + } + } + + public void Clear() { + m_Container.Clear(); + m_Cursor = null; + } + + public int GetCount() { + return m_Container.Count; + } + + public bool IsEmpty() { + return GetCount() == 0; + } + + public IEnumerator GetEnumerator() { + return m_Container.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + } +} diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs new file mode 100644 index 0000000..f940190 --- /dev/null +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs @@ -0,0 +1,136 @@ +using CommunityToolkit.HighPerformance; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace BallanceTasEditor.Backend { + public static class TasStorage { + internal const int SIZEOF_F32 = sizeof(float); + internal const int SIZEOF_I32 = sizeof(int); + internal const int SIZEOF_U32 = sizeof(uint); + internal const int SIZEOF_RAW_TAS_FRAME = SIZEOF_F32 + SIZEOF_U32; + + public static void Save(string filepath, ITasSequence seq) { + using (var fs = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)) { + Save(fs, seq); + fs.Close(); + } + } + + public static void Save(Stream fs, ITasSequence seq) { + var totalByte = seq.GetCount() * SIZEOF_RAW_TAS_FRAME; + fs.Write(BitConverter.GetBytes(totalByte), 0, SIZEOF_I32); + + using (var zo = new Ionic.Zlib.ZlibStream(fs, Ionic.Zlib.CompressionMode.Compress, Ionic.Zlib.CompressionLevel.Level9, true)) { + foreach (var item in seq) { + var rawItem = item.ToRaw(); + zo.Write(BitConverter.GetBytes(rawItem.TimeDelta), 0, SIZEOF_F32); + zo.Write(BitConverter.GetBytes(rawItem.KeyFlags), 0, SIZEOF_U32); + } + zo.Close(); + } + + //var zo = new zlib.ZOutputStream(file, 9); + //var node = mem.First; + //while (node != null) { + // zo.Write(BitConverter.GetBytes(node.Value.deltaTime), 0, 4); + // zo.Write(BitConverter.GetBytes(node.Value.keystates), 0, 4); + // node = node.Next; + //} + //zo.finish(); + //zo.Close(); + } + + public static void Load(string filepath, ITasSequence seq) { + using (var fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read)) { + Load(fs, seq); + fs.Close(); + } + } + + public static void Load(Stream fs, ITasSequence seq) { + // Read total bytes + var lenCache = new byte[SIZEOF_I32]; + fs.Read(lenCache, 0, 4); + int expectedLength = BitConverter.ToInt32(lenCache, 0); + // Check length and compute count + int expectedCount = Math.DivRem(expectedLength, SIZEOF_RAW_TAS_FRAME, out var remainder); + ArgumentOutOfRangeException.ThrowIfNotEqual(remainder, 0); + + using (var mem = new MemoryStream()) { + using (var zo = new Ionic.Zlib.ZlibStream(mem, Ionic.Zlib.CompressionMode.Decompress, true)) { + CopyStream(fs, zo); + zo.Close(); + } + + var memWrapper = new EnumerableMemoryStream(mem, expectedCount); + seq.Clear(); + seq.Insert(0, new CountableEnumerable(memWrapper, expectedCount)); + + mem.Close(); + } + + //mem.Seek(0, SeekOrigin.Begin); + //for (long i = 0; i < expectedCount; i++) { + // ls.AddLast(new FrameData(mem)); + //} + //mem.Close(); + //zo.Close(); + + //var zo = new zlib.ZOutputStream(mem); + //CopyStream(file, zo); + //zo.finish(); + + //mem.Seek(0, SeekOrigin.Begin); + //for (long i = 0; i < expectedCount; i++) { + // ls.AddLast(new FrameData(mem)); + //} + //mem.Close(); + //zo.Close(); + } + + private const int STREAM_COPY_CHUNK_SIZE = 10240; + + private static void CopyStream(Stream origin, Stream target) { + var buffer = new byte[STREAM_COPY_CHUNK_SIZE]; + int len; + while ((len = origin.Read(buffer, 0, STREAM_COPY_CHUNK_SIZE)) > 0) { + target.Write(buffer, 0, len); + } + //target.Flush(); + } + + private class EnumerableMemoryStream : IEnumerable { + public EnumerableMemoryStream(MemoryStream mem, int frameCnt) { + m_MemoryStream = mem; + m_FrameCount = frameCnt; + } + + private MemoryStream m_MemoryStream; + private int m_FrameCount; + + public IEnumerator GetEnumerator() { + // Get the view of underlying array + var memory = m_MemoryStream.GetBuffer().AsMemory(); + // Get the span which actually storing the data, + // because the length of buffer is equal or longer than the length of all stored data. + var exactMemory = memory.Slice(0, m_FrameCount * SIZEOF_RAW_TAS_FRAME); + // Convert to raw frame type. + var frameMemory = exactMemory.Cast(); + // Map it and return. + return MemoryMarshal.ToEnumerable(frameMemory).Select((rawFrame) => new TasFrame(rawFrame)).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + } + + } +} diff --git a/BallanceTasEditor/BallanceTasEditor/BallanceTasEditor.csproj b/BallanceTasEditor/BallanceTasEditor/BallanceTasEditor.csproj index f07b815..73ee33d 100644 --- a/BallanceTasEditor/BallanceTasEditor/BallanceTasEditor.csproj +++ b/BallanceTasEditor/BallanceTasEditor/BallanceTasEditor.csproj @@ -14,9 +14,9 @@ + - diff --git a/BallanceTasEditor/BallanceTasEditor/Utils/FpsConverter.cs b/BallanceTasEditor/BallanceTasEditor/Utils/FpsConverter.cs deleted file mode 100644 index d779032..0000000 --- a/BallanceTasEditor/BallanceTasEditor/Utils/FpsConverter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BallanceTasEditor.Utils { - public static class FpsConverter { - public static float ToFps(float delta) { - if (delta <= 0f) - throw new ArgumentOutOfRangeException("invalid time delta (not positive)"); - - return 1f / delta; - } - - public static uint ToFloorFps(float delta) { - return (uint)Math.Floor(ToFps(delta)); - } - - public static float ToDelta(uint fps) { - return ToDelta((float)fps); - } - - public static float ToDelta(float fps) { - if (fps <= 0f) - throw new ArgumentOutOfRangeException("invalid fps (not positive)"); - - return 1f / fps; - } - } -} diff --git a/BallanceTasEditor/BallanceTasEditor/Utils/TasOperation.cs b/BallanceTasEditor/BallanceTasEditor/Utils/TasOperation.cs deleted file mode 100644 index 535798f..0000000 --- a/BallanceTasEditor/BallanceTasEditor/Utils/TasOperation.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BallanceTasEditor.Utils { - - /// - /// TAS操作接口。所有TAS操作均需要支持此接口。 - /// - public interface ITasOperation { - /// - /// 执行对应的TAS操作。 - /// - /// 所要操作的TAS存储容器。 - void Execute(ITasStorage storage); - } - - /// - /// 可撤销的TAS操作接口,所有可撤销的TAS操作均需支持此接口。 - /// - public interface ITasRevocableOperation : ITasOperation { - /// - /// 撤销对应TAS操作。 - /// - /// 所要撤销操作的TAS存储容器。 - void Revoke(ITasStorage storage); - } - - public enum CellKeysOperationKind { - Set, Unset, Flip - } - - public class CellKeysOperation : ITasRevocableOperation { - - private CellKeysOperationKind m_Kind; - - public void Execute(ITasStorage storage) { - throw new NotImplementedException(); - } - - public void Revoke(ITasStorage storage) { - throw new NotImplementedException(); - } - } - - public class CellFpsOperation : ITasRevocableOperation { - public void Execute(ITasStorage storage) { - throw new NotImplementedException(); - } - - public void Revoke(ITasStorage storage) { - throw new NotImplementedException(); - } - } - - public class RemoveFrameOperation : ITasRevocableOperation { - public void Execute(ITasStorage storage) { - throw new NotImplementedException(); - } - - public void Revoke(ITasStorage storage) { - throw new NotImplementedException(); - } - } - - public class AddFrameOperation : ITasRevocableOperation { - public void Execute(ITasStorage storage) { - throw new NotImplementedException(); - } - - public void Revoke(ITasStorage storage) { - throw new NotImplementedException(); - } - } - - public class InsertFrameOperation : ITasRevocableOperation { - public void Execute(ITasStorage storage) { - throw new NotImplementedException(); - } - - public void Revoke(ITasStorage storage) { - throw new NotImplementedException(); - } - } - - public class ClearKeysOperation : ITasOperation { - public void Execute(ITasStorage storage) { - throw new NotImplementedException(); - } - } - - public class UniformFpsOperation : ITasOperation { - public void Execute(ITasStorage storage) { - throw new NotImplementedException(); - } - } - -} diff --git a/BallanceTasEditor/BallanceTasEditor/Utils/TasStorage.cs b/BallanceTasEditor/BallanceTasEditor/Utils/TasStorage.cs deleted file mode 100644 index e4a11a6..0000000 --- a/BallanceTasEditor/BallanceTasEditor/Utils/TasStorage.cs +++ /dev/null @@ -1,299 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace BallanceTasEditor.Utils { - - /// - /// 所有用于在内存中存储TAS帧的结构都必须实现此interface。 - /// - public interface ITasStorage { - /// - /// 访问给定索引的值。 - /// - /// 要访问的单元的索引。 - /// 被访问的单元。 - /// 给定的索引超出范围。 - T Visit(int index); - /// - /// 在给定的索引之前插入给定的项目。 - /// - /// - /// 按照此函数约定,如果要在头部插入数据,则可以通过指定0来实现。 - /// 然而对于在尾部插入数据,或在空的存储中插入数据,可以指定存储结构的长度来实现。 - /// 即指定最大Index + 1的值来实现。 - /// 实现此函数时需要格外注意。 - /// - /// 要在前方插入数据的元素的索引。 - /// 要插入的元素的迭代器。 - /// 给定的索引超出范围。 - void Insert(int index, CountableEnumerable items); - /// - /// 从给定单元开始,移除给定个数的元素。 - /// - /// 要开始移除的单元的索引。 - /// 要移除的元素的个数。 - /// 给定的索引超出范围。 - void Remove(int index, int count); - - /// - /// 清空存储结构。 - /// - void Clear(); - /// - /// 获取当前存储的TAS帧的个数。 - /// - /// 存储的TAS帧的个数。 - int GetCount(); - /// - /// 获取当前存储结构是不是空的。 - /// - /// 如果是空的就返回true,否则返回false。 - bool IsEmpty(); - } - - /// - /// 基于Gap Buffer思想的TAS存储器。 - /// - /// - /// 其实就是把List的InsertRange的复杂度从O(n*m)修正为O(n)。 - /// - public class GapBufferTasStorage : ITasStorage { - public GapBufferTasStorage() { - - } - - public T Visit(int index) { - throw new NotImplementedException(); - } - - public void Insert(int index, CountableEnumerable items) { - throw new NotImplementedException(); - } - - public void Remove(int index, int count) { - throw new NotImplementedException(); - } - - public void Clear() { - throw new NotImplementedException(); - } - - public int GetCount() { - throw new NotImplementedException(); - } - - public bool IsEmpty() { - throw new NotImplementedException(); - } - } - - /// - /// 基于简单的List的TAS存储器。 - /// - /// - /// 由于List的InsertRange的复杂度是O(n*m),可能不符合要求。 - /// - public class ListTasStorage : ITasStorage { - public ListTasStorage() { - m_Container = new List(); - } - - private List m_Container; - - public T Visit(int index) { - return m_Container[index]; - } - - public void Insert(int index, CountableEnumerable items) { - m_Container.InsertRange(index, items.GetInner()); - } - - public void Remove(int index, int count) { - m_Container.RemoveRange(index, count); - } - - public void Clear() { - m_Container.Clear(); - } - - public int GetCount() { - return m_Container.Count; - } - - public bool IsEmpty() { - return GetCount() == 0; - } - } - - /// - /// 传统的基于LinkedList的TAS存储器。 - /// - public class LegacyTasStorage : ITasStorage { - public LegacyTasStorage() { - m_Container = new LinkedList(); - m_Cursor = null; - m_CursorIndex = null; - } - - private LinkedList m_Container; - private LinkedListNode m_Cursor; - private int? m_CursorIndex; - - private enum NodeSeekOrigin { - Head, - Cursor, - Tail, - } - - private struct NodeSeekInfo : IComparable { - public NodeSeekOrigin Origin; - public int Offset; - - public int CompareTo(NodeSeekInfo other) { - return Math.Abs(this.Offset).CompareTo(Math.Abs(other.Offset)); - } - } - - /// - /// 快速将内部游标移动到指定Index,并更新与之匹配的Index。 - /// - /// - /// - private void MoveToIndex(int desiredIndex) { - // 检查基本环境 - if (desiredIndex < 0 || desiredIndex >= GetCount()) - throw new ArgumentOutOfRangeException("Index out of range"); - if (m_Cursor is null || !m_CursorIndex.HasValue || IsEmpty()) - throw new InvalidOperationException("Can not move cursor when container is empty."); - - // 创建三个候选方案。 - var candidates = new NodeSeekInfo[3] { - new NodeSeekInfo() { Origin = NodeSeekOrigin.Head, Offset = desiredIndex }, - new NodeSeekInfo() { Origin = NodeSeekOrigin.Tail, Offset = desiredIndex - (GetCount() - 1) }, - new NodeSeekInfo() { Origin = NodeSeekOrigin.Cursor, Offset = desiredIndex - m_CursorIndex.Value }, - }; - // 确定哪个候选方案最短。 - var bestCandidate = candidates.Min(); - // 用最短候选方案移动。 - int pickedOffset = bestCandidate.Offset; - LinkedListNode pickedNode = null; - switch (bestCandidate.Origin) { - case NodeSeekOrigin.Head: - pickedNode = m_Container.First; - break; - case NodeSeekOrigin.Cursor: - pickedNode = m_Cursor; - break; - case NodeSeekOrigin.Tail: - pickedNode = m_Container.Last; - break; - } - long alreadyMoved = 0; - if (pickedOffset < 0) { - while (alreadyMoved != pickedOffset) { - pickedNode = pickedNode.Previous; - alreadyMoved--; - } - } else if (pickedOffset > 0) { - while (alreadyMoved != pickedOffset) { - pickedNode = pickedNode.Next; - alreadyMoved++; - } - } - - // 设置Cursor和Index - m_Cursor = pickedNode; - m_CursorIndex = desiredIndex; - } - - public T Visit(int index) { - if (index < 0 || index >= GetCount()) { - throw new ArgumentOutOfRangeException("Index out of range."); - } else { - MoveToIndex(index); - return m_Cursor.Value; - } - } - - public void Insert(int index, CountableEnumerable items) { - if (index < 0 || index > GetCount()) { - throw new ArgumentOutOfRangeException("Index out of range."); - } else if (index == GetCount()) { - foreach (T item in items.GetInner()) { - m_Container.AddLast(item); - } - - m_Cursor = m_Container.First; - if (m_Cursor is null) m_CursorIndex = null; - else m_CursorIndex = 0; - } else { - MoveToIndex(index); - - int count = 0; - foreach (T item in items.GetInner()) { - m_Container.AddBefore(m_Cursor, item); - ++count; - } - m_CursorIndex += count; - } - } - - public void Remove(int index, int count) { - if (count == 0) - return; - if (index + count > GetCount()) - throw new ArgumentOutOfRangeException("Expected removed items out of range."); - - MoveToIndex(index); - - // 我们总是获取要删除的项目的前一项来作为参照。 - // 如果获取到的是null,则说明是正在删第一项,从m_Container里获取First来删除就行, - // 否则就继续用这个Node的Next来删除。 - var prevNode = m_Cursor.Previous; - if (prevNode is null) { - for (int i = 0; i < count; ++i) { - m_Container.RemoveFirst(); - } - } else { - for (int i = 0; i < count; ++i) { - m_Container.Remove(prevNode.Next); - } - } - - // 然后设置Cursor和Index - if (IsEmpty()) { - // 如果全部删完了,就清除这两个的设置。 - m_Cursor = null; - m_CursorIndex = null; - } else { - if (prevNode is null) { - // 如果是按头部删除的,则直接获取头部及其Index。 - m_Cursor = m_Container.First; - m_CursorIndex = 0; - } else { - // 否则就以prevNode为当前Cursor,Index--为对应Index。 - m_Cursor = prevNode; - --m_CursorIndex; - } - } - } - - public void Clear() { - m_Container.Clear(); - m_Cursor = null; - m_CursorIndex = null; - } - - public int GetCount() { - return m_Container.Count(); - } - - public bool IsEmpty() { - return GetCount() == 0; - } - } -}