diff --git a/packages/core/src/Utils/Debug/DebugManager.ts b/packages/core/src/Utils/Debug/DebugManager.ts index 4cd36a0b..92a57aca 100644 --- a/packages/core/src/Utils/Debug/DebugManager.ts +++ b/packages/core/src/Utils/Debug/DebugManager.ts @@ -206,7 +206,8 @@ export class DebugManager implements IService, IUpdatable { return; } - const expandedData = this.entityCollector.expandLazyObject(entityId, componentIndex, propertyPath); + const scene = this.sceneManager.currentScene; + const expandedData = this.entityCollector.expandLazyObject(entityId, componentIndex, propertyPath, scene); this.webSocketManager.send({ type: 'expand_lazy_object_response', @@ -238,7 +239,8 @@ export class DebugManager implements IService, IUpdatable { return; } - const properties = this.entityCollector.getComponentProperties(entityId, componentIndex); + const scene = this.sceneManager.currentScene; + const properties = this.entityCollector.getComponentProperties(entityId, componentIndex, scene); this.webSocketManager.send({ type: 'get_component_properties_response', @@ -261,7 +263,8 @@ export class DebugManager implements IService, IUpdatable { try { const { requestId } = message; - const rawEntityList = this.entityCollector.getRawEntityList(); + const scene = this.sceneManager.currentScene; + const rawEntityList = this.entityCollector.getRawEntityList(scene); this.webSocketManager.send({ type: 'get_raw_entity_list_response', @@ -293,7 +296,8 @@ export class DebugManager implements IService, IUpdatable { return; } - const entityDetails = this.entityCollector.getEntityDetails(entityId); + const scene = this.sceneManager.currentScene; + const entityDetails = this.entityCollector.getEntityDetails(entityId, scene); this.webSocketManager.send({ type: 'get_entity_details_response', diff --git a/packages/core/src/Utils/Debug/EntityDataCollector.ts b/packages/core/src/Utils/Debug/EntityDataCollector.ts index 14e860ec..aeef95e3 100644 --- a/packages/core/src/Utils/Debug/EntityDataCollector.ts +++ b/packages/core/src/Utils/Debug/EntityDataCollector.ts @@ -263,8 +263,7 @@ export class EntityDataCollector { componentCount: entity.components?.length || 0, memory: 0 })) - .sort((a: any, b: any) => b.componentCount - a.componentCount) - .slice(0, 10); + .sort((a: any, b: any) => b.componentCount - a.componentCount); } @@ -303,7 +302,7 @@ export class EntityDataCollector { }); if (archetype.entities) { - archetype.entities.slice(0, 5).forEach((entity: any) => { + archetype.entities.forEach((entity: any) => { topEntities.push({ id: entity.id.toString(), name: entity.name || `Entity_${entity.id}`, @@ -352,7 +351,7 @@ export class EntityDataCollector { }); if (archetype.entities) { - archetype.entities.slice(0, 5).forEach((entity: any) => { + archetype.entities.forEach((entity: any) => { topEntities.push({ id: entity.id.toString(), name: entity.name || `Entity_${entity.id}`, diff --git a/packages/editor-app/src-tauri/src/profiler_ws.rs b/packages/editor-app/src-tauri/src/profiler_ws.rs index f88430ac..0bfa0c0c 100644 --- a/packages/editor-app/src-tauri/src/profiler_ws.rs +++ b/packages/editor-app/src-tauri/src/profiler_ws.rs @@ -131,10 +131,10 @@ async fn handle_connection( while let Some(msg) = ws_receiver.next().await { match msg { Ok(Message::Text(text)) => { - // Parse incoming debug data from game client + // Parse incoming messages if let Ok(json_value) = serde_json::from_str::(&text) { if json_value.get("type").and_then(|t| t.as_str()) == Some("debug_data") { - // Broadcast to frontend (ProfilerWindow) + // Broadcast debug data from game client to all clients (including frontend) tx.send(text).ok(); } else if json_value.get("type").and_then(|t| t.as_str()) == Some("ping") { // Respond to ping @@ -145,6 +145,10 @@ async fn handle_connection( }) .to_string(), ); + } else { + // Forward all other messages (like get_raw_entity_list, get_entity_details, etc.) + // to all connected clients (this enables frontend -> game client communication) + tx.send(text).ok(); } } } diff --git a/packages/editor-app/src/components/EntityInspector.tsx b/packages/editor-app/src/components/EntityInspector.tsx index b0bf94d4..01f9fff5 100644 --- a/packages/editor-app/src/components/EntityInspector.tsx +++ b/packages/editor-app/src/components/EntityInspector.tsx @@ -13,19 +13,42 @@ interface EntityInspectorProps { export function EntityInspector({ entityStore: _entityStore, messageHub }: EntityInspectorProps) { const [selectedEntity, setSelectedEntity] = useState(null); + const [remoteEntity, setRemoteEntity] = useState(null); + const [remoteEntityDetails, setRemoteEntityDetails] = useState(null); const [showAddComponent, setShowAddComponent] = useState(false); const [expandedComponents, setExpandedComponents] = useState>(new Set()); useEffect(() => { const handleSelection = (data: { entity: Entity | null }) => { setSelectedEntity(data.entity); + setRemoteEntity(null); + setRemoteEntityDetails(null); setShowAddComponent(false); }; + const handleRemoteSelection = (data: { entity: any }) => { + setRemoteEntity(data.entity); + setRemoteEntityDetails(null); + setSelectedEntity(null); + setShowAddComponent(false); + }; + + const handleEntityDetails = (event: Event) => { + const customEvent = event as CustomEvent; + const details = customEvent.detail; + console.log('[EntityInspector] Received entity details:', details); + setRemoteEntityDetails(details); + }; + const unsubSelect = messageHub.subscribe('entity:selected', handleSelection); + const unsubRemoteSelect = messageHub.subscribe('remote-entity:selected', handleRemoteSelection); + + window.addEventListener('profiler:entity-details', handleEntityDetails); return () => { unsubSelect(); + unsubRemoteSelect(); + window.removeEventListener('profiler:entity-details', handleEntityDetails); }; }, [messageHub]); @@ -82,7 +105,215 @@ export function EntityInspector({ entityStore: _entityStore, messageHub }: Entit }); }; - if (!selectedEntity) { + const renderRemoteProperty = (key: string, value: any) => { + if (value === null || value === undefined) { + return ( +
+ + null +
+ ); + } + + if (Array.isArray(value)) { + return ( +
+ +
+ {value.length === 0 ? ( + Empty Array + ) : ( + value.map((item, index) => ( + + {typeof item === 'object' ? JSON.stringify(item) : String(item)} + + )) + )} +
+
+ ); + } + + const valueType = typeof value; + + if (valueType === 'boolean') { + return ( +
+ +
+ +
+
+ ); + } + + if (valueType === 'number') { + return ( +
+ + +
+ ); + } + + if (valueType === 'string') { + return ( +
+ + +
+ ); + } + + if (valueType === 'object' && value.minX !== undefined && value.maxX !== undefined && value.minY !== undefined && value.maxY !== undefined) { + return ( +
+ +
+
+
+ X + +
+
+ X + +
+
+
+
+ Y + +
+
+ Y + +
+
+
+
+ ); + } + + if (valueType === 'object' && value.x !== undefined && value.y !== undefined) { + if (value.z !== undefined) { + return ( +
+ +
+
+ X + +
+
+ Y + +
+
+ Z + +
+
+
+ ); + } else { + return ( +
+ +
+
+ X + +
+
+ Y + +
+
+
+ ); + } + } + + return ( +
+ + {JSON.stringify(value)} +
+ ); + }; + + if (!selectedEntity && !remoteEntity) { return (
@@ -100,7 +331,102 @@ export function EntityInspector({ entityStore: _entityStore, messageHub }: Entit ); } - const components = selectedEntity.components; + // 显示远程实体 + if (remoteEntity) { + const displayData = remoteEntityDetails || remoteEntity; + const hasDetailedComponents = remoteEntityDetails && remoteEntityDetails.components && remoteEntityDetails.components.length > 0; + + return ( +
+
+ +

Inspector

+
+
+
+
+ + Entity Info (Remote) +
+
+
+ ID: + {displayData.id} +
+
+ Name: + {displayData.name} +
+
+ Enabled: + {displayData.enabled ? 'Yes' : 'No'} +
+ {displayData.scene && ( +
+ Scene: + {displayData.scene} +
+ )} +
+
+ +
+
+ + Components ({displayData.componentCount}) +
+
+ {hasDetailedComponents ? ( +
    + {remoteEntityDetails!.components.map((component: any, index: number) => { + const isExpanded = expandedComponents.has(index); + return ( +
  • +
    toggleComponentExpanded(index)}> + + + {component.typeName} +
    + {isExpanded && ( +
    +
    + {Object.entries(component.properties).map(([key, value]) => + renderRemoteProperty(key, value) + )} +
    +
    + )} +
  • + ); + })} +
+ ) : displayData.componentTypes && displayData.componentTypes.length > 0 ? ( +
    + {displayData.componentTypes.map((componentType: string, index: number) => ( +
  • +
    + + {componentType} +
    +
  • + ))} +
+ ) : ( +
No components
+ )} +
+
+
+
+ ); + } + + const components = selectedEntity!.components; return (
@@ -121,11 +447,11 @@ export function EntityInspector({ entityStore: _entityStore, messageHub }: Entit
Name: - Entity {selectedEntity.id} + Entity {selectedEntity!.id}
Enabled: - {selectedEntity.enabled ? 'Yes' : 'No'} + {selectedEntity!.enabled ? 'Yes' : 'No'}
diff --git a/packages/editor-app/src/components/SceneHierarchy.tsx b/packages/editor-app/src/components/SceneHierarchy.tsx index e009ab9f..1501541e 100644 --- a/packages/editor-app/src/components/SceneHierarchy.tsx +++ b/packages/editor-app/src/components/SceneHierarchy.tsx @@ -74,6 +74,27 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps) entityStore.selectEntity(entity); }; + const handleRemoteEntityClick = (entity: RemoteEntity) => { + setSelectedId(entity.id); + + // 请求完整的实体详情(包含组件属性) + const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined; + if (profilerService) { + profilerService.requestEntityDetails(entity.id); + } + + // 先发布基本信息,详细信息稍后通过 ProfilerService 异步返回 + messageHub.publish('remote-entity:selected', { + entity: { + id: entity.id, + name: entity.name, + enabled: entity.enabled, + componentCount: entity.componentCount, + componentTypes: entity.componentTypes + } + }); + }; + // Determine which entities to display const displayEntities = isRemoteConnected ? remoteEntities : entities; const showRemoteIndicator = isRemoteConnected && remoteEntities.length > 0; @@ -105,13 +126,14 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps) {remoteEntities.map(entity => (
  • handleRemoteEntityClick(entity)} > {entity.name} - {entity.components.length > 0 && ( - {entity.components.length} + {entity.componentCount > 0 && ( + {entity.componentCount} )}
  • ))} diff --git a/packages/editor-app/src/services/ProfilerService.ts b/packages/editor-app/src/services/ProfilerService.ts index 64e53de0..73f7f6eb 100644 --- a/packages/editor-app/src/services/ProfilerService.ts +++ b/packages/editor-app/src/services/ProfilerService.ts @@ -12,7 +12,35 @@ export interface RemoteEntity { id: number; name: string; enabled: boolean; - components: string[]; + active: boolean; + activeInHierarchy: boolean; + componentCount: number; + componentTypes: string[]; + parentId: number | null; + childIds: number[]; + depth: number; + tag: number; + updateOrder: number; +} + +export interface RemoteComponentDetail { + typeName: string; + properties: Record; +} + +export interface RemoteEntityDetails { + id: number; + name: string; + enabled: boolean; + active: boolean; + activeInHierarchy: boolean; + scene: string; + sceneName: string; + sceneType: string; + componentCount: number; + componentTypes: string[]; + components: RemoteComponentDetail[]; + parentName: string | null; } export interface ProfilerData { @@ -140,6 +168,10 @@ export class ProfilerService { const message = JSON.parse(event.data); if (message.type === 'debug_data' && message.data) { this.handleDebugData(message.data); + } else if (message.type === 'get_raw_entity_list_response' && message.data) { + this.handleRawEntityListResponse(message.data); + } else if (message.type === 'get_entity_details_response' && message.data) { + this.handleEntityDetailsResponse(message.data); } } catch (error) { console.error('[ProfilerService] Failed to parse message:', error); @@ -152,6 +184,25 @@ export class ProfilerService { } } + public requestEntityDetails(entityId: number): void { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { + console.warn('[ProfilerService] Cannot request entity details: WebSocket not connected'); + return; + } + + try { + const request = { + type: 'get_entity_details', + requestId: `entity_details_${entityId}_${Date.now()}`, + entityId + }; + console.log('[ProfilerService] Requesting entity details:', request); + this.ws.send(JSON.stringify(request)); + } catch (error) { + console.error('[ProfilerService] Failed to request entity details:', error); + } + } + private handleDebugData(debugData: any): void { const performance = debugData.performance; if (!performance) return; @@ -179,49 +230,98 @@ export class ProfilerService { }); } - const entityCount = debugData.entities?.totalCount || 0; + const entityCount = debugData.entities?.totalEntities || debugData.entities?.totalCount || 0; const componentTypes = debugData.components?.types || []; const componentCount = componentTypes.length; - // 解析实体列表 - console.log('[ProfilerService] debugData.entities:', debugData.entities); - let entities: RemoteEntity[] = []; - - // 尝试从 topEntitiesByComponents 获取实体列表 - if (debugData.entities?.topEntitiesByComponents && Array.isArray(debugData.entities.topEntitiesByComponents)) { - console.log('[ProfilerService] Found topEntitiesByComponents, length:', debugData.entities.topEntitiesByComponents.length); - entities = debugData.entities.topEntitiesByComponents.map((e: any) => ({ - id: parseInt(e.id) || 0, - name: e.name || `Entity ${e.id}`, - enabled: true, // topEntitiesByComponents doesn't have enabled flag, assume true - components: [] // componentCount is provided but not component names - })); - console.log('[ProfilerService] Parsed entities from topEntitiesByComponents:', entities.length); - } - // 尝试从 entities 获取实体列表(旧格式兼容) - else if (debugData.entities?.entities && Array.isArray(debugData.entities.entities)) { - console.log('[ProfilerService] Found entities array, length:', debugData.entities.entities.length); - entities = debugData.entities.entities.map((e: any) => ({ - id: e.id, - name: e.name || `Entity ${e.id}`, - enabled: e.enabled !== false, - components: e.components || [] - })); - console.log('[ProfilerService] Parsed entities:', entities.length); - } else { - console.log('[ProfilerService] No entities array found'); - } - this.currentData = { totalFrameTime, systems, entityCount, componentCount, fps, - entities + entities: [] }; this.notifyListeners(this.currentData); + + // 请求完整的实体列表 + this.requestRawEntityList(); + } + + private requestRawEntityList(): void { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { + console.warn('[ProfilerService] Cannot request entity list: WebSocket not connected'); + return; + } + + try { + const request = { + type: 'get_raw_entity_list', + requestId: `entity_list_${Date.now()}` + }; + console.log('[ProfilerService] Requesting entity list:', request); + this.ws.send(JSON.stringify(request)); + } catch (error) { + console.error('[ProfilerService] Failed to request entity list:', error); + } + } + + private handleRawEntityListResponse(data: any): void { + if (!data || !Array.isArray(data)) { + console.warn('[ProfilerService] Invalid raw entity list response:', data); + return; + } + + console.log('[ProfilerService] Received raw entity list, count:', data.length); + + const entities: RemoteEntity[] = data.map((e: any) => ({ + id: e.id, + name: e.name || `Entity ${e.id}`, + enabled: e.enabled !== false, + active: e.active !== false, + activeInHierarchy: e.activeInHierarchy !== false, + componentCount: e.componentCount || 0, + componentTypes: e.componentTypes || [], + parentId: e.parentId || null, + childIds: e.childIds || [], + depth: e.depth || 0, + tag: e.tag || 0, + updateOrder: e.updateOrder || 0 + })); + + if (this.currentData) { + this.currentData.entities = entities; + this.notifyListeners(this.currentData); + } + } + + private handleEntityDetailsResponse(data: any): void { + if (!data) { + console.warn('[ProfilerService] Invalid entity details response:', data); + return; + } + + console.log('[ProfilerService] Received entity details:', data); + + const entityDetails: RemoteEntityDetails = { + id: data.id, + name: data.name || `Entity ${data.id}`, + enabled: data.enabled !== false, + active: data.active !== false, + activeInHierarchy: data.activeInHierarchy !== false, + scene: data.scene || '', + sceneName: data.sceneName || '', + sceneType: data.sceneType || '', + componentCount: data.componentCount || 0, + componentTypes: data.componentTypes || [], + components: data.components || [], + parentName: data.parentName || null + }; + + window.dispatchEvent(new CustomEvent('profiler:entity-details', { + detail: entityDetails + })); } private createEmptyData(): ProfilerData { diff --git a/packages/editor-app/src/styles/EntityInspector.css b/packages/editor-app/src/styles/EntityInspector.css index 41ca0eb4..d88961ab 100644 --- a/packages/editor-app/src/styles/EntityInspector.css +++ b/packages/editor-app/src/styles/EntityInspector.css @@ -39,6 +39,26 @@ min-height: 0; } +.inspector-content::-webkit-scrollbar { + width: 14px; +} + +.inspector-content::-webkit-scrollbar-track { + background: transparent; +} + +.inspector-content::-webkit-scrollbar-thumb { + background: rgba(121, 121, 121, 0.4); + border-radius: 8px; + border: 3px solid transparent; + background-clip: padding-box; +} + +.inspector-content::-webkit-scrollbar-thumb:hover { + background: rgba(100, 100, 100, 0.7); + background-clip: padding-box; +} + .inspector-section { margin-bottom: var(--spacing-lg); }