From c73f8c1f42e7bb57d6c23cfe400fe0001155c844 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 6 May 2020 16:03:04 +0200 Subject: [PATCH] [csharp] Ported automated test set 'AnimationStateTests' to csharp and Unity. --- spine-csharp/tests/assets/test.json | 32 + spine-csharp/tests/src/AnimationStateTests.cs | 995 ++++++++++++++++++ spine-unity/Assets/SpineTests.meta | 8 + .../Assets/SpineTests/SpineTests.asmdef | 13 + .../Assets/SpineTests/SpineTests.asmdef.meta | 7 + .../Assets/SpineTests/spine-csharp-tests.meta | 8 + .../add spine-csharp-tests here.txt | 1 + .../add spine-csharp-tests here.txt.meta | 7 + spine-unity/Assets/SpineTests/src.meta | 8 + .../SpineTests/src/RunAnimationStateTests.cs | 27 + .../src/RunAnimationStateTests.cs.meta | 11 + 11 files changed, 1117 insertions(+) create mode 100644 spine-csharp/tests/assets/test.json create mode 100644 spine-csharp/tests/src/AnimationStateTests.cs create mode 100644 spine-unity/Assets/SpineTests.meta create mode 100644 spine-unity/Assets/SpineTests/SpineTests.asmdef create mode 100644 spine-unity/Assets/SpineTests/SpineTests.asmdef.meta create mode 100644 spine-unity/Assets/SpineTests/spine-csharp-tests.meta create mode 100644 spine-unity/Assets/SpineTests/spine-csharp-tests/add spine-csharp-tests here.txt create mode 100644 spine-unity/Assets/SpineTests/spine-csharp-tests/add spine-csharp-tests here.txt.meta create mode 100644 spine-unity/Assets/SpineTests/src.meta create mode 100644 spine-unity/Assets/SpineTests/src/RunAnimationStateTests.cs create mode 100644 spine-unity/Assets/SpineTests/src/RunAnimationStateTests.cs.meta diff --git a/spine-csharp/tests/assets/test.json b/spine-csharp/tests/assets/test.json new file mode 100644 index 000000000..c95671028 --- /dev/null +++ b/spine-csharp/tests/assets/test.json @@ -0,0 +1,32 @@ +{ +"skeleton": { "hash": "hj8P+t8L2OIWCj7RHV1Nzql4Y5E", "spine": "3.8.95", "images": "", "audio": "" }, +"bones": [ + { "name": "root" } +], +"events": { + "event": {} +}, +"animations": { + "events0": { + "events": [ + { "name": "event", "string": "0" }, + { "time": 0.4667, "name": "event", "string": "14" }, + { "time": 1, "name": "event", "string": "30" } + ] + }, + "events1": { + "events": [ + { "name": "event", "string": "0" }, + { "time": 0.4667, "name": "event", "string": "14" }, + { "time": 1, "name": "event", "string": "30" } + ] + }, + "events2": { + "events": [ + { "name": "event", "string": "0" }, + { "time": 0.4667, "name": "event", "string": "14" }, + { "time": 1, "name": "event", "string": "30" } + ] + } +} +} \ No newline at end of file diff --git a/spine-csharp/tests/src/AnimationStateTests.cs b/spine-csharp/tests/src/AnimationStateTests.cs new file mode 100644 index 000000000..8ac46e332 --- /dev/null +++ b/spine-csharp/tests/src/AnimationStateTests.cs @@ -0,0 +1,995 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +//#define RUN_ADDITIONAL_FORUM_RELATED_TEST + +using Spine; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Spine { + + public class AnimationStateTests { + + static readonly float FLOAT_ROUNDING_ERROR = 0.000001f; // 32 bits + static bool IsEqual (float a, float b) { + return Math.Abs(a - b) <= FLOAT_ROUNDING_ERROR; + } + + class NullAttachmentLoader : AttachmentLoader { + public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { + return null; + } + + public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { + return null; + } + + public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { + return null; + } + + public ClippingAttachment NewClippingAttachment (Skin skin, string name) { + return null; + } + + public PathAttachment NewPathAttachment (Skin skin, string name) { + return null; + } + + public PointAttachment NewPointAttachment (Skin skin, string name) { + return null; + } + } + + class LoggingAnimationStateListener { + + AnimationStateTests tests; + + public LoggingAnimationStateListener (AnimationStateTests tests) { + this.tests = tests; + } + + public void RegisterAtAnimationState (AnimationState state) { + state.Start += Start; + state.Interrupt += Interrupt; + state.End += End; + state.Dispose += Dispose; + state.Complete += Complete; + state.Event += Event; + } + + public void UnregisterFromAnimationState (AnimationState state) { + state.Start -= Start; + state.Interrupt -= Interrupt; + state.End -= End; + state.Dispose -= Dispose; + state.Complete -= Complete; + state.Event -= Event; + } + + public void Start (TrackEntry entry) { + Add(tests.Actual("start", entry)); + } + + public void Interrupt (TrackEntry entry) { + Add(tests.Actual("interrupt", entry)); + } + + public void End (TrackEntry entry) { + Add(tests.Actual("end", entry)); + } + + public void Dispose (TrackEntry entry) { + Add(tests.Actual("dispose", entry)); + } + + public void Complete (TrackEntry entry) { + Add(tests.Actual("complete", entry)); + } + + public void Event (TrackEntry entry, Event ev) { + Add(tests.Actual("event " + ev.String, entry)); + } + + private void Add (Result result) { + while (tests.expected.Count > tests.actual.Count) { + Result note = tests.expected[tests.actual.Count]; + if (!note.note) break; + tests.actual.Add(note); + Log(note.name); + } + + string message = result.ToString(); + if (tests.actual.Count >= tests.expected.Count) { + message += "FAIL: "; + tests.fail = true; + } else if (!tests.expected[tests.actual.Count].Equals(result)) { + message += "FAIL: " + tests.expected[tests.actual.Count]; + tests.fail = true; + } else + message += "PASS"; + Log(message); + tests.actual.Add(result); + } + }; + + readonly SkeletonJson json = new SkeletonJson(); + + LoggingAnimationStateListener stateListener = null; + + readonly SkeletonData skeletonData; + readonly List actual = new List(); + readonly List expected = new List(); + + AnimationStateData stateData; + AnimationState state; + float time = 0; + bool fail; + int test; + + AnimationStateTests (string testJsonFilePath) { + skeletonData = json.ReadSkeletonData(testJsonFilePath); + + TrackEntry entry; + + Setup("0.1 time step", // 1 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "end", 1, 1.1f), // + Expect(0, "dispose", 1, 1.1f) // + ); + state.SetAnimation(0, "events0", false).TrackEnd = 1; + Run(0.1f, 1000, null); + + Setup("1/60 time step, dispose queued", // 2 + Expect(0, "start", 0, 0), // + Expect(0, "interrupt", 0, 0), // + Expect(0, "end", 0, 0), // + Expect(0, "dispose", 0, 0), // + Expect(1, "dispose", 0, 0), // + Expect(0, "dispose", 0, 0), // + Expect(1, "dispose", 0, 0), // + + Note("First 2 set/addAnimation calls are done."), + + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.483f, 0.483f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "end", 1, 1.017f), // + Expect(0, "dispose", 1, 1.017f) // + ); + state.SetAnimation(0, "events0", false); + state.AddAnimation(0, "events1", false, 0); + state.AddAnimation(0, "events0", false, 0); + state.AddAnimation(0, "events1", false, 0); + state.SetAnimation(0, "events0", false).TrackEnd = 1; + Run(1 / 60f, 1000, null); + + Setup("30 time step", // 3 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 30, 30), // + Expect(0, "event 30", 30, 30), // + Expect(0, "complete", 30, 30), // + Expect(0, "end", 30, 60), // + Expect(0, "dispose", 30, 60) // + ); + state.SetAnimation(0, "events0", false).TrackEnd = 1; + Run(30, 1000, null); + + Setup("1 time step", // 4 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 1, 1), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "end", 1, 2), // + Expect(0, "dispose", 1, 2) // + ); + state.SetAnimation(0, "events0", false).TrackEnd = 1; + Run(1, 1.01f, null); + + Setup("interrupt", // 5 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "interrupt", 1.1f, 1.1f), // + + Expect(1, "start", 0.1f, 1.1f), // + Expect(1, "event 0", 0.1f, 1.1f), // + + Expect(0, "end", 1.1f, 1.2f), // + Expect(0, "dispose", 1.1f, 1.2f), // + + Expect(1, "event 14", 0.5f, 1.5f), // + Expect(1, "event 30", 1, 2), // + Expect(1, "complete", 1, 2), // + Expect(1, "interrupt", 1.1f, 2.1f), // + + Expect(0, "start", 0.1f, 2.1f), // + Expect(0, "event 0", 0.1f, 2.1f), // + + Expect(1, "end", 1.1f, 2.2f), // + Expect(1, "dispose", 1.1f, 2.2f), // + + Expect(0, "event 14", 0.5f, 2.5f), // + Expect(0, "event 30", 1, 3), // + Expect(0, "complete", 1, 3), // + Expect(0, "end", 1, 3.1f), // + Expect(0, "dispose", 1, 3.1f) // + ); + state.SetAnimation(0, "events0", false); + state.AddAnimation(0, "events1", false, 0); + state.AddAnimation(0, "events0", false, 0).TrackEnd = 1; + Run(0.1f, 4f, null); + + Setup("interrupt with delay", // 6 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "interrupt", 0.6f, 0.6f), // + + Expect(1, "start", 0.1f, 0.6f), // + Expect(1, "event 0", 0.1f, 0.6f), // + + Expect(0, "end", 0.6f, 0.7f), // + Expect(0, "dispose", 0.6f, 0.7f), // + + Expect(1, "event 14", 0.5f, 1.0f), // + Expect(1, "event 30", 1, 1.5f), // + Expect(1, "complete", 1, 1.5f), // + Expect(1, "end", 1, 1.6f), // + Expect(1, "dispose", 1, 1.6f) // + ); + state.SetAnimation(0, "events0", false); + state.AddAnimation(0, "events1", false, 0.5f).TrackEnd = 1; + Run(0.1f, 1000, null); + + Setup("interrupt with delay and mix time", // 7 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "interrupt", 1, 1), // + + Expect(1, "start", 0.1f, 1), // + + Expect(0, "complete", 1, 1), // + + Expect(1, "event 0", 0.1f, 1), // + Expect(1, "event 14", 0.5f, 1.4f), // + + Expect(0, "end", 1.6f, 1.7f), // + Expect(0, "dispose", 1.6f, 1.7f), // + + Expect(1, "event 30", 1, 1.9f), // + Expect(1, "complete", 1, 1.9f), // + Expect(1, "end", 1, 2), // + Expect(1, "dispose", 1, 2) // + ); + stateData.SetMix("events0", "events1", 0.7f); + state.SetAnimation(0, "events0", true); + state.AddAnimation(0, "events1", false, 0.9f).TrackEnd = 1; + Run(0.1f, 1000, null); + + Setup("animation 0 events do not fire during mix", // 8 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "interrupt", 0.5f, 0.5f), // + + Expect(1, "start", 0.1f, 0.5f), // + Expect(1, "event 0", 0.1f, 0.5f), // + Expect(1, "event 14", 0.5f, 0.9f), // + + Expect(0, "complete", 1, 1), // + Expect(0, "end", 1.1f, 1.2f), // + Expect(0, "dispose", 1.1f, 1.2f), // + + Expect(1, "event 30", 1, 1.4f), // + Expect(1, "complete", 1, 1.4f), // + Expect(1, "end", 1, 1.5f), // + Expect(1, "dispose", 1, 1.5f) // + ); + stateData.DefaultMix = 0.7f; + state.SetAnimation(0, "events0", false); + state.AddAnimation(0, "events1", false, 0.4f).TrackEnd = 1; + Run(0.1f, 1000, null); + + Setup("event threshold, some animation 0 events fire during mix", // 9 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "interrupt", 0.5f, 0.5f), // + + Expect(1, "start", 0.1f, 0.5f), // + + Expect(0, "event 14", 0.5f, 0.5f), // + + Expect(1, "event 0", 0.1f, 0.5f), // + Expect(1, "event 14", 0.5f, 0.9f), // + + Expect(0, "complete", 1, 1), // + Expect(0, "end", 1.1f, 1.2f), // + Expect(0, "dispose", 1.1f, 1.2f), // + + Expect(1, "event 30", 1, 1.4f), // + Expect(1, "complete", 1, 1.4f), // + Expect(1, "end", 1, 1.5f), // + Expect(1, "dispose", 1, 1.5f) // + ); + stateData.SetMix("events0", "events1", 0.7f); + state.SetAnimation(0, "events0", false).EventThreshold = 0.5f; + state.AddAnimation(0, "events1", false, 0.4f).TrackEnd = 1; + Run(0.1f, 1000, null); + + Setup("event threshold, all animation 0 events fire during mix", // 10 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "interrupt", 0.9f, 0.9f), // + + Expect(1, "start", 0.1f, 0.9f), // + Expect(1, "event 0", 0.1f, 0.9f), // + + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "event 0", 1, 1), // + + Expect(1, "event 14", 0.5f, 1.3f), // + + Expect(0, "end", 1.5f, 1.6f), // + Expect(0, "dispose", 1.5f, 1.6f), // + + Expect(1, "event 30", 1, 1.8f), // + Expect(1, "complete", 1, 1.8f), // + Expect(1, "end", 1, 1.9f), // + Expect(1, "dispose", 1, 1.9f) // + ); + state.SetAnimation(0, "events0", true).EventThreshold = 1; + entry = state.AddAnimation(0, "events1", false, 0.8f); + entry.MixDuration = 0.7f; + entry.TrackEnd = 1; + Run(0.1f, 1000, null); + + Setup("looping", // 11 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "event 0", 1, 1), // + Expect(0, "event 14", 1.5f, 1.5f), // + Expect(0, "event 30", 2, 2), // + Expect(0, "complete", 2, 2), // + Expect(0, "event 0", 2, 2), // + Expect(0, "event 14", 2.5f, 2.5f), // + Expect(0, "event 30", 3, 3), // + Expect(0, "complete", 3, 3), // + Expect(0, "event 0", 3, 3), // + Expect(0, "event 14", 3.5f, 3.5f), // + Expect(0, "event 30", 4, 4), // + Expect(0, "complete", 4, 4), // + Expect(0, "event 0", 4, 4), // + Expect(0, "end", 4.1f, 4.1f), // + Expect(0, "dispose", 4.1f, 4.1f) // + ); + state.SetAnimation(0, "events0", true); + Run(0.1f, 4, null); + + Setup("not looping, track end past animation 0 duration", // 12 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "interrupt", 2.1f, 2.1f), // + + Expect(1, "start", 0.1f, 2.1f), // + Expect(1, "event 0", 0.1f, 2.1f), // + + Expect(0, "end", 2.1f, 2.2f), // + Expect(0, "dispose", 2.1f, 2.2f), // + + Expect(1, "event 14", 0.5f, 2.5f), // + Expect(1, "event 30", 1, 3), // + Expect(1, "complete", 1, 3), // + Expect(1, "end", 1, 3.1f), // + Expect(1, "dispose", 1, 3.1f) // + ); + state.SetAnimation(0, "events0", false); + state.AddAnimation(0, "events1", false, 2).TrackEnd = 1; + Run(0.1f, 4f, null); + + + Setup("interrupt animation after first loop complete", // 13 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "event 0", 1, 1), // + Expect(0, "event 14", 1.5f, 1.5f), // + Expect(0, "event 30", 2, 2), // + Expect(0, "complete", 2, 2), // + Expect(0, "event 0", 2, 2), // + Expect(0, "interrupt", 2.1f, 2.1f), // + + Expect(1, "start", 0.1f, 2.1f), // + Expect(1, "event 0", 0.1f, 2.1f), // + + Expect(0, "end", 2.1f, 2.2f), // + Expect(0, "dispose", 2.1f, 2.2f), // + + Expect(1, "event 14", 0.5f, 2.5f), // + Expect(1, "event 30", 1, 3), // + Expect(1, "complete", 1, 3), // + Expect(1, "end", 1, 3.1f), // + Expect(1, "dispose", 1, 3.1f) // + ); + state.SetAnimation(0, "events0", true); + Run(0.1f, 6, new TestListener( + (time) => { + if (IsEqual(time, 1.4f)) state.AddAnimation(0, "events1", false, 0).TrackEnd = 1; + })); + + Setup ("add animation on empty track", // 14 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "end", 1, 1.1f), // + Expect(0, "dispose", 1, 1.1f) // + ); + state.AddAnimation(0, "events0", false, 0).TrackEnd = 1; + Run(0.1f, 1.9f, null); + + Setup("end time beyond non-looping animation duration", // 15 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "end", 9f, 9.1f), // + Expect(0, "dispose", 9f, 9.1f) // + ); + state.SetAnimation(0, "events0", false).TrackEnd = 9; + Run(0.1f, 10, null); + + Setup("looping with animation start", // 16 + Expect(0, "start", 0, 0), // + Expect(0, "event 30", 0.4f, 0.4f), // + Expect(0, "complete", 0.4f, 0.4f), // + Expect(0, "event 30", 0.8f, 0.8f), // + Expect(0, "complete", 0.8f, 0.8f), // + Expect(0, "event 30", 1.2f, 1.2f), // + Expect(0, "complete", 1.2f, 1.2f), // + Expect(0, "end", 1.4f, 1.4f), // + Expect(0, "dispose", 1.4f, 1.4f) // + ); + entry = state.SetAnimation(0, "events0", true); + entry.AnimationLast = 0.6f; + entry.AnimationStart = 0.6f; + Run(0.1f, 1.4f, null); + + Setup("looping with animation start and end", // 17 + Expect(0, "start", 0, 0), // + Expect(0, "event 14", 0.3f, 0.3f), // + Expect(0, "complete", 0.6f, 0.6f), // + Expect(0, "event 14", 0.9f, 0.9f), // + Expect(0, "complete", 1.2f, 1.2f), // + Expect(0, "event 14", 1.5f, 1.5f), // + Expect(0, "end", 1.8f, 1.8f), // + Expect(0, "dispose", 1.8f, 1.8f) // + ); + entry = state.SetAnimation(0, "events0", true); + entry.AnimationStart = 0.2f; + entry.AnimationLast = 0.2f; + entry.AnimationEnd = 0.8f; + Run(0.1f, 1.8f, null); + + Setup("non-looping with animation start and end", // 18 + Expect(0, "start", 0, 0), // + Expect(0, "event 14", 0.3f, 0.3f), // + Expect(0, "complete", 0.6f, 0.6f), // + Expect(0, "end", 1, 1.1f), // + Expect(0, "dispose", 1, 1.1f) // + ); + entry = state.SetAnimation(0, "events0", false); + entry.AnimationStart = 0.2f; + entry.AnimationLast = 0.2f; + entry.AnimationEnd = 0.8f; + entry.TrackEnd = 1; + Run(0.1f, 1.8f, null); + + Setup("mix out looping with animation start and end", // 19 + Expect(0, "start", 0, 0), // + Expect(0, "event 14", 0.3f, 0.3f), // + Expect(0, "complete", 0.6f, 0.6f), // + Expect(0, "interrupt", 0.8f, 0.8f), // + + Expect(1, "start", 0.1f, 0.8f), // + Expect(1, "event 0", 0.1f, 0.8f), // + + Expect(0, "event 14", 0.9f, 0.9f), // + Expect(0, "complete", 1.2f, 1.2f), // + + Expect(1, "event 14", 0.5f, 1.2f), // + + Expect(0, "end", 1.4f, 1.5f), // + Expect(0, "dispose", 1.4f, 1.5f), // + + Expect(1, "event 30", 1, 1.7f), // + Expect(1, "complete", 1, 1.7f), // + Expect(1, "end", 1, 1.8f), // + Expect(1, "dispose", 1, 1.8f) // + ); + entry = state.SetAnimation(0, "events0", true); + entry.AnimationStart = (0.2f); + entry.AnimationLast = (0.2f); + entry.AnimationEnd = (0.8f); + entry.EventThreshold = 1; + entry = state.AddAnimation(0, "events1", false, 0.7f); + entry.MixDuration = (0.7f); + entry.TrackEnd = 1; + Run(0.1f, 20, null); + + Setup("setAnimation with track entry mix", // 20 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "event 0", 1, 1), // + Expect(0, "interrupt", 1, 1), // + + Expect(1, "start", 0, 1), // + + Expect(1, "event 0", 0.1f, 1.1f), // + Expect(1, "event 14", 0.5f, 1.5f), // + + Expect(0, "end", 1.7f, 1.8f), // + Expect(0, "dispose", 1.7f, 1.8f), // + + Expect(1, "event 30", 1, 2), // + Expect(1, "complete", 1, 2), // + Expect(1, "end", 1, 2.1f), // + Expect(1, "dispose", 1, 2.1f) // + ); + state.SetAnimation(0, "events0", true); + Run(0.1f, 1000, new TestListener( + (time) => { + if (IsEqual(time, 1f)) { + TrackEntry ent = state.SetAnimation(0, "events1", false); + ent.MixDuration = (0.7f); + ent.TrackEnd = 1; + } + })); + + Setup("setAnimation twice", // 21 + Expect(0, "start", 0, 0), // + Expect(0, "interrupt", 0, 0), // + Expect(0, "end", 0, 0), // + Expect(0, "dispose", 0, 0), // + + Expect(1, "start", 0, 0), // + Expect(1, "event 0", 0, 0), // + Expect(1, "event 14", 0.5f, 0.5f), // + + Note("First 2 setAnimation calls are done."), + + Expect(1, "interrupt", 0.8f, 0.8f), // + + Expect(0, "start", 0, 0.8f), // + Expect(0, "interrupt", 0, 0.8f), // + Expect(0, "end", 0, 0.8f), // + Expect(0, "dispose", 0, 0.8f), // + + Expect(2, "start", 0, 0.8f), // + Expect(2, "event 0", 0.1f, 0.9f), // + + Expect(1, "end", 0.9f, 1), // + Expect(1, "dispose", 0.9f, 1), // + + Expect(2, "event 14", 0.5f, 1.3f), // + Expect(2, "event 30", 1, 1.8f), // + Expect(2, "complete", 1, 1.8f), // + Expect(2, "end", 1, 1.9f), // + Expect(2, "dispose", 1, 1.9f) // + ); + state.SetAnimation(0, "events0", false); // First should be ignored. + state.SetAnimation(0, "events1", false); + Run(0.1f, 1000, new TestListener( + (time) => { + if (IsEqual(time, 0.8f)) { + state.SetAnimation(0, "events0", false); // First should be ignored. + state.SetAnimation(0, "events2", false).TrackEnd = 1; + } + })); + + Setup("setAnimation twice with multiple mixing", // 22 + Expect(0, "start", 0, 0), // + Expect(0, "interrupt", 0, 0), // + Expect(0, "end", 0, 0), // + Expect(0, "dispose", 0, 0), // + + Expect(1, "start", 0, 0), // + Expect(1, "event 0", 0, 0), // + + Note("First 2 setAnimation calls are done."), + + Expect(1, "interrupt", 0.2f, 0.2f), // + + Expect(0, "start", 0, 0.2f), // + Expect(0, "interrupt", 0, 0.2f), // + Expect(0, "end", 0, 0.2f), // + Expect(0, "dispose", 0, 0.2f), // + + Expect(2, "start", 0, 0.2f), // + Expect(2, "event 0", 0.1f, 0.3f), // + + Note("Second 2 setAnimation calls are done."), + + Expect(2, "interrupt", 0.2f, 0.4f), // + + Expect(1, "start", 0, 0.4f), // + Expect(1, "interrupt", 0, 0.4f), // + Expect(1, "end", 0, 0.4f), // + Expect(1, "dispose", 0, 0.4f), // + + Expect(0, "start", 0, 0.4f), // + Expect(0, "event 0", 0.1f, 0.5f), // + + Expect(1, "end", 0.8f, 0.9f), // + Expect(1, "dispose", 0.8f, 0.9f), // + + Expect(0, "event 14", 0.5f, 0.9f), // + + Expect(2, "end", 0.8f, 1.1f), // + Expect(2, "dispose", 0.8f, 1.1f), // + + Expect(0, "event 30", 1, 1.4f), // + Expect(0, "complete", 1, 1.4f), // + Expect(0, "end", 1, 1.5f), // + Expect(0, "dispose", 1, 1.5f) // + ); + stateData.DefaultMix = 0.6f; + state.SetAnimation(0, "events0", false); // First should be ignored. + state.SetAnimation(0, "events1", false); + Run(0.1f, 1000, new TestListener( + (time) => { + if (IsEqual(time, 0.2f)) { + state.SetAnimation(0, "events0", false); // First should be ignored. + state.SetAnimation(0, "events2", false); + } + if (IsEqual(time, 0.4f)) { + state.SetAnimation(0, "events1", false); // First should be ignored. + state.SetAnimation(0, "events0", false).TrackEnd = 1; + } + })); + + Setup("addAnimation with delay on empty track", // 23 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 5), // + Expect(0, "event 14", 0.5f, 5.5f), // + Expect(0, "event 30", 1, 6), // + Expect(0, "complete", 1, 6), // + Expect(0, "end", 1, 6.1f), // + Expect(0, "dispose", 1, 6.1f) // + ); + state.AddAnimation(0, "events0", false, 5).TrackEnd = 1; + Run(0.1f, 10, null); + + Setup("setAnimation during AnimationStateListener"); // 24 + state.Start += (trackEntry) => { + if (trackEntry.Animation.Name.Equals("events0")) state.SetAnimation(1, "events1", false); + }; + state.Interrupt += (trackEntry) => { + state.AddAnimation(3, "events1", false, 0); + }; + state.End += (trackEntry) => { + if (trackEntry.Animation.Name.Equals("events0")) state.SetAnimation(0, "events1", false); + }; + state.Dispose += (trackEntry) => { + if (trackEntry.Animation.Name.Equals("events0")) state.SetAnimation(1, "events1", false); + }; + state.Complete += (trackEntry) => { + if (trackEntry.Animation.Name.Equals("events0")) state.SetAnimation(1, "events1", false); + }; + state.Event += (trackEntry, ev) => { + if (trackEntry.TrackIndex != 2) state.SetAnimation(2, "events1", false); + }; + state.AddAnimation(0, "events0", false, 0); + state.AddAnimation(0, "events1", false, 0); + state.SetAnimation(1, "events1", false).TrackEnd = 1; + Run(0.1f, 10, null); + + Setup("clearTrack", // 25 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "end", 0.7f, 0.7f), // + Expect(0, "dispose", 0.7f, 0.7f) // + ); + state.AddAnimation(0, "events0", false, 0).TrackEnd = 1; + Run(0.1f, 10, new TestListener( + (time) => { + if (IsEqual(time, 0.7f)) state.ClearTrack(0); + })); + + Setup("setEmptyAnimation", // 26 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "interrupt", 0.7f, 0.7f), // + + Expect(-1, "start", 0, 0.7f), // + Expect(-1, "complete", 0.1f, 0.8f), // + + Expect(0, "end", 0.8f, 0.9f), // + Expect(0, "dispose", 0.8f, 0.9f), // + + Expect(-1, "end", 0.2f, 1), // + Expect(-1, "dispose", 0.2f, 1) // + ); + state.AddAnimation(0, "events0", false, 0).TrackEnd = 1; + Run(0.1f, 10, new TestListener( + (time) => { + if (IsEqual(time, 0.7f)) state.SetEmptyAnimation(0, 0); + })); + + Setup("TrackEntry listener"); // 27 + int counter = 0; + entry = state.AddAnimation(0, "events0", false, 0); + entry.Start += (trackEntry) => { + Interlocked.Add(ref counter, 1 << 1); + }; + entry.Interrupt += (trackEntry) => { + Interlocked.Add(ref counter, 1 << 5); + }; + entry.End += (trackEntry) => { + Interlocked.Add(ref counter, 1 << 9); + }; + entry.Dispose += (trackEntry) => { + Interlocked.Add(ref counter, 1 << 13); + }; + entry.Complete += (trackEntry) => { + Interlocked.Add(ref counter, 1 << 17); + }; + entry.Event += (trackEntry, ev) => { + Interlocked.Add(ref counter, 1 << 21); + }; + state.AddAnimation(0, "events0", false, 0); + state.AddAnimation(0, "events1", false, 0); + state.SetAnimation(1, "events1", false).TrackEnd = 1; + Run(0.1f, 10, null); + if (counter != 15082016) { + string message = "TEST 27 FAILED! " + counter; + Log(message); + FailTestRun(message); + } + +#if RUN_ADDITIONAL_FORUM_RELATED_TEST + Setup("0.1 time step, start and add", // 2 + Expect(0, "start", 0, 0), // + Expect(0, "event 0", 0, 0), // + Expect(0, "event 14", 0.5f, 0.5f), // + Expect(0, "event 30", 1, 1), // + Expect(0, "complete", 1, 1), // + Expect(0, "interrupt", 1.1f, 1.1f), // + Expect(1, "start", 0.1f, 1.1f), // + Expect(1, "event 0", 0.1f, 1.1f), // + Expect(0, "end", 1.3f, 1.4f), // + Expect(0, "dispose", 1.3f, 1.4f), // + Expect(1, "event 14", 0.5f, 1.5f), // + Expect(1, "event 30", 1, 2), // + Expect(1, "complete", 1, 2), // + Expect(1, "end", 1, 2.1f), // + Expect(1, "dispose", 1, 2.1f) // + ); + state.SetAnimation(0, "events0", false); + var entry1 = state.AddAnimation(0, "events1", false, 0); + entry1.MixDuration = 0.25f; + entry1.TrackEnd = 1.0f; + Run(0.1f, 1000, null); +#endif // RUN_ADDITIONAL_FORUM_RELATED_TEST + + Log("AnimationState tests passed."); + } + + void Setup (string description, params Result[] expectedArray) { + test++; + expected.AddRange(expectedArray); + stateData = new AnimationStateData(skeletonData); + state = new AnimationState(stateData); + + stateListener = new LoggingAnimationStateListener(this); + time = 0; + fail = false; + Log(test + ": " + description); + if (expectedArray.Length > 0) { + stateListener.RegisterAtAnimationState(state); + Log(string.Format("{0,-3}{1,-12}{2,-7}{3,-7}{4,-7}", "#", "EVENT", "TRACK", "TOTAL", "RESULT")); + } + } + + void Run (float incr, float endTime, TestListener listener) { + Skeleton skeleton = new Skeleton(skeletonData); + state.Apply(skeleton); + while (time < endTime) { + time += incr; + skeleton.Update(incr); + state.Update(incr); + + // Reduce float discrepancies for tests. + foreach (TrackEntry entry in state.Tracks) { + if (entry == null) continue; + entry.TrackTime = Round(entry.TrackTime, 6); + entry.Delay = Round(entry.Delay, 3); + if (entry.MixingFrom != null) entry.MixingFrom.TrackTime = Round(entry.MixingFrom.TrackTime, 6); + } + + state.Apply(skeleton); + + // Apply multiple times to ensure no side effects. + if (expected.Count > 0) stateListener.UnregisterFromAnimationState(state); + state.Apply(skeleton); + state.Apply(skeleton); + if (expected.Count > 0) stateListener.RegisterAtAnimationState(state); + + if (listener != null) listener.Frame(time); + } + state.ClearTracks(); + + // Expecting more than actual is a failure. + for (int i = actual.Count, n = expected.Count; i < n; i++) { + Log(string.Format("{0,-29}", "") + "FAIL: " + expected[i]); + fail = true; + } + + actual.Clear(); + expected.Clear(); + Log(""); + if (fail) { + string message = "TEST " + test + " FAILED!"; + Log(message); + FailTestRun(message); + } + } + + Result Expect (int animationIndex, string name, float trackTime, float totalTime) { + Result result = new Result(); + result.name = name; + result.animationIndex = animationIndex; + result.trackTime = trackTime; + result.totalTime = totalTime; + return result; + } + + Result Actual (string name, TrackEntry entry) { + Result result = new Result(); + result.name = name; + result.animationIndex = skeletonData.Animations.IndexOf(entry.Animation); + result.trackTime = (float)Math.Round(entry.TrackTime * 1000) / 1000f; + result.totalTime = (float)Math.Round(time * 1000) / 1000f; + return result; + } + + Result Note (string message) { + Result result = new Result(); + result.name = message; + result.note = true; + return result; + } + + static void Log (string message) { + if (logImplementation != null) { + logImplementation(message); + return; + } + Console.WriteLine(message); + } + + static void FailTestRun (string message) { + failImplementation(message); + } + + class Result { + public string name; + public int animationIndex; + public float trackTime, totalTime; + public bool note; + + public override int GetHashCode () { + int result = 31 + animationIndex; + result = 31 * result + name.GetHashCode(); + result = 31 * result + totalTime.GetHashCode(); + result = 31 * result + trackTime.GetHashCode(); + return result; + } + + public override bool Equals (object obj) { + Result other = (Result)obj; + if (animationIndex != other.animationIndex) return false; + if (!name.Equals(other.name)) return false; + if (!IsEqual(totalTime, other.totalTime)) return false; + if (!IsEqual(trackTime, other.trackTime)) return false; + return true; + } + + public override string ToString () { + return string.Format("{0,-3}{1,-12}{2,-7}{3,-7}", "" + animationIndex, name, RoundTime(trackTime), RoundTime(totalTime)); + } + } + + static float Round (float value, int decimals) { + float shift = (float)Math.Pow(10, decimals); + return (float)Math.Round((double)value * shift) / shift; + } + + static string RoundTime (float value) { + float roundedValue = Round(value, 3); + string text = roundedValue.ToString(); + return text.EndsWith(".0") ? text.Substring(0, text.Length - 2) : text; + } + + class TestListener { + + public TestListener(FrameDelegate frame) { + this.frame = frame; + } + + public delegate void FrameDelegate (float time); + public event FrameDelegate frame; + + public void Frame(float time) { + frame(time); + } + } + + #region Test API + static public void Main (string testJsonFilePath) { + new AnimationStateTests(testJsonFilePath); + } + + public delegate void LogDelegate (string message); + public static event LogDelegate logImplementation; + + public delegate void FailDelegate (string message); + public static event FailDelegate failImplementation; + #endregion + } +} diff --git a/spine-unity/Assets/SpineTests.meta b/spine-unity/Assets/SpineTests.meta new file mode 100644 index 000000000..a08f759cb --- /dev/null +++ b/spine-unity/Assets/SpineTests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f5588a995395d7d428bb39ca0bfb7bd8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/SpineTests/SpineTests.asmdef b/spine-unity/Assets/SpineTests/SpineTests.asmdef new file mode 100644 index 000000000..ef4d60bd4 --- /dev/null +++ b/spine-unity/Assets/SpineTests/SpineTests.asmdef @@ -0,0 +1,13 @@ +{ + "name": "SpineTests", + "optionalUnityReferences": [ + "TestAssemblies" + ], + "references" : [ + "spine-unity", + "spine-csharp" + ], + "includePlatforms": [ + "Editor" + ] +} \ No newline at end of file diff --git a/spine-unity/Assets/SpineTests/SpineTests.asmdef.meta b/spine-unity/Assets/SpineTests/SpineTests.asmdef.meta new file mode 100644 index 000000000..03d332bd0 --- /dev/null +++ b/spine-unity/Assets/SpineTests/SpineTests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 20ae6c9683f839243a01c1b259d5159f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/SpineTests/spine-csharp-tests.meta b/spine-unity/Assets/SpineTests/spine-csharp-tests.meta new file mode 100644 index 000000000..b2ded2a7d --- /dev/null +++ b/spine-unity/Assets/SpineTests/spine-csharp-tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 25fb7418b1ef3784ea5c57726a348d2a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/SpineTests/spine-csharp-tests/add spine-csharp-tests here.txt b/spine-unity/Assets/SpineTests/spine-csharp-tests/add spine-csharp-tests here.txt new file mode 100644 index 000000000..7e3e69d38 --- /dev/null +++ b/spine-unity/Assets/SpineTests/spine-csharp-tests/add spine-csharp-tests here.txt @@ -0,0 +1 @@ +Add the directory content of 'spine-csharp/tests' as 'tests' here (e.g. using a symlink). \ No newline at end of file diff --git a/spine-unity/Assets/SpineTests/spine-csharp-tests/add spine-csharp-tests here.txt.meta b/spine-unity/Assets/SpineTests/spine-csharp-tests/add spine-csharp-tests here.txt.meta new file mode 100644 index 000000000..230fc592d --- /dev/null +++ b/spine-unity/Assets/SpineTests/spine-csharp-tests/add spine-csharp-tests here.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 21606d7e3a0ef4e4799397a605bf7bed +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/SpineTests/src.meta b/spine-unity/Assets/SpineTests/src.meta new file mode 100644 index 000000000..84b9fc363 --- /dev/null +++ b/spine-unity/Assets/SpineTests/src.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a3f30b00835dcac44b588dd09758649d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/SpineTests/src/RunAnimationStateTests.cs b/spine-unity/Assets/SpineTests/src/RunAnimationStateTests.cs new file mode 100644 index 000000000..8b0b34968 --- /dev/null +++ b/spine-unity/Assets/SpineTests/src/RunAnimationStateTests.cs @@ -0,0 +1,27 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Spine.Unity.Tests +{ + public class RunAnimationStateTests + { + [Test] + public void RunAnimationStateTestsSimplePasses () + { + AnimationStateTests.logImplementation += Log; + AnimationStateTests.failImplementation += Fail; + AnimationStateTests.Main("Assets/SpineTests/spine-csharp-tests/tests/assets/test.json"); + } + + public void Log (string message) { + UnityEngine.Debug.Log(message); + } + + public void Fail (string message) { + Assert.Fail(message); + } + } +} diff --git a/spine-unity/Assets/SpineTests/src/RunAnimationStateTests.cs.meta b/spine-unity/Assets/SpineTests/src/RunAnimationStateTests.cs.meta new file mode 100644 index 000000000..c50c27b7f --- /dev/null +++ b/spine-unity/Assets/SpineTests/src/RunAnimationStateTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33608366930c2854399d13aea2543e3c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: