消消方块阵换皮表情
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

259 lines
9.5 KiB

1 week ago
/**
* / sp.SkeletonData skeletonData
* _updateSkeletonData Inspector
*
* **** skeletonData API
* `xxx is not a function` engine `cocos/spine/skeleton-data.ts`
*
* resetEnumsisEmptygetRuntimeDatagetSkinsEnumgetAnimsEnum
* sp.Skeleton resetEnums
*/
import { Enum, error, murmurhash2_32_gc, sp } from 'cc';
function noop() {
// 运行态无编辑器枚举 UI,空实现即可
}
function mergedUUIDForSkeletonData(this: any): string {
const textures = this.textures || [];
const hashContent = [this._atlasText ?? '', ...textures.map((t: { getId: () => string }) => t.getId())].join('');
const id = this._uuid ?? this.uuid ?? '';
return `${id}${murmurhash2_32_gc(hashContent, 668)}`;
}
function skeletonDataGetRuntimeData(this: any, quiet?: boolean): any {
if (this._skeletonCache) {
return this._skeletonCache;
}
if (!(this.textures && this.textures.length > 0) && this.textureNames && this.textureNames.length > 0) {
if (!quiet) {
error(`${this.name} no textures found!`);
}
return null;
}
const wasm = sp.spine?.wasmUtil as typeof sp.spine.wasmUtil | undefined;
if (!wasm || typeof wasm.querySpineSkeletonDataByUUID !== 'function') {
return null;
}
const heap = wasm.wasm?.HEAPU8;
if (!heap) {
return null;
}
const uuid = mergedUUIDForSkeletonData.call(this);
let spData: any;
try {
spData = wasm.querySpineSkeletonDataByUUID(uuid);
} catch {
spData = null;
}
if (spData) {
this._skeletonCache = spData;
} else {
const texturesArr = this.textures || [];
const size = texturesArr.length;
const textureUUIDs: string[] = [];
for (let i = 0; i < size; ++i) {
const tex = texturesArr[i];
textureUUIDs.push(tex.uuid || tex.getId());
}
const names = this.textureNames || [];
if (this._skeletonJson) {
try {
const jsonStr =
typeof this.skeletonJsonStr === 'string'
? this.skeletonJsonStr
: JSON.stringify(this._skeletonJson);
this._skeletonCache = wasm.createSpineSkeletonDataWithJson(jsonStr, this._atlasText ?? '', names, textureUUIDs);
wasm.registerSpineSkeletonDataWithUUID(this._skeletonCache, uuid);
} catch (e) {
if (!quiet) {
console.warn('[SpineRuntimePatch] createSpineSkeletonDataWithJson failed', this.name, e);
}
return null;
}
} else {
const native = this._nativeAsset;
if (!native) {
return null;
}
const rawData = new Uint8Array(native);
const byteSize = rawData.length;
if (byteSize === 0) {
return null;
}
// 与 engine 一致:先拷到 wasm 堆再解码;任一步失败都要 freeStoreMemory,否则堆状态异常会触发后续 memory access out of bounds
const ptr = wasm.createStoreMemory(byteSize);
try {
const wasmMem = heap.subarray(ptr, ptr + byteSize);
wasmMem.set(rawData);
this._skeletonCache = wasm.createSpineSkeletonDataWithBinary(
byteSize,
this._atlasText ?? '',
names,
textureUUIDs
);
wasm.registerSpineSkeletonDataWithUUID(this._skeletonCache, uuid);
} catch (e) {
if (!quiet) {
console.warn(
'[SpineRuntimePatch] createSpineSkeletonDataWithBinary failed (资源与 Spine 运行时版本不匹配或二进制损坏时常见)',
this.name,
e
);
}
this._skeletonCache = null;
return null;
} finally {
try {
wasm.freeStoreMemory();
} catch {
/* ignore */
}
}
}
}
return this._skeletonCache;
}
function skeletonDataIsEmpty(this: any): boolean {
const atlas = this._atlasText;
const atlasLen = typeof atlas === 'string' ? atlas.length : 0;
return atlasLen === 0 && !this._skeletonJson && !this._nativeAsset;
}
function skinNameToKey(name: unknown): string {
if (typeof name === 'string') return name;
if (name && typeof (name as { str?: string }).str === 'string') return (name as { str: string }).str;
return String(name ?? '');
}
/** 与 engine skeleton-data.ts getSkinsEnum() 一致 */
function skeletonDataGetSkinsEnum(this: any): Record<string, number> | null {
if (this._skinsEnum) {
return this._skinsEnum;
}
const sd = this.getRuntimeData(true);
if (!sd) return null;
const skins = sd.skins;
if (!skins || skins.length === 0) {
this._skinsEnum = Enum({});
return this._skinsEnum;
}
const enumDef: Record<string, number> = {};
for (let i = 0; i < skins.length; i++) {
const key = skinNameToKey(skins[i]?.name);
enumDef[key] = i;
}
this._skinsEnum = Enum(enumDef);
return this._skinsEnum;
}
/** 与 engine skeleton-data.ts getAnimsEnum() 一致 */
function skeletonDataGetAnimsEnum(this: any): Record<string, number> | null {
if (this._animsEnum && Object.keys(this._animsEnum).length > 1) {
return this._animsEnum;
}
const sd = this.getRuntimeData(true);
if (!sd) return null;
const anims = sd.animations;
if (!anims || anims.length === 0) {
const empty = Enum({ ' ': 0 });
this._animsEnum = empty;
return this._animsEnum;
}
const enumDef: Record<string, number> = { ' ': 0 };
for (let i = 0; i < anims.length; i++) {
const key = skinNameToKey(anims[i]?.name);
enumDef[key] = i + 1;
}
this._animsEnum = Enum(enumDef);
return this._animsEnum;
}
/** 与 sp.SkeletonData 上可能被裁掉的方法一一对应 */
const SKELETON_DATA_FALLBACKS: ReadonlyArray<{ name: string; impl: (...args: any[]) => any }> = [
{ name: 'resetEnums', impl: noop },
{ name: 'isEmpty', impl: skeletonDataIsEmpty },
{ name: 'getRuntimeData', impl: skeletonDataGetRuntimeData },
{ name: 'getSkinsEnum', impl: skeletonDataGetSkinsEnum },
{ name: 'getAnimsEnum', impl: skeletonDataGetAnimsEnum },
];
/**
* sp.SkeletonData / sp.Skeleton
*/
export function patchSpineSkeletonDataRuntime(): void {
try {
const sdp = sp.SkeletonData?.prototype as unknown as Record<string, unknown> | undefined;
if (sdp) {
for (const { name, impl } of SKELETON_DATA_FALLBACKS) {
if (typeof sdp[name] !== 'function') {
sdp[name] = impl;
}
}
}
const skp = sp.Skeleton?.prototype as { resetEnums?: unknown } | undefined;
if (skp && typeof skp.resetEnums !== 'function') {
skp.resetEnums = noop;
}
} catch {
/* ignore */
}
}
/**
* skeletonData
*
* `resetEnums: null` /
* sp.Skeleton setter `value.resetEnums()` t.resetEnums is not a function
*
*
* 1. undefined / null / accessor
* 2. SKELETON_DATA_FALLBACKS defineProperty
*/
export function ensureSpineSkeletonDataRuntime(asset: unknown): void {
if (asset == null) return;
patchSpineSkeletonDataRuntime();
const a = asset as any;
for (const { name } of SKELETON_DATA_FALLBACKS) {
const own = Object.getOwnPropertyDescriptor(a, name);
if (!own || own.configurable === false || !('value' in own)) continue;
const v = own.value;
if (v === undefined || v === null || typeof v !== 'function') {
try {
delete a[name];
} catch {
/* ignore */
}
}
}
for (const { name, impl } of SKELETON_DATA_FALLBACKS) {
if (typeof a[name] === 'function') continue;
try {
Object.defineProperty(a, name, {
value: impl,
writable: true,
configurable: true,
enumerable: false,
});
} catch {
try {
a[name] = impl;
} catch {
/* ignore */
}
}
}
}
/** 兼容旧名:历史代码与 GameController 等仍引用此名 */
export const patchSpineSkeletonResetEnums = patchSpineSkeletonDataRuntime;
/** 兼容旧名:LangSpine / SpineLoader / SetSpine 等仍引用此名 */
export const ensureSpineAssetResetEnums = ensureSpineSkeletonDataRuntime;
patchSpineSkeletonDataRuntime();