@plasius/gpu-worker 0.1.8 → 0.1.9

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/CHANGELOG.md CHANGED
@@ -20,6 +20,39 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
20
20
  - **Security**
21
21
  - (placeholder)
22
22
 
23
+ ## [0.1.9] - 2026-03-13
24
+
25
+ - **Added**
26
+ - ADR and TDR guidance describing `@plasius/gpu-worker` as the preferred
27
+ discrete execution plane for current and future `@plasius/gpu-*` compute
28
+ packages.
29
+ - Design documentation for multi-package worker coordination and future effect
30
+ package expansion.
31
+ - Optional `createWorkerLoop` telemetry hooks for dispatch samples and
32
+ per-tick summaries with shared `frameId` correlation support.
33
+ - `queueMode: "dag"` support for loading and assembling against
34
+ multi-root DAG-ready queue helpers from `@plasius/gpu-lock-free-queue`.
35
+ - ADR, TDR, and design documentation for flat versus DAG queue assembly
36
+ contracts.
37
+
38
+ - **Changed**
39
+ - README guidance now points package authors toward worker-job manifests,
40
+ `@plasius/gpu-performance` budget coordination, and optional
41
+ `@plasius/gpu-debug` instrumentation.
42
+ - Worker assembly now guarantees a `complete_job(job_index)` hook is present
43
+ so flat and DAG queue assets share the same worker completion path.
44
+ - Queue package resolution now uses the published npm release
45
+ `@plasius/gpu-lock-free-queue@^0.2.13` instead of the temporary GitHub
46
+ fallback.
47
+ - Updated patch-level eslint tooling to the latest versions allowed by the
48
+ current semver ranges.
49
+
50
+ - **Fixed**
51
+ - (placeholder)
52
+
53
+ - **Security**
54
+ - (placeholder)
55
+
23
56
  ## [0.1.8] - 2026-03-04
24
57
 
25
58
  - **Added**
@@ -156,7 +189,7 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
156
189
 
157
190
  ---
158
191
 
159
- [Unreleased]: https://github.com/Plasius-LTD/gpu-worker/compare/v0.1.8...HEAD
192
+ [Unreleased]: https://github.com/Plasius-LTD/gpu-worker/compare/v0.1.9...HEAD
160
193
  [0.1.0-beta.1]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.0-beta.1
161
194
  [0.1.0]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.0
162
195
  [0.1.2]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.2
@@ -177,3 +210,4 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
177
210
  [0.1.3]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.3
178
211
  [0.1.4]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.4
179
212
  [0.1.8]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.8
213
+ [0.1.9]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.9
package/README.md CHANGED
@@ -54,6 +54,22 @@ By default it applies queue compatibility renames (for example `JobMeta` -> `Job
54
54
  set `queueCompat: false` to disable that behavior.
55
55
  If you are concatenating WGSL manually, `loadQueueWgsl` provides the same
56
56
  compatibility renames by default: `loadQueueWgsl({ url, fetcher, queueCompat: false })`.
57
+ Set `queueMode: "dag"` on `loadQueueWgsl(...)` or `assembleWorkerWgsl(...)`
58
+ to assemble against the multi-root DAG-ready queue helpers from
59
+ `@plasius/gpu-lock-free-queue`.
60
+
61
+ ```js
62
+ const shaderCode = await assembleWorkerWgsl(workerWgsl, {
63
+ preludeWgsl,
64
+ jobs,
65
+ queueMode: "dag",
66
+ });
67
+ ```
68
+
69
+ Worker WGSL now calls `complete_job(job_index)` after `process_job(...)`.
70
+ Queue assets from `@plasius/gpu-lock-free-queue` already provide that hook. If
71
+ you pass a custom queue source without it, the assembler appends a no-op shim so
72
+ existing flat queue integrations keep working.
57
73
 
58
74
  To bypass the registry, pass jobs directly:
59
75
  ```js
@@ -85,11 +101,102 @@ const loop = createWorkerLoop({
85
101
  loop.start();
86
102
  ```
87
103
 
104
+ For opt-in local instrumentation, `createWorkerLoop` also accepts:
105
+
106
+ - `frameId`: a string or function returning the current frame correlation id.
107
+ - `worker.owner`, `worker.queueClass`, `worker.jobType`, `worker.label`,
108
+ `worker.workgroupSize`: optional metadata for the dequeue pass.
109
+ - the same metadata fields on each job descriptor.
110
+ - `telemetry.onDispatch(sample)`: called after submit for each worker/job
111
+ dispatch.
112
+ - `telemetry.onTick(summary)`: called after submit with the per-tick dispatch
113
+ summary.
114
+
115
+ That allows direct integration with `@plasius/gpu-debug` without coupling the
116
+ runtime to the package:
117
+
118
+ ```js
119
+ import { createGpuDebugSession } from "@plasius/gpu-debug";
120
+
121
+ const debug = createGpuDebugSession({ enabled: true });
122
+
123
+ const loop = createWorkerLoop({
124
+ device,
125
+ frameId: () => `frame-${frameNumber}`,
126
+ worker: {
127
+ pipeline: workerPipeline,
128
+ bindGroups: [queueBindGroup],
129
+ workgroups: [2, 1, 1],
130
+ workgroupSize: 64,
131
+ owner: "lighting",
132
+ queueClass: "lighting",
133
+ jobType: "worker.dequeue",
134
+ },
135
+ jobs: [
136
+ {
137
+ pipeline: directLightingPipeline,
138
+ bindGroups: [queueBindGroup, lightingBindGroup],
139
+ workgroupCount: [32, 18, 1],
140
+ workgroupSize: [8, 8, 1],
141
+ owner: "lighting",
142
+ queueClass: "lighting",
143
+ jobType: "lighting.direct",
144
+ },
145
+ ],
146
+ telemetry: {
147
+ onDispatch(sample) {
148
+ debug.recordDispatch({
149
+ owner: sample.owner,
150
+ queueClass: sample.queueClass,
151
+ jobType: sample.jobType,
152
+ frameId: sample.frameId,
153
+ workgroups: sample.workgroups,
154
+ workgroupSize: sample.workgroupSize,
155
+ });
156
+ },
157
+ },
158
+ });
159
+ ```
160
+
88
161
  ## What this is
89
162
  - A minimal GPU worker layer that combines a lock-free queue with user WGSL jobs.
90
163
  - A helper to assemble WGSL modules with queue helpers included.
91
164
  - A reference job format for fixed-size job dispatch (u32 indices).
92
165
 
166
+ ## DAG Queue Modes
167
+
168
+ `@plasius/gpu-worker` now supports two scheduler assembly modes:
169
+
170
+ - `flat`: the original lock-free FIFO/worklist execution flow.
171
+ - `dag`: a multi-root, priority-aware ready-queue flow where jobs can declare
172
+ dependencies and join points through their package-owned manifests.
173
+
174
+ The worker runtime still stays lock-free and policy-light. It does not resolve
175
+ budgets or decide priorities itself. Instead it exposes the queue mode and the
176
+ completion hook needed by package manifests and `@plasius/gpu-performance` to
177
+ coordinate DAG-shaped workloads.
178
+
179
+ ## Package Integration Model
180
+
181
+ `@plasius/gpu-worker` is the preferred execution plane for discrete GPU work
182
+ across current and future `@plasius/gpu-*` compute packages.
183
+
184
+ Package authors should:
185
+
186
+ - register stable worker job types for each effect family,
187
+ - keep scheduling in terms of compact worklists and bounded dispatches,
188
+ - expose `priority`, `dependencies`, and `schedulerMode` metadata in manifests
189
+ when jobs form ordered DAG stages instead of a flat queue,
190
+ - let `@plasius/gpu-performance` adjust worker budgets instead of building
191
+ separate package-local governors,
192
+ - expose optional local instrumentation through `createWorkerLoop(..., {
193
+ telemetry })` and route that into `@plasius/gpu-debug` when clients enable
194
+ it.
195
+
196
+ This pattern is intended to scale across post-processing, cloth, fluids,
197
+ lighting refresh, voxel generation, and additional GPU job families without
198
+ splitting scheduling policy across packages.
199
+
93
200
  ## Demo
94
201
  The demo enqueues physics and render jobs on the GPU, builds per-type worklists, runs the
95
202
  physics kernel, and uses an indirect draw for the particle pass. Install dependencies first
@@ -139,6 +246,9 @@ npm run pack:check
139
246
  - `demo/jobs/render.job.wgsl`: Render job kernel (worklist + indirect args).
140
247
  - `src/worker.wgsl`: Minimal worker entry point template (dequeue + `process_job` hook).
141
248
  - `src/index.js`: Helper functions to load/assemble WGSL.
249
+ - `docs/adrs/*`: architectural decisions for worker runtime and scheduling.
250
+ - `docs/tdrs/*`: technical design records for multi-package worker integration.
251
+ - `docs/design/*`: design notes for package integration, DAG queue modes, and future expansion.
142
252
 
143
253
  ## Job shape
144
254
  Jobs are variable-length payloads stored in a caller-managed buffer. Each job supplies `job_type`, `payload_offset`, and `payload_words` metadata plus a payload stored in the input payload buffer. For simple cases, use a single-word payload containing an index into your workload array.
package/dist/index.cjs CHANGED
@@ -202,6 +202,15 @@ function getQueueCompatMap(source) {
202
202
  }
203
203
  return [{ from: /\bJobMeta\b/g, to: "JobDesc" }];
204
204
  }
205
+ function normalizeQueueMode(mode) {
206
+ const resolved = mode ?? "flat";
207
+ if (!import_gpu_lock_free_queue.schedulerModes.includes(resolved)) {
208
+ throw new Error(
209
+ `queueMode must be one of: ${import_gpu_lock_free_queue.schedulerModes.join(", ")}.`
210
+ );
211
+ }
212
+ return resolved;
213
+ }
205
214
  function applyCompatMap(source, map) {
206
215
  if (!map || map.length === 0) {
207
216
  return source;
@@ -276,8 +285,11 @@ async function loadWorkerWgsl(options = {}) {
276
285
  return source;
277
286
  }
278
287
  async function loadQueueWgsl(options = {}) {
279
- const { queueCompat = true, ...rest } = options ?? {};
280
- const source = await (0, import_gpu_lock_free_queue.loadQueueWgsl)(rest);
288
+ const { queueCompat = true, queueMode = "flat", ...rest } = options ?? {};
289
+ const source = await (0, import_gpu_lock_free_queue.loadSchedulerWgsl)({
290
+ mode: normalizeQueueMode(queueMode),
291
+ ...rest
292
+ });
281
293
  if (typeof source !== "string") {
282
294
  throw new Error("Failed to load queue WGSL source.");
283
295
  }
@@ -288,6 +300,16 @@ async function loadQueueWgsl(options = {}) {
288
300
  const compatMap = getQueueCompatMap(source);
289
301
  return applyCompatMap(source, compatMap);
290
302
  }
303
+ function ensureQueueLifecycleHooks(source) {
304
+ if (/\bfn\s+complete_job\b/.test(source)) {
305
+ return source;
306
+ }
307
+ return `${source}
308
+
309
+ fn complete_job(job_index: u32) {
310
+ _ = job_index;
311
+ }`;
312
+ }
291
313
  async function loadJobWgsl(options = {}) {
292
314
  const { wgsl, url, fetcher, label } = options ?? {};
293
315
  const source = await loadWgslSource({
@@ -318,12 +340,20 @@ async function assembleWorkerWgsl(workerWgsl, options = {}) {
318
340
  fetcher,
319
341
  jobs,
320
342
  debug,
321
- queueCompat = true
343
+ queueCompat = true,
344
+ queueMode = "flat"
322
345
  } = options ?? {};
323
- const rawQueueSource = queueWgsl ?? await (0, import_gpu_lock_free_queue.loadQueueWgsl)({ url: queueUrl, fetcher });
346
+ const resolvedQueueMode = normalizeQueueMode(queueMode);
347
+ const rawQueueSource = queueWgsl ?? await (0, import_gpu_lock_free_queue.loadSchedulerWgsl)({
348
+ mode: resolvedQueueMode,
349
+ url: queueUrl,
350
+ fetcher
351
+ });
324
352
  const bodyRaw = workerWgsl ?? await loadWorkerWgsl({ fetcher });
325
353
  const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;
326
- const queueSource = applyCompatMap(rawQueueSource, compatMap);
354
+ const queueSource = ensureQueueLifecycleHooks(
355
+ applyCompatMap(rawQueueSource, compatMap)
356
+ );
327
357
  const preludeRaw = preludeWgsl ?? (preludeUrl ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl }) : "");
328
358
  if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== "string") {
329
359
  throw new Error("Failed to load prelude WGSL source.");
@@ -396,6 +426,88 @@ function resolveWorkgroups(value, label) {
396
426
  }
397
427
  return normalizeWorkgroups(value, label);
398
428
  }
429
+ function normalizeTelemetryText(value, fallback) {
430
+ if (typeof value !== "string") {
431
+ return fallback;
432
+ }
433
+ const trimmed = value.trim();
434
+ return trimmed ? trimmed.slice(0, 120) : fallback;
435
+ }
436
+ function resolveFrameId(value) {
437
+ if (typeof value === "function") {
438
+ return resolveFrameId(value());
439
+ }
440
+ if (typeof value !== "string") {
441
+ return void 0;
442
+ }
443
+ const trimmed = value.trim();
444
+ return trimmed ? trimmed.slice(0, 120) : void 0;
445
+ }
446
+ function toVector(workgroups) {
447
+ return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };
448
+ }
449
+ function resolveTelemetryWorkgroupSize(descriptor, fallback, label) {
450
+ const size = descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;
451
+ if (size == null) {
452
+ return void 0;
453
+ }
454
+ const normalized = typeof size === "number" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);
455
+ if (!normalized) {
456
+ return void 0;
457
+ }
458
+ return toVector(normalized);
459
+ }
460
+ function getNow() {
461
+ if (globalThis.performance && typeof globalThis.performance.now === "function") {
462
+ return globalThis.performance.now();
463
+ }
464
+ return Date.now();
465
+ }
466
+ function reportOptionalError(error, onError) {
467
+ if (!onError) {
468
+ return;
469
+ }
470
+ if (error instanceof Error) {
471
+ onError(error);
472
+ return;
473
+ }
474
+ onError(new Error(String(error)));
475
+ }
476
+ function emitOptionalHook(callback, payload, onError) {
477
+ if (typeof callback !== "function") {
478
+ return;
479
+ }
480
+ try {
481
+ callback(payload);
482
+ } catch (error) {
483
+ reportOptionalError(error, onError);
484
+ }
485
+ }
486
+ function buildDispatchTelemetrySample({
487
+ kind,
488
+ descriptor,
489
+ index,
490
+ frameId,
491
+ workgroups,
492
+ workgroupSize
493
+ }) {
494
+ const labelFallback = kind === "worker" ? "worker" : `job_${index}`;
495
+ const label = normalizeTelemetryText(descriptor?.label, labelFallback);
496
+ return {
497
+ kind,
498
+ index,
499
+ label,
500
+ owner: normalizeTelemetryText(descriptor?.owner, label),
501
+ queueClass: normalizeTelemetryText(descriptor?.queueClass, "custom"),
502
+ jobType: normalizeTelemetryText(
503
+ descriptor?.jobType,
504
+ kind === "worker" ? "worker.dispatch" : label
505
+ ),
506
+ frameId,
507
+ workgroups: toVector(workgroups),
508
+ workgroupSize
509
+ };
510
+ }
399
511
  function setBindGroups(pass, bindGroups) {
400
512
  if (!bindGroups) {
401
513
  return;
@@ -427,7 +539,9 @@ function createWorkerLoop(options = {}) {
427
539
  rateHz,
428
540
  label,
429
541
  onTick,
430
- onError
542
+ onError,
543
+ frameId,
544
+ telemetry
431
545
  } = options ?? {};
432
546
  if (!device) {
433
547
  throw new Error("createWorkerLoop requires a GPUDevice.");
@@ -441,6 +555,9 @@ function createWorkerLoop(options = {}) {
441
555
  const intervalMs = Number.isFinite(rateHz) && rateHz > 0 ? 1e3 / rateHz : null;
442
556
  const tick = () => {
443
557
  try {
558
+ const tickStartMs = getNow();
559
+ const currentFrameId = resolveFrameId(frameId);
560
+ const telemetryDispatches = [];
444
561
  const encoder = device.createCommandEncoder();
445
562
  const pass = encoder.beginComputePass(
446
563
  label ? { label } : void 0
@@ -451,6 +568,20 @@ function createWorkerLoop(options = {}) {
451
568
  const workerGroups = explicitWorkerGroups ? explicitWorkerGroups : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];
452
569
  if (workerGroups[0] > 0) {
453
570
  pass.dispatchWorkgroups(...workerGroups);
571
+ telemetryDispatches.push(
572
+ buildDispatchTelemetrySample({
573
+ kind: "worker",
574
+ descriptor: worker,
575
+ index: 0,
576
+ frameId: currentFrameId,
577
+ workgroups: workerGroups,
578
+ workgroupSize: resolveTelemetryWorkgroupSize(
579
+ worker,
580
+ workgroupSize,
581
+ "worker workgroupSize"
582
+ )
583
+ })
584
+ );
454
585
  }
455
586
  jobs.forEach((job, index) => {
456
587
  if (!job || !job.pipeline) {
@@ -467,10 +598,43 @@ function createWorkerLoop(options = {}) {
467
598
  }
468
599
  if (groups[0] > 0) {
469
600
  pass.dispatchWorkgroups(...groups);
601
+ telemetryDispatches.push(
602
+ buildDispatchTelemetrySample({
603
+ kind: "job",
604
+ descriptor: job,
605
+ index,
606
+ frameId: currentFrameId,
607
+ workgroups: groups,
608
+ workgroupSize: resolveTelemetryWorkgroupSize(
609
+ job,
610
+ void 0,
611
+ `job ${index} workgroupSize`
612
+ )
613
+ })
614
+ );
470
615
  }
471
616
  });
472
617
  pass.end();
473
618
  device.queue.submit([encoder.finish()]);
619
+ telemetryDispatches.forEach((sample) => {
620
+ emitOptionalHook(telemetry?.onDispatch, sample, onError);
621
+ });
622
+ emitOptionalHook(
623
+ telemetry?.onTick,
624
+ {
625
+ frameId: currentFrameId,
626
+ tickDurationMs: getNow() - tickStartMs,
627
+ dispatchCount: telemetryDispatches.length,
628
+ workerDispatchCount: telemetryDispatches.filter(
629
+ (sample) => sample.kind === "worker"
630
+ ).length,
631
+ jobDispatchCount: telemetryDispatches.filter(
632
+ (sample) => sample.kind === "job"
633
+ ).length,
634
+ dispatches: telemetryDispatches
635
+ },
636
+ onError
637
+ );
474
638
  if (onTick) {
475
639
  onTick();
476
640
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.js"],"sourcesContent":["import { loadQueueWgsl as loadQueueWgslRaw } from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nconst jobRegistry = [];\nlet nextJobType = 0;\n\nasync function loadWgslSource(options = {}) {\n const { wgsl, url, fetcher = globalThis.fetch, baseUrl } = options ?? {};\n if (typeof wgsl === \"string\") {\n assertNotHtmlWgsl(wgsl, \"inline WGSL\");\n return wgsl;\n }\n if (!url) {\n return null;\n }\n const resolved = url instanceof URL ? url : new URL(url, baseUrl);\n if (!fetcher) {\n if (resolved.protocol !== \"file:\") {\n throw new Error(\"No fetcher available for non-file WGSL URL.\");\n }\n const { readFile } = await import(\"fs/promises\");\n const { fileURLToPath } = await import(\"url\");\n const source = await readFile(fileURLToPath(resolved), \"utf8\");\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n }\n const response = await fetcher(resolved);\n if (!response.ok) {\n const status = \"status\" in response ? response.status : \"unknown\";\n const statusText = \"statusText\" in response ? response.statusText : \"\";\n const detail = statusText ? `${status} ${statusText}` : `${status}`;\n throw new Error(`Failed to load WGSL (${detail})`);\n }\n const source = await response.text();\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n}\n\nfunction stripComments(source) {\n return source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\")\n .replace(/\\/\\/.*$/gm, \"\");\n}\n\nfunction tokenize(source) {\n return source.match(/[A-Za-z_][A-Za-z0-9_]*|[{}();<>,:=]/g) ?? [];\n}\n\nfunction isIdentifier(token) {\n return /^[A-Za-z_][A-Za-z0-9_]*$/.test(token);\n}\n\nfunction readNameAfterType(tokens, startIndex) {\n let i = startIndex;\n if (tokens[i] === \"<\") {\n let depth = 1;\n i += 1;\n while (i < tokens.length && depth > 0) {\n if (tokens[i] === \"<\") {\n depth += 1;\n } else if (tokens[i] === \">\") {\n depth -= 1;\n }\n i += 1;\n }\n }\n return tokens[i];\n}\n\nfunction scanModuleNames(source) {\n const cleaned = stripComments(source);\n const tokens = tokenize(cleaned);\n const names = [];\n let depth = 0;\n for (let i = 0; i < tokens.length; i += 1) {\n const token = tokens[i];\n if (token === \"{\") {\n depth += 1;\n continue;\n }\n if (token === \"}\") {\n depth = Math.max(0, depth - 1);\n continue;\n }\n if (depth !== 0) {\n continue;\n }\n if (token === \"fn\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"fn\", name });\n }\n continue;\n }\n if (token === \"struct\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"struct\", name });\n }\n continue;\n }\n if (token === \"alias\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"alias\", name });\n }\n continue;\n }\n if (token === \"var\" || token === \"let\" || token === \"const\" || token === \"override\") {\n const name = readNameAfterType(tokens, i + 1);\n if (isIdentifier(name)) {\n names.push({ kind: token, name });\n }\n }\n }\n return names;\n}\n\nfunction buildNameIndex(modules) {\n const index = new Map();\n for (const module of modules) {\n for (const item of scanModuleNames(module.source)) {\n const bucket = index.get(item.name) ?? [];\n bucket.push({ kind: item.kind, module: module.name });\n index.set(item.name, bucket);\n }\n }\n return index;\n}\n\nfunction assertNoNameClashes(modules) {\n const index = buildNameIndex(modules);\n const clashes = [];\n for (const [name, entries] of index.entries()) {\n if (entries.length > 1) {\n clashes.push({ name, entries });\n }\n }\n if (clashes.length === 0) {\n return;\n }\n const lines = [\"WGSL debug: identifier clashes detected:\"];\n for (const clash of clashes) {\n const locations = clash.entries\n .map((entry) => `${entry.module} (${entry.kind})`)\n .join(\", \");\n lines.push(`- ${clash.name}: ${locations}`);\n }\n throw new Error(lines.join(\"\\n\"));\n}\n\nfunction assertNotHtmlWgsl(source, context) {\n const sample = source.slice(0, 200).toLowerCase();\n if (\n sample.includes(\"<!doctype\") ||\n sample.includes(\"<html\") ||\n sample.includes(\"<meta\")\n ) {\n const label = context ? ` for ${context}` : \"\";\n throw new Error(\n `Expected WGSL${label} but received HTML. Check the URL or server root.`\n );\n }\n}\n\nfunction renameProcessJob(source, name) {\n return source.replace(/\\bprocess_job\\b/g, name);\n}\n\nfunction getQueueCompatMap(source) {\n if (!/\\bJobMeta\\b/.test(source)) {\n return null;\n }\n return [{ from: /\\bJobMeta\\b/g, to: \"JobDesc\" }];\n}\n\nfunction applyCompatMap(source, map) {\n if (!map || map.length === 0) {\n return source;\n }\n let next = source;\n for (const entry of map) {\n next = next.replace(entry.from, entry.to);\n }\n return next;\n}\n\nfunction normalizeJobs(jobs) {\n const normalized = jobs.map((job, index) => {\n if (typeof job === \"string\") {\n return {\n jobType: index,\n wgsl: job,\n label: `job_${index}`,\n sourceName: `job-${index}`,\n };\n }\n if (!job || typeof job.wgsl !== \"string\") {\n throw new Error(\"Job entries must provide WGSL source strings.\");\n }\n const jobType = job.jobType ?? index;\n const label = job.label ?? `job_${jobType}`;\n return {\n jobType,\n wgsl: job.wgsl,\n label,\n sourceName: job.sourceName ?? job.label ?? `job-${jobType}`,\n };\n });\n const seen = new Set();\n for (const job of normalized) {\n if (seen.has(job.jobType)) {\n throw new Error(`Duplicate job_type detected: ${job.jobType}`);\n }\n seen.add(job.jobType);\n }\n return normalized;\n}\n\nfunction buildProcessJobDispatch(jobs) {\n const lines = [\n \"fn process_job(job_index: u32, job_type: u32, payload_words: u32) {\",\n ];\n if (jobs.length === 0) {\n lines.push(\" return;\");\n lines.push(\"}\");\n return lines.join(\"\\n\");\n }\n jobs.forEach((job, idx) => {\n const clause = idx === 0 ? \"if\" : \"else if\";\n lines.push(` ${clause} (job_type == ${job.jobType}u) {`);\n lines.push(\n ` ${job.entryName}(job_index, job_type, payload_words);`\n );\n lines.push(\" }\");\n });\n lines.push(\"}\");\n return lines.join(\"\\n\");\n}\n\nexport async function loadWorkerWgsl(options = {}) {\n const { url = workerWgslUrl, fetcher } = options ?? {};\n const source = await loadWgslSource({\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load worker WGSL source.\");\n }\n return source;\n}\n\nexport async function loadQueueWgsl(options = {}) {\n const { queueCompat = true, ...rest } = options ?? {};\n const source = await loadQueueWgslRaw(rest);\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load queue WGSL source.\");\n }\n assertNotHtmlWgsl(source, rest?.url ? String(rest.url) : \"queue WGSL\");\n if (!queueCompat) {\n return source;\n }\n const compatMap = getQueueCompatMap(source);\n return applyCompatMap(source, compatMap);\n}\n\nexport async function loadJobWgsl(options = {}) {\n const { wgsl, url, fetcher, label } = options ?? {};\n const source = await loadWgslSource({\n wgsl,\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"loadJobWgsl requires a WGSL string or URL.\");\n }\n const jobType = nextJobType;\n nextJobType += 1;\n jobRegistry.push({\n jobType,\n wgsl: source,\n label: label ?? `job_${jobType}`,\n sourceName: label ?? `job-${jobType}`,\n });\n return jobType;\n}\n\nexport async function assembleWorkerWgsl(workerWgsl, options = {}) {\n const {\n queueWgsl,\n queueUrl,\n preludeWgsl,\n preludeUrl,\n fetcher,\n jobs,\n debug,\n queueCompat = true,\n } = options ?? {};\n const rawQueueSource =\n queueWgsl ?? (await loadQueueWgslRaw({ url: queueUrl, fetcher }));\n const bodyRaw = workerWgsl ?? (await loadWorkerWgsl({ fetcher }));\n const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;\n const queueSource = applyCompatMap(rawQueueSource, compatMap);\n const preludeRaw =\n preludeWgsl ??\n (preludeUrl\n ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl })\n : \"\");\n if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== \"string\") {\n throw new Error(\"Failed to load prelude WGSL source.\");\n }\n const preludeSource =\n typeof preludeRaw === \"string\" && preludeRaw.length > 0\n ? applyCompatMap(preludeRaw, compatMap)\n : \"\";\n const body = applyCompatMap(bodyRaw, compatMap);\n const jobList = normalizeJobs(\n typeof jobs === \"undefined\" ? jobRegistry : jobs\n );\n if (!jobList || jobList.length === 0) {\n return `${queueSource}\\n\\n${body}`;\n }\n const rewrittenJobs = jobList.map((job) => {\n const source = applyCompatMap(job.wgsl, compatMap);\n const hasProcessJob = /\\bfn\\s+process_job\\b/.test(source);\n if (!hasProcessJob) {\n throw new Error(\n `Job ${job.sourceName} is missing a process_job() entry function.`\n );\n }\n const entryName = `process_job__${job.jobType}`;\n const renamed = renameProcessJob(source, entryName);\n return { ...job, entryName, wgsl: renamed };\n });\n const dispatch = buildProcessJobDispatch(rewrittenJobs);\n const modulesForDebug = debug\n ? [\n { name: \"queue.wgsl\", source: queueSource },\n ...(preludeSource\n ? [{ name: \"jobs.prelude.wgsl\", source: preludeSource }]\n : []),\n ...rewrittenJobs.map((job) => ({\n name: job.sourceName,\n source: job.wgsl,\n })),\n { name: \"jobs.dispatch.wgsl\", source: dispatch },\n { name: \"worker.wgsl\", source: body },\n ]\n : null;\n if (modulesForDebug) {\n assertNoNameClashes(modulesForDebug);\n }\n const jobBlocks = rewrittenJobs\n .map((job) => `// Job ${job.jobType}: ${job.label}\\n${job.wgsl}`)\n .join(\"\\n\\n\");\n const preludeBlock = preludeSource ? `${preludeSource}\\n\\n` : \"\";\n return `${queueSource}\\n\\n${preludeBlock}${jobBlocks}\\n\\n${dispatch}\\n\\n${body}`;\n}\n\nfunction normalizeWorkgroups(value, label) {\n if (typeof value === \"number\") {\n return [value, 1, 1];\n }\n if (Array.isArray(value)) {\n const [x = 0, y = 1, z = 1] = value;\n return [x, y, z];\n }\n throw new Error(`Invalid workgroup count for ${label}.`);\n}\n\nfunction resolveWorkgroups(value, label) {\n if (typeof value === \"function\") {\n return normalizeWorkgroups(value(), label);\n }\n if (value == null) {\n return null;\n }\n return normalizeWorkgroups(value, label);\n}\n\nfunction setBindGroups(pass, bindGroups) {\n if (!bindGroups) {\n return;\n }\n bindGroups.forEach((group, index) => {\n if (group) {\n pass.setBindGroup(index, group);\n }\n });\n}\n\nfunction computeWorkerWorkgroups(maxJobs, workgroupSize) {\n const jobs =\n typeof maxJobs === \"function\" ? Number(maxJobs()) : Number(maxJobs);\n if (!Number.isFinite(jobs) || jobs <= 0) {\n throw new Error(\"maxJobsPerDispatch must be a positive number.\");\n }\n const size = Number(workgroupSize);\n if (!Number.isFinite(size) || size <= 0) {\n throw new Error(\"workgroupSize must be a positive number.\");\n }\n return Math.max(1, Math.ceil(jobs / size));\n}\n\nexport function createWorkerLoop(options = {}) {\n const {\n device,\n worker,\n jobs = [],\n workgroupSize = 64,\n maxJobsPerDispatch,\n rateHz,\n label,\n onTick,\n onError,\n } = options ?? {};\n\n if (!device) {\n throw new Error(\"createWorkerLoop requires a GPUDevice.\");\n }\n if (!worker || !worker.pipeline) {\n throw new Error(\"createWorkerLoop requires a worker pipeline.\");\n }\n\n let running = false;\n let handle = null;\n let usingRaf = false;\n const intervalMs =\n Number.isFinite(rateHz) && rateHz > 0 ? 1000 / rateHz : null;\n\n const tick = () => {\n try {\n const encoder = device.createCommandEncoder();\n const pass = encoder.beginComputePass(\n label ? { label } : undefined\n );\n\n pass.setPipeline(worker.pipeline);\n setBindGroups(pass, worker.bindGroups);\n\n const explicitWorkerGroups =\n resolveWorkgroups(worker.workgroups, \"worker\") ??\n resolveWorkgroups(worker.workgroupCount, \"worker\") ??\n resolveWorkgroups(worker.dispatch, \"worker\");\n\n const workerGroups = explicitWorkerGroups\n ? explicitWorkerGroups\n : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];\n\n if (workerGroups[0] > 0) {\n pass.dispatchWorkgroups(...workerGroups);\n }\n\n jobs.forEach((job, index) => {\n if (!job || !job.pipeline) {\n throw new Error(`Job pipeline missing at index ${index}.`);\n }\n pass.setPipeline(job.pipeline);\n setBindGroups(pass, job.bindGroups);\n const groups = resolveWorkgroups(\n job.workgroups ?? job.workgroupCount ?? job.dispatch,\n `job ${index}`\n );\n if (!groups) {\n throw new Error(`Job ${index} requires a workgroup count.`);\n }\n if (groups[0] > 0) {\n pass.dispatchWorkgroups(...groups);\n }\n });\n\n pass.end();\n device.queue.submit([encoder.finish()]);\n\n if (onTick) {\n onTick();\n }\n } catch (err) {\n if (onError) {\n onError(err);\n return;\n }\n throw err;\n }\n };\n\n const scheduleNext = () => {\n if (!running) {\n return;\n }\n if (intervalMs != null) {\n tick();\n usingRaf = false;\n handle = setTimeout(scheduleNext, intervalMs);\n return;\n }\n tick();\n if (typeof requestAnimationFrame === \"function\") {\n usingRaf = true;\n handle = requestAnimationFrame(scheduleNext);\n } else {\n usingRaf = false;\n handle = setTimeout(scheduleNext, 0);\n }\n };\n\n const start = () => {\n if (running) {\n return;\n }\n running = true;\n scheduleNext();\n };\n\n const stop = () => {\n running = false;\n if (handle == null) {\n return;\n }\n if (usingRaf && typeof cancelAnimationFrame === \"function\") {\n cancelAnimationFrame(handle);\n } else {\n clearTimeout(handle);\n }\n handle = null;\n };\n\n return {\n start,\n stop,\n tick,\n get running() {\n return running;\n },\n };\n}\n"],"mappings":"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","names":["source","module","loadQueueWgslRaw"]}
1
+ {"version":3,"sources":["../src/index.js"],"sourcesContent":["import {\n loadSchedulerWgsl as loadSchedulerWgslRaw,\n schedulerModes as workerSchedulerModes,\n} from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nconst jobRegistry = [];\nlet nextJobType = 0;\n\nasync function loadWgslSource(options = {}) {\n const { wgsl, url, fetcher = globalThis.fetch, baseUrl } = options ?? {};\n if (typeof wgsl === \"string\") {\n assertNotHtmlWgsl(wgsl, \"inline WGSL\");\n return wgsl;\n }\n if (!url) {\n return null;\n }\n const resolved = url instanceof URL ? url : new URL(url, baseUrl);\n if (!fetcher) {\n if (resolved.protocol !== \"file:\") {\n throw new Error(\"No fetcher available for non-file WGSL URL.\");\n }\n const { readFile } = await import(\"fs/promises\");\n const { fileURLToPath } = await import(\"url\");\n const source = await readFile(fileURLToPath(resolved), \"utf8\");\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n }\n const response = await fetcher(resolved);\n if (!response.ok) {\n const status = \"status\" in response ? response.status : \"unknown\";\n const statusText = \"statusText\" in response ? response.statusText : \"\";\n const detail = statusText ? `${status} ${statusText}` : `${status}`;\n throw new Error(`Failed to load WGSL (${detail})`);\n }\n const source = await response.text();\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n}\n\nfunction stripComments(source) {\n return source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\")\n .replace(/\\/\\/.*$/gm, \"\");\n}\n\nfunction tokenize(source) {\n return source.match(/[A-Za-z_][A-Za-z0-9_]*|[{}();<>,:=]/g) ?? [];\n}\n\nfunction isIdentifier(token) {\n return /^[A-Za-z_][A-Za-z0-9_]*$/.test(token);\n}\n\nfunction readNameAfterType(tokens, startIndex) {\n let i = startIndex;\n if (tokens[i] === \"<\") {\n let depth = 1;\n i += 1;\n while (i < tokens.length && depth > 0) {\n if (tokens[i] === \"<\") {\n depth += 1;\n } else if (tokens[i] === \">\") {\n depth -= 1;\n }\n i += 1;\n }\n }\n return tokens[i];\n}\n\nfunction scanModuleNames(source) {\n const cleaned = stripComments(source);\n const tokens = tokenize(cleaned);\n const names = [];\n let depth = 0;\n for (let i = 0; i < tokens.length; i += 1) {\n const token = tokens[i];\n if (token === \"{\") {\n depth += 1;\n continue;\n }\n if (token === \"}\") {\n depth = Math.max(0, depth - 1);\n continue;\n }\n if (depth !== 0) {\n continue;\n }\n if (token === \"fn\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"fn\", name });\n }\n continue;\n }\n if (token === \"struct\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"struct\", name });\n }\n continue;\n }\n if (token === \"alias\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"alias\", name });\n }\n continue;\n }\n if (token === \"var\" || token === \"let\" || token === \"const\" || token === \"override\") {\n const name = readNameAfterType(tokens, i + 1);\n if (isIdentifier(name)) {\n names.push({ kind: token, name });\n }\n }\n }\n return names;\n}\n\nfunction buildNameIndex(modules) {\n const index = new Map();\n for (const module of modules) {\n for (const item of scanModuleNames(module.source)) {\n const bucket = index.get(item.name) ?? [];\n bucket.push({ kind: item.kind, module: module.name });\n index.set(item.name, bucket);\n }\n }\n return index;\n}\n\nfunction assertNoNameClashes(modules) {\n const index = buildNameIndex(modules);\n const clashes = [];\n for (const [name, entries] of index.entries()) {\n if (entries.length > 1) {\n clashes.push({ name, entries });\n }\n }\n if (clashes.length === 0) {\n return;\n }\n const lines = [\"WGSL debug: identifier clashes detected:\"];\n for (const clash of clashes) {\n const locations = clash.entries\n .map((entry) => `${entry.module} (${entry.kind})`)\n .join(\", \");\n lines.push(`- ${clash.name}: ${locations}`);\n }\n throw new Error(lines.join(\"\\n\"));\n}\n\nfunction assertNotHtmlWgsl(source, context) {\n const sample = source.slice(0, 200).toLowerCase();\n if (\n sample.includes(\"<!doctype\") ||\n sample.includes(\"<html\") ||\n sample.includes(\"<meta\")\n ) {\n const label = context ? ` for ${context}` : \"\";\n throw new Error(\n `Expected WGSL${label} but received HTML. Check the URL or server root.`\n );\n }\n}\n\nfunction renameProcessJob(source, name) {\n return source.replace(/\\bprocess_job\\b/g, name);\n}\n\nfunction getQueueCompatMap(source) {\n if (!/\\bJobMeta\\b/.test(source)) {\n return null;\n }\n return [{ from: /\\bJobMeta\\b/g, to: \"JobDesc\" }];\n}\n\nfunction normalizeQueueMode(mode) {\n const resolved = mode ?? \"flat\";\n if (!workerSchedulerModes.includes(resolved)) {\n throw new Error(\n `queueMode must be one of: ${workerSchedulerModes.join(\", \")}.`\n );\n }\n return resolved;\n}\n\nfunction applyCompatMap(source, map) {\n if (!map || map.length === 0) {\n return source;\n }\n let next = source;\n for (const entry of map) {\n next = next.replace(entry.from, entry.to);\n }\n return next;\n}\n\nfunction normalizeJobs(jobs) {\n const normalized = jobs.map((job, index) => {\n if (typeof job === \"string\") {\n return {\n jobType: index,\n wgsl: job,\n label: `job_${index}`,\n sourceName: `job-${index}`,\n };\n }\n if (!job || typeof job.wgsl !== \"string\") {\n throw new Error(\"Job entries must provide WGSL source strings.\");\n }\n const jobType = job.jobType ?? index;\n const label = job.label ?? `job_${jobType}`;\n return {\n jobType,\n wgsl: job.wgsl,\n label,\n sourceName: job.sourceName ?? job.label ?? `job-${jobType}`,\n };\n });\n const seen = new Set();\n for (const job of normalized) {\n if (seen.has(job.jobType)) {\n throw new Error(`Duplicate job_type detected: ${job.jobType}`);\n }\n seen.add(job.jobType);\n }\n return normalized;\n}\n\nfunction buildProcessJobDispatch(jobs) {\n const lines = [\n \"fn process_job(job_index: u32, job_type: u32, payload_words: u32) {\",\n ];\n if (jobs.length === 0) {\n lines.push(\" return;\");\n lines.push(\"}\");\n return lines.join(\"\\n\");\n }\n jobs.forEach((job, idx) => {\n const clause = idx === 0 ? \"if\" : \"else if\";\n lines.push(` ${clause} (job_type == ${job.jobType}u) {`);\n lines.push(\n ` ${job.entryName}(job_index, job_type, payload_words);`\n );\n lines.push(\" }\");\n });\n lines.push(\"}\");\n return lines.join(\"\\n\");\n}\n\nexport async function loadWorkerWgsl(options = {}) {\n const { url = workerWgslUrl, fetcher } = options ?? {};\n const source = await loadWgslSource({\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load worker WGSL source.\");\n }\n return source;\n}\n\nexport async function loadQueueWgsl(options = {}) {\n const { queueCompat = true, queueMode = \"flat\", ...rest } = options ?? {};\n const source = await loadSchedulerWgslRaw({\n mode: normalizeQueueMode(queueMode),\n ...rest,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load queue WGSL source.\");\n }\n assertNotHtmlWgsl(source, rest?.url ? String(rest.url) : \"queue WGSL\");\n if (!queueCompat) {\n return source;\n }\n const compatMap = getQueueCompatMap(source);\n return applyCompatMap(source, compatMap);\n}\n\nfunction ensureQueueLifecycleHooks(source) {\n if (/\\bfn\\s+complete_job\\b/.test(source)) {\n return source;\n }\n return `${source}\\n\\nfn complete_job(job_index: u32) {\\n _ = job_index;\\n}`;\n}\n\nexport async function loadJobWgsl(options = {}) {\n const { wgsl, url, fetcher, label } = options ?? {};\n const source = await loadWgslSource({\n wgsl,\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"loadJobWgsl requires a WGSL string or URL.\");\n }\n const jobType = nextJobType;\n nextJobType += 1;\n jobRegistry.push({\n jobType,\n wgsl: source,\n label: label ?? `job_${jobType}`,\n sourceName: label ?? `job-${jobType}`,\n });\n return jobType;\n}\n\nexport async function assembleWorkerWgsl(workerWgsl, options = {}) {\n const {\n queueWgsl,\n queueUrl,\n preludeWgsl,\n preludeUrl,\n fetcher,\n jobs,\n debug,\n queueCompat = true,\n queueMode = \"flat\",\n } = options ?? {};\n const resolvedQueueMode = normalizeQueueMode(queueMode);\n const rawQueueSource =\n queueWgsl ??\n (await loadSchedulerWgslRaw({\n mode: resolvedQueueMode,\n url: queueUrl,\n fetcher,\n }));\n const bodyRaw = workerWgsl ?? (await loadWorkerWgsl({ fetcher }));\n const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;\n const queueSource = ensureQueueLifecycleHooks(\n applyCompatMap(rawQueueSource, compatMap)\n );\n const preludeRaw =\n preludeWgsl ??\n (preludeUrl\n ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl })\n : \"\");\n if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== \"string\") {\n throw new Error(\"Failed to load prelude WGSL source.\");\n }\n const preludeSource =\n typeof preludeRaw === \"string\" && preludeRaw.length > 0\n ? applyCompatMap(preludeRaw, compatMap)\n : \"\";\n const body = applyCompatMap(bodyRaw, compatMap);\n const jobList = normalizeJobs(\n typeof jobs === \"undefined\" ? jobRegistry : jobs\n );\n if (!jobList || jobList.length === 0) {\n return `${queueSource}\\n\\n${body}`;\n }\n const rewrittenJobs = jobList.map((job) => {\n const source = applyCompatMap(job.wgsl, compatMap);\n const hasProcessJob = /\\bfn\\s+process_job\\b/.test(source);\n if (!hasProcessJob) {\n throw new Error(\n `Job ${job.sourceName} is missing a process_job() entry function.`\n );\n }\n const entryName = `process_job__${job.jobType}`;\n const renamed = renameProcessJob(source, entryName);\n return { ...job, entryName, wgsl: renamed };\n });\n const dispatch = buildProcessJobDispatch(rewrittenJobs);\n const modulesForDebug = debug\n ? [\n { name: \"queue.wgsl\", source: queueSource },\n ...(preludeSource\n ? [{ name: \"jobs.prelude.wgsl\", source: preludeSource }]\n : []),\n ...rewrittenJobs.map((job) => ({\n name: job.sourceName,\n source: job.wgsl,\n })),\n { name: \"jobs.dispatch.wgsl\", source: dispatch },\n { name: \"worker.wgsl\", source: body },\n ]\n : null;\n if (modulesForDebug) {\n assertNoNameClashes(modulesForDebug);\n }\n const jobBlocks = rewrittenJobs\n .map((job) => `// Job ${job.jobType}: ${job.label}\\n${job.wgsl}`)\n .join(\"\\n\\n\");\n const preludeBlock = preludeSource ? `${preludeSource}\\n\\n` : \"\";\n return `${queueSource}\\n\\n${preludeBlock}${jobBlocks}\\n\\n${dispatch}\\n\\n${body}`;\n}\n\nfunction normalizeWorkgroups(value, label) {\n if (typeof value === \"number\") {\n return [value, 1, 1];\n }\n if (Array.isArray(value)) {\n const [x = 0, y = 1, z = 1] = value;\n return [x, y, z];\n }\n throw new Error(`Invalid workgroup count for ${label}.`);\n}\n\nfunction resolveWorkgroups(value, label) {\n if (typeof value === \"function\") {\n return normalizeWorkgroups(value(), label);\n }\n if (value == null) {\n return null;\n }\n return normalizeWorkgroups(value, label);\n}\n\nfunction normalizeTelemetryText(value, fallback) {\n if (typeof value !== \"string\") {\n return fallback;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed.slice(0, 120) : fallback;\n}\n\nfunction resolveFrameId(value) {\n if (typeof value === \"function\") {\n return resolveFrameId(value());\n }\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed.slice(0, 120) : undefined;\n}\n\nfunction toVector(workgroups) {\n return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };\n}\n\nfunction resolveTelemetryWorkgroupSize(descriptor, fallback, label) {\n const size =\n descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;\n if (size == null) {\n return undefined;\n }\n const normalized =\n typeof size === \"number\" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);\n if (!normalized) {\n return undefined;\n }\n return toVector(normalized);\n}\n\nfunction getNow() {\n if (globalThis.performance && typeof globalThis.performance.now === \"function\") {\n return globalThis.performance.now();\n }\n return Date.now();\n}\n\nfunction reportOptionalError(error, onError) {\n if (!onError) {\n return;\n }\n if (error instanceof Error) {\n onError(error);\n return;\n }\n onError(new Error(String(error)));\n}\n\nfunction emitOptionalHook(callback, payload, onError) {\n if (typeof callback !== \"function\") {\n return;\n }\n try {\n callback(payload);\n } catch (error) {\n reportOptionalError(error, onError);\n }\n}\n\nfunction buildDispatchTelemetrySample({\n kind,\n descriptor,\n index,\n frameId,\n workgroups,\n workgroupSize,\n}) {\n const labelFallback = kind === \"worker\" ? \"worker\" : `job_${index}`;\n const label = normalizeTelemetryText(descriptor?.label, labelFallback);\n return {\n kind,\n index,\n label,\n owner: normalizeTelemetryText(descriptor?.owner, label),\n queueClass: normalizeTelemetryText(descriptor?.queueClass, \"custom\"),\n jobType: normalizeTelemetryText(\n descriptor?.jobType,\n kind === \"worker\" ? \"worker.dispatch\" : label\n ),\n frameId,\n workgroups: toVector(workgroups),\n workgroupSize,\n };\n}\n\nfunction setBindGroups(pass, bindGroups) {\n if (!bindGroups) {\n return;\n }\n bindGroups.forEach((group, index) => {\n if (group) {\n pass.setBindGroup(index, group);\n }\n });\n}\n\nfunction computeWorkerWorkgroups(maxJobs, workgroupSize) {\n const jobs =\n typeof maxJobs === \"function\" ? Number(maxJobs()) : Number(maxJobs);\n if (!Number.isFinite(jobs) || jobs <= 0) {\n throw new Error(\"maxJobsPerDispatch must be a positive number.\");\n }\n const size = Number(workgroupSize);\n if (!Number.isFinite(size) || size <= 0) {\n throw new Error(\"workgroupSize must be a positive number.\");\n }\n return Math.max(1, Math.ceil(jobs / size));\n}\n\nexport function createWorkerLoop(options = {}) {\n const {\n device,\n worker,\n jobs = [],\n workgroupSize = 64,\n maxJobsPerDispatch,\n rateHz,\n label,\n onTick,\n onError,\n frameId,\n telemetry,\n } = options ?? {};\n\n if (!device) {\n throw new Error(\"createWorkerLoop requires a GPUDevice.\");\n }\n if (!worker || !worker.pipeline) {\n throw new Error(\"createWorkerLoop requires a worker pipeline.\");\n }\n\n let running = false;\n let handle = null;\n let usingRaf = false;\n const intervalMs =\n Number.isFinite(rateHz) && rateHz > 0 ? 1000 / rateHz : null;\n\n const tick = () => {\n try {\n const tickStartMs = getNow();\n const currentFrameId = resolveFrameId(frameId);\n const telemetryDispatches = [];\n const encoder = device.createCommandEncoder();\n const pass = encoder.beginComputePass(\n label ? { label } : undefined\n );\n\n pass.setPipeline(worker.pipeline);\n setBindGroups(pass, worker.bindGroups);\n\n const explicitWorkerGroups =\n resolveWorkgroups(worker.workgroups, \"worker\") ??\n resolveWorkgroups(worker.workgroupCount, \"worker\") ??\n resolveWorkgroups(worker.dispatch, \"worker\");\n\n const workerGroups = explicitWorkerGroups\n ? explicitWorkerGroups\n : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];\n\n if (workerGroups[0] > 0) {\n pass.dispatchWorkgroups(...workerGroups);\n telemetryDispatches.push(\n buildDispatchTelemetrySample({\n kind: \"worker\",\n descriptor: worker,\n index: 0,\n frameId: currentFrameId,\n workgroups: workerGroups,\n workgroupSize: resolveTelemetryWorkgroupSize(\n worker,\n workgroupSize,\n \"worker workgroupSize\"\n ),\n })\n );\n }\n\n jobs.forEach((job, index) => {\n if (!job || !job.pipeline) {\n throw new Error(`Job pipeline missing at index ${index}.`);\n }\n pass.setPipeline(job.pipeline);\n setBindGroups(pass, job.bindGroups);\n const groups = resolveWorkgroups(\n job.workgroups ?? job.workgroupCount ?? job.dispatch,\n `job ${index}`\n );\n if (!groups) {\n throw new Error(`Job ${index} requires a workgroup count.`);\n }\n if (groups[0] > 0) {\n pass.dispatchWorkgroups(...groups);\n telemetryDispatches.push(\n buildDispatchTelemetrySample({\n kind: \"job\",\n descriptor: job,\n index,\n frameId: currentFrameId,\n workgroups: groups,\n workgroupSize: resolveTelemetryWorkgroupSize(\n job,\n undefined,\n `job ${index} workgroupSize`\n ),\n })\n );\n }\n });\n\n pass.end();\n device.queue.submit([encoder.finish()]);\n\n telemetryDispatches.forEach((sample) => {\n emitOptionalHook(telemetry?.onDispatch, sample, onError);\n });\n emitOptionalHook(\n telemetry?.onTick,\n {\n frameId: currentFrameId,\n tickDurationMs: getNow() - tickStartMs,\n dispatchCount: telemetryDispatches.length,\n workerDispatchCount: telemetryDispatches.filter(\n (sample) => sample.kind === \"worker\"\n ).length,\n jobDispatchCount: telemetryDispatches.filter(\n (sample) => sample.kind === \"job\"\n ).length,\n dispatches: telemetryDispatches,\n },\n onError\n );\n\n if (onTick) {\n onTick();\n }\n } catch (err) {\n if (onError) {\n onError(err);\n return;\n }\n throw err;\n }\n };\n\n const scheduleNext = () => {\n if (!running) {\n return;\n }\n if (intervalMs != null) {\n tick();\n usingRaf = false;\n handle = setTimeout(scheduleNext, intervalMs);\n return;\n }\n tick();\n if (typeof requestAnimationFrame === \"function\") {\n usingRaf = true;\n handle = requestAnimationFrame(scheduleNext);\n } else {\n usingRaf = false;\n handle = setTimeout(scheduleNext, 0);\n }\n };\n\n const start = () => {\n if (running) {\n return;\n }\n running = true;\n scheduleNext();\n };\n\n const stop = () => {\n running = false;\n if (handle == null) {\n return;\n }\n if (usingRaf && typeof cancelAnimationFrame === \"function\") {\n cancelAnimationFrame(handle);\n } else {\n clearTimeout(handle);\n }\n handle = null;\n };\n\n return {\n start,\n stop,\n tick,\n get running() {\n return running;\n },\n };\n}\n"],"mappings":"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","names":["source","module","workerSchedulerModes","loadSchedulerWgslRaw"]}
package/dist/index.js CHANGED
@@ -6,7 +6,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/index.js
9
- import { loadQueueWgsl as loadQueueWgslRaw } from "@plasius/gpu-lock-free-queue";
9
+ import {
10
+ loadSchedulerWgsl as loadSchedulerWgslRaw,
11
+ schedulerModes as workerSchedulerModes
12
+ } from "@plasius/gpu-lock-free-queue";
10
13
  var workerWgslUrl = (() => {
11
14
  if (typeof import.meta.url !== "undefined") {
12
15
  return new URL("./worker.wgsl", import.meta.url);
@@ -171,6 +174,15 @@ function getQueueCompatMap(source) {
171
174
  }
172
175
  return [{ from: /\bJobMeta\b/g, to: "JobDesc" }];
173
176
  }
177
+ function normalizeQueueMode(mode) {
178
+ const resolved = mode ?? "flat";
179
+ if (!workerSchedulerModes.includes(resolved)) {
180
+ throw new Error(
181
+ `queueMode must be one of: ${workerSchedulerModes.join(", ")}.`
182
+ );
183
+ }
184
+ return resolved;
185
+ }
174
186
  function applyCompatMap(source, map) {
175
187
  if (!map || map.length === 0) {
176
188
  return source;
@@ -245,8 +257,11 @@ async function loadWorkerWgsl(options = {}) {
245
257
  return source;
246
258
  }
247
259
  async function loadQueueWgsl(options = {}) {
248
- const { queueCompat = true, ...rest } = options ?? {};
249
- const source = await loadQueueWgslRaw(rest);
260
+ const { queueCompat = true, queueMode = "flat", ...rest } = options ?? {};
261
+ const source = await loadSchedulerWgslRaw({
262
+ mode: normalizeQueueMode(queueMode),
263
+ ...rest
264
+ });
250
265
  if (typeof source !== "string") {
251
266
  throw new Error("Failed to load queue WGSL source.");
252
267
  }
@@ -257,6 +272,16 @@ async function loadQueueWgsl(options = {}) {
257
272
  const compatMap = getQueueCompatMap(source);
258
273
  return applyCompatMap(source, compatMap);
259
274
  }
275
+ function ensureQueueLifecycleHooks(source) {
276
+ if (/\bfn\s+complete_job\b/.test(source)) {
277
+ return source;
278
+ }
279
+ return `${source}
280
+
281
+ fn complete_job(job_index: u32) {
282
+ _ = job_index;
283
+ }`;
284
+ }
260
285
  async function loadJobWgsl(options = {}) {
261
286
  const { wgsl, url, fetcher, label } = options ?? {};
262
287
  const source = await loadWgslSource({
@@ -287,12 +312,20 @@ async function assembleWorkerWgsl(workerWgsl, options = {}) {
287
312
  fetcher,
288
313
  jobs,
289
314
  debug,
290
- queueCompat = true
315
+ queueCompat = true,
316
+ queueMode = "flat"
291
317
  } = options ?? {};
292
- const rawQueueSource = queueWgsl ?? await loadQueueWgslRaw({ url: queueUrl, fetcher });
318
+ const resolvedQueueMode = normalizeQueueMode(queueMode);
319
+ const rawQueueSource = queueWgsl ?? await loadSchedulerWgslRaw({
320
+ mode: resolvedQueueMode,
321
+ url: queueUrl,
322
+ fetcher
323
+ });
293
324
  const bodyRaw = workerWgsl ?? await loadWorkerWgsl({ fetcher });
294
325
  const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;
295
- const queueSource = applyCompatMap(rawQueueSource, compatMap);
326
+ const queueSource = ensureQueueLifecycleHooks(
327
+ applyCompatMap(rawQueueSource, compatMap)
328
+ );
296
329
  const preludeRaw = preludeWgsl ?? (preludeUrl ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl }) : "");
297
330
  if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== "string") {
298
331
  throw new Error("Failed to load prelude WGSL source.");
@@ -365,6 +398,88 @@ function resolveWorkgroups(value, label) {
365
398
  }
366
399
  return normalizeWorkgroups(value, label);
367
400
  }
401
+ function normalizeTelemetryText(value, fallback) {
402
+ if (typeof value !== "string") {
403
+ return fallback;
404
+ }
405
+ const trimmed = value.trim();
406
+ return trimmed ? trimmed.slice(0, 120) : fallback;
407
+ }
408
+ function resolveFrameId(value) {
409
+ if (typeof value === "function") {
410
+ return resolveFrameId(value());
411
+ }
412
+ if (typeof value !== "string") {
413
+ return void 0;
414
+ }
415
+ const trimmed = value.trim();
416
+ return trimmed ? trimmed.slice(0, 120) : void 0;
417
+ }
418
+ function toVector(workgroups) {
419
+ return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };
420
+ }
421
+ function resolveTelemetryWorkgroupSize(descriptor, fallback, label) {
422
+ const size = descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;
423
+ if (size == null) {
424
+ return void 0;
425
+ }
426
+ const normalized = typeof size === "number" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);
427
+ if (!normalized) {
428
+ return void 0;
429
+ }
430
+ return toVector(normalized);
431
+ }
432
+ function getNow() {
433
+ if (globalThis.performance && typeof globalThis.performance.now === "function") {
434
+ return globalThis.performance.now();
435
+ }
436
+ return Date.now();
437
+ }
438
+ function reportOptionalError(error, onError) {
439
+ if (!onError) {
440
+ return;
441
+ }
442
+ if (error instanceof Error) {
443
+ onError(error);
444
+ return;
445
+ }
446
+ onError(new Error(String(error)));
447
+ }
448
+ function emitOptionalHook(callback, payload, onError) {
449
+ if (typeof callback !== "function") {
450
+ return;
451
+ }
452
+ try {
453
+ callback(payload);
454
+ } catch (error) {
455
+ reportOptionalError(error, onError);
456
+ }
457
+ }
458
+ function buildDispatchTelemetrySample({
459
+ kind,
460
+ descriptor,
461
+ index,
462
+ frameId,
463
+ workgroups,
464
+ workgroupSize
465
+ }) {
466
+ const labelFallback = kind === "worker" ? "worker" : `job_${index}`;
467
+ const label = normalizeTelemetryText(descriptor?.label, labelFallback);
468
+ return {
469
+ kind,
470
+ index,
471
+ label,
472
+ owner: normalizeTelemetryText(descriptor?.owner, label),
473
+ queueClass: normalizeTelemetryText(descriptor?.queueClass, "custom"),
474
+ jobType: normalizeTelemetryText(
475
+ descriptor?.jobType,
476
+ kind === "worker" ? "worker.dispatch" : label
477
+ ),
478
+ frameId,
479
+ workgroups: toVector(workgroups),
480
+ workgroupSize
481
+ };
482
+ }
368
483
  function setBindGroups(pass, bindGroups) {
369
484
  if (!bindGroups) {
370
485
  return;
@@ -396,7 +511,9 @@ function createWorkerLoop(options = {}) {
396
511
  rateHz,
397
512
  label,
398
513
  onTick,
399
- onError
514
+ onError,
515
+ frameId,
516
+ telemetry
400
517
  } = options ?? {};
401
518
  if (!device) {
402
519
  throw new Error("createWorkerLoop requires a GPUDevice.");
@@ -410,6 +527,9 @@ function createWorkerLoop(options = {}) {
410
527
  const intervalMs = Number.isFinite(rateHz) && rateHz > 0 ? 1e3 / rateHz : null;
411
528
  const tick = () => {
412
529
  try {
530
+ const tickStartMs = getNow();
531
+ const currentFrameId = resolveFrameId(frameId);
532
+ const telemetryDispatches = [];
413
533
  const encoder = device.createCommandEncoder();
414
534
  const pass = encoder.beginComputePass(
415
535
  label ? { label } : void 0
@@ -420,6 +540,20 @@ function createWorkerLoop(options = {}) {
420
540
  const workerGroups = explicitWorkerGroups ? explicitWorkerGroups : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];
421
541
  if (workerGroups[0] > 0) {
422
542
  pass.dispatchWorkgroups(...workerGroups);
543
+ telemetryDispatches.push(
544
+ buildDispatchTelemetrySample({
545
+ kind: "worker",
546
+ descriptor: worker,
547
+ index: 0,
548
+ frameId: currentFrameId,
549
+ workgroups: workerGroups,
550
+ workgroupSize: resolveTelemetryWorkgroupSize(
551
+ worker,
552
+ workgroupSize,
553
+ "worker workgroupSize"
554
+ )
555
+ })
556
+ );
423
557
  }
424
558
  jobs.forEach((job, index) => {
425
559
  if (!job || !job.pipeline) {
@@ -436,10 +570,43 @@ function createWorkerLoop(options = {}) {
436
570
  }
437
571
  if (groups[0] > 0) {
438
572
  pass.dispatchWorkgroups(...groups);
573
+ telemetryDispatches.push(
574
+ buildDispatchTelemetrySample({
575
+ kind: "job",
576
+ descriptor: job,
577
+ index,
578
+ frameId: currentFrameId,
579
+ workgroups: groups,
580
+ workgroupSize: resolveTelemetryWorkgroupSize(
581
+ job,
582
+ void 0,
583
+ `job ${index} workgroupSize`
584
+ )
585
+ })
586
+ );
439
587
  }
440
588
  });
441
589
  pass.end();
442
590
  device.queue.submit([encoder.finish()]);
591
+ telemetryDispatches.forEach((sample) => {
592
+ emitOptionalHook(telemetry?.onDispatch, sample, onError);
593
+ });
594
+ emitOptionalHook(
595
+ telemetry?.onTick,
596
+ {
597
+ frameId: currentFrameId,
598
+ tickDurationMs: getNow() - tickStartMs,
599
+ dispatchCount: telemetryDispatches.length,
600
+ workerDispatchCount: telemetryDispatches.filter(
601
+ (sample) => sample.kind === "worker"
602
+ ).length,
603
+ jobDispatchCount: telemetryDispatches.filter(
604
+ (sample) => sample.kind === "job"
605
+ ).length,
606
+ dispatches: telemetryDispatches
607
+ },
608
+ onError
609
+ );
443
610
  if (onTick) {
444
611
  onTick();
445
612
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.js"],"sourcesContent":["import { loadQueueWgsl as loadQueueWgslRaw } from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nconst jobRegistry = [];\nlet nextJobType = 0;\n\nasync function loadWgslSource(options = {}) {\n const { wgsl, url, fetcher = globalThis.fetch, baseUrl } = options ?? {};\n if (typeof wgsl === \"string\") {\n assertNotHtmlWgsl(wgsl, \"inline WGSL\");\n return wgsl;\n }\n if (!url) {\n return null;\n }\n const resolved = url instanceof URL ? url : new URL(url, baseUrl);\n if (!fetcher) {\n if (resolved.protocol !== \"file:\") {\n throw new Error(\"No fetcher available for non-file WGSL URL.\");\n }\n const { readFile } = await import(\"fs/promises\");\n const { fileURLToPath } = await import(\"url\");\n const source = await readFile(fileURLToPath(resolved), \"utf8\");\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n }\n const response = await fetcher(resolved);\n if (!response.ok) {\n const status = \"status\" in response ? response.status : \"unknown\";\n const statusText = \"statusText\" in response ? response.statusText : \"\";\n const detail = statusText ? `${status} ${statusText}` : `${status}`;\n throw new Error(`Failed to load WGSL (${detail})`);\n }\n const source = await response.text();\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n}\n\nfunction stripComments(source) {\n return source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\")\n .replace(/\\/\\/.*$/gm, \"\");\n}\n\nfunction tokenize(source) {\n return source.match(/[A-Za-z_][A-Za-z0-9_]*|[{}();<>,:=]/g) ?? [];\n}\n\nfunction isIdentifier(token) {\n return /^[A-Za-z_][A-Za-z0-9_]*$/.test(token);\n}\n\nfunction readNameAfterType(tokens, startIndex) {\n let i = startIndex;\n if (tokens[i] === \"<\") {\n let depth = 1;\n i += 1;\n while (i < tokens.length && depth > 0) {\n if (tokens[i] === \"<\") {\n depth += 1;\n } else if (tokens[i] === \">\") {\n depth -= 1;\n }\n i += 1;\n }\n }\n return tokens[i];\n}\n\nfunction scanModuleNames(source) {\n const cleaned = stripComments(source);\n const tokens = tokenize(cleaned);\n const names = [];\n let depth = 0;\n for (let i = 0; i < tokens.length; i += 1) {\n const token = tokens[i];\n if (token === \"{\") {\n depth += 1;\n continue;\n }\n if (token === \"}\") {\n depth = Math.max(0, depth - 1);\n continue;\n }\n if (depth !== 0) {\n continue;\n }\n if (token === \"fn\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"fn\", name });\n }\n continue;\n }\n if (token === \"struct\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"struct\", name });\n }\n continue;\n }\n if (token === \"alias\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"alias\", name });\n }\n continue;\n }\n if (token === \"var\" || token === \"let\" || token === \"const\" || token === \"override\") {\n const name = readNameAfterType(tokens, i + 1);\n if (isIdentifier(name)) {\n names.push({ kind: token, name });\n }\n }\n }\n return names;\n}\n\nfunction buildNameIndex(modules) {\n const index = new Map();\n for (const module of modules) {\n for (const item of scanModuleNames(module.source)) {\n const bucket = index.get(item.name) ?? [];\n bucket.push({ kind: item.kind, module: module.name });\n index.set(item.name, bucket);\n }\n }\n return index;\n}\n\nfunction assertNoNameClashes(modules) {\n const index = buildNameIndex(modules);\n const clashes = [];\n for (const [name, entries] of index.entries()) {\n if (entries.length > 1) {\n clashes.push({ name, entries });\n }\n }\n if (clashes.length === 0) {\n return;\n }\n const lines = [\"WGSL debug: identifier clashes detected:\"];\n for (const clash of clashes) {\n const locations = clash.entries\n .map((entry) => `${entry.module} (${entry.kind})`)\n .join(\", \");\n lines.push(`- ${clash.name}: ${locations}`);\n }\n throw new Error(lines.join(\"\\n\"));\n}\n\nfunction assertNotHtmlWgsl(source, context) {\n const sample = source.slice(0, 200).toLowerCase();\n if (\n sample.includes(\"<!doctype\") ||\n sample.includes(\"<html\") ||\n sample.includes(\"<meta\")\n ) {\n const label = context ? ` for ${context}` : \"\";\n throw new Error(\n `Expected WGSL${label} but received HTML. Check the URL or server root.`\n );\n }\n}\n\nfunction renameProcessJob(source, name) {\n return source.replace(/\\bprocess_job\\b/g, name);\n}\n\nfunction getQueueCompatMap(source) {\n if (!/\\bJobMeta\\b/.test(source)) {\n return null;\n }\n return [{ from: /\\bJobMeta\\b/g, to: \"JobDesc\" }];\n}\n\nfunction applyCompatMap(source, map) {\n if (!map || map.length === 0) {\n return source;\n }\n let next = source;\n for (const entry of map) {\n next = next.replace(entry.from, entry.to);\n }\n return next;\n}\n\nfunction normalizeJobs(jobs) {\n const normalized = jobs.map((job, index) => {\n if (typeof job === \"string\") {\n return {\n jobType: index,\n wgsl: job,\n label: `job_${index}`,\n sourceName: `job-${index}`,\n };\n }\n if (!job || typeof job.wgsl !== \"string\") {\n throw new Error(\"Job entries must provide WGSL source strings.\");\n }\n const jobType = job.jobType ?? index;\n const label = job.label ?? `job_${jobType}`;\n return {\n jobType,\n wgsl: job.wgsl,\n label,\n sourceName: job.sourceName ?? job.label ?? `job-${jobType}`,\n };\n });\n const seen = new Set();\n for (const job of normalized) {\n if (seen.has(job.jobType)) {\n throw new Error(`Duplicate job_type detected: ${job.jobType}`);\n }\n seen.add(job.jobType);\n }\n return normalized;\n}\n\nfunction buildProcessJobDispatch(jobs) {\n const lines = [\n \"fn process_job(job_index: u32, job_type: u32, payload_words: u32) {\",\n ];\n if (jobs.length === 0) {\n lines.push(\" return;\");\n lines.push(\"}\");\n return lines.join(\"\\n\");\n }\n jobs.forEach((job, idx) => {\n const clause = idx === 0 ? \"if\" : \"else if\";\n lines.push(` ${clause} (job_type == ${job.jobType}u) {`);\n lines.push(\n ` ${job.entryName}(job_index, job_type, payload_words);`\n );\n lines.push(\" }\");\n });\n lines.push(\"}\");\n return lines.join(\"\\n\");\n}\n\nexport async function loadWorkerWgsl(options = {}) {\n const { url = workerWgslUrl, fetcher } = options ?? {};\n const source = await loadWgslSource({\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load worker WGSL source.\");\n }\n return source;\n}\n\nexport async function loadQueueWgsl(options = {}) {\n const { queueCompat = true, ...rest } = options ?? {};\n const source = await loadQueueWgslRaw(rest);\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load queue WGSL source.\");\n }\n assertNotHtmlWgsl(source, rest?.url ? String(rest.url) : \"queue WGSL\");\n if (!queueCompat) {\n return source;\n }\n const compatMap = getQueueCompatMap(source);\n return applyCompatMap(source, compatMap);\n}\n\nexport async function loadJobWgsl(options = {}) {\n const { wgsl, url, fetcher, label } = options ?? {};\n const source = await loadWgslSource({\n wgsl,\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"loadJobWgsl requires a WGSL string or URL.\");\n }\n const jobType = nextJobType;\n nextJobType += 1;\n jobRegistry.push({\n jobType,\n wgsl: source,\n label: label ?? `job_${jobType}`,\n sourceName: label ?? `job-${jobType}`,\n });\n return jobType;\n}\n\nexport async function assembleWorkerWgsl(workerWgsl, options = {}) {\n const {\n queueWgsl,\n queueUrl,\n preludeWgsl,\n preludeUrl,\n fetcher,\n jobs,\n debug,\n queueCompat = true,\n } = options ?? {};\n const rawQueueSource =\n queueWgsl ?? (await loadQueueWgslRaw({ url: queueUrl, fetcher }));\n const bodyRaw = workerWgsl ?? (await loadWorkerWgsl({ fetcher }));\n const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;\n const queueSource = applyCompatMap(rawQueueSource, compatMap);\n const preludeRaw =\n preludeWgsl ??\n (preludeUrl\n ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl })\n : \"\");\n if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== \"string\") {\n throw new Error(\"Failed to load prelude WGSL source.\");\n }\n const preludeSource =\n typeof preludeRaw === \"string\" && preludeRaw.length > 0\n ? applyCompatMap(preludeRaw, compatMap)\n : \"\";\n const body = applyCompatMap(bodyRaw, compatMap);\n const jobList = normalizeJobs(\n typeof jobs === \"undefined\" ? jobRegistry : jobs\n );\n if (!jobList || jobList.length === 0) {\n return `${queueSource}\\n\\n${body}`;\n }\n const rewrittenJobs = jobList.map((job) => {\n const source = applyCompatMap(job.wgsl, compatMap);\n const hasProcessJob = /\\bfn\\s+process_job\\b/.test(source);\n if (!hasProcessJob) {\n throw new Error(\n `Job ${job.sourceName} is missing a process_job() entry function.`\n );\n }\n const entryName = `process_job__${job.jobType}`;\n const renamed = renameProcessJob(source, entryName);\n return { ...job, entryName, wgsl: renamed };\n });\n const dispatch = buildProcessJobDispatch(rewrittenJobs);\n const modulesForDebug = debug\n ? [\n { name: \"queue.wgsl\", source: queueSource },\n ...(preludeSource\n ? [{ name: \"jobs.prelude.wgsl\", source: preludeSource }]\n : []),\n ...rewrittenJobs.map((job) => ({\n name: job.sourceName,\n source: job.wgsl,\n })),\n { name: \"jobs.dispatch.wgsl\", source: dispatch },\n { name: \"worker.wgsl\", source: body },\n ]\n : null;\n if (modulesForDebug) {\n assertNoNameClashes(modulesForDebug);\n }\n const jobBlocks = rewrittenJobs\n .map((job) => `// Job ${job.jobType}: ${job.label}\\n${job.wgsl}`)\n .join(\"\\n\\n\");\n const preludeBlock = preludeSource ? `${preludeSource}\\n\\n` : \"\";\n return `${queueSource}\\n\\n${preludeBlock}${jobBlocks}\\n\\n${dispatch}\\n\\n${body}`;\n}\n\nfunction normalizeWorkgroups(value, label) {\n if (typeof value === \"number\") {\n return [value, 1, 1];\n }\n if (Array.isArray(value)) {\n const [x = 0, y = 1, z = 1] = value;\n return [x, y, z];\n }\n throw new Error(`Invalid workgroup count for ${label}.`);\n}\n\nfunction resolveWorkgroups(value, label) {\n if (typeof value === \"function\") {\n return normalizeWorkgroups(value(), label);\n }\n if (value == null) {\n return null;\n }\n return normalizeWorkgroups(value, label);\n}\n\nfunction setBindGroups(pass, bindGroups) {\n if (!bindGroups) {\n return;\n }\n bindGroups.forEach((group, index) => {\n if (group) {\n pass.setBindGroup(index, group);\n }\n });\n}\n\nfunction computeWorkerWorkgroups(maxJobs, workgroupSize) {\n const jobs =\n typeof maxJobs === \"function\" ? Number(maxJobs()) : Number(maxJobs);\n if (!Number.isFinite(jobs) || jobs <= 0) {\n throw new Error(\"maxJobsPerDispatch must be a positive number.\");\n }\n const size = Number(workgroupSize);\n if (!Number.isFinite(size) || size <= 0) {\n throw new Error(\"workgroupSize must be a positive number.\");\n }\n return Math.max(1, Math.ceil(jobs / size));\n}\n\nexport function createWorkerLoop(options = {}) {\n const {\n device,\n worker,\n jobs = [],\n workgroupSize = 64,\n maxJobsPerDispatch,\n rateHz,\n label,\n onTick,\n onError,\n } = options ?? {};\n\n if (!device) {\n throw new Error(\"createWorkerLoop requires a GPUDevice.\");\n }\n if (!worker || !worker.pipeline) {\n throw new Error(\"createWorkerLoop requires a worker pipeline.\");\n }\n\n let running = false;\n let handle = null;\n let usingRaf = false;\n const intervalMs =\n Number.isFinite(rateHz) && rateHz > 0 ? 1000 / rateHz : null;\n\n const tick = () => {\n try {\n const encoder = device.createCommandEncoder();\n const pass = encoder.beginComputePass(\n label ? { label } : undefined\n );\n\n pass.setPipeline(worker.pipeline);\n setBindGroups(pass, worker.bindGroups);\n\n const explicitWorkerGroups =\n resolveWorkgroups(worker.workgroups, \"worker\") ??\n resolveWorkgroups(worker.workgroupCount, \"worker\") ??\n resolveWorkgroups(worker.dispatch, \"worker\");\n\n const workerGroups = explicitWorkerGroups\n ? explicitWorkerGroups\n : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];\n\n if (workerGroups[0] > 0) {\n pass.dispatchWorkgroups(...workerGroups);\n }\n\n jobs.forEach((job, index) => {\n if (!job || !job.pipeline) {\n throw new Error(`Job pipeline missing at index ${index}.`);\n }\n pass.setPipeline(job.pipeline);\n setBindGroups(pass, job.bindGroups);\n const groups = resolveWorkgroups(\n job.workgroups ?? job.workgroupCount ?? job.dispatch,\n `job ${index}`\n );\n if (!groups) {\n throw new Error(`Job ${index} requires a workgroup count.`);\n }\n if (groups[0] > 0) {\n pass.dispatchWorkgroups(...groups);\n }\n });\n\n pass.end();\n device.queue.submit([encoder.finish()]);\n\n if (onTick) {\n onTick();\n }\n } catch (err) {\n if (onError) {\n onError(err);\n return;\n }\n throw err;\n }\n };\n\n const scheduleNext = () => {\n if (!running) {\n return;\n }\n if (intervalMs != null) {\n tick();\n usingRaf = false;\n handle = setTimeout(scheduleNext, intervalMs);\n return;\n }\n tick();\n if (typeof requestAnimationFrame === \"function\") {\n usingRaf = true;\n handle = requestAnimationFrame(scheduleNext);\n } else {\n usingRaf = false;\n handle = setTimeout(scheduleNext, 0);\n }\n };\n\n const start = () => {\n if (running) {\n return;\n }\n running = true;\n scheduleNext();\n };\n\n const stop = () => {\n running = false;\n if (handle == null) {\n return;\n }\n if (usingRaf && typeof cancelAnimationFrame === \"function\") {\n cancelAnimationFrame(handle);\n } else {\n clearTimeout(handle);\n }\n handle = null;\n };\n\n return {\n start,\n stop,\n tick,\n get running() {\n return running;\n },\n };\n}\n"],"mappings":"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","names":["source"]}
1
+ {"version":3,"sources":["../src/index.js"],"sourcesContent":["import {\n loadSchedulerWgsl as loadSchedulerWgslRaw,\n schedulerModes as workerSchedulerModes,\n} from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nconst jobRegistry = [];\nlet nextJobType = 0;\n\nasync function loadWgslSource(options = {}) {\n const { wgsl, url, fetcher = globalThis.fetch, baseUrl } = options ?? {};\n if (typeof wgsl === \"string\") {\n assertNotHtmlWgsl(wgsl, \"inline WGSL\");\n return wgsl;\n }\n if (!url) {\n return null;\n }\n const resolved = url instanceof URL ? url : new URL(url, baseUrl);\n if (!fetcher) {\n if (resolved.protocol !== \"file:\") {\n throw new Error(\"No fetcher available for non-file WGSL URL.\");\n }\n const { readFile } = await import(\"fs/promises\");\n const { fileURLToPath } = await import(\"url\");\n const source = await readFile(fileURLToPath(resolved), \"utf8\");\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n }\n const response = await fetcher(resolved);\n if (!response.ok) {\n const status = \"status\" in response ? response.status : \"unknown\";\n const statusText = \"statusText\" in response ? response.statusText : \"\";\n const detail = statusText ? `${status} ${statusText}` : `${status}`;\n throw new Error(`Failed to load WGSL (${detail})`);\n }\n const source = await response.text();\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n}\n\nfunction stripComments(source) {\n return source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\")\n .replace(/\\/\\/.*$/gm, \"\");\n}\n\nfunction tokenize(source) {\n return source.match(/[A-Za-z_][A-Za-z0-9_]*|[{}();<>,:=]/g) ?? [];\n}\n\nfunction isIdentifier(token) {\n return /^[A-Za-z_][A-Za-z0-9_]*$/.test(token);\n}\n\nfunction readNameAfterType(tokens, startIndex) {\n let i = startIndex;\n if (tokens[i] === \"<\") {\n let depth = 1;\n i += 1;\n while (i < tokens.length && depth > 0) {\n if (tokens[i] === \"<\") {\n depth += 1;\n } else if (tokens[i] === \">\") {\n depth -= 1;\n }\n i += 1;\n }\n }\n return tokens[i];\n}\n\nfunction scanModuleNames(source) {\n const cleaned = stripComments(source);\n const tokens = tokenize(cleaned);\n const names = [];\n let depth = 0;\n for (let i = 0; i < tokens.length; i += 1) {\n const token = tokens[i];\n if (token === \"{\") {\n depth += 1;\n continue;\n }\n if (token === \"}\") {\n depth = Math.max(0, depth - 1);\n continue;\n }\n if (depth !== 0) {\n continue;\n }\n if (token === \"fn\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"fn\", name });\n }\n continue;\n }\n if (token === \"struct\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"struct\", name });\n }\n continue;\n }\n if (token === \"alias\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"alias\", name });\n }\n continue;\n }\n if (token === \"var\" || token === \"let\" || token === \"const\" || token === \"override\") {\n const name = readNameAfterType(tokens, i + 1);\n if (isIdentifier(name)) {\n names.push({ kind: token, name });\n }\n }\n }\n return names;\n}\n\nfunction buildNameIndex(modules) {\n const index = new Map();\n for (const module of modules) {\n for (const item of scanModuleNames(module.source)) {\n const bucket = index.get(item.name) ?? [];\n bucket.push({ kind: item.kind, module: module.name });\n index.set(item.name, bucket);\n }\n }\n return index;\n}\n\nfunction assertNoNameClashes(modules) {\n const index = buildNameIndex(modules);\n const clashes = [];\n for (const [name, entries] of index.entries()) {\n if (entries.length > 1) {\n clashes.push({ name, entries });\n }\n }\n if (clashes.length === 0) {\n return;\n }\n const lines = [\"WGSL debug: identifier clashes detected:\"];\n for (const clash of clashes) {\n const locations = clash.entries\n .map((entry) => `${entry.module} (${entry.kind})`)\n .join(\", \");\n lines.push(`- ${clash.name}: ${locations}`);\n }\n throw new Error(lines.join(\"\\n\"));\n}\n\nfunction assertNotHtmlWgsl(source, context) {\n const sample = source.slice(0, 200).toLowerCase();\n if (\n sample.includes(\"<!doctype\") ||\n sample.includes(\"<html\") ||\n sample.includes(\"<meta\")\n ) {\n const label = context ? ` for ${context}` : \"\";\n throw new Error(\n `Expected WGSL${label} but received HTML. Check the URL or server root.`\n );\n }\n}\n\nfunction renameProcessJob(source, name) {\n return source.replace(/\\bprocess_job\\b/g, name);\n}\n\nfunction getQueueCompatMap(source) {\n if (!/\\bJobMeta\\b/.test(source)) {\n return null;\n }\n return [{ from: /\\bJobMeta\\b/g, to: \"JobDesc\" }];\n}\n\nfunction normalizeQueueMode(mode) {\n const resolved = mode ?? \"flat\";\n if (!workerSchedulerModes.includes(resolved)) {\n throw new Error(\n `queueMode must be one of: ${workerSchedulerModes.join(\", \")}.`\n );\n }\n return resolved;\n}\n\nfunction applyCompatMap(source, map) {\n if (!map || map.length === 0) {\n return source;\n }\n let next = source;\n for (const entry of map) {\n next = next.replace(entry.from, entry.to);\n }\n return next;\n}\n\nfunction normalizeJobs(jobs) {\n const normalized = jobs.map((job, index) => {\n if (typeof job === \"string\") {\n return {\n jobType: index,\n wgsl: job,\n label: `job_${index}`,\n sourceName: `job-${index}`,\n };\n }\n if (!job || typeof job.wgsl !== \"string\") {\n throw new Error(\"Job entries must provide WGSL source strings.\");\n }\n const jobType = job.jobType ?? index;\n const label = job.label ?? `job_${jobType}`;\n return {\n jobType,\n wgsl: job.wgsl,\n label,\n sourceName: job.sourceName ?? job.label ?? `job-${jobType}`,\n };\n });\n const seen = new Set();\n for (const job of normalized) {\n if (seen.has(job.jobType)) {\n throw new Error(`Duplicate job_type detected: ${job.jobType}`);\n }\n seen.add(job.jobType);\n }\n return normalized;\n}\n\nfunction buildProcessJobDispatch(jobs) {\n const lines = [\n \"fn process_job(job_index: u32, job_type: u32, payload_words: u32) {\",\n ];\n if (jobs.length === 0) {\n lines.push(\" return;\");\n lines.push(\"}\");\n return lines.join(\"\\n\");\n }\n jobs.forEach((job, idx) => {\n const clause = idx === 0 ? \"if\" : \"else if\";\n lines.push(` ${clause} (job_type == ${job.jobType}u) {`);\n lines.push(\n ` ${job.entryName}(job_index, job_type, payload_words);`\n );\n lines.push(\" }\");\n });\n lines.push(\"}\");\n return lines.join(\"\\n\");\n}\n\nexport async function loadWorkerWgsl(options = {}) {\n const { url = workerWgslUrl, fetcher } = options ?? {};\n const source = await loadWgslSource({\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load worker WGSL source.\");\n }\n return source;\n}\n\nexport async function loadQueueWgsl(options = {}) {\n const { queueCompat = true, queueMode = \"flat\", ...rest } = options ?? {};\n const source = await loadSchedulerWgslRaw({\n mode: normalizeQueueMode(queueMode),\n ...rest,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load queue WGSL source.\");\n }\n assertNotHtmlWgsl(source, rest?.url ? String(rest.url) : \"queue WGSL\");\n if (!queueCompat) {\n return source;\n }\n const compatMap = getQueueCompatMap(source);\n return applyCompatMap(source, compatMap);\n}\n\nfunction ensureQueueLifecycleHooks(source) {\n if (/\\bfn\\s+complete_job\\b/.test(source)) {\n return source;\n }\n return `${source}\\n\\nfn complete_job(job_index: u32) {\\n _ = job_index;\\n}`;\n}\n\nexport async function loadJobWgsl(options = {}) {\n const { wgsl, url, fetcher, label } = options ?? {};\n const source = await loadWgslSource({\n wgsl,\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"loadJobWgsl requires a WGSL string or URL.\");\n }\n const jobType = nextJobType;\n nextJobType += 1;\n jobRegistry.push({\n jobType,\n wgsl: source,\n label: label ?? `job_${jobType}`,\n sourceName: label ?? `job-${jobType}`,\n });\n return jobType;\n}\n\nexport async function assembleWorkerWgsl(workerWgsl, options = {}) {\n const {\n queueWgsl,\n queueUrl,\n preludeWgsl,\n preludeUrl,\n fetcher,\n jobs,\n debug,\n queueCompat = true,\n queueMode = \"flat\",\n } = options ?? {};\n const resolvedQueueMode = normalizeQueueMode(queueMode);\n const rawQueueSource =\n queueWgsl ??\n (await loadSchedulerWgslRaw({\n mode: resolvedQueueMode,\n url: queueUrl,\n fetcher,\n }));\n const bodyRaw = workerWgsl ?? (await loadWorkerWgsl({ fetcher }));\n const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;\n const queueSource = ensureQueueLifecycleHooks(\n applyCompatMap(rawQueueSource, compatMap)\n );\n const preludeRaw =\n preludeWgsl ??\n (preludeUrl\n ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl })\n : \"\");\n if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== \"string\") {\n throw new Error(\"Failed to load prelude WGSL source.\");\n }\n const preludeSource =\n typeof preludeRaw === \"string\" && preludeRaw.length > 0\n ? applyCompatMap(preludeRaw, compatMap)\n : \"\";\n const body = applyCompatMap(bodyRaw, compatMap);\n const jobList = normalizeJobs(\n typeof jobs === \"undefined\" ? jobRegistry : jobs\n );\n if (!jobList || jobList.length === 0) {\n return `${queueSource}\\n\\n${body}`;\n }\n const rewrittenJobs = jobList.map((job) => {\n const source = applyCompatMap(job.wgsl, compatMap);\n const hasProcessJob = /\\bfn\\s+process_job\\b/.test(source);\n if (!hasProcessJob) {\n throw new Error(\n `Job ${job.sourceName} is missing a process_job() entry function.`\n );\n }\n const entryName = `process_job__${job.jobType}`;\n const renamed = renameProcessJob(source, entryName);\n return { ...job, entryName, wgsl: renamed };\n });\n const dispatch = buildProcessJobDispatch(rewrittenJobs);\n const modulesForDebug = debug\n ? [\n { name: \"queue.wgsl\", source: queueSource },\n ...(preludeSource\n ? [{ name: \"jobs.prelude.wgsl\", source: preludeSource }]\n : []),\n ...rewrittenJobs.map((job) => ({\n name: job.sourceName,\n source: job.wgsl,\n })),\n { name: \"jobs.dispatch.wgsl\", source: dispatch },\n { name: \"worker.wgsl\", source: body },\n ]\n : null;\n if (modulesForDebug) {\n assertNoNameClashes(modulesForDebug);\n }\n const jobBlocks = rewrittenJobs\n .map((job) => `// Job ${job.jobType}: ${job.label}\\n${job.wgsl}`)\n .join(\"\\n\\n\");\n const preludeBlock = preludeSource ? `${preludeSource}\\n\\n` : \"\";\n return `${queueSource}\\n\\n${preludeBlock}${jobBlocks}\\n\\n${dispatch}\\n\\n${body}`;\n}\n\nfunction normalizeWorkgroups(value, label) {\n if (typeof value === \"number\") {\n return [value, 1, 1];\n }\n if (Array.isArray(value)) {\n const [x = 0, y = 1, z = 1] = value;\n return [x, y, z];\n }\n throw new Error(`Invalid workgroup count for ${label}.`);\n}\n\nfunction resolveWorkgroups(value, label) {\n if (typeof value === \"function\") {\n return normalizeWorkgroups(value(), label);\n }\n if (value == null) {\n return null;\n }\n return normalizeWorkgroups(value, label);\n}\n\nfunction normalizeTelemetryText(value, fallback) {\n if (typeof value !== \"string\") {\n return fallback;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed.slice(0, 120) : fallback;\n}\n\nfunction resolveFrameId(value) {\n if (typeof value === \"function\") {\n return resolveFrameId(value());\n }\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed.slice(0, 120) : undefined;\n}\n\nfunction toVector(workgroups) {\n return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };\n}\n\nfunction resolveTelemetryWorkgroupSize(descriptor, fallback, label) {\n const size =\n descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;\n if (size == null) {\n return undefined;\n }\n const normalized =\n typeof size === \"number\" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);\n if (!normalized) {\n return undefined;\n }\n return toVector(normalized);\n}\n\nfunction getNow() {\n if (globalThis.performance && typeof globalThis.performance.now === \"function\") {\n return globalThis.performance.now();\n }\n return Date.now();\n}\n\nfunction reportOptionalError(error, onError) {\n if (!onError) {\n return;\n }\n if (error instanceof Error) {\n onError(error);\n return;\n }\n onError(new Error(String(error)));\n}\n\nfunction emitOptionalHook(callback, payload, onError) {\n if (typeof callback !== \"function\") {\n return;\n }\n try {\n callback(payload);\n } catch (error) {\n reportOptionalError(error, onError);\n }\n}\n\nfunction buildDispatchTelemetrySample({\n kind,\n descriptor,\n index,\n frameId,\n workgroups,\n workgroupSize,\n}) {\n const labelFallback = kind === \"worker\" ? \"worker\" : `job_${index}`;\n const label = normalizeTelemetryText(descriptor?.label, labelFallback);\n return {\n kind,\n index,\n label,\n owner: normalizeTelemetryText(descriptor?.owner, label),\n queueClass: normalizeTelemetryText(descriptor?.queueClass, \"custom\"),\n jobType: normalizeTelemetryText(\n descriptor?.jobType,\n kind === \"worker\" ? \"worker.dispatch\" : label\n ),\n frameId,\n workgroups: toVector(workgroups),\n workgroupSize,\n };\n}\n\nfunction setBindGroups(pass, bindGroups) {\n if (!bindGroups) {\n return;\n }\n bindGroups.forEach((group, index) => {\n if (group) {\n pass.setBindGroup(index, group);\n }\n });\n}\n\nfunction computeWorkerWorkgroups(maxJobs, workgroupSize) {\n const jobs =\n typeof maxJobs === \"function\" ? Number(maxJobs()) : Number(maxJobs);\n if (!Number.isFinite(jobs) || jobs <= 0) {\n throw new Error(\"maxJobsPerDispatch must be a positive number.\");\n }\n const size = Number(workgroupSize);\n if (!Number.isFinite(size) || size <= 0) {\n throw new Error(\"workgroupSize must be a positive number.\");\n }\n return Math.max(1, Math.ceil(jobs / size));\n}\n\nexport function createWorkerLoop(options = {}) {\n const {\n device,\n worker,\n jobs = [],\n workgroupSize = 64,\n maxJobsPerDispatch,\n rateHz,\n label,\n onTick,\n onError,\n frameId,\n telemetry,\n } = options ?? {};\n\n if (!device) {\n throw new Error(\"createWorkerLoop requires a GPUDevice.\");\n }\n if (!worker || !worker.pipeline) {\n throw new Error(\"createWorkerLoop requires a worker pipeline.\");\n }\n\n let running = false;\n let handle = null;\n let usingRaf = false;\n const intervalMs =\n Number.isFinite(rateHz) && rateHz > 0 ? 1000 / rateHz : null;\n\n const tick = () => {\n try {\n const tickStartMs = getNow();\n const currentFrameId = resolveFrameId(frameId);\n const telemetryDispatches = [];\n const encoder = device.createCommandEncoder();\n const pass = encoder.beginComputePass(\n label ? { label } : undefined\n );\n\n pass.setPipeline(worker.pipeline);\n setBindGroups(pass, worker.bindGroups);\n\n const explicitWorkerGroups =\n resolveWorkgroups(worker.workgroups, \"worker\") ??\n resolveWorkgroups(worker.workgroupCount, \"worker\") ??\n resolveWorkgroups(worker.dispatch, \"worker\");\n\n const workerGroups = explicitWorkerGroups\n ? explicitWorkerGroups\n : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];\n\n if (workerGroups[0] > 0) {\n pass.dispatchWorkgroups(...workerGroups);\n telemetryDispatches.push(\n buildDispatchTelemetrySample({\n kind: \"worker\",\n descriptor: worker,\n index: 0,\n frameId: currentFrameId,\n workgroups: workerGroups,\n workgroupSize: resolveTelemetryWorkgroupSize(\n worker,\n workgroupSize,\n \"worker workgroupSize\"\n ),\n })\n );\n }\n\n jobs.forEach((job, index) => {\n if (!job || !job.pipeline) {\n throw new Error(`Job pipeline missing at index ${index}.`);\n }\n pass.setPipeline(job.pipeline);\n setBindGroups(pass, job.bindGroups);\n const groups = resolveWorkgroups(\n job.workgroups ?? job.workgroupCount ?? job.dispatch,\n `job ${index}`\n );\n if (!groups) {\n throw new Error(`Job ${index} requires a workgroup count.`);\n }\n if (groups[0] > 0) {\n pass.dispatchWorkgroups(...groups);\n telemetryDispatches.push(\n buildDispatchTelemetrySample({\n kind: \"job\",\n descriptor: job,\n index,\n frameId: currentFrameId,\n workgroups: groups,\n workgroupSize: resolveTelemetryWorkgroupSize(\n job,\n undefined,\n `job ${index} workgroupSize`\n ),\n })\n );\n }\n });\n\n pass.end();\n device.queue.submit([encoder.finish()]);\n\n telemetryDispatches.forEach((sample) => {\n emitOptionalHook(telemetry?.onDispatch, sample, onError);\n });\n emitOptionalHook(\n telemetry?.onTick,\n {\n frameId: currentFrameId,\n tickDurationMs: getNow() - tickStartMs,\n dispatchCount: telemetryDispatches.length,\n workerDispatchCount: telemetryDispatches.filter(\n (sample) => sample.kind === \"worker\"\n ).length,\n jobDispatchCount: telemetryDispatches.filter(\n (sample) => sample.kind === \"job\"\n ).length,\n dispatches: telemetryDispatches,\n },\n onError\n );\n\n if (onTick) {\n onTick();\n }\n } catch (err) {\n if (onError) {\n onError(err);\n return;\n }\n throw err;\n }\n };\n\n const scheduleNext = () => {\n if (!running) {\n return;\n }\n if (intervalMs != null) {\n tick();\n usingRaf = false;\n handle = setTimeout(scheduleNext, intervalMs);\n return;\n }\n tick();\n if (typeof requestAnimationFrame === \"function\") {\n usingRaf = true;\n handle = requestAnimationFrame(scheduleNext);\n } else {\n usingRaf = false;\n handle = setTimeout(scheduleNext, 0);\n }\n };\n\n const start = () => {\n if (running) {\n return;\n }\n running = true;\n scheduleNext();\n };\n\n const stop = () => {\n running = false;\n if (handle == null) {\n return;\n }\n if (usingRaf && typeof cancelAnimationFrame === \"function\") {\n cancelAnimationFrame(handle);\n } else {\n clearTimeout(handle);\n }\n handle = null;\n };\n\n return {\n start,\n stop,\n tick,\n get running() {\n return running;\n },\n };\n}\n"],"mappings":"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","names":["source"]}
package/dist/worker.wgsl CHANGED
@@ -34,4 +34,5 @@ fn worker_main(@builtin(global_invocation_id) gid: vec3<u32>) {
34
34
 
35
35
  let job_info = output_jobs[idx];
36
36
  process_job(idx, job_info.job_type, job_info.payload_words);
37
+ complete_job(idx);
37
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/gpu-worker",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "WebGPU worker runtime with a lock-free job queue for WGSL workloads.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -55,7 +55,7 @@
55
55
  "author": "Plasius LTD <development@plasius.co.uk>",
56
56
  "license": "Apache-2.0",
57
57
  "dependencies": {
58
- "@plasius/gpu-lock-free-queue": "^0.2.12"
58
+ "@plasius/gpu-lock-free-queue": "^0.2.13"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@eslint/js": "^9.39.1",
@@ -89,5 +89,8 @@
89
89
  ],
90
90
  "overrides": {
91
91
  "minimatch": "^10.2.1"
92
+ },
93
+ "engines": {
94
+ "node": ">=24"
92
95
  }
93
96
  }
package/src/index.js CHANGED
@@ -1,4 +1,7 @@
1
- import { loadQueueWgsl as loadQueueWgslRaw } from "@plasius/gpu-lock-free-queue";
1
+ import {
2
+ loadSchedulerWgsl as loadSchedulerWgslRaw,
3
+ schedulerModes as workerSchedulerModes,
4
+ } from "@plasius/gpu-lock-free-queue";
2
5
 
3
6
  export const workerWgslUrl = (() => {
4
7
  if (typeof __IMPORT_META_URL__ !== "undefined") {
@@ -188,6 +191,16 @@ function getQueueCompatMap(source) {
188
191
  return [{ from: /\bJobMeta\b/g, to: "JobDesc" }];
189
192
  }
190
193
 
194
+ function normalizeQueueMode(mode) {
195
+ const resolved = mode ?? "flat";
196
+ if (!workerSchedulerModes.includes(resolved)) {
197
+ throw new Error(
198
+ `queueMode must be one of: ${workerSchedulerModes.join(", ")}.`
199
+ );
200
+ }
201
+ return resolved;
202
+ }
203
+
191
204
  function applyCompatMap(source, map) {
192
205
  if (!map || map.length === 0) {
193
206
  return source;
@@ -266,8 +279,11 @@ export async function loadWorkerWgsl(options = {}) {
266
279
  }
267
280
 
268
281
  export async function loadQueueWgsl(options = {}) {
269
- const { queueCompat = true, ...rest } = options ?? {};
270
- const source = await loadQueueWgslRaw(rest);
282
+ const { queueCompat = true, queueMode = "flat", ...rest } = options ?? {};
283
+ const source = await loadSchedulerWgslRaw({
284
+ mode: normalizeQueueMode(queueMode),
285
+ ...rest,
286
+ });
271
287
  if (typeof source !== "string") {
272
288
  throw new Error("Failed to load queue WGSL source.");
273
289
  }
@@ -279,6 +295,13 @@ export async function loadQueueWgsl(options = {}) {
279
295
  return applyCompatMap(source, compatMap);
280
296
  }
281
297
 
298
+ function ensureQueueLifecycleHooks(source) {
299
+ if (/\bfn\s+complete_job\b/.test(source)) {
300
+ return source;
301
+ }
302
+ return `${source}\n\nfn complete_job(job_index: u32) {\n _ = job_index;\n}`;
303
+ }
304
+
282
305
  export async function loadJobWgsl(options = {}) {
283
306
  const { wgsl, url, fetcher, label } = options ?? {};
284
307
  const source = await loadWgslSource({
@@ -311,12 +334,21 @@ export async function assembleWorkerWgsl(workerWgsl, options = {}) {
311
334
  jobs,
312
335
  debug,
313
336
  queueCompat = true,
337
+ queueMode = "flat",
314
338
  } = options ?? {};
339
+ const resolvedQueueMode = normalizeQueueMode(queueMode);
315
340
  const rawQueueSource =
316
- queueWgsl ?? (await loadQueueWgslRaw({ url: queueUrl, fetcher }));
341
+ queueWgsl ??
342
+ (await loadSchedulerWgslRaw({
343
+ mode: resolvedQueueMode,
344
+ url: queueUrl,
345
+ fetcher,
346
+ }));
317
347
  const bodyRaw = workerWgsl ?? (await loadWorkerWgsl({ fetcher }));
318
348
  const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;
319
- const queueSource = applyCompatMap(rawQueueSource, compatMap);
349
+ const queueSource = ensureQueueLifecycleHooks(
350
+ applyCompatMap(rawQueueSource, compatMap)
351
+ );
320
352
  const preludeRaw =
321
353
  preludeWgsl ??
322
354
  (preludeUrl
@@ -394,6 +426,98 @@ function resolveWorkgroups(value, label) {
394
426
  return normalizeWorkgroups(value, label);
395
427
  }
396
428
 
429
+ function normalizeTelemetryText(value, fallback) {
430
+ if (typeof value !== "string") {
431
+ return fallback;
432
+ }
433
+ const trimmed = value.trim();
434
+ return trimmed ? trimmed.slice(0, 120) : fallback;
435
+ }
436
+
437
+ function resolveFrameId(value) {
438
+ if (typeof value === "function") {
439
+ return resolveFrameId(value());
440
+ }
441
+ if (typeof value !== "string") {
442
+ return undefined;
443
+ }
444
+ const trimmed = value.trim();
445
+ return trimmed ? trimmed.slice(0, 120) : undefined;
446
+ }
447
+
448
+ function toVector(workgroups) {
449
+ return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };
450
+ }
451
+
452
+ function resolveTelemetryWorkgroupSize(descriptor, fallback, label) {
453
+ const size =
454
+ descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;
455
+ if (size == null) {
456
+ return undefined;
457
+ }
458
+ const normalized =
459
+ typeof size === "number" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);
460
+ if (!normalized) {
461
+ return undefined;
462
+ }
463
+ return toVector(normalized);
464
+ }
465
+
466
+ function getNow() {
467
+ if (globalThis.performance && typeof globalThis.performance.now === "function") {
468
+ return globalThis.performance.now();
469
+ }
470
+ return Date.now();
471
+ }
472
+
473
+ function reportOptionalError(error, onError) {
474
+ if (!onError) {
475
+ return;
476
+ }
477
+ if (error instanceof Error) {
478
+ onError(error);
479
+ return;
480
+ }
481
+ onError(new Error(String(error)));
482
+ }
483
+
484
+ function emitOptionalHook(callback, payload, onError) {
485
+ if (typeof callback !== "function") {
486
+ return;
487
+ }
488
+ try {
489
+ callback(payload);
490
+ } catch (error) {
491
+ reportOptionalError(error, onError);
492
+ }
493
+ }
494
+
495
+ function buildDispatchTelemetrySample({
496
+ kind,
497
+ descriptor,
498
+ index,
499
+ frameId,
500
+ workgroups,
501
+ workgroupSize,
502
+ }) {
503
+ const labelFallback = kind === "worker" ? "worker" : `job_${index}`;
504
+ const label = normalizeTelemetryText(descriptor?.label, labelFallback);
505
+ return {
506
+ kind,
507
+ index,
508
+ label,
509
+ owner: normalizeTelemetryText(descriptor?.owner, label),
510
+ queueClass: normalizeTelemetryText(descriptor?.queueClass, "custom"),
511
+ jobType: normalizeTelemetryText(
512
+ descriptor?.jobType,
513
+ kind === "worker" ? "worker.dispatch" : label
514
+ ),
515
+ frameId,
516
+ workgroups: toVector(workgroups),
517
+ workgroupSize,
518
+ };
519
+ }
520
+
397
521
  function setBindGroups(pass, bindGroups) {
398
522
  if (!bindGroups) {
399
523
  return;
@@ -429,6 +553,8 @@ export function createWorkerLoop(options = {}) {
429
553
  label,
430
554
  onTick,
431
555
  onError,
556
+ frameId,
557
+ telemetry,
432
558
  } = options ?? {};
433
559
 
434
560
  if (!device) {
@@ -446,6 +572,9 @@ export function createWorkerLoop(options = {}) {
446
572
 
447
573
  const tick = () => {
448
574
  try {
575
+ const tickStartMs = getNow();
576
+ const currentFrameId = resolveFrameId(frameId);
577
+ const telemetryDispatches = [];
449
578
  const encoder = device.createCommandEncoder();
450
579
  const pass = encoder.beginComputePass(
451
580
  label ? { label } : undefined
@@ -465,6 +594,20 @@ export function createWorkerLoop(options = {}) {
465
594
 
466
595
  if (workerGroups[0] > 0) {
467
596
  pass.dispatchWorkgroups(...workerGroups);
597
+ telemetryDispatches.push(
598
+ buildDispatchTelemetrySample({
599
+ kind: "worker",
600
+ descriptor: worker,
601
+ index: 0,
602
+ frameId: currentFrameId,
603
+ workgroups: workerGroups,
604
+ workgroupSize: resolveTelemetryWorkgroupSize(
605
+ worker,
606
+ workgroupSize,
607
+ "worker workgroupSize"
608
+ ),
609
+ })
610
+ );
468
611
  }
469
612
 
470
613
  jobs.forEach((job, index) => {
@@ -482,12 +625,46 @@ export function createWorkerLoop(options = {}) {
482
625
  }
483
626
  if (groups[0] > 0) {
484
627
  pass.dispatchWorkgroups(...groups);
628
+ telemetryDispatches.push(
629
+ buildDispatchTelemetrySample({
630
+ kind: "job",
631
+ descriptor: job,
632
+ index,
633
+ frameId: currentFrameId,
634
+ workgroups: groups,
635
+ workgroupSize: resolveTelemetryWorkgroupSize(
636
+ job,
637
+ undefined,
638
+ `job ${index} workgroupSize`
639
+ ),
640
+ })
641
+ );
485
642
  }
486
643
  });
487
644
 
488
645
  pass.end();
489
646
  device.queue.submit([encoder.finish()]);
490
647
 
648
+ telemetryDispatches.forEach((sample) => {
649
+ emitOptionalHook(telemetry?.onDispatch, sample, onError);
650
+ });
651
+ emitOptionalHook(
652
+ telemetry?.onTick,
653
+ {
654
+ frameId: currentFrameId,
655
+ tickDurationMs: getNow() - tickStartMs,
656
+ dispatchCount: telemetryDispatches.length,
657
+ workerDispatchCount: telemetryDispatches.filter(
658
+ (sample) => sample.kind === "worker"
659
+ ).length,
660
+ jobDispatchCount: telemetryDispatches.filter(
661
+ (sample) => sample.kind === "job"
662
+ ).length,
663
+ dispatches: telemetryDispatches,
664
+ },
665
+ onError
666
+ );
667
+
491
668
  if (onTick) {
492
669
  onTick();
493
670
  }
package/src/worker.wgsl CHANGED
@@ -34,4 +34,5 @@ fn worker_main(@builtin(global_invocation_id) gid: vec3<u32>) {
34
34
 
35
35
  let job_info = output_jobs[idx];
36
36
  process_job(idx, job_info.job_type, job_info.payload_words);
37
+ complete_job(idx);
37
38
  }