Files
esengine/packages/editor-app/src/services/EditorEngineSync.ts

332 lines
11 KiB
TypeScript
Raw Normal View History

/**
* 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;