@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
129 lines • 5.69 kB
JavaScript
// eslint-disable-next-line @typescript-eslint/naming-convention
const __bjsSerializableKey = "__bjs_serializable__";
// eslint-disable-next-line @typescript-eslint/naming-convention
const _mergedStoreCache = new WeakMap();
// Universal `Object.hasOwn` equivalent. We intentionally avoid the native `Object.hasOwn` (ES2022)
// because TypeScript does not down-level runtime APIs and some Babylon Native engines (e.g. ChakraCore)
// do not provide it. `Object.prototype.hasOwnProperty` exists on every engine, and routing through it
// here also stays safe for the null-prototype metadata objects created below.
function HasOwn(target, key) {
return Object.prototype.hasOwnProperty.call(target, key);
}
function GetMetadataSymbol() {
let metadataSymbol = Symbol.metadata;
if (!metadataSymbol) {
// Defensive idempotent fallback for runtimes (or import orders) where the entry-point polyfill
// has not yet run. Matches symbolMetadataPolyfill.ts.
metadataSymbol = Symbol("Symbol.metadata");
Object.defineProperty(Symbol, "metadata", {
configurable: true,
writable: true,
value: metadataSymbol,
});
}
return metadataSymbol;
}
/**
* The well-known `Symbol.metadata` symbol, resolved once at module-evaluation time.
*
* Reading (and thereby resolving) this symbol at module load guarantees that `Symbol.metadata`
* exists BEFORE any decorated class which imports the decorator infrastructure evaluates its class
* body. TypeScript's TC39 decorator emit captures `context.metadata` from `Symbol.metadata` at the
* top of the class `static {}` block (before the decorator factory runs), so if the symbol is not
* yet installed the metadata object is `void 0` and every metadata-based decorator throws.
*
* It is intentionally an exported `const` initialized from a function call: bundlers that treat this
* module as side-effect-free may drop bare top-level calls, but they cannot drop a `const` whose
* value is referenced by the retained decorator helpers below. The tree-shaking side-effect detector
* likewise skips `const X = ...` initializers, so this module stays classified as pure and remains
* importable from `.pure` modules.
* @internal
*/
export const MetadataSymbol = GetMetadataSymbol();
// Returns the constructor for the provided decorator/serialization target.
// Experimental decorators pass a prototype; serialization passes an instance or a constructor.
function GetConstructor(target) {
return typeof target === "function" ? target : target?.constructor;
}
// Returns (creating if necessary) the metadata object owned by the given constructor.
// The metadata's prototype mirrors the class hierarchy so merged lookups can walk it.
function GetOwnMetadata(ctor) {
if (!ctor) {
return undefined;
}
if (!HasOwn(ctor, MetadataSymbol)) {
const parent = Object.getPrototypeOf(ctor);
const parentMetadata = parent ? parent[MetadataSymbol] : null;
Object.defineProperty(ctor, MetadataSymbol, {
value: Object.create(parentMetadata ?? null),
configurable: true,
writable: true,
enumerable: false,
});
}
return ctor[MetadataSymbol];
}
/**
* Returns (creating if necessary) the serialization store owned by the provided decorator metadata.
* Used by the TC39 decorators, which receive `context.metadata` directly.
* @internal
*/
export function GetDirectStoreFromMetadata(metadata) {
if (!metadata) {
// `metadata` is `context.metadata`, which is `void 0` when `Symbol.metadata` was not installed
// before the class was evaluated. Referencing `MetadataSymbol` here (a) produces an actionable
// error instead of a cryptic "Cannot convert undefined to object" and (b) keeps the module-load
// polyfill anchored so bundlers cannot tree-shake it away on the decorate-time serialize path.
throw new Error(`Decorator metadata is unavailable; the Symbol.metadata (${String(MetadataSymbol)}) polyfill must run before decorated classes are evaluated.`);
}
if (!HasOwn(metadata, __bjsSerializableKey)) {
metadata[__bjsSerializableKey] = {};
}
return metadata[__bjsSerializableKey];
}
/** @internal */
export function GetDirectStore(target) {
const metadata = GetOwnMetadata(GetConstructor(target));
if (!metadata) {
return {};
}
if (!HasOwn(metadata, __bjsSerializableKey)) {
metadata[__bjsSerializableKey] = {};
}
return metadata[__bjsSerializableKey];
}
/**
* @returns the list of properties flagged as serializable
* @param target host object
*/
export function GetMergedStore(target) {
const ctor = GetConstructor(target);
const metadata = ctor ? ctor[MetadataSymbol] : undefined;
if (!metadata) {
return {};
}
const cached = _mergedStoreCache.get(metadata);
if (cached) {
return cached;
}
const store = {};
// Walk the metadata prototype chain (most derived first); parents overwrite children to match the
// original class-name-keyed merge order.
const chain = [];
let current = metadata;
while (current) {
chain.push(current);
current = Object.getPrototypeOf(current);
}
for (const meta of chain) {
if (HasOwn(meta, __bjsSerializableKey)) {
const initialStore = meta[__bjsSerializableKey];
for (const property in initialStore) {
store[property] = initialStore[property];
}
}
}
_mergedStoreCache.set(metadata, store);
return store;
}
//# sourceMappingURL=decorators.functions.js.map