@downdraft/engine 0.2.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.
Files changed (138) hide show
  1. package/app/src/renderer/bootstrap.ts +0 -6
  2. package/app/src/renderer/feature-log.ts +1 -9
  3. package/app/src/renderer/mcp-harness.ts +8 -11
  4. package/app/src/renderer/standard-automation-tools.ts +8 -145
  5. package/core/src/index.ts +2 -2
  6. package/core/src/module/host.ts +3 -6
  7. package/core/src/module/index.ts +1 -1
  8. package/core/src/module/module.ts +4 -47
  9. package/core/src/physics/interface.ts +5 -0
  10. package/core/src/platform/render-surface.ts +13 -0
  11. package/core/src/platform/rpc.ts +4 -1
  12. package/core/src/render/canvas-resize-watcher.ts +1 -10
  13. package/core/src/render/game-renderer.ts +164 -79
  14. package/core/src/render/index.ts +0 -2
  15. package/core/src/render/input-manager.ts +7 -7
  16. package/core/src/render/renderer-module-host.ts +2 -4
  17. package/core/src/sab/grid-buffer.ts +55 -2
  18. package/core/src/sab/record-buffer.ts +48 -5
  19. package/core/src/sab/sim-channel.ts +181 -17
  20. package/core/src/sab/slot-buffer.ts +55 -1
  21. package/core/src/sab/types.ts +11 -0
  22. package/core/src/telemetry/gpu-resource-tracker.ts +24 -0
  23. package/core/src/ui-font-scale.ts +5 -38
  24. package/core/src/util/index.ts +1 -0
  25. package/core/src/util/session-timer.ts +23 -0
  26. package/core/src/worker/base-worker-host.ts +2 -1
  27. package/core/src/worker/buffer-sync.ts +20 -13
  28. package/core/src/worker/rpc.ts +119 -5
  29. package/core/src/worker/sim-worker-base.ts +5 -2
  30. package/core/src/worker/task-pool.ts +4 -0
  31. package/dist/.dd-manifest.json +893 -908
  32. package/dist/app/src/renderer/bootstrap.js +0 -4
  33. package/dist/app/src/renderer/feature-log.js +1 -10
  34. package/dist/app/src/renderer/mcp-harness.js +8 -11
  35. package/dist/app/src/renderer/standard-automation-tools.js +6 -138
  36. package/dist/core/src/index.js +2 -2
  37. package/dist/core/src/module/host.js +3 -6
  38. package/dist/core/src/platform/rpc.js +2 -1
  39. package/dist/core/src/render/canvas-resize-watcher.js +1 -10
  40. package/dist/core/src/render/game-renderer.js +166 -74
  41. package/dist/core/src/render/index.js +0 -1
  42. package/dist/core/src/render/input-manager.js +7 -7
  43. package/dist/core/src/render/renderer-module-host.js +2 -4
  44. package/dist/core/src/sab/grid-buffer.js +53 -1
  45. package/dist/core/src/sab/record-buffer.js +49 -5
  46. package/dist/core/src/sab/sim-channel.js +173 -17
  47. package/dist/core/src/sab/slot-buffer.js +54 -1
  48. package/dist/core/src/telemetry/gpu-resource-tracker.js +26 -0
  49. package/dist/core/src/ui-font-scale.js +5 -38
  50. package/dist/core/src/util/index.js +1 -0
  51. package/dist/core/src/util/session-timer.js +22 -0
  52. package/dist/core/src/worker/base-worker-host.js +2 -1
  53. package/dist/core/src/worker/buffer-sync.js +16 -9
  54. package/dist/core/src/worker/rpc.js +105 -8
  55. package/dist/core/src/worker/sim-worker-base.js +5 -2
  56. package/dist/core/src/worker/task-pool.js +5 -0
  57. package/dist/libraries/blitz-ui/src/native-module.js +8 -1
  58. package/dist/libraries/devtools/src/backend.js +35 -9
  59. package/dist/libraries/devtools/src/collectors.js +73 -22
  60. package/dist/libraries/devtools/src/native-providers.js +0 -2
  61. package/dist/libraries/entities/src/model-renderer.js +87 -27
  62. package/dist/libraries/entities/src/shaders/model.wgsl +13 -4
  63. package/dist/libraries/networking/src/index.js +1 -0
  64. package/dist/libraries/networking/src/ws-server.js +387 -0
  65. package/dist/libraries/physics-rapier/src/api.js +15 -0
  66. package/dist/libraries/physics-rapier/src/backend.js +15 -0
  67. package/dist/libraries/physics-rapier/src/rapier-backend.js +5 -0
  68. package/dist/libraries/postfx/src/post-process-stack.js +1 -3
  69. package/dist/libraries/sand/src/materials.js +2 -32
  70. package/dist/libraries/sand/src/sand-world.js +0 -3
  71. package/dist/modules/devtools/src/api.js +8 -23
  72. package/dist/modules/devtools/src/blitz/fonts.js +75 -0
  73. package/dist/modules/devtools/src/blitz/host.js +124 -30
  74. package/dist/modules/devtools/src/blitz/panels/console.js +19 -7
  75. package/dist/modules/devtools/src/blitz/panels/elements.js +217 -28
  76. package/dist/modules/devtools/src/blitz/panels/gpu.js +5 -2
  77. package/dist/modules/devtools/src/blitz/panels/performance.js +26 -12
  78. package/dist/modules/devtools/src/blitz/panels/snapshot.js +31 -8
  79. package/dist/modules/devtools/src/blitz/panels/types.js +2 -1
  80. package/dist/modules/devtools/src/blitz/theme.js +26 -4
  81. package/dist/modules/devtools/src/data-bridge.js +0 -11
  82. package/dist/modules/devtools/src/index.js +0 -6
  83. package/dist/modules/devtools/src/init.js +8 -42
  84. package/dist/modules/devtools/src/scene-inspector.js +1 -4
  85. package/dist/modules/devtools/src/worker-sync.js +3 -11
  86. package/dist/modules/html-ui/src/doc-backend.js +52 -3
  87. package/dist/modules/html-ui/src/host.js +6 -3
  88. package/dist/test/src/vision-test.js +1 -3
  89. package/libraries/blitz-ui/native/Cargo.toml +6 -0
  90. package/libraries/blitz-ui/native-osr/Cargo.toml +3 -1
  91. package/libraries/blitz-ui/src/native-module.ts +8 -1
  92. package/libraries/devtools/src/backend.ts +38 -12
  93. package/libraries/devtools/src/collectors.ts +75 -22
  94. package/libraries/devtools/src/native-providers.ts +0 -2
  95. package/libraries/entities/src/model-renderer.ts +88 -27
  96. package/libraries/entities/src/shaders/model.wgsl +13 -4
  97. package/libraries/networking/src/index.ts +4 -0
  98. package/libraries/networking/src/ws-server.ts +397 -0
  99. package/libraries/physics-rapier/src/api.ts +15 -0
  100. package/libraries/physics-rapier/src/backend.ts +15 -0
  101. package/libraries/physics-rapier/src/rapier-backend.ts +6 -0
  102. package/libraries/postfx/src/post-process-stack.ts +1 -2
  103. package/libraries/sand/src/materials.ts +2 -32
  104. package/libraries/sand/src/sand-world.ts +0 -4
  105. package/modules/devtools/README.md +1 -3
  106. package/modules/devtools/src/api.ts +10 -34
  107. package/modules/devtools/src/blitz/fonts.ts +78 -0
  108. package/modules/devtools/src/blitz/host.ts +107 -31
  109. package/modules/devtools/src/blitz/panels/console.ts +20 -8
  110. package/modules/devtools/src/blitz/panels/elements.ts +212 -31
  111. package/modules/devtools/src/blitz/panels/gpu.ts +4 -2
  112. package/modules/devtools/src/blitz/panels/performance.ts +24 -12
  113. package/modules/devtools/src/blitz/panels/snapshot.ts +30 -8
  114. package/modules/devtools/src/blitz/panels/types.ts +2 -1
  115. package/modules/devtools/src/blitz/theme.ts +26 -4
  116. package/modules/devtools/src/data-bridge.ts +0 -16
  117. package/modules/devtools/src/index.ts +1 -11
  118. package/modules/devtools/src/init.ts +8 -51
  119. package/modules/devtools/src/scene-inspector.ts +2 -6
  120. package/modules/devtools/src/types.ts +5 -90
  121. package/modules/devtools/src/worker-sync.ts +3 -13
  122. package/modules/html-ui/src/doc-backend.ts +67 -4
  123. package/modules/html-ui/src/host.ts +11 -3
  124. package/modules/html-ui/src/protocol.ts +1 -1
  125. package/package.json +4 -5
  126. package/test/src/vision-test.ts +2 -4
  127. package/core/src/render/sab-canvas-overlay.ts +0 -122
  128. package/dist/core/src/render/sab-canvas-overlay.js +0 -83
  129. package/dist/modules/devtools/src/debug-overlay.js +0 -181
  130. package/dist/modules/devtools/src/doctor-panel.js +0 -163
  131. package/dist/modules/devtools/src/label-overlay.js +0 -129
  132. package/dist/modules/devtools/src/material-stats-panel.js +0 -171
  133. package/dist/modules/devtools/src/sim-stats-panel.js +0 -250
  134. package/modules/devtools/src/debug-overlay.ts +0 -238
  135. package/modules/devtools/src/doctor-panel.ts +0 -182
  136. package/modules/devtools/src/label-overlay.ts +0 -154
  137. package/modules/devtools/src/material-stats-panel.ts +0 -188
  138. package/modules/devtools/src/sim-stats-panel.ts +0 -272
@@ -5,6 +5,7 @@
5
5
  // ============================================================================
6
6
 
7
7
  import { createLogger } from "../util/logger";
8
+ import { untrackedTimeout } from "../util/session-timer";
8
9
 
9
10
  const log = createLogger();
10
11
 
@@ -91,6 +92,66 @@ interface EventMessage {
91
92
  data: any;
92
93
  }
93
94
 
95
+ interface EventBatchMessage {
96
+ __eventBatch: true;
97
+ events: { kind: string; data: any }[];
98
+ }
99
+
100
+ // --- Event channel stats ---
101
+ // Cheap counters the DOWNDRAFT_EVENT_STATS logger (and QA tooling) read to
102
+ // verify batching is actually engaging: emitted events vs postMessage posts
103
+ // on the worker side, messages vs delivered events on the host side.
104
+ const eventChannelStats = {
105
+ /** Worker side: emit() calls. */
106
+ emitted: 0,
107
+ /** Worker side: postMessage calls carrying events (batch or single). */
108
+ posts: 0,
109
+ /** Host side: messages received carrying events. */
110
+ messages: 0,
111
+ /** Host side: events delivered to listeners. */
112
+ events: 0,
113
+ /** Host side: largest observed __eventBatch. */
114
+ maxBatch: 0,
115
+ };
116
+
117
+ export function getEventChannelStats(): Readonly<typeof eventChannelStats> {
118
+ return eventChannelStats;
119
+ }
120
+
121
+ function envFlag(name: string): boolean {
122
+ try {
123
+ return (
124
+ (globalThis as { process?: { env?: Record<string, string> } }).process?.env?.[name] === "1"
125
+ );
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ let statsLoggerStarted = false;
132
+ function startEventStatsLogger(): void {
133
+ if (statsLoggerStarted || !envFlag("DOWNDRAFT_EVENT_STATS")) return;
134
+ statsLoggerStarted = true;
135
+ let prev = { emitted: 0, posts: 0, messages: 0, events: 0 };
136
+ const t = setInterval(() => {
137
+ const s = eventChannelStats;
138
+ const d = {
139
+ emitted: s.emitted - prev.emitted,
140
+ posts: s.posts - prev.posts,
141
+ messages: s.messages - prev.messages,
142
+ events: s.events - prev.events,
143
+ };
144
+ prev = { emitted: s.emitted, posts: s.posts, messages: s.messages, events: s.events };
145
+ if (d.emitted || d.posts || d.messages || d.events) {
146
+ log.info(
147
+ "rpc-events",
148
+ `5s Δ emitted=${d.emitted} posts=${d.posts} msgs=${d.messages} events=${d.events} maxBatch=${s.maxBatch}`
149
+ );
150
+ }
151
+ }, 5000);
152
+ (t as { unref?: () => void }).unref?.();
153
+ }
154
+
94
155
  function isRpcRequest(msg: any): msg is RpcRequest {
95
156
  return msg && msg.__rpc === true && typeof msg.method === "string";
96
157
  }
@@ -103,6 +164,10 @@ function isEventMessage(msg: any): msg is EventMessage {
103
164
  return msg && msg.__event === true;
104
165
  }
105
166
 
167
+ function isEventBatchMessage(msg: any): msg is EventBatchMessage {
168
+ return msg && msg.__eventBatch === true;
169
+ }
170
+
106
171
  // --- Worker side: expose an API object ---
107
172
 
108
173
  export type WorkerApi = Record<string, (...args: any[]) => any>;
@@ -138,13 +203,55 @@ export interface WorkerEventEmitter {
138
203
 
139
204
  export function exposeEvents(host?: WorkerHost): WorkerEventEmitter {
140
205
  const h = host ?? getWorkerHost();
206
+ startEventStatsLogger();
141
207
  return {
142
208
  emit: (kind: string, data: any) => {
209
+ eventChannelStats.emitted++;
210
+ eventChannelStats.posts++;
143
211
  h.postToHost({ __event: true, kind, data } as EventMessage);
144
212
  },
145
213
  };
146
214
  }
147
215
 
216
+ export interface BatchedWorkerEventEmitter extends WorkerEventEmitter {
217
+ /** Post any queued events immediately. */
218
+ flush: () => void;
219
+ }
220
+
221
+ /**
222
+ * Event emitter that coalesces emit() calls into a single `__eventBatch`
223
+ * postMessage per macrotask. A sim emitting per-tick or per-entity events
224
+ * otherwise produces one structured clone + one main-thread task per event,
225
+ * and the task burst competes with rAF. Events emitted during a tick batch
226
+ * (the worker never yields to timers mid-tick) all land in one message;
227
+ * events emitted between ticks flush on the next macrotask, so latency for
228
+ * isolated events is unchanged.
229
+ */
230
+ export function exposeBatchedEvents(host?: WorkerHost): BatchedWorkerEventEmitter {
231
+ const h = host ?? getWorkerHost();
232
+ startEventStatsLogger();
233
+ let queue: { kind: string; data: any }[] = [];
234
+ let flushScheduled = false;
235
+ const flush = () => {
236
+ flushScheduled = false;
237
+ if (queue.length === 0) return;
238
+ eventChannelStats.posts++;
239
+ h.postToHost({ __eventBatch: true, events: queue } as EventBatchMessage);
240
+ queue = [];
241
+ };
242
+ return {
243
+ emit: (kind: string, data: any) => {
244
+ eventChannelStats.emitted++;
245
+ queue.push({ kind, data });
246
+ if (!flushScheduled) {
247
+ flushScheduled = true;
248
+ setTimeout(flush, 0);
249
+ }
250
+ },
251
+ flush,
252
+ };
253
+ }
254
+
148
255
  // --- Main side: wrap a worker as a typed async proxy ---
149
256
 
150
257
  type AnyWorker = {
@@ -171,6 +278,7 @@ export interface WrapOptions {
171
278
  const DEFAULT_TIMEOUT_MS = 30_000;
172
279
 
173
280
  export function wrap<T extends WorkerApi>(worker: AnyWorker, options?: WrapOptions): WorkerProxy<T> {
281
+ startEventStatsLogger();
174
282
  const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
175
283
  // A worker nobody terminates must not pin the runtime after the host's
176
284
  // window closes — in-flight RPCs still hold the loop via their timeout.
@@ -196,10 +304,16 @@ export function wrap<T extends WorkerApi>(worker: AnyWorker, options?: WrapOptio
196
304
  return;
197
305
  }
198
306
 
199
- if (isEventMessage(msg)) {
200
- for (const cb of eventListeners.values()) {
201
- try { cb(msg.kind, msg.data); } catch (err) {
202
- log.error("worker-proxy", `Event listener error: ${err}`);
307
+ if (isEventMessage(msg) || isEventBatchMessage(msg)) {
308
+ const events = isEventBatchMessage(msg) ? msg.events : [msg];
309
+ eventChannelStats.messages++;
310
+ eventChannelStats.events += events.length;
311
+ if (events.length > eventChannelStats.maxBatch) eventChannelStats.maxBatch = events.length;
312
+ for (const ev of events) {
313
+ for (const cb of eventListeners.values()) {
314
+ try { cb(ev.kind, ev.data); } catch (err) {
315
+ log.error("worker-proxy", `Event listener error: ${err}`);
316
+ }
203
317
  }
204
318
  }
205
319
  }
@@ -248,7 +362,7 @@ export function wrap<T extends WorkerApi>(worker: AnyWorker, options?: WrapOptio
248
362
  const id = ++reqId;
249
363
  const entry: { resolve: (v: unknown) => void; reject: (e: Error) => void; timer?: ReturnType<typeof setTimeout> } = { resolve, reject };
250
364
  if (timeoutMs > 0) {
251
- entry.timer = setTimeout(() => {
365
+ entry.timer = untrackedTimeout(() => {
252
366
  if (pending.delete(id)) {
253
367
  reject(new Error(`RPC '${method}' timed out after ${timeoutMs}ms`));
254
368
  }
@@ -52,7 +52,7 @@ import type {
52
52
  GCController
53
53
  } from "../telemetry/gc-controller";
54
54
  import { createLogger } from "../util/logger";
55
- import { expose, exposeEvents, type WorkerApi } from "./rpc";
55
+ import { expose, exposeBatchedEvents, type WorkerApi } from "./rpc";
56
56
 
57
57
  const log = createLogger();
58
58
 
@@ -465,7 +465,7 @@ export function createSimWorker(opts: CreateSimWorkerOptions): SimWorkerControl
465
465
  // Serializes withLoopStopped critical sections.
466
466
  let stopChain: Promise<unknown> = Promise.resolve();
467
467
 
468
- const events = exposeEvents();
468
+ const events = exposeBatchedEvents();
469
469
 
470
470
  // Deterministic RNG — reseedable via control.setSeed(); the stable `rng`
471
471
  // closure is what ctx.rng and the Math.random shim draw from.
@@ -750,6 +750,9 @@ export function createSimWorker(opts: CreateSimWorkerOptions): SimWorkerControl
750
750
  syncWorker = null; // release sync worker reference
751
751
  saveStore = null;
752
752
  await opts.onShutdown?.();
753
+ // Drain any events queued during onShutdown so they aren't lost to
754
+ // a flush timer the terminating worker never runs.
755
+ events.flush();
753
756
  },
754
757
 
755
758
  async setSpeed(speed: number): Promise<void> {
@@ -121,6 +121,10 @@ export class TaskPool {
121
121
  for (let i = 0; i < this.workerCount; i++) {
122
122
  const worker = this.createWorker();
123
123
  worker.onerror = (e: ErrorEvent) => {
124
+ // Session teardown tags tracked workers __ddExpectTerminate and
125
+ // fires synthetic error events to settle waiters — not faults
126
+ // (see session-tracker.ts terminateTrackedWorker).
127
+ if ((worker as any).__ddExpectTerminate || this.destroyed) return;
124
128
  if (this.onWorkerError) this.onWorkerError(i, e);
125
129
  else log.error("task-pool", `Worker ${i} error: ${e.message}`);
126
130
  };