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;
- }
- }
-}