@yagejs/debug 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,655 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ DebugPlugin: () => DebugPlugin,
25
+ DebugRegistryImpl: () => DebugRegistryImpl,
26
+ StatsStore: () => StatsStore
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/DebugPlugin.ts
31
+ var import_core3 = require("@yagejs/core");
32
+
33
+ // src/types.ts
34
+ var import_core = require("@yagejs/core");
35
+ var DebugRegistryKey = new import_core.ServiceKey("debugRegistry");
36
+
37
+ // src/DebugPlugin.ts
38
+ var import_renderer = require("@yagejs/renderer");
39
+
40
+ // src/DebugClock.ts
41
+ var DebugClock = class {
42
+ constructor(gameLoop, stopTicker, startTicker, render) {
43
+ this.gameLoop = gameLoop;
44
+ this.stopTicker = stopTicker;
45
+ this.startTicker = startTicker;
46
+ this.render = render;
47
+ }
48
+ gameLoop;
49
+ stopTicker;
50
+ startTicker;
51
+ render;
52
+ static {
53
+ __name(this, "DebugClock");
54
+ }
55
+ _isManual = false;
56
+ get isManual() {
57
+ return this._isManual;
58
+ }
59
+ startAuto() {
60
+ if (!this._isManual) return;
61
+ this.startTicker();
62
+ this._isManual = false;
63
+ }
64
+ stopAuto() {
65
+ if (this._isManual) return;
66
+ this.stopTicker();
67
+ this._isManual = true;
68
+ }
69
+ step(dtMs) {
70
+ if (!this._isManual) {
71
+ throw new Error(
72
+ "Manual clock is not active. Call clock.stopAuto() first."
73
+ );
74
+ }
75
+ const dt = dtMs ?? this.gameLoop.fixedTimestep;
76
+ this.gameLoop.tick(dt);
77
+ this.render();
78
+ }
79
+ stepFrames(count, dtMs) {
80
+ if (!Number.isInteger(count) || count < 0) {
81
+ throw new Error(
82
+ "stepFrames(count) requires a non-negative integer count."
83
+ );
84
+ }
85
+ for (let i = 0; i < count; i++) {
86
+ this.step(dtMs);
87
+ }
88
+ }
89
+ /** Enter or exit manual mode. Used by DebugPlugin for config-driven init. */
90
+ setManual(enabled) {
91
+ if (enabled === this._isManual) return;
92
+ if (enabled) {
93
+ this.stopTicker();
94
+ } else {
95
+ this.startTicker();
96
+ }
97
+ this._isManual = enabled;
98
+ }
99
+ };
100
+
101
+ // src/DebugRegistryImpl.ts
102
+ var DebugRegistryImpl = class {
103
+ static {
104
+ __name(this, "DebugRegistryImpl");
105
+ }
106
+ contributors = /* @__PURE__ */ new Map();
107
+ enabled = false;
108
+ flags = /* @__PURE__ */ new Map();
109
+ register(contributor) {
110
+ if (this.contributors.has(contributor.name)) return;
111
+ this.contributors.set(contributor.name, contributor);
112
+ for (const flag of contributor.flags) {
113
+ this.flags.set(`${contributor.name}.${flag}`, true);
114
+ }
115
+ }
116
+ isEnabled() {
117
+ return this.enabled;
118
+ }
119
+ isFlagEnabled(contributorName, flag) {
120
+ return this.flags.get(`${contributorName}.${flag}`) ?? true;
121
+ }
122
+ toggle() {
123
+ this.enabled = !this.enabled;
124
+ }
125
+ toggleFlag(contributorName, flag) {
126
+ const key = `${contributorName}.${flag}`;
127
+ this.flags.set(key, !(this.flags.get(key) ?? true));
128
+ }
129
+ setFlag(contributorName, flag, value) {
130
+ this.flags.set(`${contributorName}.${flag}`, value);
131
+ }
132
+ };
133
+
134
+ // src/StatsStore.ts
135
+ var WINDOW_SIZE = 120;
136
+ var StatsStore = class {
137
+ static {
138
+ __name(this, "StatsStore");
139
+ }
140
+ rings = /* @__PURE__ */ new Map();
141
+ push(key, value) {
142
+ let ring = this.rings.get(key);
143
+ if (!ring) {
144
+ ring = { data: new Float64Array(WINDOW_SIZE), count: 0, index: 0 };
145
+ this.rings.set(key, ring);
146
+ }
147
+ ring.data[ring.index] = value;
148
+ ring.index = (ring.index + 1) % WINDOW_SIZE;
149
+ if (ring.count < WINDOW_SIZE) ring.count++;
150
+ }
151
+ average(key) {
152
+ const ring = this.rings.get(key);
153
+ if (!ring || ring.count === 0) return 0;
154
+ const { data, count } = ring;
155
+ let sum = 0;
156
+ for (let i = 0; i < count; i++) sum += data[i] ?? 0;
157
+ return sum / count;
158
+ }
159
+ latest(key) {
160
+ const ring = this.rings.get(key);
161
+ if (!ring || ring.count === 0) return 0;
162
+ return ring.data[(ring.index - 1 + WINDOW_SIZE) % WINDOW_SIZE] ?? 0;
163
+ }
164
+ min(key) {
165
+ const ring = this.rings.get(key);
166
+ if (!ring || ring.count === 0) return 0;
167
+ const { data, count } = ring;
168
+ let m = Infinity;
169
+ for (let i = 0; i < count; i++) {
170
+ const v = data[i] ?? 0;
171
+ if (v < m) m = v;
172
+ }
173
+ return m;
174
+ }
175
+ max(key) {
176
+ const ring = this.rings.get(key);
177
+ if (!ring || ring.count === 0) return 0;
178
+ const { data, count } = ring;
179
+ let m = -Infinity;
180
+ for (let i = 0; i < count; i++) {
181
+ const v = data[i] ?? 0;
182
+ if (v > m) m = v;
183
+ }
184
+ return m;
185
+ }
186
+ };
187
+
188
+ // src/GraphicsPool.ts
189
+ var import_pixi = require("pixi.js");
190
+ var GraphicsPool = class {
191
+ static {
192
+ __name(this, "GraphicsPool");
193
+ }
194
+ pool = null;
195
+ index = 0;
196
+ container;
197
+ maxSize;
198
+ constructor(container, maxSize = 256) {
199
+ this.container = container;
200
+ this.maxSize = maxSize;
201
+ }
202
+ /** Lazily create Graphics objects on first use (ensures PixiJS is fully ready). */
203
+ ensure() {
204
+ if (this.pool) return this.pool;
205
+ this.pool = [];
206
+ for (let i = 0; i < this.maxSize; i++) {
207
+ const g = new import_pixi.Graphics();
208
+ g.visible = false;
209
+ g.eventMode = "none";
210
+ this.container.addChild(g);
211
+ this.pool.push(g);
212
+ }
213
+ return this.pool;
214
+ }
215
+ /** Return the next available Graphics, or undefined if the pool is exhausted. */
216
+ acquire() {
217
+ const pool = this.ensure();
218
+ if (this.index >= pool.length) return void 0;
219
+ const g = pool[this.index];
220
+ g.visible = true;
221
+ this.index++;
222
+ return g;
223
+ }
224
+ /** Clear and hide all previously acquired graphics, reset index. */
225
+ resetFrame() {
226
+ if (!this.pool) return;
227
+ for (let i = 0; i < this.index; i++) {
228
+ const g = this.pool[i];
229
+ g.clear();
230
+ g.visible = false;
231
+ g.position.set(0, 0);
232
+ g.rotation = 0;
233
+ g.scale.set(1, 1);
234
+ }
235
+ this.index = 0;
236
+ }
237
+ destroy() {
238
+ if (!this.pool) return;
239
+ for (const g of this.pool) {
240
+ g.destroy();
241
+ }
242
+ this.pool.length = 0;
243
+ }
244
+ };
245
+
246
+ // src/TextPool.ts
247
+ var import_pixi2 = require("pixi.js");
248
+ var LINE_HEIGHT = 16;
249
+ var FONT_SIZE = 14;
250
+ var PADDING = 4;
251
+ var TextPool = class {
252
+ static {
253
+ __name(this, "TextPool");
254
+ }
255
+ pool = null;
256
+ index = 0;
257
+ container;
258
+ maxLines;
259
+ constructor(container, maxLines = 32) {
260
+ this.container = container;
261
+ this.maxLines = maxLines;
262
+ }
263
+ /** Lazily create Text objects on first use (ensures PixiJS is fully ready). */
264
+ ensure() {
265
+ if (this.pool) return this.pool;
266
+ this.pool = [];
267
+ for (let i = 0; i < this.maxLines; i++) {
268
+ const t = new import_pixi2.Text({
269
+ text: "",
270
+ style: {
271
+ fontFamily: "monospace",
272
+ fontSize: FONT_SIZE,
273
+ fill: 16777215
274
+ }
275
+ });
276
+ t.visible = false;
277
+ t.eventMode = "none";
278
+ this.container.addChild(t);
279
+ this.pool.push(t);
280
+ }
281
+ return this.pool;
282
+ }
283
+ /** Show a text line at the next available slot. */
284
+ addLine(text) {
285
+ const pool = this.ensure();
286
+ if (this.index >= pool.length) return;
287
+ const t = pool[this.index];
288
+ t.text = text;
289
+ t.visible = true;
290
+ t.position.set(PADDING, PADDING + this.index * LINE_HEIGHT);
291
+ this.index++;
292
+ }
293
+ /** Hide all lines and reset for the next frame. */
294
+ resetFrame() {
295
+ if (!this.pool) return;
296
+ for (let i = 0; i < this.index; i++) {
297
+ this.pool[i].visible = false;
298
+ }
299
+ this.index = 0;
300
+ }
301
+ destroy() {
302
+ if (!this.pool) return;
303
+ for (const t of this.pool) {
304
+ t.destroy();
305
+ }
306
+ this.pool.length = 0;
307
+ }
308
+ };
309
+
310
+ // src/WorldDebugApiImpl.ts
311
+ var WorldDebugApiImpl = class {
312
+ constructor(pool, registry, _camera) {
313
+ this.pool = pool;
314
+ this.registry = registry;
315
+ this._camera = _camera;
316
+ }
317
+ pool;
318
+ registry;
319
+ _camera;
320
+ static {
321
+ __name(this, "WorldDebugApiImpl");
322
+ }
323
+ _contributorName = "";
324
+ /** Set the current contributor name (called before each contributor's drawWorld). */
325
+ setContributor(name) {
326
+ this._contributorName = name;
327
+ }
328
+ acquireGraphics() {
329
+ return this.pool.acquire();
330
+ }
331
+ isFlagEnabled(flag) {
332
+ return this.registry.isFlagEnabled(this._contributorName, flag);
333
+ }
334
+ get cameraZoom() {
335
+ return this._camera.zoom;
336
+ }
337
+ };
338
+
339
+ // src/HudDebugApiImpl.ts
340
+ var HudDebugApiImpl = class {
341
+ constructor(textPool, registry, screenWidth, screenHeight) {
342
+ this.textPool = textPool;
343
+ this.registry = registry;
344
+ this.screenWidth = screenWidth;
345
+ this.screenHeight = screenHeight;
346
+ }
347
+ textPool;
348
+ registry;
349
+ screenWidth;
350
+ screenHeight;
351
+ static {
352
+ __name(this, "HudDebugApiImpl");
353
+ }
354
+ _contributorName = "";
355
+ /** Set the current contributor name (called before each contributor's drawHud). */
356
+ setContributor(name) {
357
+ this._contributorName = name;
358
+ }
359
+ addLine(text) {
360
+ this.textPool.addLine(text);
361
+ }
362
+ isFlagEnabled(flag) {
363
+ return this.registry.isFlagEnabled(this._contributorName, flag);
364
+ }
365
+ };
366
+
367
+ // src/DebugRenderSystem.ts
368
+ var import_core2 = require("@yagejs/core");
369
+ var DebugRenderSystem = class extends import_core2.System {
370
+ constructor(registry, graphicsPool, textPool, worldApi, hudApi, stats, worldContainer, hudContainer) {
371
+ super();
372
+ this.registry = registry;
373
+ this.graphicsPool = graphicsPool;
374
+ this.textPool = textPool;
375
+ this.worldApi = worldApi;
376
+ this.hudApi = hudApi;
377
+ this.stats = stats;
378
+ this.worldContainer = worldContainer;
379
+ this.hudContainer = hudContainer;
380
+ }
381
+ registry;
382
+ graphicsPool;
383
+ textPool;
384
+ worldApi;
385
+ hudApi;
386
+ stats;
387
+ worldContainer;
388
+ hudContainer;
389
+ static {
390
+ __name(this, "DebugRenderSystem");
391
+ }
392
+ phase = import_core2.Phase.Render;
393
+ priority = 9999;
394
+ update(dt) {
395
+ if (!this.registry.enabled) {
396
+ this.worldContainer.visible = false;
397
+ this.hudContainer.visible = false;
398
+ return;
399
+ }
400
+ this.worldContainer.visible = true;
401
+ this.hudContainer.visible = true;
402
+ this.graphicsPool.resetFrame();
403
+ this.textPool.resetFrame();
404
+ for (const [name, contributor] of this.registry.contributors) {
405
+ contributor.sample?.(this.stats, dt);
406
+ if (contributor.drawWorld) {
407
+ this.worldApi.setContributor(name);
408
+ contributor.drawWorld(this.worldApi);
409
+ }
410
+ if (contributor.drawHud) {
411
+ this.hudApi.setContributor(name);
412
+ contributor.drawHud(this.hudApi);
413
+ }
414
+ }
415
+ }
416
+ };
417
+
418
+ // src/contributors/FpsContributor.ts
419
+ var FpsContributor = class {
420
+ static {
421
+ __name(this, "FpsContributor");
422
+ }
423
+ name = "fps";
424
+ flags = [];
425
+ stats = null;
426
+ sample(stats, dt) {
427
+ this.stats = stats;
428
+ if (dt > 0) stats.push("fps", 1e3 / dt);
429
+ }
430
+ drawHud(api) {
431
+ const avg = this.stats?.average("fps") ?? 0;
432
+ api.addLine(`FPS: ${Math.round(avg)}`);
433
+ }
434
+ };
435
+
436
+ // src/contributors/EntityCountContributor.ts
437
+ var EntityCountContributor = class {
438
+ constructor(inspector) {
439
+ this.inspector = inspector;
440
+ }
441
+ inspector;
442
+ static {
443
+ __name(this, "EntityCountContributor");
444
+ }
445
+ name = "entities";
446
+ flags = [];
447
+ drawHud(api) {
448
+ const count = this.inspector.snapshot().entityCount;
449
+ api.addLine(`Entities: ${count}`);
450
+ }
451
+ };
452
+
453
+ // src/contributors/SystemTimingContributor.ts
454
+ var SystemTimingContributor = class {
455
+ constructor(timings) {
456
+ this.timings = timings;
457
+ }
458
+ timings;
459
+ static {
460
+ __name(this, "SystemTimingContributor");
461
+ }
462
+ name = "timing";
463
+ flags = ["breakdown"];
464
+ stats = null;
465
+ sample(stats) {
466
+ this.stats = stats;
467
+ for (const [name, ms] of this.timings) {
468
+ stats.push(`system.${name}`, ms);
469
+ }
470
+ }
471
+ drawHud(api) {
472
+ if (!this.stats) return;
473
+ let total = 0;
474
+ const entries = [];
475
+ for (const name of this.timings.keys()) {
476
+ const avg = this.stats.average(`system.${name}`);
477
+ total += avg;
478
+ entries.push([name, avg]);
479
+ }
480
+ api.addLine(`Systems: ${total.toFixed(2)}ms`);
481
+ if (api.isFlagEnabled("breakdown")) {
482
+ entries.sort((a, b) => b[1] - a[1]);
483
+ for (const [name, avg] of entries) {
484
+ api.addLine(` ${name}: ${avg.toFixed(2)}ms`);
485
+ }
486
+ }
487
+ }
488
+ };
489
+
490
+ // src/DebugPlugin.ts
491
+ var DebugPlugin = class {
492
+ static {
493
+ __name(this, "DebugPlugin");
494
+ }
495
+ name = "debug";
496
+ version = "2.0.0";
497
+ dependencies = ["renderer"];
498
+ config;
499
+ registry;
500
+ stats;
501
+ graphicsPool;
502
+ textPool;
503
+ worldContainer;
504
+ hudContainer;
505
+ worldApi;
506
+ hudApi;
507
+ systemTimings = /* @__PURE__ */ new Map();
508
+ originalUpdates = /* @__PURE__ */ new Map();
509
+ keyListener = null;
510
+ context;
511
+ renderer;
512
+ clock = null;
513
+ constructor(config) {
514
+ this.config = config ?? {};
515
+ }
516
+ install(context) {
517
+ this.context = context;
518
+ this.renderer = context.resolve(import_renderer.RendererKey);
519
+ const camera = context.resolve(import_renderer.CameraKey);
520
+ const worldLayers = context.resolve(import_renderer.RenderLayerManagerKey);
521
+ const debugLayer = worldLayers.getOrCreate("debug", 999999);
522
+ this.worldContainer = debugLayer.container;
523
+ this.worldContainer.eventMode = "none";
524
+ const hudLayers = this.renderer.createScreenContainer("debug-hud");
525
+ this.hudContainer = hudLayers.defaultLayer.container;
526
+ this.hudContainer.eventMode = "none";
527
+ this.graphicsPool = new GraphicsPool(
528
+ this.worldContainer,
529
+ this.config.maxGraphics
530
+ );
531
+ this.textPool = new TextPool(this.hudContainer, this.config.maxHudLines);
532
+ this.registry = new DebugRegistryImpl();
533
+ this.registry.enabled = this.config.startEnabled ?? false;
534
+ if (this.config.flags) {
535
+ for (const [key, value] of Object.entries(this.config.flags)) {
536
+ const dot = key.indexOf(".");
537
+ if (dot > 0) {
538
+ this.registry.setFlag(key.slice(0, dot), key.slice(dot + 1), value);
539
+ }
540
+ }
541
+ }
542
+ this.stats = new StatsStore();
543
+ this.worldApi = new WorldDebugApiImpl(
544
+ this.graphicsPool,
545
+ this.registry,
546
+ camera
547
+ );
548
+ this.hudApi = new HudDebugApiImpl(
549
+ this.textPool,
550
+ this.registry,
551
+ camera.viewportWidth,
552
+ camera.viewportHeight
553
+ );
554
+ context.register(DebugRegistryKey, this.registry);
555
+ }
556
+ registerSystems(scheduler) {
557
+ scheduler.add(
558
+ new DebugRenderSystem(
559
+ this.registry,
560
+ this.graphicsPool,
561
+ this.textPool,
562
+ this.worldApi,
563
+ this.hudApi,
564
+ this.stats,
565
+ this.worldContainer,
566
+ this.hudContainer
567
+ )
568
+ );
569
+ }
570
+ onStart() {
571
+ const toggleKey = this.config.toggleKey ?? "Backquote";
572
+ const stepKey = this.config.stepKey ?? "Period";
573
+ this.keyListener = (e) => {
574
+ if (e.code === toggleKey) {
575
+ this.registry.toggle();
576
+ return;
577
+ }
578
+ if (e.code === stepKey && this.clock?.isManual) {
579
+ e.preventDefault();
580
+ this.clock.step();
581
+ }
582
+ };
583
+ if (typeof window !== "undefined") {
584
+ window.addEventListener("keydown", this.keyListener);
585
+ }
586
+ const scheduler = this.context.resolve(import_core3.SystemSchedulerKey);
587
+ for (const system of scheduler.getAllSystems()) {
588
+ if (system instanceof DebugRenderSystem) continue;
589
+ const name = system.constructor.name;
590
+ const original = system.update.bind(system);
591
+ this.originalUpdates.set(system, original);
592
+ system.update = (dt) => {
593
+ const t0 = performance.now();
594
+ original(dt);
595
+ this.systemTimings.set(name, performance.now() - t0);
596
+ };
597
+ }
598
+ const inspector = this.context.resolve(import_core3.InspectorKey);
599
+ this.registry.register(new FpsContributor());
600
+ this.registry.register(new EntityCountContributor(inspector));
601
+ this.registry.register(new SystemTimingContributor(this.systemTimings));
602
+ const gameLoop = this.context.resolve(import_core3.GameLoopKey);
603
+ const app = this.renderer.application;
604
+ this.clock = new DebugClock(
605
+ gameLoop,
606
+ () => app.stop(),
607
+ () => app.start(),
608
+ () => app.render()
609
+ );
610
+ if (this.config.manualClock) {
611
+ this.clock.setManual(true);
612
+ }
613
+ this.attachToGlobal(this.clock);
614
+ }
615
+ onDestroy() {
616
+ for (const [system, original] of this.originalUpdates) {
617
+ system.update = original;
618
+ }
619
+ this.originalUpdates.clear();
620
+ if (this.keyListener) {
621
+ if (typeof window !== "undefined") {
622
+ window.removeEventListener("keydown", this.keyListener);
623
+ }
624
+ this.keyListener = null;
625
+ }
626
+ this.detachFromGlobal();
627
+ this.clock = null;
628
+ for (const contributor of this.registry.contributors.values()) {
629
+ contributor.dispose?.();
630
+ }
631
+ this.registry.contributors.clear();
632
+ this.graphicsPool.destroy();
633
+ this.textPool.destroy();
634
+ this.renderer.destroyScreenContainer("debug-hud");
635
+ }
636
+ attachToGlobal(clock) {
637
+ const g = globalThis["__yage__"];
638
+ if (g && typeof g === "object") {
639
+ g["clock"] = clock;
640
+ }
641
+ }
642
+ detachFromGlobal() {
643
+ const g = globalThis["__yage__"];
644
+ if (g && typeof g === "object" && g["clock"] === this.clock) {
645
+ delete g["clock"];
646
+ }
647
+ }
648
+ };
649
+ // Annotate the CommonJS export names for ESM import in node:
650
+ 0 && (module.exports = {
651
+ DebugPlugin,
652
+ DebugRegistryImpl,
653
+ StatsStore
654
+ });
655
+ //# sourceMappingURL=index.cjs.map