mirror of
https://github.com/cocos/cocos-engine.git
synced 2026-09-09 09:08:54 +08:00
883 lines
35 KiB
TypeScript
883 lines
35 KiB
TypeScript
/*
|
|
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
|
Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
|
|
|
|
https://www.cocos.com
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated engine source code (the "Software"), a limited,
|
|
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
|
|
to use Cocos Creator solely to develop games on your target platforms. You shall
|
|
not use Cocos Creator software for developing other software or tools that's
|
|
used for developing games. You are not granted to publish, distribute,
|
|
sublicense, and/or sell copies of Cocos Creator.
|
|
|
|
The software or tools in this License Agreement are licensed, not sold.
|
|
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
|
|
import { EDITOR, TEST, DEV, DEBUG, JSB, PREVIEW, SUPPORT_JIT } from 'internal:constants';
|
|
import { legacyCC } from '../global-exports';
|
|
import * as js from '../utils/js';
|
|
import * as misc from '../utils/misc';
|
|
import { CCClass } from './class';
|
|
import * as Attr from './utils/attribute';
|
|
import MissingScript from '../components/missing-script';
|
|
import { Details } from './deserialize';
|
|
import { Platform } from '../../../pal/system-info/enum-type';
|
|
import { sys } from '../platform/sys';
|
|
import { error } from '../platform/debug';
|
|
import { CustomSerializable, DeserializationContext, deserializeTag, SerializationContext, SerializationInput } from './custom-serializable';
|
|
import type { deserialize, CCClassConstructor } from './deserialize';
|
|
import { CCON } from './ccon';
|
|
import { assertIsTrue } from './utils/asserts';
|
|
import { reportMissingClass as defaultReportMissingClass } from './report-missing-class';
|
|
|
|
function compileObjectTypeJit (
|
|
sources: string[],
|
|
defaultValue: unknown,
|
|
accessorToSet: string,
|
|
propNameLiteralToSet: string,
|
|
assumeHavePropIfIsValue: boolean,
|
|
) {
|
|
if (defaultValue instanceof legacyCC.ValueType) {
|
|
// fast case
|
|
if (!assumeHavePropIfIsValue) {
|
|
sources.push('if(prop){');
|
|
}
|
|
// @ts-expect-error Typing
|
|
const ctorCode = js.getClassName(defaultValue);
|
|
sources.push(`s._deserializeFastDefinedObject(o${accessorToSet},prop,${ctorCode});`);
|
|
if (!assumeHavePropIfIsValue) {
|
|
sources.push(`}else o${accessorToSet}=null;`);
|
|
}
|
|
} else {
|
|
sources.push(`
|
|
if (prop) {
|
|
s._deserializeAndAssignField(o, prop, ${propNameLiteralToSet});
|
|
} else {
|
|
o${accessorToSet}=null;
|
|
}
|
|
`);
|
|
}
|
|
}
|
|
|
|
type ReportMissingClass = deserialize.ReportMissingClass;
|
|
|
|
type ClassFinder = deserialize.ClassFinder;
|
|
|
|
type SerializableClassConstructor = deserialize.SerializableClassConstructor;
|
|
|
|
export type CompiledDeserializeFn = (
|
|
deserializer: _Deserializer,
|
|
object: Record<string, unknown>,
|
|
deserialized: Record<string, unknown>,
|
|
constructor: AnyFunction,
|
|
) => void;
|
|
|
|
const compileDeserialize = SUPPORT_JIT ? compileDeserializeJIT : compileDeserializeNative;
|
|
|
|
const DELIMITER = Attr.DELIMETER;
|
|
const POSTFIX_TYPE: `${typeof DELIMITER}type` = `${DELIMITER}type`;
|
|
const POSTFIX_EDITOR_ONLY: `${typeof DELIMITER}editorOnly` = `${DELIMITER}editorOnly`;
|
|
const POSTFIX_DEFAULT: `${typeof DELIMITER}default` = `${DELIMITER}default`;
|
|
const POSTFIX_FORMERLY_SERIALIZED_AS: `${typeof DELIMITER}formerlySerializedAs` = `${DELIMITER}formerlySerializedAs`;
|
|
type AttributeName = string;
|
|
type AttributeFormerlySerializedAs = `${AttributeName}${typeof POSTFIX_FORMERLY_SERIALIZED_AS}`;
|
|
type AttributeDefault = `${AttributeName}${typeof POSTFIX_DEFAULT}`;
|
|
type AttributeType = `${AttributeName}${typeof POSTFIX_TYPE}`;
|
|
type AttributeEditorOnly = `${AttributeName}${typeof POSTFIX_EDITOR_ONLY}`;
|
|
type AttrResult = {
|
|
[K: string]: typeof K extends AttributeFormerlySerializedAs ? string :
|
|
typeof K extends AttributeDefault ? unknown :
|
|
typeof K extends AttributeType ? AnyFunction :
|
|
typeof K extends AttributeEditorOnly ? boolean : never;
|
|
};
|
|
|
|
function compileDeserializeJIT (self: _Deserializer, klass: CCClassConstructor<unknown>): CompiledDeserializeFn {
|
|
const attrs: AttrResult = Attr.getClassAttrs(klass);
|
|
|
|
const props = klass.__values__;
|
|
// self, obj, serializedData, klass
|
|
const sources = [
|
|
'var prop;',
|
|
];
|
|
const fastMode = misc.BUILTIN_CLASSID_RE.test(js._getClassId(klass));
|
|
// sources.push('var vb,vn,vs,vo,vu,vf;'); // boolean, number, string, object, undefined, function
|
|
|
|
for (let p = 0; p < props.length; p++) {
|
|
const propName = props[p];
|
|
// @ts-expect-error 2341
|
|
if ((PREVIEW || (EDITOR && self._ignoreEditorOnly)) && attrs[propName + POSTFIX_EDITOR_ONLY]) {
|
|
continue; // skip editor only if in preview
|
|
}
|
|
|
|
let accessorToSet: string;
|
|
let propNameLiteralToSet: string;
|
|
if (CCClass.IDENTIFIER_RE.test(propName)) {
|
|
propNameLiteralToSet = `"${propName}"`;
|
|
accessorToSet = `.${propName}`;
|
|
} else {
|
|
propNameLiteralToSet = CCClass.escapeForJS(propName);
|
|
accessorToSet = `[${propNameLiteralToSet}]`;
|
|
}
|
|
|
|
let accessorToGet = accessorToSet;
|
|
if (attrs[propName + POSTFIX_FORMERLY_SERIALIZED_AS]) {
|
|
const propNameToRead = attrs[propName + POSTFIX_FORMERLY_SERIALIZED_AS] as string;
|
|
if (CCClass.IDENTIFIER_RE.test(propNameToRead)) {
|
|
accessorToGet = `.${propNameToRead}`;
|
|
} else {
|
|
accessorToGet = `[${CCClass.escapeForJS(propNameToRead)}]`;
|
|
}
|
|
}
|
|
|
|
sources.push(`prop=d${accessorToGet};`);
|
|
sources.push(`if(typeof ${JSB ? '(prop)' : 'prop'}!=="undefined"){`);
|
|
|
|
// function undefined object(null) string boolean number
|
|
const defaultValue = CCClass.getDefault(attrs[propName + POSTFIX_DEFAULT]);
|
|
if (fastMode) {
|
|
let isPrimitiveType;
|
|
const userType = attrs[propName + POSTFIX_TYPE] as AnyFunction | undefined;
|
|
if (defaultValue === undefined && userType) {
|
|
isPrimitiveType = userType instanceof Attr.PrimitiveType;
|
|
} else {
|
|
const defaultType = typeof defaultValue;
|
|
isPrimitiveType = defaultType === 'string'
|
|
|| defaultType === 'number'
|
|
|| defaultType === 'boolean';
|
|
}
|
|
|
|
if (isPrimitiveType) {
|
|
sources.push(`o${accessorToSet}=prop;`);
|
|
} else {
|
|
compileObjectTypeJit(sources, defaultValue, accessorToSet, propNameLiteralToSet, true);
|
|
}
|
|
} else {
|
|
sources.push(`${`if(typeof ${JSB ? '(prop)' : 'prop'}!=="object"){`
|
|
+ 'o'}${accessorToSet}=prop;`
|
|
+ `}else{`);
|
|
compileObjectTypeJit(sources, defaultValue, accessorToSet, propNameLiteralToSet, false);
|
|
sources.push('}');
|
|
}
|
|
sources.push('}');
|
|
}
|
|
if (legacyCC.js.isChildClassOf(klass, legacyCC._BaseNode) || legacyCC.js.isChildClassOf(klass, legacyCC.Component)) {
|
|
// @ts-expect-error 2341
|
|
if (PREVIEW || (EDITOR && self._ignoreEditorOnly)) {
|
|
const mayUsedInPersistRoot = js.isChildClassOf(klass, legacyCC.Node);
|
|
if (mayUsedInPersistRoot) {
|
|
sources.push('d._id&&(o._id=d._id);');
|
|
}
|
|
} else {
|
|
sources.push('d._id&&(o._id=d._id);');
|
|
}
|
|
}
|
|
if (props[props.length - 1] === '_$erialized') {
|
|
// deep copy original serialized data
|
|
sources.push('o._$erialized=JSON.parse(JSON.stringify(d));');
|
|
// parse the serialized data as primitive javascript object, so its __id__ will be dereferenced
|
|
sources.push('s._fillPlainObject(o._$erialized,d);');
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
|
|
return Function('s', 'o', 'd', 'k', sources.join('')) as CompiledDeserializeFn;
|
|
}
|
|
|
|
function compileDeserializeNative (_self: _Deserializer, klass: CCClassConstructor<unknown>): CompiledDeserializeFn {
|
|
const fastMode = misc.BUILTIN_CLASSID_RE.test(js._getClassId(klass));
|
|
const shouldCopyId = js.isChildClassOf(klass, legacyCC._BaseNode) || js.isChildClassOf(klass, legacyCC.Component);
|
|
let shouldCopyRawData = false;
|
|
|
|
const simpleProps: string[] = [];
|
|
let simplePropsToRead = simpleProps;
|
|
const advancedProps: string[] = [];
|
|
let advancedPropsToRead = advancedProps;
|
|
const advancedPropsValueType: any = [];
|
|
|
|
(() => {
|
|
const props: string[] = klass.__values__;
|
|
shouldCopyRawData = props[props.length - 1] === '_$erialized';
|
|
|
|
const attrs = Attr.getClassAttrs(klass);
|
|
|
|
for (let p = 0; p < props.length; p++) {
|
|
const propName = props[p];
|
|
let propNameToRead = propName;
|
|
if (attrs[propName + POSTFIX_FORMERLY_SERIALIZED_AS]) {
|
|
propNameToRead = attrs[propName + POSTFIX_FORMERLY_SERIALIZED_AS];
|
|
}
|
|
// function undefined object(null) string boolean number
|
|
const defaultValue = CCClass.getDefault(attrs[propName + POSTFIX_DEFAULT]);
|
|
let isPrimitiveType = false;
|
|
if (fastMode) {
|
|
const userType = attrs[propName + POSTFIX_TYPE];
|
|
if (defaultValue === undefined && userType) {
|
|
isPrimitiveType = userType instanceof Attr.PrimitiveType;
|
|
} else {
|
|
const defaultType = typeof defaultValue;
|
|
isPrimitiveType = defaultType === 'string'
|
|
|| defaultType === 'number'
|
|
|| defaultType === 'boolean';
|
|
}
|
|
}
|
|
if (fastMode && isPrimitiveType) {
|
|
if (propNameToRead !== propName && simplePropsToRead === simpleProps) {
|
|
simplePropsToRead = simpleProps.slice();
|
|
}
|
|
simpleProps.push(propName);
|
|
if (simplePropsToRead !== simpleProps) {
|
|
simplePropsToRead.push(propNameToRead);
|
|
}
|
|
} else {
|
|
if (propNameToRead !== propName && advancedPropsToRead === advancedProps) {
|
|
advancedPropsToRead = advancedProps.slice();
|
|
}
|
|
advancedProps.push(propName);
|
|
if (advancedPropsToRead !== advancedProps) {
|
|
advancedPropsToRead.push(propNameToRead);
|
|
}
|
|
advancedPropsValueType.push((defaultValue instanceof legacyCC.ValueType) && defaultValue.constructor);
|
|
}
|
|
}
|
|
})();
|
|
|
|
return (s, o, d, k) => {
|
|
for (let i = 0; i < simpleProps.length; ++i) {
|
|
const prop = d[simplePropsToRead[i]];
|
|
if (prop !== undefined) {
|
|
o[simpleProps[i]] = prop;
|
|
}
|
|
}
|
|
for (let i = 0; i < advancedProps.length; ++i) {
|
|
const propName = advancedProps[i];
|
|
const prop = d[advancedPropsToRead[i]];
|
|
if (prop === undefined) {
|
|
continue;
|
|
}
|
|
if (!fastMode && typeof prop !== 'object') {
|
|
o[propName] = prop;
|
|
} else {
|
|
// fastMode (so will not simpleProp) or object
|
|
const valueTypeCtor = advancedPropsValueType[i];
|
|
if (valueTypeCtor) {
|
|
if (fastMode || prop) {
|
|
// @ts-expect-error 2341
|
|
s._deserializeFastDefinedObject(o[propName] as Record<PropertyKey, unknown>, prop as SerializedGeneralTypedObject, valueTypeCtor);
|
|
} else {
|
|
o[propName] = null;
|
|
}
|
|
} else if (prop) {
|
|
// @ts-expect-error 2341
|
|
s._deserializeAndAssignField(o, prop, propName);
|
|
} else {
|
|
o[propName] = null;
|
|
}
|
|
}
|
|
}
|
|
if (shouldCopyId && d._id) {
|
|
o._id = d._id;
|
|
}
|
|
if (shouldCopyRawData) {
|
|
// deep copy original serialized data
|
|
o._$erialized = JSON.parse(JSON.stringify(d));
|
|
// parse the serialized data as primitive javascript object, so its __id__ will be dereferenced
|
|
// @ts-expect-error 2341
|
|
s._fillPlainObject(o._$erialized as Record<PropertyKey, unknown>, d);
|
|
}
|
|
};
|
|
}
|
|
|
|
type TypedArrayViewConstructorName =
|
|
| 'Uint8Array' | 'Int8Array'
|
|
| 'Uint16Array' | 'Int16Array'
|
|
| 'Uint32Array' | 'Int32Array'
|
|
| 'Float32Array' | 'Float64Array';
|
|
|
|
type SerializedTypedArray = {
|
|
__id__: never;
|
|
__uuid__: never;
|
|
__type__: 'TypedArray';
|
|
array: number[];
|
|
ctor: TypedArrayViewConstructorName;
|
|
};
|
|
|
|
type SerializedTypedArrayRef = {
|
|
__id__: never;
|
|
__uuid__: never;
|
|
__type__: 'TypedArrayRef';
|
|
ctor: TypedArrayViewConstructorName;
|
|
offset: number;
|
|
length: number;
|
|
};
|
|
|
|
type SerializedGeneralTypedObject = {
|
|
__id__: never;
|
|
__uuid__: never;
|
|
__type__?: NotKnownTypeTag;
|
|
} & Record<NotTypeTag, SerializedFieldValue>;
|
|
|
|
type SerializedObjectReference = {
|
|
__type__: never;
|
|
__uuid__: never;
|
|
__id__: number;
|
|
}
|
|
|
|
type SerializedUUIDReference = {
|
|
__type__: never;
|
|
__id__: never;
|
|
__uuid__: string;
|
|
__expectedType__: string;
|
|
};
|
|
|
|
type SerializedObject = SerializedTypedArray | SerializedTypedArrayRef | SerializedGeneralTypedObject;
|
|
|
|
type SerializedValue = SerializedObject | SerializedValue[] | string | number | boolean | null;
|
|
|
|
type SerializedPropertyKey = string | number;
|
|
|
|
type SerializedFieldObjectValue = SerializedObjectReference | SerializedUUIDReference | unknown;
|
|
|
|
type SerializedFieldValue = string | number | boolean | null | SerializedFieldObjectValue;
|
|
|
|
type NotA<T, ReservedNames> = T extends ReservedNames ? never : T;
|
|
|
|
type NotB<T, ReservedNames> = ReservedNames extends T ? never : T;
|
|
|
|
type FooName<T, ReservedNames> = NotA<T, ReservedNames> & NotB<T, ReservedNames>
|
|
|
|
type NotTypeTag = FooName<string, '__type__'>;
|
|
|
|
type NotKnownTypeTag = FooName<string, 'TypedArray' | 'TypedArrayRef'>;
|
|
|
|
type SerializedData = SerializedObject | SerializedObject[];
|
|
|
|
class DeserializerPool extends js.Pool<_Deserializer> {
|
|
constructor () {
|
|
super((deserializer: _Deserializer) => {
|
|
deserializer.clear();
|
|
}, 1);
|
|
}
|
|
|
|
// @ts-expect-error We only use this signature.
|
|
public get (
|
|
details: Details,
|
|
classFinder: ClassFinder,
|
|
reportMissingClass: ReportMissingClass,
|
|
customEnv: unknown,
|
|
ignoreEditorOnly: boolean | undefined,
|
|
) {
|
|
const cache = this._get();
|
|
if (cache) {
|
|
cache.reset(details, classFinder, reportMissingClass, customEnv, ignoreEditorOnly);
|
|
return cache;
|
|
} else {
|
|
return new _Deserializer(details, classFinder, reportMissingClass, customEnv, ignoreEditorOnly);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _Deserializer {
|
|
public static pool: DeserializerPool = new DeserializerPool();
|
|
|
|
public declare result: Details;
|
|
public declare customEnv: unknown;
|
|
public deserializedList: Array<Record<PropertyKey, unknown> | undefined>;
|
|
public deserializedData: any;
|
|
private declare _classFinder: ClassFinder;
|
|
private declare _reportMissingClass: ReportMissingClass;
|
|
private declare _onDereferenced: ClassFinder['onDereferenced'];
|
|
private _ignoreEditorOnly: any;
|
|
private declare _mainBinChunk: Uint8Array;
|
|
private declare _serializedData: SerializedObject | SerializedObject[];
|
|
private declare _context: DeserializationContext;
|
|
|
|
constructor (result: Details, classFinder: ClassFinder, reportMissingClass: ReportMissingClass, customEnv: unknown, ignoreEditorOnly: unknown) {
|
|
this.result = result;
|
|
this.customEnv = customEnv;
|
|
this.deserializedList = [];
|
|
this.deserializedData = null;
|
|
this._classFinder = classFinder;
|
|
this._reportMissingClass = reportMissingClass;
|
|
this._onDereferenced = classFinder?.onDereferenced;
|
|
if (DEV) {
|
|
this._ignoreEditorOnly = ignoreEditorOnly;
|
|
}
|
|
}
|
|
|
|
public reset (result: Details, classFinder: ClassFinder, reportMissingClass: ReportMissingClass, customEnv: unknown, ignoreEditorOnly: unknown) {
|
|
this.result = result;
|
|
this.customEnv = customEnv;
|
|
this._classFinder = classFinder;
|
|
this._reportMissingClass = reportMissingClass;
|
|
this._onDereferenced = classFinder?.onDereferenced;
|
|
if (DEV) {
|
|
this._ignoreEditorOnly = ignoreEditorOnly;
|
|
}
|
|
}
|
|
|
|
public clear () {
|
|
this.result = null!;
|
|
this.customEnv = null;
|
|
this.deserializedList.length = 0;
|
|
this.deserializedData = null;
|
|
this._classFinder = null!;
|
|
this._reportMissingClass = null!;
|
|
this._onDereferenced = null!;
|
|
}
|
|
|
|
public deserialize (serializedData: SerializedData | CCON) {
|
|
let fromCCON = false;
|
|
let jsonObj: SerializedData;
|
|
if (serializedData instanceof CCON) {
|
|
fromCCON = true;
|
|
jsonObj = serializedData.document as SerializedData;
|
|
if (serializedData.chunks.length > 0) {
|
|
assertIsTrue(serializedData.chunks.length === 1);
|
|
this._mainBinChunk = serializedData.chunks[0];
|
|
}
|
|
} else {
|
|
jsonObj = serializedData;
|
|
}
|
|
|
|
this._serializedData = jsonObj;
|
|
this._context = {
|
|
fromCCON,
|
|
};
|
|
|
|
const serializedRootObject = Array.isArray(jsonObj) ? jsonObj[0] : jsonObj;
|
|
|
|
if (EDITOR || TEST) {
|
|
this.deserializedData = this._deserializeObject(serializedRootObject, 0, this.deserializedList, `${0}`);
|
|
} else {
|
|
this.deserializedData = this._deserializeObject(serializedRootObject, 0);
|
|
}
|
|
|
|
this._serializedData = undefined!;
|
|
this._mainBinChunk = undefined!;
|
|
this._context = undefined!;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
return this.deserializedData;
|
|
}
|
|
|
|
/**
|
|
* @param serialized - The object to deserialize, must be non-nil.
|
|
* @param globalIndex - If the object is deserialized from "root objects" array.
|
|
* @param owner - Tracing purpose.
|
|
* @param propName - Tracing purpose.
|
|
*/
|
|
private _deserializeObject (
|
|
serialized: SerializedObject,
|
|
globalIndex: number,
|
|
owner?: Record<PropertyKey, unknown> | unknown[],
|
|
propName?: string,
|
|
) {
|
|
switch (serialized.__type__) {
|
|
case 'TypedArray':
|
|
return this._deserializeTypedArrayView(serialized);
|
|
case 'TypedArrayRef':
|
|
return this._deserializeTypedArrayViewRef(serialized);
|
|
default:
|
|
if (serialized.__type__) { // Typed object (including CCClass)
|
|
return this._deserializeTypeTaggedObject(serialized, globalIndex, owner, propName);
|
|
} else if (!Array.isArray(serialized)) { // Embedded primitive javascript object
|
|
return this._deserializePlainObject(serialized);
|
|
} else { // Array
|
|
return this._deserializeArray(serialized);
|
|
}
|
|
}
|
|
}
|
|
|
|
private _deserializeTypedArrayView (value: SerializedTypedArray) {
|
|
return globalThis[value.ctor].from(value.array);
|
|
}
|
|
|
|
private _deserializeTypedArrayViewRef (value: SerializedTypedArrayRef) {
|
|
const { offset, length, ctor: constructorName } = value;
|
|
const obj = new globalThis[constructorName](
|
|
this._mainBinChunk.buffer,
|
|
this._mainBinChunk.byteOffset + offset,
|
|
length,
|
|
);
|
|
return obj;
|
|
}
|
|
|
|
private _deserializeArray (value: SerializedValue[]) {
|
|
const obj = new Array<unknown>(value.length);
|
|
let prop: unknown;
|
|
for (let i = 0; i < value.length; i++) {
|
|
prop = value[i];
|
|
if (typeof prop === 'object' && prop) {
|
|
const isAssetType = this._deserializeAndAssignField(obj, prop, `${i}`);
|
|
if (isAssetType) {
|
|
// fill default value for primitive objects (no constructor)
|
|
obj[i] = null;
|
|
}
|
|
} else {
|
|
obj[i] = prop;
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
private _deserializePlainObject (value: Record<string, unknown>) {
|
|
const obj = {};
|
|
this._fillPlainObject(obj, value);
|
|
return obj;
|
|
}
|
|
|
|
private _deserializeTypeTaggedObject (
|
|
value: SerializedGeneralTypedObject,
|
|
globalIndex: number,
|
|
owner?: Record<PropertyKey, unknown> | unknown[],
|
|
propName?: string,
|
|
) {
|
|
const type = value.__type__ as unknown as string;
|
|
|
|
const klass = this._classFinder(type, value, owner, propName);
|
|
if (!klass) {
|
|
const notReported = this._classFinder === js._getClassById;
|
|
if (notReported) {
|
|
this._reportMissingClass(type);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const createObject = (constructor: deserialize.SerializableClassConstructor) => {
|
|
// eslint-disable-next-line new-cap
|
|
const obj = new constructor() as Record<string, unknown>;
|
|
if (globalIndex >= 0) {
|
|
this.deserializedList[globalIndex] = obj;
|
|
}
|
|
return obj;
|
|
};
|
|
|
|
if (!(EDITOR && legacyCC.js.isChildClassOf(klass, legacyCC.Component))) {
|
|
const obj = createObject(klass);
|
|
this._deserializeInto(value, obj, klass);
|
|
return obj;
|
|
} else {
|
|
try {
|
|
const obj = createObject(klass);
|
|
this._deserializeInto(value, obj, klass);
|
|
return obj;
|
|
} catch (e: unknown) {
|
|
if (DEBUG) {
|
|
error(`Deserialize ${klass.name} failed, ${(e as { stack: string; }).stack}`);
|
|
}
|
|
const obj = createObject(MissingScript);
|
|
this._deserializeInto(value, obj, MissingScript);
|
|
return obj;
|
|
}
|
|
}
|
|
}
|
|
|
|
private _deserializeInto (
|
|
value: SerializedGeneralTypedObject,
|
|
object: Record<PropertyKey, unknown>,
|
|
constructor: deserialize.SerializableClassConstructor,
|
|
skipCustomized = false,
|
|
) {
|
|
if (!skipCustomized && (object as Partial<CustomSerializable>)[deserializeTag]) {
|
|
this._runCustomizedDeserialize(
|
|
value,
|
|
object as Record<PropertyKey, unknown> & CustomSerializable,
|
|
constructor,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// cSpell:words Deserializable
|
|
|
|
type ClassicCustomizedDeserializable = { _deserialize: (content: unknown, deserializer: _Deserializer) => void; };
|
|
if ((object as Partial<ClassicCustomizedDeserializable>)._deserialize) {
|
|
// TODO: content check?
|
|
(object as ClassicCustomizedDeserializable)._deserialize((value as unknown as { content: unknown }).content, this);
|
|
return;
|
|
}
|
|
|
|
if (legacyCC.Class._isCCClass(constructor)) {
|
|
this._deserializeFireClass(object, value, constructor as CCClassConstructor<unknown>);
|
|
} else {
|
|
this._deserializeFastDefinedObject(object, value, constructor);
|
|
}
|
|
}
|
|
|
|
private _runCustomizedDeserialize (
|
|
value: SerializedGeneralTypedObject,
|
|
object: Record<PropertyKey, unknown> & CustomSerializable,
|
|
constructor: deserialize.SerializableClassConstructor,
|
|
) {
|
|
const serializationInput: SerializationInput = {
|
|
readProperty: (name: string) => {
|
|
const serializedField = value[name];
|
|
if (typeof serializedField !== 'object' || !serializedField) {
|
|
return serializedField as unknown;
|
|
} else {
|
|
return this._deserializeObjectField(serializedField) as unknown;
|
|
}
|
|
},
|
|
|
|
readThis: () => {
|
|
this._deserializeInto(value, object, constructor, true);
|
|
},
|
|
|
|
readSuper: () => {
|
|
const superConstructor = js.getSuper(constructor);
|
|
if (superConstructor) {
|
|
this._deserializeInto(value, object, superConstructor);
|
|
}
|
|
},
|
|
};
|
|
|
|
object[deserializeTag]!(serializationInput, this._context);
|
|
}
|
|
|
|
private _deserializeFireClass (obj: Record<PropertyKey, unknown>, serialized: SerializedGeneralTypedObject, klass: CCClassConstructor<unknown>) {
|
|
let deserialize: CompiledDeserializeFn;
|
|
// eslint-disable-next-line no-prototype-builtins
|
|
if (klass.hasOwnProperty('__deserialize__')) {
|
|
deserialize = klass.__deserialize__ as CompiledDeserializeFn;
|
|
} else {
|
|
deserialize = compileDeserialize(this, klass);
|
|
|
|
// DEBUG: Check MissingScript data for issue 9878
|
|
try {
|
|
if (klass === MissingScript) {
|
|
const props: string[] = klass.__values__;
|
|
if (props.length === 0 || props[props.length - 1] !== '_$erialized') {
|
|
error(`The '_$erialized' prop of MissingScript is missing. Will force the raw data to be save.`);
|
|
error(` Error props: ['${props}']. Please contact jare.`);
|
|
// props.push('_$erialized');
|
|
}
|
|
|
|
const rawDeserialize: CompiledDeserializeFn = deserialize;
|
|
deserialize = function (deserializer: _Deserializer,
|
|
object: Record<string, unknown>,
|
|
deserialized: Record<string, unknown>,
|
|
constructor: AnyFunction) {
|
|
rawDeserialize(deserializer, object, deserialized, constructor);
|
|
if (!object._$erialized) {
|
|
error(`Unable to stash previously serialized data. ${JSON.stringify(deserialized)}`);
|
|
}
|
|
};
|
|
}
|
|
} catch (e) {
|
|
error(`Error when checking MissingScript 6, ${e}`);
|
|
}
|
|
|
|
js.value(klass, '__deserialize__', deserialize, true);
|
|
}
|
|
deserialize(this, obj, serialized, klass);
|
|
}
|
|
|
|
private _deserializeAndAssignField (
|
|
obj: Record<PropertyKey, unknown> | unknown[],
|
|
serializedField: SerializedFieldObjectValue,
|
|
propName: string,
|
|
) {
|
|
const id = (serializedField as Partial<SerializedObjectReference>).__id__;
|
|
if (typeof id === 'number') {
|
|
const field = this.deserializedList[id];
|
|
if (field) {
|
|
obj[propName] = field;
|
|
} else {
|
|
// TODO: assertion
|
|
const source = (this._serializedData as SerializedObject[])[id];
|
|
if (EDITOR || TEST) {
|
|
obj[propName] = this._deserializeObject(source, id, obj, propName);
|
|
} else {
|
|
obj[propName] = this._deserializeObject(source, id, undefined, propName);
|
|
}
|
|
this._onDereferenced?.(this.deserializedList, id, obj, propName);
|
|
}
|
|
} else {
|
|
const uuid = (serializedField as Partial<SerializedUUIDReference>).__uuid__;
|
|
if (uuid) {
|
|
const expectedType = (serializedField as SerializedUUIDReference).__expectedType__;
|
|
this.result.push(obj, propName, uuid, expectedType);
|
|
} else if (EDITOR || TEST) {
|
|
obj[propName] = this._deserializeObject(serializedField as SerializedObject, -1, obj, propName);
|
|
} else {
|
|
obj[propName] = this._deserializeObject(serializedField as SerializedObject, -1);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private _deserializeObjectField (serializedField: SerializedFieldObjectValue) {
|
|
const id = (serializedField as Partial<SerializedObjectReference>).__id__;
|
|
if (typeof id === 'number') {
|
|
const field = this.deserializedList[id];
|
|
if (field) {
|
|
return field;
|
|
} else {
|
|
// TODO: assertion
|
|
const source = (this._serializedData as SerializedObject[])[id];
|
|
const field = this._deserializeObject(source, id, undefined, undefined);
|
|
return field;
|
|
}
|
|
} else {
|
|
const uuid = (serializedField as Partial<SerializedUUIDReference>).__uuid__;
|
|
if (uuid) {
|
|
const _expectedType = (serializedField as SerializedUUIDReference).__expectedType__;
|
|
throw new Error(`Asset reference field serialization is currently not supported in custom serialization.`);
|
|
} else {
|
|
return this._deserializeObject(serializedField as SerializedObject, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
private _fillPlainObject (instance: Record<string, unknown>, serialized: Record<string, unknown>) {
|
|
for (const propName in serialized) {
|
|
// eslint-disable-next-line no-prototype-builtins
|
|
if (!serialized.hasOwnProperty(propName)) {
|
|
continue;
|
|
}
|
|
const prop = serialized[propName];
|
|
if (typeof prop !== 'object') {
|
|
if (propName !== '__type__'/* && k != '__id__' */) {
|
|
instance[propName] = prop;
|
|
}
|
|
} else if (prop) {
|
|
const isAssetType = this._deserializeAndAssignField(instance, prop, propName);
|
|
if (isAssetType) {
|
|
// fill default value for primitive objects (no constructor)
|
|
instance[propName] = null;
|
|
}
|
|
} else {
|
|
instance[propName] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private _deserializeFastDefinedObject (
|
|
instance: Record<PropertyKey, unknown>,
|
|
serialized: SerializedGeneralTypedObject,
|
|
klass: SerializableClassConstructor,
|
|
) {
|
|
if (klass === legacyCC.Vec2) {
|
|
type SerializedVec2 = { x?: number; y?: number; };
|
|
instance.x = (serialized as SerializedVec2).x || 0;
|
|
instance.y = (serialized as SerializedVec2).y || 0;
|
|
return;
|
|
} else if (klass === legacyCC.Vec3) {
|
|
type SerializedVec3 = { x?: number; y?: number; z?: number; };
|
|
instance.x = (serialized as SerializedVec3).x || 0;
|
|
instance.y = (serialized as SerializedVec3).y || 0;
|
|
instance.z = (serialized as SerializedVec3).z || 0;
|
|
return;
|
|
} else if (klass === legacyCC.Color) {
|
|
type SerializedColor = { r?: number; g?: number; b?: number; a?: number; };
|
|
instance.r = (serialized as SerializedColor).r || 0;
|
|
instance.g = (serialized as SerializedColor).g || 0;
|
|
instance.b = (serialized as SerializedColor).b || 0;
|
|
const a = (serialized as SerializedColor).a;
|
|
instance.a = (a === undefined ? 255 : a);
|
|
return;
|
|
} else if (klass === legacyCC.Size) {
|
|
type SerializedSize = { width?: number; height?: number; };
|
|
instance.width = (serialized as SerializedSize).width || 0;
|
|
instance.height = (serialized as SerializedSize).height || 0;
|
|
return;
|
|
}
|
|
|
|
const attrs = Attr.getClassAttrs(klass);
|
|
// @ts-expect-error 2339
|
|
const props: string[] = klass.__values__;
|
|
if (DEBUG && !props) {
|
|
error(`Unable to deserialize ${js.getClassName(klass)}. `
|
|
+ 'For non-CCClass types, they can only be marked as serializable by `CCClass.fastDefine`.');
|
|
}
|
|
|
|
for (let i = 0; i < props.length; i++) {
|
|
const propName: string = props[i];
|
|
let value = serialized[propName];
|
|
// eslint-disable-next-line no-prototype-builtins
|
|
const exists: boolean = (value !== undefined || serialized.hasOwnProperty(propName));
|
|
if (!exists) {
|
|
// not serialized,
|
|
// recover to default value in ValueType, because eliminated properties equals to
|
|
// its default value in ValueType, not default value in user class
|
|
value = CCClass.getDefault(attrs[propName + POSTFIX_DEFAULT]);
|
|
}
|
|
|
|
if (typeof value !== 'object') {
|
|
instance[propName] = value;
|
|
} else if (value) {
|
|
this._deserializeAndAssignField(instance, value, propName);
|
|
} else {
|
|
instance[propName] = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function deserializeDynamic (data: SerializedData | CCON, details: Details, options?: {
|
|
classFinder?: ClassFinder;
|
|
ignoreEditorOnly?: boolean;
|
|
createAssetRefs?: boolean;
|
|
customEnv?: unknown;
|
|
reportMissingClass?: ReportMissingClass;
|
|
}) {
|
|
options = options || {};
|
|
const classFinder = options.classFinder || js._getClassById;
|
|
const createAssetRefs = options.createAssetRefs || sys.platform === Platform.EDITOR_CORE;
|
|
const customEnv = options.customEnv;
|
|
const ignoreEditorOnly = options.ignoreEditorOnly;
|
|
const reportMissingClass = options.reportMissingClass ?? legacyCC.deserialize.reportMissingClass;
|
|
|
|
// var oldJson = JSON.stringify(data, null, 2);
|
|
|
|
details.init();
|
|
|
|
const deserializer = _Deserializer.pool.get(details, classFinder, reportMissingClass, customEnv, ignoreEditorOnly);
|
|
|
|
legacyCC.game._isCloning = true;
|
|
const res = deserializer.deserialize(data);
|
|
legacyCC.game._isCloning = false;
|
|
|
|
_Deserializer.pool.put(deserializer);
|
|
if (createAssetRefs) {
|
|
details.assignAssetsBy(EditorExtends.serialize.asAsset);
|
|
}
|
|
|
|
// var afterJson = JSON.stringify(data, null, 2);
|
|
// if (oldJson !== afterJson) {
|
|
// throw new Error('JSON SHOULD not changed');
|
|
// }
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
return res;
|
|
}
|
|
|
|
export function parseUuidDependenciesDynamic (serialized: unknown) {
|
|
const depends = [];
|
|
const parseDependRecursively = (data: any, out: string[]) => {
|
|
if (!data || typeof data !== 'object' || typeof data.__id__ === 'number') { return; }
|
|
const uuid = data.__uuid__;
|
|
if (Array.isArray(data)) {
|
|
for (let i = 0, l = data.length; i < l; i++) {
|
|
parseDependRecursively(data[i], out);
|
|
}
|
|
} else if (uuid) {
|
|
out.push(uuid);
|
|
} else {
|
|
for (const prop in data) {
|
|
parseDependRecursively(data[prop], out);
|
|
}
|
|
}
|
|
};
|
|
parseDependRecursively(serialized, depends);
|
|
return depends;
|
|
}
|