@phalanx-engine/ecs 0.1.0

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.
Files changed (89) hide show
  1. package/README.md +666 -0
  2. package/dist/Component.d.ts +7 -0
  3. package/dist/Component.d.ts.map +1 -0
  4. package/dist/Component.js +7 -0
  5. package/dist/Entity.d.ts +25 -0
  6. package/dist/Entity.d.ts.map +1 -0
  7. package/dist/Entity.js +58 -0
  8. package/dist/EntityManager.d.ts +36 -0
  9. package/dist/EntityManager.d.ts.map +1 -0
  10. package/dist/EntityManager.js +218 -0
  11. package/dist/EventBus.d.ts +15 -0
  12. package/dist/EventBus.d.ts.map +1 -0
  13. package/dist/EventBus.js +69 -0
  14. package/dist/GameSystem.d.ts +23 -0
  15. package/dist/GameSystem.d.ts.map +1 -0
  16. package/dist/GameSystem.js +43 -0
  17. package/dist/GameWorld.d.ts +69 -0
  18. package/dist/GameWorld.d.ts.map +1 -0
  19. package/dist/GameWorld.js +237 -0
  20. package/dist/IAbilitySystem.d.ts +18 -0
  21. package/dist/IAbilitySystem.d.ts.map +1 -0
  22. package/dist/IAbilitySystem.js +1 -0
  23. package/dist/IPhysicsWorld.d.ts +21 -0
  24. package/dist/IPhysicsWorld.d.ts.map +1 -0
  25. package/dist/IPhysicsWorld.js +1 -0
  26. package/dist/ISystemLifecycleHooks.d.ts +18 -0
  27. package/dist/ISystemLifecycleHooks.d.ts.map +1 -0
  28. package/dist/ISystemLifecycleHooks.js +12 -0
  29. package/dist/ITickFrameProvider.d.ts +24 -0
  30. package/dist/ITickFrameProvider.d.ts.map +1 -0
  31. package/dist/ITickFrameProvider.js +1 -0
  32. package/dist/SoAComponent.d.ts +22 -0
  33. package/dist/SoAComponent.d.ts.map +1 -0
  34. package/dist/SoAComponent.js +59 -0
  35. package/dist/SoAComponentStore.d.ts +41 -0
  36. package/dist/SoAComponentStore.d.ts.map +1 -0
  37. package/dist/SoAComponentStore.js +253 -0
  38. package/dist/SoASchema.d.ts +22 -0
  39. package/dist/SoASchema.d.ts.map +1 -0
  40. package/dist/SoASchema.js +33 -0
  41. package/dist/SystemContext.d.ts +18 -0
  42. package/dist/SystemContext.d.ts.map +1 -0
  43. package/dist/SystemContext.js +18 -0
  44. package/dist/SystemRegistry.d.ts +20 -0
  45. package/dist/SystemRegistry.d.ts.map +1 -0
  46. package/dist/SystemRegistry.js +73 -0
  47. package/dist/TickFrameManager.d.ts +35 -0
  48. package/dist/TickFrameManager.d.ts.map +1 -0
  49. package/dist/TickFrameManager.js +130 -0
  50. package/dist/debug/DebugDataProvider.d.ts +26 -0
  51. package/dist/debug/DebugDataProvider.d.ts.map +1 -0
  52. package/dist/debug/DebugDataProvider.js +128 -0
  53. package/dist/debug/DebugPanel.d.ts +57 -0
  54. package/dist/debug/DebugPanel.d.ts.map +1 -0
  55. package/dist/debug/DebugPanel.js +482 -0
  56. package/dist/debug/index.d.ts +4 -0
  57. package/dist/debug/index.d.ts.map +1 -0
  58. package/dist/debug/index.js +2 -0
  59. package/dist/debug/types.d.ts +47 -0
  60. package/dist/debug/types.d.ts.map +1 -0
  61. package/dist/debug/types.js +1 -0
  62. package/dist/index.d.ts +27 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +15 -0
  65. package/dist/pool/EntityPool.d.ts +21 -0
  66. package/dist/pool/EntityPool.d.ts.map +1 -0
  67. package/dist/pool/EntityPool.js +86 -0
  68. package/dist/pool/IPoolable.d.ts +4 -0
  69. package/dist/pool/IPoolable.d.ts.map +1 -0
  70. package/dist/pool/IPoolable.js +1 -0
  71. package/dist/pool/IPoolableComponent.d.ts +7 -0
  72. package/dist/pool/IPoolableComponent.d.ts.map +1 -0
  73. package/dist/pool/IPoolableComponent.js +4 -0
  74. package/dist/pool/IPoolableEntity.d.ts +6 -0
  75. package/dist/pool/IPoolableEntity.d.ts.map +1 -0
  76. package/dist/pool/IPoolableEntity.js +1 -0
  77. package/dist/pool/ObjectPool.d.ts +20 -0
  78. package/dist/pool/ObjectPool.d.ts.map +1 -0
  79. package/dist/pool/ObjectPool.js +76 -0
  80. package/dist/pool/PoolManager.d.ts +20 -0
  81. package/dist/pool/PoolManager.d.ts.map +1 -0
  82. package/dist/pool/PoolManager.js +92 -0
  83. package/dist/pool/index.d.ts +10 -0
  84. package/dist/pool/index.d.ts.map +1 -0
  85. package/dist/pool/index.js +5 -0
  86. package/dist/pool/types.d.ts +31 -0
  87. package/dist/pool/types.d.ts.map +1 -0
  88. package/dist/pool/types.js +8 -0
  89. package/package.json +46 -0
@@ -0,0 +1,128 @@
1
+ import { calculateSchemaByteSize } from '../SoASchema';
2
+ const DEFAULT_UPDATE_INTERVAL = 500;
3
+ export class DebugDataProvider {
4
+ entityManager;
5
+ pools;
6
+ updateInterval;
7
+ subscribers = new Set();
8
+ intervalId = null;
9
+ _paused = false;
10
+ set paused(value) {
11
+ this._paused = value;
12
+ }
13
+ constructor(entityManager, pools, config) {
14
+ this.entityManager = entityManager;
15
+ this.pools = pools;
16
+ this.updateInterval = config?.updateInterval ?? DEFAULT_UPDATE_INTERVAL;
17
+ }
18
+ subscribe(callback) {
19
+ this.subscribers.add(callback);
20
+ return () => {
21
+ this.subscribers.delete(callback);
22
+ };
23
+ }
24
+ getSnapshot() {
25
+ return this.collectSnapshot();
26
+ }
27
+ start() {
28
+ if (this.intervalId !== null)
29
+ return;
30
+ if (this.updateInterval <= 0)
31
+ return;
32
+ this.intervalId = setInterval(() => {
33
+ this.push();
34
+ }, this.updateInterval);
35
+ }
36
+ stop() {
37
+ if (this.intervalId !== null) {
38
+ clearInterval(this.intervalId);
39
+ this.intervalId = null;
40
+ }
41
+ }
42
+ dispose() {
43
+ this.stop();
44
+ this.subscribers.clear();
45
+ }
46
+ push() {
47
+ if (this.subscribers.size === 0)
48
+ return;
49
+ const snapshot = this.collectSnapshot();
50
+ for (const cb of this.subscribers) {
51
+ cb(snapshot);
52
+ }
53
+ }
54
+ collectSnapshot() {
55
+ const soaStores = this.collectSoAStores();
56
+ return {
57
+ timestamp: Date.now(),
58
+ world: {
59
+ entityCount: this.entityManager.count,
60
+ soaStoreCount: soaStores.length,
61
+ paused: this._paused,
62
+ },
63
+ entities: this.collectEntities(),
64
+ soaStores,
65
+ pools: this.collectPools(),
66
+ };
67
+ }
68
+ collectEntities() {
69
+ const entities = this.entityManager.getAllEntities();
70
+ const result = [];
71
+ for (const entity of entities) {
72
+ const components = [];
73
+ for (const [typeSymbol, component] of entity.getComponents()) {
74
+ const data = {};
75
+ for (const key of Object.keys(component)) {
76
+ if (key === 'type')
77
+ continue;
78
+ data[key] = component[key];
79
+ }
80
+ components.push({
81
+ typeName: typeSymbol.description ?? 'unknown',
82
+ typeSymbol,
83
+ data,
84
+ });
85
+ }
86
+ result.push({
87
+ id: entity.id,
88
+ destroyed: entity.isDestroyed,
89
+ components,
90
+ });
91
+ }
92
+ return result;
93
+ }
94
+ collectSoAStores() {
95
+ const stores = this.entityManager.getAllSoAStores();
96
+ const result = [];
97
+ for (const store of stores.values()) {
98
+ const schema = store.schema;
99
+ const entities = [];
100
+ for (const entityId of store.entityIds()) {
101
+ const fields = store.get(entityId);
102
+ if (fields) {
103
+ entities.push({ entityId, fields: fields });
104
+ }
105
+ }
106
+ result.push({
107
+ name: schema.type.description ?? 'unknown',
108
+ fieldNames: [...schema.fieldNames],
109
+ fieldTypes: { ...schema.fieldTypes },
110
+ count: store.count,
111
+ capacity: store.capacity,
112
+ bytesPerEntity: calculateSchemaByteSize(schema),
113
+ entities,
114
+ });
115
+ }
116
+ return result;
117
+ }
118
+ collectPools() {
119
+ if (!this.pools)
120
+ return [];
121
+ const result = [];
122
+ const stats = this.pools.getStats();
123
+ for (const [typeKey, poolStats] of stats) {
124
+ result.push({ typeKey, stats: { ...poolStats } });
125
+ }
126
+ return result;
127
+ }
128
+ }
@@ -0,0 +1,57 @@
1
+ import type { DebugDataProvider } from './DebugDataProvider';
2
+ import type { DebugPanelConfig } from './types';
3
+ export declare class DebugPanel {
4
+ private readonly provider;
5
+ private readonly toggleKey;
6
+ private readonly unsubscribe;
7
+ private readonly keydownHandler;
8
+ private readonly mouseMoveHandler;
9
+ private readonly mouseUpHandler;
10
+ private readonly root;
11
+ private readonly titleBar;
12
+ private readonly titleText;
13
+ private readonly toggleBtn;
14
+ private readonly body;
15
+ private readonly worldSection;
16
+ private readonly entityCountEl;
17
+ private readonly soaCountEl;
18
+ private readonly pausedEl;
19
+ private readonly entitiesSection;
20
+ private readonly entitiesHeader;
21
+ private readonly entitiesBody;
22
+ private readonly entitiesSummary;
23
+ private readonly soaSection;
24
+ private readonly soaHeader;
25
+ private readonly soaBody;
26
+ private readonly soaSummary;
27
+ private readonly poolSection;
28
+ private readonly poolHeader;
29
+ private readonly poolBody;
30
+ private readonly poolSummary;
31
+ private collapsed;
32
+ private entitiesSectionCollapsed;
33
+ private soaSectionCollapsed;
34
+ private poolSectionCollapsed;
35
+ private expandedEntities;
36
+ private dragging;
37
+ private dragOffsetX;
38
+ private dragOffsetY;
39
+ constructor(provider: DebugDataProvider, config?: DebugPanelConfig);
40
+ destroy(): void;
41
+ get element(): HTMLDivElement;
42
+ private renderSnapshot;
43
+ private renderEntities;
44
+ private renderSoAStores;
45
+ private renderPoolStats;
46
+ private toggleCollapse;
47
+ private updateToggleBtn;
48
+ private applySectionCollapse;
49
+ private buildCollapsibleSection;
50
+ private createLabel;
51
+ private createSeparator;
52
+ private formatValue;
53
+ private applyRootStyles;
54
+ private applyTitleBarStyles;
55
+ private applyToggleBtnStyles;
56
+ }
57
+ //# sourceMappingURL=DebugPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DebugPanel.d.ts","sourceRoot":"","sources":["../../src/debug/DebugPanel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAKV,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAejB,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA0B;IAC3D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAa;IAG5C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;IAC5C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;IAGtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiB;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAE3C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiB;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAElD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;IAE7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkB;IAG9C,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,wBAAwB,CAAS;IACzC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,gBAAgB,CAA0B;IAGlD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAK;gBAEZ,QAAQ,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,gBAAgB;IAmK3D,OAAO,IAAI,IAAI;IAatB,IAAW,OAAO,IAAI,cAAc,CAEnC;IAID,OAAO,CAAC,cAAc;IAoCtB,OAAO,CAAC,cAAc;IAiEtB,OAAO,CAAC,eAAe;IA4FvB,OAAO,CAAC,eAAe;IAmEvB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,oBAAoB;IAc5B,OAAO,CAAC,uBAAuB;IA6C/B,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,oBAAoB;CAO7B"}
@@ -0,0 +1,482 @@
1
+ const FONT_MONO = "'Consolas', 'Monaco', 'Courier New', monospace";
2
+ const COLOR_BG = 'rgba(15, 15, 20, 0.92)';
3
+ const COLOR_TEXT = '#c8ccd0';
4
+ const COLOR_ACCENT = '#4fc3f7';
5
+ const COLOR_WARNING = '#ffb74d';
6
+ export class DebugPanel {
7
+ provider;
8
+ toggleKey;
9
+ unsubscribe;
10
+ keydownHandler;
11
+ mouseMoveHandler;
12
+ mouseUpHandler;
13
+ root;
14
+ titleBar;
15
+ titleText;
16
+ toggleBtn;
17
+ body;
18
+ worldSection;
19
+ entityCountEl;
20
+ soaCountEl;
21
+ pausedEl;
22
+ entitiesSection;
23
+ entitiesHeader;
24
+ entitiesBody;
25
+ entitiesSummary;
26
+ soaSection;
27
+ soaHeader;
28
+ soaBody;
29
+ soaSummary;
30
+ poolSection;
31
+ poolHeader;
32
+ poolBody;
33
+ poolSummary;
34
+ collapsed;
35
+ entitiesSectionCollapsed = false;
36
+ soaSectionCollapsed = false;
37
+ poolSectionCollapsed = false;
38
+ expandedEntities = new Set();
39
+ dragging = false;
40
+ dragOffsetX = 0;
41
+ dragOffsetY = 0;
42
+ constructor(provider, config) {
43
+ this.provider = provider;
44
+ this.toggleKey = config?.toggleKey ?? '`';
45
+ this.collapsed = config?.startCollapsed ?? false;
46
+ this.root = document.createElement('div');
47
+ this.applyRootStyles();
48
+ this.titleBar = document.createElement('div');
49
+ this.applyTitleBarStyles();
50
+ this.titleText = document.createElement('span');
51
+ this.titleText.textContent = '\u2699 Phalanx Debug';
52
+ this.toggleBtn = document.createElement('span');
53
+ this.applyToggleBtnStyles();
54
+ this.updateToggleBtn();
55
+ this.titleBar.appendChild(this.titleText);
56
+ this.titleBar.appendChild(this.toggleBtn);
57
+ this.root.appendChild(this.titleBar);
58
+ this.body = document.createElement('div');
59
+ this.body.style.padding = '6px 8px';
60
+ this.body.style.maxHeight = '80vh';
61
+ this.body.style.overflowY = 'auto';
62
+ this.body.style.display = this.collapsed ? 'none' : 'block';
63
+ this.root.appendChild(this.body);
64
+ this.worldSection = document.createElement('div');
65
+ this.worldSection.style.marginBottom = '6px';
66
+ this.worldSection.style.paddingBottom = '4px';
67
+ this.worldSection.style.borderBottom = '1px solid rgba(255,255,255,0.1)';
68
+ const worldLabel = document.createElement('div');
69
+ worldLabel.style.fontSize = '12px';
70
+ worldLabel.style.fontWeight = 'bold';
71
+ worldLabel.style.color = COLOR_ACCENT;
72
+ worldLabel.style.marginBottom = '2px';
73
+ worldLabel.textContent = 'World Overview';
74
+ this.worldSection.appendChild(worldLabel);
75
+ const worldDataRow = document.createElement('div');
76
+ worldDataRow.style.fontSize = '11px';
77
+ this.entityCountEl = document.createElement('span');
78
+ this.soaCountEl = document.createElement('span');
79
+ this.pausedEl = document.createElement('span');
80
+ worldDataRow.appendChild(this.createLabel('Entities: '));
81
+ worldDataRow.appendChild(this.entityCountEl);
82
+ worldDataRow.appendChild(this.createSeparator());
83
+ worldDataRow.appendChild(this.createLabel('SoA Stores: '));
84
+ worldDataRow.appendChild(this.soaCountEl);
85
+ worldDataRow.appendChild(this.createSeparator());
86
+ worldDataRow.appendChild(this.pausedEl);
87
+ this.worldSection.appendChild(worldDataRow);
88
+ this.body.appendChild(this.worldSection);
89
+ const entitiesResult = this.buildCollapsibleSection('Entities');
90
+ this.entitiesSection = entitiesResult.section;
91
+ this.entitiesHeader = entitiesResult.header;
92
+ this.entitiesBody = entitiesResult.body;
93
+ this.entitiesSummary = entitiesResult.summary;
94
+ this.body.appendChild(this.entitiesSection);
95
+ const soaResult = this.buildCollapsibleSection('SoA Stores');
96
+ this.soaSection = soaResult.section;
97
+ this.soaHeader = soaResult.header;
98
+ this.soaBody = soaResult.body;
99
+ this.soaSummary = soaResult.summary;
100
+ this.body.appendChild(this.soaSection);
101
+ const poolResult = this.buildCollapsibleSection('Pool Stats');
102
+ this.poolSection = poolResult.section;
103
+ this.poolHeader = poolResult.header;
104
+ this.poolBody = poolResult.body;
105
+ this.poolSummary = poolResult.summary;
106
+ this.body.appendChild(this.poolSection);
107
+ document.body.appendChild(this.root);
108
+ this.unsubscribe = this.provider.subscribe((snapshot) => {
109
+ this.renderSnapshot(snapshot);
110
+ });
111
+ this.renderSnapshot(this.provider.getSnapshot());
112
+ this.titleBar.addEventListener('mousedown', (e) => {
113
+ e.preventDefault();
114
+ this.dragging = true;
115
+ this.dragOffsetX = e.clientX - this.root.offsetLeft;
116
+ this.dragOffsetY = e.clientY - this.root.offsetTop;
117
+ });
118
+ this.mouseMoveHandler = (e) => {
119
+ if (!this.dragging)
120
+ return;
121
+ this.root.style.left = (e.clientX - this.dragOffsetX) + 'px';
122
+ this.root.style.top = (e.clientY - this.dragOffsetY) + 'px';
123
+ this.root.style.right = 'auto';
124
+ };
125
+ this.mouseUpHandler = () => {
126
+ this.dragging = false;
127
+ };
128
+ document.addEventListener('mousemove', this.mouseMoveHandler);
129
+ document.addEventListener('mouseup', this.mouseUpHandler);
130
+ this.keydownHandler = (e) => {
131
+ if (this.toggleKey === '')
132
+ return;
133
+ const isToggleMatch = e.key === this.toggleKey ||
134
+ e.code === this.toggleKey ||
135
+ (this.toggleKey === '`' && e.code === 'Backquote');
136
+ if (!isToggleMatch)
137
+ return;
138
+ const tag = e.target?.tagName;
139
+ if (tag === 'INPUT' || tag === 'TEXTAREA')
140
+ return;
141
+ this.toggleCollapse();
142
+ };
143
+ document.addEventListener('keydown', this.keydownHandler);
144
+ this.toggleBtn.addEventListener('mousedown', (e) => {
145
+ e.stopPropagation();
146
+ });
147
+ this.toggleBtn.addEventListener('click', (e) => {
148
+ e.stopPropagation();
149
+ this.toggleCollapse();
150
+ });
151
+ this.entitiesHeader.addEventListener('click', () => {
152
+ this.entitiesSectionCollapsed = !this.entitiesSectionCollapsed;
153
+ this.applySectionCollapse(this.entitiesHeader, this.entitiesBody, this.entitiesSectionCollapsed);
154
+ });
155
+ this.soaHeader.addEventListener('click', () => {
156
+ this.soaSectionCollapsed = !this.soaSectionCollapsed;
157
+ this.applySectionCollapse(this.soaHeader, this.soaBody, this.soaSectionCollapsed);
158
+ });
159
+ this.poolHeader.addEventListener('click', () => {
160
+ this.poolSectionCollapsed = !this.poolSectionCollapsed;
161
+ this.applySectionCollapse(this.poolHeader, this.poolBody, this.poolSectionCollapsed);
162
+ });
163
+ }
164
+ destroy() {
165
+ this.unsubscribe();
166
+ document.removeEventListener('keydown', this.keydownHandler);
167
+ document.removeEventListener('mousemove', this.mouseMoveHandler);
168
+ document.removeEventListener('mouseup', this.mouseUpHandler);
169
+ if (this.root.parentNode) {
170
+ this.root.parentNode.removeChild(this.root);
171
+ }
172
+ }
173
+ get element() {
174
+ return this.root;
175
+ }
176
+ renderSnapshot(snapshot) {
177
+ this.entityCountEl.textContent = String(snapshot.world.entityCount);
178
+ this.soaCountEl.textContent = String(snapshot.world.soaStoreCount);
179
+ if (snapshot.world.paused) {
180
+ this.pausedEl.textContent = 'PAUSED';
181
+ this.pausedEl.style.color = COLOR_WARNING;
182
+ this.pausedEl.style.fontWeight = 'bold';
183
+ }
184
+ else {
185
+ this.pausedEl.textContent = 'Running';
186
+ this.pausedEl.style.color = COLOR_TEXT;
187
+ this.pausedEl.style.fontWeight = 'normal';
188
+ }
189
+ this.entitiesSummary.textContent = `(${snapshot.entities.length} entities)`;
190
+ this.soaSummary.textContent = `(${snapshot.soaStores.length} stores)`;
191
+ this.poolSummary.textContent = `(${snapshot.pools.length} pools)`;
192
+ if (!this.entitiesSectionCollapsed) {
193
+ this.renderEntities(snapshot.entities);
194
+ }
195
+ if (!this.soaSectionCollapsed) {
196
+ this.renderSoAStores(snapshot.soaStores);
197
+ }
198
+ if (!this.poolSectionCollapsed) {
199
+ this.renderPoolStats(snapshot.pools);
200
+ }
201
+ }
202
+ renderEntities(entities) {
203
+ this.entitiesBody.innerHTML = '';
204
+ for (const entity of entities) {
205
+ const row = document.createElement('div');
206
+ row.style.cursor = 'pointer';
207
+ row.style.padding = '2px 0';
208
+ row.style.fontSize = '11px';
209
+ row.style.borderBottom = '1px solid rgba(255,255,255,0.05)';
210
+ const componentNames = entity.components.map((c) => c.typeName).join(', ');
211
+ const label = document.createElement('span');
212
+ label.textContent = `[ID: ${entity.id}] ${componentNames}`;
213
+ row.appendChild(label);
214
+ const isExpanded = this.expandedEntities.has(entity.id);
215
+ row.addEventListener('click', () => {
216
+ if (this.expandedEntities.has(entity.id)) {
217
+ this.expandedEntities.delete(entity.id);
218
+ }
219
+ else {
220
+ this.expandedEntities.add(entity.id);
221
+ }
222
+ this.renderEntities(this.provider.getSnapshot().entities);
223
+ });
224
+ this.entitiesBody.appendChild(row);
225
+ if (isExpanded) {
226
+ const detail = document.createElement('div');
227
+ detail.style.paddingLeft = '12px';
228
+ detail.style.fontSize = '11px';
229
+ detail.style.color = '#a0a4a8';
230
+ detail.style.marginBottom = '4px';
231
+ for (const comp of entity.components) {
232
+ const compHeader = document.createElement('div');
233
+ compHeader.style.color = COLOR_ACCENT;
234
+ compHeader.style.marginTop = '2px';
235
+ compHeader.textContent = comp.typeName;
236
+ detail.appendChild(compHeader);
237
+ for (const [key, value] of Object.entries(comp.data)) {
238
+ const kvRow = document.createElement('div');
239
+ kvRow.style.paddingLeft = '8px';
240
+ const keySpan = document.createElement('span');
241
+ keySpan.style.color = '#888';
242
+ keySpan.textContent = `${key}: `;
243
+ const valSpan = document.createElement('span');
244
+ valSpan.textContent = this.formatValue(value);
245
+ kvRow.appendChild(keySpan);
246
+ kvRow.appendChild(valSpan);
247
+ detail.appendChild(kvRow);
248
+ }
249
+ }
250
+ this.entitiesBody.appendChild(detail);
251
+ }
252
+ }
253
+ }
254
+ renderSoAStores(stores) {
255
+ this.soaBody.innerHTML = '';
256
+ for (const store of stores) {
257
+ const storeDiv = document.createElement('div');
258
+ storeDiv.style.marginBottom = '6px';
259
+ storeDiv.style.paddingBottom = '4px';
260
+ storeDiv.style.borderBottom = '1px solid rgba(255,255,255,0.05)';
261
+ const nameEl = document.createElement('div');
262
+ nameEl.style.color = COLOR_ACCENT;
263
+ nameEl.style.fontSize = '11px';
264
+ nameEl.style.fontWeight = 'bold';
265
+ nameEl.textContent = store.name;
266
+ storeDiv.appendChild(nameEl);
267
+ const layoutEl = document.createElement('div');
268
+ layoutEl.style.fontSize = '11px';
269
+ layoutEl.style.color = '#888';
270
+ const fieldDesc = store.fieldNames
271
+ .map((n) => `${n}: ${store.fieldTypes[n]}`)
272
+ .join(', ');
273
+ layoutEl.textContent = `Fields: ${fieldDesc}`;
274
+ storeDiv.appendChild(layoutEl);
275
+ const utilPct = store.capacity > 0
276
+ ? ((store.count / store.capacity) * 100).toFixed(0)
277
+ : '0';
278
+ const capacityEl = document.createElement('div');
279
+ capacityEl.style.fontSize = '11px';
280
+ capacityEl.textContent = `Count: ${store.count}/${store.capacity} (${utilPct}% utilised)`;
281
+ storeDiv.appendChild(capacityEl);
282
+ if (store.entities.length > 0) {
283
+ const gridEl = document.createElement('div');
284
+ gridEl.style.fontSize = '11px';
285
+ gridEl.style.marginTop = '2px';
286
+ gridEl.style.overflowX = 'auto';
287
+ const headerRow = document.createElement('div');
288
+ headerRow.style.display = 'flex';
289
+ headerRow.style.gap = '8px';
290
+ headerRow.style.color = '#888';
291
+ headerRow.style.borderBottom = '1px solid rgba(255,255,255,0.1)';
292
+ headerRow.style.paddingBottom = '1px';
293
+ headerRow.style.marginBottom = '1px';
294
+ const idHeader = document.createElement('span');
295
+ idHeader.style.minWidth = '40px';
296
+ idHeader.textContent = 'ID';
297
+ headerRow.appendChild(idHeader);
298
+ for (const fieldName of store.fieldNames) {
299
+ const fh = document.createElement('span');
300
+ fh.style.minWidth = '60px';
301
+ fh.textContent = fieldName;
302
+ headerRow.appendChild(fh);
303
+ }
304
+ gridEl.appendChild(headerRow);
305
+ for (const entityData of store.entities) {
306
+ const dataRow = document.createElement('div');
307
+ dataRow.style.display = 'flex';
308
+ dataRow.style.gap = '8px';
309
+ const idCell = document.createElement('span');
310
+ idCell.style.minWidth = '40px';
311
+ idCell.textContent = String(entityData.entityId);
312
+ dataRow.appendChild(idCell);
313
+ for (const fieldName of store.fieldNames) {
314
+ const cell = document.createElement('span');
315
+ cell.style.minWidth = '60px';
316
+ cell.textContent = this.formatValue(entityData.fields[fieldName]);
317
+ dataRow.appendChild(cell);
318
+ }
319
+ gridEl.appendChild(dataRow);
320
+ }
321
+ storeDiv.appendChild(gridEl);
322
+ }
323
+ this.soaBody.appendChild(storeDiv);
324
+ }
325
+ }
326
+ renderPoolStats(pools) {
327
+ this.poolBody.innerHTML = '';
328
+ if (pools.length === 0) {
329
+ const emptyEl = document.createElement('div');
330
+ emptyEl.style.fontSize = '11px';
331
+ emptyEl.style.color = '#888';
332
+ emptyEl.textContent = 'No pools registered';
333
+ this.poolBody.appendChild(emptyEl);
334
+ return;
335
+ }
336
+ const table = document.createElement('table');
337
+ table.style.width = '100%';
338
+ table.style.fontSize = '11px';
339
+ table.style.borderCollapse = 'collapse';
340
+ const thead = document.createElement('thead');
341
+ const headerRow = document.createElement('tr');
342
+ const columns = ['typeKey', 'available', 'totalCreated', 'acquireCount', 'releaseCount', 'missCount'];
343
+ for (const col of columns) {
344
+ const th = document.createElement('th');
345
+ th.style.textAlign = 'left';
346
+ th.style.padding = '2px 4px';
347
+ th.style.borderBottom = '1px solid rgba(255,255,255,0.15)';
348
+ th.style.color = '#888';
349
+ th.style.fontWeight = 'normal';
350
+ th.textContent = col;
351
+ headerRow.appendChild(th);
352
+ }
353
+ thead.appendChild(headerRow);
354
+ table.appendChild(thead);
355
+ const tbody = document.createElement('tbody');
356
+ for (const pool of pools) {
357
+ const tr = document.createElement('tr');
358
+ const tdKey = document.createElement('td');
359
+ tdKey.style.padding = '2px 4px';
360
+ tdKey.textContent = pool.typeKey;
361
+ tr.appendChild(tdKey);
362
+ const statValues = [
363
+ pool.stats.available,
364
+ pool.stats.totalCreated,
365
+ pool.stats.acquireCount,
366
+ pool.stats.releaseCount,
367
+ pool.stats.missCount,
368
+ ];
369
+ for (const val of statValues) {
370
+ const td = document.createElement('td');
371
+ td.style.padding = '2px 4px';
372
+ td.textContent = String(val);
373
+ tr.appendChild(td);
374
+ }
375
+ tbody.appendChild(tr);
376
+ }
377
+ table.appendChild(tbody);
378
+ this.poolBody.appendChild(table);
379
+ }
380
+ toggleCollapse() {
381
+ this.collapsed = !this.collapsed;
382
+ this.body.style.display = this.collapsed ? 'none' : 'block';
383
+ this.updateToggleBtn();
384
+ }
385
+ updateToggleBtn() {
386
+ this.toggleBtn.textContent = this.collapsed ? '[+]' : '[\u2212]';
387
+ }
388
+ applySectionCollapse(header, body, isCollapsed) {
389
+ body.style.display = isCollapsed ? 'none' : 'block';
390
+ const arrow = header.querySelector('[data-role="arrow"]');
391
+ if (arrow) {
392
+ arrow.textContent = isCollapsed ? '\u25B6' : '\u25BC';
393
+ }
394
+ }
395
+ buildCollapsibleSection(title) {
396
+ const section = document.createElement('div');
397
+ section.style.marginBottom = '4px';
398
+ const header = document.createElement('div');
399
+ header.style.cursor = 'pointer';
400
+ header.style.fontSize = '12px';
401
+ header.style.fontWeight = 'bold';
402
+ header.style.color = COLOR_ACCENT;
403
+ header.style.padding = '2px 0';
404
+ header.style.userSelect = 'none';
405
+ const arrow = document.createElement('span');
406
+ arrow.setAttribute('data-role', 'arrow');
407
+ arrow.textContent = '\u25BC';
408
+ arrow.style.marginRight = '4px';
409
+ arrow.style.fontSize = '10px';
410
+ const titleSpan = document.createElement('span');
411
+ titleSpan.textContent = title;
412
+ const summary = document.createElement('span');
413
+ summary.style.fontWeight = 'normal';
414
+ summary.style.fontSize = '11px';
415
+ summary.style.color = '#888';
416
+ summary.style.marginLeft = '6px';
417
+ header.appendChild(arrow);
418
+ header.appendChild(titleSpan);
419
+ header.appendChild(summary);
420
+ const body = document.createElement('div');
421
+ body.style.paddingLeft = '4px';
422
+ section.appendChild(header);
423
+ section.appendChild(body);
424
+ return { section, header, body, summary };
425
+ }
426
+ createLabel(text) {
427
+ const el = document.createElement('span');
428
+ el.style.color = '#888';
429
+ el.textContent = text;
430
+ return el;
431
+ }
432
+ createSeparator() {
433
+ const el = document.createElement('span');
434
+ el.style.margin = '0 6px';
435
+ el.style.color = '#555';
436
+ el.textContent = '|';
437
+ return el;
438
+ }
439
+ formatValue(value) {
440
+ if (typeof value === 'bigint') {
441
+ return `${value}n`;
442
+ }
443
+ return String(value);
444
+ }
445
+ applyRootStyles() {
446
+ const s = this.root.style;
447
+ s.position = 'fixed';
448
+ s.top = '8px';
449
+ s.right = '8px';
450
+ s.width = '360px';
451
+ s.backgroundColor = COLOR_BG;
452
+ s.color = COLOR_TEXT;
453
+ s.fontFamily = FONT_MONO;
454
+ s.fontSize = '11px';
455
+ s.zIndex = '99999';
456
+ s.borderRadius = '4px';
457
+ s.border = '1px solid rgba(255,255,255,0.1)';
458
+ s.boxShadow = '0 2px 12px rgba(0,0,0,0.5)';
459
+ s.overflow = 'hidden';
460
+ }
461
+ applyTitleBarStyles() {
462
+ const s = this.titleBar.style;
463
+ s.backgroundColor = 'rgba(30, 30, 40, 0.95)';
464
+ s.padding = '4px 8px';
465
+ s.cursor = 'move';
466
+ s.userSelect = 'none';
467
+ s.display = 'flex';
468
+ s.justifyContent = 'space-between';
469
+ s.alignItems = 'center';
470
+ s.fontSize = '12px';
471
+ s.fontWeight = 'bold';
472
+ s.color = COLOR_ACCENT;
473
+ s.minHeight = '20px';
474
+ }
475
+ applyToggleBtnStyles() {
476
+ const s = this.toggleBtn.style;
477
+ s.cursor = 'pointer';
478
+ s.fontSize = '12px';
479
+ s.color = COLOR_TEXT;
480
+ s.marginLeft = '8px';
481
+ }
482
+ }
@@ -0,0 +1,4 @@
1
+ export { DebugDataProvider } from './DebugDataProvider';
2
+ export { DebugPanel } from './DebugPanel';
3
+ export type { DebugSnapshot, DebugEntitySnapshot, DebugComponentSnapshot, DebugSoAStoreSnapshot, DebugPoolSnapshot, DebugDataProviderConfig, DebugPanelConfig, } from './types';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/debug/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { DebugDataProvider } from './DebugDataProvider';
2
+ export { DebugPanel } from './DebugPanel';