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

Support binary data in asset pack, support typed array in compiled serialization #15904

Merged
merged 7 commits into from
Sep 8, 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
14 changes: 11 additions & 3 deletions cocos/asset/asset-manager/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export default class Config {
this._initUuid(options.uuids);
this._initPath(options.paths);
this._initScene(options.scenes);
this._initPackage(options.packs);
this._initPackage(options.packs, options.extensionMap);
this._initVersion(options.versions);
this._initRedirect(options.redirect);
for (const ext in options.extensionMap) {
Expand Down Expand Up @@ -382,12 +382,20 @@ export default class Config {
}
}

private _initPackage (packageList: Record<string, string[]>): void {
private _initPackage (packageList: Record<string, string[]>, extensionMap: IConfigOption['extensionMap']): void {
if (!packageList) { return; }
const assetInfos = this.assetInfos;
for (const packUuid in packageList) {
const uuids = packageList[packUuid];
const pack = { uuid: packUuid, packedUuids: uuids, ext: '.json' };
let mappedExtension = '.json';
for (const ext in extensionMap) {
const mappedUUIDs = extensionMap[ext];
if (mappedUUIDs.includes(packUuid)) {
mappedExtension = ext;
break;
}
}
const pack = { uuid: packUuid, packedUuids: uuids, ext: mappedExtension };
assetInfos.add(packUuid, pack);

for (let i = 0, l = uuids.length; i < l; i++) {
Expand Down
13 changes: 7 additions & 6 deletions cocos/asset/asset-manager/pack-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import { ImageAsset } from '../assets/image-asset';
import { Texture2D } from '../assets/texture-2d';
import { packCustomObjData, unpackJSONs } from '../../serialization/deserialize';
import { isGeneralPurposePack, packCustomObjData, unpackJSONs } from '../../serialization/deserialize';
import { assertIsTrue, error, errorID, js } from '../../core';
import Cache from './cache';
import downloader from './downloader';
Expand Down Expand Up @@ -56,6 +56,7 @@ export class PackManager {
private _loading = new Cache<IUnpackRequest[]>();
private _unpackers: Record<string, Unpacker> = {
'.json': this.unpackJson,
'.ccon': this.unpackJson,
};

/**
Expand All @@ -79,22 +80,22 @@ export class PackManager {
*
*/
public unpackJson (
pack: string[],
pack: readonly string[],
json: any,
options: Record<string, any>,
onComplete: ((err: Error | null, data?: Record<string, any> | null) => void),
): void {
const out: Record<string, any> = js.createMap(true);
let err: Error | null = null;

if (Array.isArray(json)) {
json = unpackJSONs(json as unknown as Parameters<typeof unpackJSONs>[0]);
if (isGeneralPurposePack(json)) {
const unpacked = unpackJSONs(json);

if (json.length !== pack.length) {
if (unpacked.length !== pack.length) {
errorID(4915);
}
for (let i = 0; i < pack.length; i++) {
out[`${pack[i]}@import`] = json[i];
out[`${pack[i]}@import`] = unpacked[i];
}
} else {
const textureType = js.getClassId(Texture2D);
Expand Down
129 changes: 129 additions & 0 deletions cocos/serialization/compiled/typed-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { assertIsTrue, sys } from '../../core';
import { IRuntimeFileData } from '../deserialize';

assertIsTrue(sys.isLittleEndian, `Deserialization system currently suppose little endian.`);

export const typedArrayTypeTable = Object.freeze([
Float32Array,
Float64Array,

Int8Array,
Int16Array,
Int32Array,

Uint8Array,
Uint16Array,
Uint32Array,

Uint8ClampedArray,
// BigInt64Array,
// BigUint64Array,
] as const);

/**
* Describes the serialized data of an typed array.
* - If it's an array, it's `TypedArrayDataJson`.
* - Otherwise, it's `TypedArrayDataPtr`.
*/
export type TypedArrayData = TypedArrayDataJson | TypedArrayDataPtr;

export type TypedArrayDataJson = [
/**
* Indicates the constructor of typed array.
* It's index of the constructor in `TypedArrays`.
*/
typeIndex: number,

/**
* Array element values.
*/
elements: number[],
];

/**
* Let `offset` be this value,
* Let `storage` be the binary buffer attached to the deserialized document.
* Then, the data of `storage` started from `offset`
* can be described using the following structure(in C++, assuming fields are packed tightly):
*
* ```cpp
* struct _ {
* /// Indicates the constructor of typed array.
* /// It's index of the constructor in `typedArrayTypeTable`.
* std::uint32_t typeIndex;
*
* /// The typed array's element count. Note this is not "byte length".
* std:: uint32_t length;
*
* /// Automatically padding bytes to align the `arrayBufferBytes`.
* /// See comments on `arrayBufferBytes`.
* std::byte[] _padding;
*
* /// Bytes of the underlying `ArrayBuffer` of this typed array.
* /// Should be aligned to `typedArrayConstructor.BYTES_PER_ELEMENT`
* /// according to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#bytelength_must_be_aligned.
* std::byte[] arrayBufferBytes;
* }
* ```
*/
export type TypedArrayDataPtr = number;

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function getTypedArrayConstructor (typeIndex: number) {
assertIsTrue(typeIndex >= 0 && typeIndex < typedArrayTypeTable.length);
return typedArrayTypeTable[typeIndex];
}

function calculatePaddingToAlignAs (v: number, align: number): number {
if (align === 0) {
return 0;
}
const remainder = v % align;
if (remainder !== 0) {
return align - remainder;
}
return 0;
}

function decodeTypedArray (data: IRuntimeFileData, value: TypedArrayData): ArrayBufferView {
if (Array.isArray(value)) {
const [typeIndex, elements] = value;
const TypedArrayConstructor = getTypedArrayConstructor(typeIndex);
return new TypedArrayConstructor(elements);
} else {
const context = data[0];
jareguo marked this conversation as resolved.
Show resolved Hide resolved
const attachedBinary = context._attachedBinary;
assertIsTrue(attachedBinary, `Incorrect data: binary is expected.`);
const dataView = (context._attachedBinaryDataViewCache
??= new DataView(attachedBinary.buffer, attachedBinary.byteOffset, attachedBinary.byteLength));

let p = value;
const header = dataView.getUint8(p);
p += 1;
const length = dataView.getUint32(p, true);
p += 4;

const typeIndex = header & 0xFF;
const TypedArrayConstructor = getTypedArrayConstructor(typeIndex);

// The elements must be padded.
p += calculatePaddingToAlignAs(p + attachedBinary.byteOffset, TypedArrayConstructor.BYTES_PER_ELEMENT);

// Copy the section:
// - Allocates the result.
// - Creates a view on big buffer.
// - Copy using `TypedArray.prototype.set`.
// This manner do not consider the endianness problem.
//
// Here listed the benchmark in various other ways:
// https://jsperf.app/vayeri/2/preview
//
const result = new TypedArrayConstructor(length);
result.set(new TypedArrayConstructor(attachedBinary.buffer, attachedBinary.byteOffset + p, length));
return result;
}
}

export function deserializeTypedArray (data: IRuntimeFileData, owner: any, key: string, value: TypedArrayData): void {
owner[key] = decodeTypedArray(data, value);
}
Loading
Loading