Browse Source

Fixed events not firing sometimes.

http://www.esotericsoftware.com/forum/viewtopic.php?f=9&t=1462
NathanSweet 12 years ago
parent
commit
af5ff6c11f

+ 6 - 5
spine-as3/spine-as3/src/spine/animation/EventTimeline.as

@@ -55,18 +55,19 @@ public class EventTimeline implements Timeline {
 		events[frameIndex] = event;
 	}
 
+	/** Fires events for frames > lastTime and <= time. */
 	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
 		if (!firedEvents) return;
 		
-		if (lastTime >= frames[frameCount - 1]) return; // Last time is after last frame.
-		
 		if (lastTime > time) { // Fire events after last time for looped animations.
 			apply(skeleton, lastTime, int.MAX_VALUE, firedEvents, alpha);
-			lastTime = 0;
-		}
+			lastTime = -1;
+		} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
+			return;
+		if (time < frames[0]) return; // Time is before first frame.
 		
 		var frameIndex:int;
-		if (lastTime <= frames[0] || frameCount == 1)
+		if (lastTime < frames[0])
 			frameIndex = 0;
 		else {
 			frameIndex = Animation.binarySearch(frames, lastTime, 1);

+ 1 - 1
spine-as3/spine-as3/src/spine/animation/TrackEntry.as

@@ -40,7 +40,7 @@ public class TrackEntry {
 	internal var previous:TrackEntry;
 	public var animation:Animation;
 	public var loop:Boolean;
-	public var delay:Number, time:Number = 0, lastTime:Number = 0, endTime:Number, timeScale:Number = 1;
+	public var delay:Number, time:Number = 0, lastTime:Number = -1, endTime:Number, timeScale:Number = 1;
 	internal var mixTime:Number, mixDuration:Number;
 	public var onStart:Function, onEnd:Function, onComplete:Function, onEvent:Function;
 	

+ 7 - 7
spine-c/src/spine/Animation.c

@@ -514,21 +514,21 @@ void spAttachmentTimeline_setFrame (spAttachmentTimeline* self, int frameIndex,
 
 /**/
 
+/** Fires events for frames > lastTime and <= time. */
 void _spEventTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents,
 		int* eventCount, float alpha) {
 	spEventTimeline* self = (spEventTimeline*)timeline;
 	int frameIndex;
 	if (!firedEvents) return;
 
-	if (lastTime >= self->frames[self->framesLength - 1]) return; /* Last time is after last frame. */
-
-	if (lastTime > time) {
-		/* Fire events after last time for looped animations. */
+	if (lastTime > time) { /* Fire events after last time for looped animations. */
 		_spEventTimeline_apply(timeline, skeleton, lastTime, (float)INT_MAX, firedEvents, eventCount, alpha);
-		lastTime = 0;
-	}
+		lastTime = -1;
+	} else if (lastTime >= self->frames[self->framesLength - 1]) /* Last time is after last frame. */
+		return;
+	if (time < self->frames[0]) return; /* Time is before first frame. */
 
-	if (lastTime <= self->frames[0] || self->framesLength == 1)
+	if (lastTime < self->frames[0])
 		frameIndex = 0;
 	else {
 		float frame;

+ 1 - 2
spine-c/src/spine/AnimationState.c

@@ -43,6 +43,7 @@
 spTrackEntry* _spTrackEntry_create () {
 	spTrackEntry* entry = NEW(spTrackEntry);
 	entry->timeScale = 1;
+	entry->lastTime = -1;
 	return entry;
 }
 
@@ -232,7 +233,6 @@ spTrackEntry* spAnimationState_setAnimation (spAnimationState* self, int trackIn
 	entry = _spTrackEntry_create();
 	entry->animation = animation;
 	entry->loop = loop;
-	entry->time = 0;
 	entry->endTime = animation->duration;
 	_spAnimationState_setCurrent(self, trackIndex, entry);
 	return entry;
@@ -251,7 +251,6 @@ spTrackEntry* spAnimationState_addAnimation (spAnimationState* self, int trackIn
 	spTrackEntry* entry = _spTrackEntry_create();
 	entry->animation = animation;
 	entry->loop = loop;
-	entry->time = 0;
 	entry->endTime = animation->duration;
 
 	last = _spAnimationState_expandToIndex(self, trackIndex);

+ 7 - 6
spine-csharp/src/Animation.cs

@@ -459,20 +459,21 @@ namespace Spine {
 			events[frameIndex] = e;
 		}
 
+		/// <summary>Fires events for frames > lastTime and <= time.</summary>
 		public void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha) {
 			if (firedEvents == null) return;
 			float[] frames = this.frames;
 			int frameCount = frames.Length;
 
-			if (lastTime >= frames[frameCount - 1]) return; // Last time is after last frame.
-
 			if (lastTime > time) { // Fire events after last time for looped animations.
-				Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha);
-				lastTime = 0;
-			}
+				apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha);
+				lastTime = -1f;
+			} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
+				return;
+			if (time < frames[0]) return; // Time is before first frame.
 
 			int frameIndex;
-			if (lastTime <= frames[0] || frameCount == 1)
+			if (lastTime < frames[0])
 				frameIndex = 0;
 			else {
 				frameIndex = Animation.binarySearch(frames, lastTime, 1);

+ 1 - 1
spine-csharp/src/AnimationState.cs

@@ -275,7 +275,7 @@ namespace Spine {
 		internal TrackEntry next, previous;
 		internal Animation animation;
 		internal bool loop;
-		internal float delay, time, lastTime, endTime, timeScale = 1;
+		internal float delay, time, lastTime = -1, endTime, timeScale = 1;
 		internal float mixTime, mixDuration;
 
 		public Animation Animation { get { return animation; } }

+ 8 - 6
spine-js/spine.js

@@ -532,20 +532,22 @@ spine.EventTimeline.prototype = {
 		this.frames[frameIndex] = time;
 		this.events[frameIndex] = event;
 	},
+	/** Fires events for frames > lastTime and <= time. */
 	apply: function (skeleton, lastTime, time, firedEvents, alpha) {
 		if (!firedEvents) return;
 
 		var frames = this.frames;
 		var frameCount = frames.length;
-		if (lastTime >= frames[frameCount - 1]) return; // Last time is after last frame.
 
 		if (lastTime > time) { // Fire events after last time for looped animations.
-			this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha);
-			lastTime = 0;
-		}
+			apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha);
+			lastTime = -1f;
+		} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
+			return;
+		if (time < frames[0]) return; // Time is before first frame.
 
 		var frameIndex;
-		if (lastTime <= frames[0] || frameCount == 1)
+		if (lastTime < frames[0])
 			frameIndex = 0;
 		else {
 			frameIndex = spine.binarySearch(frames, lastTime, 1);
@@ -947,7 +949,7 @@ spine.TrackEntry.prototype = {
 	next: null, previous: null,
 	animation: null,
 	loop: false,
-	delay: 0, time: 0, lastTime: 0, endTime: 0,
+	delay: 0, time: 0, lastTime: -1, endTime: 0,
 	timeScale: 1,
 	mixTime: 0, mixDuration: 0,
 	onStart: null, onEnd: null, onComplete: null, onEvent: null

+ 6 - 5
spine-libgdx/src/com/esotericsoftware/spine/Animation.java

@@ -533,20 +533,21 @@ public class Animation {
 			events[frameIndex] = event;
 		}
 
+		/** Fires events for frames > lastTime and <= time. */
 		public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha) {
 			if (firedEvents == null) return;
 			float[] frames = this.frames;
 			int frameCount = frames.length;
 
-			if (lastTime >= frames[frameCount - 1]) return; // Last time is after last frame.
-
 			if (lastTime > time) { // Fire events after last time for looped animations.
 				apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha);
-				lastTime = 0;
-			}
+				lastTime = -1f;
+			} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
+				return;
+			if (time < frames[0]) return; // Time is before first frame.
 
 			int frameIndex;
-			if (lastTime <= frames[0] || frameCount == 1)
+			if (lastTime < frames[0])
 				frameIndex = 0;
 			else {
 				frameIndex = binarySearch(frames, lastTime, 1);

+ 1 - 1
spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java

@@ -297,7 +297,7 @@ public class AnimationState {
 			animation = null;
 			listener = null;
 			timeScale = 1;
-			lastTime = 0;
+			lastTime = -1;
 			time = 0;
 		}
 

+ 198 - 0
spine-libgdx/test/com/esotericsoftware/spine/EventTimelineTests.java

@@ -0,0 +1,198 @@
+
+package com.esotericsoftware.spine;
+
+import com.esotericsoftware.spine.Animation.EventTimeline;
+
+import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.StringBuilder;
+
+import java.util.Arrays;
+
+/** Unit tests for {@link EventTimeline}. */
+public class EventTimelineTests {
+	private final SkeletonData skeletonData;
+	private final Skeleton skeleton;
+	private final Array<Event> firedEvents = new Array();
+	private EventTimeline timeline = new EventTimeline(0);
+	private char[] events;
+	private float[] frames;
+
+	public EventTimelineTests () {
+		skeletonData = new SkeletonData();
+		skeleton = new Skeleton(skeletonData);
+
+		test(0);
+		test(1);
+		test(1, 1);
+		test(1, 2);
+		test(1, 2);
+		test(1, 2, 3);
+		test(1, 2, 3);
+		test(0, 0, 0);
+		test(0, 0, 1);
+		test(0, 1, 1);
+		test(1, 1, 1);
+		test(1, 2, 3, 4);
+		test(0, 2, 3, 4);
+		test(0, 2, 2, 4);
+		test(0, 0, 0, 0);
+		test(2, 2, 2, 2);
+		test(0.1f);
+		test(0.1f, 0.1f);
+		test(0.1f, 50f);
+		test(0.1f, 0.2f, 0.3f, 0.4f);
+		test(1, 2, 3, 4, 5, 6, 6, 7, 7, 8, 9, 10, 11, 11.01f, 12, 12, 12, 12);
+
+		System.out.println("All tests passed.");
+	}
+
+	private void test (float... frames) {
+		int eventCount = frames.length;
+
+		StringBuilder buffer = new StringBuilder();
+		for (int i = 0; i < eventCount; i++)
+			buffer.append((char)('a' + i));
+
+		this.events = buffer.toString().toCharArray();
+		this.frames = frames;
+		timeline = new EventTimeline(eventCount);
+
+		float maxFrame = 0;
+		int distinctCount = 0;
+		float lastFrame = -1;
+		for (int i = 0; i < eventCount; i++) {
+			float frame = frames[i];
+			Event event = new Event(new EventData("" + events[i]));
+			timeline.setFrame(i, frame, event);
+			maxFrame = Math.max(maxFrame, frame);
+			if (lastFrame != frame) distinctCount++;
+			lastFrame = frame;
+		}
+
+		run(0, 99, 0.1f);
+		run(0, maxFrame, 0.1f);
+		run(frames[0], 999, 2f);
+		run(frames[0], maxFrame, 0.1f);
+		run(0, maxFrame, (float)Math.ceil(maxFrame / 100));
+		run(0, 99, 0.1f);
+		run(0, 999, 100f);
+		if (distinctCount > 1) {
+			float epsilon = 0.02f;
+			// Ending before last.
+			run(frames[0], maxFrame - epsilon, 0.1f);
+			run(0, maxFrame - epsilon, 0.1f);
+			// Starting after first.
+			run(frames[0] + epsilon, maxFrame, 0.1f);
+			run(frames[0] + epsilon, 99, 0.1f);
+		}
+	}
+
+	private void run (float startTime, float endTime, float timeStep) {
+		timeStep = Math.max(timeStep, 0.00001f);
+		boolean loop = false;
+		try {
+			fire(startTime, endTime, timeStep, loop, false);
+			loop = true;
+			fire(startTime, endTime, timeStep, loop, false);
+		} catch (FailException ignored) {
+			try {
+				fire(startTime, endTime, timeStep, loop, true);
+			} catch (FailException ex) {
+				System.out.println(ex.getMessage());
+				System.exit(0);
+			}
+		}
+	}
+
+	private void fire (float timeStart, float timeEnd, float timeStep, boolean loop, boolean print) {
+		if (print) {
+			System.out.println("events: " + Arrays.toString(events));
+			System.out.println("frames: " + Arrays.toString(frames));
+			System.out.println("timeStart: " + timeStart);
+			System.out.println("timeEnd: " + timeEnd);
+			System.out.println("timeStep: " + timeStep);
+			System.out.println("loop: " + loop);
+		}
+
+		// Expected starting event.
+		int eventIndex = 0;
+		while (frames[eventIndex] < timeStart)
+			eventIndex++;
+
+		// Expected number of events when not looping.
+		int eventsCount = frames.length;
+		while (frames[eventsCount - 1] > timeEnd)
+			eventsCount--;
+		eventsCount -= eventIndex;
+
+		float duration = frames[eventIndex + eventsCount - 1];
+		if (loop && duration > 0) { // When looping timeStep can't be > nyquist.
+			while (timeStep > duration / 2f)
+				timeStep /= 2f;
+		}
+		// duration *= 2; // Everything should still pass with this uncommented.
+
+		firedEvents.clear();
+		int i = 0;
+		float lastTime = timeStart - 0.00001f;
+		float timeLooped, lastTimeLooped;
+		while (true) {
+			float time = Math.min(timeStart + timeStep * i, timeEnd);
+			lastTimeLooped = lastTime;
+			timeLooped = time;
+			if (loop && duration != 0) {
+				lastTimeLooped %= duration;
+				timeLooped %= duration;
+			}
+
+			int beforeCount = firedEvents.size;
+			Array<Event> original = new Array(firedEvents);
+			timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1);
+
+			while (beforeCount < firedEvents.size) {
+				char fired = firedEvents.get(beforeCount).getData().getName().charAt(0);
+				if (loop) {
+					eventIndex %= events.length;
+				} else {
+					if (firedEvents.size > eventsCount) {
+						if (print) System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == ?");
+						timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1);
+						fail("Too many events fired.");
+					}
+				}
+				if (print) {
+					System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == " + events[eventIndex]);
+				}
+				if (fired != events[eventIndex]) {
+					timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1);
+					fail("Wrong event fired.");
+				}
+				eventIndex++;
+				beforeCount++;
+			}
+
+			if (time >= timeEnd) break;
+			lastTime = time;
+			i++;
+		}
+		if (firedEvents.size < eventsCount) {
+			timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1);
+			if (print) System.out.println(firedEvents);
+			fail("Event not fired: " + events[eventIndex] + ", " + frames[eventIndex]);
+		}
+	}
+
+	private void fail (String message) {
+		throw new FailException(message);
+	}
+
+	static class FailException extends RuntimeException {
+		public FailException (String message) {
+			super(message);
+		}
+	}
+
+	static public void main (String[] args) throws Exception {
+		new EventTimelineTests();
+	}
+}

+ 6 - 4
spine-lua/Animation.lua

@@ -463,21 +463,23 @@ function Animation.EventTimeline.new ()
 		self.events[frameIndex] = event
 	end
 
+	-- Fires events for frames > lastTime and <= time.
 	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		if not firedEvents then return end
 
 		local frames = self.frames
 		local frameCount = #frames
-		if lastTime >= frames[frameCount] then return end -- Last time is after last frame.
-		frameCount = frameCount + 1
 
 		if lastTime > time then -- Fire events after last time for looped animations.
 			self:apply(skeleton, lastTime, 999999, firedEvents, alpha)
-			lastTime = 0
+			lastTime = -1
+		elseif lastTime >= frames[frameCount - 1] then -- Last time is after last frame.
+			return
 		end
+		if time < frames[0] then return end -- Time is before first frame.
 
 		local frameIndex
-		if lastTime <= frames[0] or frameCount == 1 then
+		if lastTime < frames[0] then
 			frameIndex = 0
 		else
 			frameIndex = binarySearch(frames, lastTime, 1)

+ 1 - 1
spine-lua/AnimationState.lua

@@ -225,7 +225,7 @@ function AnimationState.TrackEntry.new (data)
 		next = nil, previous = nil,
 		animation = nil,
 		loop = false,
-		delay = 0, time = 0, lastTime = 0, endTime = 0,
+		delay = 0, time = 0, lastTime = -1, endTime = 0,
 		timeScale = 1,
 		mixTime = 0, mixDuration = 0,
 		onStart = nil, onEnd = nil, onComplete = nil, onEvent = nil