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:
331
packages/editor-app/src/services/EditorEngineSync.ts
Normal file
331
packages/editor-app/src/services/EditorEngineSync.ts
Normal file
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* Editor-Engine Sync Service
|
||||
* 编辑器-引擎同步服务
|
||||
*
|
||||
* Synchronizes editor entities to Rust engine for rendering.
|
||||
* 将编辑器实体同步到Rust引擎进行渲染。
|
||||
*/
|
||||
|
||||
import { Entity, Component } from '@esengine/ecs-framework';
|
||||
import { MessageHub, EntityStoreService } from '@esengine/editor-core';
|
||||
import { TransformComponent, SpriteComponent, SpriteAnimatorComponent } from '@esengine/ecs-components';
|
||||
import { EngineService } from './EngineService';
|
||||
|
||||
export class EditorEngineSync {
|
||||
private static instance: EditorEngineSync | null = null;
|
||||
|
||||
private engineService: EngineService;
|
||||
private messageHub: MessageHub | null = null;
|
||||
private entityStore: EntityStoreService | null = null;
|
||||
|
||||
// Track synced entities: editor entity id -> engine entity id
|
||||
private syncedEntities: Map<number, Entity> = new Map();
|
||||
|
||||
// Subscription IDs
|
||||
private subscriptions: Array<() => void> = [];
|
||||
|
||||
private initialized = false;
|
||||
|
||||
private constructor() {
|
||||
this.engineService = EngineService.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance.
|
||||
* 获取单例实例。
|
||||
*/
|
||||
static getInstance(): EditorEngineSync {
|
||||
if (!EditorEngineSync.instance) {
|
||||
EditorEngineSync.instance = new EditorEngineSync();
|
||||
}
|
||||
return EditorEngineSync.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize sync service.
|
||||
* 初始化同步服务。
|
||||
*/
|
||||
initialize(messageHub: MessageHub, entityStore: EntityStoreService): void {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageHub = messageHub;
|
||||
this.entityStore = entityStore;
|
||||
|
||||
// Subscribe to entity events
|
||||
this.subscribeToEvents();
|
||||
|
||||
// Sync existing entities
|
||||
this.syncAllEntities();
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to MessageHub events.
|
||||
* 订阅MessageHub事件。
|
||||
*/
|
||||
private subscribeToEvents(): void {
|
||||
if (!this.messageHub) return;
|
||||
|
||||
// Entity added
|
||||
const unsubAdd = this.messageHub.subscribe('entity:added', (data: { entity: Entity }) => {
|
||||
this.syncEntity(data.entity);
|
||||
});
|
||||
this.subscriptions.push(unsubAdd);
|
||||
|
||||
// Entity removed
|
||||
const unsubRemove = this.messageHub.subscribe('entity:removed', (data: { entity: Entity }) => {
|
||||
this.removeEntityFromEngine(data.entity);
|
||||
});
|
||||
this.subscriptions.push(unsubRemove);
|
||||
|
||||
// Component property changed - need to re-sync entity
|
||||
const unsubComponent = this.messageHub.subscribe('component:property:changed', (data: { entity: Entity; component: Component; propertyName: string; value: any }) => {
|
||||
this.updateEntityInEngine(data.entity, data.component, data.propertyName, data.value);
|
||||
});
|
||||
this.subscriptions.push(unsubComponent);
|
||||
|
||||
// Component added - sync entity if it has sprite
|
||||
const unsubComponentAdded = this.messageHub.subscribe('component:added', (data: { entity: Entity; component: Component }) => {
|
||||
this.syncEntity(data.entity);
|
||||
});
|
||||
this.subscriptions.push(unsubComponentAdded);
|
||||
|
||||
// Entities cleared
|
||||
const unsubClear = this.messageHub.subscribe('entities:cleared', () => {
|
||||
this.clearAllFromEngine();
|
||||
});
|
||||
this.subscriptions.push(unsubClear);
|
||||
|
||||
// Entity selected - update gizmo display
|
||||
const unsubSelected = this.messageHub.subscribe('entity:selected', (data: { entity: Entity | null }) => {
|
||||
this.updateSelectedEntity(data.entity);
|
||||
});
|
||||
this.subscriptions.push(unsubSelected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update selected entity for gizmo display.
|
||||
* 更新选中的实体用于Gizmo显示。
|
||||
*/
|
||||
private updateSelectedEntity(entity: Entity | null): void {
|
||||
if (entity) {
|
||||
this.engineService.setSelectedEntityIds([entity.id]);
|
||||
} else {
|
||||
this.engineService.setSelectedEntityIds([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all existing entities.
|
||||
* 同步所有现有实体。
|
||||
*/
|
||||
private syncAllEntities(): void {
|
||||
if (!this.entityStore) return;
|
||||
|
||||
const entities = this.entityStore.getAllEntities();
|
||||
for (const entity of entities) {
|
||||
this.syncEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a single entity to engine.
|
||||
* 将单个实体同步到引擎。
|
||||
*
|
||||
* Note: Texture loading is now handled automatically by EngineRenderSystem
|
||||
* via Rust engine's path-based texture loading.
|
||||
* 注意:纹理加载现在由EngineRenderSystem通过Rust引擎的路径加载自动处理。
|
||||
*/
|
||||
private syncEntity(entity: Entity): void {
|
||||
// Check if entity has sprite component
|
||||
const spriteComponent = entity.getComponent(SpriteComponent);
|
||||
if (!spriteComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Preload animator textures and set first frame
|
||||
// 预加载动画纹理并设置第一帧
|
||||
const animator = entity.getComponent(SpriteAnimatorComponent);
|
||||
if (animator && animator.clips) {
|
||||
const bridge = this.engineService.getBridge();
|
||||
if (bridge) {
|
||||
for (const clip of animator.clips) {
|
||||
for (const frame of clip.frames) {
|
||||
if (frame.texture) {
|
||||
// Trigger texture loading
|
||||
bridge.getOrLoadTextureByPath(frame.texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set sprite texture to first frame (static preview in editor)
|
||||
// 设置精灵纹理为第一帧(编辑器中的静态预览)
|
||||
if (animator.clips && animator.clips.length > 0) {
|
||||
const firstClip = animator.clips[0];
|
||||
if (firstClip && firstClip.frames && firstClip.frames.length > 0) {
|
||||
const firstFrame = firstClip.frames[0];
|
||||
if (firstFrame && firstFrame.texture && spriteComponent) {
|
||||
spriteComponent.texture = firstFrame.texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track synced entity
|
||||
this.syncedEntities.set(entity.id, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entity from tracking.
|
||||
* 从跟踪中移除实体。
|
||||
*/
|
||||
private removeEntityFromEngine(entity: Entity): void {
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
// Just remove from tracking, entity destruction is handled by the command
|
||||
this.syncedEntities.delete(entity.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update entity in engine when component changes.
|
||||
* 当组件变化时更新引擎中的实体。
|
||||
*/
|
||||
private updateEntityInEngine(entity: Entity, component: Component, propertyName: string, value: any): void {
|
||||
const engineEntity = this.syncedEntities.get(entity.id);
|
||||
if (!engineEntity) {
|
||||
// Entity not synced yet, try to sync it
|
||||
this.syncEntity(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update based on component type
|
||||
if (component instanceof TransformComponent) {
|
||||
this.updateTransform(engineEntity, component);
|
||||
} else if (component instanceof SpriteComponent) {
|
||||
this.updateSprite(engineEntity, component, propertyName, value);
|
||||
} else if (component instanceof SpriteAnimatorComponent) {
|
||||
this.updateAnimator(engineEntity, component, propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update animator - preload textures and set initial frame.
|
||||
* 更新动画器 - 预加载纹理并设置初始帧。
|
||||
*/
|
||||
private updateAnimator(entity: Entity, animator: SpriteAnimatorComponent, propertyName: string): void {
|
||||
// In editor mode, only preload textures and show first frame (no animation playback)
|
||||
// 编辑模式下只预加载纹理并显示第一帧(不播放动画)
|
||||
const bridge = this.engineService.getBridge();
|
||||
const sprite = entity.getComponent(SpriteComponent);
|
||||
|
||||
if (bridge && animator.clips) {
|
||||
// Preload all frame textures
|
||||
for (const clip of animator.clips) {
|
||||
for (const frame of clip.frames) {
|
||||
if (frame.texture) {
|
||||
bridge.getOrLoadTextureByPath(frame.texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set sprite texture to first frame if available (static preview in editor)
|
||||
// 设置精灵纹理为第一帧(编辑器中的静态预览)
|
||||
if (sprite && animator.clips && animator.clips.length > 0) {
|
||||
const firstClip = animator.clips[0];
|
||||
if (firstClip && firstClip.frames && firstClip.frames.length > 0) {
|
||||
const firstFrame = firstClip.frames[0];
|
||||
if (firstFrame && firstFrame.texture) {
|
||||
sprite.texture = firstFrame.texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update transform in engine entity.
|
||||
* 更新引擎实体的变换。
|
||||
*/
|
||||
private updateTransform(engineEntity: Entity, transform: TransformComponent): void {
|
||||
// Get engine transform component (same type as editor)
|
||||
const engineTransform = engineEntity.getComponent(TransformComponent);
|
||||
if (engineTransform) {
|
||||
engineTransform.position = {
|
||||
x: transform.position?.x ?? 0,
|
||||
y: transform.position?.y ?? 0,
|
||||
z: transform.position?.z ?? 0
|
||||
};
|
||||
engineTransform.rotation = {
|
||||
x: transform.rotation?.x ?? 0,
|
||||
y: transform.rotation?.y ?? 0,
|
||||
z: transform.rotation?.z ?? 0
|
||||
};
|
||||
engineTransform.scale = {
|
||||
x: transform.scale?.x ?? 1,
|
||||
y: transform.scale?.y ?? 1,
|
||||
z: transform.scale?.z ?? 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update sprite in engine entity.
|
||||
* 更新引擎实体的精灵。
|
||||
*
|
||||
* Note: Texture loading is now handled automatically by EngineRenderSystem.
|
||||
* 注意:纹理加载现在由EngineRenderSystem自动处理。
|
||||
*/
|
||||
private updateSprite(entity: Entity, sprite: SpriteComponent, property: string, value: any): void {
|
||||
// No manual texture loading needed - EngineRenderSystem handles it
|
||||
// 不需要手动加载纹理 - EngineRenderSystem会处理
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all synced entities from tracking.
|
||||
* 清除所有已同步实体的跟踪。
|
||||
*/
|
||||
private clearAllFromEngine(): void {
|
||||
// Just clear tracking, entity destruction is handled elsewhere
|
||||
this.syncedEntities.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if initialized.
|
||||
* 检查是否已初始化。
|
||||
*/
|
||||
isInitialized(): boolean {
|
||||
return this.initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get synced entity count.
|
||||
* 获取已同步实体数量。
|
||||
*/
|
||||
getSyncedCount(): number {
|
||||
return this.syncedEntities.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose sync service.
|
||||
* 释放同步服务。
|
||||
*/
|
||||
dispose(): void {
|
||||
// Unsubscribe from all events
|
||||
for (const unsub of this.subscriptions) {
|
||||
unsub();
|
||||
}
|
||||
this.subscriptions = [];
|
||||
|
||||
// Clear synced entities
|
||||
this.syncedEntities.clear();
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditorEngineSync;
|
||||
Reference in New Issue
Block a user