refactor: reorganize package structure and decouple framework packages (#338)

* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
YHH
2025-12-26 14:50:35 +08:00
committed by GitHub
parent a84ff902e4
commit 155411e743
1936 changed files with 4147 additions and 11578 deletions

View File

@@ -0,0 +1,149 @@
import { EPackageItemType, EObjectType } from '../core/FieldTypes';
import type { UIPackage } from './UIPackage';
import type { ByteBuffer } from '../utils/ByteBuffer';
/**
* PackageItem
*
* Represents a resource item in a UI package.
*
* 表示 UI 包中的资源项
*/
export class PackageItem {
/** Owner package | 所属包 */
public owner: UIPackage | null = null;
/** Item type | 项目类型 */
public type: EPackageItemType = EPackageItemType.Unknown;
/** Object type | 对象类型 */
public objectType: EObjectType = EObjectType.Image;
/** Item ID | 项目 ID */
public id: string = '';
/** Item name | 项目名称 */
public name: string = '';
/** Width | 宽度 */
public width: number = 0;
/** Height | 高度 */
public height: number = 0;
/** File path | 文件路径 */
public file: string = '';
/** Is exported | 是否导出 */
public exported: boolean = false;
// Image specific | 图像相关
/** Scale9 grid | 九宫格 */
public scale9Grid: { x: number; y: number; width: number; height: number } | null = null;
/** Scale by tile | 平铺缩放 */
public scaleByTile: boolean = false;
/** Tile grid indent | 平铺网格缩进 */
public tileGridIndice: number = 0;
// MovieClip specific | 动画相关
/** Frame delay | 帧延迟 */
public interval: number = 0;
/** Repeat delay | 重复延迟 */
public repeatDelay: number = 0;
/** Swing | 摇摆 */
public swing: boolean = false;
// Sound specific | 音频相关
/** Volume | 音量 */
public volume: number = 1;
// Component specific | 组件相关
/** Raw data (ByteBuffer for parsed data) | 原始数据 */
public rawData: ByteBuffer | null = null;
/** Branch index | 分支索引 */
public branches: string[] | null = null;
/** High resolution | 高分辨率 */
public highResolution: string[] | null = null;
// Loaded content | 加载的内容
/** Loaded texture | 加载的纹理 */
public texture: any = null;
/** Loaded frames | 加载的帧 */
public frames: any[] | null = null;
/** Loaded font | 加载的字体 */
public bitmapFont: any = null;
/** Loading flag | 加载中标记 */
public loading: boolean = false;
/** Decoded flag | 已解码标记 */
public decoded: boolean = false;
/**
* Get full path
* 获取完整路径
*/
public toString(): string {
return this.owner ? `${this.owner.name}/${this.name}` : this.name;
}
/**
* Get branch version of this item
* 获取此项目的分支版本
*/
public getBranch(): PackageItem {
if (this.branches && this.branches.length > 0 && this.owner) {
const branchName = this.owner.constructor.name === 'UIPackage'
? (this.owner as any).constructor.branch
: '';
if (branchName) {
const branchIndex = this.branches.indexOf(branchName);
if (branchIndex >= 0) {
const branchItem = this.owner.getItemById(this.branches[branchIndex]);
if (branchItem) return branchItem;
}
}
}
return this;
}
/**
* Get high resolution version of this item
* 获取此项目的高分辨率版本
*/
public getHighResolution(): PackageItem {
if (this.highResolution && this.highResolution.length > 0 && this.owner) {
// For now, return first high res version if available
const hiResItem = this.owner.getItemById(this.highResolution[0]);
if (hiResItem) return hiResItem;
}
return this;
}
/**
* Load this item's content
* 加载此项目的内容
*/
public load(): void {
if (this.loading || this.decoded) return;
this.loading = true;
// Loading is typically done by the package
// This is a placeholder for async loading
if (this.owner) {
this.owner.getItemAsset(this);
}
this.loading = false;
this.decoded = true;
}
}

View File

@@ -0,0 +1,867 @@
import { PackageItem } from './PackageItem';
import { EPackageItemType, EObjectType } from '../core/FieldTypes';
import { UIObjectFactory } from '../core/UIObjectFactory';
import { ByteBuffer } from '../utils/ByteBuffer';
import type { GObject } from '../core/GObject';
/** FairyGUI package file magic number | FairyGUI 包文件魔数 */
const PACKAGE_MAGIC = 0x46475549; // 'FGUI'
/** Package dependency | 包依赖 */
interface IPackageDependency {
id: string;
name: string;
}
/** Atlas sprite info | 图集精灵信息 */
interface IAtlasSprite {
atlas: PackageItem;
rect: { x: number; y: number; width: number; height: number };
offset: { x: number; y: number };
originalSize: { x: number; y: number };
rotated: boolean;
}
/**
* UIPackage
*
* Represents a FairyGUI package (.fui file).
* Manages loading and accessing package resources.
*
* 表示 FairyGUI 包(.fui 文件),管理包资源的加载和访问
*/
export class UIPackage {
/** Package ID | 包 ID */
public id: string = '';
/** Package name | 包名称 */
public name: string = '';
/** Package URL | 包 URL */
public url: string = '';
/** Is constructing | 正在构造中 */
public static _constructing: number = 0;
private _items: PackageItem[] = [];
private _itemsById: Map<string, PackageItem> = new Map();
private _itemsByName: Map<string, PackageItem> = new Map();
private _sprites: Map<string, IAtlasSprite> = new Map();
private _dependencies: IPackageDependency[] = [];
private _branches: string[] = [];
private _branchIndex: number = -1;
private _resKey: string = '';
private static _packages: Map<string, UIPackage> = new Map();
private static _packagesByUrl: Map<string, UIPackage> = new Map();
private static _branch: string = '';
private static _vars: Map<string, string> = new Map();
/**
* Get branch name
* 获取分支名称
*/
public static get branch(): string {
return UIPackage._branch;
}
/**
* Set branch name
* 设置分支名称
*/
public static set branch(value: string) {
UIPackage._branch = value;
for (const pkg of UIPackage._packages.values()) {
if (pkg._branches.length > 0) {
pkg._branchIndex = pkg._branches.indexOf(value);
}
}
}
/**
* Get variable
* 获取变量
*/
public static getVar(key: string): string | undefined {
return UIPackage._vars.get(key);
}
/**
* Set variable
* 设置变量
*/
public static setVar(key: string, value: string): void {
UIPackage._vars.set(key, value);
}
/**
* Get package by ID
* 通过 ID 获取包
*/
public static getById(id: string): UIPackage | null {
return UIPackage._packages.get(id) || null;
}
/**
* Get package by ID (instance method wrapper)
* 通过 ID 获取包(实例方法包装器)
*/
public getPackageById(id: string): UIPackage | null {
return UIPackage.getById(id);
}
/**
* Get package by name
* 通过名称获取包
*/
public static getByName(name: string): UIPackage | null {
for (const pkg of UIPackage._packages.values()) {
if (pkg.name === name) {
return pkg;
}
}
return null;
}
/**
* Add package from binary data
* 从二进制数据添加包
*/
public static addPackageFromBuffer(resKey: string, descData: ArrayBuffer): UIPackage {
const buffer = new ByteBuffer(descData);
const pkg = new UIPackage();
pkg._resKey = resKey;
pkg.loadPackage(buffer);
UIPackage._packages.set(pkg.id, pkg);
UIPackage._packages.set(resKey, pkg);
return pkg;
}
/**
* Add a loaded package
* 添加已加载的包
*/
public static addPackage(pkg: UIPackage): void {
UIPackage._packages.set(pkg.id, pkg);
if (pkg.url) {
UIPackage._packagesByUrl.set(pkg.url, pkg);
}
}
/**
* Remove a package
* 移除包
*/
public static removePackage(idOrName: string): void {
let pkg: UIPackage | null | undefined = UIPackage._packages.get(idOrName);
if (!pkg) {
pkg = UIPackage.getByName(idOrName);
}
if (pkg) {
UIPackage._packages.delete(pkg.id);
UIPackage._packages.delete(pkg._resKey);
if (pkg.url) {
UIPackage._packagesByUrl.delete(pkg.url);
}
pkg.dispose();
}
}
/**
* Create object from URL
* 从 URL 创建对象
*/
public static createObject(pkgName: string, resName: string): GObject | null {
const pkg = UIPackage.getByName(pkgName);
if (pkg) {
return pkg.createObject(resName);
}
return null;
}
/**
* Create object from URL string
* 从 URL 字符串创建对象
*/
public static createObjectFromURL(url: string): GObject | null {
const pi = UIPackage.getItemByURL(url);
if (pi) {
return pi.owner?.internalCreateObject(pi) ?? null;
}
return null;
}
/**
* Get item by URL
* 通过 URL 获取项
*/
public static getItemByURL(url: string): PackageItem | null {
if (!url) return null;
// URL format: ui://pkgName/resName or ui://pkgId/resId
const pos = url.indexOf('//');
if (pos === -1) return null;
const pos2 = url.indexOf('/', pos + 2);
if (pos2 === -1) {
if (url.length > 13) {
const pkgId = url.substring(5, 13);
const pkg = UIPackage.getById(pkgId);
if (pkg) {
const srcId = url.substring(13);
return pkg.getItemById(srcId);
}
}
} else {
const pkgName = url.substring(pos + 2, pos2);
const pkg = UIPackage.getByName(pkgName);
if (pkg) {
const resName = url.substring(pos2 + 1);
return pkg.getItemByName(resName);
}
}
return null;
}
/**
* Get item asset by URL
* 通过 URL 获取项资源
*/
public static getItemAssetByURL(url: string): any {
const pi = UIPackage.getItemByURL(url);
if (pi) {
return pi.owner?.getItemAsset(pi);
}
return null;
}
/**
* Normalize URL
* 标准化 URL
*/
public static normalizeURL(url: string): string {
if (!url) return '';
if (url.startsWith('ui://')) return url;
return 'ui://' + url;
}
/**
* Get item URL
* 获取项目 URL
*/
public static getItemURL(pkgName: string, resName: string): string | null {
const pkg = UIPackage.getByName(pkgName);
if (!pkg) return null;
const pi = pkg.getItemByName(resName);
if (!pi) return null;
return 'ui://' + pkg.id + pi.id;
}
// Instance methods | 实例方法
/**
* Load package from buffer
* 从缓冲区加载包
*/
private loadPackage(buffer: ByteBuffer): void {
if (buffer.getUint32() !== PACKAGE_MAGIC) {
throw new Error('FairyGUI: invalid package format in \'' + this._resKey + '\'');
}
buffer.version = buffer.getInt32();
const compressed = buffer.readBool();
this.id = buffer.readUTFString();
this.name = buffer.readUTFString();
buffer.skip(20);
// Handle compressed data
if (compressed) {
// Note: Compression requires pako or similar library
// For now, we'll throw an error if the package is compressed
throw new Error('FairyGUI: compressed packages are not supported yet');
}
const ver2 = buffer.version >= 2;
const indexTablePos = buffer.pos;
// Read string table
buffer.seek(indexTablePos, 4);
const strCount = buffer.getInt32();
const stringTable: string[] = [];
for (let i = 0; i < strCount; i++) {
stringTable[i] = buffer.readUTFString();
}
buffer.stringTable = stringTable;
// Read custom strings (version 2+)
if (buffer.seek(indexTablePos, 5)) {
const customCount = buffer.readInt32();
for (let i = 0; i < customCount; i++) {
const index = buffer.readUint16();
const len = buffer.readInt32();
stringTable[index] = buffer.getCustomString(len);
}
}
// Read dependencies
buffer.seek(indexTablePos, 0);
const depCount = buffer.getInt16();
for (let i = 0; i < depCount; i++) {
this._dependencies.push({
id: buffer.readS(),
name: buffer.readS()
});
}
// Read branches (version 2+)
let branchIncluded = false;
if (ver2) {
const branchCount = buffer.getInt16();
if (branchCount > 0) {
this._branches = buffer.readSArray(branchCount);
if (UIPackage._branch) {
this._branchIndex = this._branches.indexOf(UIPackage._branch);
}
branchIncluded = true;
}
}
// Read items
buffer.seek(indexTablePos, 1);
const path = this._resKey;
const lastSlash = path.lastIndexOf('/');
const shortPath = lastSlash === -1 ? '' : path.substring(0, lastSlash + 1);
// Remove .fui extension for atlas base path (e.g., "assets/ui/Bag.fui" -> "assets/ui/Bag_")
// 移除 .fui 扩展名用于图集基础路径
const baseName = path.endsWith('.fui') ? path.slice(0, -4) : path;
const basePath = baseName + '_';
const itemCount = buffer.getUint16();
for (let i = 0; i < itemCount; i++) {
let nextPos = buffer.getInt32();
nextPos += buffer.pos;
const pi = new PackageItem();
pi.owner = this;
pi.type = buffer.readByte() as EPackageItemType;
pi.id = buffer.readS();
pi.name = buffer.readS();
buffer.readS(); // path
const file = buffer.readS();
if (file) {
pi.file = file;
}
buffer.readBool(); // exported
pi.width = buffer.getInt32();
pi.height = buffer.getInt32();
switch (pi.type) {
case EPackageItemType.Image: {
pi.objectType = EObjectType.Image;
const scaleOption = buffer.readByte();
if (scaleOption === 1) {
pi.scale9Grid = {
x: buffer.getInt32(),
y: buffer.getInt32(),
width: buffer.getInt32(),
height: buffer.getInt32()
};
pi.tileGridIndice = buffer.getInt32();
} else if (scaleOption === 2) {
pi.scaleByTile = true;
}
buffer.readBool(); // smoothing
break;
}
case EPackageItemType.MovieClip: {
buffer.readBool(); // smoothing
pi.objectType = EObjectType.MovieClip;
pi.rawData = buffer.readBuffer();
break;
}
case EPackageItemType.Font: {
pi.rawData = buffer.readBuffer();
break;
}
case EPackageItemType.Component: {
const extension = buffer.readByte();
if (extension > 0) {
pi.objectType = extension as EObjectType;
} else {
pi.objectType = EObjectType.Component;
}
pi.rawData = buffer.readBuffer();
UIObjectFactory.resolvePackageItemExtension(pi);
break;
}
case EPackageItemType.Atlas:
case EPackageItemType.Sound:
case EPackageItemType.Misc: {
pi.file = basePath + pi.file;
break;
}
case EPackageItemType.Spine:
case EPackageItemType.DragonBones: {
pi.file = shortPath + pi.file;
buffer.getFloat32(); // skeletonAnchor.x
buffer.getFloat32(); // skeletonAnchor.y
break;
}
}
// Version 2 specific
if (ver2) {
const branchStr = buffer.readS();
if (branchStr) {
pi.name = branchStr + '/' + pi.name;
}
const branchCnt = buffer.getUint8();
if (branchCnt > 0) {
if (branchIncluded) {
pi.branches = buffer.readSArray(branchCnt);
} else {
this._itemsById.set(buffer.readS(), pi);
}
}
const highResCnt = buffer.getUint8();
if (highResCnt > 0) {
pi.highResolution = buffer.readSArray(highResCnt);
}
}
this._items.push(pi);
this._itemsById.set(pi.id, pi);
if (pi.name) {
this._itemsByName.set(pi.name, pi);
}
buffer.pos = nextPos;
}
// Read sprites
buffer.seek(indexTablePos, 2);
const spriteCount = buffer.getUint16();
for (let i = 0; i < spriteCount; i++) {
let nextPos = buffer.getUint16();
nextPos += buffer.pos;
const itemId = buffer.readS();
const atlasItem = this._itemsById.get(buffer.readS());
if (atlasItem) {
const sprite: IAtlasSprite = {
atlas: atlasItem,
rect: {
x: buffer.getInt32(),
y: buffer.getInt32(),
width: buffer.getInt32(),
height: buffer.getInt32()
},
offset: { x: 0, y: 0 },
originalSize: { x: 0, y: 0 },
rotated: buffer.readBool()
};
if (ver2 && buffer.readBool()) {
sprite.offset.x = buffer.getInt32();
sprite.offset.y = buffer.getInt32();
sprite.originalSize.x = buffer.getInt32();
sprite.originalSize.y = buffer.getInt32();
} else {
sprite.originalSize.x = sprite.rect.width;
sprite.originalSize.y = sprite.rect.height;
}
this._sprites.set(itemId, sprite);
}
buffer.pos = nextPos;
}
// Read hit test data (optional)
if (buffer.seek(indexTablePos, 3)) {
const hitTestCount = buffer.getUint16();
for (let i = 0; i < hitTestCount; i++) {
let nextPos = buffer.getInt32();
nextPos += buffer.pos;
const pi = this._itemsById.get(buffer.readS());
if (pi && pi.type === EPackageItemType.Image) {
// PixelHitTestData would be loaded here
// For now we skip this
}
buffer.pos = nextPos;
}
}
}
/**
* Get item by ID
* 通过 ID 获取项
*/
public getItemById(id: string): PackageItem | null {
return this._itemsById.get(id) || null;
}
/**
* Get item by name
* 通过名称获取项
*/
public getItemByName(name: string): PackageItem | null {
return this._itemsByName.get(name) || null;
}
/**
* Get all atlas file paths in this package
* 获取此包中所有图集文件路径
*/
public getAtlasFiles(): string[] {
const files: string[] = [];
for (const item of this._items) {
if (item.type === EPackageItemType.Atlas && item.file) {
files.push(item.file);
}
}
return files;
}
/**
* Get sprite by item ID
* 通过项目 ID 获取精灵
*/
public getSprite(itemId: string): IAtlasSprite | null {
return this._sprites.get(itemId) || null;
}
/**
* Get item asset
* 获取项资源
*/
public getItemAsset(item: PackageItem): any {
switch (item.type) {
case EPackageItemType.Image:
if (!item.decoded) {
item.decoded = true;
const sprite = this._sprites.get(item.id);
if (sprite) {
// Store sprite info for rendering
// The atlas file path is used as texture ID
// Include atlas dimensions for UV calculation
item.texture = {
atlas: sprite.atlas.file,
atlasId: sprite.atlas.id,
rect: sprite.rect,
offset: sprite.offset,
originalSize: sprite.originalSize,
rotated: sprite.rotated,
atlasWidth: sprite.atlas.width,
atlasHeight: sprite.atlas.height
};
}
}
return item.texture;
case EPackageItemType.Atlas:
if (!item.decoded) {
item.decoded = true;
// Load atlas texture
// This would require asset loading infrastructure
}
return item.texture;
case EPackageItemType.MovieClip:
if (!item.decoded) {
item.decoded = true;
this.loadMovieClip(item);
}
return item.frames;
case EPackageItemType.Font:
if (!item.decoded) {
item.decoded = true;
this.loadFont(item);
}
return item.bitmapFont;
case EPackageItemType.Component:
return item.rawData;
default:
return null;
}
}
/**
* Load movie clip data
* 加载动画片段数据
*/
private loadMovieClip(item: PackageItem): void {
const buffer = item.rawData as ByteBuffer;
if (!buffer) return;
buffer.seek(0, 0);
item.interval = buffer.getInt32();
item.swing = buffer.readBool();
item.repeatDelay = buffer.getInt32();
buffer.seek(0, 1);
const frameCount = buffer.getInt16();
item.frames = [];
for (let i = 0; i < frameCount; i++) {
let nextPos = buffer.getInt16();
nextPos += buffer.pos;
const fx = buffer.getInt32();
const fy = buffer.getInt32();
buffer.getInt32(); // width
buffer.getInt32(); // height
const addDelay = buffer.getInt32();
const spriteId = buffer.readS();
const frame: any = { addDelay, texture: null };
if (spriteId) {
const sprite = this._sprites.get(spriteId);
if (sprite) {
// Create texture from sprite with atlas info for UV calculation
// 从 sprite 创建纹理信息,包含用于 UV 计算的图集信息
frame.texture = {
atlas: sprite.atlas.file,
atlasId: sprite.atlas.id,
rect: sprite.rect,
offset: sprite.offset,
originalSize: sprite.originalSize,
rotated: sprite.rotated,
atlasWidth: sprite.atlas.width,
atlasHeight: sprite.atlas.height
};
}
}
item.frames[i] = frame;
buffer.pos = nextPos;
}
}
/**
* Load font data
* 加载字体数据
*/
private loadFont(item: PackageItem): void {
const buffer = item.rawData as ByteBuffer;
if (!buffer) return;
buffer.seek(0, 0);
const ttf = buffer.readBool();
const tint = buffer.readBool();
buffer.readBool(); // autoScaleSize
buffer.readBool(); // has channel
const fontSize = Math.max(buffer.getInt32(), 1);
const xadvance = buffer.getInt32();
const lineHeight = buffer.getInt32();
const font: any = {
ttf,
tint,
fontSize,
lineHeight: Math.max(lineHeight, fontSize),
glyphs: new Map()
};
buffer.seek(0, 1);
const glyphCount = buffer.getInt32();
for (let i = 0; i < glyphCount; i++) {
let nextPos = buffer.getInt16();
nextPos += buffer.pos;
const ch = buffer.getUint16();
const glyph: any = {};
const img = buffer.readS();
const bx = buffer.getInt32();
const by = buffer.getInt32();
glyph.x = buffer.getInt32();
glyph.y = buffer.getInt32();
glyph.width = buffer.getInt32();
glyph.height = buffer.getInt32();
glyph.advance = buffer.getInt32();
buffer.readByte(); // channel
if (!ttf && glyph.advance === 0) {
glyph.advance = xadvance > 0 ? xadvance : glyph.x + glyph.width;
}
font.glyphs.set(ch, glyph);
buffer.pos = nextPos;
}
item.bitmapFont = font;
}
/**
* Create object from item name
* 从项名称创建对象
*/
public createObject(resName: string): GObject | null {
const pi = this.getItemByName(resName);
if (pi) {
return this.internalCreateObject(pi);
}
console.warn(`[UIPackage] createObject: item not found: "${resName}" in package "${this.name}". Available items:`, Array.from(this._itemsByName.keys()));
return null;
}
/**
* Internal create object from package item
* 从包资源项内部创建对象
*/
public internalCreateObject(item: PackageItem): GObject | null {
// Check for extension first
const url = 'ui://' + this.id + item.id;
if (UIObjectFactory.hasExtension(url)) {
const obj = UIObjectFactory.createObjectFromURL(url);
if (obj) {
obj.packageItem = item;
UIPackage._constructing++;
obj.constructFromResource();
UIPackage._constructing--;
return obj;
}
}
// Create object based on item type
const obj = UIObjectFactory.createObject(item.objectType);
if (obj) {
obj.packageItem = item;
UIPackage._constructing++;
obj.constructFromResource();
UIPackage._constructing--;
}
return obj;
}
/**
* Create object asynchronously
* 异步创建对象
*/
public createObjectAsync(resName: string, callback: (obj: GObject | null) => void): void {
const pi = this.getItemByName(resName);
if (pi) {
this.internalCreateObjectAsync(pi, callback);
} else {
callback(null);
}
}
/**
* Internal create object asynchronously
* 内部异步创建对象
*/
public internalCreateObjectAsync(item: PackageItem, callback: (obj: GObject | null) => void): void {
const obj = this.internalCreateObject(item);
callback(obj);
}
/**
* Get item URL
* 获取项目 URL
*/
public getItemUrl(item: PackageItem): string {
return 'ui://' + this.id + item.id;
}
/**
* Add item
* 添加项
*/
public addItem(item: PackageItem): void {
item.owner = this;
this._items.push(item);
this._itemsById.set(item.id, item);
this._itemsByName.set(item.name, item);
}
/**
* Get all items
* 获取所有项
*/
public get items(): readonly PackageItem[] {
return this._items;
}
/**
* Get all exported component names
* 获取所有导出的组件名称
*/
public getExportedComponentNames(): string[] {
return this._items
.filter(item => item.type === EPackageItemType.Component && item.exported)
.map(item => item.name);
}
/**
* Get all component names (including non-exported)
* 获取所有组件名称(包括未导出的)
*/
public getAllComponentNames(): string[] {
return this._items
.filter(item => item.type === EPackageItemType.Component)
.map(item => item.name);
}
/**
* Get dependencies
* 获取依赖
*/
public get dependencies(): readonly IPackageDependency[] {
return this._dependencies;
}
/**
* Load all assets
* 加载所有资源
*/
public loadAllAssets(): void {
for (const item of this._items) {
this.getItemAsset(item);
}
}
/**
* Dispose
* 销毁
*/
public dispose(): void {
for (const item of this._items) {
item.owner = null;
if (item.type === EPackageItemType.Atlas && item.texture) {
// Dispose texture if needed
item.texture = null;
}
}
this._items.length = 0;
this._itemsById.clear();
this._itemsByName.clear();
this._sprites.clear();
this._dependencies.length = 0;
}
}