fix(animation): invalidate cache on state removal

This commit is contained in:
luzhuang
2026-06-17 11:42:25 +08:00
parent ae7ae016b1
commit 08ec9a1eb4
3 changed files with 72 additions and 0 deletions

View File

@@ -19,6 +19,9 @@ export class AnimatorController extends ReferResource {
_layersMap: Record<string, AnimatorControllerLayer> = {};
private _updateFlagManager: UpdateFlagManager = new UpdateFlagManager();
private _onStateMachineChanged = (): void => {
this._updateFlagManager.dispatch();
};
/**
* The layers in the controller.
@@ -112,6 +115,7 @@ export class AnimatorController extends ReferResource {
addLayer(layer: AnimatorControllerLayer): void {
this._layers.push(layer);
this._layersMap[layer.name] = layer;
layer.stateMachine._setChangeCallback(this._onStateMachineChanged);
layer._setEngine(this._engine);
this._updateFlagManager.dispatch();
}
@@ -123,6 +127,7 @@ export class AnimatorController extends ReferResource {
removeLayer(layerIndex: number): void {
const theLayer = this.layers[layerIndex];
this._layers.splice(layerIndex, 1);
theLayer.stateMachine._setChangeCallback(null);
delete this._layersMap[theLayer.name];
this._updateFlagManager.dispatch();
}
@@ -131,6 +136,10 @@ export class AnimatorController extends ReferResource {
* Clear layers.
*/
clearLayers(): void {
const { _layers: layers } = this;
for (let i = 0, n = layers.length; i < n; i++) {
layers[i].stateMachine._setChangeCallback(null);
}
this._layers.length = 0;
for (let name in this._layersMap) {
delete this._layersMap[name];
@@ -151,6 +160,7 @@ export class AnimatorController extends ReferResource {
_setEngine(engine: Engine): void {
const { _layers: layers } = this;
for (let i = 0, n = layers.length; i < n; i++) {
layers[i].stateMachine._setChangeCallback(this._onStateMachineChanged);
layers[i]._setEngine(engine);
}
}

View File

@@ -14,6 +14,7 @@ export class AnimatorStateMachine {
readonly states: AnimatorState[] = [];
private _engine: Engine;
private _onChanged: (() => void) | null = null;
/**
* The state will be played automatically.
@@ -72,6 +73,7 @@ export class AnimatorStateMachine {
if (this.defaultState === state) {
this.defaultState = null;
}
this._onChanged?.();
}
}
@@ -170,4 +172,11 @@ export class AnimatorStateMachine {
states[i]._setEngine(engine);
}
}
/**
* @internal
*/
_setChangeCallback(onChanged: (() => void) | null): void {
this._onChanged = onChanged;
}
}

View File

@@ -1487,6 +1487,59 @@ describe("Animator test", function () {
}
});
it("removing a state invalidates cached layer data before the state name is reused", () => {
const entity = new Entity(engine);
const localAnimator = entity.addComponent(Animator);
const controller = new AnimatorController(engine);
const layer = new AnimatorControllerLayer("layer");
controller.addLayer(layer);
const oldState = layer.stateMachine.addState("Temp");
const oldClip = new AnimationClip("old-temp-clip");
const oldCurve = new AnimationFloatCurve();
const oldStart = new Keyframe<number>();
const oldEnd = new Keyframe<number>();
oldStart.time = 0;
oldStart.value = 0;
oldEnd.time = 1;
oldEnd.value = 1;
oldCurve.addKey(oldStart);
oldCurve.addKey(oldEnd);
oldClip.addCurveBinding("", Transform, "position.x", oldCurve);
oldState.clip = oldClip;
localAnimator.animatorController = controller;
try {
localAnimator.play("Temp");
let layerData = localAnimator["_animatorLayersData"][0];
expect(layerData.animatorStateDataMap.has(oldState)).to.eq(true);
layer.stateMachine.removeState(oldState);
const newState = layer.stateMachine.addState("Temp");
const newClip = new AnimationClip("new-temp-clip");
const newCurve = new AnimationFloatCurve();
const newStart = new Keyframe<number>();
const newEnd = new Keyframe<number>();
newStart.time = 0;
newStart.value = 0;
newEnd.time = 1;
newEnd.value = 2;
newCurve.addKey(newStart);
newCurve.addKey(newEnd);
newClip.addCurveBinding("", Transform, "position.x", newCurve);
newState.clip = newClip;
localAnimator.play("Temp");
layerData = localAnimator["_animatorLayersData"][0];
expect(localAnimator.getCurrentAnimatorState(0)._state).to.eq(newState);
expect(layerData.animatorStateDataMap.has(oldState)).to.eq(false);
expect(layerData.animatorStateDataMap.has(newState)).to.eq(true);
} finally {
entity.destroy();
}
});
it("Clone", () => {
expect(animator.entity.clone().getComponent(Animator).animatorController).to.eq(animator.animatorController);
});