@downdraft/engine 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/src/renderer/bootstrap.ts +0 -6
- package/app/src/renderer/feature-log.ts +1 -9
- package/app/src/renderer/mcp-harness.ts +8 -11
- package/app/src/renderer/standard-automation-tools.ts +8 -145
- package/core/src/module/host.ts +3 -6
- package/core/src/module/index.ts +1 -1
- package/core/src/module/module.ts +4 -47
- package/core/src/physics/interface.ts +5 -0
- package/core/src/platform/render-surface.ts +13 -0
- package/core/src/render/canvas-resize-watcher.ts +1 -10
- package/core/src/render/game-renderer.ts +40 -18
- package/core/src/render/index.ts +0 -2
- package/core/src/render/input-manager.ts +7 -7
- package/core/src/render/renderer-module-host.ts +2 -4
- package/core/src/ui-font-scale.ts +5 -38
- package/core/src/worker/rpc.ts +65 -0
- package/dist/.dd-manifest.json +859 -884
- package/dist/app/src/renderer/bootstrap.js +0 -4
- package/dist/app/src/renderer/feature-log.js +1 -10
- package/dist/app/src/renderer/mcp-harness.js +8 -11
- package/dist/app/src/renderer/standard-automation-tools.js +6 -138
- package/dist/core/src/module/host.js +3 -6
- package/dist/core/src/render/canvas-resize-watcher.js +1 -10
- package/dist/core/src/render/game-renderer.js +44 -18
- package/dist/core/src/render/index.js +0 -1
- package/dist/core/src/render/input-manager.js +7 -7
- package/dist/core/src/render/renderer-module-host.js +2 -4
- package/dist/core/src/ui-font-scale.js +5 -38
- package/dist/core/src/worker/rpc.js +59 -0
- package/dist/libraries/blitz-ui/src/native-module.js +8 -1
- package/dist/libraries/devtools/src/native-providers.js +0 -2
- package/dist/libraries/entities/src/model-renderer.js +87 -27
- package/dist/libraries/entities/src/shaders/model.wgsl +13 -4
- package/dist/libraries/networking/src/index.js +1 -0
- package/dist/libraries/networking/src/ws-server.js +387 -0
- package/dist/libraries/physics-rapier/src/api.js +15 -0
- package/dist/libraries/physics-rapier/src/backend.js +15 -0
- package/dist/libraries/physics-rapier/src/rapier-backend.js +5 -0
- package/dist/libraries/postfx/src/post-process-stack.js +1 -3
- package/dist/libraries/sand/src/materials.js +2 -32
- package/dist/libraries/sand/src/sand-world.js +0 -3
- package/dist/modules/devtools/src/api.js +8 -23
- package/dist/modules/devtools/src/data-bridge.js +0 -11
- package/dist/modules/devtools/src/index.js +0 -6
- package/dist/modules/devtools/src/init.js +8 -42
- package/dist/modules/devtools/src/scene-inspector.js +1 -4
- package/dist/modules/devtools/src/worker-sync.js +3 -11
- package/dist/test/src/vision-test.js +1 -3
- package/libraries/blitz-ui/src/native-module.ts +8 -1
- package/libraries/devtools/src/native-providers.ts +0 -2
- package/libraries/entities/src/model-renderer.ts +88 -27
- package/libraries/entities/src/shaders/model.wgsl +13 -4
- package/libraries/networking/src/index.ts +4 -0
- package/libraries/networking/src/ws-server.ts +397 -0
- package/libraries/physics-rapier/src/api.ts +15 -0
- package/libraries/physics-rapier/src/backend.ts +15 -0
- package/libraries/physics-rapier/src/rapier-backend.ts +6 -0
- package/libraries/postfx/src/post-process-stack.ts +1 -2
- package/libraries/sand/src/materials.ts +2 -32
- package/libraries/sand/src/sand-world.ts +0 -4
- package/modules/devtools/README.md +1 -3
- package/modules/devtools/src/api.ts +10 -34
- package/modules/devtools/src/data-bridge.ts +0 -16
- package/modules/devtools/src/index.ts +1 -11
- package/modules/devtools/src/init.ts +8 -51
- package/modules/devtools/src/scene-inspector.ts +2 -6
- package/modules/devtools/src/types.ts +5 -90
- package/modules/devtools/src/worker-sync.ts +3 -13
- package/package.json +4 -5
- package/test/src/vision-test.ts +2 -4
- package/core/src/render/sab-canvas-overlay.ts +0 -122
- package/dist/core/src/render/sab-canvas-overlay.js +0 -83
- package/dist/modules/devtools/src/debug-overlay.js +0 -181
- package/dist/modules/devtools/src/doctor-panel.js +0 -163
- package/dist/modules/devtools/src/label-overlay.js +0 -129
- package/dist/modules/devtools/src/material-stats-panel.js +0 -171
- package/dist/modules/devtools/src/sim-stats-panel.js +0 -250
- package/modules/devtools/src/debug-overlay.ts +0 -238
- package/modules/devtools/src/doctor-panel.ts +0 -182
- package/modules/devtools/src/label-overlay.ts +0 -154
- package/modules/devtools/src/material-stats-panel.ts +0 -188
- package/modules/devtools/src/sim-stats-panel.ts +0 -272
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// downdraft doctor — devtools panel for module graph diagnostics
|
|
3
|
-
//
|
|
4
|
-
// Displays the resolved module graph, resource table, system schedule,
|
|
5
|
-
// diagnostics (unresolved requires, version conflicts, leaks), and the
|
|
6
|
-
// cross-thread dependency report (sim + renderer).
|
|
7
|
-
// ============================================================================
|
|
8
|
-
|
|
9
|
-
import type { CrossThreadReport, ModuleThreadInfo, PluginInfo } from "@downdraft/engine";
|
|
10
|
-
import type { IDevToolsPanelExtension } from "./types";
|
|
11
|
-
|
|
12
|
-
export interface DoctorPanelOptions {
|
|
13
|
-
/** Snapshot provider for sim-side modules. */
|
|
14
|
-
getSimModules?: () => ModuleThreadInfo[];
|
|
15
|
-
/** Snapshot provider for renderer-side modules. */
|
|
16
|
-
getRendererModules?: () => ModuleThreadInfo[];
|
|
17
|
-
/** Snapshot provider for the cross-thread report (pre-built). */
|
|
18
|
-
getReport?: () => CrossThreadReport | null;
|
|
19
|
-
/** Snapshot provider for user-authored plugins (the modding system). */
|
|
20
|
-
getPlugins?: () => PluginInfo[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Escape untrusted strings before interpolating into innerHTML — mod
|
|
24
|
-
* manifests are third-party content and must not be able to inject markup. */
|
|
25
|
-
function esc(s: unknown): string {
|
|
26
|
-
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function createDoctorPanelExtension(opts: DoctorPanelOptions = {}): IDevToolsPanelExtension {
|
|
30
|
-
return {
|
|
31
|
-
id: "downdraft-doctor",
|
|
32
|
-
tabLabel: "Doctor",
|
|
33
|
-
tabTooltip: "Module graph diagnostics: resolved dependencies, resource table, version conflicts, leak detection",
|
|
34
|
-
order: 5,
|
|
35
|
-
|
|
36
|
-
html: `
|
|
37
|
-
<div class="dd-doctor">
|
|
38
|
-
<div class="dd-doctor__header">
|
|
39
|
-
<h2>downdraft doctor</h2>
|
|
40
|
-
<button class="dd-doctor__refresh">Refresh</button>
|
|
41
|
-
</div>
|
|
42
|
-
<div class="dd-doctor__summary"></div>
|
|
43
|
-
<div class="dd-doctor__plugins"></div>
|
|
44
|
-
<div class="dd-doctor__user-plugins"></div>
|
|
45
|
-
<div class="dd-doctor__resources"></div>
|
|
46
|
-
<div class="dd-doctor__issues"></div>
|
|
47
|
-
</div>
|
|
48
|
-
`,
|
|
49
|
-
|
|
50
|
-
css: `
|
|
51
|
-
.dd-doctor { padding: 12px; font-family: monospace; font-size: 12px; color: #ccc; }
|
|
52
|
-
.dd-doctor h2 { margin: 0 0 8px; font-size: 16px; color: #4fc3f7; }
|
|
53
|
-
.dd-doctor__header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
|
|
54
|
-
.dd-doctor__refresh { padding: 4px 12px; background: #333; color: #ccc; border: 1px solid #555; border-radius: 4px; cursor: pointer; }
|
|
55
|
-
.dd-doctor__refresh:hover { background: #444; }
|
|
56
|
-
.dd-doctor__summary { margin-bottom: 16px; padding: 8px; background: #1a1a2e; border-radius: 4px; }
|
|
57
|
-
.dd-doctor__summary div { margin: 2px 0; }
|
|
58
|
-
.dd-doctor__plugins h3, .dd-doctor__resources h3, .dd-doctor__issues h3, .dd-doctor__user-plugins h3 { color: #4fc3f7; margin: 12px 0 4px; font-size: 13px; }
|
|
59
|
-
.dd-doctor__badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; margin-right: 4px; }
|
|
60
|
-
.dd-doctor__badge--format { background: #2a3a4a; color: #64b5f6; }
|
|
61
|
-
.dd-doctor__badge--tier { background: #3a2a4a; color: #ce93d8; }
|
|
62
|
-
.dd-doctor__badge--thread { background: #2a4a3a; color: #81c784; }
|
|
63
|
-
.dd-doctor__badge--perm { background: #4a3a2a; color: #ffb74d; }
|
|
64
|
-
.dd-doctor__status--active { color: #81c784; }
|
|
65
|
-
.dd-doctor__status--error { color: #ef5350; }
|
|
66
|
-
.dd-doctor__status--loading, .dd-doctor__status--pending { color: #ffb74d; }
|
|
67
|
-
.dd-doctor__status--disabled { color: #888; }
|
|
68
|
-
.dd-doctor__table { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
|
|
69
|
-
.dd-doctor__table th { text-align: left; padding: 4px 8px; background: #1a1a2e; color: #888; font-weight: normal; }
|
|
70
|
-
.dd-doctor__table td { padding: 4px 8px; border-bottom: 1px solid #222; }
|
|
71
|
-
.dd-doctor__table .dd-thread-sim { color: #81c784; }
|
|
72
|
-
.dd-doctor__table .dd-thread-renderer { color: #64b5f6; }
|
|
73
|
-
.dd-doctor__table .dd-thread-shared { color: #ffb74d; }
|
|
74
|
-
.dd-doctor__issue { padding: 6px 8px; margin: 4px 0; border-radius: 4px; }
|
|
75
|
-
.dd-doctor__issue--error { background: #3e1e1e; color: #ef5350; }
|
|
76
|
-
.dd-doctor__issue--warning { background: #3e3a1e; color: #ffb74d; }
|
|
77
|
-
.dd-doctor__issue--ok { background: #1e3e2a; color: #81c784; }
|
|
78
|
-
`,
|
|
79
|
-
|
|
80
|
-
script: `(() => {
|
|
81
|
-
const helpers = arguments[0];
|
|
82
|
-
const container = helpers.container;
|
|
83
|
-
const opts = window.__ddDoctorOpts || {};
|
|
84
|
-
let refreshTimer = null;
|
|
85
|
-
|
|
86
|
-
function render() {
|
|
87
|
-
const simModules = opts.getSimModules ? opts.getSimModules() : [];
|
|
88
|
-
const rendererModules = opts.getRendererModules ? opts.getRendererModules() : [];
|
|
89
|
-
let report = opts.getReport ? opts.getReport() : null;
|
|
90
|
-
if (!report && (simModules.length || rendererModules.length)) {
|
|
91
|
-
// Build report inline if no pre-built report provider
|
|
92
|
-
const { buildCrossThreadReport } = window.__ddCrossThread || {};
|
|
93
|
-
if (buildCrossThreadReport) {
|
|
94
|
-
report = buildCrossThreadReport(simModules, rendererModules);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (!report) {
|
|
98
|
-
report = { modules: [...simModules, ...rendererModules], unresolved: [], shared: [], versionConflicts: [] };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Summary
|
|
102
|
-
const summary = container.querySelector('.dd-doctor__summary');
|
|
103
|
-
const simCount = report.modules.filter(p => p.thread === 'sim').length;
|
|
104
|
-
const rendererCount = report.modules.filter(p => p.thread === 'renderer').length;
|
|
105
|
-
const sharedCount = report.shared.length;
|
|
106
|
-
const unresolvedCount = report.unresolved.length;
|
|
107
|
-
const conflictCount = report.versionConflicts.length;
|
|
108
|
-
summary.innerHTML = [
|
|
109
|
-
'<div>Sim modules: <b>' + simCount + '</b></div>',
|
|
110
|
-
'<div>Renderer modules: <b>' + rendererCount + '</b></div>',
|
|
111
|
-
'<div>Shared resources: <b>' + sharedCount + '</b></div>',
|
|
112
|
-
'<div>Unresolved requires: <b style="color:' + (unresolvedCount ? '#ef5350' : '#81c784') + '">' + unresolvedCount + '</b></div>',
|
|
113
|
-
'<div>Version conflicts: <b style="color:' + (conflictCount ? '#ef5350' : '#81c784') + '">' + conflictCount + '</b></div>',
|
|
114
|
-
].join('');
|
|
115
|
-
|
|
116
|
-
// Modules table
|
|
117
|
-
const pluginsDiv = container.querySelector('.dd-doctor__plugins');
|
|
118
|
-
if (report.modules.length === 0) {
|
|
119
|
-
pluginsDiv.innerHTML = '<h3>Modules</h3><p>No modules registered.</p>';
|
|
120
|
-
} else {
|
|
121
|
-
let html = '<h3>Modules (' + report.modules.length + ')</h3>';
|
|
122
|
-
html += '<table class="dd-doctor__table"><thead><tr><th>Name</th><th>Version</th><th>Thread</th><th>Provides</th><th>Requires</th><th>Status</th></tr></thead><tbody>';
|
|
123
|
-
for (const p of report.modules) {
|
|
124
|
-
const threadClass = 'dd-thread-' + esc(p.thread);
|
|
125
|
-
html += '<tr><td>' + esc(p.name) + '</td><td>' + esc(p.version) + '</td><td class="' + threadClass + '">' + esc(p.thread) + '</td><td>' + esc(p.provides.join(', ') || '—') + '</td><td>' + esc(p.requires.join(', ') || '—') + '</td><td>' + (p.active ? '✓ active' : 'inactive') + '</td></tr>';
|
|
126
|
-
}
|
|
127
|
-
html += '</tbody></table>';
|
|
128
|
-
pluginsDiv.innerHTML = html;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// User-authored plugins (modding system)
|
|
132
|
-
const userPluginsDiv = container.querySelector('.dd-doctor__user-plugins');
|
|
133
|
-
const plugins = opts.getPlugins ? opts.getPlugins() : [];
|
|
134
|
-
if (plugins.length === 0) {
|
|
135
|
-
userPluginsDiv.innerHTML = '';
|
|
136
|
-
} else {
|
|
137
|
-
let phtml = '<h3>Plugins (' + plugins.length + ')</h3>';
|
|
138
|
-
phtml += '<table class="dd-doctor__table"><thead><tr><th>ID</th><th>Version</th><th>Format</th><th>Tier</th><th>Thread</th><th>Permissions</th><th>Status</th></tr></thead><tbody>';
|
|
139
|
-
for (const p of plugins) {
|
|
140
|
-
const fmtBadge = '<span class="dd-doctor__badge dd-doctor__badge--format">' + esc(p.format) + '</span>';
|
|
141
|
-
const tierBadge = '<span class="dd-doctor__badge dd-doctor__badge--tier">' + esc(p.tier) + '</span>';
|
|
142
|
-
const threadBadge = '<span class="dd-doctor__badge dd-doctor__badge--thread">' + esc(p.thread) + '</span>';
|
|
143
|
-
const permBadges = p.permissions.map(function(perm) { return '<span class="dd-doctor__badge dd-doctor__badge--perm">' + esc(perm) + '</span>'; }).join('');
|
|
144
|
-
const statusClass = 'dd-doctor__status--' + esc(p.status);
|
|
145
|
-
const statusText = esc(p.status + (p.error ? ': ' + p.error : ''));
|
|
146
|
-
phtml += '<tr><td>' + esc(p.id) + '</td><td>' + esc(p.version) + '</td><td>' + fmtBadge + '</td><td>' + tierBadge + '</td><td>' + threadBadge + '</td><td>' + (permBadges || '—') + '</td><td class="' + statusClass + '">' + statusText + '</td></tr>';
|
|
147
|
-
}
|
|
148
|
-
phtml += '</tbody></table>';
|
|
149
|
-
userPluginsDiv.innerHTML = phtml;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Issues
|
|
153
|
-
const issuesDiv = container.querySelector('.dd-doctor__issues');
|
|
154
|
-
let issuesHtml = '<h3>Diagnostics</h3>';
|
|
155
|
-
if (report.unresolved.length > 0) {
|
|
156
|
-
issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--error">Unresolved requires: ' + esc(report.unresolved.join(', ')) + '</div>';
|
|
157
|
-
}
|
|
158
|
-
if (report.versionConflicts.length > 0) {
|
|
159
|
-
for (const vc of report.versionConflicts) {
|
|
160
|
-
issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--error">Version conflict: ' + esc(vc.name) + ' (sim=' + esc(vc.simVersion) + ', renderer=' + esc(vc.rendererVersion) + ')</div>';
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (report.shared.length > 0) {
|
|
164
|
-
issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--ok">Shared resources: ' + esc(report.shared.join(', ')) + '</div>';
|
|
165
|
-
}
|
|
166
|
-
if (report.unresolved.length === 0 && report.versionConflicts.length === 0) {
|
|
167
|
-
issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--ok">✓ No issues detected</div>';
|
|
168
|
-
}
|
|
169
|
-
issuesDiv.innerHTML = issuesHtml;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
container.querySelector('.dd-doctor__refresh').addEventListener('click', render);
|
|
173
|
-
render();
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
onActivate() { refreshTimer = setInterval(render, 2000); },
|
|
177
|
-
onDeactivate() { if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; } },
|
|
178
|
-
onDestroy() { if (refreshTimer) clearInterval(refreshTimer); }
|
|
179
|
-
};
|
|
180
|
-
})`,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// LabelOverlay — HTML-based billboard labels for dev mode
|
|
3
|
-
// Generic DOM management + projection; game provides labels via ILabelProvider.
|
|
4
|
-
// ============================================================================
|
|
5
|
-
|
|
6
|
-
import { calculateViewProj, type RenderSurface } from "@downdraft/engine";
|
|
7
|
-
import type { ILabelProvider } from "./types";
|
|
8
|
-
|
|
9
|
-
interface LabelEntry {
|
|
10
|
-
el: HTMLDivElement;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const MAX_LABELS = 512;
|
|
14
|
-
|
|
15
|
-
export class LabelOverlay {
|
|
16
|
-
private container: HTMLDivElement;
|
|
17
|
-
private labels: Map<string, LabelEntry> = new Map();
|
|
18
|
-
private active = false;
|
|
19
|
-
private canvas: HTMLCanvasElement;
|
|
20
|
-
private provider: ILabelProvider | null = null;
|
|
21
|
-
|
|
22
|
-
constructor(surface: RenderSurface) {
|
|
23
|
-
// DOM-overlay widget — the canvas/element members it touches
|
|
24
|
-
// (parentElement, 2d overlay canvas) are DOM-host concepts; on native
|
|
25
|
-
// they degrade to the compat stubs (display:none anyway).
|
|
26
|
-
const canvas = surface as unknown as HTMLCanvasElement;
|
|
27
|
-
this.canvas = canvas;
|
|
28
|
-
this.container = document.createElement("div");
|
|
29
|
-
this.container.className = "label-overlay";
|
|
30
|
-
this.container.style.cssText = `
|
|
31
|
-
position: absolute;
|
|
32
|
-
top: 0; left: 0;
|
|
33
|
-
width: 100%; height: 100%;
|
|
34
|
-
pointer-events: none;
|
|
35
|
-
overflow: hidden;
|
|
36
|
-
z-index: 0;
|
|
37
|
-
display: none;
|
|
38
|
-
`;
|
|
39
|
-
const parent = canvas.parentElement;
|
|
40
|
-
if (parent) {
|
|
41
|
-
parent.style.position = parent.style.position || "relative";
|
|
42
|
-
parent.appendChild(this.container);
|
|
43
|
-
} else {
|
|
44
|
-
document.body.appendChild(this.container);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
setProvider(provider: ILabelProvider): void {
|
|
49
|
-
this.provider = provider;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
setActive(active: boolean): void {
|
|
53
|
-
this.active = active;
|
|
54
|
-
this.container.style.display = active ? "block" : "none";
|
|
55
|
-
if (!active) {
|
|
56
|
-
this.clearLabels();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
isActive(): boolean {
|
|
61
|
-
return this.active;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private clearLabels(): void {
|
|
65
|
-
for (const entry of this.labels.values()) {
|
|
66
|
-
entry.el.remove();
|
|
67
|
-
}
|
|
68
|
-
this.labels.clear();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private getOrCreateLabel(key: string): HTMLDivElement | null {
|
|
72
|
-
let entry = this.labels.get(key);
|
|
73
|
-
if (entry) return entry.el;
|
|
74
|
-
|
|
75
|
-
if (this.labels.size >= MAX_LABELS) return null;
|
|
76
|
-
|
|
77
|
-
const el = document.createElement("div");
|
|
78
|
-
el.className = "dev-label";
|
|
79
|
-
el.style.cssText = `
|
|
80
|
-
position: absolute;
|
|
81
|
-
font-family: monospace;
|
|
82
|
-
font-size: 11px;
|
|
83
|
-
color: #fff;
|
|
84
|
-
background: rgba(0, 0, 0, 0.65);
|
|
85
|
-
border-radius: 3px;
|
|
86
|
-
padding: 1px 5px;
|
|
87
|
-
white-space: nowrap;
|
|
88
|
-
pointer-events: none;
|
|
89
|
-
transform: translate(-50%, -100%);
|
|
90
|
-
will-change: transform, left, top;
|
|
91
|
-
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
92
|
-
`;
|
|
93
|
-
this.container.appendChild(el);
|
|
94
|
-
this.labels.set(key, { el });
|
|
95
|
-
return el;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private projectToScreen(
|
|
99
|
-
worldX: number, worldY: number, worldZ: number,
|
|
100
|
-
viewProj: Float32Array,
|
|
101
|
-
canvasW: number, canvasH: number,
|
|
102
|
-
): { x: number; y: number; behind: boolean } | null {
|
|
103
|
-
const x = viewProj[0] * worldX + viewProj[4] * worldY + viewProj[8] * worldZ + viewProj[12];
|
|
104
|
-
const y = viewProj[1] * worldX + viewProj[5] * worldY + viewProj[9] * worldZ + viewProj[13];
|
|
105
|
-
const w = viewProj[3] * worldX + viewProj[7] * worldY + viewProj[11] * worldZ + viewProj[15];
|
|
106
|
-
|
|
107
|
-
if (w <= 0) return { x: 0, y: 0, behind: true };
|
|
108
|
-
|
|
109
|
-
const ndcX = x / w;
|
|
110
|
-
const ndcY = y / w;
|
|
111
|
-
|
|
112
|
-
const screenX = (ndcX + 1) * 0.5 * canvasW;
|
|
113
|
-
const screenY = (1 - ndcY) * 0.5 * canvasH;
|
|
114
|
-
|
|
115
|
-
return { x: screenX, y: screenY, behind: false };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
update(camera: Parameters<typeof calculateViewProj>[0]): void {
|
|
119
|
-
if (!this.active || !this.provider) return;
|
|
120
|
-
|
|
121
|
-
const viewProj = calculateViewProj(camera);
|
|
122
|
-
const canvasW = this.canvas.clientWidth;
|
|
123
|
-
const canvasH = this.canvas.clientHeight;
|
|
124
|
-
|
|
125
|
-
const labels = this.provider.getLabels();
|
|
126
|
-
const usedKeys = new Set<string>();
|
|
127
|
-
|
|
128
|
-
for (let _i = 0, _it = labels, _n = _it.length; _i < _n; _i++) { const label = _it[_i];
|
|
129
|
-
usedKeys.add(label.key);
|
|
130
|
-
const proj = this.projectToScreen(label.x, label.y, label.z, viewProj, canvasW, canvasH);
|
|
131
|
-
if (!proj || proj.behind) continue;
|
|
132
|
-
|
|
133
|
-
const el = this.getOrCreateLabel(label.key);
|
|
134
|
-
if (!el) continue;
|
|
135
|
-
|
|
136
|
-
el.textContent = label.text;
|
|
137
|
-
el.style.left = `${proj.x}px`;
|
|
138
|
-
el.style.top = `${proj.y}px`;
|
|
139
|
-
el.style.color = label.color;
|
|
140
|
-
el.style.display = "block";
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
for (const [key, entry] of this.labels.entries()) {
|
|
144
|
-
if (!usedKeys.has(key)) {
|
|
145
|
-
entry.el.style.display = "none";
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
destroy(): void {
|
|
151
|
-
this.clearLabels();
|
|
152
|
-
this.container.remove();
|
|
153
|
-
}
|
|
154
|
-
}
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// createMaterialStatsPanelExtension — reusable DevTools "Materials" tab for
|
|
3
|
-
// any game using the unified material system.
|
|
4
|
-
//
|
|
5
|
-
// Shows:
|
|
6
|
-
// - Registered material count, material types breakdown
|
|
7
|
-
// - Variant cache size, LRU hits/misses
|
|
8
|
-
// - Shader graph node count, compiled WGSL size
|
|
9
|
-
// - GPU texture count/bytes (from GPUResourceTracker)
|
|
10
|
-
//
|
|
11
|
-
// Gated by requiredMethods: ["getMaterialStats"]. The framework auto-provides
|
|
12
|
-
// getMaterialStats() from the renderer's MaterialLibrary / variant cache
|
|
13
|
-
// (feature-detected in createDevToolsRendererAdapter or registered via
|
|
14
|
-
// devtools.registerDataFeed).
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
import type { IDevToolsPanelExtension } from "./types";
|
|
18
|
-
|
|
19
|
-
export interface MaterialStatsPanelOptions {
|
|
20
|
-
tabLabel?: string;
|
|
21
|
-
tabTooltip?: string;
|
|
22
|
-
order?: number;
|
|
23
|
-
/** Optional callback to append custom rows to the stats grid. */
|
|
24
|
-
extraRows?: (stats: any) => [string, string][];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function createMaterialStatsPanelExtension(
|
|
28
|
-
opts: MaterialStatsPanelOptions = {},
|
|
29
|
-
): IDevToolsPanelExtension {
|
|
30
|
-
const tabLabel = opts.tabLabel ?? "Materials";
|
|
31
|
-
const tabTooltip = opts.tabTooltip ?? "Material system stats (library, variants, GPU textures)";
|
|
32
|
-
const order = opts.order ?? 25;
|
|
33
|
-
const hasExtra = typeof opts.extraRows === "function";
|
|
34
|
-
const extraRowsSrc = hasExtra ? opts.extraRows!.toString() : "null";
|
|
35
|
-
|
|
36
|
-
const html = `
|
|
37
|
-
<div class="panel-header">
|
|
38
|
-
<h3>Materials</h3>
|
|
39
|
-
<span id="mat-status-badge" class="perf-status">Waiting for data...</span>
|
|
40
|
-
</div>
|
|
41
|
-
<div id="mat-stats-panel">
|
|
42
|
-
<div class="debug-section">
|
|
43
|
-
<h4>Library</h4>
|
|
44
|
-
<div id="mat-library-grid" class="debug-grid"></div>
|
|
45
|
-
</div>
|
|
46
|
-
<div class="debug-section">
|
|
47
|
-
<h4>Variants</h4>
|
|
48
|
-
<div id="mat-variants-grid" class="debug-grid"></div>
|
|
49
|
-
</div>
|
|
50
|
-
<div class="debug-section">
|
|
51
|
-
<h4>GPU Textures</h4>
|
|
52
|
-
<div id="mat-textures-grid" class="debug-grid"></div>
|
|
53
|
-
</div>
|
|
54
|
-
<div class="debug-section">
|
|
55
|
-
<h4>Shader Graph</h4>
|
|
56
|
-
<div id="mat-graph-grid" class="debug-grid"></div>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
`;
|
|
60
|
-
|
|
61
|
-
const css = `
|
|
62
|
-
#mat-stats-panel {
|
|
63
|
-
flex: 1;
|
|
64
|
-
overflow-y: auto;
|
|
65
|
-
padding: 8px 12px;
|
|
66
|
-
}
|
|
67
|
-
#mat-stats-panel .debug-section {
|
|
68
|
-
margin-bottom: 12px;
|
|
69
|
-
}
|
|
70
|
-
#mat-stats-panel .debug-section h4 {
|
|
71
|
-
font-size: 12px;
|
|
72
|
-
color: #569cd6;
|
|
73
|
-
margin: 8px 0 4px 0;
|
|
74
|
-
border-bottom: 1px solid #333;
|
|
75
|
-
padding-bottom: 2px;
|
|
76
|
-
}
|
|
77
|
-
#mat-status-badge.running { color: #98c379; }
|
|
78
|
-
#mat-status-badge.empty { color: #e5c07b; }
|
|
79
|
-
`;
|
|
80
|
-
|
|
81
|
-
const script = `
|
|
82
|
-
(function(DevToolsPanel) {
|
|
83
|
-
var callInspector = DevToolsPanel.callInspector;
|
|
84
|
-
var evalInPage = DevToolsPanel.evalInPage;
|
|
85
|
-
var escapeHtml = DevToolsPanel.escapeHtml;
|
|
86
|
-
var fmtVal = DevToolsPanel.fmtVal;
|
|
87
|
-
var debugGridHtml = DevToolsPanel.debugGridHtml;
|
|
88
|
-
|
|
89
|
-
var extraRowsFn = ${extraRowsSrc};
|
|
90
|
-
|
|
91
|
-
function refreshMatStats() {
|
|
92
|
-
var libEl = document.getElementById("mat-library-grid");
|
|
93
|
-
var varEl = document.getElementById("mat-variants-grid");
|
|
94
|
-
var texEl = document.getElementById("mat-textures-grid");
|
|
95
|
-
var graphEl = document.getElementById("mat-graph-grid");
|
|
96
|
-
var statusEl = document.getElementById("mat-status-badge");
|
|
97
|
-
|
|
98
|
-
evalInPage(
|
|
99
|
-
"window.__sceneInspector ? JSON.stringify(window.__sceneInspector.getMaterialStats()) : null",
|
|
100
|
-
function (result, err) {
|
|
101
|
-
if (err || !result) {
|
|
102
|
-
if (libEl) libEl.innerHTML = '<div class="debug-label">Not available</div>';
|
|
103
|
-
if (statusEl) { statusEl.textContent = "Not available"; statusEl.className = "perf-status"; }
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
try {
|
|
107
|
-
var s = JSON.parse(result);
|
|
108
|
-
if (!s) {
|
|
109
|
-
if (libEl) libEl.innerHTML = '<div class="debug-label">Not available</div>';
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Library stats
|
|
114
|
-
var libRows = [
|
|
115
|
-
["Registered", s.library?.count ?? 0],
|
|
116
|
-
["Types", s.library?.types?.join(", ") ?? "—"],
|
|
117
|
-
];
|
|
118
|
-
if (libEl) libEl.innerHTML = debugGridHtml(libRows);
|
|
119
|
-
|
|
120
|
-
// Variant stats
|
|
121
|
-
var varRows = [
|
|
122
|
-
["Cached Pipelines", s.variants?.cachedPipelines ?? 0],
|
|
123
|
-
["Max Pipelines", s.variants?.maxPipelines ?? 0],
|
|
124
|
-
["LRU Hits", s.variants?.lruHits ?? 0],
|
|
125
|
-
["LRU Misses", s.variants?.lruMisses ?? 0],
|
|
126
|
-
["Permutation Count", s.variants?.permutationCount ?? 0],
|
|
127
|
-
];
|
|
128
|
-
if (varEl) varEl.innerHTML = debugGridHtml(varRows);
|
|
129
|
-
|
|
130
|
-
// GPU textures
|
|
131
|
-
var texRows = [
|
|
132
|
-
["Texture Count", s.textures?.count ?? 0],
|
|
133
|
-
["Texture Memory", fmtVal((s.textures?.bytes ?? 0) / 1048576, 1) + " MB"],
|
|
134
|
-
["Bindless Entries", s.textures?.bindlessEntries ?? 0],
|
|
135
|
-
];
|
|
136
|
-
if (texEl) texEl.innerHTML = debugGridHtml(texRows);
|
|
137
|
-
|
|
138
|
-
// Shader graph
|
|
139
|
-
var graphRows = [
|
|
140
|
-
["Graph Materials", s.graph?.materialCount ?? 0],
|
|
141
|
-
["Total Nodes", s.graph?.totalNodes ?? 0],
|
|
142
|
-
["Total Connections", s.graph?.totalConnections ?? 0],
|
|
143
|
-
["Compiled WGSL Size", fmtVal((s.graph?.compiledWgslBytes ?? 0) / 1024, 1) + " KB"],
|
|
144
|
-
];
|
|
145
|
-
if (graphEl) graphEl.innerHTML = debugGridHtml(graphRows);
|
|
146
|
-
|
|
147
|
-
// Extra rows
|
|
148
|
-
if (extraRowsFn && s) {
|
|
149
|
-
try {
|
|
150
|
-
var extra = extraRowsFn(s);
|
|
151
|
-
if (extra && extra.length) {
|
|
152
|
-
var extraHtml = debugGridHtml(extra);
|
|
153
|
-
if (graphEl) graphEl.innerHTML += extraHtml;
|
|
154
|
-
}
|
|
155
|
-
} catch (e) { /* ignore */ }
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (statusEl) {
|
|
159
|
-
var count = s.library?.count ?? 0;
|
|
160
|
-
statusEl.textContent = count > 0 ? count + " materials" : "No materials";
|
|
161
|
-
statusEl.className = "perf-status " + (count > 0 ? "running" : "empty");
|
|
162
|
-
}
|
|
163
|
-
} catch (e) {
|
|
164
|
-
if (libEl) libEl.innerHTML = '<div class="debug-label">Error: ' + escapeHtml(String(e)) + '</div>';
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
onActivate: function () { refreshMatStats(); },
|
|
172
|
-
onRefresh: refreshMatStats,
|
|
173
|
-
onDeactivate: function () {},
|
|
174
|
-
};
|
|
175
|
-
})
|
|
176
|
-
`;
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
id: "material-stats",
|
|
180
|
-
tabLabel,
|
|
181
|
-
tabTooltip,
|
|
182
|
-
order,
|
|
183
|
-
requiredMethods: ["getMaterialStats"],
|
|
184
|
-
html,
|
|
185
|
-
css,
|
|
186
|
-
script,
|
|
187
|
-
};
|
|
188
|
-
}
|