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

View 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
};
}

View File

@@ -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
};
}