refactor: refactor editor
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BallanceTasEditor.Utils {
|
namespace BallanceTasEditor.Backend {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 一种提前给定元素个数的的IEnumerable。
|
/// 一种提前给定元素个数的的IEnumerable。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -28,8 +28,8 @@ namespace BallanceTasEditor.Utils {
|
|||||||
m_Count = array.Length;
|
m_Count = array.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<T> m_Inner;
|
private readonly IEnumerable<T> m_Inner;
|
||||||
private int m_Count;
|
private readonly int m_Count;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取迭代器对象。
|
/// 获取迭代器对象。
|
||||||
50
BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs
Normal file
50
BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BallanceTasEditor.Backend {
|
||||||
|
/// <summary>
|
||||||
|
/// FPS converter
|
||||||
|
/// </summary>
|
||||||
|
public static class FpsConverter {
|
||||||
|
/// <summary>
|
||||||
|
/// Convert float point delta time to float point FPS
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delta">Delta time in float point</param>
|
||||||
|
/// <returns>FPS in float point</returns>
|
||||||
|
public static float ToFps(float delta) {
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(delta, nameof(delta));
|
||||||
|
return 1f / delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert float point delta time to integer FPS
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delta">Delta time in float point</param>
|
||||||
|
/// <returns>FPS in floor integer</returns>
|
||||||
|
public static uint ToFloorFps(float delta) {
|
||||||
|
return (uint)Math.Floor(ToFps(delta));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert integer FPS to float point delta time
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fps">FPS in integer</param>
|
||||||
|
/// <returns>Delta time in float point</returns>
|
||||||
|
public static float ToDelta(uint fps) {
|
||||||
|
return ToDelta((float)fps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert float point FPS to float point delta time
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fps">FPS in float point</param>
|
||||||
|
/// <returns>Delta time in float point</returns>
|
||||||
|
public static float ToDelta(float fps) {
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(fps, nameof(fps));
|
||||||
|
return 1f / fps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<T>(this T? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) where T : class {
|
||||||
|
ArgumentNullException.ThrowIfNull(value, paramName);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BallanceTasEditor.Utils {
|
namespace BallanceTasEditor.Backend {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 原始的TAS帧结构,与二进制结构保持一致。
|
/// 原始的TAS帧结构,与二进制结构保持一致。
|
||||||
@@ -149,6 +149,13 @@ namespace BallanceTasEditor.Utils {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool KeyEnterPressed { get { return IsKeyPressed(TasKey.KeyEnter); } set { SetKeyPressed(TasKey.KeyEnter, value); } }
|
public bool KeyEnterPressed { get { return IsKeyPressed(TasKey.KeyEnter); } set { SetKeyPressed(TasKey.KeyEnter, value); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除所有按键,将所有按键设置为不按下。
|
||||||
|
/// </summary>
|
||||||
|
public void ClearKeyPressed() {
|
||||||
|
m_KeyFlags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
146
BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs
Normal file
146
BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BallanceTasEditor.Backend {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TAS操作接口。所有TAS操作均需要支持此接口。
|
||||||
|
/// </summary>
|
||||||
|
public interface ITasOperation {
|
||||||
|
/// <summary>
|
||||||
|
/// 执行对应的TAS操作。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">所要操作的TAS存储容器。</param>
|
||||||
|
void Execute(ITasSequence storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可撤销的TAS操作接口,所有可撤销的TAS操作均需支持此接口。
|
||||||
|
/// </summary>
|
||||||
|
public interface ITasRevocableOperation : ITasOperation {
|
||||||
|
/// <summary>
|
||||||
|
/// 撤销对应TAS操作。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="storage">所要撤销操作的TAS存储容器。</param>
|
||||||
|
void Revoke(ITasSequence storage);
|
||||||
|
/// <summary>
|
||||||
|
/// 返回该TAS操作占用的内存大小。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 可撤销的TAS操作会在内存中存储一定数据,用于撤销对应操作。
|
||||||
|
/// 该函数返回的占用用于衡量该操作的开销。
|
||||||
|
/// 我们应当基于大小,而非写死的个数决定撤销栈中的最大操作次数,
|
||||||
|
/// 例如对于小型操作我们可以存储100个,对于大型操作则只能存储5个等。
|
||||||
|
/// 用于解决编辑者目前认为撤销栈大小不足的情况。
|
||||||
|
/// <para/>
|
||||||
|
/// 该函数返回的大小可以不是特别精确,但要准确反映空间复杂度。
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>占用的内存大小(以byte为单位)。</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
361
BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs
Normal file
361
BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs
Normal file
@@ -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 {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 所有用于在内存中存储TAS帧的结构都必须实现此interface。
|
||||||
|
/// </summary>
|
||||||
|
public interface ITasSequence : IEnumerable<TasFrame> {
|
||||||
|
/// <summary>
|
||||||
|
/// 访问给定索引的帧的值。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 实现此函数时需要格外注意以下事项:
|
||||||
|
/// <para/>
|
||||||
|
/// 该函数应当保证在访问临近项时有较高的效率。
|
||||||
|
/// <para/>
|
||||||
|
/// 该函数理论上的复杂度应为O(1)。
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="index">要访问的单元的索引。</param>
|
||||||
|
/// <returns>被访问的单元。</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">给定的索引无效。</exception>
|
||||||
|
TasFrame Visit(int index);
|
||||||
|
/// <summary>
|
||||||
|
/// 在给定的帧索引<b>之前</b>插入给定的项目。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 实现此函数时需要格外注意以下事项:
|
||||||
|
/// <para/>
|
||||||
|
/// 按照函数约定,如果要在头部插入数据,则可以通过指定0来实现。
|
||||||
|
/// 然而对于在尾部插入数据,或在空的存储中插入数据,可以指定存储结构的长度来实现。
|
||||||
|
/// 即指定<c>(最大Index + 1)</c>作为参数来实现。
|
||||||
|
/// <para/>
|
||||||
|
/// 该函数理论上的复杂度应为O(1)。
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="index">要在前方插入数据的元素的索引。</param>
|
||||||
|
/// <param name="items">要插入的元素的迭代器。</param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">给定的索引无效。</exception>
|
||||||
|
void Insert(int index, CountableEnumerable<TasFrame> items);
|
||||||
|
/// <summary>
|
||||||
|
/// 从序列中移出给定帧区间的元素。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 实现此函数时需要格外注意以下事项:
|
||||||
|
/// <para/>
|
||||||
|
/// 该函数理论上的复杂度应为O(1)。
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="startIndex">要移除的帧区间的起始索引(包含)。</param>
|
||||||
|
/// <param name="endIndex">要移除的帧区间的终止索引(包含)</param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">给定的索引无效。</exception>
|
||||||
|
void Remove(int startIndex, int endIndex);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清空存储结构。
|
||||||
|
/// </summary>
|
||||||
|
void Clear();
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前存储的TAS帧的个数。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>存储的TAS帧的个数。</returns>
|
||||||
|
int GetCount();
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前存储结构是不是空的。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>如果是空的就返回true,否则返回false。</returns>
|
||||||
|
bool IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于Gap Buffer思想的TAS存储器。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 其实就是把List的InsertRange的复杂度从O(n*m)修正为O(n)。
|
||||||
|
/// </remarks>
|
||||||
|
public class GapBufferSequence : ITasSequence, IEnumerable<TasFrame> {
|
||||||
|
public GapBufferSequence() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
public TasFrame Visit(int index) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, CountableEnumerable<TasFrame> 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<TasFrame> GetEnumerator() {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于简单的List的TAS存储器。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 由于List的InsertRange的复杂度是O(n*m),可能不符合要求。
|
||||||
|
/// </remarks>
|
||||||
|
public class ListTasSequence : ITasSequence, IEnumerable<TasFrame> {
|
||||||
|
public ListTasSequence() {
|
||||||
|
m_Container = new List<TasFrame>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TasFrame> 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<TasFrame> 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<TasFrame> GetEnumerator() {
|
||||||
|
return m_Container.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 传统的基于LinkedList的TAS存储器。
|
||||||
|
/// </summary>
|
||||||
|
public class LegacyTasSequence : ITasSequence, IEnumerable<TasFrame> {
|
||||||
|
public LegacyTasSequence() {
|
||||||
|
m_Container = new LinkedList<TasFrame>();
|
||||||
|
m_Cursor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LinkedListCursor<T> {
|
||||||
|
public LinkedListCursor(LinkedListNode<T> node, int index) {
|
||||||
|
this.Node = node;
|
||||||
|
this.Index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedListNode<T> Node;
|
||||||
|
public int Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedList<TasFrame> m_Container;
|
||||||
|
private LinkedListCursor<TasFrame>? m_Cursor;
|
||||||
|
|
||||||
|
private enum NodeSeekOrigin {
|
||||||
|
Head,
|
||||||
|
Cursor,
|
||||||
|
Tail,
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct NodeSeekInfo : IComparable<NodeSeekInfo> {
|
||||||
|
public required NodeSeekOrigin Origin;
|
||||||
|
public required int Offset;
|
||||||
|
|
||||||
|
public int CompareTo(NodeSeekInfo other) {
|
||||||
|
return Math.Abs(this.Offset).CompareTo(Math.Abs(other.Offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 快速将内部游标移动到指定Index,并更新与之匹配的Index。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="desiredIndex"></param>
|
||||||
|
/// <exception cref="Exception"></exception>
|
||||||
|
[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<TasFrame> 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<TasFrame>(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<TasFrame> 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<TasFrame>(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<TasFrame>(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<TasFrame> GetEnumerator() {
|
||||||
|
return m_Container.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs
Normal file
136
BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs
Normal file
@@ -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<TasFrame>(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<TasFrame> {
|
||||||
|
public EnumerableMemoryStream(MemoryStream mem, int frameCnt) {
|
||||||
|
m_MemoryStream = mem;
|
||||||
|
m_FrameCount = frameCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MemoryStream m_MemoryStream;
|
||||||
|
private int m_FrameCount;
|
||||||
|
|
||||||
|
public IEnumerator<TasFrame> 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<byte, RawTasFrame>();
|
||||||
|
// Map it and return.
|
||||||
|
return MemoryMarshal.ToEnumerable<RawTasFrame>(frameMemory).Select((rawFrame) => new TasFrame(rawFrame)).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.4.0" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||||
<PackageReference Include="DotNetZip" Version="1.9.1.8" />
|
<PackageReference Include="DotNetZip" Version="1.9.1.8" />
|
||||||
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace BallanceTasEditor.Utils {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// TAS操作接口。所有TAS操作均需要支持此接口。
|
|
||||||
/// </summary>
|
|
||||||
public interface ITasOperation {
|
|
||||||
/// <summary>
|
|
||||||
/// 执行对应的TAS操作。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">所要操作的TAS存储容器。</param>
|
|
||||||
void Execute(ITasStorage<TasFrame> storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 可撤销的TAS操作接口,所有可撤销的TAS操作均需支持此接口。
|
|
||||||
/// </summary>
|
|
||||||
public interface ITasRevocableOperation : ITasOperation {
|
|
||||||
/// <summary>
|
|
||||||
/// 撤销对应TAS操作。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="storage">所要撤销操作的TAS存储容器。</param>
|
|
||||||
void Revoke(ITasStorage<TasFrame> storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum CellKeysOperationKind {
|
|
||||||
Set, Unset, Flip
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CellKeysOperation : ITasRevocableOperation {
|
|
||||||
|
|
||||||
private CellKeysOperationKind m_Kind;
|
|
||||||
|
|
||||||
public void Execute(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Revoke(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CellFpsOperation : ITasRevocableOperation {
|
|
||||||
public void Execute(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Revoke(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RemoveFrameOperation : ITasRevocableOperation {
|
|
||||||
public void Execute(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Revoke(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AddFrameOperation : ITasRevocableOperation {
|
|
||||||
public void Execute(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Revoke(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InsertFrameOperation : ITasRevocableOperation {
|
|
||||||
public void Execute(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Revoke(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ClearKeysOperation : ITasOperation {
|
|
||||||
public void Execute(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UniformFpsOperation : ITasOperation {
|
|
||||||
public void Execute(ITasStorage<TasFrame> storage) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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 {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 所有用于在内存中存储TAS帧的结构都必须实现此interface。
|
|
||||||
/// </summary>
|
|
||||||
public interface ITasStorage<T> {
|
|
||||||
/// <summary>
|
|
||||||
/// 访问给定索引的值。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">要访问的单元的索引。</param>
|
|
||||||
/// <returns>被访问的单元。</returns>
|
|
||||||
/// <exception cref="ArgumentException">给定的索引超出范围。</exception>
|
|
||||||
T Visit(int index);
|
|
||||||
/// <summary>
|
|
||||||
/// 在给定的索引<b>之前</b>插入给定的项目。
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 按照此函数约定,如果要在头部插入数据,则可以通过指定0来实现。
|
|
||||||
/// 然而对于在尾部插入数据,或在空的存储中插入数据,可以指定存储结构的长度来实现。
|
|
||||||
/// 即指定最大Index + 1的值来实现。
|
|
||||||
/// 实现此函数时需要格外注意。
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="index">要在前方插入数据的元素的索引。</param>
|
|
||||||
/// <param name="items">要插入的元素的迭代器。</param>
|
|
||||||
/// <exception cref="ArgumentException">给定的索引超出范围。</exception>
|
|
||||||
void Insert(int index, CountableEnumerable<T> items);
|
|
||||||
/// <summary>
|
|
||||||
/// 从给定单元开始,移除给定个数的元素。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">要开始移除的单元的索引。</param>
|
|
||||||
/// <param name="count">要移除的元素的个数。</param>
|
|
||||||
/// <exception cref="ArgumentException">给定的索引超出范围。</exception>
|
|
||||||
void Remove(int index, int count);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清空存储结构。
|
|
||||||
/// </summary>
|
|
||||||
void Clear();
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前存储的TAS帧的个数。
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>存储的TAS帧的个数。</returns>
|
|
||||||
int GetCount();
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前存储结构是不是空的。
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>如果是空的就返回true,否则返回false。</returns>
|
|
||||||
bool IsEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 基于Gap Buffer思想的TAS存储器。
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 其实就是把List的InsertRange的复杂度从O(n*m)修正为O(n)。
|
|
||||||
/// </remarks>
|
|
||||||
public class GapBufferTasStorage<T> : ITasStorage<T> {
|
|
||||||
public GapBufferTasStorage() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Visit(int index) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Insert(int index, CountableEnumerable<T> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 基于简单的List的TAS存储器。
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 由于List的InsertRange的复杂度是O(n*m),可能不符合要求。
|
|
||||||
/// </remarks>
|
|
||||||
public class ListTasStorage<T> : ITasStorage<T> {
|
|
||||||
public ListTasStorage() {
|
|
||||||
m_Container = new List<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<T> m_Container;
|
|
||||||
|
|
||||||
public T Visit(int index) {
|
|
||||||
return m_Container[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Insert(int index, CountableEnumerable<T> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 传统的基于LinkedList的TAS存储器。
|
|
||||||
/// </summary>
|
|
||||||
public class LegacyTasStorage<T> : ITasStorage<T> {
|
|
||||||
public LegacyTasStorage() {
|
|
||||||
m_Container = new LinkedList<T>();
|
|
||||||
m_Cursor = null;
|
|
||||||
m_CursorIndex = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LinkedList<T> m_Container;
|
|
||||||
private LinkedListNode<T> m_Cursor;
|
|
||||||
private int? m_CursorIndex;
|
|
||||||
|
|
||||||
private enum NodeSeekOrigin {
|
|
||||||
Head,
|
|
||||||
Cursor,
|
|
||||||
Tail,
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct NodeSeekInfo : IComparable<NodeSeekInfo> {
|
|
||||||
public NodeSeekOrigin Origin;
|
|
||||||
public int Offset;
|
|
||||||
|
|
||||||
public int CompareTo(NodeSeekInfo other) {
|
|
||||||
return Math.Abs(this.Offset).CompareTo(Math.Abs(other.Offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 快速将内部游标移动到指定Index,并更新与之匹配的Index。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="desiredIndex"></param>
|
|
||||||
/// <exception cref="Exception"></exception>
|
|
||||||
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<T> 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<T> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user