Feature/render pipeline (#232)

* refactor(engine): 重构2D渲染管线坐标系统

* feat(engine): 完善2D渲染管线和编辑器视口功能

* feat(editor): 实现Viewport变换工具系统

* feat(editor): 优化Inspector渲染性能并修复Gizmo变换工具显示

* feat(editor): 实现Run on Device移动预览功能

* feat(editor): 添加组件属性控制和依赖关系系统

* feat(editor): 实现动画预览功能和优化SpriteAnimator编辑器

* feat(editor): 修复SpriteAnimator动画预览功能并迁移CI到pnpm

* feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm

* feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm

* feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm

* feat(editor): 修复SpriteAnimator动画预览并迁移到pnpm

* feat(ci): 迁移项目到pnpm并修复CI构建问题

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 迁移CI工作流到pnpm并添加WASM构建支持

* chore: 移除 network 相关包

* chore: 移除 network 相关包
This commit is contained in:
YHH
2025-11-23 14:49:37 +08:00
committed by GitHub
parent b15cbab313
commit a3f7cc38b1
247 changed files with 33561 additions and 52047 deletions
@@ -0,0 +1,130 @@
/**
* Asset cache implementation
* 资产缓存实现
*/
import { AssetGUID } from '../types/AssetTypes';
/**
* Cache entry
* 缓存条目
*/
interface CacheEntry {
guid: AssetGUID;
asset: unknown;
lastAccessTime: number;
accessCount: number;
}
/**
* Asset cache implementation
* 资产缓存实现
*/
export class AssetCache {
private readonly _cache = new Map<AssetGUID, CacheEntry>();
constructor() {
// 无配置,无限制缓存 / No config, unlimited cache
}
/**
* Get cached asset
* 获取缓存的资产
*/
get<T = unknown>(guid: AssetGUID): T | null {
const entry = this._cache.get(guid);
if (!entry) return null;
// 更新访问信息 / Update access info
entry.lastAccessTime = Date.now();
entry.accessCount++;
return entry.asset as T;
}
/**
* Set cached asset
* 设置缓存的资产
*/
set<T = unknown>(guid: AssetGUID, asset: T): void {
const now = Date.now();
const entry: CacheEntry = {
guid,
asset,
lastAccessTime: now,
accessCount: 1
};
// 如果已存在,更新 / Update if exists
const oldEntry = this._cache.get(guid);
if (oldEntry) {
entry.accessCount = oldEntry.accessCount + 1;
}
this._cache.set(guid, entry);
}
/**
* Check if asset is cached
* 检查资产是否缓存
*/
has(guid: AssetGUID): boolean {
return this._cache.has(guid);
}
/**
* Remove asset from cache
* 从缓存移除资产
*/
remove(guid: AssetGUID): void {
this._cache.delete(guid);
}
/**
* Clear all cache
* 清空所有缓存
*/
clear(): void {
this._cache.clear();
}
/**
* Get cache size
* 获取缓存大小
*/
getSize(): number {
return this._cache.size;
}
/**
* Get all cached GUIDs
* 获取所有缓存的GUID
*/
getAllGuids(): AssetGUID[] {
return Array.from(this._cache.keys());
}
/**
* Get cache statistics
* 获取缓存统计
*/
getStatistics(): {
count: number;
entries: Array<{
guid: AssetGUID;
accessCount: number;
lastAccessTime: number;
}>;
} {
const entries = Array.from(this._cache.values()).map((entry) => ({
guid: entry.guid,
accessCount: entry.accessCount,
lastAccessTime: entry.lastAccessTime
}));
return {
count: this._cache.size,
entries
};
}
}
@@ -0,0 +1,431 @@
/**
* Asset database for managing asset metadata
* 用于管理资产元数据的资产数据库
*/
import {
AssetGUID,
AssetType,
IAssetMetadata,
IAssetCatalogEntry
} from '../types/AssetTypes';
/**
* Asset database implementation
* 资产数据库实现
*/
export class AssetDatabase {
private readonly _metadata = new Map<AssetGUID, IAssetMetadata>();
private readonly _pathToGuid = new Map<string, AssetGUID>();
private readonly _typeToGuids = new Map<AssetType, Set<AssetGUID>>();
private readonly _labelToGuids = new Map<string, Set<AssetGUID>>();
private readonly _dependencies = new Map<AssetGUID, Set<AssetGUID>>();
private readonly _dependents = new Map<AssetGUID, Set<AssetGUID>>();
/**
* Add asset to database
* 添加资产到数据库
*/
addAsset(metadata: IAssetMetadata): void {
const { guid, path, type, labels, dependencies } = metadata;
// 存储元数据 / Store metadata
this._metadata.set(guid, metadata);
this._pathToGuid.set(path, guid);
// 按类型索引 / Index by type
if (!this._typeToGuids.has(type)) {
this._typeToGuids.set(type, new Set());
}
this._typeToGuids.get(type)!.add(guid);
// 按标签索引 / Index by labels
labels.forEach((label) => {
if (!this._labelToGuids.has(label)) {
this._labelToGuids.set(label, new Set());
}
this._labelToGuids.get(label)!.add(guid);
});
// 建立依赖关系 / Establish dependencies
this.updateDependencies(guid, dependencies);
}
/**
* Remove asset from database
* 从数据库移除资产
*/
removeAsset(guid: AssetGUID): void {
const metadata = this._metadata.get(guid);
if (!metadata) return;
// 清理元数据 / Clean up metadata
this._metadata.delete(guid);
this._pathToGuid.delete(metadata.path);
// 清理类型索引 / Clean up type index
const typeSet = this._typeToGuids.get(metadata.type);
if (typeSet) {
typeSet.delete(guid);
if (typeSet.size === 0) {
this._typeToGuids.delete(metadata.type);
}
}
// 清理标签索引 / Clean up label indices
metadata.labels.forEach((label) => {
const labelSet = this._labelToGuids.get(label);
if (labelSet) {
labelSet.delete(guid);
if (labelSet.size === 0) {
this._labelToGuids.delete(label);
}
}
});
// 清理依赖关系 / Clean up dependencies
this.clearDependencies(guid);
}
/**
* Update asset metadata
* 更新资产元数据
*/
updateAsset(guid: AssetGUID, updates: Partial<IAssetMetadata>): void {
const metadata = this._metadata.get(guid);
if (!metadata) return;
// 如果路径改变,更新索引 / Update index if path changed
if (updates.path && updates.path !== metadata.path) {
this._pathToGuid.delete(metadata.path);
this._pathToGuid.set(updates.path, guid);
}
// 如果类型改变,更新索引 / Update index if type changed
if (updates.type && updates.type !== metadata.type) {
const oldTypeSet = this._typeToGuids.get(metadata.type);
if (oldTypeSet) {
oldTypeSet.delete(guid);
}
if (!this._typeToGuids.has(updates.type)) {
this._typeToGuids.set(updates.type, new Set());
}
this._typeToGuids.get(updates.type)!.add(guid);
}
// 如果依赖改变,更新关系 / Update relations if dependencies changed
if (updates.dependencies) {
this.updateDependencies(guid, updates.dependencies);
}
// 合并更新 / Merge updates
Object.assign(metadata, updates);
metadata.lastModified = Date.now();
metadata.version++;
}
/**
* Get asset metadata
* 获取资产元数据
*/
getMetadata(guid: AssetGUID): IAssetMetadata | undefined {
return this._metadata.get(guid);
}
/**
* Get metadata by path
* 通过路径获取元数据
*/
getMetadataByPath(path: string): IAssetMetadata | undefined {
const guid = this._pathToGuid.get(path);
return guid ? this._metadata.get(guid) : undefined;
}
/**
* Find assets by type
* 按类型查找资产
*/
findAssetsByType(type: AssetType): AssetGUID[] {
const guids = this._typeToGuids.get(type);
return guids ? Array.from(guids) : [];
}
/**
* Find assets by label
* 按标签查找资产
*/
findAssetsByLabel(label: string): AssetGUID[] {
const guids = this._labelToGuids.get(label);
return guids ? Array.from(guids) : [];
}
/**
* Find assets by multiple labels (AND operation)
* 按多个标签查找资产(AND操作)
*/
findAssetsByLabels(labels: string[]): AssetGUID[] {
if (labels.length === 0) return [];
let result: Set<AssetGUID> | null = null;
for (const label of labels) {
const labelGuids = this._labelToGuids.get(label);
if (!labelGuids || labelGuids.size === 0) return [];
if (!result) {
result = new Set(labelGuids);
} else {
// 交集 / Intersection
const intersection = new Set<AssetGUID>();
labelGuids.forEach((guid) => {
if (result!.has(guid)) {
intersection.add(guid);
}
});
result = intersection;
}
}
return result ? Array.from(result) : [];
}
/**
* Search assets by query
* 通过查询搜索资产
*/
searchAssets(query: {
name?: string;
type?: AssetType;
labels?: string[];
path?: string;
}): AssetGUID[] {
let results = Array.from(this._metadata.keys());
// 按名称过滤 / Filter by name
if (query.name) {
const nameLower = query.name.toLowerCase();
results = results.filter((guid) => {
const metadata = this._metadata.get(guid)!;
return metadata.name.toLowerCase().includes(nameLower);
});
}
// 按类型过滤 / Filter by type
if (query.type) {
const typeGuids = this._typeToGuids.get(query.type);
if (!typeGuids) return [];
results = results.filter((guid) => typeGuids.has(guid));
}
// 按标签过滤 / Filter by labels
if (query.labels && query.labels.length > 0) {
const labelResults = this.findAssetsByLabels(query.labels);
const labelSet = new Set(labelResults);
results = results.filter((guid) => labelSet.has(guid));
}
// 按路径过滤 / Filter by path
if (query.path) {
const pathLower = query.path.toLowerCase();
results = results.filter((guid) => {
const metadata = this._metadata.get(guid)!;
return metadata.path.toLowerCase().includes(pathLower);
});
}
return results;
}
/**
* Get asset dependencies
* 获取资产依赖
*/
getDependencies(guid: AssetGUID): AssetGUID[] {
const deps = this._dependencies.get(guid);
return deps ? Array.from(deps) : [];
}
/**
* Get asset dependents (assets that depend on this one)
* 获取资产的依赖者(依赖此资产的其他资产)
*/
getDependents(guid: AssetGUID): AssetGUID[] {
const deps = this._dependents.get(guid);
return deps ? Array.from(deps) : [];
}
/**
* Get all dependencies recursively
* 递归获取所有依赖
*/
getAllDependencies(guid: AssetGUID, visited = new Set<AssetGUID>()): AssetGUID[] {
if (visited.has(guid)) return [];
visited.add(guid);
const result: AssetGUID[] = [];
const directDeps = this.getDependencies(guid);
for (const dep of directDeps) {
result.push(dep);
const transitiveDeps = this.getAllDependencies(dep, visited);
result.push(...transitiveDeps);
}
return result;
}
/**
* Check for circular dependencies
* 检查循环依赖
*/
hasCircularDependency(guid: AssetGUID): boolean {
const visited = new Set<AssetGUID>();
const recursionStack = new Set<AssetGUID>();
const checkCycle = (current: AssetGUID): boolean => {
visited.add(current);
recursionStack.add(current);
const deps = this.getDependencies(current);
for (const dep of deps) {
if (!visited.has(dep)) {
if (checkCycle(dep)) return true;
} else if (recursionStack.has(dep)) {
return true;
}
}
recursionStack.delete(current);
return false;
};
return checkCycle(guid);
}
/**
* Update dependencies
* 更新依赖关系
*/
private updateDependencies(guid: AssetGUID, newDependencies: AssetGUID[]): void {
// 清除旧的依赖关系 / Clear old dependencies
this.clearDependencies(guid);
// 建立新的依赖关系 / Establish new dependencies
if (newDependencies.length > 0) {
this._dependencies.set(guid, new Set(newDependencies));
// 更新被依赖关系 / Update dependent relations
newDependencies.forEach((dep) => {
if (!this._dependents.has(dep)) {
this._dependents.set(dep, new Set());
}
this._dependents.get(dep)!.add(guid);
});
}
}
/**
* Clear dependencies
* 清除依赖关系
*/
private clearDependencies(guid: AssetGUID): void {
// 清除依赖 / Clear dependencies
const deps = this._dependencies.get(guid);
if (deps) {
deps.forEach((dep) => {
const dependents = this._dependents.get(dep);
if (dependents) {
dependents.delete(guid);
if (dependents.size === 0) {
this._dependents.delete(dep);
}
}
});
this._dependencies.delete(guid);
}
// 清除被依赖 / Clear dependents
const dependents = this._dependents.get(guid);
if (dependents) {
dependents.forEach((dependent) => {
const dependencies = this._dependencies.get(dependent);
if (dependencies) {
dependencies.delete(guid);
if (dependencies.size === 0) {
this._dependencies.delete(dependent);
}
}
});
this._dependents.delete(guid);
}
}
/**
* Get database statistics
* 获取数据库统计
*/
getStatistics(): {
totalAssets: number;
assetsByType: Map<AssetType, number>;
totalDependencies: number;
assetsWithDependencies: number;
circularDependencies: number;
} {
const assetsByType = new Map<AssetType, number>();
this._typeToGuids.forEach((guids, type) => {
assetsByType.set(type, guids.size);
});
let circularDependencies = 0;
this._metadata.forEach((_, guid) => {
if (this.hasCircularDependency(guid)) {
circularDependencies++;
}
});
return {
totalAssets: this._metadata.size,
assetsByType,
totalDependencies: Array.from(this._dependencies.values()).reduce(
(sum, deps) => sum + deps.size,
0
),
assetsWithDependencies: this._dependencies.size,
circularDependencies
};
}
/**
* Export to catalog entries
* 导出为目录条目
*/
exportToCatalog(): IAssetCatalogEntry[] {
const entries: IAssetCatalogEntry[] = [];
this._metadata.forEach((metadata) => {
entries.push({
guid: metadata.guid,
path: metadata.path,
type: metadata.type,
size: metadata.size,
hash: metadata.hash
});
});
return entries;
}
/**
* Clear database
* 清空数据库
*/
clear(): void {
this._metadata.clear();
this._pathToGuid.clear();
this._typeToGuids.clear();
this._labelToGuids.clear();
this._dependencies.clear();
this._dependents.clear();
}
}
@@ -0,0 +1,193 @@
/**
* Priority-based asset loading queue
* 基于优先级的资产加载队列
*/
import { AssetGUID, IAssetLoadOptions } from '../types/AssetTypes';
import { IAssetLoadQueue } from '../interfaces/IAssetManager';
/**
* Queue item
* 队列项
*/
interface QueueItem {
guid: AssetGUID;
priority: number;
options?: IAssetLoadOptions;
timestamp: number;
}
/**
* Asset load queue implementation
* 资产加载队列实现
*/
export class AssetLoadQueue implements IAssetLoadQueue {
private readonly _queue: QueueItem[] = [];
private readonly _guidToIndex = new Map<AssetGUID, number>();
/**
* Add to queue
* 添加到队列
*/
enqueue(guid: AssetGUID, priority: number, options?: IAssetLoadOptions): void {
// 检查是否已在队列中 / Check if already in queue
if (this._guidToIndex.has(guid)) {
this.reprioritize(guid, priority);
return;
}
const item: QueueItem = {
guid,
priority,
options,
timestamp: Date.now()
};
// 二分查找插入位置 / Binary search for insertion position
const index = this.findInsertIndex(priority);
this._queue.splice(index, 0, item);
// 更新索引映射 / Update index mapping
this.updateIndices(index);
}
/**
* Remove from queue
* 从队列移除
*/
dequeue(): { guid: AssetGUID; options?: IAssetLoadOptions } | null {
if (this._queue.length === 0) return null;
const item = this._queue.shift();
if (!item) return null;
// 更新索引映射 / Update index mapping
this._guidToIndex.delete(item.guid);
this.updateIndices(0);
return {
guid: item.guid,
options: item.options
};
}
/**
* Check if queue is empty
* 检查队列是否为空
*/
isEmpty(): boolean {
return this._queue.length === 0;
}
/**
* Get queue size
* 获取队列大小
*/
getSize(): number {
return this._queue.length;
}
/**
* Clear queue
* 清空队列
*/
clear(): void {
this._queue.length = 0;
this._guidToIndex.clear();
}
/**
* Reprioritize item
* 重新设置优先级
*/
reprioritize(guid: AssetGUID, newPriority: number): void {
const index = this._guidToIndex.get(guid);
if (index === undefined) return;
const item = this._queue[index];
if (!item || item.priority === newPriority) return;
// 移除旧项 / Remove old item
this._queue.splice(index, 1);
this._guidToIndex.delete(guid);
// 重新插入 / Reinsert with new priority
item.priority = newPriority;
const newIndex = this.findInsertIndex(newPriority);
this._queue.splice(newIndex, 0, item);
// 更新索引 / Update indices
this.updateIndices(Math.min(index, newIndex));
}
/**
* Find insertion index for priority
* 查找优先级的插入索引
*/
private findInsertIndex(priority: number): number {
let left = 0;
let right = this._queue.length;
while (left < right) {
const mid = Math.floor((left + right) / 2);
// 高优先级在前 / Higher priority first
if (this._queue[mid].priority >= priority) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
/**
* Update indices after modification
* 修改后更新索引
*/
private updateIndices(startIndex: number): void {
for (let i = startIndex; i < this._queue.length; i++) {
this._guidToIndex.set(this._queue[i].guid, i);
}
}
/**
* Get queue items (for debugging)
* 获取队列项(用于调试)
*/
getItems(): ReadonlyArray<{
guid: AssetGUID;
priority: number;
waitTime: number;
}> {
const now = Date.now();
return this._queue.map((item) => ({
guid: item.guid,
priority: item.priority,
waitTime: now - item.timestamp
}));
}
/**
* Remove specific item from queue
* 从队列中移除特定项
*/
remove(guid: AssetGUID): boolean {
const index = this._guidToIndex.get(guid);
if (index === undefined) return false;
this._queue.splice(index, 1);
this._guidToIndex.delete(guid);
this.updateIndices(index);
return true;
}
/**
* Check if guid is in queue
* 检查guid是否在队列中
*/
contains(guid: AssetGUID): boolean {
return this._guidToIndex.has(guid);
}
}
@@ -0,0 +1,541 @@
/**
* Asset manager implementation
* 资产管理器实现
*/
import {
AssetGUID,
AssetHandle,
AssetType,
AssetState,
IAssetLoadOptions,
IAssetLoadResult,
IAssetReferenceInfo,
IAssetPreloadGroup,
IAssetLoadProgress,
IAssetMetadata,
AssetLoadError,
IAssetCatalog
} from '../types/AssetTypes';
import {
IAssetManager,
IAssetLoadQueue
} from '../interfaces/IAssetManager';
import { IAssetLoader, IAssetLoaderFactory } from '../interfaces/IAssetLoader';
import { AssetCache } from './AssetCache';
import { AssetLoadQueue } from './AssetLoadQueue';
import { AssetLoaderFactory } from '../loaders/AssetLoaderFactory';
import { AssetDatabase } from './AssetDatabase';
/**
* Asset entry in the manager
* 管理器中的资产条目
*/
interface AssetEntry {
guid: AssetGUID;
handle: AssetHandle;
asset: unknown;
metadata: IAssetMetadata;
state: AssetState;
referenceCount: number;
lastAccessTime: number;
loadPromise?: Promise<IAssetLoadResult>;
}
/**
* Asset manager implementation
* 资产管理器实现
*/
export class AssetManager implements IAssetManager {
private readonly _assets = new Map<AssetGUID, AssetEntry>();
private readonly _handleToGuid = new Map<AssetHandle, AssetGUID>();
private readonly _pathToGuid = new Map<string, AssetGUID>();
private readonly _cache: AssetCache;
private readonly _loadQueue: IAssetLoadQueue;
private readonly _loaderFactory: IAssetLoaderFactory;
private readonly _database: AssetDatabase;
private _nextHandle: AssetHandle = 1;
private _statistics = {
loadedCount: 0,
failedCount: 0
};
private _isDisposed = false;
private _loadingCount = 0;
constructor(catalog?: IAssetCatalog) {
this._cache = new AssetCache();
this._loadQueue = new AssetLoadQueue();
this._loaderFactory = new AssetLoaderFactory();
this._database = new AssetDatabase();
// 如果提供了目录,初始化数据库 / Initialize database if catalog provided
if (catalog) {
this.initializeFromCatalog(catalog);
}
}
/**
* Initialize from catalog
* 从目录初始化
*/
private initializeFromCatalog(catalog: IAssetCatalog): void {
catalog.entries.forEach((entry, guid) => {
const metadata: IAssetMetadata = {
guid,
path: entry.path,
type: entry.type,
name: entry.path.split('/').pop() || '',
size: entry.size,
hash: entry.hash,
dependencies: [],
labels: [],
tags: new Map(),
lastModified: Date.now(),
version: 1
};
this._database.addAsset(metadata);
this._pathToGuid.set(entry.path, guid);
});
}
/**
* Load asset by GUID
* 通过GUID加载资产
*/
async loadAsset<T = unknown>(
guid: AssetGUID,
options?: IAssetLoadOptions
): Promise<IAssetLoadResult<T>> {
// 检查是否已加载 / Check if already loaded
const entry = this._assets.get(guid);
if (entry) {
if (entry.state === AssetState.Loaded && !options?.forceReload) {
entry.lastAccessTime = Date.now();
return {
asset: entry.asset as T,
handle: entry.handle,
metadata: entry.metadata,
loadTime: 0
};
}
if (entry.state === AssetState.Loading && entry.loadPromise) {
return entry.loadPromise as Promise<IAssetLoadResult<T>>;
}
}
// 获取元数据 / Get metadata
const metadata = this._database.getMetadata(guid);
if (!metadata) {
throw AssetLoadError.fileNotFound(guid, 'Unknown');
}
// 创建加载器 / Create loader
const loader = this._loaderFactory.createLoader(metadata.type);
if (!loader) {
throw AssetLoadError.unsupportedType(guid, metadata.type);
}
// 开始加载 / Start loading
const loadStartTime = performance.now();
const newEntry: AssetEntry = {
guid,
handle: this._nextHandle++,
asset: null,
metadata,
state: AssetState.Loading,
referenceCount: 0,
lastAccessTime: Date.now()
};
this._assets.set(guid, newEntry);
this._handleToGuid.set(newEntry.handle, guid);
this._loadingCount++;
// 创建加载Promise / Create loading promise
const loadPromise = this.performLoad<T>(loader, metadata, options, loadStartTime, newEntry);
newEntry.loadPromise = loadPromise;
try {
const result = await loadPromise;
return result;
} catch (error) {
this._statistics.failedCount++;
newEntry.state = AssetState.Failed;
throw error;
} finally {
this._loadingCount--;
delete newEntry.loadPromise;
}
}
/**
* Perform asset loading
* 执行资产加载
*/
private async performLoad<T>(
loader: IAssetLoader,
metadata: IAssetMetadata,
options: IAssetLoadOptions | undefined,
startTime: number,
entry: AssetEntry
): Promise<IAssetLoadResult<T>> {
// 加载依赖 / Load dependencies
if (metadata.dependencies.length > 0) {
await this.loadDependencies(metadata.dependencies, options);
}
// 执行加载 / Execute loading
const result = await loader.load(metadata.path, metadata, options);
// 更新条目 / Update entry
entry.asset = result.asset;
entry.state = AssetState.Loaded;
// 缓存资产 / Cache asset
this._cache.set(metadata.guid, result.asset);
// 更新统计 / Update statistics
this._statistics.loadedCount++;
const loadResult: IAssetLoadResult<T> = {
asset: result.asset as T,
handle: entry.handle,
metadata,
loadTime: performance.now() - startTime
};
return loadResult;
}
/**
* Load dependencies
* 加载依赖
*/
private async loadDependencies(
dependencies: AssetGUID[],
options?: IAssetLoadOptions
): Promise<void> {
const promises = dependencies.map((dep) => this.loadAsset(dep, options));
await Promise.all(promises);
}
/**
* Load asset by path
* 通过路径加载资产
*/
async loadAssetByPath<T = unknown>(
path: string,
options?: IAssetLoadOptions
): Promise<IAssetLoadResult<T>> {
const guid = this._pathToGuid.get(path);
if (!guid) {
// 尝试从数据库查找 / Try to find from database
let metadata = this._database.getMetadataByPath(path);
if (!metadata) {
// 动态创建元数据 / Create metadata dynamically
const fileExt = path.substring(path.lastIndexOf('.')).toLowerCase();
let assetType = AssetType.Custom;
// 根据文件扩展名确定资产类型 / Determine asset type by file extension
if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'].includes(fileExt)) {
assetType = AssetType.Texture;
} else if (['.json'].includes(fileExt)) {
assetType = AssetType.Json;
} else if (['.txt', '.md', '.xml', '.yaml'].includes(fileExt)) {
assetType = AssetType.Text;
}
// 生成唯一GUID / Generate unique GUID
const dynamicGuid = `dynamic_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
metadata = {
guid: dynamicGuid,
path: path,
type: assetType,
name: path.split('/').pop() || path.split('\\').pop() || 'unnamed',
size: 0, // 动态加载时未知大小 / Unknown size for dynamic loading
hash: '',
dependencies: [],
labels: [],
tags: new Map(),
lastModified: Date.now(),
version: 1
};
// 注册到数据库 / Register to database
this._database.addAsset(metadata);
this._pathToGuid.set(path, metadata.guid);
} else {
this._pathToGuid.set(path, metadata.guid);
}
return this.loadAsset<T>(metadata.guid, options);
}
return this.loadAsset<T>(guid, options);
}
/**
* Load multiple assets
* 批量加载资产
*/
async loadAssets(
guids: AssetGUID[],
options?: IAssetLoadOptions
): Promise<Map<AssetGUID, IAssetLoadResult>> {
const results = new Map<AssetGUID, IAssetLoadResult>();
// 并行加载所有资产 / Load all assets in parallel
const promises = guids.map(async (guid) => {
try {
const result = await this.loadAsset(guid, options);
results.set(guid, result);
} catch (error) {
console.error(`Failed to load asset ${guid}:`, error);
}
});
await Promise.all(promises);
return results;
}
/**
* Preload asset group
* 预加载资产组
*/
async preloadGroup(
group: IAssetPreloadGroup,
onProgress?: (progress: IAssetLoadProgress) => void
): Promise<void> {
const totalCount = group.assets.length;
let loadedCount = 0;
let loadedBytes = 0;
let totalBytes = 0;
// 计算总大小 / Calculate total size
for (const guid of group.assets) {
const metadata = this._database.getMetadata(guid);
if (metadata) {
totalBytes += metadata.size;
}
}
// 加载每个资产 / Load each asset
for (const guid of group.assets) {
const metadata = this._database.getMetadata(guid);
if (!metadata) continue;
if (onProgress) {
onProgress({
currentAsset: metadata.name,
loadedCount,
totalCount,
loadedBytes,
totalBytes,
progress: loadedCount / totalCount
});
}
await this.loadAsset(guid, { priority: group.priority });
loadedCount++;
loadedBytes += metadata.size;
}
// 最终进度 / Final progress
if (onProgress) {
onProgress({
currentAsset: '',
loadedCount: totalCount,
totalCount,
loadedBytes: totalBytes,
totalBytes,
progress: 1
});
}
}
/**
* Get loaded asset
* 获取已加载的资产
*/
getAsset<T = unknown>(guid: AssetGUID): T | null {
const entry = this._assets.get(guid);
if (entry && entry.state === AssetState.Loaded) {
entry.lastAccessTime = Date.now();
return entry.asset as T;
}
return null;
}
/**
* Get asset by handle
* 通过句柄获取资产
*/
getAssetByHandle<T = unknown>(handle: AssetHandle): T | null {
const guid = this._handleToGuid.get(handle);
if (!guid) return null;
return this.getAsset<T>(guid);
}
/**
* Check if asset is loaded
* 检查资产是否已加载
*/
isLoaded(guid: AssetGUID): boolean {
const entry = this._assets.get(guid);
return entry?.state === AssetState.Loaded;
}
/**
* Get asset state
* 获取资产状态
*/
getAssetState(guid: AssetGUID): AssetState {
const entry = this._assets.get(guid);
return entry?.state || AssetState.Unloaded;
}
/**
* Unload asset
* 卸载资产
*/
unloadAsset(guid: AssetGUID): void {
const entry = this._assets.get(guid);
if (!entry) return;
// 检查引用计数 / Check reference count
if (entry.referenceCount > 0) {
return;
}
// 获取加载器以释放资源 / Get loader to dispose resources
const loader = this._loaderFactory.createLoader(entry.metadata.type);
if (loader) {
loader.dispose(entry.asset);
}
// 清理条目 / Clean up entry
this._handleToGuid.delete(entry.handle);
this._assets.delete(guid);
this._cache.remove(guid);
// 更新统计 / Update statistics
this._statistics.loadedCount--;
entry.state = AssetState.Unloaded;
}
/**
* Unload all assets
* 卸载所有资产
*/
unloadAllAssets(): void {
const guids = Array.from(this._assets.keys());
guids.forEach((guid) => this.unloadAsset(guid));
}
/**
* Unload unused assets
* 卸载未使用的资产
*/
unloadUnusedAssets(): void {
const guids = Array.from(this._assets.keys());
guids.forEach((guid) => {
const entry = this._assets.get(guid);
if (entry && entry.referenceCount === 0) {
this.unloadAsset(guid);
}
});
}
/**
* Add reference to asset
* 增加资产引用
*/
addReference(guid: AssetGUID): void {
const entry = this._assets.get(guid);
if (entry) {
entry.referenceCount++;
}
}
/**
* Remove reference from asset
* 移除资产引用
*/
removeReference(guid: AssetGUID): void {
const entry = this._assets.get(guid);
if (entry && entry.referenceCount > 0) {
entry.referenceCount--;
}
}
/**
* Get reference info
* 获取引用信息
*/
getReferenceInfo(guid: AssetGUID): IAssetReferenceInfo | null {
const entry = this._assets.get(guid);
if (!entry) return null;
return {
guid,
handle: entry.handle,
referenceCount: entry.referenceCount,
lastAccessTime: entry.lastAccessTime,
state: entry.state
};
}
/**
* Register custom loader
* 注册自定义加载器
*/
registerLoader(type: AssetType, loader: IAssetLoader): void {
this._loaderFactory.registerLoader(type, loader);
}
/**
* Get asset statistics
* 获取资产统计信息
*/
getStatistics(): { loadedCount: number; loadQueue: number; failedCount: number } {
return {
loadedCount: this._statistics.loadedCount,
loadQueue: this._loadQueue.getSize(),
failedCount: this._statistics.failedCount
};
}
/**
* Clear cache
* 清空缓存
*/
clearCache(): void {
this._cache.clear();
}
/**
* Dispose manager
* 释放管理器
*/
dispose(): void {
if (this._isDisposed) return;
this.unloadAllAssets();
this._cache.clear();
this._loadQueue.clear();
this._assets.clear();
this._handleToGuid.clear();
this._pathToGuid.clear();
this._isDisposed = true;
}
}
@@ -0,0 +1,243 @@
/**
* Asset path resolver for different platforms and protocols
* 不同平台和协议的资产路径解析器
*/
import { AssetPlatform } from '../types/AssetTypes';
import { PathValidator } from '../utils/PathValidator';
/**
* Asset path resolver configuration
* 资产路径解析器配置
*/
export interface IAssetPathConfig {
/** Base URL for web assets | Web资产的基础URL */
baseUrl?: string;
/** Asset directory path | 资产目录路径 */
assetDir?: string;
/** Asset host for asset:// protocol | 资产协议的主机名 */
assetHost?: string;
/** Current platform | 当前平台 */
platform?: AssetPlatform;
/** Custom path transformer | 自定义路径转换器 */
pathTransformer?: (path: string) => string;
}
/**
* Asset path resolver
* 资产路径解析器
*/
export class AssetPathResolver {
private config: IAssetPathConfig;
constructor(config: IAssetPathConfig = {}) {
this.config = {
baseUrl: '',
assetDir: 'assets',
platform: AssetPlatform.H5,
...config
};
}
/**
* Update configuration
* 更新配置
*/
updateConfig(config: Partial<IAssetPathConfig>): void {
this.config = { ...this.config, ...config };
}
/**
* Resolve asset path to full URL
* 解析资产路径为完整URL
*/
resolve(path: string): string {
// Validate input path
const validation = PathValidator.validate(path);
if (!validation.valid) {
console.warn(`Invalid asset path: ${path} - ${validation.reason}`);
// Sanitize the path instead of throwing
path = PathValidator.sanitize(path);
if (!path) {
throw new Error(`Cannot resolve invalid path: ${validation.reason}`);
}
}
// Already a full URL
// 已经是完整URL
if (this.isAbsoluteUrl(path)) {
return path;
}
// Data URL
// 数据URL
if (path.startsWith('data:')) {
return path;
}
// Normalize the path
path = PathValidator.normalize(path);
// Apply custom transformer if provided
// 应用自定义转换器(如果提供)
if (this.config.pathTransformer) {
path = this.config.pathTransformer(path);
// Re-validate after transformation
const postTransform = PathValidator.validate(path);
if (!postTransform.valid) {
throw new Error(`Path transformer produced invalid path: ${postTransform.reason}`);
}
}
// Platform-specific resolution
// 平台特定解析
switch (this.config.platform) {
case AssetPlatform.H5:
return this.resolveH5Path(path);
case AssetPlatform.WeChat:
return this.resolveWeChatPath(path);
case AssetPlatform.Playable:
return this.resolvePlayablePath(path);
case AssetPlatform.Android:
case AssetPlatform.iOS:
return this.resolveMobilePath(path);
case AssetPlatform.Editor:
return this.resolveEditorPath(path);
default:
return this.resolveH5Path(path);
}
}
/**
* Resolve path for H5 platform
* 解析H5平台路径
*/
private resolveH5Path(path: string): string {
// Remove leading slash if present
// 移除开头的斜杠(如果存在)
path = path.replace(/^\//, '');
// Combine with base URL and asset directory
// 与基础URL和资产目录结合
const base = this.config.baseUrl || (typeof window !== 'undefined' ? window.location.origin : '');
const assetDir = this.config.assetDir || 'assets';
return `${base}/${assetDir}/${path}`.replace(/\/+/g, '/');
}
/**
* Resolve path for WeChat Mini Game
* 解析微信小游戏路径
*/
private resolveWeChatPath(path: string): string {
// WeChat mini games use relative paths
// 微信小游戏使用相对路径
return `${this.config.assetDir}/${path}`.replace(/\/+/g, '/');
}
/**
* Resolve path for Playable Ads platform
* 解析试玩广告平台路径
*/
private resolvePlayablePath(path: string): string {
// Playable ads typically use base64 embedded resources or relative paths
// 试玩广告通常使用base64内嵌资源或相对路径
// If custom transformer is provided (e.g., for base64 encoding)
// 如果提供了自定义转换器(例如用于base64编码)
if (this.config.pathTransformer) {
return this.config.pathTransformer(path);
}
// Default to relative path without directory prefix
// 默认使用不带目录前缀的相对路径
return path;
}
/**
* Resolve path for mobile platform (Android/iOS)
* 解析移动平台路径(Android/iOS
*/
private resolveMobilePath(path: string): string {
// Mobile platforms use relative paths or file:// protocol
// 移动平台使用相对路径或file://协议
return `./${this.config.assetDir}/${path}`.replace(/\/+/g, '/');
}
/**
* Resolve path for Editor platform (Tauri)
* 解析编辑器平台路径(Tauri)
*/
private resolveEditorPath(path: string): string {
// For Tauri editor, use pathTransformer if provided
// 对于Tauri编辑器,使用pathTransformer(如果提供)
if (this.config.pathTransformer) {
return this.config.pathTransformer(path);
}
// Use configurable asset host or default to 'localhost'
// 使用可配置的资产主机或默认为 'localhost'
const host = this.config.assetHost || 'localhost';
const sanitizedPath = PathValidator.sanitize(path);
return `asset://${host}/${sanitizedPath}`;
}
/**
* Check if path is absolute URL
* 检查路径是否为绝对URL
*/
private isAbsoluteUrl(path: string): boolean {
return /^(https?:\/\/|file:\/\/|asset:\/\/)/.test(path);
}
/**
* Get asset directory from path
* 从路径获取资产目录
*/
getAssetDirectory(path: string): string {
const resolved = this.resolve(path);
const lastSlash = resolved.lastIndexOf('/');
return lastSlash >= 0 ? resolved.substring(0, lastSlash) : '';
}
/**
* Get asset filename from path
* 从路径获取资产文件名
*/
getAssetFilename(path: string): string {
const resolved = this.resolve(path);
const lastSlash = resolved.lastIndexOf('/');
return lastSlash >= 0 ? resolved.substring(lastSlash + 1) : resolved;
}
/**
* Join paths
* 连接路径
*/
join(...paths: string[]): string {
return paths.join('/').replace(/\/+/g, '/');
}
/**
* Normalize path
* 规范化路径
*/
normalize(path: string): string {
return path.replace(/\\/g, '/').replace(/\/+/g, '/');
}
}
/**
* Global asset path resolver instance
* 全局资产路径解析器实例
*/
export const globalPathResolver = new AssetPathResolver();
@@ -0,0 +1,338 @@
/**
* Asset reference for lazy loading
* 用于懒加载的资产引用
*/
import { AssetGUID, IAssetLoadOptions, AssetState } from '../types/AssetTypes';
import { IAssetManager } from '../interfaces/IAssetManager';
/**
* Asset reference class for lazy loading
* 懒加载资产引用类
*/
export class AssetReference<T = unknown> {
private _guid: AssetGUID;
private _asset?: T;
private _loadPromise?: Promise<T>;
private _manager?: IAssetManager;
private _isReleased = false;
private _autoRelease = false;
/**
* Constructor
* 构造函数
*/
constructor(guid: AssetGUID, manager?: IAssetManager) {
this._guid = guid;
this._manager = manager;
}
/**
* Get asset GUID
* 获取资产GUID
*/
get guid(): AssetGUID {
return this._guid;
}
/**
* Check if asset is loaded
* 检查资产是否已加载
*/
get isLoaded(): boolean {
return this._asset !== undefined && !this._isReleased;
}
/**
* Get asset synchronously (returns null if not loaded)
* 同步获取资产(如果未加载则返回null)
*/
get asset(): T | null {
return this._asset ?? null;
}
/**
* Set asset manager
* 设置资产管理器
*/
setManager(manager: IAssetManager): void {
this._manager = manager;
}
/**
* Load asset asynchronously
* 异步加载资产
*/
async loadAsync(options?: IAssetLoadOptions): Promise<T> {
if (this._isReleased) {
throw new Error(`Asset reference ${this._guid} has been released`);
}
// 如果已经加载,直接返回 / Return if already loaded
if (this._asset !== undefined) {
return this._asset;
}
// 如果正在加载,返回加载Promise / Return loading promise if loading
if (this._loadPromise) {
return this._loadPromise;
}
if (!this._manager) {
throw new Error('Asset manager not set for AssetReference');
}
// 开始加载 / Start loading
this._loadPromise = this.performLoad(options);
try {
const asset = await this._loadPromise;
return asset;
} finally {
this._loadPromise = undefined;
}
}
/**
* Perform asset loading
* 执行资产加载
*/
private async performLoad(options?: IAssetLoadOptions): Promise<T> {
if (!this._manager) {
throw new Error('Asset manager not set');
}
const result = await this._manager.loadAsset<T>(this._guid, options);
this._asset = result.asset;
// 增加引用计数 / Increase reference count
this._manager.addReference(this._guid);
return this._asset;
}
/**
* Release asset reference
* 释放资产引用
*/
release(): void {
if (this._isReleased) return;
if (this._manager && this._asset !== undefined) {
// 减少引用计数 / Decrease reference count
this._manager.removeReference(this._guid);
// 如果引用计数为0,可以考虑卸载 / Consider unloading if reference count is 0
const refInfo = this._manager.getReferenceInfo(this._guid);
if (refInfo && refInfo.referenceCount === 0 && this._autoRelease) {
this._manager.unloadAsset(this._guid);
}
}
this._asset = undefined;
this._isReleased = true;
}
/**
* Set auto-release mode
* 设置自动释放模式
*/
setAutoRelease(autoRelease: boolean): void {
this._autoRelease = autoRelease;
}
/**
* Validate reference
* 验证引用
*/
validate(): boolean {
if (!this._manager) return false;
const state = this._manager.getAssetState(this._guid);
return state !== AssetState.Failed;
}
/**
* Get asset state
* 获取资产状态
*/
getState(): AssetState {
if (this._isReleased) return AssetState.Unloaded;
if (!this._manager) return AssetState.Unloaded;
return this._manager.getAssetState(this._guid);
}
/**
* Clone reference
* 克隆引用
*/
clone(): AssetReference<T> {
const newRef = new AssetReference<T>(this._guid, this._manager);
newRef.setAutoRelease(this._autoRelease);
return newRef;
}
/**
* Convert to JSON
* 转换为JSON
*/
toJSON(): { guid: AssetGUID } {
return { guid: this._guid };
}
/**
* Create from JSON
* 从JSON创建
*/
static fromJSON<T = unknown>(
json: { guid: AssetGUID },
manager?: IAssetManager
): AssetReference<T> {
return new AssetReference<T>(json.guid, manager);
}
}
/**
* Weak asset reference that doesn't prevent unloading
* 不阻止卸载的弱资产引用
*/
export class WeakAssetReference<T = unknown> {
private _guid: AssetGUID;
private _manager?: IAssetManager;
constructor(guid: AssetGUID, manager?: IAssetManager) {
this._guid = guid;
this._manager = manager;
}
/**
* Get asset GUID
* 获取资产GUID
*/
get guid(): AssetGUID {
return this._guid;
}
/**
* Try get asset without loading
* 尝试获取资产而不加载
*/
tryGet(): T | null {
if (!this._manager) return null;
return this._manager.getAsset<T>(this._guid);
}
/**
* Load asset if not loaded
* 如果未加载则加载资产
*/
async loadAsync(options?: IAssetLoadOptions): Promise<T> {
if (!this._manager) {
throw new Error('Asset manager not set');
}
const result = await this._manager.loadAsset<T>(this._guid, options);
// 不增加引用计数 / Don't increase reference count for weak reference
return result.asset;
}
/**
* Check if asset is loaded
* 检查资产是否已加载
*/
isLoaded(): boolean {
if (!this._manager) return false;
return this._manager.isLoaded(this._guid);
}
/**
* Set asset manager
* 设置资产管理器
*/
setManager(manager: IAssetManager): void {
this._manager = manager;
}
}
/**
* Asset reference array for managing multiple references
* 用于管理多个引用的资产引用数组
*/
export class AssetReferenceArray<T = unknown> {
private _references: AssetReference<T>[] = [];
private _manager?: IAssetManager;
constructor(guids: AssetGUID[] = [], manager?: IAssetManager) {
this._manager = manager;
this._references = guids.map((guid) => new AssetReference<T>(guid, manager));
}
/**
* Add reference
* 添加引用
*/
add(guid: AssetGUID): void {
this._references.push(new AssetReference<T>(guid, this._manager));
}
/**
* Remove reference
* 移除引用
*/
remove(guid: AssetGUID): boolean {
const index = this._references.findIndex((ref) => ref.guid === guid);
if (index >= 0) {
this._references[index].release();
this._references.splice(index, 1);
return true;
}
return false;
}
/**
* Load all assets
* 加载所有资产
*/
async loadAllAsync(options?: IAssetLoadOptions): Promise<T[]> {
const promises = this._references.map((ref) => ref.loadAsync(options));
return Promise.all(promises);
}
/**
* Release all references
* 释放所有引用
*/
releaseAll(): void {
this._references.forEach((ref) => ref.release());
this._references = [];
}
/**
* Get all loaded assets
* 获取所有已加载的资产
*/
getLoadedAssets(): T[] {
return this._references
.filter((ref) => ref.isLoaded)
.map((ref) => ref.asset!)
.filter((asset) => asset !== null);
}
/**
* Get reference count
* 获取引用数量
*/
get count(): number {
return this._references.length;
}
/**
* Set asset manager
* 设置资产管理器
*/
setManager(manager: IAssetManager): void {
this._manager = manager;
this._references.forEach((ref) => ref.setManager(manager));
}
}