@gueshbzh-perso/scanner 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @gueshbzh-perso/scanner
2
2
 
3
3
  Fast QR code & barcode scanning for the web. Rust/WebAssembly decoder, WebGL2
4
- frame pipeline, decoding in a Web Worker, camera management and a ready-made
4
+ frame pipeline, parallel decoding in Web Workers, camera management and a ready-made
5
5
  overlay. No license key, no network calls: everything runs on the device.
6
6
 
7
7
  ⚠️ Work in progress: the API may still change before 1.0.
@@ -52,7 +52,8 @@ await reader.destroy(); // removes the preview, frees the engine
52
52
  | `overlay` | all on | `showRegion`, `showDetections`, `showTorchButton`, `showCameraSwitch`, `color`, or `false`. |
53
53
  | `feedback` | beep + vibrate | `{ beep, vibrate }` or `false`. |
54
54
  | `processing` | `{ maxDimension: 1024, backend: 'auto' }` | Decoder image size; `'webgl'`, `'canvas'` or `'auto'`. |
55
- | `worker` | `true` | Decode in a Web Worker; falls back to the main thread if unavailable. |
55
+ | `worker` | `true` | Decode in Web Workers; falls back to the main thread if unavailable. |
56
+ | `workers` | cores − 1 (1–3) | Number of decoding workers: frames are decoded in parallel. |
56
57
  | `wasmUrl`, `workerUrl` | auto | Override asset URLs (CDN, custom bundler setup). |
57
58
 
58
59
  ### Reader API
@@ -63,7 +64,7 @@ await reader.destroy(); // removes the preview, frees the engine
63
64
  - `setRegionOfInterest(region)`
64
65
  - `setTorch(on)`, `torchAvailable`, `setZoom(zoom)`, `switchCamera(deviceId?)`, `BarcodeReader.listCameras()`
65
66
  - `clearCooldown(text)` — allow a code to be reported again immediately
66
- - `stats` — `{ fps, decodeMs, grabMs, frameWidth, frameHeight, backend, worker, simd }`
67
+ - `stats` — `{ fps, decodeMs, grabMs, frameWidth, frameHeight, backend, worker, workers, simd }`
67
68
  - `video` — the underlying `<video>` element
68
69
 
69
70
  Detection points are in video pixels (`video.videoWidth` space).
@@ -102,20 +103,24 @@ to your public folder and pass `workerUrl` / `wasmUrl` explicitly.
102
103
  camera ─▶ <video> ─▶ WebGL2: crop to ROI · RGB→luma · box-filter downscale · pack 4 px/texel
103
104
  │ async readback (pixel-pack buffer + fence) — width×height bytes
104
105
  ▼
105
- Web Worker ─▶ WASM memory (no extra copy) ─▶ Rust engine
106
- │ fast pass every frame
107
- │ + 1 fallback per frame (CPU-time share)
108
- │ (try-harder, inverted, CLAHE, Sauvola)
109
- │ 1D quiet-zone check · multi-frame confirmation · de-dup
110
- ◀────────────── new codes + live detections
106
+ session (main thread) ─ plan ─▶ pool of Web Workers (parallel) ─▶ WASM memory ─▶ Rust decoder
107
+ strategy scheduling │ fast pass every frame
108
+ multi-frame confirmation │ + 1 fallback per frame (CPU-time share)
109
+ de-dup │ (try-harder, inverted, CLAHE, Sauvola, Linear)
110
+ ◀────────── report (codes, timing) ───────┘ 1D quiet-zone check
111
+ new codes + live detections
111
112
  overlay (ROI mask, highlights, torch / camera buttons) · beep · vibration
112
113
  ```
113
114
 
114
- - One frame in flight at a time: when the decoder is busy, frames are skipped
115
- instead of queued, so latency never builds up.
115
+ - The grab of the next frame overlaps the decoding of the previous ones, and
116
+ frames are decoded by several workers at once. When every worker is busy,
117
+ frames are skipped instead of queued, so latency never builds up.
118
+ - The scan state lives in one place (main thread), so multi-frame
119
+ confirmation and de-duplication work across workers.
116
120
  - The SIMD build of the engine is picked automatically when the browser supports it.
117
121
  - If WebGL2 is missing or the context is lost, the pipeline switches to Canvas 2D.
118
122
  - If the engine crashes, the worker is restarted transparently.
123
+ - The engine is downloaded and compiled once, then shared by every worker.
119
124
 
120
125
  ## Browser support
121
126
 
package/dist/core.d.ts CHANGED
@@ -1,9 +1,17 @@
1
- import type { ScanResult, WasmScanConfig, PixelFormat } from './wasm/scanner_wasm.js';
1
+ import type { FramePlan, FrameReport, PixelFormat, ScanResult, WasmScanConfig } from './wasm/scanner_wasm.js';
2
2
  import type { Detection, EngineOptions } from './types.js';
3
+ export type { FramePlan, FrameReport };
3
4
  /** Feature-detects WebAssembly SIMD (same probe as `wasm-feature-detect`). */
4
5
  export declare function simdSupported(): boolean;
5
6
  /** Default location of the engine binary, next to this module. */
6
7
  export declare function defaultWasmUrl(simd?: boolean): URL;
8
+ /**
9
+ * Compile the engine once. The compiled module can be posted to any number
10
+ * of workers, which then only instantiate it (no second download or compile).
11
+ */
12
+ export declare function compileWasm(url: string | URL): Promise<WebAssembly.Module>;
13
+ /** Instantiate the engine in this realm (once). Returns the engine version. */
14
+ export declare function loadWasm(source: string | URL | WebAssembly.Module): Promise<string>;
7
15
  /** Keep only the engine options (drops URLs and other non-cloneable values). */
8
16
  export declare function pickEngineOptions(o: EngineOptions): EngineOptions;
9
17
  export declare function toWasmConfig(o: EngineOptions, extra?: Partial<WasmScanConfig>): WasmScanConfig;
@@ -15,17 +23,29 @@ export interface FrameOutcome {
15
23
  live: Detection[];
16
24
  decodeMs: number;
17
25
  }
18
- export declare class EngineCore {
19
- readonly version: string;
26
+ /** The scan session of a camera stream (state only, cheap calls). */
27
+ export declare class SessionCore {
20
28
  private session;
21
- private scanners;
22
- private constructor();
23
- static create(wasmUrl?: string | URL): Promise<EngineCore>;
24
29
  configure(options: EngineOptions): void;
30
+ /** Strategy plan of the next frame, for a `DecoderCore` configured the same way. */
31
+ plan(): FramePlan;
32
+ /** Record a decoded frame; returns the new codes and the live ones. */
33
+ commit(report: FrameReport): FrameOutcome;
34
+ /** Decode on this thread (inline engine): plan, decode and commit at once. */
25
35
  process(pixels: Uint8Array, width: number, height: number, format: PixelFormat): FrameOutcome;
26
- scan(pixels: Uint8Array, width: number, height: number, format: PixelFormat, options: EngineOptions): Detection[];
27
36
  reset(): void;
28
37
  clearCooldown(text: string): void;
29
38
  destroy(): void;
39
+ private active;
40
+ }
41
+ /** Frame decoding (stateless) and still-image scanning: the expensive part. */
42
+ export declare class DecoderCore {
43
+ private decoder;
44
+ private scanners;
45
+ configure(options: EngineOptions): void;
46
+ decode(pixels: Uint8Array, width: number, height: number, format: PixelFormat, plan: FramePlan): FrameReport;
47
+ /** One-shot scan of a still image, running every strategy. */
48
+ scan(pixels: Uint8Array, width: number, height: number, format: PixelFormat, options: EngineOptions): Detection[];
49
+ destroy(): void;
30
50
  }
31
51
  //# sourceMappingURL=core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3D,8EAA8E;AAC9E,wBAAgB,aAAa,IAAI,OAAO,CAWvC;AAED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,IAAI,UAAkB,GAAG,GAAG,CAI1D;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,aAAa,GAAG,aAAa,CAGjE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,GAAE,OAAO,CAAC,cAAc,CAAM,GAAG,cAAc,CASlG;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,CAIpD;AAED,MAAM,WAAW,YAAY;IAC3B,4BAA4B;IAC5B,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC5B,wEAAwE;IACxE,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,qBAAa,UAAU;IAID,QAAQ,CAAC,OAAO,EAAE,MAAM;IAH5C,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,QAAQ,CAAkC;IAElD,OAAO;WAEM,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC;IAMhE,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAOvC,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,YAAY;IAY7F,IAAI,CACF,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,aAAa,GACrB,SAAS,EAAE;IAiBd,KAAK,IAAI,IAAI;IAIb,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIjC,OAAO,IAAI,IAAI;CAMhB"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC9G,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3D,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAEvC,8EAA8E;AAC9E,wBAAgB,aAAa,IAAI,OAAO,CAWvC;AAED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,IAAI,UAAkB,GAAG,GAAG,CAI1D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAQhF;AAID,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,GAAG,WAAW,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGnF;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,aAAa,GAAG,aAAa,CAGjE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,GAAE,OAAO,CAAC,cAAc,CAAM,GAAG,cAAc,CASlG;AAYD,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,CAIpD;AAED,MAAM,WAAW,YAAY;IAC3B,4BAA4B;IAC5B,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC5B,wEAAwE;IACxE,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qEAAqE;AACrE,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAgC;IAE/C,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAKvC,oFAAoF;IACpF,IAAI,IAAI,SAAS;IAIjB,uEAAuE;IACvE,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY;IAOzC,8EAA8E;IAC9E,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,YAAY;IAW7F,KAAK,IAAI,IAAI;IAIb,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIjC,OAAO,IAAI,IAAI;IAKf,OAAO,CAAC,MAAM;CAIf;AAED,+EAA+E;AAC/E,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,QAAQ,CAAkC;IAElD,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAKvC,MAAM,CACJ,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,SAAS,GACd,WAAW;IAOd,8DAA8D;IAC9D,IAAI,CACF,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,aAAa,GACrB,SAAS,EAAE;IAiBd,OAAO,IAAI,IAAI;CAMhB"}
package/dist/core.js CHANGED
@@ -1,6 +1,12 @@
1
- // Engine core: owns the WebAssembly module and the scan session.
2
- // Runs either inside the Web Worker (default) or on the main thread (fallback).
3
- import init, { WasmScanner, WasmScanSession, version } from './wasm/scanner_wasm.js';
1
+ // Engine core: owns the WebAssembly module and its objects.
2
+ //
3
+ // Two roles, so that frames can be decoded on several threads while a single
4
+ // session keeps the state (strategy scheduling, multi-frame confirmation,
5
+ // de-duplication):
6
+ // - `SessionCore`: the session. Cheap calls (`plan`, `commit`), main thread.
7
+ // - `DecoderCore`: the expensive decoding, one per Web Worker.
8
+ // The inline engine (no Worker support) uses both on the main thread.
9
+ import init, { WasmFrameDecoder, WasmScanner, WasmScanSession, version } from './wasm/scanner_wasm.js';
4
10
  /** Feature-detects WebAssembly SIMD (same probe as `wasm-feature-detect`). */
5
11
  export function simdSupported() {
6
12
  try {
@@ -19,6 +25,26 @@ export function defaultWasmUrl(simd = simdSupported()) {
19
25
  ? new URL('./wasm/scanner_wasm_simd_bg.wasm', import.meta.url)
20
26
  : new URL('./wasm/scanner_wasm_bg.wasm', import.meta.url);
21
27
  }
28
+ /**
29
+ * Compile the engine once. The compiled module can be posted to any number
30
+ * of workers, which then only instantiate it (no second download or compile).
31
+ */
32
+ export async function compileWasm(url) {
33
+ const response = fetch(url);
34
+ try {
35
+ return await WebAssembly.compileStreaming(response);
36
+ }
37
+ catch {
38
+ // Servers that do not send `application/wasm` reject streaming compilation.
39
+ return WebAssembly.compile(await (await response).arrayBuffer());
40
+ }
41
+ }
42
+ let initPromise = null;
43
+ /** Instantiate the engine in this realm (once). Returns the engine version. */
44
+ export function loadWasm(source) {
45
+ initPromise ?? (initPromise = init({ module_or_path: source }).then(() => version()));
46
+ return initPromise;
47
+ }
22
48
  /** Keep only the engine options (drops URLs and other non-cloneable values). */
23
49
  export function pickEngineOptions(o) {
24
50
  const { formats, preprocessing, tryInvert, maxResults, duplicateIntervalMs, linearConfirmations } = o;
@@ -39,42 +65,86 @@ export function toWasmConfig(o, extra = {}) {
39
65
  cfg.linearConfirmations = o.linearConfirmations;
40
66
  return cfg;
41
67
  }
68
+ /**
69
+ * Configuration of camera sessions and their decoders: they must be equal,
70
+ * since a plan refers to the decoder's list of fallback strategies.
71
+ */
72
+ function streamConfig(o) {
73
+ // Back-pressure is handled by the SDK (frames are skipped, never queued),
74
+ // so the engine's own frame-skipping throttle would only drop useful frames.
75
+ return toWasmConfig(o, { adaptiveThrottle: false });
76
+ }
42
77
  export function toDetection(r) {
43
78
  const d = { text: r.text, format: r.format, points: r.points, timestamp: r.timestampMs };
44
79
  if (r.rawBytes)
45
80
  d.rawBytes = r.rawBytes;
46
81
  return d;
47
82
  }
48
- let initPromise = null;
49
- export class EngineCore {
50
- constructor(version) {
51
- this.version = version;
83
+ /** The scan session of a camera stream (state only, cheap calls). */
84
+ export class SessionCore {
85
+ constructor() {
52
86
  this.session = null;
53
- this.scanners = new Map();
54
- }
55
- static async create(wasmUrl) {
56
- initPromise ?? (initPromise = init({ module_or_path: wasmUrl ?? defaultWasmUrl() }));
57
- await initPromise;
58
- return new EngineCore(version());
59
87
  }
60
88
  configure(options) {
61
89
  this.session?.free();
62
- // Back-pressure is handled by the SDK (one frame in flight), so the
63
- // engine's own frame-skipping throttle would only drop useful frames.
64
- this.session = new WasmScanSession(toWasmConfig(options, { adaptiveThrottle: false }));
90
+ this.session = new WasmScanSession(streamConfig(options));
65
91
  }
92
+ /** Strategy plan of the next frame, for a `DecoderCore` configured the same way. */
93
+ plan() {
94
+ return this.active().plan();
95
+ }
96
+ /** Record a decoded frame; returns the new codes and the live ones. */
97
+ commit(report) {
98
+ const session = this.active();
99
+ const fresh = session.commit(report);
100
+ const results = fresh ? fresh.map(toDetection) : null;
101
+ return { results, live: results ?? session.lastDetections.map(toDetection), decodeMs: report.elapsedMs };
102
+ }
103
+ /** Decode on this thread (inline engine): plan, decode and commit at once. */
66
104
  process(pixels, width, height, format) {
67
- const session = this.session;
68
- if (!session)
69
- throw new Error('Engine not configured');
105
+ const session = this.active();
70
106
  // Write straight into WASM memory: avoids one more copy of the frame.
71
107
  session.frameBuffer(pixels.length).set(pixels);
72
108
  const t0 = performance.now();
73
- const results = session.processFrameBuffer(width, height, format);
109
+ const fresh = session.processFrameBuffer(width, height, format);
74
110
  const decodeMs = performance.now() - t0;
75
- const fresh = results ? results.map(toDetection) : null;
76
- return { results: fresh, live: fresh ?? session.lastDetections.map(toDetection), decodeMs };
111
+ const results = fresh ? fresh.map(toDetection) : null;
112
+ return { results, live: results ?? session.lastDetections.map(toDetection), decodeMs };
113
+ }
114
+ reset() {
115
+ this.session?.reset();
77
116
  }
117
+ clearCooldown(text) {
118
+ this.session?.clearCooldown(text);
119
+ }
120
+ destroy() {
121
+ this.session?.free();
122
+ this.session = null;
123
+ }
124
+ active() {
125
+ if (!this.session)
126
+ throw new Error('Engine not configured');
127
+ return this.session;
128
+ }
129
+ }
130
+ /** Frame decoding (stateless) and still-image scanning: the expensive part. */
131
+ export class DecoderCore {
132
+ constructor() {
133
+ this.decoder = null;
134
+ this.scanners = new Map();
135
+ }
136
+ configure(options) {
137
+ this.decoder?.free();
138
+ this.decoder = new WasmFrameDecoder(streamConfig(options));
139
+ }
140
+ decode(pixels, width, height, format, plan) {
141
+ const decoder = this.decoder;
142
+ if (!decoder)
143
+ throw new Error('Engine not configured');
144
+ decoder.frameBuffer(pixels.length).set(pixels);
145
+ return decoder.decodeFrameBuffer(width, height, format, plan);
146
+ }
147
+ /** One-shot scan of a still image, running every strategy. */
78
148
  scan(pixels, width, height, format, options) {
79
149
  const config = toWasmConfig(options);
80
150
  const key = JSON.stringify(config);
@@ -91,15 +161,9 @@ export class EngineCore {
91
161
  scanner.frameBuffer(pixels.length).set(pixels);
92
162
  return scanner.scanFrameBuffer(width, height, format).map(toDetection);
93
163
  }
94
- reset() {
95
- this.session?.reset();
96
- }
97
- clearCooldown(text) {
98
- this.session?.clearCooldown(text);
99
- }
100
164
  destroy() {
101
- this.session?.free();
102
- this.session = null;
165
+ this.decoder?.free();
166
+ this.decoder = null;
103
167
  for (const s of this.scanners.values())
104
168
  s.free();
105
169
  this.scanners.clear();
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
package/dist/engine.d.ts CHANGED
@@ -11,6 +11,8 @@ export interface Engine {
11
11
  readonly inWorker: boolean;
12
12
  readonly simd: boolean;
13
13
  readonly version: string;
14
+ /** Frames that can be decoded at the same time (number of workers). */
15
+ readonly concurrency: number;
14
16
  configure(options: EngineOptions): Promise<void>;
15
17
  /** Decode a camera frame. `buffer` is transferred and returned in the reply. */
16
18
  processFrame(buffer: ArrayBuffer, width: number, height: number, format: PixelFormat): Promise<FrameReply>;
@@ -20,8 +22,15 @@ export interface Engine {
20
22
  clearCooldown(text: string): Promise<void>;
21
23
  destroy(): void;
22
24
  }
23
- /** Load the engine, in a Web Worker when possible. */
25
+ /**
26
+ * Default pool size: one worker per core, minus one for the main thread
27
+ * (camera, GPU, UI), capped at 3. Beyond 3 the frame rate is limited by the
28
+ * frame grab (~55 ms on a Galaxy A14, so ~18 fps) and more workers only cost
29
+ * memory (one WebAssembly instance each).
30
+ */
31
+ export declare function defaultWorkerCount(): number;
32
+ /** Load the engine: a pool of Web Workers when possible, else the main thread. */
24
33
  export declare function createEngine(options?: AssetOptions): Promise<Engine>;
25
- /** Engine shared by `scanImage` calls. */
34
+ /** Engine shared by `scanImage` calls (one worker is enough for still images). */
26
35
  export declare function sharedEngine(options?: AssetOptions): Promise<Engine>;
27
36
  //# sourceMappingURL=engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEzE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,gFAAgF;IAChF,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3G,8DAA8D;IAC9D,SAAS,CACP,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,IAAI,IAAI,CAAC;CACjB;AA4KD,sDAAsD;AACtD,wBAAsB,YAAY,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB9E;AAID,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAMpE"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEzE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,gFAAgF;IAChF,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3G,8DAA8D;IAC9D,SAAS,CACP,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAG3C;AA+OD,kFAAkF;AAClF,wBAAsB,YAAY,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2B9E;AAID,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAMpE"}
package/dist/engine.js CHANGED
@@ -1,12 +1,32 @@
1
- // Decoding engine facade: same API whether decoding runs in a Web Worker or inline.
2
- import { EngineCore, defaultWasmUrl, simdSupported } from './core.js';
3
- class WorkerEngine {
4
- constructor(spawn, wasmUrl, simd) {
1
+ // Decoding engine facade: same API whether decoding runs in Web Workers or inline.
2
+ //
3
+ // With workers, frames are decoded **in parallel** by a pool of stateless
4
+ // decoders (one WebAssembly instance per worker), while the scan session
5
+ // (strategy scheduling, multi-frame confirmation, de-duplication) runs on the
6
+ // main thread: its calls take microseconds, and a single instance of the
7
+ // state is what keeps confirmations and de-duplication correct across
8
+ // workers. Decoding is CPU-bound and single-threaded per frame, so on phones
9
+ // with many slow cores (Galaxy A14: decode ~125 ms) the pool multiplies the
10
+ // frame rate.
11
+ import { DecoderCore, SessionCore, compileWasm, defaultWasmUrl, loadWasm, simdSupported } from './core.js';
12
+ /**
13
+ * Default pool size: one worker per core, minus one for the main thread
14
+ * (camera, GPU, UI), capped at 3. Beyond 3 the frame rate is limited by the
15
+ * frame grab (~55 ms on a Galaxy A14, so ~18 fps) and more workers only cost
16
+ * memory (one WebAssembly instance each).
17
+ */
18
+ export function defaultWorkerCount() {
19
+ const cores = typeof navigator !== 'undefined' ? navigator.hardwareConcurrency || 2 : 2;
20
+ return Math.min(3, Math.max(1, cores - 1));
21
+ }
22
+ /** One decoding worker, restarted if the engine crashes. */
23
+ class DecoderWorker {
24
+ constructor(spawn, module) {
5
25
  this.spawn = spawn;
6
- this.wasmUrl = wasmUrl;
7
- this.simd = simd;
8
- this.inWorker = true;
26
+ this.module = module;
9
27
  this.version = '';
28
+ /** Requests sent and not answered yet. */
29
+ this.busy = 0;
10
30
  this.nextId = 1;
11
31
  this.pending = new Map();
12
32
  this.dead = null;
@@ -45,15 +65,21 @@ class WorkerEngine {
45
65
  }
46
66
  /** Send a request; on a fatal engine crash, restart the worker before throwing. */
47
67
  async ok(req, transfer) {
48
- if (this.restarting)
49
- await this.restarting;
50
- const res = await this.call(req, transfer);
51
- if (!res.ok) {
52
- if (res.fatal)
53
- await this.restart();
54
- throw Object.assign(new Error(res.error), { buffer: res.buffer });
68
+ this.busy++;
69
+ try {
70
+ if (this.restarting)
71
+ await this.restarting;
72
+ const res = await this.call(req, transfer);
73
+ if (!res.ok) {
74
+ if (res.fatal)
75
+ await this.restart();
76
+ throw Object.assign(new Error(res.error), { buffer: res.buffer });
77
+ }
78
+ return res;
79
+ }
80
+ finally {
81
+ this.busy--;
55
82
  }
56
- return res;
57
83
  }
58
84
  restart() {
59
85
  this.restarting ?? (this.restarting = (async () => {
@@ -62,12 +88,12 @@ class WorkerEngine {
62
88
  this.attach(this.spawn());
63
89
  await this.init();
64
90
  if (this.options)
65
- await this.ok({ type: 'configure', options: this.options });
91
+ await this.call({ type: 'configure', options: this.options });
66
92
  })().finally(() => (this.restarting = null)));
67
93
  return this.restarting;
68
94
  }
69
95
  async init() {
70
- const res = await this.call({ type: 'init', wasmUrl: this.wasmUrl });
96
+ const res = await this.call({ type: 'init', module: this.module });
71
97
  if (!res.ok)
72
98
  throw new Error(res.error);
73
99
  this.version = res.version ?? '';
@@ -76,82 +102,148 @@ class WorkerEngine {
76
102
  this.options = options;
77
103
  await this.ok({ type: 'configure', options });
78
104
  }
105
+ destroy() {
106
+ this.fail(new Error('Engine destroyed'));
107
+ this.worker.terminate();
108
+ }
109
+ }
110
+ /** A pool of decoding workers serving one scan session on the main thread. */
111
+ class WorkerPoolEngine {
112
+ constructor(workers, simd) {
113
+ this.workers = workers;
114
+ this.simd = simd;
115
+ this.inWorker = true;
116
+ this.session = new SessionCore();
117
+ this.configured = false;
118
+ /** Bumped by `configure`: reports planned with an older configuration are dropped. */
119
+ this.generation = 0;
120
+ this.version = workers[0]?.version ?? '';
121
+ }
122
+ get concurrency() {
123
+ return this.workers.length;
124
+ }
125
+ async configure(options) {
126
+ // Session and decoders must share the configuration: a plan is an index
127
+ // in the decoder's list of fallback strategies.
128
+ this.generation++;
129
+ this.session.configure(options);
130
+ this.configured = true;
131
+ await Promise.all(this.workers.map((w) => w.configure(options)));
132
+ }
79
133
  async processFrame(buffer, width, height, format) {
80
- const res = await this.ok({ type: 'frame', buffer, width, height, format }, [buffer]);
81
- return {
82
- results: (res.ok && res.results) || null,
83
- live: (res.ok && res.live) || [],
84
- decodeMs: (res.ok && res.decodeMs) || 0,
85
- buffer: res.buffer ?? new ArrayBuffer(width * height),
86
- };
134
+ if (!this.configured)
135
+ throw new Error('Engine not configured');
136
+ const generation = this.generation;
137
+ const plan = this.session.plan();
138
+ let res;
139
+ try {
140
+ res = await this.idleWorker().ok({ type: 'decode', buffer, width, height, format, plan }, [buffer]);
141
+ }
142
+ catch (err) {
143
+ // Every plan must be committed, or the scheduler keeps counting it as in flight.
144
+ this.session.commit(emptyReport(plan));
145
+ throw err;
146
+ }
147
+ const returned = res.buffer ?? new ArrayBuffer(width * height);
148
+ if (generation !== this.generation || !res.report) {
149
+ return { results: null, live: [], decodeMs: 0, buffer: returned };
150
+ }
151
+ return { ...this.session.commit(res.report), buffer: returned };
87
152
  }
88
153
  async scanImage(buffer, width, height, format, options) {
89
- const res = await this.ok({ type: 'scan', buffer, width, height, format, options }, [buffer]);
90
- return (res.ok && res.results) || [];
154
+ const res = await this.idleWorker().ok({ type: 'scan', buffer, width, height, format, options }, [
155
+ buffer,
156
+ ]);
157
+ return res.results ?? [];
91
158
  }
92
159
  async reset() {
93
- await this.ok({ type: 'reset' });
160
+ this.session.reset();
94
161
  }
95
162
  async clearCooldown(text) {
96
- await this.ok({ type: 'clearCooldown', text });
163
+ this.session.clearCooldown(text);
97
164
  }
98
165
  destroy() {
99
- this.fail(new Error('Engine destroyed'));
100
- this.worker.terminate();
166
+ for (const w of this.workers)
167
+ w.destroy();
168
+ this.session.destroy();
169
+ }
170
+ /** The worker with the fewest requests in progress. */
171
+ idleWorker() {
172
+ let best = this.workers[0];
173
+ if (!best)
174
+ throw new Error('Engine destroyed');
175
+ for (const w of this.workers)
176
+ if (w.busy < best.busy)
177
+ best = w;
178
+ return best;
101
179
  }
102
180
  }
181
+ function emptyReport(plan) {
182
+ return { plan, results: [], fallbackRan: false, fallbackRead: false, elapsedMs: 0 };
183
+ }
184
+ /** Everything on the main thread (no Worker support). */
103
185
  class InlineEngine {
104
- constructor(core, simd) {
105
- this.core = core;
186
+ constructor(version, simd) {
187
+ this.version = version;
106
188
  this.simd = simd;
107
189
  this.inWorker = false;
108
- this.version = core.version;
190
+ this.concurrency = 1;
191
+ this.session = new SessionCore();
192
+ this.decoder = new DecoderCore();
109
193
  }
110
194
  async configure(options) {
111
- this.core.configure(options);
195
+ this.session.configure(options);
112
196
  }
113
197
  async processFrame(buffer, width, height, format) {
114
- return { ...this.core.process(new Uint8Array(buffer), width, height, format), buffer };
198
+ return { ...this.session.process(new Uint8Array(buffer), width, height, format), buffer };
115
199
  }
116
200
  async scanImage(buffer, width, height, format, options) {
117
- return this.core.scan(new Uint8Array(buffer), width, height, format, options);
201
+ return this.decoder.scan(new Uint8Array(buffer), width, height, format, options);
118
202
  }
119
203
  async reset() {
120
- this.core.reset();
204
+ this.session.reset();
121
205
  }
122
206
  async clearCooldown(text) {
123
- this.core.clearCooldown(text);
207
+ this.session.clearCooldown(text);
124
208
  }
125
209
  destroy() {
126
- this.core.destroy();
210
+ this.session.destroy();
211
+ this.decoder.destroy();
127
212
  }
128
213
  }
129
- /** Load the engine, in a Web Worker when possible. */
214
+ /** Load the engine: a pool of Web Workers when possible, else the main thread. */
130
215
  export async function createEngine(options = {}) {
131
216
  const simd = simdSupported();
132
217
  const wasmUrl = new URL(String(options.wasmUrl ?? defaultWasmUrl(simd)), location.href).href;
218
+ // Compiled once here: the main thread needs the engine for the session,
219
+ // and the workers instantiate the same module without compiling it again.
220
+ const module = await compileWasm(wasmUrl);
221
+ const version = await loadWasm(module);
133
222
  if (options.worker !== false && typeof Worker !== 'undefined') {
134
223
  const workerUrl = options.workerUrl;
135
224
  const spawn = () => workerUrl
136
225
  ? new Worker(workerUrl, { type: 'module' })
137
226
  : new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
138
- let engine = null;
227
+ const count = Math.max(1, Math.floor(options.workers ?? defaultWorkerCount()));
228
+ const workers = [];
139
229
  try {
140
- engine = new WorkerEngine(spawn, wasmUrl, simd);
141
- await engine.init();
142
- return engine;
230
+ for (let i = 0; i < count; i++)
231
+ workers.push(new DecoderWorker(spawn, module));
232
+ await Promise.all(workers.map((w) => w.init()));
233
+ return new WorkerPoolEngine(workers, simd);
143
234
  }
144
235
  catch (err) {
145
- engine?.destroy();
236
+ for (const w of workers)
237
+ w.destroy();
146
238
  console.warn('[scanner] Web Worker unavailable, decoding on the main thread:', err);
147
239
  }
148
240
  }
149
- return new InlineEngine(await EngineCore.create(wasmUrl), simd);
241
+ return new InlineEngine(version, simd);
150
242
  }
151
243
  let shared = null;
152
- /** Engine shared by `scanImage` calls. */
244
+ /** Engine shared by `scanImage` calls (one worker is enough for still images). */
153
245
  export function sharedEngine(options) {
154
- shared ?? (shared = createEngine(options).catch((err) => {
246
+ shared ?? (shared = createEngine({ ...options, workers: 1 }).catch((err) => {
155
247
  shared = null;
156
248
  throw err;
157
249
  }));