diff --git a/spine-libgdx/spine-libgdx-tests/assets/test/test.json b/spine-libgdx/spine-libgdx-tests/assets/test/test.json index 872fb7692..4f6cd3105 100644 --- a/spine-libgdx/spine-libgdx-tests/assets/test/test.json +++ b/spine-libgdx/spine-libgdx-tests/assets/test/test.json @@ -1,5 +1,5 @@ { -"skeleton": { "hash": "NG9aJneROk8CsMAugcCOiMeXbGA", "spine": "Dev", "width": 0, "height": 0 }, +"skeleton": { "hash": "Qw/GA2Pge/zEE/8xq3vX9lpp4Qg", "spine": "3.0.00", "width": 0, "height": 0 }, "bones": [ { "name": "root" } ], @@ -7,7 +7,14 @@ "event": {} }, "animations": { - "events": { + "events1": { + "events": [ + { "time": 0, "name": "event", "string": "0" }, + { "time": 0.4666, "name": "event", "string": "14" }, + { "time": 1, "name": "event", "string": "30" } + ] + }, + "events2": { "events": [ { "time": 0, "name": "event", "string": "0" }, { "time": 0.4666, "name": "event", "string": "14" }, diff --git a/spine-libgdx/spine-libgdx-tests/assets/test/test.spine b/spine-libgdx/spine-libgdx-tests/assets/test/test.spine index 13f3b53f4..97538e1d2 100644 Binary files a/spine-libgdx/spine-libgdx-tests/assets/test/test.spine and b/spine-libgdx/spine-libgdx-tests/assets/test/test.spine differ diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java index 67503c58a..ea5d06296 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java @@ -35,19 +35,20 @@ import com.badlogic.gdx.Files.FileType; import com.badlogic.gdx.backends.lwjgl.LwjglFileHandle; import com.badlogic.gdx.utils.Array; import com.esotericsoftware.spine.AnimationState.AnimationStateListener; +import com.esotericsoftware.spine.AnimationState.TrackEntry; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; -import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; +import com.esotericsoftware.spine.attachments.RegionAttachment; public class AnimationStateTest { final SkeletonJson json = new SkeletonJson(new AttachmentLoader() { - public MeshAttachment newMeshAttachment (Skin skin, String name, String path) { + public RegionAttachment newRegionAttachment (Skin skin, String name, String path) { return null; } - public RegionAttachment newRegionAttachment (Skin skin, String name, String path) { + public MeshAttachment newMeshAttachment (Skin skin, String name, String path) { return null; } @@ -60,154 +61,274 @@ public class AnimationStateTest { } }); - AnimationStateListener stateListener = new AnimationStateListener() { - public void start (int trackIndex) { - actual.add(new Result("start", null)); + final AnimationStateListener stateListener = new AnimationStateListener() { + public void start (TrackEntry entry) { + add(actual("start", entry)); } - public void event (int trackIndex, Event event) { - actual.add(new Result("event", event.getString())); + public void event (TrackEntry entry, Event event) { + add(actual("event " + event.getString(), entry)); } - public void complete (int trackIndex, int loopCount) { - actual.add(new Result("complete", null)); + public void interrupt (TrackEntry entry) { + add(actual("interrupt", entry)); } - public void end (int trackIndex) { - actual.add(new Result("end", null)); + public void complete (TrackEntry entry, int loopCount) { + add(actual("complete " + loopCount, entry)); + } + + public void end (TrackEntry entry) { + add(actual("end", entry)); + } + + private void add (Result result) { + String error = "PASS"; + if (actual.size >= expected.size) { + error = "FAIL: "; + fail = true; + } else if (!expected.get(actual.size).equals(result)) { + error = "FAIL: " + expected.get(actual.size); + fail = true; + } + buffer.append(result.toString()); + buffer.append(error); + buffer.append('\n'); + actual.add(result); } }; final SkeletonData skeletonData; final AnimationStateData stateData; final Array actual = new Array(); + final Array expected = new Array(); + final StringBuilder buffer = new StringBuilder(512); - public AnimationStateTest () { + AnimationState state; + float time = 0; + boolean fail; + int test; + + AnimationStateTest () { skeletonData = json.readSkeletonData(new LwjglFileHandle("test/test.json", FileType.Internal)); stateData = new AnimationStateData(skeletonData); - AnimationState state; - - state = newState(); - state.setAnimation(0, "events", false); - test(state, 1 / 60f, 1000, // - new Result("start", null), // - new Result("event", "0"), // - new Result("event", "14"), // - new Result("event", "30"), // - new Result("complete", null), // - new Result("end", null) // + setup( // 1 + expect("start", 0, 0, 0), // + expect("event 0", 0, 0, 0), // + expect("event 14", 0, 0.5f, 0.5f), // + expect("event 30", 0, 1, 1), // + expect("complete 1", 0, 1, 1), // + expect("end", 0, 1, 1.1f) // ); + state.setAnimation(0, "events1", false); + run(0.1f, 1000); - state = newState(); - state.setAnimation(0, "events", false); - test(state, 30, 1000, // - new Result("start", null), // - new Result("event", "0"), // - new Result("event", "14"), // - new Result("event", "30"), // - new Result("complete", null), // - new Result("end", null) // + setup( // 2 + expect("start", 0, 0, 0), // + expect("event 0", 0, 0, 0), // + expect("event 14", 0, 0.467f, 0.467f), // + expect("event 30", 0, 1.017f, 1.017f), // + expect("complete 1", 0, 1.017f, 1.017f), // + expect("end", 0, 1.017f, 1.033f) // ); + state.setAnimation(0, "events1", false); + run(1 / 60f, 1000); - state = newState(); - state.setAnimation(0, "events", false); - test(state, 1, 1.01f, // - new Result("start", null), // - new Result("event", "0"), // - new Result("event", "14"), // - new Result("event", "30"), // - new Result("complete", null), // - new Result("end", null) // + setup( // 3 + expect("start", 0, 0, 0), // + expect("event 0", 0, 0, 0), // + expect("event 14", 0, 30, 30), // + expect("event 30", 0, 30, 30), // + expect("complete 1", 0, 30, 30), // + expect("end", 0, 30, 60) // ); + state.setAnimation(0, "events1", false); + run(30, 1000); - state = newState(); - state.setAnimation(0, "events", false); - state.addAnimation(0, "events", false, 0); - test(state, 0.1f, 3f, // - new Result("start", null), // - new Result("event", "0"), // - new Result("event", "14"), // - new Result("event", "30"), // - new Result("complete", null), // - new Result("end", null), // - new Result("start", null), // - new Result("event", "0"), // - new Result("event", "14"), // - new Result("event", "30"), // - new Result("complete", null), // - new Result("end", null) // + setup( // 4 + expect("start", 0, 0, 0), // + expect("event 0", 0, 0, 0), // + expect("event 14", 0, 1, 1), // + expect("event 30", 0, 1, 1), // + expect("complete 1", 0, 1, 1), // + expect("end", 0, 1, 2) // ); + state.setAnimation(0, "events1", false); + run(1, 1.01f); + + setup( // 5 + expect("start", 0, 0, 0), // + expect("event 0", 0, 0, 0), // + expect("event 14", 0, 0.5f, 0.5f), // + expect("event 30", 0, 1, 1), // + expect("complete 1", 0, 1, 1), // + expect("event 0", 0, 1, 1), // + expect("event 14", 0, 1.5f, 1.5f), // + expect("event 30", 0, 2, 2), // + expect("complete 2", 0, 2, 2), // + expect("event 0", 0, 2, 2) // + ); + state.setAnimation(0, "events1", true); + run(0.1f, 2.3f); + + setup( // 6 + expect("start", 0, 0, 0), // + expect("event 0", 0, 0, 0), // + expect("event 14", 0, 0.5f, 0.5f), // + expect("event 30", 0, 1, 1), // + expect("complete 1", 0, 1, 1), // + + expect("start", 1, 0.1f, 1.1f), // + + expect("interrupt", 0, 1.1f, 1.1f), // + expect("end", 0, 1.1f, 1.1f), // + + expect("event 0", 1, 0.1f, 1.1f), // + expect("event 14", 1, 0.5f, 1.5f), // + expect("event 30", 1, 1, 2), // + expect("complete 1", 1, 1, 2), // + + expect("start", 0, 0.1f, 2.1f), // + + expect("interrupt", 1, 1.1f, 2.1f), // + expect("end", 1, 1.1f, 2.1f), // + + expect("event 0", 0, 0.1f, 2.1f), // + expect("event 14", 0, 0.5f, 2.5f), // + expect("event 30", 0, 1, 3), // + expect("complete 1", 0, 1, 3), // + expect("end", 0, 1, 3.1f) // + ); + state.setAnimation(0, "events1", false); + state.addAnimation(0, "events2", false, 0); + state.addAnimation(0, "events1", false, 0); + run(0.1f, 4f); + + setup( // 7 + expect("start", 0, 0, 0), // + expect("event 0", 0, 0, 0), // + expect("event 14", 0, 0.5f, 0.5f), // + + expect("start", 1, 0.1f, 0.6f), // + + expect("interrupt", 0, 0.6f, 0.6f), // + expect("end", 0, 0.6f, 0.6f), // + + expect("event 0", 1, 0.1f, 0.6f), // + expect("event 14", 1, 0.5f, 1.0f), // + expect("event 30", 1, 1, 1.5f), // + expect("complete 1", 1, 1, 1.5f), // + expect("end", 1, 1, 1.6f) // + ); + state.setAnimation(0, "events1", false); + state.addAnimation(0, "events2", false, 0.5f); + run(0.1f, 1000); + + setup( // 8 + expect("start", 0, 0, 0), // + expect("event 0", 0, 0, 0), // + expect("event 14", 0, 0.5f, 0.5f), // + + expect("start", 1, 0.1f, 1), // + + expect("interrupt", 0, 1, 1), // + expect("event 30", 0, 1, 1), // + expect("complete 1", 0, 1, 1), // + expect("event 0", 0, 1, 1), // + + expect("event 0", 1, 0.1f, 1), // + expect("event 14", 1, 0.5f, 1.4f), // + + expect("event 14", 0, 1.5f, 1.5f), // + expect("end", 0, 1.6f, 1.6f), // + + expect("event 30", 1, 1, 1.9f), // + expect("complete 1", 1, 1, 1.9f), // + expect("end", 1, 1, 2) // + ); + stateData.setMix("events1", "events2", 0.7f); + state.setAnimation(0, "events1", true); + state.addAnimation(0, "events2", false, 0.9f); + run(0.1f, 1000); + + System.out.println("AnimationState tests passed."); } - private AnimationState newState () { - AnimationState state = new AnimationState(stateData); + void setup (Result... expectedArray) { + test++; + expected.addAll(expectedArray); + state = new AnimationState(stateData); state.addListener(stateListener); - return state; + time = 0; + fail = false; + buffer.setLength(0); + buffer.append(String.format("%-12s%-8s%-8s%-8s%s\n", "", "anim", "track", "total", "result")); } - private void test (AnimationState state, float incr, float endTime, Result... expectedArray) { - Array expected = new Array(expectedArray); - + void run (float incr, float endTime) { Skeleton skeleton = new Skeleton(skeletonData); - - for (int i = 0; i < endTime; i++) { + state.apply(skeleton); + while (time < endTime) { + time += incr; skeleton.update(incr); state.update(incr); state.apply(skeleton); } - - if (expected.equals(actual)) { - actual.clear(); - return; + actual.clear(); + expected.clear(); + if (fail) { + System.out.println("Test failed: " + test); + System.out.println(buffer); + System.exit(0); } - int i = 0; - for (int n = expected.size; i < n; i++) { - System.out.print(expected.get(i) + " == " + (i < actual.size ? actual.get(i) : "")); - if (i >= actual.size || !actual.get(i).equals(expected.get(i))) - System.out.println(" <- FAIL"); - else - System.out.println(); - } - for (int n = actual.size; i < n; i++) - System.out.print(" == " + actual.get(i) + " <- FAIL"); - System.exit(0); + System.out.println(buffer); } - static public class Result { - String eventName; - String payload; + Result expect (String name, int animationIndex, float trackTime, float totalTime) { + Result result = new Result(); + result.name = name; + result.animationIndex = animationIndex; + result.trackTime = trackTime; + result.totalTime = totalTime; + return result; + } - public Result (String eventName, String payload) { - this.eventName = eventName; - this.payload = payload; - } + Result actual (String name, TrackEntry entry) { + Result result = new Result(); + result.name = name; + result.animationIndex = skeletonData.getAnimations().indexOf(entry.animation, true); + result.trackTime = Math.round(entry.time * 1000) / 1000f; + result.totalTime = Math.round(time * 1000) / 1000f; + return result; + } + + class Result { + String name; + int animationIndex; + float trackTime, totalTime; public int hashCode () { - final int prime = 31; - int result = 1; - result = prime * result + ((eventName == null) ? 0 : eventName.hashCode()); - result = prime * result + ((payload == null) ? 0 : payload.hashCode()); + int result = 31 + animationIndex; + result = 31 * result + name.hashCode(); + result = 31 * result + Float.floatToIntBits(totalTime); + result = 31 * result + Float.floatToIntBits(trackTime); return result; } public boolean equals (Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; Result other = (Result)obj; - if (eventName == null) { - if (other.eventName != null) return false; - } else if (!eventName.equals(other.eventName)) return false; - if (payload == null) { - if (other.payload != null) return false; - } else if (!payload.equals(other.payload)) return false; + if (animationIndex != other.animationIndex) return false; + if (!name.equals(other.name)) return false; + if (totalTime != other.totalTime) return false; + if (trackTime != other.trackTime) return false; return true; } public String toString () { - return "[" + eventName + ", " + payload + "]"; + return String.format("%-12s%-8s%-8s%-8s", name, "" + animationIndex, "" + trackTime, "" + totalTime); } + } static public void main (String[] args) throws Exception { diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest2.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest2.java index f0ffafca2..7740ca477 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest2.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest2.java @@ -32,6 +32,7 @@ package com.esotericsoftware.spine; import com.esotericsoftware.spine.AnimationState.AnimationStateListener; +import com.esotericsoftware.spine.AnimationState.TrackEntry; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.badlogic.gdx.ApplicationAdapter; @@ -82,24 +83,28 @@ public class SimpleTest2 extends ApplicationAdapter { state = new AnimationState(stateData); // Holds the animation state for a skeleton (current animation, time, etc). state.setTimeScale(0.3f); // Slow all animations down to 30% speed. state.addListener(new AnimationStateListener() { - public void event (int trackIndex, Event event) { - System.out.println(trackIndex + " event: " + state.getCurrent(trackIndex) + ", " + event.getData().getName() + ", " - + event.getInt()); + public void event (TrackEntry entry, Event event) { + System.out + .println(entry.getTrackIndex() + " event: " + entry + ", " + event.getData().getName() + ", " + event.getInt()); } - public void complete (int trackIndex, int loopCount) { - System.out.println(trackIndex + " complete: " + state.getCurrent(trackIndex) + ", " + loopCount); + public void complete (TrackEntry entry, int loopCount) { + System.out.println(entry.getTrackIndex() + " complete: " + entry + ", " + loopCount); } - public void start (int trackIndex) { - System.out.println(trackIndex + " start: " + state.getCurrent(trackIndex)); + public void interrupt (TrackEntry entry) { + System.out.println(entry.getTrackIndex() + " interrupt: " + entry); } - public void end (int trackIndex) { - System.out.println(trackIndex + " end: " + state.getCurrent(trackIndex)); + public void start (TrackEntry entry) { + System.out.println(entry.getTrackIndex() + " start: " + entry); + } + + public void end (TrackEntry entry) { + System.out.println(entry.getTrackIndex() + " end: " + entry); } }); - + // Set animation on track 0. state.setAnimation(0, "run", true); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index eda2a2426..486dc5b7b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -32,6 +32,7 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.utils.Pool.Poolable; @@ -40,16 +41,17 @@ public class AnimationState { private AnimationStateData data; private Array tracks = new Array(); private final Array events = new Array(); - private final Array listeners = new Array(); + private final EventQueue queue = new EventQueue(); + final Array listeners = new Array(); private float timeScale = 1; - private Pool trackEntryPool = new Pool() { + final Pool trackEntryPool = new Pool() { protected Object newObject () { return new TrackEntry(); } }; - /** Creates an uninitialized AnimationState. The animation state data must be set. */ + /** Creates an uninitialized AnimationState. The animation state data must be set before use. */ public AnimationState () { } @@ -64,16 +66,19 @@ public class AnimationState { TrackEntry current = tracks.get(i); if (current == null) continue; + float currentDelta = delta * current.timeScale; + TrackEntry next = current.next; if (next != null) { + // When the next entry's delay is passed, change to it. float nextTime = current.lastTime - next.delay; if (nextTime >= 0) { - float nextDelta = delta * next.timeScale; - next.time = nextTime + nextDelta; // For start event to see correct time. - current.time += delta * current.timeScale; // For end event to see correct time. + next.time = nextTime + delta * next.timeScale; + current.time += currentDelta; setCurrent(i, next); - next.time -= nextDelta; // Prevent increasing time twice, below. - current = next; + queue.drain(); + if (next.previous != null) next.mixTime += currentDelta; + continue; } } else if (!current.loop && current.lastTime >= current.endTime) { // End non-looping animation when it reaches its end time and there is no next entry. @@ -81,7 +86,7 @@ public class AnimationState { continue; } - current.time += delta * current.timeScale; + current.time += currentDelta; if (current.previous != null) { float previousDelta = delta * current.previous.timeScale; current.previous.time += previousDelta; @@ -92,54 +97,63 @@ public class AnimationState { public void apply (Skeleton skeleton) { Array events = this.events; - int listenerCount = listeners.size; for (int i = 0; i < tracks.size; i++) { TrackEntry current = tracks.get(i); if (current == null) continue; - events.size = 0; - float time = current.time; float lastTime = current.lastTime; float endTime = current.endTime; boolean loop = current.loop; if (!loop && time > endTime) time = endTime; + float alpha = current.mix; TrackEntry previous = current.previous; - if (previous == null) - current.animation.mix(skeleton, lastTime, time, loop, events, current.mix); - else { + if (previous != null) { float previousTime = previous.time; if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; - previous.animation.apply(skeleton, previousTime, previousTime, previous.loop, null); + previous.animation.mix(skeleton, previous.lastTime, previousTime, previous.loop, events, previous.mix); + queueEvents(previous, previous.lastTime, previousTime, previous.endTime); + previous.lastTime = previousTime; - float alpha = current.mixTime / current.mixDuration * current.mix; + alpha *= current.mixTime / current.mixDuration; if (alpha >= 1) { alpha = 1; - trackEntryPool.free(previous); + queue.queueEnd(current.previous); current.previous = null; } - current.animation.mix(skeleton, lastTime, time, loop, events, alpha); - } - - for (int ii = 0, nn = events.size; ii < nn; ii++) { - Event event = events.get(ii); - if (current.listener != null) current.listener.event(i, event); - for (int iii = 0; iii < listenerCount; iii++) - listeners.get(iii).event(i, event); - } - - // Check if completed the animation or a loop iteration. - if (loop ? (lastTime % endTime > time % endTime) : (lastTime < endTime && time >= endTime)) { - int count = (int)(time / endTime); - if (current.listener != null) current.listener.complete(i, count); - for (int ii = 0, nn = listeners.size; ii < nn; ii++) - listeners.get(ii).complete(i, count); } + current.animation.mix(skeleton, lastTime, time, loop, events, alpha); + queueEvents(current, lastTime, time, endTime); current.lastTime = current.time; } + + queue.drain(); + } + + private void queueEvents (TrackEntry entry, float lastTime, float time, float endTime) { + Array events = this.events; + int n = events.size; + + // Queue events before complete. + float lastTimeWrapped = lastTime % endTime; + int i = 0; + for (; i < n; i++) { + Event event = events.get(i); + if (events.get(i).time < lastTimeWrapped) break; + queue.queueEvent(entry, event); + } + + // Queue complete if completed the animation or a loop iteration. + if (entry.loop ? (lastTime % endTime > time % endTime) : (lastTime < endTime && time >= endTime)) + queue.queueComplete(entry, (int)(time / endTime)); + + // Queue events after complete. + for (; i < n; i++) + queue.queueEvent(entry, events.get(i)); + events.clear(); } public void clearTracks () { @@ -153,9 +167,9 @@ public class AnimationState { TrackEntry current = tracks.get(trackIndex); if (current == null) return; - if (current.listener != null) current.listener.end(trackIndex); + if (current.listener != null) current.listener.end(current); for (int i = 0, n = listeners.size; i < n; i++) - listeners.get(i).end(trackIndex); + listeners.get(i).end(current); tracks.set(trackIndex, null); @@ -180,13 +194,16 @@ public class AnimationState { private void setCurrent (int index, TrackEntry entry) { TrackEntry current = expandToIndex(index); + + tracks.set(index, entry); + + queue.fireStart(entry); + if (current != null) { TrackEntry previous = current.previous; current.previous = null; - if (current.listener != null) current.listener.end(index); - for (int i = 0, n = listeners.size; i < n; i++) - listeners.get(i).end(index); + queue.fireInterrupt(current); entry.mixDuration = data.getMix(current.animation, entry.animation); if (entry.mixDuration > 0) { @@ -195,19 +212,14 @@ public class AnimationState { if (previous != null && current.mixTime / current.mixDuration < 0.5f) { entry.previous = previous; previous = current; - } else + } else { entry.previous = current; + } } else - trackEntryPool.free(current); + queue.fireEnd(current); - if (previous != null) trackEntryPool.free(previous); + if (previous != null) queue.fireEnd(previous); } - - tracks.set(index, entry); - - if (entry.listener != null) entry.listener.start(index); - for (int i = 0, n = listeners.size; i < n; i++) - listeners.get(i).start(index); } /** @see #setAnimation(int, Animation, boolean) */ @@ -227,6 +239,7 @@ public class AnimationState { entry.loop = loop; entry.endTime = animation.getDuration(); setCurrent(trackIndex, entry); + queue.drain(); return entry; } @@ -319,6 +332,7 @@ public class AnimationState { } static public class TrackEntry implements Poolable { + int index; TrackEntry next, previous; Animation animation; boolean loop; @@ -422,37 +436,119 @@ public class AnimationState { return time >= endTime; } + public int getTrackIndex () { + return index; + } + public String toString () { return animation == null ? "" : animation.name; } } + class EventQueue { + static private final int EVENT = 0, END = -1; + + final Array objects = new Array(); + final IntArray loopCounts = new IntArray(); + + public void queueEvent (TrackEntry entry, Event event) { + objects.add(entry); + objects.add(event); + loopCounts.add(EVENT); + } + + public void queueComplete (TrackEntry entry, int loopCount) { + objects.add(entry); + objects.add(null); + loopCounts.add(loopCount); + } + + public void queueEnd (TrackEntry entry) { + objects.add(entry); + objects.add(null); + loopCounts.add(END); + } + + public void drain () { + Array objects = this.objects; + IntArray loopCounts = this.loopCounts; + Array listeners = AnimationState.this.listeners; + int listenerCount = listeners.size; + for (int i = 0, ii = 0, n = loopCounts.size; i < n; i++, ii += 2) { + TrackEntry entry = (TrackEntry)objects.get(ii); + int loopCount = loopCounts.get(i); + switch (loopCount) { + case EVENT: + Event event = (Event)objects.get(ii + 1); + if (entry.listener != null) entry.listener.event(entry, event); + for (int iii = 0; iii < listenerCount; iii++) + listeners.get(iii).event(entry, event); + break; + case END: + fireEnd(entry); + break; + default: + if (entry.listener != null) entry.listener.complete(entry, loopCount); + for (int iii = 0; iii < listenerCount; iii++) + listeners.get(iii).complete(entry, loopCount); + } + } + objects.clear(); + loopCounts.clear(); + } + + public void fireStart (TrackEntry entry) { + if (entry.listener != null) entry.listener.start(entry); + for (int i = 0, n = listeners.size; i < n; i++) + listeners.get(i).start(entry); + } + + public void fireEnd (TrackEntry entry) { + if (entry.listener != null) entry.listener.end(entry); + for (int i = 0, n = listeners.size; i < n; i++) + listeners.get(i).end(entry); + trackEntryPool.free(entry); + } + + public void fireInterrupt (TrackEntry entry) { + if (entry.listener != null) entry.listener.interrupt(entry); + for (int i = 0, n = listeners.size; i < n; i++) + listeners.get(i).interrupt(entry); + } + } + static public interface AnimationStateListener { - /** Invoked when the current animation triggers an event. */ - public void event (int trackIndex, Event event); + /** Invoked when this animation triggers an event. */ + public void event (TrackEntry entry, Event event); - /** Invoked when the current animation has completed. + /** Invoked every time this animation completes a loop. * @param loopCount The number of times the animation reached the end. */ - public void complete (int trackIndex, int loopCount); + public void complete (TrackEntry entry, int loopCount); - /** Invoked just after the current animation is set. */ - public void start (int trackIndex); + /** Invoked just after this animation is set. */ + public void start (TrackEntry entry); - /** Invoked just before the current animation is replaced. */ - public void end (int trackIndex); + /** Invoked just after another animation is set. */ + public void interrupt (TrackEntry entry); + + /** Invoked when this animation will no longer be applied. */ + public void end (TrackEntry entry); } static public abstract class AnimationStateAdapter implements AnimationStateListener { - public void event (int trackIndex, Event event) { + public void event (TrackEntry entry, Event event) { } - public void complete (int trackIndex, int loopCount) { + public void complete (TrackEntry entry, int loopCount) { } - public void start (int trackIndex) { + public void start (TrackEntry entry) { } - public void end (int trackIndex) { + public void interrupt (TrackEntry entry) { + } + + public void end (TrackEntry entry) { } } }