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:
207
packages/editor-app/src/hooks/useAssetSystem.ts
Normal file
207
packages/editor-app/src/hooks/useAssetSystem.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Asset system integration hook
|
||||
* 资产系统集成Hook
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import {
|
||||
AssetManager,
|
||||
AssetGUID,
|
||||
IAssetLoadProgress,
|
||||
AssetReference,
|
||||
EngineIntegration
|
||||
} from '@esengine/asset-system';
|
||||
|
||||
/**
|
||||
* Asset system hook
|
||||
* 资产系统Hook
|
||||
*/
|
||||
export function useAssetSystem() {
|
||||
const [assetManager, setAssetManager] = useState<AssetManager | null>(null);
|
||||
const [engineIntegration, setEngineIntegration] = useState<EngineIntegration | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loadProgress, setLoadProgress] = useState<IAssetLoadProgress | null>(null);
|
||||
const loadingCountRef = useRef(0);
|
||||
|
||||
/**
|
||||
* Initialize asset system
|
||||
* 初始化资产系统
|
||||
*/
|
||||
useEffect(() => {
|
||||
// 创建资产管理器 / Create asset manager
|
||||
const manager = new AssetManager();
|
||||
|
||||
setAssetManager(manager);
|
||||
|
||||
// 创建引擎集成 / Create engine integration
|
||||
const integration = new EngineIntegration(manager);
|
||||
setEngineIntegration(integration);
|
||||
|
||||
return () => {
|
||||
if (assetManager) {
|
||||
assetManager.dispose();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Load asset by path
|
||||
* 通过路径加载资产
|
||||
*/
|
||||
const loadAssetByPath = useCallback(async <T = unknown>(path: string): Promise<T | null> => {
|
||||
if (!assetManager) return null;
|
||||
|
||||
try {
|
||||
loadingCountRef.current++;
|
||||
setIsLoading(true);
|
||||
|
||||
const result = await assetManager.loadAssetByPath<T>(path, {
|
||||
onProgress: (progress) => {
|
||||
setLoadProgress({
|
||||
currentAsset: path,
|
||||
loadedCount: Math.floor(progress * 100),
|
||||
totalCount: 100,
|
||||
loadedBytes: 0,
|
||||
totalBytes: 0,
|
||||
progress
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result.asset;
|
||||
} catch (error) {
|
||||
console.error(`Failed to load asset at ${path}:`, error);
|
||||
return null;
|
||||
} finally {
|
||||
loadingCountRef.current--;
|
||||
if (loadingCountRef.current === 0) {
|
||||
setIsLoading(false);
|
||||
setLoadProgress(null);
|
||||
}
|
||||
}
|
||||
}, [assetManager]);
|
||||
|
||||
/**
|
||||
* Load texture for sprite component
|
||||
* 为精灵组件加载纹理
|
||||
*/
|
||||
const loadTextureForSprite = useCallback(async (path: string): Promise<number> => {
|
||||
if (!engineIntegration) return 0;
|
||||
|
||||
try {
|
||||
return await engineIntegration.loadTextureForComponent(path);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load texture ${path}:`, error);
|
||||
return 0;
|
||||
}
|
||||
}, [engineIntegration]);
|
||||
|
||||
/**
|
||||
* Create asset reference
|
||||
* 创建资产引用
|
||||
*/
|
||||
const createAssetReference = useCallback((guid: AssetGUID): AssetReference | null => {
|
||||
if (!assetManager) return null;
|
||||
return new AssetReference(guid, assetManager);
|
||||
}, [assetManager]);
|
||||
|
||||
/**
|
||||
* Unload unused assets
|
||||
* 卸载未使用的资产
|
||||
*/
|
||||
const unloadUnusedAssets = useCallback(() => {
|
||||
if (!assetManager) return;
|
||||
assetManager.unloadUnusedAssets();
|
||||
}, [assetManager]);
|
||||
|
||||
|
||||
/**
|
||||
* Get statistics
|
||||
* 获取统计信息
|
||||
*/
|
||||
const getStatistics = useCallback(() => {
|
||||
if (!assetManager) {
|
||||
return { loadedCount: 0, loadQueue: 0, failedCount: 0 };
|
||||
}
|
||||
return assetManager.getStatistics();
|
||||
}, [assetManager]);
|
||||
|
||||
return {
|
||||
assetManager,
|
||||
engineIntegration,
|
||||
isLoading,
|
||||
loadProgress,
|
||||
loadAssetByPath,
|
||||
loadTextureForSprite,
|
||||
createAssetReference,
|
||||
unloadUnusedAssets,
|
||||
getStatistics
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Asset reference hook
|
||||
* 资产引用Hook
|
||||
*/
|
||||
export function useAssetReference<T = unknown>(
|
||||
reference: AssetReference<T> | null,
|
||||
autoLoad = false
|
||||
) {
|
||||
const [asset, setAsset] = useState<T | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
// 自动加载 / Auto load
|
||||
useEffect(() => {
|
||||
if (autoLoad && reference) {
|
||||
loadAsset();
|
||||
}
|
||||
}, [reference, autoLoad]);
|
||||
|
||||
/**
|
||||
* Load asset
|
||||
* 加载资产
|
||||
*/
|
||||
const loadAsset = useCallback(async () => {
|
||||
if (!reference) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
const loadedAsset = await reference.loadAsync();
|
||||
setAsset(loadedAsset);
|
||||
} catch (err) {
|
||||
setError(err as Error);
|
||||
console.error('Failed to load asset reference:', err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [reference]);
|
||||
|
||||
/**
|
||||
* Release asset
|
||||
* 释放资产
|
||||
*/
|
||||
const release = useCallback(() => {
|
||||
if (!reference) return;
|
||||
reference.release();
|
||||
setAsset(null);
|
||||
}, [reference]);
|
||||
|
||||
// 清理 / Cleanup
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (reference && reference.isLoaded) {
|
||||
reference.release();
|
||||
}
|
||||
};
|
||||
}, [reference]);
|
||||
|
||||
return {
|
||||
asset,
|
||||
isLoading,
|
||||
error,
|
||||
load: loadAsset,
|
||||
release
|
||||
};
|
||||
}
|
||||
@@ -3,8 +3,17 @@
|
||||
* 使用Rust游戏引擎的React钩子。
|
||||
*/
|
||||
|
||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import { useRef, useState, useCallback, useEffect } from 'react';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { MessageHub, EntityStoreService } from '@esengine/editor-core';
|
||||
import { TransformComponent, CameraComponent } from '@esengine/ecs-components';
|
||||
import { EngineService } from '../services/EngineService';
|
||||
import { EditorEngineSync } from '../services/EditorEngineSync';
|
||||
|
||||
// Module-level initialization tracking (outside React lifecycle)
|
||||
// 模块级别的初始化追踪(在React生命周期外部)
|
||||
let engineInitialized = false;
|
||||
let engineInitializing = false;
|
||||
|
||||
export interface EngineState {
|
||||
initialized: boolean;
|
||||
@@ -27,6 +36,66 @@ export interface UseEngineReturn {
|
||||
height?: number;
|
||||
}) => void;
|
||||
loadTexture: (id: number, url: string) => void;
|
||||
viewportId: string;
|
||||
}
|
||||
|
||||
export interface UseEngineOptions {
|
||||
viewportId: string;
|
||||
canvasId: string;
|
||||
showGrid?: boolean;
|
||||
showGizmos?: boolean;
|
||||
autoInit?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize engine once at module level
|
||||
* 在模块级别初始化引擎一次
|
||||
*/
|
||||
async function initializeEngine(canvasId: string): Promise<void> {
|
||||
if (engineInitialized || engineInitializing) {
|
||||
return;
|
||||
}
|
||||
|
||||
engineInitializing = true;
|
||||
|
||||
try {
|
||||
const engine = EngineService.getInstance();
|
||||
await engine.initialize(canvasId);
|
||||
|
||||
// Initialize sync service
|
||||
try {
|
||||
const messageHub = Core.services.resolve(MessageHub);
|
||||
const entityStore = Core.services.resolve(EntityStoreService);
|
||||
if (messageHub && entityStore) {
|
||||
EditorEngineSync.getInstance().initialize(messageHub, entityStore);
|
||||
|
||||
// Create default camera if none exists
|
||||
// 如果不存在相机则创建默认相机
|
||||
const scene = Core.scene;
|
||||
if (scene) {
|
||||
const existingCameras = scene.entities.findEntitiesWithComponent(CameraComponent);
|
||||
if (existingCameras.length === 0) {
|
||||
const cameraEntity = scene.createEntity('Main Camera');
|
||||
cameraEntity.addComponent(new TransformComponent());
|
||||
const camera = new CameraComponent();
|
||||
camera.orthographicSize = 1;
|
||||
cameraEntity.addComponent(camera);
|
||||
|
||||
// Register with EntityStore so it appears in hierarchy
|
||||
// 注册到 EntityStore 以便在层级视图中显示
|
||||
entityStore.addEntity(cameraEntity);
|
||||
messageHub.publish('entity:added', { entity: cameraEntity });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (syncError) {
|
||||
console.warn('Failed to initialize sync service | 同步服务初始化失败:', syncError);
|
||||
}
|
||||
|
||||
engineInitialized = true;
|
||||
} finally {
|
||||
engineInitializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,12 +105,34 @@ export interface UseEngineReturn {
|
||||
* @param canvasId - Canvas element ID | Canvas元素ID
|
||||
* @param autoInit - Whether to auto-initialize | 是否自动初始化
|
||||
*/
|
||||
export function useEngine(canvasId: string, autoInit = true): UseEngineReturn {
|
||||
export function useEngine(canvasId: string, autoInit?: boolean): UseEngineReturn;
|
||||
export function useEngine(options: UseEngineOptions): UseEngineReturn;
|
||||
export function useEngine(
|
||||
canvasIdOrOptions: string | UseEngineOptions,
|
||||
autoInit?: boolean
|
||||
): UseEngineReturn {
|
||||
// Parse options
|
||||
const options: UseEngineOptions = typeof canvasIdOrOptions === 'string'
|
||||
? {
|
||||
viewportId: canvasIdOrOptions, // Use canvasId as viewportId for backward compatibility
|
||||
canvasId: canvasIdOrOptions,
|
||||
showGrid: true,
|
||||
showGizmos: true,
|
||||
autoInit
|
||||
}
|
||||
: {
|
||||
showGrid: true,
|
||||
showGizmos: true,
|
||||
autoInit: true,
|
||||
...canvasIdOrOptions
|
||||
};
|
||||
|
||||
const engineRef = useRef<EngineService>(EngineService.getInstance());
|
||||
const statsIntervalRef = useRef<number | null>(null);
|
||||
const viewportRegisteredRef = useRef(false);
|
||||
|
||||
const [state, setState] = useState<EngineState>({
|
||||
initialized: false,
|
||||
initialized: engineInitialized,
|
||||
running: false,
|
||||
fps: 0,
|
||||
drawCalls: 0,
|
||||
@@ -49,28 +140,42 @@ export function useEngine(canvasId: string, autoInit = true): UseEngineReturn {
|
||||
error: null
|
||||
});
|
||||
|
||||
// Initialize engine | 初始化引擎
|
||||
// Initialize engine and register viewport
|
||||
useEffect(() => {
|
||||
if (!autoInit) return;
|
||||
if (!options.autoInit) return;
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
await engineRef.current.initialize(canvasId);
|
||||
setState(prev => ({ ...prev, initialized: true, error: null }));
|
||||
// Initialize engine with primary canvas (first viewport)
|
||||
await initializeEngine(options.canvasId);
|
||||
setState((prev) => ({ ...prev, initialized: true, error: null }));
|
||||
|
||||
// Start stats update interval | 启动统计更新间隔
|
||||
statsIntervalRef.current = window.setInterval(() => {
|
||||
const stats = engineRef.current.getStats();
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
fps: stats.fps,
|
||||
drawCalls: stats.drawCalls,
|
||||
spriteCount: stats.spriteCount
|
||||
}));
|
||||
}, 100);
|
||||
// Register this viewport
|
||||
if (!viewportRegisteredRef.current) {
|
||||
engineRef.current.registerViewport(options.viewportId, options.canvasId);
|
||||
engineRef.current.setViewportConfig(
|
||||
options.viewportId,
|
||||
options.showGrid ?? true,
|
||||
options.showGizmos ?? true
|
||||
);
|
||||
viewportRegisteredRef.current = true;
|
||||
}
|
||||
|
||||
// Start stats update interval
|
||||
if (!statsIntervalRef.current) {
|
||||
statsIntervalRef.current = window.setInterval(() => {
|
||||
const stats = engineRef.current.getStats();
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
fps: stats.fps,
|
||||
drawCalls: stats.drawCalls,
|
||||
spriteCount: stats.spriteCount
|
||||
}));
|
||||
}, 100);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize engine | 引擎初始化失败:', error);
|
||||
setState(prev => ({
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
}));
|
||||
@@ -82,24 +187,29 @@ export function useEngine(canvasId: string, autoInit = true): UseEngineReturn {
|
||||
return () => {
|
||||
if (statsIntervalRef.current) {
|
||||
clearInterval(statsIntervalRef.current);
|
||||
statsIntervalRef.current = null;
|
||||
}
|
||||
// Unregister viewport on cleanup
|
||||
if (viewportRegisteredRef.current) {
|
||||
engineRef.current.unregisterViewport(options.viewportId);
|
||||
viewportRegisteredRef.current = false;
|
||||
}
|
||||
engineRef.current.dispose();
|
||||
};
|
||||
}, [canvasId, autoInit]);
|
||||
}, [options.canvasId, options.viewportId, options.autoInit, options.showGrid, options.showGizmos]);
|
||||
|
||||
// Start engine | 启动引擎
|
||||
// Start engine
|
||||
const start = useCallback(() => {
|
||||
engineRef.current.start();
|
||||
setState(prev => ({ ...prev, running: true }));
|
||||
setState((prev) => ({ ...prev, running: true }));
|
||||
}, []);
|
||||
|
||||
// Stop engine | 停止引擎
|
||||
// Stop engine
|
||||
const stop = useCallback(() => {
|
||||
engineRef.current.stop();
|
||||
setState(prev => ({ ...prev, running: false }));
|
||||
setState((prev) => ({ ...prev, running: false }));
|
||||
}, []);
|
||||
|
||||
// Create sprite entity | 创建精灵实体
|
||||
// Create sprite entity
|
||||
const createSprite = useCallback((name: string, options?: {
|
||||
x?: number;
|
||||
y?: number;
|
||||
@@ -110,7 +220,7 @@ export function useEngine(canvasId: string, autoInit = true): UseEngineReturn {
|
||||
engineRef.current.createSpriteEntity(name, options);
|
||||
}, []);
|
||||
|
||||
// Load texture | 加载纹理
|
||||
// Load texture
|
||||
const loadTexture = useCallback((id: number, url: string) => {
|
||||
engineRef.current.loadTexture(id, url);
|
||||
}, []);
|
||||
@@ -120,7 +230,8 @@ export function useEngine(canvasId: string, autoInit = true): UseEngineReturn {
|
||||
start,
|
||||
stop,
|
||||
createSprite,
|
||||
loadTexture
|
||||
loadTexture,
|
||||
viewportId: options.viewportId
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user