diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasClipboard.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasClipboard.cs index a0495fd..7fa7a2f 100644 --- a/BallanceTasEditor/BallanceTasEditor/Backend/TasClipboard.cs +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasClipboard.cs @@ -50,7 +50,7 @@ namespace BallanceTasEditor.Backend { private RawTasFrame[] m_RawFrames; public IEnumerator GetEnumerator() { - return m_RawFrames.Select((f) => new TasFrame(f)).GetEnumerator(); + return m_RawFrames.Select((f) => TasFrame.FromRaw(f)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasFrame.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasFrame.cs index ed8675d..d85e78e 100644 --- a/BallanceTasEditor/BallanceTasEditor/Backend/TasFrame.cs +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasFrame.cs @@ -23,32 +23,114 @@ namespace BallanceTasEditor.Backend { public uint KeyFlags; } + /// + /// 描述TAS文件中的可能的按键。 + /// + public struct TasKey : IEquatable { + private TasKey(int bitPos) { + m_BitPos = bitPos; + } + + private int m_BitPos; + + public static readonly TasKey KEY_UP = new TasKey(0); + public static readonly TasKey KEY_DOWN = new TasKey(1); + public static readonly TasKey KEY_LEFT = new TasKey(2); + public static readonly TasKey KEY_RIGHT = new TasKey(3); + public static readonly TasKey KEY_SHIFT = new TasKey(4); + public static readonly TasKey KEY_SPACE = new TasKey(5); + public static readonly TasKey KEY_Q = new TasKey(6); + public static readonly TasKey KEY_ESC = new TasKey(7); + public static readonly TasKey KEY_ENTER = new TasKey(8); + + public const int MIN_KEY_INDEX = 0; + public const int MAX_KEY_INDEX = 8; + + public static TasKey FromIndex(int index) { + if (index < MIN_KEY_INDEX || index > MAX_KEY_INDEX) { + throw new ArgumentOutOfRangeException(nameof(index)); + } else { + return new TasKey(index); + } + } + + public int ToIndex() { + return m_BitPos; + } + + public uint ToBitMaskKey() { + return 1u << m_BitPos; + } + + public bool Equals(TasKey other) { + return m_BitPos == other.m_BitPos; + } + + public override bool Equals(object? obj) { + if (obj is TasKey other) { + return Equals(other); + } else { + return false; + } + } + + public override int GetHashCode() { + return m_BitPos.GetHashCode(); + } + + public static bool operator ==(TasKey left, TasKey right) { + return left.Equals(right); + } + + public static bool operator !=(TasKey left, TasKey right) { + return !left.Equals(right); + } + + public override string ToString() { + return m_BitPos switch { + 0 => "KeyUp", + 1 => "KeyDown", + 2 => "KeyLeft", + 3 => "KeyRight", + 4 => "KeyShift", + 5 => "KeySpace", + 6 => "KeyQ", + 7 => "KeyEsc", + 8 => "KeyEnter", + _ => $"KeyUnknown" + }; + } + } + /// /// 描述TAS文件中一帧的结构。 /// public class TasFrame : IEquatable { + private TasFrame(float timeDelta, uint keyFlags) { + m_TimeDelta = timeDelta; + m_KeyFlags = keyFlags; + } + /// /// 以指定的FPS,无任何按键初始化当前帧。 /// - public TasFrame(uint fps = 60) { - m_TimeDelta = FpsConverter.ToDelta(fps); - m_KeyFlags = 0; + public static TasFrame FromFps(uint fps = 60) { + return new TasFrame(FpsConverter.ToDelta(fps), 0); } /// /// 从原始TAS数据初始化。 /// /// 要用来初始化的原始数据。 - public TasFrame(RawTasFrame raw) { - m_TimeDelta = raw.TimeDelta; - m_KeyFlags = raw.KeyFlags; + public static TasFrame FromRaw(RawTasFrame raw) { + return new TasFrame(raw.TimeDelta, raw.KeyFlags); } - + /// /// 将原始TAS数据覆写到自身 /// /// 要写入的原始TAS数据 - public void FromRaw(RawTasFrame raw) { + public void FromRawImplace(RawTasFrame raw) { m_TimeDelta = raw.TimeDelta; m_KeyFlags = raw.KeyFlags; } @@ -65,7 +147,7 @@ namespace BallanceTasEditor.Backend { /// 原位转换为原始TAS数据。 /// /// 以引用传递的原始TAS数据。 - public void ToImplaceRaw(ref RawTasFrame raw) { + public void ToRawImplace(ref RawTasFrame raw) { raw.TimeDelta = m_TimeDelta; raw.KeyFlags = m_KeyFlags; } @@ -101,7 +183,7 @@ namespace BallanceTasEditor.Backend { /// 要检查的按键。 /// true表示被按下,否则为false。 public bool IsKeyPressed(TasKey key) { - return (m_KeyFlags & (1u << (int)key)) != 0; + return (m_KeyFlags & key.ToBitMaskKey()) != 0; } /// @@ -110,8 +192,8 @@ namespace BallanceTasEditor.Backend { /// 要设置的按键。 /// true表示设置为按下,否则为松开。 public void SetKeyPressed(TasKey key, bool pressed = true) { - if (pressed) m_KeyFlags |= (1u << (int)key); - else m_KeyFlags &= ~(1u << (int)key); + if (pressed) m_KeyFlags |= key.ToBitMaskKey(); + else m_KeyFlags &= ~key.ToBitMaskKey(); } /// @@ -119,45 +201,45 @@ namespace BallanceTasEditor.Backend { /// /// 要反转的按键。 public void FlipKeyPressed(TasKey key) { - m_KeyFlags ^= (1u << (int)key); + m_KeyFlags ^= key.ToBitMaskKey(); } /// /// 获取或设置Up键的按下状态。 /// - public bool KeyUpPressed { get { return IsKeyPressed(TasKey.KeyUp); } set { SetKeyPressed(TasKey.KeyUp, value); } } + public bool KeyUpPressed { get { return IsKeyPressed(TasKey.KEY_UP); } set { SetKeyPressed(TasKey.KEY_UP, value); } } /// /// 获取或设置Down键的按下状态。 /// - public bool KeyDownPressed { get { return IsKeyPressed(TasKey.KeyDown); } set { SetKeyPressed(TasKey.KeyDown, value); } } + public bool KeyDownPressed { get { return IsKeyPressed(TasKey.KEY_DOWN); } set { SetKeyPressed(TasKey.KEY_DOWN, value); } } /// /// 获取或设置Left键的按下状态。 /// - public bool KeyLeftPressed { get { return IsKeyPressed(TasKey.KeyLeft); } set { SetKeyPressed(TasKey.KeyLeft, value); } } + public bool KeyLeftPressed { get { return IsKeyPressed(TasKey.KEY_LEFT); } set { SetKeyPressed(TasKey.KEY_LEFT, value); } } /// /// 获取或设置Right键的按下状态。 /// - public bool KeyRightPressed { get { return IsKeyPressed(TasKey.KeyRight); } set { SetKeyPressed(TasKey.KeyRight, value); } } + public bool KeyRightPressed { get { return IsKeyPressed(TasKey.KEY_RIGHT); } set { SetKeyPressed(TasKey.KEY_RIGHT, value); } } /// /// 获取或设置Shift键的按下状态。 /// - public bool KeyShiftPressed { get { return IsKeyPressed(TasKey.KeyShift); } set { SetKeyPressed(TasKey.KeyShift, value); } } + public bool KeyShiftPressed { get { return IsKeyPressed(TasKey.KEY_SHIFT); } set { SetKeyPressed(TasKey.KEY_SHIFT, value); } } /// /// 获取或设置Space键的按下状态。 /// - public bool KeySpacePressed { get { return IsKeyPressed(TasKey.KeySpace); } set { SetKeyPressed(TasKey.KeySpace, value); } } + public bool KeySpacePressed { get { return IsKeyPressed(TasKey.KEY_SPACE); } set { SetKeyPressed(TasKey.KEY_SPACE, value); } } /// /// 获取或设置Q键的按下状态。 /// - public bool KeyQPressed { get { return IsKeyPressed(TasKey.KeyQ); } set { SetKeyPressed(TasKey.KeyQ, value); } } + public bool KeyQPressed { get { return IsKeyPressed(TasKey.KEY_Q); } set { SetKeyPressed(TasKey.KEY_Q, value); } } /// /// 获取或设置Esc键的按下状态。 /// - public bool KeyEscPressed { get { return IsKeyPressed(TasKey.KeyEsc); } set { SetKeyPressed(TasKey.KeyEsc, value); } } + public bool KeyEscPressed { get { return IsKeyPressed(TasKey.KEY_ESC); } set { SetKeyPressed(TasKey.KEY_ESC, value); } } /// /// 获取或设置回车键的按下状态。 /// - public bool KeyEnterPressed { get { return IsKeyPressed(TasKey.KeyEnter); } set { SetKeyPressed(TasKey.KeyEnter, value); } } + public bool KeyEnterPressed { get { return IsKeyPressed(TasKey.KEY_ENTER); } set { SetKeyPressed(TasKey.KEY_ENTER, value); } } /// /// 清除所有按键,将所有按键设置为不按下。 @@ -200,19 +282,4 @@ namespace BallanceTasEditor.Backend { } - /// - /// 描述TAS文件中的可能的按键。 - /// - public enum TasKey : int { - KeyUp = 0, - KeyDown = 1, - KeyLeft = 2, - KeyRight = 3, - KeyShift = 4, - KeySpace = 5, - KeyQ = 6, - KeyEsc = 7, - KeyEnter = 8, - } - } diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs index 13bfc5c..651afd1 100644 --- a/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs @@ -13,8 +13,17 @@ namespace BallanceTasEditor.Backend { /// /// 执行对应的TAS操作。 /// - /// 所要操作的TAS存储容器。 - void Execute(ITasSequence storage); + /// 所要操作的TAS存储容器。 + void Execute(ITasSequence seq); + /// + /// 检查该操作是否已经被执行过。 + /// + /// + /// 所有Tas操作类创建后只能执行一次或者不执行, + /// 因此有此函数用于获取是否已经执行过。 + /// + /// 如果已经执行过,返回true,否则返回false。 + bool IsExecuted(); } /// @@ -24,8 +33,8 @@ namespace BallanceTasEditor.Backend { /// /// 撤销对应TAS操作。 /// - /// 所要撤销操作的TAS存储容器。 - void Revoke(ITasSequence storage); + /// 所要撤销操作的TAS存储容器。 + void Revoke(ITasSequence seq); /// /// 返回该TAS操作占用的内存大小。 /// @@ -47,42 +56,124 @@ namespace BallanceTasEditor.Backend { } public class CellKeysOperation : ITasRevocableOperation { - - private CellKeysOperationKind m_Kind; - - public void Execute(ITasSequence storage) { - throw new NotImplementedException(); + public static CellKeysOperation FromSingleCell(CellKeysOperationKind kind, int index, TasKey key) { + return new CellKeysOperation(kind, index, index, key, key); } - public void Revoke(ITasSequence storage) { - throw new NotImplementedException(); + public static CellKeysOperation FromCellRange(CellKeysOperationKind kind, int startIndex, int endIndex, TasKey startKey, TasKey endKey) { + return new CellKeysOperation(kind, startIndex, endIndex, startKey, endKey); + } + + private CellKeysOperation(CellKeysOperationKind kind, int startIndex, int endIndex, TasKey startKey, TasKey endKey) { + // Check arguments. + ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex); + ArgumentOutOfRangeException.ThrowIfGreaterThan(startKey.ToIndex(), endKey.ToIndex()); + // Setup members. + m_Kind = kind; + m_StartIndex = startIndex; + m_EndIndex = endIndex; + m_StartKey = startKey; + m_EndKey = endKey; + m_FramesBackup = null; + } + + private CellKeysOperationKind m_Kind; + private int m_StartIndex, m_EndIndex; + private TasKey m_StartKey, m_EndKey; + private RawTasFrame[]? m_FramesBackup; + + public bool IsExecuted() { + return m_FramesBackup is not null; + } + + public void Execute(ITasSequence seq) { + if (m_FramesBackup is not null) { + throw new InvalidOperationException("Can not execute one TAS operation multiple times."); + } + + // Check index range. + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(m_EndIndex, seq.GetCount()); + ArgumentOutOfRangeException.ThrowIfLessThan(m_StartIndex, 0); + + // Do backup and set values at the same time + var backups = new RawTasFrame[m_EndIndex - m_StartIndex]; + // Pre-build key list for fast fetching. + var keys = Enumerable.Range(m_StartKey.ToIndex(), m_EndKey.ToIndex() - m_StartKey.ToIndex()).Select((i) => TasKey.FromIndex(i)).ToArray(); + for (int index = m_StartIndex; index <= m_EndIndex; index++) { + // Fetch frame + var frame = seq.Visit(index); + // Do backup + frame.ToRawImplace(ref backups[index - m_StartIndex]); + // Modify keys + foreach (var key in keys) { + switch (m_Kind) { + case CellKeysOperationKind.Set: + frame.SetKeyPressed(key, true); + break; + case CellKeysOperationKind.Unset: + frame.SetKeyPressed(key, false); + break; + case CellKeysOperationKind.Flip: + frame.FlipKeyPressed(key); + break; + } + } + } + + // Assign backups + m_FramesBackup = backups; + } + + public void Revoke(ITasSequence seq) { + if (m_FramesBackup is null) { + throw new InvalidOperationException("Can not revoke an not executed TAS operation."); + } + + // Index range is checked, + // so we directly restore backup. + for (int index = m_StartIndex; index <= m_EndIndex; index++) { + seq.Visit(index).FromRawImplace(m_FramesBackup[index - m_StartIndex]); + } + + // Clear backups + m_FramesBackup = null; } public int Occupation() { - throw new NotImplementedException(); + return (m_EndIndex - m_StartIndex) * (m_EndKey.ToIndex() - m_StartKey.ToIndex()); } + } public class CellFpsOperation : ITasRevocableOperation { - public void Execute(ITasSequence storage) { + public bool IsExecuted() { throw new NotImplementedException(); } - public void Revoke(ITasSequence storage) { + public void Execute(ITasSequence seq) { + throw new NotImplementedException(); + } + + public void Revoke(ITasSequence seq) { throw new NotImplementedException(); } public int Occupation() { throw new NotImplementedException(); } + } public class RemoveFrameOperation : ITasRevocableOperation { - public void Execute(ITasSequence storage) { + public bool IsExecuted() { throw new NotImplementedException(); } - public void Revoke(ITasSequence storage) { + public void Execute(ITasSequence seq) { + throw new NotImplementedException(); + } + + public void Revoke(ITasSequence seq) { throw new NotImplementedException(); } @@ -92,25 +183,74 @@ namespace BallanceTasEditor.Backend { } public class AddFrameOperation : ITasRevocableOperation { - public void Execute(ITasSequence storage) { - throw new NotImplementedException(); + public AddFrameOperation(int index, uint fps, int count) { + // Check argument + ArgumentOutOfRangeException.ThrowIfNegative(count); + ArgumentOutOfRangeException.ThrowIfZero(fps); + // Assign argument + m_Index = index; + m_Fps = fps; + m_Count = count; + m_IsExecuted = false; } - public void Revoke(ITasSequence storage) { - throw new NotImplementedException(); + private int m_Index; + private uint m_Fps; + private int m_Count; + private bool m_IsExecuted; + + public bool IsExecuted() { + return m_IsExecuted; + } + + public void Execute(ITasSequence seq) { + if (m_IsExecuted) { + throw new InvalidOperationException("Can not execute one TAS operation multiple times."); + } + + // Check argument. + ArgumentOutOfRangeException.ThrowIfGreaterThan(m_Index, seq.GetCount()); + + // Prepare data builder. + var iter = Enumerable.Range(0, m_Count).Select((_) => TasFrame.FromFps(m_Fps)); + var exactSizedIter = new ExactSizeEnumerableAdapter(iter, m_Count); + // Execute inserting. + seq.Insert(m_Index, exactSizedIter); + + // Set status + m_IsExecuted = true; + } + + public void Revoke(ITasSequence seq) { + if (!m_IsExecuted) { + throw new InvalidOperationException("Can not revoke an not executed TAS operation."); + } + + // If we inserted count is not zero, remove inserted frames + if (m_Count != 0) { + seq.Remove(m_Index, m_Index + m_Count - 1); + } else { + // Otherwise just skip it. + } + // Modify execution status + m_IsExecuted = false; } public int Occupation() { - throw new NotImplementedException(); + return 1; } } public class InsertFrameOperation : ITasRevocableOperation { - public void Execute(ITasSequence storage) { + public bool IsExecuted() { throw new NotImplementedException(); } - public void Revoke(ITasSequence storage) { + public void Execute(ITasSequence seq) { + throw new NotImplementedException(); + } + + public void Revoke(ITasSequence seq) { throw new NotImplementedException(); } @@ -120,26 +260,52 @@ namespace BallanceTasEditor.Backend { } public class ClearKeysOperation : ITasOperation { - public ClearKeysOperation() { } + public ClearKeysOperation() { + m_IsExecuted = false; + } - public void Execute(ITasSequence storage) { - foreach (var frame in storage) { + private bool m_IsExecuted; + + public void Execute(ITasSequence seq) { + // Check execution status first. + if (m_IsExecuted) { + throw new InvalidOperationException("Can not execute one TAS operation multiple times."); + } + // Execute operation + foreach (var frame in seq) { frame.ClearKeyPressed(); } + m_IsExecuted = true; + } + + public bool IsExecuted() { + return m_IsExecuted; } } public class UniformFpsOperation : ITasOperation { public UniformFpsOperation(float deltaTime) { m_DeltaTime = deltaTime; + m_IsExecuted = false; } private float m_DeltaTime; + private bool m_IsExecuted; - public void Execute(ITasSequence storage) { - foreach (var frame in storage) { + public void Execute(ITasSequence seq) { + // Check execution status first. + if (m_IsExecuted) { + throw new InvalidOperationException("Can not execute one TAS operation multiple times."); + } + // Execute operation + foreach (var frame in seq) { frame.SetTimeDelta(m_DeltaTime); } + m_IsExecuted = true; + } + + public bool IsExecuted() { + return m_IsExecuted; } } diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs index 2875f67..d329184 100644 --- a/BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasSequence.cs @@ -74,6 +74,10 @@ namespace BallanceTasEditor.Backend { bool IsEmpty(); } + // TODO: + // We may introduce ITasSequenceSlice to have iterator on a specific range. + // We also need introduce a new function in ITasSequence to fetch this instance. + /// /// 基于Gap Buffer思想的TAS存储器。 /// diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs index 3f820dd..7cc1d3c 100644 --- a/BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs +++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasStorage.cs @@ -123,7 +123,7 @@ namespace BallanceTasEditor.Backend { // Convert to raw frame type. var frameMemory = exactMemory.Cast(); // Map it and return. - return MemoryMarshal.ToEnumerable(frameMemory).Select((rawFrame) => new TasFrame(rawFrame)).GetEnumerator(); + return MemoryMarshal.ToEnumerable(frameMemory).Select((rawFrame) => TasFrame.FromRaw(rawFrame)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() {