Skip to content

Conversation

@finscn
Copy link
Contributor

@finscn finscn commented Oct 20, 2025

解决 spine 动画的 complete事件会触发多次的 bug.

其实目前 cocos 代码里是有防止 complete重复被调用的逻辑的:

    // Queue complete if completed a loop iteration or the animation.
    bool complete = false;
    if (entry->_loop)
        complete = duration == 0 || (trackLastWrapped > MathUtil::fmod(entry->_trackTime, duration));
    else
        complete = animationTime >= animationEnd && entry->_animationLast < animationEnd;
    if (complete) _queue->complete(entry);

只有 entry->_animationLast < animationEnd 时才会触发 complete .

不过目前cocos 的代码是在 AnimationState.update() 方法中去更新 trackEntry 的 _animationLast 和 _trackLast 属性.
然后在 AnimationState.apply() 方法中去尝试触发的事件.

这就要求 AnimationState.update() 和 AnimationState.apply() 必须成对出现 才可以.
但是实际情况是, 某些场景下, 会出现单独执行 AnimationState.apply() 的情况.

所以这个 pr 改成, 在 AnimationState.apply() 后 就更新 trackEntry 的 _animationLast 和 _trackLast 属性.

反正这个 pr是解决了我们项目中遇到的 偶尔(某些场景下) complete事件会触发多次的 bug, 但是不确定会不会引起其他问题.
请 cocos 官方验证.

Re: #

Changelog


Continuous Integration

This pull request:

  • needs automatic test cases check.

    Manual trigger with @cocos-robot run test cases afterward.

  • does not change any runtime related code or build configuration

    If any reviewer thinks the CI checks are needed, please uncheck this option, then close and reopen the issue.


Compatibility Check

This pull request:

  • changes public API, and have ensured backward compatibility with deprecated features.
  • affects platform compatibility, e.g. system version, browser version, platform sdk version, platform toolchain, language version, hardware compatibility etc.
  • affects file structure of the build package or build configuration which requires user project upgrade.
  • introduces breaking changes, please list all changes, affected features and the scope of violation.

Greptile Overview

Updated On: 2025-10-20 08:36:37 UTC

Greptile Summary

This PR fixes a bug where Spine animation complete events fire multiple times by adding synchronization logic in the apply() method of AnimationState.cpp. After queueing events and updating the "next" animation tracking values (_nextAnimationLast and _nextTrackLast), the fix immediately synchronizes these with their current counterparts (_animationLast and _trackLast). This ensures that the completion detection logic in queueEvents() doesn't repeatedly trigger for the same animation completion across multiple frames. The Spine animation system uses these tracking variables to determine when an animation completes by comparing current time against the last processed time - without proper synchronization, the same completion condition could be satisfied multiple times.

Important Files Changed

Changed Files
Filename Score Overview
native/cocos/editor-support/spine/3.8/spine/AnimationState.cpp 3/5 Added immediate synchronization of animation tracking variables after queueing events to prevent duplicate complete event triggers

Confidence score: 3/5

  • This PR addresses a specific bug but introduces potential timing conflicts with existing synchronization logic.
  • Score reflects concerns about duplicated synchronization logic that already exists in the update() method (lines 357-358), which could cause subtle timing issues or interfere with the frame-to-frame state tracking that the animation system relies on for proper event detection. The fix may work for the immediate bug but could have unintended side effects on animation playback timing.
  • Pay close attention to native/cocos/editor-support/spine/3.8/spine/AnimationState.cpp - specifically test scenarios with looping animations, non-looping animations completing, and rapid apply() calls to ensure the immediate synchronization doesn't break the intended frame-based event detection mechanism.

Sequence Diagram

sequenceDiagram
    participant User
    participant AnimationState
    participant EventQueue
    participant TrackEntry
    participant Skeleton
    participant Listener

    User->>AnimationState: "update(delta)"
    AnimationState->>AnimationState: "Process each track entry"
    AnimationState->>TrackEntry: "Update track time and animation time"
    
    alt Track reaches end and no next entry
        AnimationState->>EventQueue: "end(entry)"
        AnimationState->>AnimationState: "disposeNext(entry)"
    end
    
    AnimationState->>EventQueue: "drain()"
    
    User->>AnimationState: "apply(skeleton)"
    AnimationState->>AnimationState: "animationsChanged() if needed"
    
    loop For each track entry
        AnimationState->>TrackEntry: "applyMixingFrom() if mixing"
        AnimationState->>Skeleton: "Apply timelines to skeleton"
        AnimationState->>AnimationState: "queueEvents(entry, animationTime)"
    end
    
    AnimationState->>EventQueue: "drain()"
    EventQueue->>EventQueue: "Process queued events"
    
    loop For each event in queue
        alt Event is Start/Interrupt/Complete
            EventQueue->>Listener: "Track entry listener callback"
            EventQueue->>Listener: "State listener callback"
        else Event is End
            EventQueue->>Listener: "Track entry listener callback"
            EventQueue->>Listener: "State listener callback"
            EventQueue->>Listener: "Dispose callback (track entry)"
            EventQueue->>Listener: "Dispose callback (state)"
            EventQueue->>TrackEntry: "reset()"
        else Event is custom Event
            EventQueue->>Listener: "Event callback with event data"
        end
    end
    
    EventQueue->>EventQueue: "Clear event queue"
Loading

解决 spine 动画的 complete事件会触发多次的 bug.
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. native/cocos/editor-support/spine/3.8/spine/AnimationState.cpp, line 476-477 (link)

    logic: These assignments duplicate lines 473-474. Since _nextAnimationLast and _nextTrackLast were just set, copying them back to _animationLast and _trackLast immediately creates a redundancy. This likely prevents the complete event detection logic in queueEvents() from working correctly on subsequent frames by not maintaining proper historical state. Is this intentional? The normal flow in update() already handles this synchronization at lines 357-358. Why is it needed here as well?

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@finscn
Copy link
Contributor Author

finscn commented Oct 20, 2025

别人也有遇到类似问题的 : #18525

删除无用代码
这个代码会引起其他问题(比如动画衔接时的闪烁)
删除无用代码
这个代码会引起其他问题(比如动画衔接时的闪烁)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant