From ad1279a71f825961d5e50d1c236c82f012f5d0b1 Mon Sep 17 00:00:00 2001 From: Serhii Yolkin Date: Fri, 1 Sep 2017 10:26:53 +0200 Subject: [PATCH 1/5] [unity] Handle null pool in SkeletonGhost.OnDestroy() (#979) --- .../Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs b/spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs index 5dea4e182f..365d1272c4 100644 --- a/spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs +++ b/spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs @@ -155,8 +155,10 @@ public class SkeletonGhost : MonoBehaviour { } void OnDestroy () { - for (int i = 0; i < maximumGhosts; i++) - if (pool[i] != null) pool[i].Cleanup(); + if (pool != null) { + for (int i = 0; i < maximumGhosts; i++) + if (pool[i] != null) pool[i].Cleanup(); + } foreach (var mat in materialTable.Values) Destroy(mat); From c9722319de5bcc5671ebd15c36bf16776dea14c4 Mon Sep 17 00:00:00 2001 From: Serhii Yolkin Date: Fri, 1 Sep 2017 11:56:18 +0200 Subject: [PATCH 2/5] [unity] SkeletonAnimator and Animation optimizations (#977) * + SkeletonAnimator: optimizations, remove per-frame GC allocations on Unity 2017.1 + Minor ScaleTimeline optimization * [unity] Check + formatting SkeletonAnimator 2017.1 --- spine-csharp/src/Animation.cs | 8 +- .../Assets/spine-unity/SkeletonAnimator.cs | 86 +++++++++++++++---- 2 files changed, 71 insertions(+), 23 deletions(-) diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index e344f7319e..2c82289499 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -436,11 +436,11 @@ public ScaleTimeline (int frameCount) } // Mixing out uses sign of setup or current pose, else use sign of key. if (direction == MixDirection.Out) { - x = Math.Abs(x) * Math.Sign(bx); - y = Math.Abs(y) * Math.Sign(by); + x = (x >= 0 ? x : -x) * (bx >= 0 ? 1 : -1); + y = (y >= 0 ? y : -y) * (by >= 0 ? 1 : -1); } else { - bx = Math.Abs(bx) * Math.Sign(x); - by = Math.Abs(by) * Math.Sign(y); + bx = (bx >= 0 ? bx : -bx) * (x >= 0 ? 1 : -1); + by = (by >= 0 ? by : -by) * (y >= 0 ? 1 : -1); } bone.scaleX = bx + (x - bx) * alpha; bone.scaleY = by + (y - by) * alpha; diff --git a/spine-unity/Assets/spine-unity/SkeletonAnimator.cs b/spine-unity/Assets/spine-unity/SkeletonAnimator.cs index 0337eedc4c..558f2177eb 100644 --- a/spine-unity/Assets/spine-unity/SkeletonAnimator.cs +++ b/spine-unity/Assets/spine-unity/SkeletonAnimator.cs @@ -36,7 +36,7 @@ namespace Spine.Unity { [RequireComponent(typeof(Animator))] public class SkeletonAnimator : SkeletonRenderer, ISkeletonAnimation { - + [SerializeField] protected MecanimTranslator translator; public MecanimTranslator Translator { get { return translator; } } @@ -108,9 +108,13 @@ public class MecanimTranslator { public enum MixMode { AlwaysMix, MixNext, SpineStyle } - readonly Dictionary animationTable = new Dictionary(); - readonly Dictionary clipNameHashCodeTable = new Dictionary(); + readonly Dictionary animationTable = new Dictionary(IntEqualityComparer.Instance); + readonly Dictionary clipNameHashCodeTable = new Dictionary(AnimationClipEqualityComparer.Instance); readonly List previousAnimations = new List(); + #if UNITY_2017_1_OR_NEWER + readonly List clipInfoCache = new List(); + readonly List nextClipInfoCache = new List(); + #endif Animator animator; public Animator Animator { get { return this.animator; } } @@ -144,16 +148,19 @@ public enum MixMode { AlwaysMix, MixNext, SpineStyle } AnimatorStateInfo nextStateInfo = animator.GetNextAnimatorStateInfo(layer); bool hasNext = nextStateInfo.fullPathHash != 0; - AnimatorClipInfo[] clipInfo = animator.GetCurrentAnimatorClipInfo(layer); - AnimatorClipInfo[] nextClipInfo = animator.GetNextAnimatorClipInfo(layer); - for (int c = 0; c < clipInfo.Length; c++) { + int clipInfoCount, nextClipInfoCount; + IList clipInfo, nextClipInfo; + GetAnimatorClipInfos(layer, out clipInfoCount, out nextClipInfoCount, out clipInfo, out nextClipInfo); + + for (int c = 0; c < clipInfoCount; c++) { var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; previousAnimations.Add(animationTable[NameHashCode(info.clip)]); } + if (hasNext) { - for (int c = 0; c < nextClipInfo.Length; c++) { + for (int c = 0; c < nextClipInfoCount; c++) { var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; previousAnimations.Add(animationTable[NameHashCode(info.clip)]); @@ -169,22 +176,20 @@ public enum MixMode { AlwaysMix, MixNext, SpineStyle } AnimatorStateInfo nextStateInfo = animator.GetNextAnimatorStateInfo(layer); bool hasNext = nextStateInfo.fullPathHash != 0; - AnimatorClipInfo[] clipInfo = animator.GetCurrentAnimatorClipInfo(layer); - AnimatorClipInfo[] nextClipInfo = animator.GetNextAnimatorClipInfo(layer); - //UNITY 4 - //bool hasNext = nextStateInfo.nameHash != 0; - //var clipInfo = animator.GetCurrentAnimationClipState(i); - //var nextClipInfo = animator.GetNextAnimationClipState(i); + + int clipInfoCount, nextClipInfoCount; + IList clipInfo, nextClipInfo; + GetAnimatorClipInfos(layer, out clipInfoCount, out nextClipInfoCount, out clipInfo, out nextClipInfo); MixMode mode = layerMixModes[layer]; if (mode == MixMode.AlwaysMix) { // Always use Mix instead of Applying the first non-zero weighted clip. - for (int c = 0; c < clipInfo.Length; c++) { + for (int c = 0; c < clipInfoCount; c++) { var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, MixPose.Current, MixDirection.In); } if (hasNext) { - for (int c = 0; c < nextClipInfo.Length; c++) { + for (int c = 0; c < nextClipInfoCount; c++) { var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length,nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, MixPose.Current, MixDirection.In); } @@ -192,13 +197,13 @@ public enum MixMode { AlwaysMix, MixNext, SpineStyle } } else { // case MixNext || SpineStyle // Apply first non-zero weighted clip int c = 0; - for (; c < clipInfo.Length; c++) { + for (; c < clipInfoCount; c++) { var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, 1f, MixPose.Current, MixDirection.In); break; } // Mix the rest - for (; c < clipInfo.Length; c++) { + for (; c < clipInfoCount; c++) { var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, MixPose.Current, MixDirection.In); } @@ -207,14 +212,14 @@ public enum MixMode { AlwaysMix, MixNext, SpineStyle } if (hasNext) { // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights) if (mode == MixMode.SpineStyle) { - for (; c < nextClipInfo.Length; c++) { + for (; c < nextClipInfoCount; c++) { var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length,nextStateInfo.speed < 0), nextStateInfo.loop, null, 1f, MixPose.Current, MixDirection.In); break; } } // Mix the rest - for (; c < nextClipInfo.Length; c++) { + for (; c < nextClipInfoCount; c++) { var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime , info.clip.length,nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, MixPose.Current, MixDirection.In); } @@ -239,6 +244,31 @@ public enum MixMode { AlwaysMix, MixNext, SpineStyle } return normalizedTime * clipLength; } + void GetAnimatorClipInfos ( + int layer, + out int clipInfoCount, + out int nextClipInfoCount, + out IList clipInfo, + out IList nextClipInfo) { + #if UNITY_2017_1_OR_NEWER + clipInfoCount = animator.GetCurrentAnimatorClipInfoCount(layer); + nextClipInfoCount = animator.GetNextAnimatorClipInfoCount(layer); + if (clipInfoCache.Capacity < clipInfoCount) clipInfoCache.Capacity = clipInfoCount; + if (nextClipInfoCache.Capacity < nextClipInfoCount) nextClipInfoCache.Capacity = nextClipInfoCount; + animator.GetCurrentAnimatorClipInfo(layer, clipInfoCache); + animator.GetNextAnimatorClipInfo(layer, nextClipInfoCache); + + clipInfo = clipInfoCache; + nextClipInfo = nextClipInfoCache; + #else + clipInfo = animator.GetCurrentAnimatorClipInfo(layer); + nextClipInfo = animator.GetNextAnimatorClipInfo(layer); + + clipInfoCount = clipInfo.Count; + nextClipInfoCount = nextClipInfo.Count; + #endif + } + int NameHashCode (AnimationClip clip) { int clipNameHashCode; if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) { @@ -247,6 +277,24 @@ public enum MixMode { AlwaysMix, MixNext, SpineStyle } } return clipNameHashCode; } + + class AnimationClipEqualityComparer : IEqualityComparer { + internal static readonly IEqualityComparer Instance = new AnimationClipEqualityComparer(); + + public bool Equals (AnimationClip x, AnimationClip y) { + return x.GetInstanceID() == y.GetInstanceID(); + } + + public int GetHashCode (AnimationClip o) { + return o.GetInstanceID(); + } + } + + class IntEqualityComparer : IEqualityComparer { + internal static readonly IEqualityComparer Instance = new IntEqualityComparer(); + public bool Equals (int x, int y) { return x == y; } + public int GetHashCode(int o) { return o; } + } } } From 247d94f10b2580cdf78b4b22ad4bc802a14330bb Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Mon, 4 Sep 2017 13:41:34 +0200 Subject: [PATCH 3/5] [libgdx] Better exception message. --- .../src/com/esotericsoftware/spine/SkeletonRenderer.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index 4240e80ba1..32e033b1a0 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -100,7 +100,9 @@ public void draw (Batch batch, Skeleton skeleton) { continue; } else if (attachment instanceof MeshAttachment) { - throw new RuntimeException("SkeletonMeshRenderer is required to render meshes."); + throw new RuntimeException( + "SkeletonRenderer#draw(PolygonSpriteBatch, Skeleton) or #draw(TwoColorPolygonBatch, Skeleton) must be used to " + + "render meshes."); } else if (attachment instanceof SkeletonAttachment) { Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton(); @@ -242,7 +244,7 @@ public void draw (PolygonSpriteBatch batch, Skeleton skeleton) { tempLight.set(temp5); tempDark.set(temp6); tempUv.x = uvs[u]; - tempUv.y = uvs[u + 1]; + tempUv.y = uvs[u + 1]; vertexEffect.transform(tempPos, tempUv, tempLight, tempDark); vertices[v] = tempPos.x; vertices[v + 1] = tempPos.y; @@ -378,7 +380,7 @@ public void draw (TwoColorPolygonBatch batch, Skeleton skeleton) { tempLight.set(temp5); tempDark.set(temp6); tempUv.x = uvs[u]; - tempUv.y = uvs[u + 1]; + tempUv.y = uvs[u + 1]; vertexEffect.transform(tempPos, tempUv, tempLight, tempDark); vertices[v] = tempPos.x; vertices[v + 1] = tempPos.y; From 017d224cb68c073f4713120813779349bd4595ad Mon Sep 17 00:00:00 2001 From: John Date: Mon, 4 Sep 2017 22:47:03 +0800 Subject: [PATCH 4/5] [unity] Handle empty slots after separators. --- spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs b/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs index 16060da329..960abc265d 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs @@ -265,7 +265,7 @@ public struct Settings { bool hasSeparators = separatorCount > 0; int clippingAttachmentSource = -1; - int lastPreActiveClipping = -1; + int lastPreActiveClipping = -1; // The index of the last slot that had an active ClippingAttachment. SlotData clippingEndSlot = null; int submeshIndex = 0; var drawOrderItems = drawOrder.Items; @@ -327,7 +327,7 @@ public struct Settings { } if (noRender) { - if (current.forceSeparate && generateMeshOverride && current.rawVertexCount > 0) { + if (current.forceSeparate && generateMeshOverride) { // && current.rawVertexCount > 0) { { // Add current.endSlot = i; current.preActiveClippingSlotSource = lastPreActiveClipping; From a4a97969e8916be7d6ba531707b662ebbad8da9b Mon Sep 17 00:00:00 2001 From: badlogic Date: Tue, 5 Sep 2017 11:46:03 +0200 Subject: [PATCH 5/5] [c] Listeners on spAnimationState and spTrackEntry will now also be called if a track entry gets disposed as part of disposing an spAnimationState. --- CHANGELOG.md | 1 + spine-c/spine-c/include/spine/dll.h | 2 +- spine-c/spine-c/src/spine/AnimationState.c | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c3e74f73..3a4aae503d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ * Removed `spBone_worldToLocalRotationX` and `spBone_worldToLocalRotationY`. Replaced by `spBone_worldToLocalRotation` (rotation given relative to x-axis, counter-clockwise, in degrees). * Replaced `r`, `g`, `b`, `a` fields with instances of new `spColor` struct in `spRegionAttachment`, `spMeshAttachment`, `spSkeleton`, `spSkeletonData`, `spSlot` and `spSlotData`. * Removed `spVertexIndex`from public API. + * Listeners on `spAnimationState` or `spTrackEntry` will now be also called in case a track entry is disposed as part of dispoing the `spAnimationState`. * **Additions** * Added support for local and relative transform constraint calculation, including additional fields in `spTransformConstraintData`. * Added `spPointAttachment`, additional method `spAtlasAttachmentLoadeR_newPointAttachment`. diff --git a/spine-c/spine-c/include/spine/dll.h b/spine-c/spine-c/include/spine/dll.h index df85643e55..ed9c328b54 100644 --- a/spine-c/spine-c/include/spine/dll.h +++ b/spine-c/spine-c/include/spine/dll.h @@ -45,4 +45,4 @@ #define SP_API SPINEPLUGIN_API #endif -#endif /* SPINE_SHAREDLIB_H */ \ No newline at end of file +#endif /* SPINE_SHAREDLIB_H */ diff --git a/spine-c/spine-c/src/spine/AnimationState.c b/spine-c/spine-c/src/spine/AnimationState.c index 33d3abba66..31d60ad4d7 100644 --- a/spine-c/spine-c/src/spine/AnimationState.c +++ b/spine-c/spine-c/src/spine/AnimationState.c @@ -193,9 +193,13 @@ void _spAnimationState_disposeTrackEntries (spAnimationState* state, spTrackEntr spTrackEntry* from = entry->mixingFrom; while (from) { spTrackEntry* nextFrom = from->mixingFrom; + if (entry->listener) entry->listener(state, SP_ANIMATION_DISPOSE, from, 0); + if (state->listener) state->listener(state, SP_ANIMATION_DISPOSE, from, 0); _spAnimationState_disposeTrackEntry(from); from = nextFrom; } + if (entry->listener) entry->listener(state, SP_ANIMATION_DISPOSE, entry, 0); + if (state->listener) state->listener(state, SP_ANIMATION_DISPOSE, entry, 0); _spAnimationState_disposeTrackEntry(entry); entry = next; }