@heojeongbo/fluxion-render 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # FluxionRender
2
+
3
+ High-performance rendering engine for real-time data visualization.
4
+
5
+ Designed for:
6
+ - LiDAR visualization
7
+ - Real-time charts
8
+ - High-frequency streaming data (30~120Hz+)
9
+ - Robotics / sensor systems
10
+
11
+ ## Architecture
12
+
13
+ ```
14
+ Data Source (ROS2 / Sensor)
15
+
16
+ Worker (OffscreenCanvas)
17
+ compute / transform / render
18
+
19
+ Float32Array (TypedArray, transferable)
20
+
21
+ Main Thread
22
+
23
+ React (DOM events: resize, mount)
24
+ ```
25
+
26
+ Binary-first, zero-copy, layered, offscreen-rendered.
27
+
28
+ ## Packages
29
+
30
+ - [`packages/fluxion-render`](packages/fluxion-render) — the library
31
+ - [`examples/vite-demo`](examples/vite-demo) — Vite + React demo with Line / Sliding / LiDAR
32
+
33
+ ## Development
34
+
35
+ ```bash
36
+ pnpm install
37
+ pnpm --filter fluxion-render build
38
+ pnpm --filter vite-demo dev
39
+ ```
40
+
41
+ ## Philosophy
42
+
43
+ > "Data is binary. Rendering is layered. UI is optional."
44
+ > "Performance first, UX later."
@@ -6,10 +6,11 @@ var Op = {
6
6
  REMOVE_LAYER: 4,
7
7
  CONFIG: 5,
8
8
  DATA: 6,
9
- DISPOSE: 7
9
+ DISPOSE: 7,
10
+ SET_BG_COLOR: 8
10
11
  };
11
12
 
12
13
  export {
13
14
  Op
14
15
  };
15
- //# sourceMappingURL=chunk-R7FLS7BG.js.map
16
+ //# sourceMappingURL=chunk-EBSPHY2J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/protocol/protocol.ts"],"sourcesContent":["/**\n * Binary message protocol between main-thread `FluxionHost` and worker `Engine`.\n * Uses a plain const object (not const enum) so consumers with\n * `isolatedModules` can import types safely from the published package.\n */\nexport const Op = {\n INIT: 1,\n RESIZE: 2,\n ADD_LAYER: 3,\n REMOVE_LAYER: 4,\n CONFIG: 5,\n DATA: 6,\n DISPOSE: 7,\n SET_BG_COLOR: 8,\n} as const;\nexport type Op = (typeof Op)[keyof typeof Op];\n\nexport type LayerKind = \"line\" | \"line-static\" | \"lidar\" | \"axis-grid\";\n\nexport type DType = \"f32\" | \"u8\" | \"i16\" | \"u16\" | \"i32\";\n\nexport interface InitMsg {\n op: typeof Op.INIT;\n canvas: OffscreenCanvas;\n width: number;\n height: number;\n dpr: number;\n /**\n * Optional canvas background color. Applied every frame before layers draw.\n * Default (when omitted): `\"#0b0d12\"` — matches the engine's dark default.\n */\n bgColor?: string;\n}\n\nexport interface ResizeMsg {\n op: typeof Op.RESIZE;\n width: number;\n height: number;\n dpr: number;\n}\n\nexport interface AddLayerMsg {\n op: typeof Op.ADD_LAYER;\n id: string;\n kind: LayerKind;\n config?: unknown;\n}\n\nexport interface RemoveLayerMsg {\n op: typeof Op.REMOVE_LAYER;\n id: string;\n}\n\nexport interface ConfigMsg {\n op: typeof Op.CONFIG;\n id: string;\n config: unknown;\n}\n\nexport interface DataMsg {\n op: typeof Op.DATA;\n id: string;\n buffer: ArrayBuffer;\n dtype: DType;\n length: number;\n}\n\nexport interface DisposeMsg {\n op: typeof Op.DISPOSE;\n}\n\n/**\n * Canvas-scope (engine-level) background color update. Takes effect on the\n * next rendered frame. Separate from `CONFIG` because `CONFIG` is layer-scope.\n */\nexport interface SetBgColorMsg {\n op: typeof Op.SET_BG_COLOR;\n color: string;\n}\n\nexport type HostMsg =\n | InitMsg\n | ResizeMsg\n | AddLayerMsg\n | RemoveLayerMsg\n | ConfigMsg\n | DataMsg\n | DisposeMsg\n | SetBgColorMsg;\n"],"mappings":";AAKO,IAAM,KAAK;AAAA,EAChB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AAAA,EACT,cAAc;AAChB;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Op
3
- } from "./chunk-R7FLS7BG.js";
3
+ } from "./chunk-EBSPHY2J.js";
4
4
 
5
5
  // src/features/host/model/layer-handles.ts
6
6
  var LineLayerHandle = class {
@@ -126,11 +126,19 @@ var FluxionHost = class {
126
126
  canvas: offscreen,
127
127
  width,
128
128
  height,
129
- dpr
129
+ dpr,
130
+ bgColor: opts.bgColor
130
131
  },
131
132
  [offscreen]
132
133
  );
133
134
  }
135
+ /**
136
+ * Update the canvas background color at runtime. Takes effect on the next
137
+ * rendered frame. Useful for theme toggles without tearing down the host.
138
+ */
139
+ setBgColor(color) {
140
+ this.post({ op: Op.SET_BG_COLOR, color });
141
+ }
134
142
  addLayer(id, kind, config) {
135
143
  this.post({ op: Op.ADD_LAYER, id, kind, config });
136
144
  }
@@ -244,4 +252,4 @@ export {
244
252
  LidarLayerHandle,
245
253
  FluxionHost
246
254
  };
247
- //# sourceMappingURL=chunk-56NZ4OEO.js.map
255
+ //# sourceMappingURL=chunk-UKVVHCFN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/features/host/model/layer-handles.ts","../src/features/host/model/fluxion-host.ts"],"sourcesContent":["/**\n * Type-safe layer handles.\n *\n * These wrap the raw `FluxionHost.pushData(id, Float32Array)` API so callers\n * can work with structured records on the main thread (e.g. after receiving\n * ROS messages or processing sensor frames) without hand-rolling the\n * interleaved Float32Array layout every time.\n *\n * Each handle is a thin encoder: the input shape is type-checked at the call\n * site, encoding to the worker-side layout happens once, and the underlying\n * ArrayBuffer is still transferred zero-copy.\n */\n\n// A minimal surface of `FluxionHost` that handles depend on. Declared here\n// so this module has no circular import with fluxion-host.ts.\nexport interface FluxionDataSink {\n pushData(id: string, data: Float32Array): void;\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Line (streaming) — data layout: [t, y, t, y, ...]\n// ────────────────────────────────────────────────────────────────────────────\n\n/** One streaming sample. `t` is host-relative ms (what `axis-grid` time mode expects). */\nexport interface LineSample {\n t: number;\n y: number;\n}\n\nexport class LineLayerHandle {\n constructor(\n private readonly sink: FluxionDataSink,\n readonly id: string,\n ) {}\n\n /** Push a single `[t, y]` sample. Allocates a 2-element Float32Array. */\n push(sample: LineSample): void {\n const buf = new Float32Array(2);\n buf[0] = sample.t;\n buf[1] = sample.y;\n this.sink.pushData(this.id, buf);\n }\n\n /**\n * Push an array of samples in one postMessage. Encodes into a single\n * contiguous Float32Array and transfers ownership. Prefer this over a loop\n * of `push()` when you already have a batch in hand.\n */\n pushBatch(samples: readonly LineSample[]): void {\n const n = samples.length;\n if (n === 0) return;\n const buf = new Float32Array(n * 2);\n for (let i = 0; i < n; i++) {\n const s = samples[i];\n buf[i * 2] = s.t;\n buf[i * 2 + 1] = s.y;\n }\n this.sink.pushData(this.id, buf);\n }\n\n /**\n * Escape hatch: push a pre-built `[t, y, t, y, ...]` Float32Array directly.\n * Use for hot paths where you can avoid the object-to-array encode step.\n * The TypedArray's byteOffset must be 0 (same rule as `pushData`).\n */\n pushRaw(data: Float32Array): void {\n this.sink.pushData(this.id, data);\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Line-static — data layout: [x, y, x, y, ...] or [y0, y1, ...]\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface XyPoint {\n x: number;\n y: number;\n}\n\n/**\n * Handle for `kind: \"line-static\"` layers. `setXY` replaces the entire series\n * with a new xy array; `setY` does the same with y-only data (x is computed\n * from the layer's configured x range). Use the variant that matches the\n * layer's `layout` config — this is not enforced at the type level since the\n * worker-side layout is a runtime config.\n */\nexport class LineStaticLayerHandle {\n constructor(\n private readonly sink: FluxionDataSink,\n readonly id: string,\n ) {}\n\n setXY(points: readonly XyPoint[]): void {\n const n = points.length;\n const buf = new Float32Array(n * 2);\n for (let i = 0; i < n; i++) {\n const p = points[i];\n buf[i * 2] = p.x;\n buf[i * 2 + 1] = p.y;\n }\n this.sink.pushData(this.id, buf);\n }\n\n setY(values: readonly number[]): void {\n const buf = new Float32Array(values.length);\n for (let i = 0; i < values.length; i++) buf[i] = values[i];\n this.sink.pushData(this.id, buf);\n }\n\n pushRaw(data: Float32Array): void {\n this.sink.pushData(this.id, data);\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Lidar — data layout: [x, y, (z), (intensity), ...] with configurable stride\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface LidarPoint {\n x: number;\n y: number;\n /** Optional when the layer stride is 2. Defaults to 0 otherwise. */\n z?: number;\n /** Optional when the layer stride is <4. Defaults to 0 otherwise. */\n intensity?: number;\n}\n\nexport type LidarStride = 2 | 3 | 4;\n\n/**\n * Handle for `kind: \"lidar\"` scatter layers. The stride (2 = xy, 3 = xyz,\n * 4 = xyz+intensity) must match the layer's stride config. Passing a\n * mismatched stride will succeed but the worker will read the wrong fields.\n */\nexport class LidarLayerHandle {\n constructor(\n private readonly sink: FluxionDataSink,\n readonly id: string,\n readonly stride: LidarStride = 4,\n ) {}\n\n push(points: readonly LidarPoint[]): void {\n const stride = this.stride;\n const n = points.length;\n const buf = new Float32Array(n * stride);\n for (let i = 0; i < n; i++) {\n const p = points[i];\n const o = i * stride;\n buf[o] = p.x;\n buf[o + 1] = p.y;\n if (stride >= 3) buf[o + 2] = p.z ?? 0;\n if (stride >= 4) buf[o + 3] = p.intensity ?? 0;\n }\n this.sink.pushData(this.id, buf);\n }\n\n pushRaw(data: Float32Array): void {\n this.sink.pushData(this.id, data);\n }\n}\n","import type { AxisGridConfig } from \"../../../entities/axis-grid-layer\";\nimport type { LidarScatterConfig } from \"../../../entities/lidar-scatter-layer\";\nimport type { LineChartConfig } from \"../../../entities/line-chart-layer\";\nimport type { LineChartStaticConfig } from \"../../../entities/line-chart-static-layer\";\nimport { Op, type DType, type HostMsg, type LayerKind } from \"../../../shared/protocol\";\nimport {\n LidarLayerHandle,\n type LidarStride,\n LineLayerHandle,\n LineStaticLayerHandle,\n} from \"./layer-handles\";\n\n/**\n * TypedArray flavors that FluxionRender accepts. `ArrayBufferView` is too\n * permissive (includes DataView), so we narrow to the specific types whose\n * layout matches the worker-side `wrapTypedArray` contract.\n */\nexport type FluxionTypedArray =\n | Float32Array\n | Uint8Array\n | Int16Array\n | Uint16Array\n | Int32Array;\n\nexport interface FluxionHostOptions {\n /**\n * Override the worker URL. Useful when bundlers don't support\n * `new Worker(new URL('./fluxion-worker.js', import.meta.url))`.\n * Pass a factory that returns a constructed Worker.\n */\n workerFactory?: () => Worker;\n /**\n * Canvas background color, applied every frame before layers draw.\n * Defaults to `\"#0b0d12\"` (dark) when omitted. Use `setBgColor` to change\n * it at runtime (e.g. for a theme toggle).\n */\n bgColor?: string;\n}\n\nfunction defaultWorkerFactory(): Worker {\n // Vite / modern bundlers resolve this to a separate worker chunk.\n return new Worker(new URL(\"./fluxion-worker.js\", import.meta.url), {\n type: \"module\",\n });\n}\n\nfunction dtypeOf(arr: FluxionTypedArray): DType {\n if (arr instanceof Float32Array) return \"f32\";\n if (arr instanceof Uint8Array) return \"u8\";\n if (arr instanceof Int16Array) return \"i16\";\n if (arr instanceof Uint16Array) return \"u16\";\n if (arr instanceof Int32Array) return \"i32\";\n throw new Error(\"fluxion-render: unsupported TypedArray\");\n}\n\n/**\n * Main-thread handle to a worker-hosted rendering engine.\n *\n * Lifecycle:\n * const host = new FluxionHost(canvas);\n * host.addLayer('chart', 'line', { color: '#0ff' });\n * host.pushData('chart', float32); // transfers ownership\n * host.resize(w, h, dpr);\n * host.dispose();\n */\nexport class FluxionHost {\n private worker: Worker;\n private disposed = false;\n\n constructor(canvas: HTMLCanvasElement, opts: FluxionHostOptions = {}) {\n this.worker = (opts.workerFactory ?? defaultWorkerFactory)();\n\n const offscreen = canvas.transferControlToOffscreen();\n const dpr = typeof devicePixelRatio === \"number\" ? devicePixelRatio : 1;\n const rect = canvas.getBoundingClientRect();\n const width = rect.width || canvas.width || 300;\n const height = rect.height || canvas.height || 150;\n\n this.post(\n {\n op: Op.INIT,\n canvas: offscreen,\n width,\n height,\n dpr,\n bgColor: opts.bgColor,\n },\n [offscreen],\n );\n }\n\n /**\n * Update the canvas background color at runtime. Takes effect on the next\n * rendered frame. Useful for theme toggles without tearing down the host.\n */\n setBgColor(color: string): void {\n this.post({ op: Op.SET_BG_COLOR, color });\n }\n\n /**\n * Typed `addLayer` overloads.\n *\n * Prefer the kind-specific helpers below (`addLineLayer`, `addAxisLayer`,\n * etc.) — they both type-check the config AND return a typed handle where\n * applicable. This overload is retained for cases where the kind is chosen\n * dynamically.\n */\n addLayer(id: string, kind: \"line\", config?: LineChartConfig): void;\n addLayer(id: string, kind: \"line-static\", config?: LineChartStaticConfig): void;\n addLayer(id: string, kind: \"lidar\", config?: LidarScatterConfig): void;\n addLayer(id: string, kind: \"axis-grid\", config?: AxisGridConfig): void;\n // Dynamic fallback for code paths that pass a runtime `LayerKind` (e.g.\n // `useFluxionCanvas({ layers: FluxionLayerSpec[] })`).\n addLayer(id: string, kind: LayerKind, config?: unknown): void;\n addLayer(id: string, kind: LayerKind, config?: unknown): void {\n this.post({ op: Op.ADD_LAYER, id, kind, config });\n }\n\n removeLayer(id: string): void {\n this.post({ op: Op.REMOVE_LAYER, id });\n }\n\n /**\n * Typed `configLayer` overloads — pick the config shape from the kind used\n * when the layer was created. There's no runtime tag check; the caller is\n * trusted to pass the right config for the right id.\n */\n configLayer(id: string, config: LineChartConfig): void;\n configLayer(id: string, config: LineChartStaticConfig): void;\n configLayer(id: string, config: LidarScatterConfig): void;\n configLayer(id: string, config: AxisGridConfig): void;\n // Dynamic fallback for helpers like `useLayerConfig` that carry an opaque\n // config alongside the layer id.\n configLayer(id: string, config: unknown): void;\n configLayer(id: string, config: unknown): void {\n this.post({ op: Op.CONFIG, id, config });\n }\n\n // ──────────────────────────────────────────────────────────────────────\n // Typed add-layer helpers: construct + return a typed handle in one call.\n // ──────────────────────────────────────────────────────────────────────\n\n /**\n * Add a streaming line layer and return a handle that accepts structured\n * `{ t, y }` samples instead of raw Float32Array interleaved layout.\n */\n addLineLayer(id: string, config?: LineChartConfig): LineLayerHandle {\n this.addLayer(id, \"line\", config);\n return new LineLayerHandle(this, id);\n }\n\n /**\n * Add a static xy line layer and return a handle that accepts\n * `{ x, y }[]` or plain y-only arrays.\n */\n addLineStaticLayer(id: string, config?: LineChartStaticConfig): LineStaticLayerHandle {\n this.addLayer(id, \"line-static\", config);\n return new LineStaticLayerHandle(this, id);\n }\n\n /**\n * Add a LiDAR scatter layer and return a handle that accepts\n * `{ x, y, z?, intensity? }[]`. The handle's stride must match\n * `config.stride` (default 4).\n */\n addLidarLayer(id: string, config?: LidarScatterConfig): LidarLayerHandle {\n this.addLayer(id, \"lidar\", config);\n const stride = (config?.stride as LidarStride | undefined) ?? 4;\n return new LidarLayerHandle(this, id, stride);\n }\n\n /**\n * Add an axis/grid layer. Axis layers don't take data, so this returns\n * void — use `configLayer` to retune bounds / time window later.\n */\n addAxisLayer(id: string, config?: AxisGridConfig): void {\n this.addLayer(id, \"axis-grid\", config);\n }\n\n // ──────────────────────────────────────────────────────────────────────\n // Attach a typed handle to a layer that was added via another API path\n // (e.g. declaratively through `<FluxionCanvas layers={...}>` or\n // `useFluxionCanvas({ layers: [...] })`).\n // ──────────────────────────────────────────────────────────────────────\n\n line(id: string): LineLayerHandle {\n return new LineLayerHandle(this, id);\n }\n\n lineStatic(id: string): LineStaticLayerHandle {\n return new LineStaticLayerHandle(this, id);\n }\n\n lidar(id: string, stride: LidarStride = 4): LidarLayerHandle {\n return new LidarLayerHandle(this, id, stride);\n }\n\n /**\n * Push TypedArray data to a layer. Transfers the underlying ArrayBuffer —\n * the caller MUST NOT use `data` again afterwards.\n *\n * The TypedArray must start at byteOffset 0 because the worker reconstructs\n * the view at offset 0. Subviews would silently read from the wrong offset,\n * so they're rejected up-front. Use `data.slice()` to get a fresh buffer.\n */\n pushData(id: string, data: FluxionTypedArray): void {\n if (data.byteOffset !== 0) {\n throw new Error(\n `fluxion-render: TypedArray must start at byteOffset 0 (got ${data.byteOffset}). ` +\n `Call .slice() to copy into a fresh buffer before pushing.`,\n );\n }\n const buffer = data.buffer as ArrayBuffer;\n this.post(\n {\n op: Op.DATA,\n id,\n buffer,\n dtype: dtypeOf(data),\n length: data.length,\n },\n [buffer],\n );\n }\n\n resize(width: number, height: number, dpr: number): void {\n this.post({ op: Op.RESIZE, width, height, dpr });\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n try {\n this.post({ op: Op.DISPOSE });\n } catch {\n // worker may already be gone\n }\n this.worker.terminate();\n }\n\n private post(msg: HostMsg, transfer?: Transferable[]): void {\n if (this.disposed) return;\n if (transfer && transfer.length) {\n this.worker.postMessage(msg, transfer);\n } else {\n this.worker.postMessage(msg);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -101,6 +101,7 @@ declare const Op: {
101
101
  readonly CONFIG: 5;
102
102
  readonly DATA: 6;
103
103
  readonly DISPOSE: 7;
104
+ readonly SET_BG_COLOR: 8;
104
105
  };
105
106
  type Op = (typeof Op)[keyof typeof Op];
106
107
  type LayerKind = "line" | "line-static" | "lidar" | "axis-grid";
@@ -111,6 +112,11 @@ interface InitMsg {
111
112
  width: number;
112
113
  height: number;
113
114
  dpr: number;
115
+ /**
116
+ * Optional canvas background color. Applied every frame before layers draw.
117
+ * Default (when omitted): `"#0b0d12"` — matches the engine's dark default.
118
+ */
119
+ bgColor?: string;
114
120
  }
115
121
  interface ResizeMsg {
116
122
  op: typeof Op.RESIZE;
@@ -143,7 +149,15 @@ interface DataMsg {
143
149
  interface DisposeMsg {
144
150
  op: typeof Op.DISPOSE;
145
151
  }
146
- type HostMsg = InitMsg | ResizeMsg | AddLayerMsg | RemoveLayerMsg | ConfigMsg | DataMsg | DisposeMsg;
152
+ /**
153
+ * Canvas-scope (engine-level) background color update. Takes effect on the
154
+ * next rendered frame. Separate from `CONFIG` because `CONFIG` is layer-scope.
155
+ */
156
+ interface SetBgColorMsg {
157
+ op: typeof Op.SET_BG_COLOR;
158
+ color: string;
159
+ }
160
+ type HostMsg = InitMsg | ResizeMsg | AddLayerMsg | RemoveLayerMsg | ConfigMsg | DataMsg | DisposeMsg | SetBgColorMsg;
147
161
 
148
162
  /**
149
163
  * Type-safe layer handles.
@@ -239,6 +253,12 @@ interface FluxionHostOptions {
239
253
  * Pass a factory that returns a constructed Worker.
240
254
  */
241
255
  workerFactory?: () => Worker;
256
+ /**
257
+ * Canvas background color, applied every frame before layers draw.
258
+ * Defaults to `"#0b0d12"` (dark) when omitted. Use `setBgColor` to change
259
+ * it at runtime (e.g. for a theme toggle).
260
+ */
261
+ bgColor?: string;
242
262
  }
243
263
  /**
244
264
  * Main-thread handle to a worker-hosted rendering engine.
@@ -254,6 +274,11 @@ declare class FluxionHost {
254
274
  private worker;
255
275
  private disposed;
256
276
  constructor(canvas: HTMLCanvasElement, opts?: FluxionHostOptions);
277
+ /**
278
+ * Update the canvas background color at runtime. Takes effect on the next
279
+ * rendered frame. Useful for theme toggles without tearing down the host.
280
+ */
281
+ setBgColor(color: string): void;
257
282
  /**
258
283
  * Typed `addLayer` overloads.
259
284
  *
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-3PK3KDO5.js";
4
4
  import {
5
5
  Op
6
- } from "./chunk-R7FLS7BG.js";
6
+ } from "./chunk-EBSPHY2J.js";
7
7
 
8
8
  // src/shared/lib/math.ts
9
9
  function niceStep(range, targetTicks) {
@@ -726,7 +726,11 @@ var Engine = class {
726
726
  dispatch(msg) {
727
727
  switch (msg.op) {
728
728
  case Op.INIT:
729
- this.init(msg.canvas, msg.width, msg.height, msg.dpr);
729
+ this.init(msg.canvas, msg.width, msg.height, msg.dpr, msg.bgColor);
730
+ break;
731
+ case Op.SET_BG_COLOR:
732
+ this.bgColor = msg.color;
733
+ this.scheduler.markDirty();
730
734
  break;
731
735
  case Op.RESIZE:
732
736
  this.resize(msg.width, msg.height, msg.dpr);
@@ -764,9 +768,10 @@ var Engine = class {
764
768
  break;
765
769
  }
766
770
  }
767
- init(canvas, width, height, dpr) {
771
+ init(canvas, width, height, dpr, bgColor) {
768
772
  this.canvas = canvas;
769
773
  this.ctx = canvas.getContext("2d");
774
+ if (bgColor !== void 0) this.bgColor = bgColor;
770
775
  this.resize(width, height, dpr);
771
776
  this.scheduler.start();
772
777
  this.scheduler.markDirty();
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/lib/math.ts","../src/entities/axis-grid-layer/model/axis-grid-layer.ts","../src/entities/layer-stack/model/layer-stack.ts","../src/shared/lib/color.ts","../src/entities/lidar-scatter-layer/model/lidar-scatter-layer.ts","../src/shared/model/ring-buffer.ts","../src/entities/line-chart-layer/model/line-chart-layer.ts","../src/entities/line-chart-static-layer/model/line-chart-static-layer.ts","../src/shared/model/scheduler.ts","../src/shared/model/viewport.ts","../src/features/engine/model/engine.ts","../src/app/worker/fluxion-worker.ts"],"sourcesContent":["/**\n * Pick an aesthetically pleasing step for an axis covering [min, max] with\n * ~`targetTicks` divisions. Based on the standard nice-number heuristic.\n */\nexport function niceStep(range: number, targetTicks: number): number {\n const rough = range / Math.max(1, targetTicks);\n const pow10 = Math.pow(10, Math.floor(Math.log10(rough)));\n const norm = rough / pow10;\n let nice: number;\n if (norm < 1.5) nice = 1;\n else if (norm < 3) nice = 2;\n else if (norm < 7) nice = 5;\n else nice = 10;\n return nice * pow10;\n}\n\nexport function niceTicks(min: number, max: number, targetTicks = 6): number[] {\n if (!isFinite(min) || !isFinite(max) || max <= min) return [];\n const step = niceStep(max - min, targetTicks);\n const start = Math.ceil(min / step) * step;\n const out: number[] = [];\n for (let v = start; v <= max + step * 1e-6; v += step) {\n out.push(Number(v.toFixed(12)));\n }\n return out;\n}\n","import { niceTicks } from \"../../../shared/lib/math\";\nimport { formatClock } from \"../../../shared/lib/time-format\";\nimport type { Layer } from \"../../../shared/model/layer\";\nimport type { Bounds, Viewport } from \"../../../shared/model/viewport\";\n\nexport interface AxisGridConfig {\n /** Fixed x-range. Used when `xMode` is \"fixed\" (default). */\n xRange?: [number, number];\n yRange?: [number, number];\n gridColor?: string;\n axisColor?: string;\n labelColor?: string;\n font?: string;\n targetTicks?: number;\n /** If true (default), writes this layer's bounds into `viewport` so data layers share them. */\n applyToViewport?: boolean;\n /**\n * \"fixed\": xRange is literal world units (default).\n * \"time\": bounds follow the streaming `viewport.latestT` as a trailing\n * sliding window `[latestT - timeWindowMs, latestT]`. yRange is still fixed.\n */\n xMode?: \"fixed\" | \"time\";\n /** Width of the sliding window in ms when xMode=\"time\". Default 5000. */\n timeWindowMs?: number;\n /**\n * Absolute wall-clock epoch (ms) corresponding to data timestamp `0`. When\n * set together with `xMode: \"time\"`, tick labels render as wall clock\n * instead of elapsed seconds. Typically set once at host creation:\n * `timeOrigin: Date.now()` on the main thread.\n */\n timeOrigin?: number;\n /**\n * Clock-pattern string used to render x tick labels when `xMode: \"time\"`\n * AND `timeOrigin` is set. Default `\"HH:mm:ss\"`. Supported tokens:\n * `HH / H / mm / m / ss / s / SSS / S`. Anything else is a literal.\n *\n * Ignored when `timeOrigin` is not provided — elapsed-seconds fallback\n * (`\"X.Xs\"`) is used instead.\n */\n xTickFormat?: string;\n\n // ─── y scaling ────────────────────────────────────────────\n /**\n * \"fixed\" (default): use configured `yRange`.\n * \"auto\": data-driven. Reads `viewport.observedYMin/Max` during draw,\n * applies padding and clamps, updates `bounds.yMin/yMax`. Requires at\n * least one data layer (e.g. `LineChartLayer`) in the stack to publish\n * observations via its `scan()` pass.\n */\n yMode?: \"fixed\" | \"auto\";\n /** Padding ratio applied above/below the observed range. Default 0.1 (10%). */\n yAutoPadding?: number;\n /** Absolute lower clamp after padding. */\n yAutoMin?: number;\n /** Absolute upper clamp after padding. */\n yAutoMax?: number;\n\n // ─── Visual toggles (all default true) ────────────────────\n /** Show vertical grid lines at x ticks. */\n showXGrid?: boolean;\n /** Show horizontal grid lines at y ticks. */\n showYGrid?: boolean;\n /** Show the x=0 / y=0 axis lines when 0 is inside the range. */\n showAxes?: boolean;\n /** Show tick labels along the x axis. */\n showXLabels?: boolean;\n /** Show tick labels along the y axis. */\n showYLabels?: boolean;\n}\n\n/**\n * Owns the viewport bounds orchestration for a chart: x window (fixed or\n * time-sliding), y range (fixed or data-driven auto), and renders the\n * visible grid/axes/labels on top.\n *\n * Orchestration (scan + bounds computation) runs independently of the\n * visual toggles — you can turn every `show*` off and still use this layer\n * purely as a controller. LayerStack insertion order matters: add this\n * before any data layer so the bounds are written before they're read.\n *\n * v0.3 limitation: single-axis only. `observedYMin/Max` live on `Viewport`,\n * so a second `AxisGridLayer` in the same stack would bleed observations.\n */\nexport class AxisGridLayer implements Layer {\n readonly id: string;\n private gridColor = \"rgba(255,255,255,0.08)\";\n private axisColor = \"rgba(255,255,255,0.4)\";\n private labelColor = \"rgba(255,255,255,0.7)\";\n private font = \"10px sans-serif\";\n private targetTicks = 6;\n private bounds: Bounds = { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };\n private applyToViewport = true;\n private xMode: \"fixed\" | \"time\" = \"fixed\";\n private timeWindowMs = 5000;\n private timeOrigin: number | null = null;\n private xTickFormat = \"HH:mm:ss\";\n private yMode: \"fixed\" | \"auto\" = \"fixed\";\n private yAutoPadding = 0.1;\n private yAutoMin: number | undefined;\n private yAutoMax: number | undefined;\n private showXGrid = true;\n private showYGrid = true;\n private showAxes = true;\n private showXLabels = true;\n private showYLabels = true;\n\n constructor(id: string) {\n this.id = id;\n }\n\n setConfig(config: unknown): void {\n const c = config as AxisGridConfig;\n if (c.xRange) {\n this.bounds.xMin = c.xRange[0];\n this.bounds.xMax = c.xRange[1];\n }\n if (c.yRange) {\n this.bounds.yMin = c.yRange[0];\n this.bounds.yMax = c.yRange[1];\n }\n if (c.gridColor) this.gridColor = c.gridColor;\n if (c.axisColor) this.axisColor = c.axisColor;\n if (c.labelColor) this.labelColor = c.labelColor;\n if (c.font) this.font = c.font;\n if (c.targetTicks) this.targetTicks = c.targetTicks;\n if (c.applyToViewport !== undefined) this.applyToViewport = c.applyToViewport;\n if (c.xMode !== undefined) this.xMode = c.xMode;\n if (c.timeWindowMs !== undefined) this.timeWindowMs = c.timeWindowMs;\n if (c.timeOrigin !== undefined) this.timeOrigin = c.timeOrigin;\n if (c.xTickFormat !== undefined) this.xTickFormat = c.xTickFormat;\n if (c.yMode !== undefined) this.yMode = c.yMode;\n if (c.yAutoPadding !== undefined) this.yAutoPadding = c.yAutoPadding;\n if (c.yAutoMin !== undefined) this.yAutoMin = c.yAutoMin;\n if (c.yAutoMax !== undefined) this.yAutoMax = c.yAutoMax;\n if (c.showXGrid !== undefined) this.showXGrid = c.showXGrid;\n if (c.showYGrid !== undefined) this.showYGrid = c.showYGrid;\n if (c.showAxes !== undefined) this.showAxes = c.showAxes;\n if (c.showXLabels !== undefined) this.showXLabels = c.showXLabels;\n if (c.showYLabels !== undefined) this.showYLabels = c.showYLabels;\n }\n\n setData(_buffer: ArrayBuffer, _length: number, _viewport: Viewport): void {}\n\n resize(_viewport: Viewport): void {}\n\n /**\n * Orchestration pass: establish x bounds so data layers' `scan` can filter\n * visible samples correctly. yMode:\"auto\" is finalized in `draw` after all\n * line layers have published their observations.\n */\n scan(viewport: Viewport): void {\n if (this.xMode === \"time\") {\n const latestT = viewport.latestT;\n this.bounds.xMin = latestT - this.timeWindowMs;\n this.bounds.xMax = latestT;\n }\n if (this.applyToViewport) {\n viewport.setBounds(this.bounds);\n }\n }\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n // Finalize y-auto bounds. Runs after all line-layer scans have\n // published their observed extents into the viewport.\n if (this.yMode === \"auto\") {\n let yMin = viewport.observedYMin;\n let yMax = viewport.observedYMax;\n if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n // No data yet — fall back to configured yRange. If that is also\n // degenerate (defaults [-1, 1] from construction), use [-1, 1].\n yMin = this.bounds.yMin;\n yMax = this.bounds.yMax;\n if (yMin === yMax) {\n yMin = -1;\n yMax = 1;\n }\n } else if (yMin === yMax) {\n // Flat line — expand so stroke has vertical room.\n yMin -= 0.5;\n yMax += 0.5;\n } else {\n const pad = (yMax - yMin) * this.yAutoPadding;\n yMin -= pad;\n yMax += pad;\n }\n if (this.yAutoMin !== undefined && yMin < this.yAutoMin) yMin = this.yAutoMin;\n if (this.yAutoMax !== undefined && yMax > this.yAutoMax) yMax = this.yAutoMax;\n this.bounds.yMin = yMin;\n this.bounds.yMax = yMax;\n if (this.applyToViewport) viewport.setBounds(this.bounds);\n }\n\n const { widthPx: w, heightPx: h } = viewport;\n const xTicks = niceTicks(this.bounds.xMin, this.bounds.xMax, this.targetTicks);\n const yTicks = niceTicks(this.bounds.yMin, this.bounds.yMax, this.targetTicks);\n\n // ── Grid lines ──\n if (this.showXGrid || this.showYGrid) {\n ctx.strokeStyle = this.gridColor;\n ctx.lineWidth = 1;\n ctx.beginPath();\n if (this.showXGrid) {\n for (let i = 0; i < xTicks.length; i++) {\n const x = Math.round(viewport.xToPx(xTicks[i])) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n }\n if (this.showYGrid) {\n for (let i = 0; i < yTicks.length; i++) {\n const y = Math.round(viewport.yToPx(yTicks[i])) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n }\n ctx.stroke();\n }\n\n // ── Zero axes ──\n if (this.showAxes) {\n ctx.strokeStyle = this.axisColor;\n ctx.beginPath();\n if (this.bounds.xMin < 0 && this.bounds.xMax > 0) {\n const x0 = Math.round(viewport.xToPx(0)) + 0.5;\n ctx.moveTo(x0, 0);\n ctx.lineTo(x0, h);\n }\n if (this.bounds.yMin < 0 && this.bounds.yMax > 0) {\n const y0 = Math.round(viewport.yToPx(0)) + 0.5;\n ctx.moveTo(0, y0);\n ctx.lineTo(w, y0);\n }\n ctx.stroke();\n }\n\n // ── Labels ──\n if (this.showXLabels || this.showYLabels) {\n ctx.fillStyle = this.labelColor;\n ctx.font = this.font;\n if (this.showXLabels) {\n ctx.textBaseline = \"top\";\n for (let i = 0; i < xTicks.length; i++) {\n const x = viewport.xToPx(xTicks[i]);\n ctx.fillText(\n formatTick(xTicks[i], this.xMode, this.timeOrigin, this.xTickFormat),\n x + 2,\n h - 12,\n );\n }\n }\n if (this.showYLabels) {\n ctx.textBaseline = \"middle\";\n for (let i = 0; i < yTicks.length; i++) {\n const y = viewport.yToPx(yTicks[i]);\n ctx.fillText(String(yTicks[i]), 2, y - 6);\n }\n }\n }\n }\n\n dispose(): void {}\n}\n\nfunction formatTick(\n value: number,\n mode: \"fixed\" | \"time\",\n timeOrigin: number | null,\n pattern: string,\n): string {\n if (mode === \"time\") {\n if (timeOrigin != null) {\n return formatClock(timeOrigin + value, pattern);\n }\n // Elapsed-only fallback (timeOrigin missing).\n const s = value / 1000;\n return `${s.toFixed(1)}s`;\n }\n return String(value);\n}\n","import type { Layer } from \"../../../shared/model/layer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport class LayerStack {\n private layers: Layer[] = [];\n private byId = new Map<string, Layer>();\n\n add(layer: Layer): void {\n this.layers.push(layer);\n this.byId.set(layer.id, layer);\n }\n\n remove(id: string): void {\n const layer = this.byId.get(id);\n if (!layer) return;\n this.byId.delete(id);\n const i = this.layers.indexOf(layer);\n if (i >= 0) this.layers.splice(i, 1);\n layer.dispose();\n }\n\n get(id: string): Layer | undefined {\n return this.byId.get(id);\n }\n\n resizeAll(viewport: Viewport): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].resize(viewport);\n }\n }\n\n /**\n * Pre-draw pass. Layers that implement `scan` update shared viewport state\n * (bounds, observed extents) here so downstream layers' `draw` sees the\n * correct values. Iterates in insertion order so axis-grid (added first)\n * writes bounds before data layers read them.\n */\n scanAll(viewport: Viewport): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].scan?.(viewport);\n }\n }\n\n drawAll(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].draw(ctx, viewport);\n }\n }\n\n disposeAll(): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].dispose();\n }\n this.layers.length = 0;\n this.byId.clear();\n }\n}\n","/**\n * Intensity (0..1) → viridis-ish RGB LUT.\n * Precomputed 256-entry lookup to avoid per-point math during rendering.\n */\nconst LUT_SIZE = 256;\nconst lutR = new Uint8ClampedArray(LUT_SIZE);\nconst lutG = new Uint8ClampedArray(LUT_SIZE);\nconst lutB = new Uint8ClampedArray(LUT_SIZE);\n\nfunction ramp(t: number): [number, number, number] {\n const stops: [number, [number, number, number]][] = [\n [0.0, [13, 8, 135]],\n [0.25, [84, 2, 163]],\n [0.5, [156, 23, 158]],\n [0.75, [225, 100, 98]],\n [1.0, [240, 249, 33]],\n ];\n for (let i = 0; i < stops.length - 1; i++) {\n const [t0, c0] = stops[i];\n const [t1, c1] = stops[i + 1];\n if (t <= t1) {\n const k = (t - t0) / (t1 - t0);\n return [\n c0[0] + (c1[0] - c0[0]) * k,\n c0[1] + (c1[1] - c0[1]) * k,\n c0[2] + (c1[2] - c0[2]) * k,\n ];\n }\n }\n return stops[stops.length - 1][1];\n}\n\nfor (let i = 0; i < LUT_SIZE; i++) {\n const [r, g, b] = ramp(i / (LUT_SIZE - 1));\n lutR[i] = r;\n lutG[i] = g;\n lutB[i] = b;\n}\n\nexport interface IntensityLUT {\n readonly r: Uint8ClampedArray;\n readonly g: Uint8ClampedArray;\n readonly b: Uint8ClampedArray;\n readonly size: number;\n}\n\nconst SINGLETON: IntensityLUT = Object.freeze({\n r: lutR,\n g: lutG,\n b: lutB,\n size: LUT_SIZE,\n});\n\nexport function intensityLUT(): IntensityLUT {\n return SINGLETON;\n}\n","import { intensityLUT } from \"../../../shared/lib/color\";\nimport type { Layer } from \"../../../shared/model/layer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport interface LidarScatterConfig {\n /** Floats per point. Default 4 (x,y,z,intensity). Minimum 2 (x,y). */\n stride?: number;\n /** Point size in pixels. */\n pointSize?: number;\n /** Max intensity value for normalization (0..1). Default 1.0. */\n intensityMax?: number;\n /** Solid color override. When set, intensity LUT is ignored. */\n color?: string;\n}\n\nconst LUT_BUCKETS = 256;\n\n/**\n * Renders a point cloud as a 2D top-down scatter.\n * Expects Float32Array with layout [x, y, z, intensity, ...] (stride configurable).\n *\n * Fast path for 10k+ points: counting-sort by intensity-LUT bucket so each\n * color bucket issues one `beginPath / rect×N / fill` cycle. This reduces\n * fillStyle state changes from O(N) to at most 256 per frame, and collapses\n * all per-point fillRect calls into a single path fill per color.\n *\n * Scratch buffers (`sortedX`, `sortedY`, `bucketCount`) live as layer fields\n * and grow by 25% when the point budget expands, so steady-state pushes\n * allocate zero memory.\n */\nexport class LidarScatterLayer implements Layer {\n readonly id: string;\n private stride = 4;\n private pointSize = 2;\n private intensityMax = 1;\n private solidColor: [number, number, number] | null = null;\n private data: Float32Array | null = null;\n private length = 0;\n\n // Scratch buffers for counting-sort batching.\n private sortedX: Float32Array = new Float32Array(0);\n private sortedY: Float32Array = new Float32Array(0);\n private bucketCount: Uint32Array = new Uint32Array(LUT_BUCKETS);\n private bucketOffset: Uint32Array = new Uint32Array(LUT_BUCKETS);\n private scratchCapacity = 0;\n\n constructor(id: string) {\n this.id = id;\n }\n\n setConfig(config: unknown): void {\n const c = config as LidarScatterConfig;\n if (c.stride !== undefined) this.stride = Math.max(2, c.stride | 0);\n if (c.pointSize !== undefined) this.pointSize = Math.max(1, c.pointSize);\n if (c.intensityMax !== undefined) this.intensityMax = c.intensityMax;\n if (c.color !== undefined) this.solidColor = parseColor(c.color);\n }\n\n setData(buffer: ArrayBuffer, length: number, _viewport: Viewport): void {\n this.data = new Float32Array(buffer, 0, length);\n this.length = length;\n }\n\n resize(_viewport: Viewport): void {}\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n const data = this.data;\n if (!data || this.length < this.stride) return;\n\n const stride = this.stride;\n const size = this.pointSize;\n const half = size / 2;\n const count = (this.length / stride) | 0;\n\n if (this.solidColor) {\n this.drawSolid(ctx, viewport, data, stride, count, size, half);\n return;\n }\n\n this.ensureScratch(count);\n this.drawBucketed(ctx, viewport, data, stride, count, size, half);\n }\n\n private drawSolid(\n ctx: OffscreenCanvasRenderingContext2D,\n viewport: Viewport,\n data: Float32Array,\n stride: number,\n count: number,\n size: number,\n half: number,\n ): void {\n const [r, g, b] = this.solidColor as [number, number, number];\n ctx.fillStyle = `rgb(${r},${g},${b})`;\n ctx.beginPath();\n for (let i = 0; i < count; i++) {\n const o = i * stride;\n const px = viewport.xToPx(data[o]);\n const py = viewport.yToPx(data[o + 1]);\n ctx.rect(px - half, py - half, size, size);\n }\n ctx.fill();\n }\n\n private drawBucketed(\n ctx: OffscreenCanvasRenderingContext2D,\n viewport: Viewport,\n data: Float32Array,\n stride: number,\n count: number,\n size: number,\n half: number,\n ): void {\n const lut = intensityLUT();\n const invMax = 1 / (this.intensityMax || 1);\n const sortedX = this.sortedX;\n const sortedY = this.sortedY;\n const bucketCount = this.bucketCount;\n const bucketOffset = this.bucketOffset;\n\n // Reset bucket histograms.\n bucketCount.fill(0);\n\n // Pass 1: compute pixel coords + bucket index, fill histogram.\n // Stash bucket index in a parallel-ish way: we'll recompute in pass 2\n // to avoid a third scratch buffer. The cost is one extra intensity read.\n for (let i = 0; i < count; i++) {\n const o = i * stride;\n const intensity = stride >= 4 ? data[o + 3] : 1;\n let idx = (intensity * invMax * (LUT_BUCKETS - 1)) | 0;\n if (idx < 0) idx = 0;\n else if (idx >= LUT_BUCKETS) idx = LUT_BUCKETS - 1;\n bucketCount[idx]++;\n }\n\n // Prefix-sum: bucketOffset[b] = sum of bucketCount[0..b-1]\n let acc = 0;\n for (let b = 0; b < LUT_BUCKETS; b++) {\n bucketOffset[b] = acc;\n acc += bucketCount[b];\n }\n\n // Pass 2: scatter points into bucket-ordered scratch. We reuse\n // `bucketCount` as a write cursor by decrementing it back to 0.\n // Snapshot offsets first, then advance cursors from each offset.\n // Simpler: use bucketCount as an independent write cursor initialised to 0.\n const writeCursor = bucketCount; // reused, currently holds histogram\n // Restore to 0 now that bucketOffset has captured the prefix sum.\n for (let b = 0; b < LUT_BUCKETS; b++) writeCursor[b] = 0;\n\n for (let i = 0; i < count; i++) {\n const o = i * stride;\n const intensity = stride >= 4 ? data[o + 3] : 1;\n let idx = (intensity * invMax * (LUT_BUCKETS - 1)) | 0;\n if (idx < 0) idx = 0;\n else if (idx >= LUT_BUCKETS) idx = LUT_BUCKETS - 1;\n const pos = bucketOffset[idx] + writeCursor[idx];\n writeCursor[idx]++;\n sortedX[pos] = viewport.xToPx(data[o]);\n sortedY[pos] = viewport.yToPx(data[o + 1]);\n }\n\n // Pass 3: emit one path per non-empty bucket.\n for (let b = 0; b < LUT_BUCKETS; b++) {\n const n = writeCursor[b];\n if (n === 0) continue;\n ctx.fillStyle = `rgb(${lut.r[b]},${lut.g[b]},${lut.b[b]})`;\n ctx.beginPath();\n const start = bucketOffset[b];\n const end = start + n;\n for (let i = start; i < end; i++) {\n ctx.rect(sortedX[i] - half, sortedY[i] - half, size, size);\n }\n ctx.fill();\n }\n }\n\n private ensureScratch(count: number): void {\n if (count <= this.scratchCapacity) return;\n const next = Math.max(count, Math.ceil(this.scratchCapacity * 1.25), 1024);\n this.sortedX = new Float32Array(next);\n this.sortedY = new Float32Array(next);\n this.scratchCapacity = next;\n }\n\n dispose(): void {\n this.data = null;\n this.sortedX = new Float32Array(0);\n this.sortedY = new Float32Array(0);\n this.scratchCapacity = 0;\n }\n}\n\nfunction parseColor(css: string): [number, number, number] {\n if (css.startsWith(\"#\")) {\n const hex = css.slice(1);\n if (hex.length === 3) {\n return [\n parseInt(hex[0] + hex[0], 16),\n parseInt(hex[1] + hex[1], 16),\n parseInt(hex[2] + hex[2], 16),\n ];\n }\n if (hex.length === 6) {\n return [\n parseInt(hex.slice(0, 2), 16),\n parseInt(hex.slice(2, 4), 16),\n parseInt(hex.slice(4, 6), 16),\n ];\n }\n }\n return [255, 255, 255];\n}\n","/**\n * Fixed-capacity ring buffer over a Float32Array.\n * Stores interleaved records of `stride` floats each (e.g. [x,y] stride=2).\n * Zero-allocation push; draw iterates from tail to head in chronological order.\n */\nexport class RingBuffer {\n readonly stride: number;\n readonly capacity: number;\n readonly data: Float32Array;\n private head = 0;\n private count = 0;\n\n constructor(capacity: number, stride: number) {\n this.capacity = capacity;\n this.stride = stride;\n this.data = new Float32Array(capacity * stride);\n }\n\n get length(): number {\n return this.count;\n }\n\n push(record: ArrayLike<number>): void {\n const base = this.head * this.stride;\n for (let i = 0; i < this.stride; i++) {\n this.data[base + i] = record[i];\n }\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) this.count++;\n }\n\n pushMany(records: Float32Array): void {\n const recCount = records.length / this.stride;\n for (let r = 0; r < recCount; r++) {\n const base = this.head * this.stride;\n const src = r * this.stride;\n for (let i = 0; i < this.stride; i++) {\n this.data[base + i] = records[src + i];\n }\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) this.count++;\n }\n }\n\n forEach(fn: (data: Float32Array, offset: number, index: number) => void): void {\n const start = this.count < this.capacity ? 0 : this.head;\n for (let i = 0; i < this.count; i++) {\n const slot = (start + i) % this.capacity;\n fn(this.data, slot * this.stride, i);\n }\n }\n\n clear(): void {\n this.head = 0;\n this.count = 0;\n }\n}\n","import type { Layer } from \"../../../shared/model/layer\";\nimport { RingBuffer } from \"../../../shared/model/ring-buffer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport interface LineChartConfig {\n color?: string;\n lineWidth?: number;\n /** Ring buffer capacity (number of [t,y] samples retained). Default 2048. */\n capacity?: number;\n}\n\n/**\n * Streaming time-series line chart. Expects `Float32Array [t, y, t, y, ...]`\n * where `t` is host-relative milliseconds (monotonic). Each `setData` call\n * appends to an internal ring buffer, so draw cost is O(capacity), not O(total\n * samples pushed).\n *\n * On every append the layer advances `viewport.latestT` so the axis-grid in\n * `xMode: \"time\"` can compute a trailing sliding window.\n */\nexport class LineChartLayer implements Layer {\n readonly id: string;\n private color = \"#4fc3f7\";\n private lineWidth = 1;\n private ring: RingBuffer;\n\n constructor(id: string) {\n this.id = id;\n this.ring = new RingBuffer(2048, 2);\n }\n\n setConfig(config: unknown): void {\n const c = config as LineChartConfig;\n if (c.color !== undefined) this.color = c.color;\n if (c.lineWidth !== undefined) this.lineWidth = c.lineWidth;\n if (c.capacity !== undefined && c.capacity !== this.ring.capacity) {\n this.ring = new RingBuffer(c.capacity, 2);\n }\n }\n\n setData(buffer: ArrayBuffer, length: number, viewport: Viewport): void {\n if (length < 2) return;\n const arr = new Float32Array(buffer, 0, length);\n this.ring.pushMany(arr);\n // The newest timestamp in this batch sits at index (length - 2).\n // Advance the shared latestT so axis-grid time mode can follow.\n const t = arr[length - 2];\n if (t > viewport.latestT) viewport.latestT = t;\n }\n\n resize(_viewport: Viewport): void {}\n\n /**\n * Pre-draw pass: compute the visible-window min/max of y values in this\n * layer's ring buffer and merge them into `viewport.observedYMin/Max`.\n * AxisGridLayer with `yMode: \"auto\"` reads the aggregate in draw.\n *\n * `viewport.bounds.xMin` was already written by AxisGridLayer.scan (which\n * runs earlier in insertion order), so we can filter stale samples here.\n */\n scan(viewport: Viewport): void {\n if (this.ring.length === 0) return;\n const xMin = viewport.bounds.xMin;\n let localMin = viewport.observedYMin;\n let localMax = viewport.observedYMax;\n this.ring.forEach((data, off) => {\n const t = data[off];\n if (t < xMin) return;\n const y = data[off + 1];\n if (y < localMin) localMin = y;\n if (y > localMax) localMax = y;\n });\n viewport.observedYMin = localMin;\n viewport.observedYMax = localMax;\n }\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n if (this.ring.length < 2) return;\n\n ctx.strokeStyle = this.color;\n ctx.lineWidth = this.lineWidth;\n ctx.beginPath();\n\n // Sample filter: skip records older than the current x-window. Combined\n // with axis-grid time mode, this lets consumers \"select a window\" by\n // changing `timeWindowMs` and have the chart both retarget AND drop\n // old samples from the drawn path in one go.\n const xMin = viewport.bounds.xMin;\n\n let first = true;\n this.ring.forEach((data, off) => {\n const t = data[off];\n if (t < xMin) return;\n const px = viewport.xToPx(t);\n const py = viewport.yToPx(data[off + 1]);\n if (first) {\n ctx.moveTo(px, py);\n first = false;\n } else {\n ctx.lineTo(px, py);\n }\n });\n ctx.stroke();\n }\n\n dispose(): void {\n this.ring.clear();\n }\n}\n","import type { Layer } from \"../../../shared/model/layer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport interface LineChartStaticConfig {\n color?: string;\n lineWidth?: number;\n /**\n * Data layout:\n * - \"xy\" (default): interleaved [x,y,x,y,...] stride=2\n * - \"y\": [y0,y1,...] with implicit x = linear sweep across `viewport.bounds.x`\n */\n layout?: \"xy\" | \"y\";\n}\n\n/**\n * One-shot xy line chart. Replaces the entire series on every `setData`.\n * Use this for pre-computed plots, snapshot visualizations, or any dataset\n * whose x-axis is not a time stream. For streaming time-series data, use\n * `LineChartLayer` (kind \"line\") instead.\n */\nexport class LineChartStaticLayer implements Layer {\n readonly id: string;\n private color = \"#4fc3f7\";\n private lineWidth = 1;\n private layout: \"xy\" | \"y\" = \"xy\";\n private data: Float32Array | null = null;\n private length = 0;\n\n constructor(id: string) {\n this.id = id;\n }\n\n setConfig(config: unknown): void {\n const c = config as LineChartStaticConfig;\n if (c.color !== undefined) this.color = c.color;\n if (c.lineWidth !== undefined) this.lineWidth = c.lineWidth;\n if (c.layout !== undefined) this.layout = c.layout;\n }\n\n setData(buffer: ArrayBuffer, length: number, _viewport: Viewport): void {\n this.data = new Float32Array(buffer, 0, length);\n this.length = length;\n }\n\n resize(_viewport: Viewport): void {}\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n const data = this.data;\n if (!data || this.length < 2) return;\n\n ctx.strokeStyle = this.color;\n ctx.lineWidth = this.lineWidth;\n ctx.beginPath();\n\n if (this.layout === \"xy\") {\n const n = this.length >> 1;\n if (n < 2) return;\n ctx.moveTo(viewport.xToPx(data[0]), viewport.yToPx(data[1]));\n for (let i = 1; i < n; i++) {\n const j = i * 2;\n ctx.lineTo(viewport.xToPx(data[j]), viewport.yToPx(data[j + 1]));\n }\n } else {\n const n = this.length;\n const xMin = viewport.bounds.xMin;\n const xMax = viewport.bounds.xMax;\n const step = (xMax - xMin) / Math.max(1, n - 1);\n ctx.moveTo(viewport.xToPx(xMin), viewport.yToPx(data[0]));\n for (let i = 1; i < n; i++) {\n ctx.lineTo(viewport.xToPx(xMin + i * step), viewport.yToPx(data[i]));\n }\n }\n ctx.stroke();\n }\n\n dispose(): void {\n this.data = null;\n }\n}\n","/**\n * rAF-based render scheduler. Only calls `tick` on frames where dirty is set.\n * Uses the worker-global requestAnimationFrame when available; otherwise falls\n * back to setTimeout(16ms).\n */\nexport class Scheduler {\n private dirty = false;\n private running = false;\n private raf: number | null = null;\n private readonly tick: () => void;\n\n constructor(tick: () => void) {\n this.tick = tick;\n }\n\n start() {\n if (this.running) return;\n this.running = true;\n this.loop();\n }\n\n stop() {\n this.running = false;\n if (this.raf != null) {\n if (typeof cancelAnimationFrame !== \"undefined\") {\n cancelAnimationFrame(this.raf);\n } else {\n clearTimeout(this.raf);\n }\n this.raf = null;\n }\n }\n\n markDirty() {\n this.dirty = true;\n }\n\n private loop = () => {\n if (!this.running) return;\n if (this.dirty) {\n this.dirty = false;\n this.tick();\n }\n if (typeof requestAnimationFrame !== \"undefined\") {\n this.raf = requestAnimationFrame(this.loop);\n } else {\n this.raf = setTimeout(this.loop, 16) as unknown as number;\n }\n };\n}\n","export interface Bounds {\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n}\n\nexport class Viewport {\n widthPx = 0;\n heightPx = 0;\n dpr = 1;\n\n bounds: Bounds = { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };\n\n /**\n * Most recent data timestamp (ms, host-relative) seen across all streaming\n * layers. Streaming `LineChartLayer` updates this on setData; `AxisGridLayer`\n * in time mode uses it to compute a sliding window.\n */\n latestT = 0;\n\n /**\n * Per-frame aggregate of observed y values across all data layers that\n * currently overlap the visible time window. `AxisGridLayer` in\n * `yMode: \"auto\"` reads these in draw to compute bounds.yMin/yMax.\n *\n * Initialised to +/-Infinity by `beginScan()` at the top of every frame,\n * then layers merge their visible-window min/max in via `scan()`.\n */\n observedYMin = Number.POSITIVE_INFINITY;\n observedYMax = Number.NEGATIVE_INFINITY;\n\n setSize(width: number, height: number, dpr: number) {\n this.widthPx = width;\n this.heightPx = height;\n this.dpr = dpr;\n }\n\n setBounds(b: Bounds) {\n this.bounds = b;\n }\n\n /** Called by Engine at the start of each render frame before scan pass. */\n beginScan(): void {\n this.observedYMin = Number.POSITIVE_INFINITY;\n this.observedYMax = Number.NEGATIVE_INFINITY;\n }\n\n xToPx(x: number): number {\n const { xMin, xMax } = this.bounds;\n return ((x - xMin) / (xMax - xMin)) * this.widthPx;\n }\n\n yToPx(y: number): number {\n const { yMin, yMax } = this.bounds;\n return this.heightPx - ((y - yMin) / (yMax - yMin)) * this.heightPx;\n }\n}\n","import { AxisGridLayer } from \"../../../entities/axis-grid-layer\";\nimport { LayerStack } from \"../../../entities/layer-stack\";\nimport { LidarScatterLayer } from \"../../../entities/lidar-scatter-layer\";\nimport { LineChartLayer } from \"../../../entities/line-chart-layer\";\nimport { LineChartStaticLayer } from \"../../../entities/line-chart-static-layer\";\nimport type { Layer } from \"../../../shared/model/layer\";\nimport { Scheduler } from \"../../../shared/model/scheduler\";\nimport { Viewport } from \"../../../shared/model/viewport\";\nimport type { HostMsg, LayerKind } from \"../../../shared/protocol\";\nimport { Op } from \"../../../shared/protocol\";\n\nfunction createLayer(id: string, kind: LayerKind): Layer {\n switch (kind) {\n case \"line\":\n return new LineChartLayer(id);\n case \"line-static\":\n return new LineChartStaticLayer(id);\n case \"lidar\":\n return new LidarScatterLayer(id);\n case \"axis-grid\":\n return new AxisGridLayer(id);\n }\n}\n\n/**\n * Worker-side engine. Owns the OffscreenCanvas, layer stack, viewport,\n * and render scheduler. All state lives here; main thread just pushes messages.\n */\nexport class Engine {\n private canvas: OffscreenCanvas | null = null;\n private ctx: OffscreenCanvasRenderingContext2D | null = null;\n private readonly viewport = new Viewport();\n private readonly stack = new LayerStack();\n private readonly scheduler: Scheduler;\n private bgColor = \"#0b0d12\";\n\n constructor() {\n this.scheduler = new Scheduler(() => this.render());\n }\n\n dispatch(msg: HostMsg): void {\n switch (msg.op) {\n case Op.INIT:\n this.init(msg.canvas, msg.width, msg.height, msg.dpr);\n break;\n case Op.RESIZE:\n this.resize(msg.width, msg.height, msg.dpr);\n break;\n case Op.ADD_LAYER: {\n const layer = createLayer(msg.id, msg.kind);\n if (msg.config !== undefined) layer.setConfig(msg.config);\n layer.resize(this.viewport);\n this.stack.add(layer);\n this.scheduler.markDirty();\n break;\n }\n case Op.REMOVE_LAYER:\n this.stack.remove(msg.id);\n this.scheduler.markDirty();\n break;\n case Op.CONFIG: {\n const layer = this.stack.get(msg.id);\n if (layer) {\n layer.setConfig(msg.config);\n this.scheduler.markDirty();\n }\n break;\n }\n case Op.DATA: {\n const layer = this.stack.get(msg.id);\n if (layer) {\n layer.setData(msg.buffer, msg.length, this.viewport);\n this.scheduler.markDirty();\n }\n break;\n }\n case Op.DISPOSE:\n this.dispose();\n break;\n }\n }\n\n private init(canvas: OffscreenCanvas, width: number, height: number, dpr: number) {\n this.canvas = canvas;\n this.ctx = canvas.getContext(\"2d\");\n this.resize(width, height, dpr);\n this.scheduler.start();\n this.scheduler.markDirty();\n }\n\n private resize(width: number, height: number, dpr: number) {\n if (!this.canvas) return;\n this.canvas.width = Math.max(1, Math.round(width * dpr));\n this.canvas.height = Math.max(1, Math.round(height * dpr));\n this.viewport.setSize(width, height, dpr);\n this.stack.resizeAll(this.viewport);\n this.scheduler.markDirty();\n }\n\n private render() {\n const ctx = this.ctx;\n if (!ctx || !this.canvas) return;\n // 2-pass: scan (orchestration: time window, observed y, bounds) then\n // draw. AxisGridLayer.scan writes bounds; LineChartLayer.scan reads\n // bounds and publishes observed y extents; AxisGridLayer.draw finishes\n // the y-auto computation using those extents.\n this.viewport.beginScan();\n this.stack.scanAll(this.viewport);\n const { dpr } = this.viewport;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.fillStyle = this.bgColor;\n ctx.fillRect(0, 0, this.viewport.widthPx, this.viewport.heightPx);\n this.stack.drawAll(ctx, this.viewport);\n }\n\n private dispose() {\n this.scheduler.stop();\n this.stack.disposeAll();\n this.canvas = null;\n this.ctx = null;\n }\n}\n","import { Engine } from \"../../features/engine\";\nimport type { HostMsg } from \"../../shared/protocol\";\n\nconst engine = new Engine();\n\nself.onmessage = (e: MessageEvent<HostMsg>) => {\n try {\n engine.dispatch(e.data);\n } catch (err) {\n console.error(\"[fluxion-worker] dispatch error:\", err);\n }\n};\n\nself.addEventListener(\"error\", (e) => {\n console.error(\"[fluxion-worker] uncaught error:\", e.message ?? e);\n});\n\nself.addEventListener(\"messageerror\", (e) => {\n console.error(\"[fluxion-worker] message deserialization failed:\", e);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/shared/lib/math.ts","../src/entities/axis-grid-layer/model/axis-grid-layer.ts","../src/entities/layer-stack/model/layer-stack.ts","../src/shared/lib/color.ts","../src/entities/lidar-scatter-layer/model/lidar-scatter-layer.ts","../src/shared/model/ring-buffer.ts","../src/entities/line-chart-layer/model/line-chart-layer.ts","../src/entities/line-chart-static-layer/model/line-chart-static-layer.ts","../src/shared/model/scheduler.ts","../src/shared/model/viewport.ts","../src/features/engine/model/engine.ts","../src/app/worker/fluxion-worker.ts"],"sourcesContent":["/**\n * Pick an aesthetically pleasing step for an axis covering [min, max] with\n * ~`targetTicks` divisions. Based on the standard nice-number heuristic.\n */\nexport function niceStep(range: number, targetTicks: number): number {\n const rough = range / Math.max(1, targetTicks);\n const pow10 = Math.pow(10, Math.floor(Math.log10(rough)));\n const norm = rough / pow10;\n let nice: number;\n if (norm < 1.5) nice = 1;\n else if (norm < 3) nice = 2;\n else if (norm < 7) nice = 5;\n else nice = 10;\n return nice * pow10;\n}\n\nexport function niceTicks(min: number, max: number, targetTicks = 6): number[] {\n if (!isFinite(min) || !isFinite(max) || max <= min) return [];\n const step = niceStep(max - min, targetTicks);\n const start = Math.ceil(min / step) * step;\n const out: number[] = [];\n for (let v = start; v <= max + step * 1e-6; v += step) {\n out.push(Number(v.toFixed(12)));\n }\n return out;\n}\n","import { niceTicks } from \"../../../shared/lib/math\";\nimport { formatClock } from \"../../../shared/lib/time-format\";\nimport type { Layer } from \"../../../shared/model/layer\";\nimport type { Bounds, Viewport } from \"../../../shared/model/viewport\";\n\nexport interface AxisGridConfig {\n /** Fixed x-range. Used when `xMode` is \"fixed\" (default). */\n xRange?: [number, number];\n yRange?: [number, number];\n gridColor?: string;\n axisColor?: string;\n labelColor?: string;\n font?: string;\n targetTicks?: number;\n /** If true (default), writes this layer's bounds into `viewport` so data layers share them. */\n applyToViewport?: boolean;\n /**\n * \"fixed\": xRange is literal world units (default).\n * \"time\": bounds follow the streaming `viewport.latestT` as a trailing\n * sliding window `[latestT - timeWindowMs, latestT]`. yRange is still fixed.\n */\n xMode?: \"fixed\" | \"time\";\n /** Width of the sliding window in ms when xMode=\"time\". Default 5000. */\n timeWindowMs?: number;\n /**\n * Absolute wall-clock epoch (ms) corresponding to data timestamp `0`. When\n * set together with `xMode: \"time\"`, tick labels render as wall clock\n * instead of elapsed seconds. Typically set once at host creation:\n * `timeOrigin: Date.now()` on the main thread.\n */\n timeOrigin?: number;\n /**\n * Clock-pattern string used to render x tick labels when `xMode: \"time\"`\n * AND `timeOrigin` is set. Default `\"HH:mm:ss\"`. Supported tokens:\n * `HH / H / mm / m / ss / s / SSS / S`. Anything else is a literal.\n *\n * Ignored when `timeOrigin` is not provided — elapsed-seconds fallback\n * (`\"X.Xs\"`) is used instead.\n */\n xTickFormat?: string;\n\n // ─── y scaling ────────────────────────────────────────────\n /**\n * \"fixed\" (default): use configured `yRange`.\n * \"auto\": data-driven. Reads `viewport.observedYMin/Max` during draw,\n * applies padding and clamps, updates `bounds.yMin/yMax`. Requires at\n * least one data layer (e.g. `LineChartLayer`) in the stack to publish\n * observations via its `scan()` pass.\n */\n yMode?: \"fixed\" | \"auto\";\n /** Padding ratio applied above/below the observed range. Default 0.1 (10%). */\n yAutoPadding?: number;\n /** Absolute lower clamp after padding. */\n yAutoMin?: number;\n /** Absolute upper clamp after padding. */\n yAutoMax?: number;\n\n // ─── Visual toggles (all default true) ────────────────────\n /** Show vertical grid lines at x ticks. */\n showXGrid?: boolean;\n /** Show horizontal grid lines at y ticks. */\n showYGrid?: boolean;\n /** Show the x=0 / y=0 axis lines when 0 is inside the range. */\n showAxes?: boolean;\n /** Show tick labels along the x axis. */\n showXLabels?: boolean;\n /** Show tick labels along the y axis. */\n showYLabels?: boolean;\n}\n\n/**\n * Owns the viewport bounds orchestration for a chart: x window (fixed or\n * time-sliding), y range (fixed or data-driven auto), and renders the\n * visible grid/axes/labels on top.\n *\n * Orchestration (scan + bounds computation) runs independently of the\n * visual toggles — you can turn every `show*` off and still use this layer\n * purely as a controller. LayerStack insertion order matters: add this\n * before any data layer so the bounds are written before they're read.\n *\n * v0.3 limitation: single-axis only. `observedYMin/Max` live on `Viewport`,\n * so a second `AxisGridLayer` in the same stack would bleed observations.\n */\nexport class AxisGridLayer implements Layer {\n readonly id: string;\n private gridColor = \"rgba(255,255,255,0.08)\";\n private axisColor = \"rgba(255,255,255,0.4)\";\n private labelColor = \"rgba(255,255,255,0.7)\";\n private font = \"10px sans-serif\";\n private targetTicks = 6;\n private bounds: Bounds = { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };\n private applyToViewport = true;\n private xMode: \"fixed\" | \"time\" = \"fixed\";\n private timeWindowMs = 5000;\n private timeOrigin: number | null = null;\n private xTickFormat = \"HH:mm:ss\";\n private yMode: \"fixed\" | \"auto\" = \"fixed\";\n private yAutoPadding = 0.1;\n private yAutoMin: number | undefined;\n private yAutoMax: number | undefined;\n private showXGrid = true;\n private showYGrid = true;\n private showAxes = true;\n private showXLabels = true;\n private showYLabels = true;\n\n constructor(id: string) {\n this.id = id;\n }\n\n setConfig(config: unknown): void {\n const c = config as AxisGridConfig;\n if (c.xRange) {\n this.bounds.xMin = c.xRange[0];\n this.bounds.xMax = c.xRange[1];\n }\n if (c.yRange) {\n this.bounds.yMin = c.yRange[0];\n this.bounds.yMax = c.yRange[1];\n }\n if (c.gridColor) this.gridColor = c.gridColor;\n if (c.axisColor) this.axisColor = c.axisColor;\n if (c.labelColor) this.labelColor = c.labelColor;\n if (c.font) this.font = c.font;\n if (c.targetTicks) this.targetTicks = c.targetTicks;\n if (c.applyToViewport !== undefined) this.applyToViewport = c.applyToViewport;\n if (c.xMode !== undefined) this.xMode = c.xMode;\n if (c.timeWindowMs !== undefined) this.timeWindowMs = c.timeWindowMs;\n if (c.timeOrigin !== undefined) this.timeOrigin = c.timeOrigin;\n if (c.xTickFormat !== undefined) this.xTickFormat = c.xTickFormat;\n if (c.yMode !== undefined) this.yMode = c.yMode;\n if (c.yAutoPadding !== undefined) this.yAutoPadding = c.yAutoPadding;\n if (c.yAutoMin !== undefined) this.yAutoMin = c.yAutoMin;\n if (c.yAutoMax !== undefined) this.yAutoMax = c.yAutoMax;\n if (c.showXGrid !== undefined) this.showXGrid = c.showXGrid;\n if (c.showYGrid !== undefined) this.showYGrid = c.showYGrid;\n if (c.showAxes !== undefined) this.showAxes = c.showAxes;\n if (c.showXLabels !== undefined) this.showXLabels = c.showXLabels;\n if (c.showYLabels !== undefined) this.showYLabels = c.showYLabels;\n }\n\n setData(_buffer: ArrayBuffer, _length: number, _viewport: Viewport): void {}\n\n resize(_viewport: Viewport): void {}\n\n /**\n * Orchestration pass: establish x bounds so data layers' `scan` can filter\n * visible samples correctly. yMode:\"auto\" is finalized in `draw` after all\n * line layers have published their observations.\n */\n scan(viewport: Viewport): void {\n if (this.xMode === \"time\") {\n const latestT = viewport.latestT;\n this.bounds.xMin = latestT - this.timeWindowMs;\n this.bounds.xMax = latestT;\n }\n if (this.applyToViewport) {\n viewport.setBounds(this.bounds);\n }\n }\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n // Finalize y-auto bounds. Runs after all line-layer scans have\n // published their observed extents into the viewport.\n if (this.yMode === \"auto\") {\n let yMin = viewport.observedYMin;\n let yMax = viewport.observedYMax;\n if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n // No data yet — fall back to configured yRange. If that is also\n // degenerate (defaults [-1, 1] from construction), use [-1, 1].\n yMin = this.bounds.yMin;\n yMax = this.bounds.yMax;\n if (yMin === yMax) {\n yMin = -1;\n yMax = 1;\n }\n } else if (yMin === yMax) {\n // Flat line — expand so stroke has vertical room.\n yMin -= 0.5;\n yMax += 0.5;\n } else {\n const pad = (yMax - yMin) * this.yAutoPadding;\n yMin -= pad;\n yMax += pad;\n }\n if (this.yAutoMin !== undefined && yMin < this.yAutoMin) yMin = this.yAutoMin;\n if (this.yAutoMax !== undefined && yMax > this.yAutoMax) yMax = this.yAutoMax;\n this.bounds.yMin = yMin;\n this.bounds.yMax = yMax;\n if (this.applyToViewport) viewport.setBounds(this.bounds);\n }\n\n const { widthPx: w, heightPx: h } = viewport;\n const xTicks = niceTicks(this.bounds.xMin, this.bounds.xMax, this.targetTicks);\n const yTicks = niceTicks(this.bounds.yMin, this.bounds.yMax, this.targetTicks);\n\n // ── Grid lines ──\n if (this.showXGrid || this.showYGrid) {\n ctx.strokeStyle = this.gridColor;\n ctx.lineWidth = 1;\n ctx.beginPath();\n if (this.showXGrid) {\n for (let i = 0; i < xTicks.length; i++) {\n const x = Math.round(viewport.xToPx(xTicks[i])) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n }\n if (this.showYGrid) {\n for (let i = 0; i < yTicks.length; i++) {\n const y = Math.round(viewport.yToPx(yTicks[i])) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n }\n ctx.stroke();\n }\n\n // ── Zero axes ──\n if (this.showAxes) {\n ctx.strokeStyle = this.axisColor;\n ctx.beginPath();\n if (this.bounds.xMin < 0 && this.bounds.xMax > 0) {\n const x0 = Math.round(viewport.xToPx(0)) + 0.5;\n ctx.moveTo(x0, 0);\n ctx.lineTo(x0, h);\n }\n if (this.bounds.yMin < 0 && this.bounds.yMax > 0) {\n const y0 = Math.round(viewport.yToPx(0)) + 0.5;\n ctx.moveTo(0, y0);\n ctx.lineTo(w, y0);\n }\n ctx.stroke();\n }\n\n // ── Labels ──\n if (this.showXLabels || this.showYLabels) {\n ctx.fillStyle = this.labelColor;\n ctx.font = this.font;\n if (this.showXLabels) {\n ctx.textBaseline = \"top\";\n for (let i = 0; i < xTicks.length; i++) {\n const x = viewport.xToPx(xTicks[i]);\n ctx.fillText(\n formatTick(xTicks[i], this.xMode, this.timeOrigin, this.xTickFormat),\n x + 2,\n h - 12,\n );\n }\n }\n if (this.showYLabels) {\n ctx.textBaseline = \"middle\";\n for (let i = 0; i < yTicks.length; i++) {\n const y = viewport.yToPx(yTicks[i]);\n ctx.fillText(String(yTicks[i]), 2, y - 6);\n }\n }\n }\n }\n\n dispose(): void {}\n}\n\nfunction formatTick(\n value: number,\n mode: \"fixed\" | \"time\",\n timeOrigin: number | null,\n pattern: string,\n): string {\n if (mode === \"time\") {\n if (timeOrigin != null) {\n return formatClock(timeOrigin + value, pattern);\n }\n // Elapsed-only fallback (timeOrigin missing).\n const s = value / 1000;\n return `${s.toFixed(1)}s`;\n }\n return String(value);\n}\n","import type { Layer } from \"../../../shared/model/layer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport class LayerStack {\n private layers: Layer[] = [];\n private byId = new Map<string, Layer>();\n\n add(layer: Layer): void {\n this.layers.push(layer);\n this.byId.set(layer.id, layer);\n }\n\n remove(id: string): void {\n const layer = this.byId.get(id);\n if (!layer) return;\n this.byId.delete(id);\n const i = this.layers.indexOf(layer);\n if (i >= 0) this.layers.splice(i, 1);\n layer.dispose();\n }\n\n get(id: string): Layer | undefined {\n return this.byId.get(id);\n }\n\n resizeAll(viewport: Viewport): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].resize(viewport);\n }\n }\n\n /**\n * Pre-draw pass. Layers that implement `scan` update shared viewport state\n * (bounds, observed extents) here so downstream layers' `draw` sees the\n * correct values. Iterates in insertion order so axis-grid (added first)\n * writes bounds before data layers read them.\n */\n scanAll(viewport: Viewport): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].scan?.(viewport);\n }\n }\n\n drawAll(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].draw(ctx, viewport);\n }\n }\n\n disposeAll(): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].dispose();\n }\n this.layers.length = 0;\n this.byId.clear();\n }\n}\n","/**\n * Intensity (0..1) → viridis-ish RGB LUT.\n * Precomputed 256-entry lookup to avoid per-point math during rendering.\n */\nconst LUT_SIZE = 256;\nconst lutR = new Uint8ClampedArray(LUT_SIZE);\nconst lutG = new Uint8ClampedArray(LUT_SIZE);\nconst lutB = new Uint8ClampedArray(LUT_SIZE);\n\nfunction ramp(t: number): [number, number, number] {\n const stops: [number, [number, number, number]][] = [\n [0.0, [13, 8, 135]],\n [0.25, [84, 2, 163]],\n [0.5, [156, 23, 158]],\n [0.75, [225, 100, 98]],\n [1.0, [240, 249, 33]],\n ];\n for (let i = 0; i < stops.length - 1; i++) {\n const [t0, c0] = stops[i];\n const [t1, c1] = stops[i + 1];\n if (t <= t1) {\n const k = (t - t0) / (t1 - t0);\n return [\n c0[0] + (c1[0] - c0[0]) * k,\n c0[1] + (c1[1] - c0[1]) * k,\n c0[2] + (c1[2] - c0[2]) * k,\n ];\n }\n }\n return stops[stops.length - 1][1];\n}\n\nfor (let i = 0; i < LUT_SIZE; i++) {\n const [r, g, b] = ramp(i / (LUT_SIZE - 1));\n lutR[i] = r;\n lutG[i] = g;\n lutB[i] = b;\n}\n\nexport interface IntensityLUT {\n readonly r: Uint8ClampedArray;\n readonly g: Uint8ClampedArray;\n readonly b: Uint8ClampedArray;\n readonly size: number;\n}\n\nconst SINGLETON: IntensityLUT = Object.freeze({\n r: lutR,\n g: lutG,\n b: lutB,\n size: LUT_SIZE,\n});\n\nexport function intensityLUT(): IntensityLUT {\n return SINGLETON;\n}\n","import { intensityLUT } from \"../../../shared/lib/color\";\nimport type { Layer } from \"../../../shared/model/layer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport interface LidarScatterConfig {\n /** Floats per point. Default 4 (x,y,z,intensity). Minimum 2 (x,y). */\n stride?: number;\n /** Point size in pixels. */\n pointSize?: number;\n /** Max intensity value for normalization (0..1). Default 1.0. */\n intensityMax?: number;\n /** Solid color override. When set, intensity LUT is ignored. */\n color?: string;\n}\n\nconst LUT_BUCKETS = 256;\n\n/**\n * Renders a point cloud as a 2D top-down scatter.\n * Expects Float32Array with layout [x, y, z, intensity, ...] (stride configurable).\n *\n * Fast path for 10k+ points: counting-sort by intensity-LUT bucket so each\n * color bucket issues one `beginPath / rect×N / fill` cycle. This reduces\n * fillStyle state changes from O(N) to at most 256 per frame, and collapses\n * all per-point fillRect calls into a single path fill per color.\n *\n * Scratch buffers (`sortedX`, `sortedY`, `bucketCount`) live as layer fields\n * and grow by 25% when the point budget expands, so steady-state pushes\n * allocate zero memory.\n */\nexport class LidarScatterLayer implements Layer {\n readonly id: string;\n private stride = 4;\n private pointSize = 2;\n private intensityMax = 1;\n private solidColor: [number, number, number] | null = null;\n private data: Float32Array | null = null;\n private length = 0;\n\n // Scratch buffers for counting-sort batching.\n private sortedX: Float32Array = new Float32Array(0);\n private sortedY: Float32Array = new Float32Array(0);\n private bucketCount: Uint32Array = new Uint32Array(LUT_BUCKETS);\n private bucketOffset: Uint32Array = new Uint32Array(LUT_BUCKETS);\n private scratchCapacity = 0;\n\n constructor(id: string) {\n this.id = id;\n }\n\n setConfig(config: unknown): void {\n const c = config as LidarScatterConfig;\n if (c.stride !== undefined) this.stride = Math.max(2, c.stride | 0);\n if (c.pointSize !== undefined) this.pointSize = Math.max(1, c.pointSize);\n if (c.intensityMax !== undefined) this.intensityMax = c.intensityMax;\n if (c.color !== undefined) this.solidColor = parseColor(c.color);\n }\n\n setData(buffer: ArrayBuffer, length: number, _viewport: Viewport): void {\n this.data = new Float32Array(buffer, 0, length);\n this.length = length;\n }\n\n resize(_viewport: Viewport): void {}\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n const data = this.data;\n if (!data || this.length < this.stride) return;\n\n const stride = this.stride;\n const size = this.pointSize;\n const half = size / 2;\n const count = (this.length / stride) | 0;\n\n if (this.solidColor) {\n this.drawSolid(ctx, viewport, data, stride, count, size, half);\n return;\n }\n\n this.ensureScratch(count);\n this.drawBucketed(ctx, viewport, data, stride, count, size, half);\n }\n\n private drawSolid(\n ctx: OffscreenCanvasRenderingContext2D,\n viewport: Viewport,\n data: Float32Array,\n stride: number,\n count: number,\n size: number,\n half: number,\n ): void {\n const [r, g, b] = this.solidColor as [number, number, number];\n ctx.fillStyle = `rgb(${r},${g},${b})`;\n ctx.beginPath();\n for (let i = 0; i < count; i++) {\n const o = i * stride;\n const px = viewport.xToPx(data[o]);\n const py = viewport.yToPx(data[o + 1]);\n ctx.rect(px - half, py - half, size, size);\n }\n ctx.fill();\n }\n\n private drawBucketed(\n ctx: OffscreenCanvasRenderingContext2D,\n viewport: Viewport,\n data: Float32Array,\n stride: number,\n count: number,\n size: number,\n half: number,\n ): void {\n const lut = intensityLUT();\n const invMax = 1 / (this.intensityMax || 1);\n const sortedX = this.sortedX;\n const sortedY = this.sortedY;\n const bucketCount = this.bucketCount;\n const bucketOffset = this.bucketOffset;\n\n // Reset bucket histograms.\n bucketCount.fill(0);\n\n // Pass 1: compute pixel coords + bucket index, fill histogram.\n // Stash bucket index in a parallel-ish way: we'll recompute in pass 2\n // to avoid a third scratch buffer. The cost is one extra intensity read.\n for (let i = 0; i < count; i++) {\n const o = i * stride;\n const intensity = stride >= 4 ? data[o + 3] : 1;\n let idx = (intensity * invMax * (LUT_BUCKETS - 1)) | 0;\n if (idx < 0) idx = 0;\n else if (idx >= LUT_BUCKETS) idx = LUT_BUCKETS - 1;\n bucketCount[idx]++;\n }\n\n // Prefix-sum: bucketOffset[b] = sum of bucketCount[0..b-1]\n let acc = 0;\n for (let b = 0; b < LUT_BUCKETS; b++) {\n bucketOffset[b] = acc;\n acc += bucketCount[b];\n }\n\n // Pass 2: scatter points into bucket-ordered scratch. We reuse\n // `bucketCount` as a write cursor by decrementing it back to 0.\n // Snapshot offsets first, then advance cursors from each offset.\n // Simpler: use bucketCount as an independent write cursor initialised to 0.\n const writeCursor = bucketCount; // reused, currently holds histogram\n // Restore to 0 now that bucketOffset has captured the prefix sum.\n for (let b = 0; b < LUT_BUCKETS; b++) writeCursor[b] = 0;\n\n for (let i = 0; i < count; i++) {\n const o = i * stride;\n const intensity = stride >= 4 ? data[o + 3] : 1;\n let idx = (intensity * invMax * (LUT_BUCKETS - 1)) | 0;\n if (idx < 0) idx = 0;\n else if (idx >= LUT_BUCKETS) idx = LUT_BUCKETS - 1;\n const pos = bucketOffset[idx] + writeCursor[idx];\n writeCursor[idx]++;\n sortedX[pos] = viewport.xToPx(data[o]);\n sortedY[pos] = viewport.yToPx(data[o + 1]);\n }\n\n // Pass 3: emit one path per non-empty bucket.\n for (let b = 0; b < LUT_BUCKETS; b++) {\n const n = writeCursor[b];\n if (n === 0) continue;\n ctx.fillStyle = `rgb(${lut.r[b]},${lut.g[b]},${lut.b[b]})`;\n ctx.beginPath();\n const start = bucketOffset[b];\n const end = start + n;\n for (let i = start; i < end; i++) {\n ctx.rect(sortedX[i] - half, sortedY[i] - half, size, size);\n }\n ctx.fill();\n }\n }\n\n private ensureScratch(count: number): void {\n if (count <= this.scratchCapacity) return;\n const next = Math.max(count, Math.ceil(this.scratchCapacity * 1.25), 1024);\n this.sortedX = new Float32Array(next);\n this.sortedY = new Float32Array(next);\n this.scratchCapacity = next;\n }\n\n dispose(): void {\n this.data = null;\n this.sortedX = new Float32Array(0);\n this.sortedY = new Float32Array(0);\n this.scratchCapacity = 0;\n }\n}\n\nfunction parseColor(css: string): [number, number, number] {\n if (css.startsWith(\"#\")) {\n const hex = css.slice(1);\n if (hex.length === 3) {\n return [\n parseInt(hex[0] + hex[0], 16),\n parseInt(hex[1] + hex[1], 16),\n parseInt(hex[2] + hex[2], 16),\n ];\n }\n if (hex.length === 6) {\n return [\n parseInt(hex.slice(0, 2), 16),\n parseInt(hex.slice(2, 4), 16),\n parseInt(hex.slice(4, 6), 16),\n ];\n }\n }\n return [255, 255, 255];\n}\n","/**\n * Fixed-capacity ring buffer over a Float32Array.\n * Stores interleaved records of `stride` floats each (e.g. [x,y] stride=2).\n * Zero-allocation push; draw iterates from tail to head in chronological order.\n */\nexport class RingBuffer {\n readonly stride: number;\n readonly capacity: number;\n readonly data: Float32Array;\n private head = 0;\n private count = 0;\n\n constructor(capacity: number, stride: number) {\n this.capacity = capacity;\n this.stride = stride;\n this.data = new Float32Array(capacity * stride);\n }\n\n get length(): number {\n return this.count;\n }\n\n push(record: ArrayLike<number>): void {\n const base = this.head * this.stride;\n for (let i = 0; i < this.stride; i++) {\n this.data[base + i] = record[i];\n }\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) this.count++;\n }\n\n pushMany(records: Float32Array): void {\n const recCount = records.length / this.stride;\n for (let r = 0; r < recCount; r++) {\n const base = this.head * this.stride;\n const src = r * this.stride;\n for (let i = 0; i < this.stride; i++) {\n this.data[base + i] = records[src + i];\n }\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) this.count++;\n }\n }\n\n forEach(fn: (data: Float32Array, offset: number, index: number) => void): void {\n const start = this.count < this.capacity ? 0 : this.head;\n for (let i = 0; i < this.count; i++) {\n const slot = (start + i) % this.capacity;\n fn(this.data, slot * this.stride, i);\n }\n }\n\n clear(): void {\n this.head = 0;\n this.count = 0;\n }\n}\n","import type { Layer } from \"../../../shared/model/layer\";\nimport { RingBuffer } from \"../../../shared/model/ring-buffer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport interface LineChartConfig {\n color?: string;\n lineWidth?: number;\n /** Ring buffer capacity (number of [t,y] samples retained). Default 2048. */\n capacity?: number;\n}\n\n/**\n * Streaming time-series line chart. Expects `Float32Array [t, y, t, y, ...]`\n * where `t` is host-relative milliseconds (monotonic). Each `setData` call\n * appends to an internal ring buffer, so draw cost is O(capacity), not O(total\n * samples pushed).\n *\n * On every append the layer advances `viewport.latestT` so the axis-grid in\n * `xMode: \"time\"` can compute a trailing sliding window.\n */\nexport class LineChartLayer implements Layer {\n readonly id: string;\n private color = \"#4fc3f7\";\n private lineWidth = 1;\n private ring: RingBuffer;\n\n constructor(id: string) {\n this.id = id;\n this.ring = new RingBuffer(2048, 2);\n }\n\n setConfig(config: unknown): void {\n const c = config as LineChartConfig;\n if (c.color !== undefined) this.color = c.color;\n if (c.lineWidth !== undefined) this.lineWidth = c.lineWidth;\n if (c.capacity !== undefined && c.capacity !== this.ring.capacity) {\n this.ring = new RingBuffer(c.capacity, 2);\n }\n }\n\n setData(buffer: ArrayBuffer, length: number, viewport: Viewport): void {\n if (length < 2) return;\n const arr = new Float32Array(buffer, 0, length);\n this.ring.pushMany(arr);\n // The newest timestamp in this batch sits at index (length - 2).\n // Advance the shared latestT so axis-grid time mode can follow.\n const t = arr[length - 2];\n if (t > viewport.latestT) viewport.latestT = t;\n }\n\n resize(_viewport: Viewport): void {}\n\n /**\n * Pre-draw pass: compute the visible-window min/max of y values in this\n * layer's ring buffer and merge them into `viewport.observedYMin/Max`.\n * AxisGridLayer with `yMode: \"auto\"` reads the aggregate in draw.\n *\n * `viewport.bounds.xMin` was already written by AxisGridLayer.scan (which\n * runs earlier in insertion order), so we can filter stale samples here.\n */\n scan(viewport: Viewport): void {\n if (this.ring.length === 0) return;\n const xMin = viewport.bounds.xMin;\n let localMin = viewport.observedYMin;\n let localMax = viewport.observedYMax;\n this.ring.forEach((data, off) => {\n const t = data[off];\n if (t < xMin) return;\n const y = data[off + 1];\n if (y < localMin) localMin = y;\n if (y > localMax) localMax = y;\n });\n viewport.observedYMin = localMin;\n viewport.observedYMax = localMax;\n }\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n if (this.ring.length < 2) return;\n\n ctx.strokeStyle = this.color;\n ctx.lineWidth = this.lineWidth;\n ctx.beginPath();\n\n // Sample filter: skip records older than the current x-window. Combined\n // with axis-grid time mode, this lets consumers \"select a window\" by\n // changing `timeWindowMs` and have the chart both retarget AND drop\n // old samples from the drawn path in one go.\n const xMin = viewport.bounds.xMin;\n\n let first = true;\n this.ring.forEach((data, off) => {\n const t = data[off];\n if (t < xMin) return;\n const px = viewport.xToPx(t);\n const py = viewport.yToPx(data[off + 1]);\n if (first) {\n ctx.moveTo(px, py);\n first = false;\n } else {\n ctx.lineTo(px, py);\n }\n });\n ctx.stroke();\n }\n\n dispose(): void {\n this.ring.clear();\n }\n}\n","import type { Layer } from \"../../../shared/model/layer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport interface LineChartStaticConfig {\n color?: string;\n lineWidth?: number;\n /**\n * Data layout:\n * - \"xy\" (default): interleaved [x,y,x,y,...] stride=2\n * - \"y\": [y0,y1,...] with implicit x = linear sweep across `viewport.bounds.x`\n */\n layout?: \"xy\" | \"y\";\n}\n\n/**\n * One-shot xy line chart. Replaces the entire series on every `setData`.\n * Use this for pre-computed plots, snapshot visualizations, or any dataset\n * whose x-axis is not a time stream. For streaming time-series data, use\n * `LineChartLayer` (kind \"line\") instead.\n */\nexport class LineChartStaticLayer implements Layer {\n readonly id: string;\n private color = \"#4fc3f7\";\n private lineWidth = 1;\n private layout: \"xy\" | \"y\" = \"xy\";\n private data: Float32Array | null = null;\n private length = 0;\n\n constructor(id: string) {\n this.id = id;\n }\n\n setConfig(config: unknown): void {\n const c = config as LineChartStaticConfig;\n if (c.color !== undefined) this.color = c.color;\n if (c.lineWidth !== undefined) this.lineWidth = c.lineWidth;\n if (c.layout !== undefined) this.layout = c.layout;\n }\n\n setData(buffer: ArrayBuffer, length: number, _viewport: Viewport): void {\n this.data = new Float32Array(buffer, 0, length);\n this.length = length;\n }\n\n resize(_viewport: Viewport): void {}\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n const data = this.data;\n if (!data || this.length < 2) return;\n\n ctx.strokeStyle = this.color;\n ctx.lineWidth = this.lineWidth;\n ctx.beginPath();\n\n if (this.layout === \"xy\") {\n const n = this.length >> 1;\n if (n < 2) return;\n ctx.moveTo(viewport.xToPx(data[0]), viewport.yToPx(data[1]));\n for (let i = 1; i < n; i++) {\n const j = i * 2;\n ctx.lineTo(viewport.xToPx(data[j]), viewport.yToPx(data[j + 1]));\n }\n } else {\n const n = this.length;\n const xMin = viewport.bounds.xMin;\n const xMax = viewport.bounds.xMax;\n const step = (xMax - xMin) / Math.max(1, n - 1);\n ctx.moveTo(viewport.xToPx(xMin), viewport.yToPx(data[0]));\n for (let i = 1; i < n; i++) {\n ctx.lineTo(viewport.xToPx(xMin + i * step), viewport.yToPx(data[i]));\n }\n }\n ctx.stroke();\n }\n\n dispose(): void {\n this.data = null;\n }\n}\n","/**\n * rAF-based render scheduler. Only calls `tick` on frames where dirty is set.\n * Uses the worker-global requestAnimationFrame when available; otherwise falls\n * back to setTimeout(16ms).\n */\nexport class Scheduler {\n private dirty = false;\n private running = false;\n private raf: number | null = null;\n private readonly tick: () => void;\n\n constructor(tick: () => void) {\n this.tick = tick;\n }\n\n start() {\n if (this.running) return;\n this.running = true;\n this.loop();\n }\n\n stop() {\n this.running = false;\n if (this.raf != null) {\n if (typeof cancelAnimationFrame !== \"undefined\") {\n cancelAnimationFrame(this.raf);\n } else {\n clearTimeout(this.raf);\n }\n this.raf = null;\n }\n }\n\n markDirty() {\n this.dirty = true;\n }\n\n private loop = () => {\n if (!this.running) return;\n if (this.dirty) {\n this.dirty = false;\n this.tick();\n }\n if (typeof requestAnimationFrame !== \"undefined\") {\n this.raf = requestAnimationFrame(this.loop);\n } else {\n this.raf = setTimeout(this.loop, 16) as unknown as number;\n }\n };\n}\n","export interface Bounds {\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n}\n\nexport class Viewport {\n widthPx = 0;\n heightPx = 0;\n dpr = 1;\n\n bounds: Bounds = { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };\n\n /**\n * Most recent data timestamp (ms, host-relative) seen across all streaming\n * layers. Streaming `LineChartLayer` updates this on setData; `AxisGridLayer`\n * in time mode uses it to compute a sliding window.\n */\n latestT = 0;\n\n /**\n * Per-frame aggregate of observed y values across all data layers that\n * currently overlap the visible time window. `AxisGridLayer` in\n * `yMode: \"auto\"` reads these in draw to compute bounds.yMin/yMax.\n *\n * Initialised to +/-Infinity by `beginScan()` at the top of every frame,\n * then layers merge their visible-window min/max in via `scan()`.\n */\n observedYMin = Number.POSITIVE_INFINITY;\n observedYMax = Number.NEGATIVE_INFINITY;\n\n setSize(width: number, height: number, dpr: number) {\n this.widthPx = width;\n this.heightPx = height;\n this.dpr = dpr;\n }\n\n setBounds(b: Bounds) {\n this.bounds = b;\n }\n\n /** Called by Engine at the start of each render frame before scan pass. */\n beginScan(): void {\n this.observedYMin = Number.POSITIVE_INFINITY;\n this.observedYMax = Number.NEGATIVE_INFINITY;\n }\n\n xToPx(x: number): number {\n const { xMin, xMax } = this.bounds;\n return ((x - xMin) / (xMax - xMin)) * this.widthPx;\n }\n\n yToPx(y: number): number {\n const { yMin, yMax } = this.bounds;\n return this.heightPx - ((y - yMin) / (yMax - yMin)) * this.heightPx;\n }\n}\n","import { AxisGridLayer } from \"../../../entities/axis-grid-layer\";\nimport { LayerStack } from \"../../../entities/layer-stack\";\nimport { LidarScatterLayer } from \"../../../entities/lidar-scatter-layer\";\nimport { LineChartLayer } from \"../../../entities/line-chart-layer\";\nimport { LineChartStaticLayer } from \"../../../entities/line-chart-static-layer\";\nimport type { Layer } from \"../../../shared/model/layer\";\nimport { Scheduler } from \"../../../shared/model/scheduler\";\nimport { Viewport } from \"../../../shared/model/viewport\";\nimport type { HostMsg, LayerKind } from \"../../../shared/protocol\";\nimport { Op } from \"../../../shared/protocol\";\n\nfunction createLayer(id: string, kind: LayerKind): Layer {\n switch (kind) {\n case \"line\":\n return new LineChartLayer(id);\n case \"line-static\":\n return new LineChartStaticLayer(id);\n case \"lidar\":\n return new LidarScatterLayer(id);\n case \"axis-grid\":\n return new AxisGridLayer(id);\n }\n}\n\n/**\n * Worker-side engine. Owns the OffscreenCanvas, layer stack, viewport,\n * and render scheduler. All state lives here; main thread just pushes messages.\n */\nexport class Engine {\n private canvas: OffscreenCanvas | null = null;\n private ctx: OffscreenCanvasRenderingContext2D | null = null;\n private readonly viewport = new Viewport();\n private readonly stack = new LayerStack();\n private readonly scheduler: Scheduler;\n private bgColor = \"#0b0d12\";\n\n constructor() {\n this.scheduler = new Scheduler(() => this.render());\n }\n\n dispatch(msg: HostMsg): void {\n switch (msg.op) {\n case Op.INIT:\n this.init(msg.canvas, msg.width, msg.height, msg.dpr, msg.bgColor);\n break;\n case Op.SET_BG_COLOR:\n this.bgColor = msg.color;\n this.scheduler.markDirty();\n break;\n case Op.RESIZE:\n this.resize(msg.width, msg.height, msg.dpr);\n break;\n case Op.ADD_LAYER: {\n const layer = createLayer(msg.id, msg.kind);\n if (msg.config !== undefined) layer.setConfig(msg.config);\n layer.resize(this.viewport);\n this.stack.add(layer);\n this.scheduler.markDirty();\n break;\n }\n case Op.REMOVE_LAYER:\n this.stack.remove(msg.id);\n this.scheduler.markDirty();\n break;\n case Op.CONFIG: {\n const layer = this.stack.get(msg.id);\n if (layer) {\n layer.setConfig(msg.config);\n this.scheduler.markDirty();\n }\n break;\n }\n case Op.DATA: {\n const layer = this.stack.get(msg.id);\n if (layer) {\n layer.setData(msg.buffer, msg.length, this.viewport);\n this.scheduler.markDirty();\n }\n break;\n }\n case Op.DISPOSE:\n this.dispose();\n break;\n }\n }\n\n private init(\n canvas: OffscreenCanvas,\n width: number,\n height: number,\n dpr: number,\n bgColor?: string,\n ) {\n this.canvas = canvas;\n this.ctx = canvas.getContext(\"2d\");\n if (bgColor !== undefined) this.bgColor = bgColor;\n this.resize(width, height, dpr);\n this.scheduler.start();\n this.scheduler.markDirty();\n }\n\n private resize(width: number, height: number, dpr: number) {\n if (!this.canvas) return;\n this.canvas.width = Math.max(1, Math.round(width * dpr));\n this.canvas.height = Math.max(1, Math.round(height * dpr));\n this.viewport.setSize(width, height, dpr);\n this.stack.resizeAll(this.viewport);\n this.scheduler.markDirty();\n }\n\n private render() {\n const ctx = this.ctx;\n if (!ctx || !this.canvas) return;\n // 2-pass: scan (orchestration: time window, observed y, bounds) then\n // draw. AxisGridLayer.scan writes bounds; LineChartLayer.scan reads\n // bounds and publishes observed y extents; AxisGridLayer.draw finishes\n // the y-auto computation using those extents.\n this.viewport.beginScan();\n this.stack.scanAll(this.viewport);\n const { dpr } = this.viewport;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.fillStyle = this.bgColor;\n ctx.fillRect(0, 0, this.viewport.widthPx, this.viewport.heightPx);\n this.stack.drawAll(ctx, this.viewport);\n }\n\n private dispose() {\n this.scheduler.stop();\n this.stack.disposeAll();\n this.canvas = null;\n this.ctx = null;\n }\n}\n","import { Engine } from \"../../features/engine\";\nimport type { HostMsg } from \"../../shared/protocol\";\n\nconst engine = new Engine();\n\nself.onmessage = (e: MessageEvent<HostMsg>) => {\n try {\n engine.dispatch(e.data);\n } catch (err) {\n console.error(\"[fluxion-worker] dispatch error:\", err);\n }\n};\n\nself.addEventListener(\"error\", (e) => {\n console.error(\"[fluxion-worker] uncaught error:\", e.message ?? e);\n});\n\nself.addEventListener(\"messageerror\", (e) => {\n console.error(\"[fluxion-worker] message deserialization failed:\", e);\n});\n"],"mappings":";;;;;;;;AAIO,SAAS,SAAS,OAAe,aAA6B;AACnE,QAAM,QAAQ,QAAQ,KAAK,IAAI,GAAG,WAAW;AAC7C,QAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,MAAM,KAAK,CAAC,CAAC;AACxD,QAAM,OAAO,QAAQ;AACrB,MAAI;AACJ,MAAI,OAAO,IAAK,QAAO;AAAA,WACd,OAAO,EAAG,QAAO;AAAA,WACjB,OAAO,EAAG,QAAO;AAAA,MACrB,QAAO;AACZ,SAAO,OAAO;AAChB;AAEO,SAAS,UAAU,KAAa,KAAa,cAAc,GAAa;AAC7E,MAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,GAAG,KAAK,OAAO,IAAK,QAAO,CAAC;AAC5D,QAAM,OAAO,SAAS,MAAM,KAAK,WAAW;AAC5C,QAAM,QAAQ,KAAK,KAAK,MAAM,IAAI,IAAI;AACtC,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,OAAO,KAAK,MAAM,OAAO,MAAM,KAAK,MAAM;AACrD,QAAI,KAAK,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;AAAA,EAChC;AACA,SAAO;AACT;;;AC0DO,IAAM,gBAAN,MAAqC;AAAA,EACjC;AAAA,EACD,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,OAAO;AAAA,EACP,cAAc;AAAA,EACd,SAAiB,EAAE,MAAM,IAAI,MAAM,GAAG,MAAM,IAAI,MAAM,EAAE;AAAA,EACxD,kBAAkB;AAAA,EAClB,QAA0B;AAAA,EAC1B,eAAe;AAAA,EACf,aAA4B;AAAA,EAC5B,cAAc;AAAA,EACd,QAA0B;AAAA,EAC1B,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,cAAc;AAAA,EAEtB,YAAY,IAAY;AACtB,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,UAAU,QAAuB;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,QAAQ;AACZ,WAAK,OAAO,OAAO,EAAE,OAAO,CAAC;AAC7B,WAAK,OAAO,OAAO,EAAE,OAAO,CAAC;AAAA,IAC/B;AACA,QAAI,EAAE,QAAQ;AACZ,WAAK,OAAO,OAAO,EAAE,OAAO,CAAC;AAC7B,WAAK,OAAO,OAAO,EAAE,OAAO,CAAC;AAAA,IAC/B;AACA,QAAI,EAAE,UAAW,MAAK,YAAY,EAAE;AACpC,QAAI,EAAE,UAAW,MAAK,YAAY,EAAE;AACpC,QAAI,EAAE,WAAY,MAAK,aAAa,EAAE;AACtC,QAAI,EAAE,KAAM,MAAK,OAAO,EAAE;AAC1B,QAAI,EAAE,YAAa,MAAK,cAAc,EAAE;AACxC,QAAI,EAAE,oBAAoB,OAAW,MAAK,kBAAkB,EAAE;AAC9D,QAAI,EAAE,UAAU,OAAW,MAAK,QAAQ,EAAE;AAC1C,QAAI,EAAE,iBAAiB,OAAW,MAAK,eAAe,EAAE;AACxD,QAAI,EAAE,eAAe,OAAW,MAAK,aAAa,EAAE;AACpD,QAAI,EAAE,gBAAgB,OAAW,MAAK,cAAc,EAAE;AACtD,QAAI,EAAE,UAAU,OAAW,MAAK,QAAQ,EAAE;AAC1C,QAAI,EAAE,iBAAiB,OAAW,MAAK,eAAe,EAAE;AACxD,QAAI,EAAE,aAAa,OAAW,MAAK,WAAW,EAAE;AAChD,QAAI,EAAE,aAAa,OAAW,MAAK,WAAW,EAAE;AAChD,QAAI,EAAE,cAAc,OAAW,MAAK,YAAY,EAAE;AAClD,QAAI,EAAE,cAAc,OAAW,MAAK,YAAY,EAAE;AAClD,QAAI,EAAE,aAAa,OAAW,MAAK,WAAW,EAAE;AAChD,QAAI,EAAE,gBAAgB,OAAW,MAAK,cAAc,EAAE;AACtD,QAAI,EAAE,gBAAgB,OAAW,MAAK,cAAc,EAAE;AAAA,EACxD;AAAA,EAEA,QAAQ,SAAsB,SAAiB,WAA2B;AAAA,EAAC;AAAA,EAE3E,OAAO,WAA2B;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnC,KAAK,UAA0B;AAC7B,QAAI,KAAK,UAAU,QAAQ;AACzB,YAAM,UAAU,SAAS;AACzB,WAAK,OAAO,OAAO,UAAU,KAAK;AAClC,WAAK,OAAO,OAAO;AAAA,IACrB;AACA,QAAI,KAAK,iBAAiB;AACxB,eAAS,UAAU,KAAK,MAAM;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,KAAK,KAAwC,UAA0B;AAGrE,QAAI,KAAK,UAAU,QAAQ;AACzB,UAAI,OAAO,SAAS;AACpB,UAAI,OAAO,SAAS;AACpB,UAAI,CAAC,OAAO,SAAS,IAAI,KAAK,CAAC,OAAO,SAAS,IAAI,GAAG;AAGpD,eAAO,KAAK,OAAO;AACnB,eAAO,KAAK,OAAO;AACnB,YAAI,SAAS,MAAM;AACjB,iBAAO;AACP,iBAAO;AAAA,QACT;AAAA,MACF,WAAW,SAAS,MAAM;AAExB,gBAAQ;AACR,gBAAQ;AAAA,MACV,OAAO;AACL,cAAM,OAAO,OAAO,QAAQ,KAAK;AACjC,gBAAQ;AACR,gBAAQ;AAAA,MACV;AACA,UAAI,KAAK,aAAa,UAAa,OAAO,KAAK,SAAU,QAAO,KAAK;AACrE,UAAI,KAAK,aAAa,UAAa,OAAO,KAAK,SAAU,QAAO,KAAK;AACrE,WAAK,OAAO,OAAO;AACnB,WAAK,OAAO,OAAO;AACnB,UAAI,KAAK,gBAAiB,UAAS,UAAU,KAAK,MAAM;AAAA,IAC1D;AAEA,UAAM,EAAE,SAAS,GAAG,UAAU,EAAE,IAAI;AACpC,UAAM,SAAS,UAAU,KAAK,OAAO,MAAM,KAAK,OAAO,MAAM,KAAK,WAAW;AAC7E,UAAM,SAAS,UAAU,KAAK,OAAO,MAAM,KAAK,OAAO,MAAM,KAAK,WAAW;AAG7E,QAAI,KAAK,aAAa,KAAK,WAAW;AACpC,UAAI,cAAc,KAAK;AACvB,UAAI,YAAY;AAChB,UAAI,UAAU;AACd,UAAI,KAAK,WAAW;AAClB,iBAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,gBAAM,IAAI,KAAK,MAAM,SAAS,MAAM,OAAO,CAAC,CAAC,CAAC,IAAI;AAClD,cAAI,OAAO,GAAG,CAAC;AACf,cAAI,OAAO,GAAG,CAAC;AAAA,QACjB;AAAA,MACF;AACA,UAAI,KAAK,WAAW;AAClB,iBAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,gBAAM,IAAI,KAAK,MAAM,SAAS,MAAM,OAAO,CAAC,CAAC,CAAC,IAAI;AAClD,cAAI,OAAO,GAAG,CAAC;AACf,cAAI,OAAO,GAAG,CAAC;AAAA,QACjB;AAAA,MACF;AACA,UAAI,OAAO;AAAA,IACb;AAGA,QAAI,KAAK,UAAU;AACjB,UAAI,cAAc,KAAK;AACvB,UAAI,UAAU;AACd,UAAI,KAAK,OAAO,OAAO,KAAK,KAAK,OAAO,OAAO,GAAG;AAChD,cAAM,KAAK,KAAK,MAAM,SAAS,MAAM,CAAC,CAAC,IAAI;AAC3C,YAAI,OAAO,IAAI,CAAC;AAChB,YAAI,OAAO,IAAI,CAAC;AAAA,MAClB;AACA,UAAI,KAAK,OAAO,OAAO,KAAK,KAAK,OAAO,OAAO,GAAG;AAChD,cAAM,KAAK,KAAK,MAAM,SAAS,MAAM,CAAC,CAAC,IAAI;AAC3C,YAAI,OAAO,GAAG,EAAE;AAChB,YAAI,OAAO,GAAG,EAAE;AAAA,MAClB;AACA,UAAI,OAAO;AAAA,IACb;AAGA,QAAI,KAAK,eAAe,KAAK,aAAa;AACxC,UAAI,YAAY,KAAK;AACrB,UAAI,OAAO,KAAK;AAChB,UAAI,KAAK,aAAa;AACpB,YAAI,eAAe;AACnB,iBAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,gBAAM,IAAI,SAAS,MAAM,OAAO,CAAC,CAAC;AAClC,cAAI;AAAA,YACF,WAAW,OAAO,CAAC,GAAG,KAAK,OAAO,KAAK,YAAY,KAAK,WAAW;AAAA,YACnE,IAAI;AAAA,YACJ,IAAI;AAAA,UACN;AAAA,QACF;AAAA,MACF;AACA,UAAI,KAAK,aAAa;AACpB,YAAI,eAAe;AACnB,iBAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,gBAAM,IAAI,SAAS,MAAM,OAAO,CAAC,CAAC;AAClC,cAAI,SAAS,OAAO,OAAO,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAgB;AAAA,EAAC;AACnB;AAEA,SAAS,WACP,OACA,MACA,YACA,SACQ;AACR,MAAI,SAAS,QAAQ;AACnB,QAAI,cAAc,MAAM;AACtB,aAAO,YAAY,aAAa,OAAO,OAAO;AAAA,IAChD;AAEA,UAAM,IAAI,QAAQ;AAClB,WAAO,GAAG,EAAE,QAAQ,CAAC,CAAC;AAAA,EACxB;AACA,SAAO,OAAO,KAAK;AACrB;;;ACnRO,IAAM,aAAN,MAAiB;AAAA,EACd,SAAkB,CAAC;AAAA,EACnB,OAAO,oBAAI,IAAmB;AAAA,EAEtC,IAAI,OAAoB;AACtB,SAAK,OAAO,KAAK,KAAK;AACtB,SAAK,KAAK,IAAI,MAAM,IAAI,KAAK;AAAA,EAC/B;AAAA,EAEA,OAAO,IAAkB;AACvB,UAAM,QAAQ,KAAK,KAAK,IAAI,EAAE;AAC9B,QAAI,CAAC,MAAO;AACZ,SAAK,KAAK,OAAO,EAAE;AACnB,UAAM,IAAI,KAAK,OAAO,QAAQ,KAAK;AACnC,QAAI,KAAK,EAAG,MAAK,OAAO,OAAO,GAAG,CAAC;AACnC,UAAM,QAAQ;AAAA,EAChB;AAAA,EAEA,IAAI,IAA+B;AACjC,WAAO,KAAK,KAAK,IAAI,EAAE;AAAA,EACzB;AAAA,EAEA,UAAU,UAA0B;AAClC,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK;AAC3C,WAAK,OAAO,CAAC,EAAE,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAQ,UAA0B;AAChC,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK;AAC3C,WAAK,OAAO,CAAC,EAAE,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,QAAQ,KAAwC,UAA0B;AACxE,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK;AAC3C,WAAK,OAAO,CAAC,EAAE,KAAK,KAAK,QAAQ;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,aAAmB;AACjB,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK;AAC3C,WAAK,OAAO,CAAC,EAAE,QAAQ;AAAA,IACzB;AACA,SAAK,OAAO,SAAS;AACrB,SAAK,KAAK,MAAM;AAAA,EAClB;AACF;;;ACpDA,IAAM,WAAW;AACjB,IAAM,OAAO,IAAI,kBAAkB,QAAQ;AAC3C,IAAM,OAAO,IAAI,kBAAkB,QAAQ;AAC3C,IAAM,OAAO,IAAI,kBAAkB,QAAQ;AAE3C,SAAS,KAAK,GAAqC;AACjD,QAAM,QAA8C;AAAA,IAClD,CAAC,GAAK,CAAC,IAAI,GAAG,GAAG,CAAC;AAAA,IAClB,CAAC,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC;AAAA,IACnB,CAAC,KAAK,CAAC,KAAK,IAAI,GAAG,CAAC;AAAA,IACpB,CAAC,MAAM,CAAC,KAAK,KAAK,EAAE,CAAC;AAAA,IACrB,CAAC,GAAK,CAAC,KAAK,KAAK,EAAE,CAAC;AAAA,EACtB;AACA,WAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,UAAM,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC;AACxB,UAAM,CAAC,IAAI,EAAE,IAAI,MAAM,IAAI,CAAC;AAC5B,QAAI,KAAK,IAAI;AACX,YAAM,KAAK,IAAI,OAAO,KAAK;AAC3B,aAAO;AAAA,QACL,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK;AAAA,QAC1B,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK;AAAA,QAC1B,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AACA,SAAO,MAAM,MAAM,SAAS,CAAC,EAAE,CAAC;AAClC;AAEA,SAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK,KAAK,WAAW,EAAE;AACzC,OAAK,CAAC,IAAI;AACV,OAAK,CAAC,IAAI;AACV,OAAK,CAAC,IAAI;AACZ;AASA,IAAM,YAA0B,OAAO,OAAO;AAAA,EAC5C,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,MAAM;AACR,CAAC;AAEM,SAAS,eAA6B;AAC3C,SAAO;AACT;;;ACxCA,IAAM,cAAc;AAeb,IAAM,oBAAN,MAAyC;AAAA,EACrC;AAAA,EACD,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,aAA8C;AAAA,EAC9C,OAA4B;AAAA,EAC5B,SAAS;AAAA;AAAA,EAGT,UAAwB,IAAI,aAAa,CAAC;AAAA,EAC1C,UAAwB,IAAI,aAAa,CAAC;AAAA,EAC1C,cAA2B,IAAI,YAAY,WAAW;AAAA,EACtD,eAA4B,IAAI,YAAY,WAAW;AAAA,EACvD,kBAAkB;AAAA,EAE1B,YAAY,IAAY;AACtB,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,UAAU,QAAuB;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,WAAW,OAAW,MAAK,SAAS,KAAK,IAAI,GAAG,EAAE,SAAS,CAAC;AAClE,QAAI,EAAE,cAAc,OAAW,MAAK,YAAY,KAAK,IAAI,GAAG,EAAE,SAAS;AACvE,QAAI,EAAE,iBAAiB,OAAW,MAAK,eAAe,EAAE;AACxD,QAAI,EAAE,UAAU,OAAW,MAAK,aAAa,WAAW,EAAE,KAAK;AAAA,EACjE;AAAA,EAEA,QAAQ,QAAqB,QAAgB,WAA2B;AACtE,SAAK,OAAO,IAAI,aAAa,QAAQ,GAAG,MAAM;AAC9C,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,OAAO,WAA2B;AAAA,EAAC;AAAA,EAEnC,KAAK,KAAwC,UAA0B;AACrE,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,QAAQ,KAAK,SAAS,KAAK,OAAQ;AAExC,UAAM,SAAS,KAAK;AACpB,UAAM,OAAO,KAAK;AAClB,UAAM,OAAO,OAAO;AACpB,UAAM,QAAS,KAAK,SAAS,SAAU;AAEvC,QAAI,KAAK,YAAY;AACnB,WAAK,UAAU,KAAK,UAAU,MAAM,QAAQ,OAAO,MAAM,IAAI;AAC7D;AAAA,IACF;AAEA,SAAK,cAAc,KAAK;AACxB,SAAK,aAAa,KAAK,UAAU,MAAM,QAAQ,OAAO,MAAM,IAAI;AAAA,EAClE;AAAA,EAEQ,UACN,KACA,UACA,MACA,QACA,OACA,MACA,MACM;AACN,UAAM,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK;AACvB,QAAI,YAAY,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;AAClC,QAAI,UAAU;AACd,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,YAAM,IAAI,IAAI;AACd,YAAM,KAAK,SAAS,MAAM,KAAK,CAAC,CAAC;AACjC,YAAM,KAAK,SAAS,MAAM,KAAK,IAAI,CAAC,CAAC;AACrC,UAAI,KAAK,KAAK,MAAM,KAAK,MAAM,MAAM,IAAI;AAAA,IAC3C;AACA,QAAI,KAAK;AAAA,EACX;AAAA,EAEQ,aACN,KACA,UACA,MACA,QACA,OACA,MACA,MACM;AACN,UAAM,MAAM,aAAa;AACzB,UAAM,SAAS,KAAK,KAAK,gBAAgB;AACzC,UAAM,UAAU,KAAK;AACrB,UAAM,UAAU,KAAK;AACrB,UAAM,cAAc,KAAK;AACzB,UAAM,eAAe,KAAK;AAG1B,gBAAY,KAAK,CAAC;AAKlB,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,YAAM,IAAI,IAAI;AACd,YAAM,YAAY,UAAU,IAAI,KAAK,IAAI,CAAC,IAAI;AAC9C,UAAI,MAAO,YAAY,UAAU,cAAc,KAAM;AACrD,UAAI,MAAM,EAAG,OAAM;AAAA,eACV,OAAO,YAAa,OAAM,cAAc;AACjD,kBAAY,GAAG;AAAA,IACjB;AAGA,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,mBAAa,CAAC,IAAI;AAClB,aAAO,YAAY,CAAC;AAAA,IACtB;AAMA,UAAM,cAAc;AAEpB,aAAS,IAAI,GAAG,IAAI,aAAa,IAAK,aAAY,CAAC,IAAI;AAEvD,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,YAAM,IAAI,IAAI;AACd,YAAM,YAAY,UAAU,IAAI,KAAK,IAAI,CAAC,IAAI;AAC9C,UAAI,MAAO,YAAY,UAAU,cAAc,KAAM;AACrD,UAAI,MAAM,EAAG,OAAM;AAAA,eACV,OAAO,YAAa,OAAM,cAAc;AACjD,YAAM,MAAM,aAAa,GAAG,IAAI,YAAY,GAAG;AAC/C,kBAAY,GAAG;AACf,cAAQ,GAAG,IAAI,SAAS,MAAM,KAAK,CAAC,CAAC;AACrC,cAAQ,GAAG,IAAI,SAAS,MAAM,KAAK,IAAI,CAAC,CAAC;AAAA,IAC3C;AAGA,aAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,YAAM,IAAI,YAAY,CAAC;AACvB,UAAI,MAAM,EAAG;AACb,UAAI,YAAY,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;AACvD,UAAI,UAAU;AACd,YAAM,QAAQ,aAAa,CAAC;AAC5B,YAAM,MAAM,QAAQ;AACpB,eAAS,IAAI,OAAO,IAAI,KAAK,KAAK;AAChC,YAAI,KAAK,QAAQ,CAAC,IAAI,MAAM,QAAQ,CAAC,IAAI,MAAM,MAAM,IAAI;AAAA,MAC3D;AACA,UAAI,KAAK;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,cAAc,OAAqB;AACzC,QAAI,SAAS,KAAK,gBAAiB;AACnC,UAAM,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,KAAK,kBAAkB,IAAI,GAAG,IAAI;AACzE,SAAK,UAAU,IAAI,aAAa,IAAI;AACpC,SAAK,UAAU,IAAI,aAAa,IAAI;AACpC,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,UAAgB;AACd,SAAK,OAAO;AACZ,SAAK,UAAU,IAAI,aAAa,CAAC;AACjC,SAAK,UAAU,IAAI,aAAa,CAAC;AACjC,SAAK,kBAAkB;AAAA,EACzB;AACF;AAEA,SAAS,WAAW,KAAuC;AACzD,MAAI,IAAI,WAAW,GAAG,GAAG;AACvB,UAAM,MAAM,IAAI,MAAM,CAAC;AACvB,QAAI,IAAI,WAAW,GAAG;AACpB,aAAO;AAAA,QACL,SAAS,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE;AAAA,QAC5B,SAAS,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE;AAAA,QAC5B,SAAS,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE;AAAA,MAC9B;AAAA,IACF;AACA,QAAI,IAAI,WAAW,GAAG;AACpB,aAAO;AAAA,QACL,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;AAAA,QAC5B,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;AAAA,QAC5B,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AACA,SAAO,CAAC,KAAK,KAAK,GAAG;AACvB;;;AC/MO,IAAM,aAAN,MAAiB;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACD,OAAO;AAAA,EACP,QAAQ;AAAA,EAEhB,YAAY,UAAkB,QAAgB;AAC5C,SAAK,WAAW;AAChB,SAAK,SAAS;AACd,SAAK,OAAO,IAAI,aAAa,WAAW,MAAM;AAAA,EAChD;AAAA,EAEA,IAAI,SAAiB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,QAAiC;AACpC,UAAM,OAAO,KAAK,OAAO,KAAK;AAC9B,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,WAAK,KAAK,OAAO,CAAC,IAAI,OAAO,CAAC;AAAA,IAChC;AACA,SAAK,QAAQ,KAAK,OAAO,KAAK,KAAK;AACnC,QAAI,KAAK,QAAQ,KAAK,SAAU,MAAK;AAAA,EACvC;AAAA,EAEA,SAAS,SAA6B;AACpC,UAAM,WAAW,QAAQ,SAAS,KAAK;AACvC,aAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,YAAM,OAAO,KAAK,OAAO,KAAK;AAC9B,YAAM,MAAM,IAAI,KAAK;AACrB,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,aAAK,KAAK,OAAO,CAAC,IAAI,QAAQ,MAAM,CAAC;AAAA,MACvC;AACA,WAAK,QAAQ,KAAK,OAAO,KAAK,KAAK;AACnC,UAAI,KAAK,QAAQ,KAAK,SAAU,MAAK;AAAA,IACvC;AAAA,EACF;AAAA,EAEA,QAAQ,IAAuE;AAC7E,UAAM,QAAQ,KAAK,QAAQ,KAAK,WAAW,IAAI,KAAK;AACpD,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,KAAK;AACnC,YAAM,QAAQ,QAAQ,KAAK,KAAK;AAChC,SAAG,KAAK,MAAM,OAAO,KAAK,QAAQ,CAAC;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,OAAO;AACZ,SAAK,QAAQ;AAAA,EACf;AACF;;;ACpCO,IAAM,iBAAN,MAAsC;AAAA,EAClC;AAAA,EACD,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ;AAAA,EAER,YAAY,IAAY;AACtB,SAAK,KAAK;AACV,SAAK,OAAO,IAAI,WAAW,MAAM,CAAC;AAAA,EACpC;AAAA,EAEA,UAAU,QAAuB;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,UAAU,OAAW,MAAK,QAAQ,EAAE;AAC1C,QAAI,EAAE,cAAc,OAAW,MAAK,YAAY,EAAE;AAClD,QAAI,EAAE,aAAa,UAAa,EAAE,aAAa,KAAK,KAAK,UAAU;AACjE,WAAK,OAAO,IAAI,WAAW,EAAE,UAAU,CAAC;AAAA,IAC1C;AAAA,EACF;AAAA,EAEA,QAAQ,QAAqB,QAAgB,UAA0B;AACrE,QAAI,SAAS,EAAG;AAChB,UAAM,MAAM,IAAI,aAAa,QAAQ,GAAG,MAAM;AAC9C,SAAK,KAAK,SAAS,GAAG;AAGtB,UAAM,IAAI,IAAI,SAAS,CAAC;AACxB,QAAI,IAAI,SAAS,QAAS,UAAS,UAAU;AAAA,EAC/C;AAAA,EAEA,OAAO,WAA2B;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUnC,KAAK,UAA0B;AAC7B,QAAI,KAAK,KAAK,WAAW,EAAG;AAC5B,UAAM,OAAO,SAAS,OAAO;AAC7B,QAAI,WAAW,SAAS;AACxB,QAAI,WAAW,SAAS;AACxB,SAAK,KAAK,QAAQ,CAAC,MAAM,QAAQ;AAC/B,YAAM,IAAI,KAAK,GAAG;AAClB,UAAI,IAAI,KAAM;AACd,YAAM,IAAI,KAAK,MAAM,CAAC;AACtB,UAAI,IAAI,SAAU,YAAW;AAC7B,UAAI,IAAI,SAAU,YAAW;AAAA,IAC/B,CAAC;AACD,aAAS,eAAe;AACxB,aAAS,eAAe;AAAA,EAC1B;AAAA,EAEA,KAAK,KAAwC,UAA0B;AACrE,QAAI,KAAK,KAAK,SAAS,EAAG;AAE1B,QAAI,cAAc,KAAK;AACvB,QAAI,YAAY,KAAK;AACrB,QAAI,UAAU;AAMd,UAAM,OAAO,SAAS,OAAO;AAE7B,QAAI,QAAQ;AACZ,SAAK,KAAK,QAAQ,CAAC,MAAM,QAAQ;AAC/B,YAAM,IAAI,KAAK,GAAG;AAClB,UAAI,IAAI,KAAM;AACd,YAAM,KAAK,SAAS,MAAM,CAAC;AAC3B,YAAM,KAAK,SAAS,MAAM,KAAK,MAAM,CAAC,CAAC;AACvC,UAAI,OAAO;AACT,YAAI,OAAO,IAAI,EAAE;AACjB,gBAAQ;AAAA,MACV,OAAO;AACL,YAAI,OAAO,IAAI,EAAE;AAAA,MACnB;AAAA,IACF,CAAC;AACD,QAAI,OAAO;AAAA,EACb;AAAA,EAEA,UAAgB;AACd,SAAK,KAAK,MAAM;AAAA,EAClB;AACF;;;ACxFO,IAAM,uBAAN,MAA4C;AAAA,EACxC;AAAA,EACD,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,SAAqB;AAAA,EACrB,OAA4B;AAAA,EAC5B,SAAS;AAAA,EAEjB,YAAY,IAAY;AACtB,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,UAAU,QAAuB;AAC/B,UAAM,IAAI;AACV,QAAI,EAAE,UAAU,OAAW,MAAK,QAAQ,EAAE;AAC1C,QAAI,EAAE,cAAc,OAAW,MAAK,YAAY,EAAE;AAClD,QAAI,EAAE,WAAW,OAAW,MAAK,SAAS,EAAE;AAAA,EAC9C;AAAA,EAEA,QAAQ,QAAqB,QAAgB,WAA2B;AACtE,SAAK,OAAO,IAAI,aAAa,QAAQ,GAAG,MAAM;AAC9C,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,OAAO,WAA2B;AAAA,EAAC;AAAA,EAEnC,KAAK,KAAwC,UAA0B;AACrE,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,QAAQ,KAAK,SAAS,EAAG;AAE9B,QAAI,cAAc,KAAK;AACvB,QAAI,YAAY,KAAK;AACrB,QAAI,UAAU;AAEd,QAAI,KAAK,WAAW,MAAM;AACxB,YAAM,IAAI,KAAK,UAAU;AACzB,UAAI,IAAI,EAAG;AACX,UAAI,OAAO,SAAS,MAAM,KAAK,CAAC,CAAC,GAAG,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC;AAC3D,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,cAAM,IAAI,IAAI;AACd,YAAI,OAAO,SAAS,MAAM,KAAK,CAAC,CAAC,GAAG,SAAS,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC;AAAA,MACjE;AAAA,IACF,OAAO;AACL,YAAM,IAAI,KAAK;AACf,YAAM,OAAO,SAAS,OAAO;AAC7B,YAAM,OAAO,SAAS,OAAO;AAC7B,YAAM,QAAQ,OAAO,QAAQ,KAAK,IAAI,GAAG,IAAI,CAAC;AAC9C,UAAI,OAAO,SAAS,MAAM,IAAI,GAAG,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC;AACxD,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAI,OAAO,SAAS,MAAM,OAAO,IAAI,IAAI,GAAG,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC;AAAA,MACrE;AAAA,IACF;AACA,QAAI,OAAO;AAAA,EACb;AAAA,EAEA,UAAgB;AACd,SAAK,OAAO;AAAA,EACd;AACF;;;ACzEO,IAAM,YAAN,MAAgB;AAAA,EACb,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,MAAqB;AAAA,EACZ;AAAA,EAEjB,YAAY,MAAkB;AAC5B,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,QAAQ;AACN,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,OAAO;AACL,SAAK,UAAU;AACf,QAAI,KAAK,OAAO,MAAM;AACpB,UAAI,OAAO,yBAAyB,aAAa;AAC/C,6BAAqB,KAAK,GAAG;AAAA,MAC/B,OAAO;AACL,qBAAa,KAAK,GAAG;AAAA,MACvB;AACA,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAEA,YAAY;AACV,SAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,OAAO,MAAM;AACnB,QAAI,CAAC,KAAK,QAAS;AACnB,QAAI,KAAK,OAAO;AACd,WAAK,QAAQ;AACb,WAAK,KAAK;AAAA,IACZ;AACA,QAAI,OAAO,0BAA0B,aAAa;AAChD,WAAK,MAAM,sBAAsB,KAAK,IAAI;AAAA,IAC5C,OAAO;AACL,WAAK,MAAM,WAAW,KAAK,MAAM,EAAE;AAAA,IACrC;AAAA,EACF;AACF;;;AC1CO,IAAM,WAAN,MAAe;AAAA,EACpB,UAAU;AAAA,EACV,WAAW;AAAA,EACX,MAAM;AAAA,EAEN,SAAiB,EAAE,MAAM,IAAI,MAAM,GAAG,MAAM,IAAI,MAAM,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOxD,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUV,eAAe,OAAO;AAAA,EACtB,eAAe,OAAO;AAAA,EAEtB,QAAQ,OAAe,QAAgB,KAAa;AAClD,SAAK,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,UAAU,GAAW;AACnB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,YAAkB;AAChB,SAAK,eAAe,OAAO;AAC3B,SAAK,eAAe,OAAO;AAAA,EAC7B;AAAA,EAEA,MAAM,GAAmB;AACvB,UAAM,EAAE,MAAM,KAAK,IAAI,KAAK;AAC5B,YAAS,IAAI,SAAS,OAAO,QAAS,KAAK;AAAA,EAC7C;AAAA,EAEA,MAAM,GAAmB;AACvB,UAAM,EAAE,MAAM,KAAK,IAAI,KAAK;AAC5B,WAAO,KAAK,YAAa,IAAI,SAAS,OAAO,QAAS,KAAK;AAAA,EAC7D;AACF;;;AC9CA,SAAS,YAAY,IAAY,MAAwB;AACvD,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,IAAI,eAAe,EAAE;AAAA,IAC9B,KAAK;AACH,aAAO,IAAI,qBAAqB,EAAE;AAAA,IACpC,KAAK;AACH,aAAO,IAAI,kBAAkB,EAAE;AAAA,IACjC,KAAK;AACH,aAAO,IAAI,cAAc,EAAE;AAAA,EAC/B;AACF;AAMO,IAAM,SAAN,MAAa;AAAA,EACV,SAAiC;AAAA,EACjC,MAAgD;AAAA,EACvC,WAAW,IAAI,SAAS;AAAA,EACxB,QAAQ,IAAI,WAAW;AAAA,EACvB;AAAA,EACT,UAAU;AAAA,EAElB,cAAc;AACZ,SAAK,YAAY,IAAI,UAAU,MAAM,KAAK,OAAO,CAAC;AAAA,EACpD;AAAA,EAEA,SAAS,KAAoB;AAC3B,YAAQ,IAAI,IAAI;AAAA,MACd,KAAK,GAAG;AACN,aAAK,KAAK,IAAI,QAAQ,IAAI,OAAO,IAAI,QAAQ,IAAI,KAAK,IAAI,OAAO;AACjE;AAAA,MACF,KAAK,GAAG;AACN,aAAK,UAAU,IAAI;AACnB,aAAK,UAAU,UAAU;AACzB;AAAA,MACF,KAAK,GAAG;AACN,aAAK,OAAO,IAAI,OAAO,IAAI,QAAQ,IAAI,GAAG;AAC1C;AAAA,MACF,KAAK,GAAG,WAAW;AACjB,cAAM,QAAQ,YAAY,IAAI,IAAI,IAAI,IAAI;AAC1C,YAAI,IAAI,WAAW,OAAW,OAAM,UAAU,IAAI,MAAM;AACxD,cAAM,OAAO,KAAK,QAAQ;AAC1B,aAAK,MAAM,IAAI,KAAK;AACpB,aAAK,UAAU,UAAU;AACzB;AAAA,MACF;AAAA,MACA,KAAK,GAAG;AACN,aAAK,MAAM,OAAO,IAAI,EAAE;AACxB,aAAK,UAAU,UAAU;AACzB;AAAA,MACF,KAAK,GAAG,QAAQ;AACd,cAAM,QAAQ,KAAK,MAAM,IAAI,IAAI,EAAE;AACnC,YAAI,OAAO;AACT,gBAAM,UAAU,IAAI,MAAM;AAC1B,eAAK,UAAU,UAAU;AAAA,QAC3B;AACA;AAAA,MACF;AAAA,MACA,KAAK,GAAG,MAAM;AACZ,cAAM,QAAQ,KAAK,MAAM,IAAI,IAAI,EAAE;AACnC,YAAI,OAAO;AACT,gBAAM,QAAQ,IAAI,QAAQ,IAAI,QAAQ,KAAK,QAAQ;AACnD,eAAK,UAAU,UAAU;AAAA,QAC3B;AACA;AAAA,MACF;AAAA,MACA,KAAK,GAAG;AACN,aAAK,QAAQ;AACb;AAAA,IACJ;AAAA,EACF;AAAA,EAEQ,KACN,QACA,OACA,QACA,KACA,SACA;AACA,SAAK,SAAS;AACd,SAAK,MAAM,OAAO,WAAW,IAAI;AACjC,QAAI,YAAY,OAAW,MAAK,UAAU;AAC1C,SAAK,OAAO,OAAO,QAAQ,GAAG;AAC9B,SAAK,UAAU,MAAM;AACrB,SAAK,UAAU,UAAU;AAAA,EAC3B;AAAA,EAEQ,OAAO,OAAe,QAAgB,KAAa;AACzD,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,OAAO,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,GAAG,CAAC;AACvD,SAAK,OAAO,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,GAAG,CAAC;AACzD,SAAK,SAAS,QAAQ,OAAO,QAAQ,GAAG;AACxC,SAAK,MAAM,UAAU,KAAK,QAAQ;AAClC,SAAK,UAAU,UAAU;AAAA,EAC3B;AAAA,EAEQ,SAAS;AACf,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,OAAO,CAAC,KAAK,OAAQ;AAK1B,SAAK,SAAS,UAAU;AACxB,SAAK,MAAM,QAAQ,KAAK,QAAQ;AAChC,UAAM,EAAE,IAAI,IAAI,KAAK;AACrB,QAAI,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;AACrC,QAAI,YAAY,KAAK;AACrB,QAAI,SAAS,GAAG,GAAG,KAAK,SAAS,SAAS,KAAK,SAAS,QAAQ;AAChE,SAAK,MAAM,QAAQ,KAAK,KAAK,QAAQ;AAAA,EACvC;AAAA,EAEQ,UAAU;AAChB,SAAK,UAAU,KAAK;AACpB,SAAK,MAAM,WAAW;AACtB,SAAK,SAAS;AACd,SAAK,MAAM;AAAA,EACb;AACF;;;ACjIA,IAAM,SAAS,IAAI,OAAO;AAE1B,KAAK,YAAY,CAAC,MAA6B;AAC7C,MAAI;AACF,WAAO,SAAS,EAAE,IAAI;AAAA,EACxB,SAAS,KAAK;AACZ,YAAQ,MAAM,oCAAoC,GAAG;AAAA,EACvD;AACF;AAEA,KAAK,iBAAiB,SAAS,CAAC,MAAM;AACpC,UAAQ,MAAM,oCAAoC,EAAE,WAAW,CAAC;AAClE,CAAC;AAED,KAAK,iBAAiB,gBAAgB,CAAC,MAAM;AAC3C,UAAQ,MAAM,oDAAoD,CAAC;AACrE,CAAC;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AxisGridConfig, D as DType, F as FluxionDataSink, a as FluxionHost, b as FluxionHostOptions, c as FluxionTypedArray, H as HostMsg, L as LayerKind, d as LidarLayerHandle, e as LidarPoint, f as LidarScatterConfig, g as LidarStride, h as LineChartConfig, i as LineChartStaticConfig, j as LineLayerHandle, k as LineSample, l as LineStaticLayerHandle, X as XyPoint } from './fluxion-host-C_n0cGQO.js';
1
+ export { A as AxisGridConfig, D as DType, F as FluxionDataSink, a as FluxionHost, b as FluxionHostOptions, c as FluxionTypedArray, H as HostMsg, L as LayerKind, d as LidarLayerHandle, e as LidarPoint, f as LidarScatterConfig, g as LidarStride, h as LineChartConfig, i as LineChartStaticConfig, j as LineLayerHandle, k as LineSample, l as LineStaticLayerHandle, X as XyPoint } from './fluxion-host-C73R280v.js';
2
2
 
3
3
  type TickFormatter = (epochMs: number) => string;
4
4
  /** Build a memoized formatter for a pattern. */
package/dist/index.js CHANGED
@@ -3,12 +3,12 @@ import {
3
3
  LidarLayerHandle,
4
4
  LineLayerHandle,
5
5
  LineStaticLayerHandle
6
- } from "./chunk-56NZ4OEO.js";
6
+ } from "./chunk-UKVVHCFN.js";
7
7
  import {
8
8
  formatClock,
9
9
  makeClockFormatter
10
10
  } from "./chunk-3PK3KDO5.js";
11
- import "./chunk-R7FLS7BG.js";
11
+ import "./chunk-EBSPHY2J.js";
12
12
  export {
13
13
  FluxionHost,
14
14
  LidarLayerHandle,
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { h as LineChartConfig, i as LineChartStaticConfig, f as LidarScatterConfig, A as AxisGridConfig, b as FluxionHostOptions, a as FluxionHost } from './fluxion-host-C_n0cGQO.js';
2
- export { L as LayerKind } from './fluxion-host-C_n0cGQO.js';
1
+ import { h as LineChartConfig, i as LineChartStaticConfig, f as LidarScatterConfig, A as AxisGridConfig, b as FluxionHostOptions, a as FluxionHost } from './fluxion-host-C73R280v.js';
2
+ export { L as LayerKind } from './fluxion-host-C73R280v.js';
3
3
  import * as react from 'react';
4
4
  import { RefObject, CSSProperties } from 'react';
5
5
 
package/dist/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  FluxionHost
3
- } from "./chunk-56NZ4OEO.js";
4
- import "./chunk-R7FLS7BG.js";
3
+ } from "./chunk-UKVVHCFN.js";
4
+ import "./chunk-EBSPHY2J.js";
5
5
 
6
6
  // src/widgets/fluxion-canvas/lib/layer-specs.ts
7
7
  function lineLayer(id, config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heojeongbo/fluxion-render",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "High-performance OffscreenCanvas rendering engine for real-time robotics data (charts, LiDAR, streaming).",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/features/host/model/layer-handles.ts","../src/features/host/model/fluxion-host.ts"],"sourcesContent":["/**\n * Type-safe layer handles.\n *\n * These wrap the raw `FluxionHost.pushData(id, Float32Array)` API so callers\n * can work with structured records on the main thread (e.g. after receiving\n * ROS messages or processing sensor frames) without hand-rolling the\n * interleaved Float32Array layout every time.\n *\n * Each handle is a thin encoder: the input shape is type-checked at the call\n * site, encoding to the worker-side layout happens once, and the underlying\n * ArrayBuffer is still transferred zero-copy.\n */\n\n// A minimal surface of `FluxionHost` that handles depend on. Declared here\n// so this module has no circular import with fluxion-host.ts.\nexport interface FluxionDataSink {\n pushData(id: string, data: Float32Array): void;\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Line (streaming) — data layout: [t, y, t, y, ...]\n// ────────────────────────────────────────────────────────────────────────────\n\n/** One streaming sample. `t` is host-relative ms (what `axis-grid` time mode expects). */\nexport interface LineSample {\n t: number;\n y: number;\n}\n\nexport class LineLayerHandle {\n constructor(\n private readonly sink: FluxionDataSink,\n readonly id: string,\n ) {}\n\n /** Push a single `[t, y]` sample. Allocates a 2-element Float32Array. */\n push(sample: LineSample): void {\n const buf = new Float32Array(2);\n buf[0] = sample.t;\n buf[1] = sample.y;\n this.sink.pushData(this.id, buf);\n }\n\n /**\n * Push an array of samples in one postMessage. Encodes into a single\n * contiguous Float32Array and transfers ownership. Prefer this over a loop\n * of `push()` when you already have a batch in hand.\n */\n pushBatch(samples: readonly LineSample[]): void {\n const n = samples.length;\n if (n === 0) return;\n const buf = new Float32Array(n * 2);\n for (let i = 0; i < n; i++) {\n const s = samples[i];\n buf[i * 2] = s.t;\n buf[i * 2 + 1] = s.y;\n }\n this.sink.pushData(this.id, buf);\n }\n\n /**\n * Escape hatch: push a pre-built `[t, y, t, y, ...]` Float32Array directly.\n * Use for hot paths where you can avoid the object-to-array encode step.\n * The TypedArray's byteOffset must be 0 (same rule as `pushData`).\n */\n pushRaw(data: Float32Array): void {\n this.sink.pushData(this.id, data);\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Line-static — data layout: [x, y, x, y, ...] or [y0, y1, ...]\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface XyPoint {\n x: number;\n y: number;\n}\n\n/**\n * Handle for `kind: \"line-static\"` layers. `setXY` replaces the entire series\n * with a new xy array; `setY` does the same with y-only data (x is computed\n * from the layer's configured x range). Use the variant that matches the\n * layer's `layout` config — this is not enforced at the type level since the\n * worker-side layout is a runtime config.\n */\nexport class LineStaticLayerHandle {\n constructor(\n private readonly sink: FluxionDataSink,\n readonly id: string,\n ) {}\n\n setXY(points: readonly XyPoint[]): void {\n const n = points.length;\n const buf = new Float32Array(n * 2);\n for (let i = 0; i < n; i++) {\n const p = points[i];\n buf[i * 2] = p.x;\n buf[i * 2 + 1] = p.y;\n }\n this.sink.pushData(this.id, buf);\n }\n\n setY(values: readonly number[]): void {\n const buf = new Float32Array(values.length);\n for (let i = 0; i < values.length; i++) buf[i] = values[i];\n this.sink.pushData(this.id, buf);\n }\n\n pushRaw(data: Float32Array): void {\n this.sink.pushData(this.id, data);\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Lidar — data layout: [x, y, (z), (intensity), ...] with configurable stride\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface LidarPoint {\n x: number;\n y: number;\n /** Optional when the layer stride is 2. Defaults to 0 otherwise. */\n z?: number;\n /** Optional when the layer stride is <4. Defaults to 0 otherwise. */\n intensity?: number;\n}\n\nexport type LidarStride = 2 | 3 | 4;\n\n/**\n * Handle for `kind: \"lidar\"` scatter layers. The stride (2 = xy, 3 = xyz,\n * 4 = xyz+intensity) must match the layer's stride config. Passing a\n * mismatched stride will succeed but the worker will read the wrong fields.\n */\nexport class LidarLayerHandle {\n constructor(\n private readonly sink: FluxionDataSink,\n readonly id: string,\n readonly stride: LidarStride = 4,\n ) {}\n\n push(points: readonly LidarPoint[]): void {\n const stride = this.stride;\n const n = points.length;\n const buf = new Float32Array(n * stride);\n for (let i = 0; i < n; i++) {\n const p = points[i];\n const o = i * stride;\n buf[o] = p.x;\n buf[o + 1] = p.y;\n if (stride >= 3) buf[o + 2] = p.z ?? 0;\n if (stride >= 4) buf[o + 3] = p.intensity ?? 0;\n }\n this.sink.pushData(this.id, buf);\n }\n\n pushRaw(data: Float32Array): void {\n this.sink.pushData(this.id, data);\n }\n}\n","import type { AxisGridConfig } from \"../../../entities/axis-grid-layer\";\nimport type { LidarScatterConfig } from \"../../../entities/lidar-scatter-layer\";\nimport type { LineChartConfig } from \"../../../entities/line-chart-layer\";\nimport type { LineChartStaticConfig } from \"../../../entities/line-chart-static-layer\";\nimport { Op, type DType, type HostMsg, type LayerKind } from \"../../../shared/protocol\";\nimport {\n LidarLayerHandle,\n type LidarStride,\n LineLayerHandle,\n LineStaticLayerHandle,\n} from \"./layer-handles\";\n\n/**\n * TypedArray flavors that FluxionRender accepts. `ArrayBufferView` is too\n * permissive (includes DataView), so we narrow to the specific types whose\n * layout matches the worker-side `wrapTypedArray` contract.\n */\nexport type FluxionTypedArray =\n | Float32Array\n | Uint8Array\n | Int16Array\n | Uint16Array\n | Int32Array;\n\nexport interface FluxionHostOptions {\n /**\n * Override the worker URL. Useful when bundlers don't support\n * `new Worker(new URL('./fluxion-worker.js', import.meta.url))`.\n * Pass a factory that returns a constructed Worker.\n */\n workerFactory?: () => Worker;\n}\n\nfunction defaultWorkerFactory(): Worker {\n // Vite / modern bundlers resolve this to a separate worker chunk.\n return new Worker(new URL(\"./fluxion-worker.js\", import.meta.url), {\n type: \"module\",\n });\n}\n\nfunction dtypeOf(arr: FluxionTypedArray): DType {\n if (arr instanceof Float32Array) return \"f32\";\n if (arr instanceof Uint8Array) return \"u8\";\n if (arr instanceof Int16Array) return \"i16\";\n if (arr instanceof Uint16Array) return \"u16\";\n if (arr instanceof Int32Array) return \"i32\";\n throw new Error(\"fluxion-render: unsupported TypedArray\");\n}\n\n/**\n * Main-thread handle to a worker-hosted rendering engine.\n *\n * Lifecycle:\n * const host = new FluxionHost(canvas);\n * host.addLayer('chart', 'line', { color: '#0ff' });\n * host.pushData('chart', float32); // transfers ownership\n * host.resize(w, h, dpr);\n * host.dispose();\n */\nexport class FluxionHost {\n private worker: Worker;\n private disposed = false;\n\n constructor(canvas: HTMLCanvasElement, opts: FluxionHostOptions = {}) {\n this.worker = (opts.workerFactory ?? defaultWorkerFactory)();\n\n const offscreen = canvas.transferControlToOffscreen();\n const dpr = typeof devicePixelRatio === \"number\" ? devicePixelRatio : 1;\n const rect = canvas.getBoundingClientRect();\n const width = rect.width || canvas.width || 300;\n const height = rect.height || canvas.height || 150;\n\n this.post(\n {\n op: Op.INIT,\n canvas: offscreen,\n width,\n height,\n dpr,\n },\n [offscreen],\n );\n }\n\n /**\n * Typed `addLayer` overloads.\n *\n * Prefer the kind-specific helpers below (`addLineLayer`, `addAxisLayer`,\n * etc.) — they both type-check the config AND return a typed handle where\n * applicable. This overload is retained for cases where the kind is chosen\n * dynamically.\n */\n addLayer(id: string, kind: \"line\", config?: LineChartConfig): void;\n addLayer(id: string, kind: \"line-static\", config?: LineChartStaticConfig): void;\n addLayer(id: string, kind: \"lidar\", config?: LidarScatterConfig): void;\n addLayer(id: string, kind: \"axis-grid\", config?: AxisGridConfig): void;\n // Dynamic fallback for code paths that pass a runtime `LayerKind` (e.g.\n // `useFluxionCanvas({ layers: FluxionLayerSpec[] })`).\n addLayer(id: string, kind: LayerKind, config?: unknown): void;\n addLayer(id: string, kind: LayerKind, config?: unknown): void {\n this.post({ op: Op.ADD_LAYER, id, kind, config });\n }\n\n removeLayer(id: string): void {\n this.post({ op: Op.REMOVE_LAYER, id });\n }\n\n /**\n * Typed `configLayer` overloads — pick the config shape from the kind used\n * when the layer was created. There's no runtime tag check; the caller is\n * trusted to pass the right config for the right id.\n */\n configLayer(id: string, config: LineChartConfig): void;\n configLayer(id: string, config: LineChartStaticConfig): void;\n configLayer(id: string, config: LidarScatterConfig): void;\n configLayer(id: string, config: AxisGridConfig): void;\n // Dynamic fallback for helpers like `useLayerConfig` that carry an opaque\n // config alongside the layer id.\n configLayer(id: string, config: unknown): void;\n configLayer(id: string, config: unknown): void {\n this.post({ op: Op.CONFIG, id, config });\n }\n\n // ──────────────────────────────────────────────────────────────────────\n // Typed add-layer helpers: construct + return a typed handle in one call.\n // ──────────────────────────────────────────────────────────────────────\n\n /**\n * Add a streaming line layer and return a handle that accepts structured\n * `{ t, y }` samples instead of raw Float32Array interleaved layout.\n */\n addLineLayer(id: string, config?: LineChartConfig): LineLayerHandle {\n this.addLayer(id, \"line\", config);\n return new LineLayerHandle(this, id);\n }\n\n /**\n * Add a static xy line layer and return a handle that accepts\n * `{ x, y }[]` or plain y-only arrays.\n */\n addLineStaticLayer(id: string, config?: LineChartStaticConfig): LineStaticLayerHandle {\n this.addLayer(id, \"line-static\", config);\n return new LineStaticLayerHandle(this, id);\n }\n\n /**\n * Add a LiDAR scatter layer and return a handle that accepts\n * `{ x, y, z?, intensity? }[]`. The handle's stride must match\n * `config.stride` (default 4).\n */\n addLidarLayer(id: string, config?: LidarScatterConfig): LidarLayerHandle {\n this.addLayer(id, \"lidar\", config);\n const stride = (config?.stride as LidarStride | undefined) ?? 4;\n return new LidarLayerHandle(this, id, stride);\n }\n\n /**\n * Add an axis/grid layer. Axis layers don't take data, so this returns\n * void — use `configLayer` to retune bounds / time window later.\n */\n addAxisLayer(id: string, config?: AxisGridConfig): void {\n this.addLayer(id, \"axis-grid\", config);\n }\n\n // ──────────────────────────────────────────────────────────────────────\n // Attach a typed handle to a layer that was added via another API path\n // (e.g. declaratively through `<FluxionCanvas layers={...}>` or\n // `useFluxionCanvas({ layers: [...] })`).\n // ──────────────────────────────────────────────────────────────────────\n\n line(id: string): LineLayerHandle {\n return new LineLayerHandle(this, id);\n }\n\n lineStatic(id: string): LineStaticLayerHandle {\n return new LineStaticLayerHandle(this, id);\n }\n\n lidar(id: string, stride: LidarStride = 4): LidarLayerHandle {\n return new LidarLayerHandle(this, id, stride);\n }\n\n /**\n * Push TypedArray data to a layer. Transfers the underlying ArrayBuffer —\n * the caller MUST NOT use `data` again afterwards.\n *\n * The TypedArray must start at byteOffset 0 because the worker reconstructs\n * the view at offset 0. Subviews would silently read from the wrong offset,\n * so they're rejected up-front. Use `data.slice()` to get a fresh buffer.\n */\n pushData(id: string, data: FluxionTypedArray): void {\n if (data.byteOffset !== 0) {\n throw new Error(\n `fluxion-render: TypedArray must start at byteOffset 0 (got ${data.byteOffset}). ` +\n `Call .slice() to copy into a fresh buffer before pushing.`,\n );\n }\n const buffer = data.buffer as ArrayBuffer;\n this.post(\n {\n op: Op.DATA,\n id,\n buffer,\n dtype: dtypeOf(data),\n length: data.length,\n },\n [buffer],\n );\n }\n\n resize(width: number, height: number, dpr: number): void {\n this.post({ op: Op.RESIZE, width, height, dpr });\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n try {\n this.post({ op: Op.DISPOSE });\n } catch {\n // worker may already be gone\n }\n this.worker.terminate();\n }\n\n private post(msg: HostMsg, transfer?: Transferable[]): void {\n if (this.disposed) return;\n if (transfer && transfer.length) {\n this.worker.postMessage(msg, transfer);\n } else {\n this.worker.postMessage(msg);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shared/protocol/protocol.ts"],"sourcesContent":["/**\n * Binary message protocol between main-thread `FluxionHost` and worker `Engine`.\n * Uses a plain const object (not const enum) so consumers with\n * `isolatedModules` can import types safely from the published package.\n */\nexport const Op = {\n INIT: 1,\n RESIZE: 2,\n ADD_LAYER: 3,\n REMOVE_LAYER: 4,\n CONFIG: 5,\n DATA: 6,\n DISPOSE: 7,\n} as const;\nexport type Op = (typeof Op)[keyof typeof Op];\n\nexport type LayerKind = \"line\" | \"line-static\" | \"lidar\" | \"axis-grid\";\n\nexport type DType = \"f32\" | \"u8\" | \"i16\" | \"u16\" | \"i32\";\n\nexport interface InitMsg {\n op: typeof Op.INIT;\n canvas: OffscreenCanvas;\n width: number;\n height: number;\n dpr: number;\n}\n\nexport interface ResizeMsg {\n op: typeof Op.RESIZE;\n width: number;\n height: number;\n dpr: number;\n}\n\nexport interface AddLayerMsg {\n op: typeof Op.ADD_LAYER;\n id: string;\n kind: LayerKind;\n config?: unknown;\n}\n\nexport interface RemoveLayerMsg {\n op: typeof Op.REMOVE_LAYER;\n id: string;\n}\n\nexport interface ConfigMsg {\n op: typeof Op.CONFIG;\n id: string;\n config: unknown;\n}\n\nexport interface DataMsg {\n op: typeof Op.DATA;\n id: string;\n buffer: ArrayBuffer;\n dtype: DType;\n length: number;\n}\n\nexport interface DisposeMsg {\n op: typeof Op.DISPOSE;\n}\n\nexport type HostMsg =\n | InitMsg\n | ResizeMsg\n | AddLayerMsg\n | RemoveLayerMsg\n | ConfigMsg\n | DataMsg\n | DisposeMsg;\n"],"mappings":";AAKO,IAAM,KAAK;AAAA,EAChB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AACX;","names":[]}