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,395 @@
/**
* ByteBuffer
*
* Binary data reader for parsing FairyGUI package files.
*
* 二进制数据读取器,用于解析 FairyGUI 包文件
*/
export class ByteBuffer {
private _data: DataView;
private _position: number = 0;
private _littleEndian: boolean = false;
private _stringTable: string[] = [];
private _version: number = 0;
constructor(buffer: ArrayBuffer, offset: number = 0, length?: number) {
length = length ?? buffer.byteLength - offset;
this._data = new DataView(buffer, offset, length);
}
/**
* Get buffer length
* 获取缓冲区长度
*/
public get length(): number {
return this._data.byteLength;
}
/**
* Get current position
* 获取当前位置
*/
public get position(): number {
return this._position;
}
/**
* Set current position
* 设置当前位置
*/
public set position(value: number) {
this._position = value;
}
/**
* Get version
* 获取版本
*/
public get version(): number {
return this._version;
}
/**
* Set version
* 设置版本
*/
public set version(value: number) {
this._version = value;
}
/**
* Check if can read more bytes
* 检查是否可以读取更多字节
*/
public get bytesAvailable(): number {
return this._data.byteLength - this._position;
}
/**
* Skip bytes
* 跳过字节
*/
public skip(count: number): void {
this._position += count;
}
/**
* Seek to position
* 定位到指定位置
*/
public seek(indexTablePos: number, blockIndex: number): boolean {
const tmp = this._position;
this._position = indexTablePos;
const segCount = this.getUint8();
if (blockIndex < segCount) {
const useShort = this.getUint8() === 1;
let newPos: number;
if (useShort) {
this._position = indexTablePos + 2 + 2 * blockIndex;
newPos = this.getUint16();
} else {
this._position = indexTablePos + 2 + 4 * blockIndex;
newPos = this.getUint32();
}
if (newPos > 0) {
this._position = indexTablePos + newPos;
return true;
} else {
this._position = tmp;
return false;
}
} else {
this._position = tmp;
return false;
}
}
// Read methods | 读取方法
public getUint8(): number {
const value = this._data.getUint8(this._position);
this._position += 1;
return value;
}
public getInt8(): number {
const value = this._data.getInt8(this._position);
this._position += 1;
return value;
}
public getUint16(): number {
const value = this._data.getUint16(this._position, this._littleEndian);
this._position += 2;
return value;
}
public getInt16(): number {
const value = this._data.getInt16(this._position, this._littleEndian);
this._position += 2;
return value;
}
public getUint32(): number {
const value = this._data.getUint32(this._position, this._littleEndian);
this._position += 4;
return value;
}
public getInt32(): number {
const value = this._data.getInt32(this._position, this._littleEndian);
this._position += 4;
return value;
}
public getFloat32(): number {
const value = this._data.getFloat32(this._position, this._littleEndian);
this._position += 4;
return value;
}
public getFloat64(): number {
const value = this._data.getFloat64(this._position, this._littleEndian);
this._position += 8;
return value;
}
/**
* Read boolean
* 读取布尔值
*/
public readBool(): boolean {
return this.getUint8() === 1;
}
/**
* Read byte
* 读取字节
*/
public readByte(): number {
return this.getUint8();
}
/**
* Read short
* 读取短整数
*/
public readShort(): number {
return this.getInt16();
}
/**
* Read unsigned short
* 读取无符号短整数
*/
public readUshort(): number {
return this.getUint16();
}
/**
* Read int
* 读取整数
*/
public readInt(): number {
return this.getInt32();
}
/**
* Read unsigned int
* 读取无符号整数
*/
public readUint(): number {
return this.getUint32();
}
/**
* Read float
* 读取浮点数
*/
public readFloat(): number {
return this.getFloat32();
}
/**
* Read string from string table
* 从字符串表读取字符串
*/
public readS(): string {
const index = this.getUint16();
if (index === 65535) {
return '';
}
return this._stringTable[index] || '';
}
/**
* Read string with length prefix
* 读取带长度前缀的字符串
*/
public readString(): string {
const len = this.getUint16();
if (len === 0) {
return '';
}
return this.readStringWithLength(len);
}
private readStringWithLength(len: number): string {
const bytes = new Uint8Array(this._data.buffer, this._data.byteOffset + this._position, len);
this._position += len;
return new TextDecoder('utf-8').decode(bytes);
}
/**
* Read color as packed u32 (0xRRGGBBAA format)
* 读取颜色为打包的 u320xRRGGBBAA 格式)
*/
public readColor(bHasAlpha: boolean = false): number {
const r = this.getUint8();
const g = this.getUint8();
const b = this.getUint8();
const a = this.getUint8();
return ((r << 24) | (g << 16) | (b << 8) | (bHasAlpha ? a : 0xFF)) >>> 0;
}
/**
* Read color as CSS string (always reads 4 bytes: r, g, b, a)
* 读取颜色为 CSS 字符串(总是读取 4 字节r, g, b, a
*
* FairyGUI 二进制格式颜色存储顺序为 R, G, B, A
*/
public readColorS(bHasAlpha: boolean = false): string {
const byte0 = this.getUint8();
const byte1 = this.getUint8();
const byte2 = this.getUint8();
const byte3 = this.getUint8();
// FairyGUI stores colors as R, G, B, A
const r = byte0;
const g = byte1;
const b = byte2;
const a = byte3;
if (bHasAlpha && a !== 255) {
return `rgba(${r},${g},${b},${(a / 255).toFixed(2)})`;
} else {
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}
}
/**
* Read bytes
* 读取字节数组
*/
public readBytes(length: number): Uint8Array {
const bytes = new Uint8Array(this._data.buffer, this._data.byteOffset + this._position, length);
this._position += length;
return bytes;
}
/**
* Set string table
* 设置字符串表
*/
public set stringTable(value: string[]) {
this._stringTable = value;
}
/**
* Get string table
* 获取字符串表
*/
public get stringTable(): string[] {
return this._stringTable;
}
/**
* Alias for position getter
* position getter 别名
*/
public get pos(): number {
return this._position;
}
/**
* Alias for position setter
* position setter 别名
*/
public set pos(value: number) {
this._position = value;
}
/**
* Get underlying buffer
* 获取底层缓冲区
*/
public get buffer(): ArrayBuffer {
return this._data.buffer as ArrayBuffer;
}
/**
* Read UTF string (length-prefixed)
* 读取 UTF 字符串(带长度前缀)
*/
public readUTFString(): string {
const len = this.getUint16();
if (len === 0) {
return '';
}
return this.readStringWithLength(len);
}
/**
* Read string array
* 读取字符串数组
*/
public readSArray(count: number): string[] {
const arr: string[] = [];
for (let i = 0; i < count; i++) {
arr.push(this.readS());
}
return arr;
}
/**
* Read custom string with specified length
* 读取指定长度的自定义字符串
*/
public getCustomString(len: number): string {
const bytes = new Uint8Array(this._data.buffer, this._data.byteOffset + this._position, len);
this._position += len;
return new TextDecoder('utf-8').decode(bytes);
}
/**
* Read sub-buffer
* 读取子缓冲区
*/
public readBuffer(): ByteBuffer {
const len = this.getUint32();
const buffer = new ByteBuffer(this._data.buffer as ArrayBuffer, this._data.byteOffset + this._position, len);
buffer.version = this._version;
buffer.stringTable = this._stringTable;
this._position += len;
return buffer;
}
/**
* Read Int32 (alias)
* 读取 Int32别名
*/
public readInt32(): number {
return this.getInt32();
}
/**
* Read Uint16 (alias)
* 读取 Uint16别名
*/
public readUint16(): number {
return this.getUint16();
}
}

View File

@@ -0,0 +1,192 @@
/**
* Point interface
* 2D point
*/
export interface IPoint {
x: number;
y: number;
}
/**
* Rectangle interface
* 2D rectangle
*/
export interface IRectangle {
x: number;
y: number;
width: number;
height: number;
}
/**
* Point class
* 2D point with utility methods
*/
export class Point implements IPoint {
public x: number;
public y: number;
constructor(x: number = 0, y: number = 0) {
this.x = x;
this.y = y;
}
public set(x: number, y: number): this {
this.x = x;
this.y = y;
return this;
}
public copyFrom(source: IPoint): this {
this.x = source.x;
this.y = source.y;
return this;
}
public clone(): Point {
return new Point(this.x, this.y);
}
public distance(target: IPoint): number {
const dx = target.x - this.x;
const dy = target.y - this.y;
return Math.sqrt(dx * dx + dy * dy);
}
public equals(other: IPoint): boolean {
return this.x === other.x && this.y === other.y;
}
public static readonly ZERO: Readonly<Point> = new Point(0, 0);
}
/**
* Rectangle class
* 2D rectangle with utility methods
*/
export class Rectangle implements IRectangle {
public x: number;
public y: number;
public width: number;
public height: number;
constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public get right(): number {
return this.x + this.width;
}
public get bottom(): number {
return this.y + this.height;
}
public set(x: number, y: number, width: number, height: number): this {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
return this;
}
public copyFrom(source: IRectangle): this {
this.x = source.x;
this.y = source.y;
this.width = source.width;
this.height = source.height;
return this;
}
public clone(): Rectangle {
return new Rectangle(this.x, this.y, this.width, this.height);
}
public contains(x: number, y: number): boolean {
return x >= this.x && x < this.right && y >= this.y && y < this.bottom;
}
public containsPoint(point: IPoint): boolean {
return this.contains(point.x, point.y);
}
public intersects(other: IRectangle): boolean {
return !(
other.x >= this.right ||
other.x + other.width <= this.x ||
other.y >= this.bottom ||
other.y + other.height <= this.y
);
}
public intersection(other: IRectangle, out?: Rectangle): Rectangle | null {
const x = Math.max(this.x, other.x);
const y = Math.max(this.y, other.y);
const right = Math.min(this.right, other.x + other.width);
const bottom = Math.min(this.bottom, other.y + other.height);
if (right <= x || bottom <= y) {
return null;
}
out = out || new Rectangle();
return out.set(x, y, right - x, bottom - y);
}
public union(other: IRectangle, out?: Rectangle): Rectangle {
const x = Math.min(this.x, other.x);
const y = Math.min(this.y, other.y);
const right = Math.max(this.right, other.x + other.width);
const bottom = Math.max(this.bottom, other.y + other.height);
out = out || new Rectangle();
return out.set(x, y, right - x, bottom - y);
}
public isEmpty(): boolean {
return this.width <= 0 || this.height <= 0;
}
public setEmpty(): this {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
return this;
}
public static readonly EMPTY: Readonly<Rectangle> = new Rectangle();
}
/**
* Margin class
* Represents margins/padding (left, top, right, bottom)
*/
export class Margin {
public left: number;
public top: number;
public right: number;
public bottom: number;
constructor(left: number = 0, top: number = 0, right: number = 0, bottom: number = 0) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public copyFrom(source: Margin): this {
this.left = source.left;
this.top = source.top;
this.right = source.right;
this.bottom = source.bottom;
return this;
}
public clone(): Margin {
return new Margin(this.left, this.top, this.right, this.bottom);
}
}