@fcannizzaro/streamdeck-react 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/action.js +0 -1
  2. package/dist/adapter/index.d.ts +2 -0
  3. package/dist/adapter/physical-device.d.ts +2 -0
  4. package/dist/adapter/physical-device.js +153 -0
  5. package/dist/adapter/types.d.ts +127 -0
  6. package/dist/devtools/bridge.d.ts +2 -2
  7. package/dist/devtools/bridge.js +7 -8
  8. package/dist/devtools/highlight.d.ts +1 -2
  9. package/dist/devtools/highlight.js +4 -3
  10. package/dist/devtools/types.d.ts +5 -5
  11. package/dist/hooks/animation.d.ts +1 -1
  12. package/dist/hooks/animation.js +2 -2
  13. package/dist/hooks/events.js +1 -1
  14. package/dist/hooks/sdk.js +11 -11
  15. package/dist/hooks/utility.js +3 -2
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.js +2 -1
  18. package/dist/plugin.js +69 -100
  19. package/dist/reconciler/vnode.d.ts +0 -2
  20. package/dist/reconciler/vnode.js +0 -1
  21. package/dist/render/cache.d.ts +5 -17
  22. package/dist/render/cache.js +7 -29
  23. package/dist/render/image-cache.d.ts +8 -7
  24. package/dist/render/image-cache.js +33 -17
  25. package/dist/render/metrics.d.ts +9 -10
  26. package/dist/render/metrics.js +36 -39
  27. package/dist/render/pipeline.d.ts +4 -14
  28. package/dist/render/pipeline.js +47 -111
  29. package/dist/render/png.d.ts +0 -9
  30. package/dist/render/png.js +5 -8
  31. package/dist/render/render-pool.d.ts +0 -2
  32. package/dist/render/render-pool.js +1 -12
  33. package/dist/roots/registry.d.ts +5 -9
  34. package/dist/roots/registry.js +10 -27
  35. package/dist/roots/root.d.ts +7 -34
  36. package/dist/roots/root.js +23 -90
  37. package/dist/roots/touchstrip-root.d.ts +6 -32
  38. package/dist/roots/touchstrip-root.js +61 -181
  39. package/dist/types.d.ts +23 -19
  40. package/package.json +6 -4
  41. package/dist/node_modules/.bun/xxhash-wasm@1.1.0/node_modules/xxhash-wasm/esm/xxhash-wasm.js +0 -3157
  42. package/dist/roots/flush-coordinator.d.ts +0 -18
  43. package/dist/roots/flush-coordinator.js +0 -38
package/dist/plugin.js CHANGED
@@ -2,10 +2,11 @@ import { metrics } from "./render/metrics.js";
2
2
  import { RootRegistry } from "./roots/registry.js";
3
3
  import { RenderPool } from "./render/render-pool.js";
4
4
  import { startDevtoolsServer } from "./devtools/index.js";
5
- import streamDeck, { SingletonAction } from "@elgato/streamdeck";
5
+ import { physicalDevice } from "./adapter/physical-device.js";
6
6
  import { Renderer } from "@takumi-rs/core";
7
7
  //#region src/plugin.ts
8
8
  function createPlugin(config) {
9
+ const adapter = config.adapter ?? physicalDevice();
9
10
  const renderer = new Renderer({ fonts: config.fonts.map((f) => ({
10
11
  name: f.name,
11
12
  data: f.data,
@@ -20,39 +21,39 @@ function createPlugin(config) {
20
21
  devicePixelRatio: config.devicePixelRatio ?? 1,
21
22
  debug: config.debug ?? process.env.NODE_ENV !== "production",
22
23
  imageCacheMaxBytes: config.imageCacheMaxBytes ?? 16 * 1024 * 1024,
23
- touchstripCacheMaxBytes: config.touchstripCacheMaxBytes ?? 8 * 1024 * 1024,
24
- renderPool,
25
- touchstripImageFormat: config.touchstripImageFormat ?? "webp"
24
+ touchStripCacheMaxBytes: config.touchStripCacheMaxBytes ?? 8 * 1024 * 1024,
25
+ renderPool
26
26
  };
27
- const registry = new RootRegistry(renderConfig, config.renderDebounceMs ?? 16, streamDeck, async (settings) => {
28
- await streamDeck.settings.setGlobalSettings(settings);
27
+ const registry = new RootRegistry(renderConfig, adapter, async (settings) => {
28
+ await adapter.setGlobalSettings(settings);
29
29
  }, config.wrapper);
30
- streamDeck.settings.getGlobalSettings().then((gs) => {
30
+ adapter.getGlobalSettings().then((gs) => {
31
31
  registry.setGlobalSettings(gs);
32
32
  }).catch((err) => {
33
33
  console.error("[@fcannizzaro/streamdeck-react] Failed to load global settings:", err);
34
34
  });
35
- streamDeck.settings.onDidReceiveGlobalSettings((ev) => {
36
- registry.setGlobalSettings(ev.settings);
35
+ adapter.onGlobalSettingsChanged((settings) => {
36
+ registry.setGlobalSettings(settings);
37
37
  });
38
- for (const definition of config.actions) {
39
- const singletonAction = createSingletonAction(definition, registry, config.onActionError);
40
- streamDeck.actions.registerAction(singletonAction);
41
- }
38
+ for (const definition of config.actions) registerActionWithAdapter(adapter, definition, registry, config.onActionError);
42
39
  if (renderConfig.debug) metrics.enable();
43
40
  if (config.devtools) startDevtoolsServer({
44
- devtoolsName: streamDeck.info.plugin.uuid,
41
+ devtoolsName: adapter.pluginUUID,
45
42
  registry,
46
43
  renderConfig
47
44
  });
48
45
  return { async connect() {
49
46
  if (renderPool != null) renderPool.initialize().catch(() => {});
50
- await streamDeck.connect();
47
+ await adapter.connect();
51
48
  } };
52
49
  }
53
- function createSingletonAction(definition, registry, onError) {
54
- return new class extends SingletonAction {
55
- manifestId = definition.uuid;
50
+ function registerActionWithAdapter(adapter, definition, registry, onError) {
51
+ const handleError = (actionId, err) => {
52
+ const error = err instanceof Error ? err : new Error(String(err));
53
+ console.error(`[@fcannizzaro/streamdeck-react] Error in action ${definition.uuid} (${actionId}):`, error);
54
+ onError?.(definition.uuid, actionId, error);
55
+ };
56
+ adapter.registerAction(definition.uuid, {
56
57
  onWillAppear(ev) {
57
58
  try {
58
59
  const isEncoder = ev.payload.controller === "Encoder";
@@ -64,126 +65,94 @@ function createSingletonAction(definition, registry, onError) {
64
65
  if (!component) return;
65
66
  registry.create(ev, component, definition);
66
67
  } catch (err) {
67
- this.handleError(ev.action.id, err);
68
+ handleError(ev.action.id, err);
68
69
  }
69
- }
70
- onWillDisappear(ev) {
70
+ },
71
+ onWillDisappear(actionId) {
71
72
  try {
72
- registry.destroy(ev.action.id);
73
+ registry.destroy(actionId);
73
74
  } catch (err) {
74
- this.handleError(ev.action.id, err);
75
+ handleError(actionId, err);
75
76
  }
76
- }
77
- onKeyDown(ev) {
77
+ },
78
+ onKeyDown(actionId, payload) {
78
79
  try {
79
- registry.dispatch(ev.action.id, "keyDown", {
80
- settings: ev.payload.settings,
81
- isInMultiAction: ev.payload.isInMultiAction,
82
- state: ev.payload.state,
83
- userDesiredState: "userDesiredState" in ev.payload ? ev.payload.userDesiredState : void 0
84
- });
80
+ registry.dispatch(actionId, "keyDown", payload);
85
81
  } catch (err) {
86
- this.handleError(ev.action.id, err);
82
+ handleError(actionId, err);
87
83
  }
88
- }
89
- onKeyUp(ev) {
84
+ },
85
+ onKeyUp(actionId, payload) {
90
86
  try {
91
- registry.dispatch(ev.action.id, "keyUp", {
92
- settings: ev.payload.settings,
93
- isInMultiAction: ev.payload.isInMultiAction,
94
- state: ev.payload.state,
95
- userDesiredState: "userDesiredState" in ev.payload ? ev.payload.userDesiredState : void 0
96
- });
87
+ registry.dispatch(actionId, "keyUp", payload);
97
88
  } catch (err) {
98
- this.handleError(ev.action.id, err);
89
+ handleError(actionId, err);
99
90
  }
100
- }
101
- onDialRotate(ev) {
91
+ },
92
+ onDialRotate(actionId, payload) {
102
93
  try {
103
- registry.dispatch(ev.action.id, "dialRotate", {
104
- ticks: ev.payload.ticks,
105
- pressed: ev.payload.pressed,
106
- settings: ev.payload.settings
107
- });
94
+ registry.dispatch(actionId, "dialRotate", payload);
108
95
  } catch (err) {
109
- this.handleError(ev.action.id, err);
96
+ handleError(actionId, err);
110
97
  }
111
- }
112
- onDialDown(ev) {
98
+ },
99
+ onDialDown(actionId, payload) {
113
100
  try {
114
- registry.dispatch(ev.action.id, "dialDown", {
115
- settings: ev.payload.settings,
116
- controller: "Encoder"
117
- });
101
+ registry.dispatch(actionId, "dialDown", payload);
118
102
  } catch (err) {
119
- this.handleError(ev.action.id, err);
103
+ handleError(actionId, err);
120
104
  }
121
- }
122
- onDialUp(ev) {
105
+ },
106
+ onDialUp(actionId, payload) {
123
107
  try {
124
- registry.dispatch(ev.action.id, "dialUp", {
125
- settings: ev.payload.settings,
126
- controller: "Encoder"
127
- });
108
+ registry.dispatch(actionId, "dialUp", payload);
128
109
  } catch (err) {
129
- this.handleError(ev.action.id, err);
110
+ handleError(actionId, err);
130
111
  }
131
- }
132
- onTouchTap(ev) {
112
+ },
113
+ onTouchTap(actionId, payload) {
133
114
  try {
134
- registry.dispatch(ev.action.id, "touchTap", {
135
- tapPos: ev.payload.tapPos,
136
- hold: ev.payload.hold,
137
- settings: ev.payload.settings
138
- });
115
+ registry.dispatch(actionId, "touchTap", payload);
139
116
  } catch (err) {
140
- this.handleError(ev.action.id, err);
117
+ handleError(actionId, err);
141
118
  }
142
- }
143
- onDidReceiveSettings(ev) {
119
+ },
120
+ onDidReceiveSettings(actionId, settings) {
144
121
  try {
145
- registry.updateSettings(ev.action.id, ev.payload.settings);
122
+ registry.updateSettings(actionId, settings);
146
123
  } catch (err) {
147
- this.handleError(ev.action.id, err);
124
+ handleError(actionId, err);
148
125
  }
149
- }
150
- onSendToPlugin(ev) {
126
+ },
127
+ onSendToPlugin(actionId, payload) {
151
128
  try {
152
- registry.dispatch(ev.action.id, "sendToPlugin", ev.payload);
129
+ registry.dispatch(actionId, "sendToPlugin", payload);
153
130
  } catch (err) {
154
- this.handleError(ev.action.id, err);
131
+ handleError(actionId, err);
155
132
  }
156
- }
157
- onPropertyInspectorDidAppear(ev) {
133
+ },
134
+ onPropertyInspectorDidAppear(actionId) {
158
135
  try {
159
- registry.dispatch(ev.action.id, "propertyInspectorDidAppear", void 0);
136
+ registry.dispatch(actionId, "propertyInspectorDidAppear", void 0);
160
137
  } catch (err) {
161
- this.handleError(ev.action.id, err);
138
+ handleError(actionId, err);
162
139
  }
163
- }
164
- onPropertyInspectorDidDisappear(ev) {
140
+ },
141
+ onPropertyInspectorDidDisappear(actionId) {
165
142
  try {
166
- registry.dispatch(ev.action.id, "propertyInspectorDidDisappear", void 0);
143
+ registry.dispatch(actionId, "propertyInspectorDidDisappear", void 0);
167
144
  } catch (err) {
168
- this.handleError(ev.action.id, err);
145
+ handleError(actionId, err);
169
146
  }
170
- }
171
- onTitleParametersDidChange(ev) {
147
+ },
148
+ onTitleParametersDidChange(actionId, payload) {
172
149
  try {
173
- registry.dispatch(ev.action.id, "titleParametersDidChange", {
174
- title: ev.payload.title,
175
- settings: ev.payload.settings
176
- });
150
+ registry.dispatch(actionId, "titleParametersDidChange", payload);
177
151
  } catch (err) {
178
- this.handleError(ev.action.id, err);
152
+ handleError(actionId, err);
179
153
  }
180
154
  }
181
- handleError(actionId, err) {
182
- const error = err instanceof Error ? err : new Error(String(err));
183
- console.error(`[@fcannizzaro/streamdeck-react] Error in action ${definition.uuid} (${actionId}):`, error);
184
- onError?.(definition.uuid, actionId, error);
185
- }
186
- }();
155
+ });
187
156
  }
188
157
  //#endregion
189
158
  export { createPlugin };
@@ -1,4 +1,3 @@
1
- import { ReactElement } from 'react';
2
1
  export interface VNode {
3
2
  type: string;
4
3
  props: Record<string, unknown>;
@@ -41,4 +40,3 @@ export declare function clearParent(child: VNode): void;
41
40
  export declare function createVNode(type: string, props: Record<string, unknown>): VNode;
42
41
  export declare function createTextVNode(text: string): VNode;
43
42
  export declare function createVContainer(renderCallback: () => void): VContainer;
44
- export declare function vnodeToElement(node: VNode): ReactElement | string;
@@ -1,4 +1,3 @@
1
- import "react";
2
1
  //#region src/reconciler/vnode.ts
3
2
  /** Mark a node (and its ancestors up to the container) as dirty. */
4
3
  function markDirty(node) {
@@ -1,24 +1,12 @@
1
1
  import { VNode, VContainer } from '../reconciler/vnode';
2
- /**
3
- * Initialize the xxHash-wasm module. Call is idempotent — subsequent
4
- * calls return the same promise. Resolves once `fnv1a()` will use the
5
- * WASM fast path for large buffers.
6
- */
7
- export declare function initBufferHasher(): Promise<void>;
8
- /** Reset the xxHash singleton — for testing only. */
9
- export declare function resetBufferHasher(): void;
10
2
  /**
11
3
  * Hash a raw byte buffer (Uint8Array or Buffer) or string.
12
4
  *
13
- * For buffers larger than {@link STRIDE_THRESHOLD} bytes:
14
- * - **Primary path**: xxHash-wasm `h32Raw()`hashes the entire buffer
15
- * in native WASM code. Faster than JS strided sampling even for
16
- * 320 KB touchstrip frames, with superior hash distribution.
17
- * - **Fallback path**: FNV-1a with strided sampling (every 16th byte)
18
- * when WASM hasn't compiled yet (startup) or is unavailable.
5
+ * For buffers larger than {@link STRIDE_THRESHOLD} bytes, uses strided
6
+ * FNV-1a sampling (every 16th byte) — at 30fps this adds <1ms even
7
+ * for 320KB TouchStrip buffers.
19
8
  *
20
- * Strings and small buffers always use JS FNV-1a (fast enough at those
21
- * sizes, and avoids the overhead of calling into WASM for tiny inputs).
9
+ * Strings and small buffers always use full byte-by-byte FNV-1a.
22
10
  */
23
11
  export declare function fnv1a(input: string | Uint8Array | Buffer): number;
24
12
  /** Feed a string into a running FNV-1a hash. */
@@ -39,4 +27,4 @@ export declare function computeHash(node: VNode): number;
39
27
  */
40
28
  export declare function computeTreeHash(container: VContainer): number;
41
29
  export declare function computeCacheKey(treeHash: number, width: number, height: number, dpr: number, format: string): number;
42
- export declare function computeNativeTouchstripCacheKey(treeHash: number, width: number, height: number, dpr: number, format: string, columns: number[]): number;
30
+ export declare function computeTouchStripSegmentCacheKey(treeHash: number, width: number, height: number, dpr: number, columns: number[]): number;
@@ -1,4 +1,3 @@
1
- import { e } from "../node_modules/.bun/xxhash-wasm@1.1.0/node_modules/xxhash-wasm/esm/xxhash-wasm.js";
2
1
  //#region src/render/cache.ts
3
2
  var FNV_OFFSET_BASIS = 2166136261;
4
3
  var FNV_PRIME = 16777619;
@@ -9,36 +8,16 @@ var SENTINEL_TRUE = 1414681925;
9
8
  var SENTINEL_FALSE = 1178684499;
10
9
  var SENTINEL_ARRAY = 1095914073;
11
10
  var SENTINEL_OBJECT = 1329744468;
12
- /** @internal WASM-accelerated buffer hash function, null before init. */
13
- var bufferHashFn = null;
14
- var xxHashInitPromise = null;
15
- /**
16
- * Initialize the xxHash-wasm module. Call is idempotent — subsequent
17
- * calls return the same promise. Resolves once `fnv1a()` will use the
18
- * WASM fast path for large buffers.
19
- */
20
- function initBufferHasher() {
21
- if (xxHashInitPromise != null) return xxHashInitPromise;
22
- xxHashInitPromise = e().then((api) => {
23
- bufferHashFn = api.h32Raw;
24
- }).catch(() => {});
25
- return xxHashInitPromise;
26
- }
27
- initBufferHasher();
28
11
  var STRIDE_THRESHOLD = 4096;
29
12
  var STRIDE = 16;
30
13
  /**
31
14
  * Hash a raw byte buffer (Uint8Array or Buffer) or string.
32
15
  *
33
- * For buffers larger than {@link STRIDE_THRESHOLD} bytes:
34
- * - **Primary path**: xxHash-wasm `h32Raw()`hashes the entire buffer
35
- * in native WASM code. Faster than JS strided sampling even for
36
- * 320 KB touchstrip frames, with superior hash distribution.
37
- * - **Fallback path**: FNV-1a with strided sampling (every 16th byte)
38
- * when WASM hasn't compiled yet (startup) or is unavailable.
16
+ * For buffers larger than {@link STRIDE_THRESHOLD} bytes, uses strided
17
+ * FNV-1a sampling (every 16th byte) — at 30fps this adds <1ms even
18
+ * for 320KB TouchStrip buffers.
39
19
  *
40
- * Strings and small buffers always use JS FNV-1a (fast enough at those
41
- * sizes, and avoids the overhead of calling into WASM for tiny inputs).
20
+ * Strings and small buffers always use full byte-by-byte FNV-1a.
42
21
  */
43
22
  function fnv1a(input) {
44
23
  let hash = FNV_OFFSET_BASIS;
@@ -47,7 +26,6 @@ function fnv1a(input) {
47
26
  hash = Math.imul(hash, FNV_PRIME);
48
27
  }
49
28
  else if (input.length > STRIDE_THRESHOLD) {
50
- if (bufferHashFn != null) return bufferHashFn(input);
51
29
  hash = fnv1aU32(input.length, hash);
52
30
  for (let i = 0; i < input.length; i += STRIDE) {
53
31
  const end = Math.min(i + 4, input.length);
@@ -159,11 +137,11 @@ function computeCacheKey(treeHash, width, height, dpr, format) {
159
137
  key = fnv1aString(format, key);
160
138
  return key >>> 0;
161
139
  }
162
- function computeNativeTouchstripCacheKey(treeHash, width, height, dpr, format, columns) {
163
- let key = computeCacheKey(treeHash, width, height, dpr, format);
140
+ function computeTouchStripSegmentCacheKey(treeHash, width, height, dpr, columns) {
141
+ let key = computeCacheKey(treeHash, width, height, dpr, "png");
164
142
  key = fnv1aU32(columns.length, key);
165
143
  for (const col of columns) key = fnv1aU32(col, key);
166
144
  return key >>> 0;
167
145
  }
168
146
  //#endregion
169
- export { computeCacheKey, computeNativeTouchstripCacheKey, computeTreeHash, fnv1a };
147
+ export { computeCacheKey, computeTouchStripSegmentCacheKey, computeTreeHash, fnv1a };
@@ -15,7 +15,7 @@ export interface CacheStats {
15
15
  * total byte size exceeds `maxBytes`.
16
16
  *
17
17
  * Generic over value type: use `string` for data URI caching (keys/dials),
18
- * `Buffer` for raw RGBA caching (touchstrip).
18
+ * `Buffer` for raw RGBA caching (TouchStrip).
19
19
  */
20
20
  export declare class ImageCache<V = string> {
21
21
  private maxBytes;
@@ -40,14 +40,15 @@ export declare class ImageCache<V = string> {
40
40
  }
41
41
  /** Get or create the shared image cache for data URIs. */
42
42
  export declare function getImageCache(maxBytes?: number): ImageCache<string>;
43
- /** Get or create the shared touchstrip raw buffer cache. */
44
- export declare function getTouchstripCache(maxBytes?: number): ImageCache<Buffer>;
43
+ /** Get or create the shared TouchStrip raw buffer cache. */
44
+ export declare function getTouchStripCache(maxBytes?: number): ImageCache<Buffer>;
45
45
  /**
46
- * Get or create the shared touchstrip native-format segment cache.
46
+ * Get or create the shared TouchStrip segment URI cache.
47
47
  * Stores sorted `[column, dataUri]` tuples per tree hash + column config.
48
- * Uses the same default budget as the raw touchstrip cache since only one
49
- * touchstrip rendering path is active at a time.
50
48
  */
51
- export declare function getTouchstripNativeCache(maxBytes?: number): ImageCache<Array<[number, string]>>;
49
+ export declare function getTouchStripSegmentCache(maxBytes?: number): ImageCache<Array<[number, string]>>;
50
+ export declare function getImageCacheStats(): CacheStats;
51
+ export declare function getTouchStripCacheStats(): CacheStats;
52
+ export declare function getTouchStripSegmentCacheStats(): CacheStats;
52
53
  /** Reset all caches (for testing or config changes). */
53
54
  export declare function resetCaches(): void;
@@ -4,7 +4,7 @@
4
4
  * total byte size exceeds `maxBytes`.
5
5
  *
6
6
  * Generic over value type: use `string` for data URI caching (keys/dials),
7
- * `Buffer` for raw RGBA caching (touchstrip).
7
+ * `Buffer` for raw RGBA caching (TouchStrip).
8
8
  */
9
9
  var ImageCache = class {
10
10
  map = /* @__PURE__ */ new Map();
@@ -97,32 +97,48 @@ var ImageCache = class {
97
97
  }
98
98
  };
99
99
  var DEFAULT_IMAGE_CACHE_MAX_BYTES = 16 * 1024 * 1024;
100
- var DEFAULT_TOUCHSTRIP_CACHE_MAX_BYTES = 8 * 1024 * 1024;
100
+ var DEFAULT_TOUCH_STRIP_CACHE_MAX_BYTES = 8 * 1024 * 1024;
101
101
  /** Shared image cache for key/dial data URIs. */
102
102
  var imageCache = null;
103
- /** Shared raw buffer cache for touchstrip RGBA data. */
104
- var touchstripCache = null;
105
- /** Shared segment URI cache for touchstrip native-format (WebP/PNG) renders. */
106
- var touchstripNativeCache = null;
103
+ /** Shared raw buffer cache for TouchStrip RGBA data. */
104
+ var touchStripCache = null;
105
+ /** Shared segment URI cache for TouchStrip shared-strip renders. */
106
+ var touchStripSegmentCache = null;
107
107
  /** Get or create the shared image cache for data URIs. */
108
108
  function getImageCache(maxBytes) {
109
109
  if (imageCache == null) imageCache = new ImageCache(maxBytes ?? DEFAULT_IMAGE_CACHE_MAX_BYTES);
110
110
  return imageCache;
111
111
  }
112
- /** Get or create the shared touchstrip raw buffer cache. */
113
- function getTouchstripCache(maxBytes) {
114
- if (touchstripCache == null) touchstripCache = new ImageCache(maxBytes ?? DEFAULT_TOUCHSTRIP_CACHE_MAX_BYTES);
115
- return touchstripCache;
112
+ /** Get or create the shared TouchStrip raw buffer cache. */
113
+ function getTouchStripCache(maxBytes) {
114
+ if (touchStripCache == null) touchStripCache = new ImageCache(maxBytes ?? DEFAULT_TOUCH_STRIP_CACHE_MAX_BYTES);
115
+ return touchStripCache;
116
116
  }
117
117
  /**
118
- * Get or create the shared touchstrip native-format segment cache.
118
+ * Get or create the shared TouchStrip segment URI cache.
119
119
  * Stores sorted `[column, dataUri]` tuples per tree hash + column config.
120
- * Uses the same default budget as the raw touchstrip cache since only one
121
- * touchstrip rendering path is active at a time.
122
120
  */
123
- function getTouchstripNativeCache(maxBytes) {
124
- if (touchstripNativeCache == null) touchstripNativeCache = new ImageCache(maxBytes ?? DEFAULT_TOUCHSTRIP_CACHE_MAX_BYTES);
125
- return touchstripNativeCache;
121
+ function getTouchStripSegmentCache(maxBytes) {
122
+ if (touchStripSegmentCache == null) touchStripSegmentCache = new ImageCache(maxBytes ?? DEFAULT_TOUCH_STRIP_CACHE_MAX_BYTES);
123
+ return touchStripSegmentCache;
124
+ }
125
+ function createEmptyStats(maxBytes) {
126
+ return {
127
+ entries: 0,
128
+ bytes: 0,
129
+ maxBytes,
130
+ hits: 0,
131
+ misses: 0
132
+ };
133
+ }
134
+ function getImageCacheStats() {
135
+ return imageCache?.stats ?? createEmptyStats(DEFAULT_IMAGE_CACHE_MAX_BYTES);
136
+ }
137
+ function getTouchStripCacheStats() {
138
+ return touchStripCache?.stats ?? createEmptyStats(DEFAULT_TOUCH_STRIP_CACHE_MAX_BYTES);
139
+ }
140
+ function getTouchStripSegmentCacheStats() {
141
+ return touchStripSegmentCache?.stats ?? createEmptyStats(DEFAULT_TOUCH_STRIP_CACHE_MAX_BYTES);
126
142
  }
127
143
  //#endregion
128
- export { getImageCache, getTouchstripCache, getTouchstripNativeCache };
144
+ export { getImageCache, getImageCacheStats, getTouchStripCache, getTouchStripCacheStats, getTouchStripSegmentCache, getTouchStripSegmentCacheStats };
@@ -16,7 +16,7 @@ export interface RenderMetrics {
16
16
  /** Image cache memory usage in bytes. */
17
17
  imageCacheBytes: number;
18
18
  /** TouchStrip cache memory usage in bytes. */
19
- touchstripCacheBytes: number;
19
+ touchStripCacheBytes: number;
20
20
  }
21
21
  declare class MetricsCollector {
22
22
  private _flushCount;
@@ -26,13 +26,12 @@ declare class MetricsCollector {
26
26
  private _hashDedupCount;
27
27
  private _totalRenderMs;
28
28
  private _peakRenderMs;
29
- private _cumFlushCount;
30
- private _cumRenderCount;
31
- private _cumCacheHitCount;
32
- private _cumDirtySkipCount;
33
- private _cumHashDedupCount;
34
- private _cumTotalRenderMs;
35
- private _cumPeakRenderMs;
29
+ private _lastFlush;
30
+ private _lastRender;
31
+ private _lastCacheHit;
32
+ private _lastDirtySkip;
33
+ private _lastHashDedup;
34
+ private _lastTotalRenderMs;
36
35
  private _reportTimer;
37
36
  private _enabled;
38
37
  /** Enable periodic reporting. Call once during plugin init in debug mode. */
@@ -49,9 +48,9 @@ declare class MetricsCollector {
49
48
  recordHashDedup(): void;
50
49
  /** Record a completed render with its duration in milliseconds. */
51
50
  recordRender(renderMs: number): void;
52
- /** Get current snapshot of all metrics (cumulative, never reset). */
51
+ /** Get current snapshot of all metrics (cumulative). */
53
52
  snapshot(): RenderMetrics;
54
- /** Log a summary to console (called periodically). */
53
+ /** Log a summary to console (called periodically). Computes deltas since last report. */
55
54
  private report;
56
55
  }
57
56
  export declare const metrics: MetricsCollector;
@@ -1,4 +1,4 @@
1
- import { getImageCache, getTouchstripCache, getTouchstripNativeCache } from "./image-cache.js";
1
+ import { getImageCacheStats, getTouchStripCacheStats, getTouchStripSegmentCacheStats } from "./image-cache.js";
2
2
  //#region src/render/metrics.ts
3
3
  var REPORT_INTERVAL_MS = 1e4;
4
4
  var MetricsCollector = class {
@@ -9,13 +9,12 @@ var MetricsCollector = class {
9
9
  _hashDedupCount = 0;
10
10
  _totalRenderMs = 0;
11
11
  _peakRenderMs = 0;
12
- _cumFlushCount = 0;
13
- _cumRenderCount = 0;
14
- _cumCacheHitCount = 0;
15
- _cumDirtySkipCount = 0;
16
- _cumHashDedupCount = 0;
17
- _cumTotalRenderMs = 0;
18
- _cumPeakRenderMs = 0;
12
+ _lastFlush = 0;
13
+ _lastRender = 0;
14
+ _lastCacheHit = 0;
15
+ _lastDirtySkip = 0;
16
+ _lastHashDedup = 0;
17
+ _lastTotalRenderMs = 0;
19
18
  _reportTimer = null;
20
19
  _enabled = false;
21
20
  /** Enable periodic reporting. Call once during plugin init in debug mode. */
@@ -38,62 +37,60 @@ var MetricsCollector = class {
38
37
  /** Record a flush attempt (before any skip checks). */
39
38
  recordFlush() {
40
39
  this._flushCount++;
41
- this._cumFlushCount++;
42
40
  }
43
41
  /** Record a dirty-skip (container was clean). */
44
42
  recordDirtySkip() {
45
43
  this._dirtySkipCount++;
46
- this._cumDirtySkipCount++;
47
44
  }
48
45
  /** Record an image cache hit. */
49
46
  recordCacheHit() {
50
47
  this._cacheHitCount++;
51
- this._cumCacheHitCount++;
52
48
  }
53
49
  /** Record a post-render hash dedup (identical output). */
54
50
  recordHashDedup() {
55
51
  this._hashDedupCount++;
56
- this._cumHashDedupCount++;
57
52
  }
58
53
  /** Record a completed render with its duration in milliseconds. */
59
54
  recordRender(renderMs) {
60
55
  this._renderCount++;
61
- this._cumRenderCount++;
62
56
  this._totalRenderMs += renderMs;
63
- this._cumTotalRenderMs += renderMs;
64
57
  if (renderMs > this._peakRenderMs) this._peakRenderMs = renderMs;
65
- if (renderMs > this._cumPeakRenderMs) this._cumPeakRenderMs = renderMs;
66
58
  }
67
- /** Get current snapshot of all metrics (cumulative, never reset). */
59
+ /** Get current snapshot of all metrics (cumulative). */
68
60
  snapshot() {
69
- const imageStats = getImageCache().stats;
70
- const touchstripStats = getTouchstripCache().stats;
71
- const nativeStats = getTouchstripNativeCache().stats;
61
+ const imageStats = getImageCacheStats();
62
+ const touchStripStats = getTouchStripCacheStats();
63
+ const segmentStats = getTouchStripSegmentCacheStats();
72
64
  return {
73
- flushCount: this._cumFlushCount,
74
- renderCount: this._cumRenderCount,
75
- cacheHitCount: this._cumCacheHitCount,
76
- dirtySkipCount: this._cumDirtySkipCount,
77
- hashDedupCount: this._cumHashDedupCount,
78
- avgRenderMs: this._cumRenderCount > 0 ? this._cumTotalRenderMs / this._cumRenderCount : 0,
79
- peakRenderMs: this._cumPeakRenderMs,
65
+ flushCount: this._flushCount,
66
+ renderCount: this._renderCount,
67
+ cacheHitCount: this._cacheHitCount,
68
+ dirtySkipCount: this._dirtySkipCount,
69
+ hashDedupCount: this._hashDedupCount,
70
+ avgRenderMs: this._renderCount > 0 ? this._totalRenderMs / this._renderCount : 0,
71
+ peakRenderMs: this._peakRenderMs,
80
72
  imageCacheBytes: imageStats.bytes,
81
- touchstripCacheBytes: touchstripStats.bytes + nativeStats.bytes
73
+ touchStripCacheBytes: touchStripStats.bytes + segmentStats.bytes
82
74
  };
83
75
  }
84
- /** Log a summary to console (called periodically). */
76
+ /** Log a summary to console (called periodically). Computes deltas since last report. */
85
77
  report() {
86
- if (this._flushCount === 0) return;
87
- const m = this.snapshot();
88
- const skipRate = m.flushCount > 0 ? ((m.dirtySkipCount + m.cacheHitCount + m.hashDedupCount) / m.flushCount * 100).toFixed(1) : "0";
89
- console.log(`[@fcannizzaro/streamdeck-react] Metrics (${REPORT_INTERVAL_MS / 1e3}s): flushes=${m.flushCount} renders=${m.renderCount} cacheHits=${m.cacheHitCount} dirtySkips=${m.dirtySkipCount} hashDedups=${m.hashDedupCount} skipRate=${skipRate}% avgRender=${m.avgRenderMs.toFixed(1)}ms peak=${m.peakRenderMs.toFixed(1)}ms imgCache=${(m.imageCacheBytes / 1024).toFixed(0)}KB tbCache=${(m.touchstripCacheBytes / 1024).toFixed(0)}KB`);
90
- this._flushCount = 0;
91
- this._renderCount = 0;
92
- this._cacheHitCount = 0;
93
- this._dirtySkipCount = 0;
94
- this._hashDedupCount = 0;
95
- this._totalRenderMs = 0;
96
- this._peakRenderMs = 0;
78
+ const dFlush = this._flushCount - this._lastFlush;
79
+ if (dFlush === 0) return;
80
+ const dRender = this._renderCount - this._lastRender;
81
+ const dCacheHit = this._cacheHitCount - this._lastCacheHit;
82
+ const dDirtySkip = this._dirtySkipCount - this._lastDirtySkip;
83
+ const dHashDedup = this._hashDedupCount - this._lastHashDedup;
84
+ const dTotalMs = this._totalRenderMs - this._lastTotalRenderMs;
85
+ const skipRate = dFlush > 0 ? ((dDirtySkip + dCacheHit + dHashDedup) / dFlush * 100).toFixed(1) : "0";
86
+ const avgMs = dRender > 0 ? (dTotalMs / dRender).toFixed(1) : "0.0";
87
+ console.log(`[@fcannizzaro/streamdeck-react] Metrics (${REPORT_INTERVAL_MS / 1e3}s): flushes=${dFlush} renders=${dRender} cacheHits=${dCacheHit} dirtySkips=${dDirtySkip} hashDedups=${dHashDedup} skipRate=${skipRate}% avgRender=${avgMs}ms peak=${this._peakRenderMs.toFixed(1)}ms imgCache=${(getImageCacheStats().bytes / 1024).toFixed(0)}KB tbCache=${((getTouchStripCacheStats().bytes + getTouchStripSegmentCacheStats().bytes) / 1024).toFixed(0)}KB`);
88
+ this._lastFlush = this._flushCount;
89
+ this._lastRender = this._renderCount;
90
+ this._lastCacheHit = this._cacheHitCount;
91
+ this._lastDirtySkip = this._dirtySkipCount;
92
+ this._lastHashDedup = this._hashDedupCount;
93
+ this._lastTotalRenderMs = this._totalRenderMs;
97
94
  }
98
95
  };
99
96
  var metrics = new MetricsCollector();