Files
esengine/packages/editor-app/src/services/EditorEngineSync.ts
yhh 574b4d08a3 refactor(editor-app): 编辑器服务和组件优化
- EngineService 改进引擎集成
- EditorEngineSync 同步优化
- AssetFileInspector 改进
- VectorFieldEditors 优化
- InstantiatePrefabCommand 改进
2025-12-16 11:28:50 +08:00

341 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 } from '@esengine/engine-core';
import { SpriteComponent, SpriteAnimatorComponent } from '@esengine/sprite';
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.textureGuid) {
// Trigger texture loading
bridge.getOrLoadTextureByPath(frame.textureGuid);
}
}
}
// 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.textureGuid && spriteComponent) {
spriteComponent.textureGuid = firstFrame.textureGuid;
}
}
}
}
}
// 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.textureGuid) {
bridge.getOrLoadTextureByPath(frame.textureGuid);
}
}
}
// 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.textureGuid) {
sprite.textureGuid = firstFrame.textureGuid;
}
}
}
}
}
/**
* 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.
* 更新引擎实体的精灵。
*
* Preloads textures when textureGuid changes to ensure they're available for rendering.
* 当 textureGuid 变更时预加载纹理以确保渲染时可用。
*/
private updateSprite(entity: Entity, sprite: SpriteComponent, property: string, value: any): void {
// When textureGuid changes, trigger texture preload
// 当 textureGuid 变更时,触发纹理预加载
if (property === 'textureGuid' && value) {
const bridge = this.engineService.getBridge();
if (bridge) {
// Preload the texture so it's ready for the next render frame
// 预加载纹理以便下一渲染帧时可用
bridge.getOrLoadTextureByPath(value);
}
}
}
/**
* 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;