@fcannizzaro/streamdeck-react 0.1.10 → 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 (76) hide show
  1. package/LICENSE +190 -21
  2. package/README.md +2 -0
  3. package/dist/action.d.ts +2 -2
  4. package/dist/action.js +1 -2
  5. package/dist/adapter/index.d.ts +2 -0
  6. package/dist/adapter/physical-device.d.ts +2 -0
  7. package/dist/adapter/physical-device.js +153 -0
  8. package/dist/adapter/types.d.ts +127 -0
  9. package/dist/bundler-shared.d.ts +11 -0
  10. package/dist/bundler-shared.js +11 -0
  11. package/dist/context/event-bus.d.ts +1 -1
  12. package/dist/context/event-bus.js +1 -1
  13. package/dist/context/touchstrip-context.d.ts +2 -0
  14. package/dist/context/touchstrip-context.js +5 -0
  15. package/dist/devtools/bridge.d.ts +35 -7
  16. package/dist/devtools/bridge.js +152 -46
  17. package/dist/devtools/highlight.d.ts +5 -0
  18. package/dist/devtools/highlight.js +107 -57
  19. package/dist/devtools/index.js +6 -0
  20. package/dist/devtools/observers/lifecycle.d.ts +4 -4
  21. package/dist/devtools/server.d.ts +6 -1
  22. package/dist/devtools/server.js +6 -1
  23. package/dist/devtools/types.d.ts +50 -6
  24. package/dist/font-inline.d.ts +5 -1
  25. package/dist/font-inline.js +8 -3
  26. package/dist/hooks/animation.d.ts +154 -0
  27. package/dist/hooks/animation.js +381 -0
  28. package/dist/hooks/events.js +2 -6
  29. package/dist/hooks/sdk.js +11 -11
  30. package/dist/hooks/touchstrip.d.ts +6 -0
  31. package/dist/hooks/touchstrip.js +37 -0
  32. package/dist/hooks/utility.js +3 -2
  33. package/dist/index.d.ts +9 -2
  34. package/dist/index.js +4 -2
  35. package/dist/manifest-codegen.d.ts +38 -0
  36. package/dist/manifest-codegen.js +110 -0
  37. package/dist/plugin.js +86 -106
  38. package/dist/reconciler/host-config.js +19 -1
  39. package/dist/reconciler/vnode.d.ts +26 -2
  40. package/dist/reconciler/vnode.js +40 -10
  41. package/dist/render/buffer-pool.d.ts +19 -0
  42. package/dist/render/buffer-pool.js +51 -0
  43. package/dist/render/cache.d.ts +29 -0
  44. package/dist/render/cache.js +137 -5
  45. package/dist/render/image-cache.d.ts +54 -0
  46. package/dist/render/image-cache.js +144 -0
  47. package/dist/render/metrics.d.ts +57 -0
  48. package/dist/render/metrics.js +98 -0
  49. package/dist/render/pipeline.d.ts +36 -1
  50. package/dist/render/pipeline.js +304 -34
  51. package/dist/render/png.d.ts +1 -1
  52. package/dist/render/png.js +26 -11
  53. package/dist/render/render-pool.d.ts +24 -0
  54. package/dist/render/render-pool.js +130 -0
  55. package/dist/render/svg.d.ts +7 -0
  56. package/dist/render/svg.js +139 -0
  57. package/dist/render/worker.d.ts +1 -0
  58. package/dist/rollup.d.ts +23 -9
  59. package/dist/rollup.js +24 -9
  60. package/dist/roots/registry.d.ts +9 -11
  61. package/dist/roots/registry.js +39 -42
  62. package/dist/roots/root.d.ts +9 -6
  63. package/dist/roots/root.js +52 -29
  64. package/dist/roots/settings-equality.d.ts +5 -0
  65. package/dist/roots/settings-equality.js +24 -0
  66. package/dist/roots/{touchbar-root.d.ts → touchstrip-root.d.ts} +30 -8
  67. package/dist/roots/touchstrip-root.js +263 -0
  68. package/dist/types.d.ts +73 -23
  69. package/dist/vite.d.ts +22 -8
  70. package/dist/vite.js +24 -8
  71. package/package.json +7 -4
  72. package/dist/context/touchbar-context.d.ts +0 -2
  73. package/dist/context/touchbar-context.js +0 -5
  74. package/dist/hooks/touchbar.d.ts +0 -6
  75. package/dist/hooks/touchbar.js +0 -37
  76. package/dist/roots/touchbar-root.js +0 -175
@@ -1,15 +1,147 @@
1
1
  //#region src/render/cache.ts
2
+ var FNV_OFFSET_BASIS = 2166136261;
3
+ var FNV_PRIME = 16777619;
4
+ var SENTINEL_NULL = 1314212940;
5
+ var SENTINEL_UNDEF = 1431192646;
6
+ var SENTINEL_NAN = 1314999841;
7
+ var SENTINEL_TRUE = 1414681925;
8
+ var SENTINEL_FALSE = 1178684499;
9
+ var SENTINEL_ARRAY = 1095914073;
10
+ var SENTINEL_OBJECT = 1329744468;
11
+ var STRIDE_THRESHOLD = 4096;
12
+ var STRIDE = 16;
13
+ /**
14
+ * Hash a raw byte buffer (Uint8Array or Buffer) or string.
15
+ *
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.
19
+ *
20
+ * Strings and small buffers always use full byte-by-byte FNV-1a.
21
+ */
2
22
  function fnv1a(input) {
3
- let hash = 2166136261;
23
+ let hash = FNV_OFFSET_BASIS;
4
24
  if (typeof input === "string") for (let i = 0; i < input.length; i++) {
5
25
  hash ^= input.charCodeAt(i);
6
- hash = Math.imul(hash, 16777619);
26
+ hash = Math.imul(hash, FNV_PRIME);
7
27
  }
8
- else for (let i = 0; i < input.length; i++) {
28
+ else if (input.length > STRIDE_THRESHOLD) {
29
+ hash = fnv1aU32(input.length, hash);
30
+ for (let i = 0; i < input.length; i += STRIDE) {
31
+ const end = Math.min(i + 4, input.length);
32
+ for (let j = i; j < end; j++) {
33
+ hash ^= input[j];
34
+ hash = Math.imul(hash, FNV_PRIME);
35
+ }
36
+ }
37
+ } else for (let i = 0; i < input.length; i++) {
9
38
  hash ^= input[i];
10
- hash = Math.imul(hash, 16777619);
39
+ hash = Math.imul(hash, FNV_PRIME);
11
40
  }
12
41
  return hash >>> 0;
13
42
  }
43
+ /** Feed a string into a running FNV-1a hash. */
44
+ function fnv1aString(str, hash) {
45
+ for (let i = 0; i < str.length; i++) {
46
+ hash ^= str.charCodeAt(i);
47
+ hash = Math.imul(hash, FNV_PRIME);
48
+ }
49
+ return hash;
50
+ }
51
+ /** Feed a uint32 value into a running FNV-1a hash (4 bytes, big-endian). */
52
+ function fnv1aU32(value, hash) {
53
+ hash ^= value >>> 24 & 255;
54
+ hash = Math.imul(hash, FNV_PRIME);
55
+ hash ^= value >>> 16 & 255;
56
+ hash = Math.imul(hash, FNV_PRIME);
57
+ hash ^= value >>> 8 & 255;
58
+ hash = Math.imul(hash, FNV_PRIME);
59
+ hash ^= value & 255;
60
+ hash = Math.imul(hash, FNV_PRIME);
61
+ return hash;
62
+ }
63
+ var MAX_HASH_DEPTH = 10;
64
+ /** Hash an arbitrary JS value into a running FNV-1a hash. */
65
+ function hashValue(value, hash, depth = 0) {
66
+ if (depth > MAX_HASH_DEPTH) return hash;
67
+ if (value === null) return fnv1aU32(SENTINEL_NULL, hash);
68
+ if (value === void 0) return fnv1aU32(SENTINEL_UNDEF, hash);
69
+ switch (typeof value) {
70
+ case "string": return fnv1aString(value, hash);
71
+ case "number":
72
+ if (Number.isNaN(value)) return fnv1aU32(SENTINEL_NAN, hash);
73
+ return fnv1aString(String(value), hash);
74
+ case "boolean": return fnv1aU32(value ? SENTINEL_TRUE : SENTINEL_FALSE, hash);
75
+ case "function":
76
+ case "symbol": return hash;
77
+ case "object": {
78
+ if (Array.isArray(value)) {
79
+ hash = fnv1aU32(SENTINEL_ARRAY, hash);
80
+ hash = fnv1aU32(value.length, hash);
81
+ for (let i = 0; i < value.length; i++) hash = hashValue(value[i], hash, depth + 1);
82
+ return hash;
83
+ }
84
+ hash = fnv1aU32(SENTINEL_OBJECT, hash);
85
+ const keys = Object.keys(value).sort();
86
+ hash = fnv1aU32(keys.length, hash);
87
+ for (const key of keys) {
88
+ const v = value[key];
89
+ if (typeof v === "function" || typeof v === "symbol") continue;
90
+ hash = fnv1aString(key, hash);
91
+ hash = hashValue(v, hash, depth + 1);
92
+ }
93
+ return hash;
94
+ }
95
+ default: return fnv1aString(String(value), hash);
96
+ }
97
+ }
98
+ /**
99
+ * Compute (or return cached) Merkle hash for a single VNode.
100
+ * The hash incorporates: type, text, props (sorted, functions skipped),
101
+ * children count, and children hashes (recursive).
102
+ */
103
+ function computeHash(node) {
104
+ if (node._hashValid && node._hash !== void 0) return node._hash;
105
+ let hash = FNV_OFFSET_BASIS;
106
+ hash = fnv1aString(node.type, hash);
107
+ if (node.text !== void 0) hash = fnv1aString(node.text, hash);
108
+ const keys = node._sortedPropKeys ??= Object.keys(node.props).sort();
109
+ for (const key of keys) {
110
+ const value = node.props[key];
111
+ if (typeof value === "function" || typeof value === "symbol") continue;
112
+ hash = fnv1aString(key, hash);
113
+ hash = hashValue(value, hash);
114
+ }
115
+ hash = fnv1aU32(node.children.length, hash);
116
+ for (const child of node.children) hash = fnv1aU32(computeHash(child), hash);
117
+ node._hash = hash >>> 0;
118
+ node._hashValid = true;
119
+ return node._hash;
120
+ }
121
+ /**
122
+ * Compute the Merkle hash for an entire VContainer tree.
123
+ * Returns 0 for empty containers.
124
+ */
125
+ function computeTreeHash(container) {
126
+ if (container.children.length === 0) return 0;
127
+ let hash = FNV_OFFSET_BASIS;
128
+ hash = fnv1aU32(container.children.length, hash);
129
+ for (const child of container.children) hash = fnv1aU32(computeHash(child), hash);
130
+ return hash >>> 0;
131
+ }
132
+ function computeCacheKey(treeHash, width, height, dpr, format) {
133
+ let key = treeHash;
134
+ key = fnv1aU32(width, key);
135
+ key = fnv1aU32(height, key);
136
+ key = fnv1aU32(Math.round(dpr * 100), key);
137
+ key = fnv1aString(format, key);
138
+ return key >>> 0;
139
+ }
140
+ function computeTouchStripSegmentCacheKey(treeHash, width, height, dpr, columns) {
141
+ let key = computeCacheKey(treeHash, width, height, dpr, "png");
142
+ key = fnv1aU32(columns.length, key);
143
+ for (const col of columns) key = fnv1aU32(col, key);
144
+ return key >>> 0;
145
+ }
14
146
  //#endregion
15
- export { fnv1a };
147
+ export { computeCacheKey, computeTouchStripSegmentCacheKey, computeTreeHash, fnv1a };
@@ -0,0 +1,54 @@
1
+ export interface CacheStats {
2
+ /** Number of entries currently in the cache. */
3
+ entries: number;
4
+ /** Current memory usage in bytes. */
5
+ bytes: number;
6
+ /** Maximum memory budget in bytes. */
7
+ maxBytes: number;
8
+ /** Total cache hits since creation or last reset. */
9
+ hits: number;
10
+ /** Total cache misses since creation or last reset. */
11
+ misses: number;
12
+ }
13
+ /**
14
+ * Byte-bounded LRU cache. Evicts least-recently-used entries when the
15
+ * total byte size exceeds `maxBytes`.
16
+ *
17
+ * Generic over value type: use `string` for data URI caching (keys/dials),
18
+ * `Buffer` for raw RGBA caching (TouchStrip).
19
+ */
20
+ export declare class ImageCache<V = string> {
21
+ private maxBytes;
22
+ private map;
23
+ private head;
24
+ private tail;
25
+ private currentBytes;
26
+ private _hits;
27
+ private _misses;
28
+ constructor(maxBytes: number);
29
+ /** Retrieve a cached value. Returns `undefined` on miss. Promotes to MRU on hit. */
30
+ get(key: number): V | undefined;
31
+ /** Insert or update a cache entry. Evicts LRU entries if over budget. */
32
+ set(key: number, value: V, byteSize: number): void;
33
+ /** Clear all entries and reset stats. */
34
+ clear(): void;
35
+ /** Current cache statistics. */
36
+ get stats(): CacheStats;
37
+ private moveToHead;
38
+ private evictTail;
39
+ private evictUntilUnderBudget;
40
+ }
41
+ /** Get or create the shared image cache for data URIs. */
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>;
45
+ /**
46
+ * Get or create the shared TouchStrip segment URI cache.
47
+ * Stores sorted `[column, dataUri]` tuples per tree hash + column config.
48
+ */
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;
53
+ /** Reset all caches (for testing or config changes). */
54
+ export declare function resetCaches(): void;
@@ -0,0 +1,144 @@
1
+ //#region src/render/image-cache.ts
2
+ /**
3
+ * Byte-bounded LRU cache. Evicts least-recently-used entries when the
4
+ * total byte size exceeds `maxBytes`.
5
+ *
6
+ * Generic over value type: use `string` for data URI caching (keys/dials),
7
+ * `Buffer` for raw RGBA caching (TouchStrip).
8
+ */
9
+ var ImageCache = class {
10
+ map = /* @__PURE__ */ new Map();
11
+ head = null;
12
+ tail = null;
13
+ currentBytes = 0;
14
+ _hits = 0;
15
+ _misses = 0;
16
+ constructor(maxBytes) {
17
+ this.maxBytes = maxBytes;
18
+ }
19
+ /** Retrieve a cached value. Returns `undefined` on miss. Promotes to MRU on hit. */
20
+ get(key) {
21
+ const entry = this.map.get(key);
22
+ if (entry == null) {
23
+ this._misses++;
24
+ return;
25
+ }
26
+ this._hits++;
27
+ this.moveToHead(entry);
28
+ return entry.value;
29
+ }
30
+ /** Insert or update a cache entry. Evicts LRU entries if over budget. */
31
+ set(key, value, byteSize) {
32
+ const existing = this.map.get(key);
33
+ if (existing != null) {
34
+ this.currentBytes -= existing.byteSize;
35
+ existing.value = value;
36
+ existing.byteSize = byteSize;
37
+ this.currentBytes += byteSize;
38
+ this.moveToHead(existing);
39
+ this.evictUntilUnderBudget();
40
+ return;
41
+ }
42
+ while (this.currentBytes + byteSize > this.maxBytes && this.tail != null) this.evictTail();
43
+ if (byteSize > this.maxBytes) return;
44
+ const entry = {
45
+ key,
46
+ value,
47
+ byteSize,
48
+ prev: null,
49
+ next: this.head
50
+ };
51
+ if (this.head != null) this.head.prev = entry;
52
+ this.head = entry;
53
+ if (this.tail == null) this.tail = entry;
54
+ this.map.set(key, entry);
55
+ this.currentBytes += byteSize;
56
+ }
57
+ /** Clear all entries and reset stats. */
58
+ clear() {
59
+ this.map.clear();
60
+ this.head = null;
61
+ this.tail = null;
62
+ this.currentBytes = 0;
63
+ this._hits = 0;
64
+ this._misses = 0;
65
+ }
66
+ /** Current cache statistics. */
67
+ get stats() {
68
+ return {
69
+ entries: this.map.size,
70
+ bytes: this.currentBytes,
71
+ maxBytes: this.maxBytes,
72
+ hits: this._hits,
73
+ misses: this._misses
74
+ };
75
+ }
76
+ moveToHead(entry) {
77
+ if (entry === this.head) return;
78
+ if (entry.prev != null) entry.prev.next = entry.next;
79
+ if (entry.next != null) entry.next.prev = entry.prev;
80
+ if (entry === this.tail) this.tail = entry.prev;
81
+ entry.prev = null;
82
+ entry.next = this.head;
83
+ if (this.head != null) this.head.prev = entry;
84
+ this.head = entry;
85
+ }
86
+ evictTail() {
87
+ if (this.tail == null) return;
88
+ const evicted = this.tail;
89
+ this.tail = evicted.prev;
90
+ if (this.tail != null) this.tail.next = null;
91
+ else this.head = null;
92
+ this.currentBytes -= evicted.byteSize;
93
+ this.map.delete(evicted.key);
94
+ }
95
+ evictUntilUnderBudget() {
96
+ while (this.currentBytes > this.maxBytes && this.tail != null) this.evictTail();
97
+ }
98
+ };
99
+ var DEFAULT_IMAGE_CACHE_MAX_BYTES = 16 * 1024 * 1024;
100
+ var DEFAULT_TOUCH_STRIP_CACHE_MAX_BYTES = 8 * 1024 * 1024;
101
+ /** Shared image cache for key/dial data URIs. */
102
+ var imageCache = 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
+ /** Get or create the shared image cache for data URIs. */
108
+ function getImageCache(maxBytes) {
109
+ if (imageCache == null) imageCache = new ImageCache(maxBytes ?? DEFAULT_IMAGE_CACHE_MAX_BYTES);
110
+ return imageCache;
111
+ }
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
+ }
117
+ /**
118
+ * Get or create the shared TouchStrip segment URI cache.
119
+ * Stores sorted `[column, dataUri]` tuples per tree hash + column config.
120
+ */
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);
142
+ }
143
+ //#endregion
144
+ export { getImageCache, getImageCacheStats, getTouchStripCache, getTouchStripCacheStats, getTouchStripSegmentCache, getTouchStripSegmentCacheStats };
@@ -0,0 +1,57 @@
1
+ export interface RenderMetrics {
2
+ /** Total flush() calls (render attempts). */
3
+ flushCount: number;
4
+ /** Flushes that reached the Takumi renderer. */
5
+ renderCount: number;
6
+ /** Tree hash cache hits (Phase 3). */
7
+ cacheHitCount: number;
8
+ /** Skipped due to clean tree — dirty flag check (Phase 2). */
9
+ dirtySkipCount: number;
10
+ /** Skipped due to identical output — post-render FNV-1a dedup. */
11
+ hashDedupCount: number;
12
+ /** Average Takumi render time in milliseconds. */
13
+ avgRenderMs: number;
14
+ /** Peak (worst-case) render time in milliseconds. */
15
+ peakRenderMs: number;
16
+ /** Image cache memory usage in bytes. */
17
+ imageCacheBytes: number;
18
+ /** TouchStrip cache memory usage in bytes. */
19
+ touchStripCacheBytes: number;
20
+ }
21
+ declare class MetricsCollector {
22
+ private _flushCount;
23
+ private _renderCount;
24
+ private _cacheHitCount;
25
+ private _dirtySkipCount;
26
+ private _hashDedupCount;
27
+ private _totalRenderMs;
28
+ private _peakRenderMs;
29
+ private _lastFlush;
30
+ private _lastRender;
31
+ private _lastCacheHit;
32
+ private _lastDirtySkip;
33
+ private _lastHashDedup;
34
+ private _lastTotalRenderMs;
35
+ private _reportTimer;
36
+ private _enabled;
37
+ /** Enable periodic reporting. Call once during plugin init in debug mode. */
38
+ enable(): void;
39
+ /** Disable periodic reporting and clear the timer. */
40
+ disable(): void;
41
+ /** Record a flush attempt (before any skip checks). */
42
+ recordFlush(): void;
43
+ /** Record a dirty-skip (container was clean). */
44
+ recordDirtySkip(): void;
45
+ /** Record an image cache hit. */
46
+ recordCacheHit(): void;
47
+ /** Record a post-render hash dedup (identical output). */
48
+ recordHashDedup(): void;
49
+ /** Record a completed render with its duration in milliseconds. */
50
+ recordRender(renderMs: number): void;
51
+ /** Get current snapshot of all metrics (cumulative). */
52
+ snapshot(): RenderMetrics;
53
+ /** Log a summary to console (called periodically). Computes deltas since last report. */
54
+ private report;
55
+ }
56
+ export declare const metrics: MetricsCollector;
57
+ export {};
@@ -0,0 +1,98 @@
1
+ import { getImageCacheStats, getTouchStripCacheStats, getTouchStripSegmentCacheStats } from "./image-cache.js";
2
+ //#region src/render/metrics.ts
3
+ var REPORT_INTERVAL_MS = 1e4;
4
+ var MetricsCollector = class {
5
+ _flushCount = 0;
6
+ _renderCount = 0;
7
+ _cacheHitCount = 0;
8
+ _dirtySkipCount = 0;
9
+ _hashDedupCount = 0;
10
+ _totalRenderMs = 0;
11
+ _peakRenderMs = 0;
12
+ _lastFlush = 0;
13
+ _lastRender = 0;
14
+ _lastCacheHit = 0;
15
+ _lastDirtySkip = 0;
16
+ _lastHashDedup = 0;
17
+ _lastTotalRenderMs = 0;
18
+ _reportTimer = null;
19
+ _enabled = false;
20
+ /** Enable periodic reporting. Call once during plugin init in debug mode. */
21
+ enable() {
22
+ if (this._enabled) return;
23
+ this._enabled = true;
24
+ this._reportTimer = setInterval(() => {
25
+ this.report();
26
+ }, REPORT_INTERVAL_MS);
27
+ if (typeof this._reportTimer === "object" && "unref" in this._reportTimer) this._reportTimer.unref();
28
+ }
29
+ /** Disable periodic reporting and clear the timer. */
30
+ disable() {
31
+ if (this._reportTimer != null) {
32
+ clearInterval(this._reportTimer);
33
+ this._reportTimer = null;
34
+ }
35
+ this._enabled = false;
36
+ }
37
+ /** Record a flush attempt (before any skip checks). */
38
+ recordFlush() {
39
+ this._flushCount++;
40
+ }
41
+ /** Record a dirty-skip (container was clean). */
42
+ recordDirtySkip() {
43
+ this._dirtySkipCount++;
44
+ }
45
+ /** Record an image cache hit. */
46
+ recordCacheHit() {
47
+ this._cacheHitCount++;
48
+ }
49
+ /** Record a post-render hash dedup (identical output). */
50
+ recordHashDedup() {
51
+ this._hashDedupCount++;
52
+ }
53
+ /** Record a completed render with its duration in milliseconds. */
54
+ recordRender(renderMs) {
55
+ this._renderCount++;
56
+ this._totalRenderMs += renderMs;
57
+ if (renderMs > this._peakRenderMs) this._peakRenderMs = renderMs;
58
+ }
59
+ /** Get current snapshot of all metrics (cumulative). */
60
+ snapshot() {
61
+ const imageStats = getImageCacheStats();
62
+ const touchStripStats = getTouchStripCacheStats();
63
+ const segmentStats = getTouchStripSegmentCacheStats();
64
+ return {
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,
72
+ imageCacheBytes: imageStats.bytes,
73
+ touchStripCacheBytes: touchStripStats.bytes + segmentStats.bytes
74
+ };
75
+ }
76
+ /** Log a summary to console (called periodically). Computes deltas since last report. */
77
+ report() {
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;
94
+ }
95
+ };
96
+ var metrics = new MetricsCollector();
97
+ //#endregion
98
+ export { metrics };
@@ -1,13 +1,48 @@
1
1
  import { Renderer, OutputFormat } from '@takumi-rs/core';
2
- import { VContainer } from '../reconciler/vnode';
2
+ import { Node as TakumiNode } from '@takumi-rs/helpers';
3
+ import { VContainer, VNode } from '../reconciler/vnode';
4
+ import { CacheStats } from './image-cache';
5
+ import { RenderPool } from './render-pool';
6
+ /** Per-render timing and diagnostic data exposed via `RenderConfig.onProfile`. */
7
+ export interface RenderProfile {
8
+ /** Time to convert VNode tree to Takumi node tree (ms). */
9
+ vnodeConversionMs: number;
10
+ takumiRenderMs: number;
11
+ hashMs: number;
12
+ base64Ms: number;
13
+ totalMs: number;
14
+ skipped: boolean;
15
+ /** Whether this render was served from the image cache. */
16
+ cacheHit: boolean;
17
+ treeDepth: number;
18
+ nodeCount: number;
19
+ /** Image cache statistics at the time of this render. */
20
+ cacheStats: CacheStats | null;
21
+ }
3
22
  export interface RenderConfig {
4
23
  renderer: Renderer;
5
24
  imageFormat: OutputFormat;
6
25
  caching: boolean;
7
26
  devicePixelRatio: number;
27
+ /** Enable performance diagnostics (duplicate detection, depth warnings). */
28
+ debug: boolean;
29
+ /** Maximum image cache size in bytes. Set to 0 to disable. @default 16777216 (16 MB) */
30
+ imageCacheMaxBytes: number;
31
+ /** Maximum TouchStrip cache size in bytes. Set to 0 to disable. @default 8388608 (8 MB) */
32
+ touchStripCacheMaxBytes: number;
33
+ /** Worker thread pool for offloading Takumi renders. null = main-thread rendering. */
34
+ renderPool: RenderPool | null;
8
35
  /** DevTools callback. Called after a non-null render with the container and data URI. */
9
36
  onRender?: (container: VContainer, dataUri: string) => void;
37
+ /** Profiling callback. Called after every renderToDataUri / renderToRaw attempt. */
38
+ onProfile?: (profile: RenderProfile) => void;
10
39
  }
40
+ /** Build the root Takumi container wrapping the VNode children. */
41
+ export declare function buildTakumiRoot(container: VContainer): TakumiNode;
42
+ export declare function measureTree(nodes: VNode[]): {
43
+ depth: number;
44
+ count: number;
45
+ };
11
46
  export declare function bufferToDataUri(buffer: Buffer | Uint8Array, format: string): string;
12
47
  export declare function renderToDataUri(container: VContainer, width: number, height: number, config: RenderConfig): Promise<string | null>;
13
48
  export interface RawRenderResult {