Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine: make multi skeleton component shared one instance of spine.Skeleton while spine working at sharedCache mode #16593

Merged
merged 6 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cocos/spine/lib/spine-core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@
duration: number;
name: string;
timelines: Array<Timeline>;
apply(skeleton: Skeleton, lastTime: number, time: number, loop: boolean, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 48 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 166. Maximum allowed is 150
hasTimeline(id: number): boolean;
}
interface Timeline {
apply(skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 52 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 151. Maximum allowed is 150
getPropertyId(): number;
}
enum MixBlend {
Expand Down Expand Up @@ -92,7 +92,7 @@
getCurveType(frameIndex: number): number;
setCurve(frameIndex: number, cx1: number, cy1: number, cx2: number, cy2: number): void;
getCurvePercent(frameIndex: number, percent: number): number;
abstract apply(skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 95 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 160. Maximum allowed is 150
}
class RotateTimeline extends CurveTimeline {
static ENTRIES: number;
Expand All @@ -104,7 +104,7 @@
constructor(frameCount: number);
getPropertyId(): number;
setFrame(frameIndex: number, time: number, degrees: number): void;
apply(skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 107 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 151. Maximum allowed is 150
}
class TranslateTimeline extends CurveTimeline {
static readonly ENTRIES: number;
Expand All @@ -112,17 +112,17 @@
constructor(frameCount: number);
getPropertyId(): number;
setFrame(frameIndex: number, time: number, x: number, y: number): void;
apply(skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 115 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 151. Maximum allowed is 150
}
class ScaleTimeline extends TranslateTimeline {
constructor(frameCount: number);
getPropertyId(): number;
apply(skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 120 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 151. Maximum allowed is 150
}
class ShearTimeline extends TranslateTimeline {
constructor(frameCount: number);
getPropertyId(): number;
apply(skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 125 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 151. Maximum allowed is 150
}
class ColorTimeline extends CurveTimeline {
static ENTRIES: number;
Expand All @@ -133,7 +133,7 @@
getSlotIndex(): number;
setSlotIndex(inValue: number): void;
setFrame(frameIndex: number, time: number, r: number, g: number, b: number, a: number): void;
apply(skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 136 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 151. Maximum allowed is 150
}
class TwoColorTimeline extends CurveTimeline {
static readonly ENTRIES: number;
Expand All @@ -144,7 +144,7 @@
getSlotIndex(): number;
setSlotIndex(inValue: number): void;
setFrame(frameIndex: number, time: number, r: number, g: number, b: number, a: number, r2: number, g2: number, b2: number): void;
apply(skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 147 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 151. Maximum allowed is 150
}
class AttachmentTimeline implements Timeline {
slotIndex: number;
Expand All @@ -157,7 +157,7 @@
setSlotIndex(inValue: number): void;
getAttachmentNames(): Array<string>;
setFrame(frameIndex: number, time: number, attachmentName: string): void;
apply(skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;

Check warning on line 160 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 151. Maximum allowed is 150
}
class DeformTimeline extends CurveTimeline {
slotIndex: number;
Expand Down Expand Up @@ -779,7 +779,7 @@
MipMapNearestNearest = 9984,
MipMapLinearNearest = 9985,
MipMapNearestLinear = 9986,
MipMapLinearLinear = 9987

Check failure on line 782 in cocos/spine/lib/spine-core.d.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Duplicate enum member value 9987
}
enum TextureWrap {
MirroredRepeat = 33648,
Expand Down Expand Up @@ -1220,6 +1220,7 @@
static createSpineSkeletonDataWithBinary(byteSize: number, atlasText: string): SkeletonData;
static registerSpineSkeletonDataWithUUID(data: SkeletonData, uuid: string);
static destroySpineSkeletonDataWithUUID(uuid: string);
static destroySpineSkeleton(skeleton: Skeleton): void;
static getCurrentListenerID(): number;
static getCurrentEventType(): EventType;
static getCurrentTrackEntry(): TrackEntry;
Expand Down
89 changes: 66 additions & 23 deletions cocos/spine/skeleton-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import spine from './lib/spine-core.js';
import { SkeletonData } from './skeleton-data';
import { warn } from '../core/platform/debug';
import { Skeleton } from './skeleton';

const MaxCacheTime = 30;
const FrameTime = 1 / 60;
Expand All @@ -52,6 +53,7 @@
listener: TrackEntryListeners;
curAnimationCache: AnimationCache | null;
animationsCache: { [key: string]: AnimationCache };
assetUUID: string;
}

class SpineModel {
Expand Down Expand Up @@ -174,13 +176,13 @@
const vPtr = model.vPtr;
const vLength = vc * Float32Array.BYTES_PER_ELEMENT * floatStride;
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
vUint8Buf.set(HEAPU8.subarray(vPtr, vPtr + vLength));

Check failure on line 179 in cocos/spine/skeleton-cache.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Unsafe argument of type `any` assigned to a parameter of type `ArrayLike<number>`

const iPtr = model.iPtr;
const iLength = Uint16Array.BYTES_PER_ELEMENT * ic;
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
const iUint8Buf = new Uint8Array(iUint16Buf.buffer);
iUint8Buf.set(HEAPU8.subarray(iPtr, iPtr + iLength));

Check failure on line 185 in cocos/spine/skeleton-cache.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Unsafe argument of type `any` assigned to a parameter of type `ArrayLike<number>`

const modelData = new SpineModel();
modelData.vCount = vc;
Expand Down Expand Up @@ -310,7 +312,11 @@

protected _privateMode: boolean;
protected _skeletonCache: { [key: string]: SkeletonCacheItemInfo };

//for shared mode only
protected _animationPool: { [key: string]: AnimationCache };
//for shared mode only, key is asset uuid and value is ref count.
private _sharedCacheMap: Map<string, number> = new Map<string, number>();
constructor () {
this._privateMode = false;
this._animationPool = {};
Expand Down Expand Up @@ -338,44 +344,81 @@
}
}

public removeSkeleton (uuid: string): void {
const skeletonInfo = this._skeletonCache[uuid];
public destroySkeleton (assetUuid: string): void {
if (!this._privateMode) {
let refCount = this._sharedCacheMap.get(assetUuid);
if (refCount) {
refCount -= 1;
if (refCount > 0) {
return;
}
this._sharedCacheMap.delete(assetUuid);
}
}

const sharedOperate = (aniKey: string, animationCache: AnimationCache): void => {
this._animationPool[`${assetUuid}#${aniKey}`] = animationCache;
animationCache.clear();
};
const privateOperate = (aniKey: string, animationCache: AnimationCache): void => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it called privateOperate? What does private mean here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

private corresponds to privateCache mode.

animationCache.destroy();
};
const operate = this._privateMode ? privateOperate : sharedOperate;

const skeletonInfo = this._skeletonCache[assetUuid];
if (!skeletonInfo) return;
const animationsCache = skeletonInfo.animationsCache;
for (const aniKey in animationsCache) {
// Clear cache texture, and put cache into pool.
// No need to create TypedArray next time.
const animationCache = animationsCache[aniKey];
if (!animationCache) continue;
this._animationPool[`${uuid}#${aniKey}`] = animationCache;
animationCache.clear();
operate(aniKey, animationCache);
}

delete this._skeletonCache[uuid];
if (skeletonInfo.skeleton) {
spine.wasmUtil.destroySpineSkeleton(skeletonInfo.skeleton);
}
delete this._skeletonCache[assetUuid];
}

public getSkeletonCache (uuid: string, skeletonData: spine.SkeletonData): SkeletonCacheItemInfo {
let skeletonInfo = this._skeletonCache[uuid];
if (!skeletonInfo) {
const skeleton = null;
const clipper = null;
const state = null;
const listener = new TrackEntryListeners();

this._skeletonCache[uuid] = skeletonInfo = {
skeleton,
clipper,
state,
listener,
// Cache all kinds of animation frame.
// When skeleton is dispose, clear all animation cache.
animationsCache: {} as any,
curAnimationCache: null,
};
public createSkeletonInfo (skeletonAsset: SkeletonData): SkeletonCacheItemInfo {
const uuid = skeletonAsset.uuid;
const runtimeData = skeletonAsset.getRuntimeData();
if (!this._privateMode) {
let refCount = this._sharedCacheMap.get(uuid);
if (!refCount) {
refCount = 1;
} else {
refCount += 1;
}
this._sharedCacheMap.set(uuid, refCount);
}

const skeleton = new spine.Skeleton(runtimeData!);
const clipper = null;
const state = null;
const listener = new TrackEntryListeners();

const skeletonInfo = this._skeletonCache[uuid] = {
skeleton,
clipper,
state,
listener,
// Cache all kinds of animation frame.
// When skeleton is dispose, clear all animation cache.
animationsCache: {} as any,
curAnimationCache: null,
assetUUID: uuid,
};
return skeletonInfo;
}

public getSkeletonInfo (skeletonAsset: SkeletonData): null | SkeletonCacheItemInfo {
const uuid = skeletonAsset.uuid;
return this._skeletonCache[uuid];
}

public getAnimationCache (uuid: string, animationName: string): null | AnimationCache {
const skeletonInfo = this._skeletonCache[uuid];
if (!skeletonInfo) return null;
Expand Down
38 changes: 26 additions & 12 deletions cocos/spine/skeleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { AttachUtil } from './attach-util';
import { SPINE_WASM } from './lib/instantiated';
import spine from './lib/spine-core.js';
import { VertexEffectDelegate } from './vertex-effect-delegate';
import SkeletonCache, { AnimationCache, AnimationFrame } from './skeleton-cache';
import SkeletonCache, { AnimationCache, AnimationFrame, SkeletonCacheItemInfo } from './skeleton-cache';
import { TrackEntryListeners } from './track-entry-listeners';
import { setPropertyEnumType } from '../core/internal-index';

Expand Down Expand Up @@ -240,6 +240,7 @@ export class Skeleton extends UIRenderer {
protected _instance: spine.SkeletonInstance = null!;
protected _state: spine.AnimationState = null!;
protected _textures: Texture2D[] = [];
private _skeletonInfo: SkeletonCacheItemInfo | null = null;
// Animation name
protected _animationName = '';
protected _skinName = '';
Expand Down Expand Up @@ -353,9 +354,6 @@ export class Skeleton extends UIRenderer {
set skeletonData (value: SkeletonData | null) {
if (value) value.resetEnums();
if (this._skeletonData !== value) {
if (this._skeletonCache && this._skeletonData) {
this._skeletonCache.removeSkeleton(this._skeletonData.uuid);
}
this.destroyRenderData();
this._skeletonData = value as any;
this.defaultSkin = '';
Expand Down Expand Up @@ -715,11 +713,11 @@ export class Skeleton extends UIRenderer {
if (!JSB) {
spine.SkeletonSystem.destroySpineInstance(this._instance);
}
if (this._skeletonCache && this._skeletonData) {
this._skeletonCache.removeSkeleton(this._skeletonData.uuid);
}
this._destroySkeletonInfo(this._skeletonCache);
this._skeletonCache = null;
super.onDestroy();
}

/**
* @en Clear animation and set to setup pose.
* @zh 清除动画并还原到初始姿势。
Expand Down Expand Up @@ -777,23 +775,32 @@ export class Skeleton extends UIRenderer {
*/
public setSkeletonData (skeletonData: spine.SkeletonData): void {
if (!EDITOR_NOT_IN_PREVIEW) {
const preSkeletonCache = this._skeletonCache;
if (this._cacheMode === AnimationCacheMode.SHARED_CACHE) {
this._skeletonCache = SkeletonCache.sharedCache;
} else if (this._cacheMode === AnimationCacheMode.PRIVATE_CACHE) {
this._skeletonCache = new SkeletonCache();
this._skeletonCache.enablePrivateMode();
} else {
this._skeletonCache = null;
}
//cache mode may be changed
if (preSkeletonCache !== this._skeletonCache) {
this._destroySkeletonInfo(preSkeletonCache);
}
}
if (this.isAnimationCached()) {
if (this.debugBones || this.debugSlots) {
warn('Debug bones or slots is invalid in cached mode');
}
if (this.skeletonData) {
const skeletonInfo = this._skeletonCache!.getSkeletonCache(this.skeletonData.uuid, skeletonData);
if (!skeletonInfo.skeleton) {
skeletonInfo.skeleton = this._instance.initSkeleton(skeletonData);
let skeletonInfo = this._skeletonCache!.getSkeletonInfo(this._skeletonData!);
if (this._skeletonInfo !== skeletonInfo) {
this._destroySkeletonInfo(this._skeletonCache);
if (!skeletonInfo) {
skeletonInfo = this._skeletonCache!.createSkeletonInfo(this._skeletonData!);
}
this._skeleton = skeletonInfo.skeleton!;
this._skeletonInfo = skeletonInfo;
this._skeleton = this._skeletonInfo.skeleton!;
}
} else {
this._skeleton = this._instance.initSkeleton(skeletonData);
Expand Down Expand Up @@ -1848,6 +1855,13 @@ export class Skeleton extends UIRenderer {
}
this._instance.setSlotTexture(slotName, textureID);
}

private _destroySkeletonInfo (skeletonCache: SkeletonCache | null): void {
if (skeletonCache && this._skeletonInfo) {
skeletonCache.destroySkeleton(this._skeletonInfo.assetUUID);
this._skeletonInfo = null;
}
}
}

legacyCC.internal.SpineSkeleton = Skeleton;
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,7 @@ EMSCRIPTEN_BINDINGS(cocos_spine) {
.class_function("createSpineSkeletonDataWithBinary", &SpineWasmUtil::createSpineSkeletonDataWithBinary, allow_raw_pointers())
.class_function("registerSpineSkeletonDataWithUUID", &SpineWasmUtil::registerSpineSkeletonDataWithUUID, allow_raw_pointers())
.class_function("destroySpineSkeletonDataWithUUID", &SpineWasmUtil::destroySpineSkeletonDataWithUUID)
.class_function("destroySpineSkeleton", &SpineWasmUtil::destroySpineSkeleton, allow_raw_pointers())
.class_function("getCurrentListenerID", &SpineWasmUtil::getCurrentListenerID)
.class_function("getCurrentEventType", &SpineWasmUtil::getCurrentEventType)
.class_function("getCurrentTrackEntry", &SpineWasmUtil::getCurrentTrackEntry, allow_raw_pointers())
Expand Down
6 changes: 6 additions & 0 deletions native/cocos/editor-support/spine-wasm/spine-wasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ void SpineWasmUtil::destroySpineSkeletonDataWithUUID(const std::string& uuid) {
}
}

void SpineWasmUtil::destroySpineSkeleton(Skeleton* skeleton) {
if (skeleton) {
delete skeleton;
}
}

uint32_t SpineWasmUtil::queryStoreMemory(uint32_t size) {
if (s_mem) {
if (s_memSize < size) {
Expand Down
1 change: 1 addition & 0 deletions native/cocos/editor-support/spine-wasm/spine-wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class SpineWasmUtil {
static SkeletonData* createSpineSkeletonDataWithBinary(uint32_t byteSize, const std::string& altasStr);
static void registerSpineSkeletonDataWithUUID(SkeletonData* data, const std::string& uuid);
static void destroySpineSkeletonDataWithUUID(const std::string& uuid);
static void destroySpineSkeleton(Skeleton* skeleton);

static uint32_t getCurrentListenerID();
static EventType getCurrentEventType();
Expand Down
2 changes: 1 addition & 1 deletion native/external-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"type": "github",
"owner": "cocos-creator",
"name": "engine-native-external",
"checkout": "v3.8.2-21"
"checkout": "v3.8.2-22"
}
}
Loading