diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs b/BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs
index c2cd032..9effe7d 100644
--- a/BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs
+++ b/BallanceTasEditor/BallanceTasEditor/Backend/FpsConverter.cs
@@ -48,9 +48,9 @@ namespace BallanceTasEditor.Backend {
/// 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));
+ /// FPS in round integer
+ public static uint ToRoundFps(float delta) {
+ return Convert.ToUInt32(ToFps(delta));
}
///
diff --git a/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs b/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs
index dc046d7..e5b76db 100644
--- a/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs
+++ b/BallanceTasEditor/BallanceTasEditor/Backend/TasOperation.cs
@@ -110,7 +110,7 @@ namespace BallanceTasEditor.Backend {
// Do backup and set values at the same time
var backups = new RawTasFrame[m_EndIndex - m_StartIndex + 1];
// 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();
+ var keys = Enumerable.Range(m_StartKey.ToIndex(), m_EndKey.ToIndex() - m_StartKey.ToIndex() + 1).Select((i) => TasKey.FromIndex(i)).ToArray();
for (int index = m_StartIndex; index <= m_EndIndex; index++) {
// Fetch frame
var frame = seq.Visit(index);
@@ -292,20 +292,26 @@ namespace BallanceTasEditor.Backend {
}
}
+ public enum AddFrameOperationKind {
+ Before, After
+ }
+
public class AddFrameOperation : ITasRevocableOperation {
- public AddFrameOperation(int index, uint fps, int count) {
+ public AddFrameOperation(AddFrameOperationKind kind, int index, uint fps, int count) {
// Check argument
if (!FpsConverter.IsValidFps(fps)) {
throw new ArgumentOutOfRangeException(nameof(fps));
}
ArgumentOutOfRangeException.ThrowIfNegative(count);
// Assign argument
+ m_Kind = kind;
m_Index = index;
m_Fps = fps;
m_Count = count;
m_IsExecuted = false;
}
+ private AddFrameOperationKind m_Kind;
private int m_Index;
private uint m_Fps;
private int m_Count;
@@ -320,16 +326,32 @@ namespace BallanceTasEditor.Backend {
throw OperationUtils.ExecutionEnvironment;
}
- // Check argument.
- ArgumentOutOfRangeException.ThrowIfGreaterThan(m_Index, seq.GetCount());
+ // Check arguments
+ // If we add before some frame, the valid index can be [0, count],
+ // however, if we add after some frame, the valid index is [0, count),
+ switch (m_Kind) {
+ case AddFrameOperationKind.Before:
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(m_Index, seq.GetCount());
+ break;
+ case AddFrameOperationKind.After:
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(m_Index, seq.GetCount());
+ break;
+ default:
+ throw new UnreachableException("Unknown AddFrameOperationKind");
+ }
// Skip if count is zero.
if (m_Count != 0) {
// 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);
+ // Compute the insert index
+ var index = m_Kind switch {
+ AddFrameOperationKind.Before => m_Index,
+ AddFrameOperationKind.After => m_Index + 1,
+ _ => throw new UnreachableException("Unknown AddFrameOperationKind"),
+ };
+ seq.Insert(index, exactSizedIter);
}
// Set status
@@ -344,8 +366,16 @@ namespace BallanceTasEditor.Backend {
// Arguments were checked so we directly resotre them.
// If we inserted count is not zero, remove inserted frames, otherwise do nothing.
if (m_Count != 0) {
- seq.Remove(m_Index, m_Index + m_Count - 1);
+ // Compute the index for removing
+ var index = m_Kind switch {
+ AddFrameOperationKind.Before => m_Index,
+ AddFrameOperationKind.After => m_Index + 1,
+ _ => throw new UnreachableException("Unknown AddFrameOperationKind"),
+ };
+ // Execute removing.
+ seq.Remove(index, index + m_Count - 1);
}
+
// Modify execution status
m_IsExecuted = false;
}
diff --git a/BallanceTasEditor/BallanceTasEditorTests/AssertExtension.cs b/BallanceTasEditor/BallanceTasEditorTests/AssertExtension.cs
index ca2e247..34c2755 100644
--- a/BallanceTasEditor/BallanceTasEditorTests/AssertExtension.cs
+++ b/BallanceTasEditor/BallanceTasEditorTests/AssertExtension.cs
@@ -1,4 +1,4 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -6,8 +6,8 @@ using System.Text;
using System.Threading.Tasks;
namespace BallanceTasEditorTests {
- public static class AssertExtension {
- public static T ThrowsDerivedException(Action action) where T : Exception {
+ internal static class AssertExtension {
+ internal static T ThrowsDerivedException(Action action) where T : Exception {
try {
action();
} catch (T ex) {
diff --git a/BallanceTasEditor/BallanceTasEditorTests/Backend/TasOperationTests.cs b/BallanceTasEditor/BallanceTasEditorTests/Backend/TasOperationTests.cs
index 89db9ee..0713f9c 100644
--- a/BallanceTasEditor/BallanceTasEditorTests/Backend/TasOperationTests.cs
+++ b/BallanceTasEditor/BallanceTasEditorTests/Backend/TasOperationTests.cs
@@ -10,12 +10,123 @@ namespace BallanceTasEditorTests.Backend {
[TestClass]
public class TasOperationTests {
- private static IEnumerable
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
- public void CellKeysOperationTest(ITasSequence sequence, CellKeysOperationKind kind, int startIndex, int endIndex, TasKey startKey, TasKey endKey) {
- throw new NotImplementedException();
+ [DynamicData(nameof(CellKeysOperationTestDataProvider))]
+ public void CellKeysOperationTest(ITasSequence sequence, CellKeysOperationTestPayload payload) {
+ var op = CellKeysOperation.FromCellRange(payload.Kind, payload.StartIndex, payload.EndIndex, payload.StartKey, payload.EndKey);
+ AssertRevocableOperation(sequence, op, payload.Source, payload.Expected);
+ }
+
+ #endregion
+
+ #region Frame Fps Operation
+
+ public record FrameFpsOperationTestPayload {
+ public required string Source { get; init; }
+ public required string Expected { get; init; }
+
+ public required int StartIndex { get; init; }
+ public required int EndIndex { get; init; }
+ public required uint Fps { get; init; }
+ }
+
+ private static IEnumerable GetFrameFpsOperationTestPayload() {
+ yield return new FrameFpsOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "2,1;2,2;2,3;2,4;1,5",
+ StartIndex = 0,
+ EndIndex = 3,
+ Fps = 2
+ };
+ yield return new FrameFpsOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "1,1;4,2;4,3;4,4;4,5",
+ StartIndex = 1,
+ EndIndex = 4,
+ Fps = 4
+ };
+ }
+
+ private static IEnumerable FrameFpsOperationTestDataProvider {
+ get {
+ return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetFrameFpsOperationTestPayload());
+ }
}
///
/// FrameFpsOperation测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
- public void FrameFpsOperationTest(ITasSequence sequence) {
- throw new NotImplementedException();
+ [DynamicData(nameof(FrameFpsOperationTestDataProvider))]
+ public void FrameFpsOperationTest(ITasSequence sequence, FrameFpsOperationTestPayload payload) {
+ var op = FrameFpsOperation.FromFrameRange(payload.StartIndex, payload.EndIndex, payload.Fps);
+ AssertRevocableOperation(sequence, op, payload.Source, payload.Expected);
+ }
+
+ #endregion
+
+ #region Remove Frame Operation
+
+ public record RemoveFrameOperationTestPayload {
+ public required string Source { get; init; }
+ public required string Expected { get; init; }
+
+ public required int StartIndex { get; init; }
+ public required int EndIndex { get; init; }
+ }
+
+ private static IEnumerable GetRemoveFrameOperationTestPayload() {
+ yield return new RemoveFrameOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "1,3;1,4;1,5",
+ StartIndex = 0,
+ EndIndex = 1
+ };
+ yield return new RemoveFrameOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "",
+ StartIndex = 0,
+ EndIndex = 4
+ };
+ yield return new RemoveFrameOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "1,1;1,2;1,3",
+ StartIndex = 3,
+ EndIndex = 4
+ };
+ yield return new RemoveFrameOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "1,2;1,3;1,4;1,5",
+ StartIndex = 0,
+ EndIndex = 0
+ };
+ yield return new RemoveFrameOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "1,1;1,2;1,3;1,4",
+ StartIndex = 4,
+ EndIndex = 4
+ };
+ }
+
+ private static IEnumerable RemoveFrameOperationTestDataProvider {
+ get {
+ return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetRemoveFrameOperationTestPayload());
+ }
}
///
/// RemoveFrameOperation测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
- public void RemoveFrameOperationTest(ITasSequence sequence) {
- throw new NotImplementedException();
+ [DynamicData(nameof(RemoveFrameOperationTestDataProvider))]
+ public void RemoveFrameOperationTest(ITasSequence sequence, RemoveFrameOperationTestPayload payload) {
+ var op = new RemoveFrameOperation(payload.StartIndex, payload.EndIndex);
+ AssertRevocableOperation(sequence, op, payload.Source, payload.Expected);
+ }
+
+ #endregion
+
+ #region Add Frame Operation
+
+ public record AddFrameOperationTestPayload {
+ public required string Source { get; init; }
+ public required string Expected { get; init; }
+
+ public required AddFrameOperationKind Kind { get; init; }
+ public required int Index { get; init; }
+ public required uint Fps { get; init; }
+ public required int Count { get; init; }
+ }
+
+ private static IEnumerable GetAddFrameOperationTestPayload() {
+ yield return new AddFrameOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "1,1;1,2;240,0;240,0;240,0;1,3;1,4;1,5",
+ Kind = AddFrameOperationKind.Before,
+ Index = 2,
+ Fps = 240,
+ Count = 3
+ };
+ yield return new AddFrameOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "1,1;1,2;1,3;240,0;240,0;240,0;1,4;1,5",
+ Kind = AddFrameOperationKind.After,
+ Index = 2,
+ Fps = 240,
+ Count = 3
+ };
+ yield return new AddFrameOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "240,0;240,0;240,0;1,1;1,2;1,3;1,4;1,5",
+ Kind = AddFrameOperationKind.Before,
+ Index = 0,
+ Fps = 240,
+ Count = 3
+ };
+ yield return new AddFrameOperationTestPayload {
+ Source = "1,1;1,2;1,3;1,4;1,5",
+ Expected = "1,1;1,2;1,3;1,4;1,5;240,0;240,0;240,0",
+ Kind = AddFrameOperationKind.After,
+ Index = 4,
+ Fps = 240,
+ Count = 3
+ };
+ }
+
+ private static IEnumerable AddFrameOperationTestDataProvider {
+ get {
+ return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetAddFrameOperationTestPayload());
+ }
}
///
/// AddFrameOperation测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
- public void AddFrameOperationTest(ITasSequence sequence) {
- throw new NotImplementedException();
+ [DynamicData(nameof(AddFrameOperationTestDataProvider))]
+ public void AddFrameOperationTest(ITasSequence sequence, AddFrameOperationTestPayload payload) {
+ var op = new AddFrameOperation(payload.Kind, payload.Index, payload.Fps, payload.Count);
+ AssertRevocableOperation(sequence, op, payload.Source, payload.Expected);
+ }
+
+ #endregion
+
+ #region Insert Frame Operation
+
+ public record InsertFrameOperationTestPayload {
+ public required string Source { get; init; }
+ public required string Inserted { get; init; }
+ public required string Expected { get; init; }
+
+ public required InsertFrameOperationKind Kind { get; init; }
+ public required int Index { get; init; }
+ public required uint Fps { get; init; }
+ public required int Count { get; init; }
+ }
+
+ private static IEnumerable GetInsertFrameOperationTestPayload() {
+ yield break;
+ }
+
+ private static IEnumerable InsertFrameOperationTestDataProvider {
+ get {
+ return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetInsertFrameOperationTestPayload());
+ }
}
///
/// InsertFrameOperation测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
- public void InsertFrameOperationTest(ITasSequence sequence) {
- throw new NotImplementedException();
+ [DynamicData(nameof(InsertFrameOperationTestDataProvider))]
+ public void InsertFrameOperationTest(ITasSequence sequence, InsertFrameOperationTestPayload payload) {
+ //var op = new InsertFrameOperation(payload.Kind, payload.Index, payload.Fps, payload.Count);
+ //AssertRevocableOperation(sequence, op, payload.Source, payload.Expected);
+ }
+
+ #endregion
+
+ #region Clear Keys Operation
+
+ public record ClearKeysOperationTestPayload {
+ public required string Source { get; init; }
+ public required string Expected { get; init; }
+ }
+
+ private static IEnumerable GetClearKeysOperationTestPayload() {
+ yield break;
+ }
+
+ private static IEnumerable ClearKeysOperationTestDataProvider {
+ get {
+ return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetClearKeysOperationTestPayload());
+ }
}
///
/// ClearKeysOperation测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
- public void ClearKeysOperationTest(ITasSequence sequence) {
- throw new NotImplementedException();
+ [DynamicData(nameof(ClearKeysOperationTestDataProvider))]
+ public void ClearKeysOperationTest(ITasSequence sequence, ClearKeysOperationTestPayload payload) {
+ var op = new ClearKeysOperation();
+ AssertOperation(sequence, op, payload.Source, payload.Expected);
+ }
+
+ #endregion
+
+ #region Uniform Fps Operation
+
+ public record UniformFpsOperationTestPayload {
+ public required string Source { get; init; }
+ public required string Expected { get; init; }
+
+ public required uint Fps { get; init; }
+ }
+
+ private static IEnumerable GetUniformFpsOperationTestPayload() {
+ yield break;
+ }
+
+ private static IEnumerable UniformFpsOperationTestDataProvider {
+ get {
+ return TestDataCombiner(TasSequenceUtils.EnumerateTasSequenceImplementation(), GetUniformFpsOperationTestPayload());
+ }
}
///
/// UniformFpsOperation测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
- public void UniformFpsOperationTest(ITasSequence sequence) {
- throw new NotImplementedException();
+ [DynamicData(nameof(UniformFpsOperationTestDataProvider))]
+ public void UniformFpsOperationTest(ITasSequence sequence, UniformFpsOperationTestPayload payload) {
+ var op = new UniformFpsOperation(payload.Fps);
+ AssertOperation(sequence, op, payload.Source, payload.Expected);
}
+ #endregion
+
}
}
diff --git a/BallanceTasEditor/BallanceTasEditorTests/Backend/TasSequenceTests.cs b/BallanceTasEditor/BallanceTasEditorTests/Backend/TasSequenceTests.cs
index 36ab806..5c8b93f 100644
--- a/BallanceTasEditor/BallanceTasEditorTests/Backend/TasSequenceTests.cs
+++ b/BallanceTasEditor/BallanceTasEditorTests/Backend/TasSequenceTests.cs
@@ -28,12 +28,9 @@ namespace BallanceTasEditorTests.Backend {
return new ExactSizeEnumerableAdapter(BLANK, BLANK.Length);
}
- private static IEnumerable TasSequenceInstanceProvider {
+ private static IEnumerable CommonTestDataProvider {
get {
- yield return new object[] { new ListTasSequence() };
- yield return new object[] { new LegacyTasSequence() };
- // TODO: Add GapBufferTasSequence once we finish it.
- //yield return new object[] { new GapBufferTasSequence() };
+ return TasSequenceUtils.EnumerateTasSequenceImplementation().Select((seq) => new object[] { seq });
}
}
@@ -41,7 +38,7 @@ namespace BallanceTasEditorTests.Backend {
/// Visit函数独立测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
+ [DynamicData(nameof(CommonTestDataProvider))]
public void VisitTest(ITasSequence sequence) {
// 空时访问
AssertExtension.ThrowsDerivedException(() => sequence.Visit(-1));
@@ -62,7 +59,7 @@ namespace BallanceTasEditorTests.Backend {
/// Insert函数独立测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
+ [DynamicData(nameof(CommonTestDataProvider))]
public void InsertTest(ITasSequence sequence) {
// 需要在不同的存储器上,分别检测在空的时候插入,
// 和在非空时的头,中,尾分别插入的结果。
@@ -101,7 +98,7 @@ namespace BallanceTasEditorTests.Backend {
/// Remove函数独立测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
+ [DynamicData(nameof(CommonTestDataProvider))]
public void RemoveTest(ITasSequence sequence) {
// 插入项目后尝试在头中尾分别删除
var indices = new int[] { 0, PROBE.Length / 2, PROBE.Length - 1 };
@@ -128,7 +125,7 @@ namespace BallanceTasEditorTests.Backend {
/// Clear函数独立测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
+ [DynamicData(nameof(CommonTestDataProvider))]
public void ClearTest(ITasSequence sequence) {
// 设置数据后清空
sequence.Insert(0, GetExactSizeProbe());
@@ -142,7 +139,7 @@ namespace BallanceTasEditorTests.Backend {
/// IsEmpty函数独立测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
+ [DynamicData(nameof(CommonTestDataProvider))]
public void IsEmptyTest(ITasSequence sequence) {
// 检查是否为空
Assert.IsTrue(sequence.IsEmpty());
@@ -156,7 +153,7 @@ namespace BallanceTasEditorTests.Backend {
/// GetCount函数独立测试。
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
+ [DynamicData(nameof(CommonTestDataProvider))]
public void GetCountTest(ITasSequence sequence) {
// 检查长度为0
Assert.AreEqual(sequence.GetCount(), 0);
@@ -171,7 +168,7 @@ namespace BallanceTasEditorTests.Backend {
///
///
[DataTestMethod]
- [DynamicData(nameof(TasSequenceInstanceProvider))]
+ [DynamicData(nameof(CommonTestDataProvider))]
public void HybridTest(ITasSequence sequence) {
// 检查空和大小
Assert.IsTrue(sequence.IsEmpty());
diff --git a/BallanceTasEditor/BallanceTasEditorTests/TasSequenceUtils.cs b/BallanceTasEditor/BallanceTasEditorTests/TasSequenceUtils.cs
new file mode 100644
index 0000000..a24d4dc
--- /dev/null
+++ b/BallanceTasEditor/BallanceTasEditorTests/TasSequenceUtils.cs
@@ -0,0 +1,22 @@
+using BallanceTasEditor.Backend;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BallanceTasEditorTests {
+
+ internal static class TasSequenceUtils {
+
+ internal static IEnumerable EnumerateTasSequenceImplementation() {
+ yield return new ListTasSequence();
+ yield return new LegacyTasSequence();
+ // TODO: Add GapBufferTasSequence once we finish it.
+ //yield return new GapBufferTasSequence();
+ }
+
+ }
+
+}