diff --git a/packages/editor-app/src/components/PortManager.tsx b/packages/editor-app/src/components/PortManager.tsx index 9ba964ca..b9b600cc 100644 --- a/packages/editor-app/src/components/PortManager.tsx +++ b/packages/editor-app/src/components/PortManager.tsx @@ -1,6 +1,8 @@ import { useState, useEffect } from 'react'; import { invoke } from '@tauri-apps/api/core'; -import { X, Server, WifiOff } from 'lucide-react'; +import { X, Server, WifiOff, Wifi } from 'lucide-react'; +import { SettingsService } from '../services/SettingsService'; +import { ProfilerService } from '../services/ProfilerService'; import '../styles/PortManager.css'; interface PortManagerProps { @@ -9,9 +11,28 @@ interface PortManagerProps { export function PortManager({ onClose }: PortManagerProps) { const [isServerRunning, setIsServerRunning] = useState(false); - const [serverPort] = useState(8080); + const [serverPort, setServerPort] = useState('8080'); const [isChecking, setIsChecking] = useState(false); const [isStopping, setIsStopping] = useState(false); + const [isStarting, setIsStarting] = useState(false); + + useEffect(() => { + const settings = SettingsService.getInstance(); + setServerPort(settings.get('profiler.port', '8080')); + + const handleSettingsChange = ((event: CustomEvent) => { + const newPort = event.detail['profiler.port']; + if (newPort) { + setServerPort(newPort); + } + }) as EventListener; + + window.addEventListener('settings:changed', handleSettingsChange); + + return () => { + window.removeEventListener('settings:changed', handleSettingsChange); + }; + }, []); useEffect(() => { checkServerStatus(); @@ -33,9 +54,11 @@ export function PortManager({ onClose }: PortManagerProps) { const handleStopServer = async () => { setIsStopping(true); try { - const result = await invoke('stop_profiler_server'); - console.log('[PortManager]', result); - setIsServerRunning(false); + const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined; + if (profilerService) { + await profilerService.manualStopServer(); + setIsServerRunning(false); + } } catch (error) { console.error('[PortManager] Failed to stop server:', error); } finally { @@ -43,6 +66,22 @@ export function PortManager({ onClose }: PortManagerProps) { } }; + const handleStartServer = async () => { + setIsStarting(true); + try { + const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined; + if (profilerService) { + await profilerService.manualStartServer(); + await new Promise(resolve => setTimeout(resolve, 500)); + await checkServerStatus(); + } + } catch (error) { + console.error('[PortManager] Failed to start server:', error); + } finally { + setIsStarting(false); + } + }; + return (
e.stopPropagation()}> @@ -88,10 +127,22 @@ export function PortManager({ onClose }: PortManagerProps) { )} {!isServerRunning && ( -
-

No server is currently running.

-

Open Profiler window to start the server.

-
+ <> +
+ +
+
+

No server is currently running.

+

Click "Start Server" to start the profiler server.

+
+ )}
@@ -100,7 +151,7 @@ export function PortManager({ onClose }: PortManagerProps) {
  • Use this when the Profiler server port is stuck and cannot be restarted
  • The server will automatically stop when the Profiler window is closed
  • -
  • Default port: 8080
  • +
  • Current configured port: {serverPort}
diff --git a/packages/editor-app/src/components/SceneHierarchy.tsx b/packages/editor-app/src/components/SceneHierarchy.tsx index 9f51784f..a14b11fb 100644 --- a/packages/editor-app/src/components/SceneHierarchy.tsx +++ b/packages/editor-app/src/components/SceneHierarchy.tsx @@ -19,6 +19,7 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps) const [selectedId, setSelectedId] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [sceneName, setSceneName] = useState('Untitled'); + const [remoteSceneName, setRemoteSceneName] = useState(null); const [sceneFilePath, setSceneFilePath] = useState(null); const [isSceneModified, setIsSceneModified] = useState(false); const { t, locale } = useLocale(); @@ -97,6 +98,9 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps) return; } + const initiallyConnected = profilerService.isConnected(); + setIsRemoteConnected(initiallyConnected); + const unsubscribe = profilerService.subscribe((data) => { const connected = profilerService.isConnected(); setIsRemoteConnected(connected); @@ -119,12 +123,31 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps) return hasChanged ? data.entities! : prev; }); + + // 请求第一个实体的详情以获取场景名称 + if (!remoteSceneName && data.entities.length > 0) { + profilerService.requestEntityDetails(data.entities[0].id); + } } else if (!connected) { setRemoteEntities([]); + setRemoteSceneName(null); } }); return () => unsubscribe(); + }, [remoteSceneName]); + + // Listen for entity details to get remote scene name + useEffect(() => { + const handleEntityDetails = ((event: CustomEvent) => { + const details = event.detail; + if (details && details.sceneName) { + setRemoteSceneName(details.sceneName); + } + }) as EventListener; + + window.addEventListener('profiler:entity-details', handleEntityDetails); + return () => window.removeEventListener('profiler:entity-details', handleEntityDetails); }, []); const handleEntityClick = (entity: Entity) => { @@ -243,6 +266,7 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps) ? filterRemoteEntities(remoteEntities) : filterLocalEntities(entities); const showRemoteIndicator = isRemoteConnected && remoteEntities.length > 0; + const displaySceneName = isRemoteConnected && remoteSceneName ? remoteSceneName : sceneName; return (
@@ -250,12 +274,12 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps)

{t('hierarchy.title')}

- {sceneName}{isSceneModified ? '*' : ''} + {displaySceneName}{!isRemoteConnected && isSceneModified ? '*' : ''}
{showRemoteIndicator && ( diff --git a/packages/editor-app/src/services/ProfilerService.ts b/packages/editor-app/src/services/ProfilerService.ts index 2772d5ee..cf3974c3 100644 --- a/packages/editor-app/src/services/ProfilerService.ts +++ b/packages/editor-app/src/services/ProfilerService.ts @@ -65,13 +65,20 @@ export class ProfilerService { private checkServerInterval: NodeJS.Timeout | null = null; private reconnectTimeout: NodeJS.Timeout | null = null; private clientIdMap: Map = new Map(); // 客户端地址 -> 客户端ID映射 + private autoStart: boolean; constructor() { const settings = SettingsService.getInstance(); this.wsPort = settings.get('profiler.port', '8080'); + this.autoStart = settings.get('profiler.autoStart', true); this.startServerCheck(); this.listenToSettingsChanges(); + + // 如果设置了自动启动,则启动服务器 + if (this.autoStart) { + this.manualStartServer(); + } } private listenToSettingsChanges(): void { @@ -123,6 +130,26 @@ export class ProfilerService { return this.isServerRunning; } + /** + * 手动启动服务器 + */ + public async manualStartServer(): Promise { + await this.startServer(); + } + + /** + * 手动停止服务器 + */ + public async manualStopServer(): Promise { + try { + await invoke('stop_profiler_server'); + this.isServerRunning = false; + this.disconnect(); + } catch (error) { + console.error('[ProfilerService] Failed to stop server:', error); + } + } + private startServerCheck(): void { this.checkServerStatus(); this.checkServerInterval = setInterval(() => { @@ -136,12 +163,6 @@ export class ProfilerService { const wasRunning = this.isServerRunning; this.isServerRunning = status; - // 如果服务器还没运行,自动启动它 - if (!status) { - await this.startServer(); - return; - } - // 服务器启动了,尝试连接 if (status && !this.ws) { this.connectToServer(); @@ -149,7 +170,6 @@ export class ProfilerService { // 服务器从运行变为停止 if (wasRunning && !status) { - console.log('[ProfilerService] Server stopped'); this.disconnect(); } } catch (error) { @@ -180,6 +200,11 @@ export class ProfilerService { ws.onclose = () => { this.ws = null; + // 通知监听器连接已断开 + if (this.currentData) { + this.notifyListeners(this.currentData); + } + // 如果服务器还在运行,尝试重连 if (this.isServerRunning && !this.reconnectTimeout) { this.reconnectTimeout = setTimeout(() => { @@ -192,6 +217,11 @@ export class ProfilerService { ws.onerror = (error) => { console.error('[ProfilerService] WebSocket error:', error); this.ws = null; + + // 通知监听器连接出错 + if (this.currentData) { + this.notifyListeners(this.currentData); + } }; ws.onmessage = (event) => { @@ -404,6 +434,8 @@ export class ProfilerService { } private disconnect(): void { + const hadConnection = this.ws !== null; + if (this.ws) { this.ws.close(); this.ws = null; @@ -413,6 +445,11 @@ export class ProfilerService { clearTimeout(this.reconnectTimeout); this.reconnectTimeout = null; } + + // 如果有连接且手动断开,通知监听器 + if (hadConnection && this.currentData) { + this.notifyListeners(this.currentData); + } } public destroy(): void { diff --git a/packages/editor-app/src/styles/PortManager.css b/packages/editor-app/src/styles/PortManager.css index 7da9175a..a5e1ec54 100644 --- a/packages/editor-app/src/styles/PortManager.css +++ b/packages/editor-app/src/styles/PortManager.css @@ -167,6 +167,15 @@ transition: all var(--transition-fast); } +.action-btn.primary { + background: var(--color-primary); + color: white; +} + +.action-btn.primary:hover:not(:disabled) { + background: var(--color-primary-dark); +} + .action-btn.danger { background: var(--color-danger); color: white;