更新池利用率
This commit is contained in:
@@ -38,6 +38,8 @@ interface DetailedDebugInfo {
|
||||
minFrameTime: number;
|
||||
maxFrameTime: number;
|
||||
frameTimeHistory: number[];
|
||||
engineFrameTime: number;
|
||||
ecsPercentage: number;
|
||||
};
|
||||
|
||||
// 内存信息
|
||||
@@ -100,6 +102,7 @@ interface DetailedDebugInfo {
|
||||
memoryUsage: number;
|
||||
updateOrder: number;
|
||||
enabled: boolean;
|
||||
percentage: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
@@ -254,6 +257,16 @@ class ECSDebugServer {
|
||||
private transformToDetailedDebugInfo(instance: GameInstance, rawData: any): DetailedDebugInfo {
|
||||
const uptime = (Date.now() - instance.connectTime) / 1000;
|
||||
|
||||
// 计算系统性能数据,包括ECS占比
|
||||
const systemBreakdown = rawData.performance?.systemBreakdown || [];
|
||||
const systemPerformance = rawData.performance?.systemPerformance || [];
|
||||
|
||||
// 创建系统名称到占比的映射
|
||||
const systemPercentageMap = new Map<string, number>();
|
||||
systemBreakdown.forEach((sys: any) => {
|
||||
systemPercentageMap.set(sys.systemName, sys.percentage || 0);
|
||||
});
|
||||
|
||||
return {
|
||||
instanceId: instance.id,
|
||||
instanceName: instance.name,
|
||||
@@ -268,7 +281,9 @@ class ECSDebugServer {
|
||||
averageFrameTime: rawData.performance?.averageFrameTime || rawData.performance?.frameTime || 0,
|
||||
minFrameTime: rawData.performance?.minFrameTime || rawData.performance?.frameTime || 0,
|
||||
maxFrameTime: rawData.performance?.maxFrameTime || rawData.performance?.frameTime || 0,
|
||||
frameTimeHistory: rawData.performance?.frameTimeHistory || []
|
||||
frameTimeHistory: rawData.performance?.frameTimeHistory || [],
|
||||
engineFrameTime: rawData.performance?.engineFrameTime || 0,
|
||||
ecsPercentage: rawData.performance?.ecsPercentage || 0
|
||||
},
|
||||
|
||||
memory: {
|
||||
@@ -307,18 +322,24 @@ class ECSDebugServer {
|
||||
|
||||
systems: {
|
||||
total: rawData.systems?.totalSystems || 0,
|
||||
systemStats: (rawData.systems?.systemsInfo || []).map((sys: any) => ({
|
||||
name: sys.name,
|
||||
type: sys.type || 'Unknown',
|
||||
entityCount: sys.entityCount || 0,
|
||||
averageExecutionTime: sys.executionTime || 0,
|
||||
minExecutionTime: sys.minExecutionTime || sys.executionTime || 0,
|
||||
maxExecutionTime: sys.maxExecutionTime || sys.executionTime || 0,
|
||||
executionTimeHistory: sys.executionTimeHistory || [],
|
||||
memoryUsage: sys.memoryUsage || 0,
|
||||
updateOrder: sys.updateOrder || 0,
|
||||
enabled: sys.enabled !== false
|
||||
}))
|
||||
systemStats: (rawData.systems?.systemsInfo || []).map((sys: any) => {
|
||||
const systemName = sys.name;
|
||||
const percentage = systemPercentageMap.get(systemName) || 0;
|
||||
|
||||
return {
|
||||
name: systemName,
|
||||
type: sys.type || 'Unknown',
|
||||
entityCount: sys.entityCount || 0,
|
||||
averageExecutionTime: sys.executionTime || 0,
|
||||
minExecutionTime: sys.minExecutionTime || sys.executionTime || 0,
|
||||
maxExecutionTime: sys.maxExecutionTime || sys.executionTime || 0,
|
||||
executionTimeHistory: sys.executionTimeHistory || [],
|
||||
memoryUsage: sys.memoryUsage || 0,
|
||||
updateOrder: sys.updateOrder || 0,
|
||||
enabled: sys.enabled !== false,
|
||||
percentage: percentage
|
||||
};
|
||||
})
|
||||
},
|
||||
|
||||
scenes: {
|
||||
@@ -348,7 +369,9 @@ const defaultDebugInfo: DetailedDebugInfo = {
|
||||
averageFrameTime: 0,
|
||||
minFrameTime: 0,
|
||||
maxFrameTime: 0,
|
||||
frameTimeHistory: []
|
||||
frameTimeHistory: [],
|
||||
engineFrameTime: 0,
|
||||
ecsPercentage: 0
|
||||
},
|
||||
memory: {
|
||||
totalMemory: 0,
|
||||
@@ -425,6 +448,7 @@ module.exports = Editor.Panel.define({
|
||||
const isAutoRefresh = ref(true);
|
||||
const refreshInterval = ref(100);
|
||||
const lastUpdateTime = ref('');
|
||||
const showComponentPoolHelp = ref(false);
|
||||
|
||||
let intervalId: NodeJS.Timeout | null = null;
|
||||
let debugServer: ECSDebugServer | null = null;
|
||||
@@ -556,6 +580,84 @@ module.exports = Editor.Panel.define({
|
||||
return 'critical';
|
||||
};
|
||||
|
||||
// 打开文档链接
|
||||
const openDocumentation = (section: string): void => {
|
||||
const urls: Record<string, string> = {
|
||||
'component-pool': 'https://github.com/esengine/ecs-framework/tree/master/docs/component-design-guide.md#1-对象池优化',
|
||||
'performance-optimization': 'https://github.com/esengine/ecs-framework/tree/master/docs/performance-optimization.md'
|
||||
};
|
||||
|
||||
const url = urls[section];
|
||||
if (!url) return;
|
||||
|
||||
try {
|
||||
// 在Cocos Creator扩展环境中,直接使用Electron的shell模块
|
||||
const { shell } = require('electron');
|
||||
shell.openExternal(url);
|
||||
} catch (error) {
|
||||
console.error('无法打开链接:', error);
|
||||
// 如果失败,复制到剪贴板
|
||||
copyUrlToClipboard(url);
|
||||
}
|
||||
};
|
||||
|
||||
// 复制链接到剪贴板的辅助函数
|
||||
const copyUrlToClipboard = (url: string): void => {
|
||||
try {
|
||||
// 尝试使用现代的剪贴板API
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
console.log(`文档链接已复制到剪贴板: ${url}`);
|
||||
// 如果可能的话,显示用户友好的提示
|
||||
if (typeof alert !== 'undefined') {
|
||||
alert(`文档链接已复制到剪贴板,请在浏览器中粘贴访问:\n${url}`);
|
||||
}
|
||||
}).catch(() => {
|
||||
fallbackCopyText(url);
|
||||
});
|
||||
} else {
|
||||
fallbackCopyText(url);
|
||||
}
|
||||
} catch (error) {
|
||||
fallbackCopyText(url);
|
||||
}
|
||||
};
|
||||
|
||||
// 备用的复制文本方法
|
||||
const fallbackCopyText = (text: string): void => {
|
||||
try {
|
||||
// 创建临时的文本区域
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
textArea.style.top = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
const successful = document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
|
||||
if (successful) {
|
||||
console.log(`文档链接已复制到剪贴板: ${text}`);
|
||||
if (typeof alert !== 'undefined') {
|
||||
alert(`文档链接已复制到剪贴板,请在浏览器中粘贴访问:\n${text}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`请手动复制文档链接: ${text}`);
|
||||
if (typeof alert !== 'undefined') {
|
||||
alert(`请手动复制文档链接:\n${text}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`请手动访问文档: ${text}`);
|
||||
if (typeof alert !== 'undefined') {
|
||||
alert(`请手动访问文档:\n${text}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(async () => {
|
||||
await initializeServer();
|
||||
@@ -575,6 +677,7 @@ module.exports = Editor.Panel.define({
|
||||
isAutoRefresh,
|
||||
refreshInterval,
|
||||
lastUpdateTime,
|
||||
showComponentPoolHelp,
|
||||
manualRefresh,
|
||||
toggleAutoRefresh,
|
||||
changeRefreshInterval,
|
||||
@@ -584,7 +687,8 @@ module.exports = Editor.Panel.define({
|
||||
getFpsColor,
|
||||
getMemoryColor,
|
||||
getECSTimeColor,
|
||||
getExecutionTimeColor
|
||||
getExecutionTimeColor,
|
||||
openDocumentation
|
||||
};
|
||||
},
|
||||
template: readFileSync(join(__dirname, '../../../static/template/debug/index.html'), 'utf-8'),
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
background: var(--color-normal-fill);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
.debug-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
background: var(--color-normal-fill);
|
||||
}
|
||||
|
||||
@@ -660,10 +660,12 @@ ui-button ui-icon {
|
||||
/* ECS调试面板样式 */
|
||||
.debug-panel {
|
||||
padding: 16px;
|
||||
padding-bottom: 32px;
|
||||
background: var(--color-normal-fill);
|
||||
color: var(--color-normal-contrast);
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
@@ -1242,7 +1244,7 @@ ui-button ui-icon {
|
||||
}
|
||||
|
||||
.debug-panel::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-normal-contrast-weaken);
|
||||
background-color: var(--color-focus-border-emphasis);
|
||||
}
|
||||
|
||||
/* 百分比条样式 */
|
||||
@@ -1261,7 +1263,340 @@ ui-button ui-icon {
|
||||
}
|
||||
|
||||
.percentage-fill {
|
||||
background: linear-gradient(90deg, var(--color-primary), var(--color-primary-emphasis));
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #4CAF50 0%, #FF9800 50%, #F44336 100%);
|
||||
border-radius: inherit;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 调试面板滚动条样式 */
|
||||
.debug-panel::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.debug-panel::-webkit-scrollbar-track {
|
||||
background: var(--color-normal-fill-weaker);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.debug-panel::-webkit-scrollbar-thumb {
|
||||
background: var(--color-normal-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.debug-panel::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-focus-border);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
帮助面板和文档链接样式
|
||||
============================================ */
|
||||
|
||||
.section-help {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.help-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
background: var(--color-normal-fill);
|
||||
border: 1px solid var(--color-normal-border);
|
||||
border-radius: 4px;
|
||||
color: var(--color-default-contrast);
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.help-btn:hover {
|
||||
background: var(--color-normal-fill-emphasis);
|
||||
border-color: var(--color-focus-border);
|
||||
color: var(--color-focus-contrast);
|
||||
}
|
||||
|
||||
.help-btn ui-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.help-panel {
|
||||
margin: 16px 0;
|
||||
padding: 20px;
|
||||
background: var(--color-normal-fill);
|
||||
border: 1px solid var(--color-normal-border);
|
||||
border-radius: 8px;
|
||||
animation: fadeInDown 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.help-content h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--color-focus-contrast);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.help-content p {
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--color-default-contrast);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.help-steps {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.help-step {
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
background: var(--color-normal-fill-emphasis);
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid var(--color-focus-border);
|
||||
}
|
||||
|
||||
.help-step:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.help-step h5 {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--color-focus-contrast);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.help-step p {
|
||||
margin: 0;
|
||||
color: var(--color-default-contrast);
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.code-example {
|
||||
background: var(--color-normal-fill);
|
||||
border: 1px solid var(--color-normal-border);
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin: 8px 0 0 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 10px;
|
||||
line-height: 1.4;
|
||||
color: var(--color-focus-border);
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.help-links {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-normal-border);
|
||||
}
|
||||
|
||||
.doc-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
background: var(--color-focus-fill);
|
||||
border: 1px solid var(--color-focus-border);
|
||||
border-radius: 4px;
|
||||
color: var(--color-focus-contrast);
|
||||
text-decoration: none;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.doc-link:hover {
|
||||
background: var(--color-focus-fill-emphasis);
|
||||
border-color: var(--color-focus-border-emphasis);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.doc-link ui-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.doc-access-note {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
background: var(--color-info-fill);
|
||||
border: 1px solid var(--color-info-border);
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
color: var(--color-info-contrast);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.doc-access-note ui-icon {
|
||||
font-size: 14px;
|
||||
color: var(--color-info-border);
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
组件池相关样式
|
||||
============================================ */
|
||||
|
||||
.help-tooltip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: 4px;
|
||||
cursor: help;
|
||||
color: var(--color-normal-contrast-weaker);
|
||||
}
|
||||
|
||||
.help-tooltip:hover {
|
||||
color: var(--color-focus-border);
|
||||
}
|
||||
|
||||
.help-tooltip ui-icon {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.not-configured {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: 4px;
|
||||
color: var(--color-warn-border);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.not-configured ui-icon {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.no-pool-hint {
|
||||
font-size: 10px;
|
||||
color: var(--color-normal-contrast-weaker);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.pool-suggestion {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, var(--color-warn-fill) 0%, var(--color-focus-fill) 100%);
|
||||
border: 1px solid var(--color-warn-border);
|
||||
border-radius: 8px;
|
||||
animation: fadeInUp 0.4s ease;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.suggestion-icon {
|
||||
color: var(--color-warn-border);
|
||||
font-size: 20px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.suggestion-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.suggestion-text h4 {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--color-focus-contrast);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.suggestion-text p {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--color-default-contrast);
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.suggestion-action {
|
||||
padding: 6px 12px;
|
||||
background: var(--color-focus-border);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.suggestion-action:hover {
|
||||
background: var(--color-focus-border-emphasis);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
响应式设计增强
|
||||
============================================ */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.help-links {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.doc-link {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.suggestion-icon {
|
||||
align-self: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.help-panel {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.help-step {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.code-example {
|
||||
font-size: 9px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.help-btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.section-help {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,68 @@
|
||||
<div class="debug-section" v-if="debugInfo.instanceId">
|
||||
<div class="section-header">
|
||||
<h2>🧩 组件分析</h2>
|
||||
<div class="section-help">
|
||||
<button class="help-btn" @click="showComponentPoolHelp = !showComponentPoolHelp" title="组件对象池使用说明">
|
||||
<ui-icon value="help"></ui-icon>
|
||||
对象池使用说明
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 对象池使用说明 -->
|
||||
<div v-if="showComponentPoolHelp" class="help-panel">
|
||||
<div class="help-content">
|
||||
<h4>📖 组件对象池使用指南</h4>
|
||||
<p>组件对象池用于复用组件实例,减少频繁创建/销毁组件带来的内存分配开销。当前利用率为0表示还未配置对象池。</p>
|
||||
|
||||
<div class="help-steps">
|
||||
<div class="help-step">
|
||||
<h5>1. 注册组件对象池</h5>
|
||||
<pre class="code-example">import { ComponentPoolManager } from '@esengine/ecs-framework';
|
||||
|
||||
// 为Transform组件注册对象池
|
||||
ComponentPoolManager.getInstance().registerPool(
|
||||
'Transform',
|
||||
() => new Transform(),
|
||||
(comp) => comp.reset(), // 可选的重置函数
|
||||
100 // 池大小
|
||||
);</pre>
|
||||
</div>
|
||||
|
||||
<div class="help-step">
|
||||
<h5>2. 使用对象池获取组件</h5>
|
||||
<pre class="code-example">// 从对象池获取组件实例
|
||||
const poolManager = ComponentPoolManager.getInstance();
|
||||
const transform = poolManager.acquireComponent('Transform');
|
||||
|
||||
// 使用完毕后释放回池中
|
||||
poolManager.releaseComponent('Transform', transform);</pre>
|
||||
</div>
|
||||
|
||||
<div class="help-step">
|
||||
<h5>3. 查看性能改进</h5>
|
||||
<p>正确配置后,利用率栏将显示池的使用情况。利用率越高说明对象池被有效使用,可以减少GC压力。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help-links">
|
||||
<a href="#" @click="openDocumentation('component-pool')" class="doc-link">
|
||||
<ui-icon value="book"></ui-icon>
|
||||
查看详细文档
|
||||
</a>
|
||||
<a href="#" @click="openDocumentation('performance-optimization')" class="doc-link">
|
||||
<ui-icon value="speed"></ui-icon>
|
||||
性能优化指南
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="doc-access-note">
|
||||
<ui-icon value="info"></ui-icon>
|
||||
<span>如果无法直接打开文档链接,链接将自动复制到剪贴板,请在浏览器中粘贴访问</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-overview">
|
||||
<div class="overview-item">
|
||||
<span class="label">组件类型:</span>
|
||||
@@ -206,18 +267,45 @@
|
||||
<div class="col-count">实例数</div>
|
||||
<div class="col-memory">内存占用</div>
|
||||
<div class="col-pool">对象池</div>
|
||||
<div class="col-efficiency">利用率</div>
|
||||
<div class="col-efficiency">
|
||||
利用率
|
||||
<span class="help-tooltip" title="对象池利用率:使用中的实例 / 池总大小。利用率为0表示未配置对象池">
|
||||
<ui-icon value="help"></ui-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="comp in debugInfo.components.componentStats" :key="comp.typeName" class="table-row">
|
||||
<div class="col-name">{{ comp.typeName }}</div>
|
||||
<div class="col-count">{{ comp.instanceCount }}</div>
|
||||
<div class="col-memory">{{ formatMemory(comp.totalMemory) }}</div>
|
||||
<div class="col-pool">{{ comp.poolSize }}</div>
|
||||
<div class="col-pool">
|
||||
{{ comp.poolSize || '未配置' }}
|
||||
<span v-if="comp.poolSize === 0" class="not-configured" title="点击上方说明了解如何配置对象池">
|
||||
<ui-icon value="warning"></ui-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-efficiency">
|
||||
<div class="efficiency-bar">
|
||||
<div class="efficiency-fill" :style="{ width: comp.poolUtilization + '%' }"></div>
|
||||
</div>
|
||||
{{ comp.poolUtilization.toFixed(1) }}%
|
||||
<span v-if="comp.poolUtilization === 0 && comp.poolSize === 0" class="no-pool-hint">
|
||||
(无对象池)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无对象池组件提示 -->
|
||||
<div v-if="debugInfo.components.componentStats.length > 0 && debugInfo.components.componentStats.every(c => c.poolSize === 0)" class="pool-suggestion">
|
||||
<div class="suggestion-content">
|
||||
<ui-icon value="lightbulb" class="suggestion-icon"></ui-icon>
|
||||
<div class="suggestion-text">
|
||||
<h4>💡 性能优化建议</h4>
|
||||
<p>检测到所有组件都未配置对象池。为频繁创建/销毁的组件配置对象池可以显著提升性能,减少垃圾回收压力。</p>
|
||||
<button @click="showComponentPoolHelp = true" class="suggestion-action">
|
||||
了解如何配置对象池
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -149,4 +149,38 @@ export class ComponentPoolManager {
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池利用率信息(用于调试)
|
||||
*/
|
||||
getPoolUtilization(): Map<string, { used: number; total: number; utilization: number }> {
|
||||
const utilization = new Map();
|
||||
for (const [name, pool] of this.pools) {
|
||||
const available = pool.getAvailableCount();
|
||||
const maxSize = pool.getMaxSize();
|
||||
const used = maxSize - available;
|
||||
const utilRate = maxSize > 0 ? (used / maxSize * 100) : 0;
|
||||
|
||||
utilization.set(name, {
|
||||
used: used,
|
||||
total: maxSize,
|
||||
utilization: utilRate
|
||||
});
|
||||
}
|
||||
return utilization;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定组件的池利用率
|
||||
*/
|
||||
getComponentUtilization(componentName: string): number {
|
||||
const pool = this.pools.get(componentName);
|
||||
if (!pool) return 0;
|
||||
|
||||
const available = pool.getAvailableCount();
|
||||
const maxSize = pool.getMaxSize();
|
||||
const used = maxSize - available;
|
||||
|
||||
return maxSize > 0 ? (used / maxSize * 100) : 0;
|
||||
}
|
||||
}
|
||||
@@ -386,6 +386,12 @@ export interface IPerformanceDebugData {
|
||||
samples: number;
|
||||
percentage?: number; // 系统占ECS总时间的百分比
|
||||
}>;
|
||||
/** 系统占比分析数据 */
|
||||
systemBreakdown?: Array<{
|
||||
systemName: string;
|
||||
executionTime: number;
|
||||
percentage: number;
|
||||
}>;
|
||||
/** 内存分配详情 */
|
||||
memoryDetails?: {
|
||||
entities: number;
|
||||
|
||||
@@ -41,6 +41,14 @@ export class DebugReporter {
|
||||
this.config = config;
|
||||
this.sceneStartTime = Date.now();
|
||||
|
||||
// 确保性能监控器在调试模式下被启用
|
||||
if (this.config.enabled && this.config.channels.performance) {
|
||||
if (!this.core._performanceMonitor.isEnabled) {
|
||||
this.core._performanceMonitor.enable();
|
||||
console.log('[ECS Debug] Performance monitor enabled for debugging');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.config.enabled) {
|
||||
this.start();
|
||||
}
|
||||
@@ -80,6 +88,14 @@ export class DebugReporter {
|
||||
|
||||
this.config = newConfig;
|
||||
|
||||
// 根据配置启用或禁用性能监控器
|
||||
if (newConfig.enabled && newConfig.channels.performance) {
|
||||
if (!this.core._performanceMonitor.isEnabled) {
|
||||
this.core._performanceMonitor.enable();
|
||||
console.log('[ECS Debug] Performance monitor enabled for debugging');
|
||||
}
|
||||
}
|
||||
|
||||
if (!newConfig.enabled && wasEnabled) {
|
||||
this.stop();
|
||||
} else if (newConfig.enabled && (!wasEnabled || urlChanged)) {
|
||||
@@ -429,6 +445,7 @@ export class DebugReporter {
|
||||
maxFrameTime: maxECSTime, // ECS最长执行时间
|
||||
frameTimeHistory: [...this.frameTimeHistory],
|
||||
systemPerformance: this.getSystemPerformance(),
|
||||
systemBreakdown: ecsPerformanceData.systemBreakdown,
|
||||
memoryDetails: this.getMemoryDetails()
|
||||
};
|
||||
}
|
||||
@@ -439,6 +456,14 @@ export class DebugReporter {
|
||||
private getECSPerformanceData(): { totalExecutionTime: number; systemBreakdown: Array<any> } {
|
||||
const monitor = this.core._performanceMonitor;
|
||||
if (!monitor) {
|
||||
console.warn('[ECS Debug] Performance monitor not found');
|
||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||
}
|
||||
|
||||
if (!monitor.isEnabled) {
|
||||
console.warn('[ECS Debug] Performance monitor is disabled. Enable it to see ECS performance data.');
|
||||
// 尝试启用性能监控器
|
||||
monitor.enable();
|
||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||
}
|
||||
|
||||
@@ -448,6 +473,11 @@ export class DebugReporter {
|
||||
|
||||
const stats = monitor.getAllSystemStats();
|
||||
|
||||
if (stats.size === 0) {
|
||||
console.log('[ECS Debug] No system performance data available yet. This is normal on first frames.');
|
||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||
}
|
||||
|
||||
// 计算各系统的执行时间
|
||||
for (const [systemName, stat] of stats.entries()) {
|
||||
const systemTime = stat.averageTime || 0;
|
||||
@@ -468,11 +498,14 @@ export class DebugReporter {
|
||||
// 按执行时间排序
|
||||
systemBreakdown.sort((a, b) => b.executionTime - a.executionTime);
|
||||
|
||||
console.log(`[ECS Debug] Performance data: ${stats.size} systems, total time: ${totalTime.toFixed(2)}ms`);
|
||||
|
||||
return {
|
||||
totalExecutionTime: totalTime,
|
||||
systemBreakdown: systemBreakdown
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[ECS Debug] Error getting ECS performance data:', error);
|
||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||
}
|
||||
}
|
||||
@@ -544,29 +577,56 @@ export class DebugReporter {
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 如果entityManager不存在,尝试从场景直接获取
|
||||
const entityList = (scene as any).entities;
|
||||
if (entityList?.buffer) {
|
||||
entityList.buffer.forEach((entity: any) => {
|
||||
entityMemory += this.estimateObjectSize(entity);
|
||||
|
||||
if (entity.components) {
|
||||
entity.components.forEach((component: any) => {
|
||||
componentMemory += this.estimateObjectSize(component);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 计算系统内存(估算)
|
||||
// 计算系统内存
|
||||
const entitySystems = (scene as any).entitySystems;
|
||||
if (entitySystems?.systems) {
|
||||
entitySystems.systems.forEach((system: any) => {
|
||||
systemMemory += this.estimateObjectSize(system);
|
||||
});
|
||||
} else {
|
||||
// 尝试从entityProcessors获取
|
||||
const entityProcessors = (scene as any).entityProcessors;
|
||||
if (entityProcessors?.processors) {
|
||||
entityProcessors.processors.forEach((system: any) => {
|
||||
systemMemory += this.estimateObjectSize(system);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 计算对象池内存(估算)
|
||||
// 计算对象池内存
|
||||
try {
|
||||
const poolManager = this.core._poolManager;
|
||||
if (poolManager) {
|
||||
// 简单估算对象池内存
|
||||
pooledMemory = 1024 * 1024; // 1MB估算值
|
||||
const { ComponentPoolManager } = require('../ECS/Core/ComponentPool');
|
||||
const poolManager = ComponentPoolManager.getInstance();
|
||||
const poolStats = poolManager.getPoolStats();
|
||||
|
||||
for (const [typeName, stats] of poolStats.entries()) {
|
||||
// 估算每个组件实例的大小
|
||||
const estimatedComponentSize = this.calculateComponentMemorySize(typeName);
|
||||
pooledMemory += stats.available * estimatedComponentSize;
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略对象池内存计算错误
|
||||
// 如果无法访问ComponentPoolManager,使用估算值
|
||||
pooledMemory = 512 * 1024; // 512KB估算值
|
||||
}
|
||||
|
||||
// 获取浏览器内存信息
|
||||
let totalMemory = 512 * 1024 * 1024; // 默认512MB
|
||||
let totalMemory = 512 * 1024 * 1024;
|
||||
let usedMemory = entityMemory + componentMemory + systemMemory + pooledMemory;
|
||||
let gcCollections = 0;
|
||||
|
||||
@@ -637,37 +697,48 @@ export class DebugReporter {
|
||||
}
|
||||
});
|
||||
|
||||
// 获取池利用率信息
|
||||
let poolUtilizations = new Map<string, number>();
|
||||
let poolSizes = new Map<string, number>();
|
||||
|
||||
try {
|
||||
const { ComponentPoolManager } = require('../ECS/Core/ComponentPool');
|
||||
const poolManager = ComponentPoolManager.getInstance();
|
||||
const poolStats = poolManager.getPoolStats();
|
||||
const utilizations = poolManager.getPoolUtilization();
|
||||
|
||||
for (const [typeName, stats] of poolStats.entries()) {
|
||||
poolSizes.set(typeName, stats.maxSize);
|
||||
}
|
||||
|
||||
for (const [typeName, util] of utilizations.entries()) {
|
||||
poolUtilizations.set(typeName, util.utilization);
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果无法获取池信息,使用默认值
|
||||
}
|
||||
|
||||
return {
|
||||
componentTypes: componentStats.size,
|
||||
componentInstances: totalInstances,
|
||||
componentStats: Array.from(componentStats.entries()).map(([typeName, stats]) => {
|
||||
const poolSize = this.getComponentPoolSize(typeName);
|
||||
const poolSize = poolSizes.get(typeName) || 0;
|
||||
const poolUtilization = poolUtilizations.get(typeName) || 0;
|
||||
const memoryPerInstance = this.calculateComponentMemorySize(typeName);
|
||||
|
||||
return {
|
||||
typeName,
|
||||
instanceCount: stats.count,
|
||||
memoryPerInstance: memoryPerInstance,
|
||||
totalMemory: stats.count * memoryPerInstance,
|
||||
poolSize: poolSize,
|
||||
poolUtilization: poolSize > 0 ? (stats.count / poolSize * 100) : 0,
|
||||
poolUtilization: poolUtilization,
|
||||
averagePerEntity: stats.count / entityList.buffer.length
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件池大小
|
||||
*/
|
||||
private getComponentPoolSize(typeName: string): number {
|
||||
try {
|
||||
const poolManager = this.core._poolManager;
|
||||
return (poolManager as any).getPoolSize?.(typeName) || 0;
|
||||
} catch (error) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算组件实际内存大小
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user