[libgdx] More efficient animation timeline loading.

This commit is contained in:
Nathan Sweet 2026-03-25 14:36:26 -04:00
parent 96f0f34d78
commit f2e622875e
5 changed files with 42 additions and 27 deletions

View File

@ -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<Timeline> 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));

View File

@ -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<Timeline> timelines;
final LongSet timelineIds;
final IntArray bones;
LongSet timelineIds;
IntArray bones;
public Animation (String name, Array<Timeline> 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<Timeline> getTimelines () {
return timelines;
}
public void setTimelines (Array<Timeline> timelines) {
/** Sets the {@link #timelines} and {@link #bones}. It can be more efficient to determine the unique bones externally. */
public void setTimelines (Array<Timeline> 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.

View File

@ -50,9 +50,13 @@ import com.esotericsoftware.spine.Animation.Timeline;
* See <a href='https://esotericsoftware.com/spine-applying-animations#AnimationState-API'>Applying Animations</a> in the Spine
* Runtimes Guide. */
public class AnimationState {
static final Animation emptyAnimation = new Animation("<empty>", 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("<empty>");
static {
emptyAnimation.setTimelines(new Array(true, 0, Timeline[]::new), new IntArray(0));
}
private AnimationStateData data;
final Array<TrackEntry> tracks = new Array(true, 4, TrackEntry[]::new);
private final Array<Event> events = new Array(true, 4, Event[]::new);

View File

@ -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<Timeline> timelines, CurveTimeline1 timeline, float scale)

View File

@ -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<Timeline> timelines, JsonValue keyMap, CurveTimeline1 timeline, float defaultValue,