From f2e622875ecea174329c8df163494e8d89a1e8c7 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Wed, 25 Mar 2026 14:36:26 -0400 Subject: [PATCH] [libgdx] More efficient animation timeline loading. --- .../spine/AttachmentTimelineTests.java | 4 ++- .../com/esotericsoftware/spine/Animation.java | 36 ++++++++----------- .../spine/AnimationState.java | 6 +++- .../spine/SkeletonBinary.java | 12 +++++-- .../esotericsoftware/spine/SkeletonJson.java | 11 ++++-- 5 files changed, 42 insertions(+), 27 deletions(-) diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AttachmentTimelineTests.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AttachmentTimelineTests.java index 4b37f7c99..50b6b1049 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AttachmentTimelineTests.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AttachmentTimelineTests.java @@ -30,6 +30,7 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.IntArray; import com.esotericsoftware.spine.Animation.AttachmentTimeline; import com.esotericsoftware.spine.Animation.Timeline; @@ -75,7 +76,8 @@ public class AttachmentTimelineTests { Array timelines = new Array(true, 1, Timeline[]::new); timelines.add(timeline); - Animation animation = new Animation("animation", timelines, 1); + Animation animation = new Animation("animation"); + animation.setTimelines(timelines, new IntArray(0)); animation.setDuration(1); state = new AnimationState(new AnimationStateData(skeletonData)); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index 2da1138d6..fdf38cebb 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -35,7 +35,6 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.IntArray; -import com.badlogic.gdx.utils.IntSet; import com.badlogic.gdx.utils.LongSet; import com.badlogic.gdx.utils.Null; @@ -54,41 +53,36 @@ public class Animation { final String name; float duration; Array timelines; - final LongSet timelineIds; - final IntArray bones; + LongSet timelineIds; + IntArray bones; - public Animation (String name, Array timelines, float duration) { + /** Creates a new animation. {@link #timelines} must be set before use. */ + public Animation (String name) { if (name == null) throw new IllegalArgumentException("name cannot be null."); this.name = name; - this.duration = duration; - int n = timelines.size << 1; - timelineIds = new LongSet(n); - bones = new IntArray(n); - setTimelines(timelines); } /** If this list or the timelines it contains are modified, the timelines must be set again to recompute the animation's bone - * indices and timeline property IDs. */ + * indices and timeline property IDs. Setting this property computes the unique {@link #bones}. */ public Array getTimelines () { return timelines; } - public void setTimelines (Array timelines) { + /** Sets the {@link #timelines} and {@link #bones}. It can be more efficient to determine the unique bones externally. */ + public void setTimelines (Array timelines, IntArray bones) { if (timelines == null) throw new IllegalArgumentException("timelines cannot be null."); + if (bones == null) throw new IllegalArgumentException("bones cannot be null."); this.timelines = timelines; + this.bones = bones; int n = timelines.size; - timelineIds.clear(n << 1); - bones.clear(); - var boneSet = new IntSet(); + if (timelineIds == null) + timelineIds = new LongSet(n << 1); + else + timelineIds.clear(n << 1); Timeline[] items = timelines.items; - for (int i = 0; i < n; i++) { - Timeline timeline = items[i]; - timelineIds.addAll(timeline.propertyIds); - if (timeline instanceof BoneTimeline boneTimeline && boneSet.add(boneTimeline.getBoneIndex())) - bones.add(boneTimeline.getBoneIndex()); - } - bones.shrink(); + for (int i = 0; i < n; i++) + timelineIds.addAll(items[i].propertyIds); } /** Returns true if this animation contains a timeline with any of the specified property IDs. 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 6f0a10d9b..cbd865897 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -50,9 +50,13 @@ import com.esotericsoftware.spine.Animation.Timeline; * See Applying Animations in the Spine * Runtimes Guide. */ public class AnimationState { - static final Animation emptyAnimation = new Animation("", new Array(true, 0, Timeline[]::new), 0); static private final int SUBSEQUENT = 0, FIRST = 1, HOLD = 2, HOLD_FIRST = 3, SETUP = 1, CURRENT = 2; + static final Animation emptyAnimation = new Animation(""); + static { + emptyAnimation.setTimelines(new Array(true, 0, Timeline[]::new), new IntArray(0)); + } + private AnimationStateData data; final Array tracks = new Array(true, 4, TrackEntry[]::new); private final Array events = new Array(true, 4, Event[]::new); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index 4f9ba6a1f..53157ef95 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -39,6 +39,7 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.DataInput; +import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.SerializationException; @@ -924,8 +925,11 @@ public class SkeletonBinary extends SkeletonLoader { } // Bone timelines. - for (int i = 0, n = input.readInt(true); i < n; i++) { + int boneCount = input.readInt(true); + var bones = new IntArray(boneCount); + for (int i = 0; i < boneCount; i++) { int boneIndex = input.readInt(true); + bones.add(boneIndex); for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { int type = input.readByte(), frameCount = input.readInt(true); if (type == BONE_INHERIT) { @@ -1211,7 +1215,11 @@ public class SkeletonBinary extends SkeletonLoader { Timeline[] items = timelines.items; for (int i = 0, n = timelines.size; i < n; i++) duration = Math.max(duration, items[i].getDuration()); - return new Animation(name, timelines, duration); + + Animation animation = new Animation(name); + animation.setTimelines(timelines, bones); + animation.setDuration(duration); + return animation; } private void readTimeline (SkeletonInput input, Array timelines, CurveTimeline1 timeline, float scale) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 251221670..23468032d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -911,9 +911,12 @@ public class SkeletonJson extends SkeletonLoader { } // Bone timelines. - for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next) { + JsonValue boneMap = map.getChild("bones"); + var bones = new IntArray(boneMap.size); + for (; boneMap != null; boneMap = boneMap.next) { BoneData bone = skeletonData.findBone(boneMap.name); if (bone == null) throw new SerializationException("Bone not found: " + boneMap.name); + bones.add(bone.index); for (JsonValue timelineMap = boneMap.child; timelineMap != null; timelineMap = timelineMap.next) { JsonValue keyMap = timelineMap.child; if (keyMap == null) continue; @@ -1265,7 +1268,11 @@ public class SkeletonJson extends SkeletonLoader { Timeline[] items = timelines.items; for (int i = 0, n = timelines.size; i < n; i++) duration = Math.max(duration, items[i].getDuration()); - skeletonData.animations.add(new Animation(name, timelines, duration)); + + Animation animation = new Animation(name); + animation.setTimelines(timelines, bones); + animation.setDuration(duration); + skeletonData.animations.add(animation); } private void readTimeline (Array timelines, JsonValue keyMap, CurveTimeline1 timeline, float defaultValue,