@camstack/addon-pipeline 1.2.296 → 1.2.298
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/dist/audio-analyzer/index.js +571 -305
- package/dist/audio-analyzer/index.mjs +571 -305
- package/dist/{default-detection-model-D1daTtqT.js → default-detection-model-BTJZvSr_.js} +368 -5
- package/dist/{default-detection-model-Co578D8C.mjs → default-detection-model-Bim_g_Ui.mjs} +339 -6
- package/dist/detection-pipeline/index.js +521 -217
- package/dist/detection-pipeline/index.mjs +520 -216
- package/dist/{dist-CCd0Q3nr.mjs → dist-DVz_88D_.mjs} +80 -123
- package/dist/{dist-8up-f2TX.js → dist-L_HlB0At.js} +91 -140
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-lpQgHes9.js → node-CmzZ_-zB.js} +1 -1
- package/dist/{node-DgMSXSWP.mjs → node-Cr8Ch1ZN.mjs} +1 -1
- package/dist/pipeline-runner/index.js +213 -203
- package/dist/pipeline-runner/index.mjs +213 -203
- package/dist/{process-memory-DFC_O5zE.mjs → process-memory-DO7La1yB.mjs} +1 -1
- package/dist/{process-memory-CX_92V_r.js → process-memory-FfRDGu11.js} +1 -1
- package/dist/recorder/index.js +3 -3
- package/dist/recorder/index.mjs +3 -3
- package/dist/{segment-demux-js-FZbBuk3F.mjs → segment-demux-js-BuioTwCh.mjs} +1 -1
- package/dist/{segment-demux-js-DzBx6NN2.js → segment-demux-js-Cd7btWna.js} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_i7oFBl.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CsbrrsyY.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DisouA2F.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dti8Ex88.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-BPtppL3W.mjs → hostInit-NCsHzij7.mjs} +2 -2
- package/dist/stream-broker/index.js +3 -3
- package/dist/stream-broker/index.mjs +3 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DUGQKsKL.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CkbplMHA.mjs +0 -26
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-emK7D4bc.js");
|
|
2
|
-
const require_dist = require("./dist-
|
|
2
|
+
const require_dist = require("./dist-L_HlB0At.js");
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
4
|
let node_fs = require("node:fs");
|
|
5
5
|
node_fs = require_chunk.__toESM(node_fs, 1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { an as isSoftwareDecode, en as errMsg, nn as Fmp4BoxSplitter, rn as buildFfmpegArgs } from "./dist-DVz_88D_.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_node = require("../node-
|
|
8
|
-
const require_default_detection_model = require("../default-detection-model-
|
|
6
|
+
const require_dist = require("../dist-L_HlB0At.js");
|
|
7
|
+
const require_node = require("../node-CmzZ_-zB.js");
|
|
8
|
+
const require_default_detection_model = require("../default-detection-model-BTJZvSr_.js");
|
|
9
9
|
const require_worker_protocol = require("../worker-protocol-C-G8qmye.js");
|
|
10
10
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
11
11
|
const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
|
|
@@ -638,6 +638,28 @@ function positiveOrUndefined(value) {
|
|
|
638
638
|
return Number.isFinite(value) && value > 0 ? value : void 0;
|
|
639
639
|
}
|
|
640
640
|
//#endregion
|
|
641
|
+
//#region src/pipeline-runner/retained-reader-hold.ts
|
|
642
|
+
/**
|
|
643
|
+
* Run `read` with `handle`'s parked frame held as a CONSUMER for its whole
|
|
644
|
+
* duration (D656).
|
|
645
|
+
*
|
|
646
|
+
* `getNativeCrop` has two rungs: a native crop, then — on a miss — the retained
|
|
647
|
+
* full-frame fallback. Between them nothing is in flight: the crop's reply
|
|
648
|
+
* settles the worker's last pending request in the same turn, and the fallback's
|
|
649
|
+
* `toBuffer` is only sent on a later microtask. A parked frame does not keep a
|
|
650
|
+
* draining worker alive, so without this hold the drain killed the worker in
|
|
651
|
+
* that gap and the fallback was lost. One rule instead of a timing trick: a
|
|
652
|
+
* read in progress is a consumer.
|
|
653
|
+
*/
|
|
654
|
+
async function withRetainedReader(store, handle, read) {
|
|
655
|
+
const release = store?.holdReader(handle) ?? null;
|
|
656
|
+
try {
|
|
657
|
+
return await read();
|
|
658
|
+
} finally {
|
|
659
|
+
release?.();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
//#endregion
|
|
641
663
|
//#region src/session-decode/native-crop-lease-age.ts
|
|
642
664
|
/**
|
|
643
665
|
* THE age bound on a native crop lease (ADR-0419).
|
|
@@ -980,6 +1002,8 @@ function createRetainedContentFallback(registry, ref) {
|
|
|
980
1002
|
timestamp: ref.timestamp,
|
|
981
1003
|
...ref.capturedAt !== void 0 ? { capturedAt: ref.capturedAt } : {}
|
|
982
1004
|
};
|
|
1005
|
+
} catch {
|
|
1006
|
+
return null;
|
|
983
1007
|
} finally {
|
|
984
1008
|
lease.release(reason);
|
|
985
1009
|
}
|
|
@@ -1422,6 +1446,14 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1422
1446
|
if (current) current.hydrating = void 0;
|
|
1423
1447
|
}
|
|
1424
1448
|
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Hold `handle`'s parked frame as a reader until the returned release runs
|
|
1451
|
+
* (D656). `null` when the handle has no un-hydrated fallback — bytes already
|
|
1452
|
+
* in RAM need no worker.
|
|
1453
|
+
*/
|
|
1454
|
+
holdReader(handle) {
|
|
1455
|
+
return this.entries.get(keyOf(handle))?.fallback?.holdReader?.() ?? null;
|
|
1456
|
+
}
|
|
1425
1457
|
/** Drop every retained frame (node/addon shutdown). */
|
|
1426
1458
|
clear() {
|
|
1427
1459
|
for (const entry of this.entries.values()) entry.fallback?.release?.();
|
|
@@ -1588,6 +1620,25 @@ var SESSION_DECODE_DRAIN_BOUND_MS = 1500;
|
|
|
1588
1620
|
*/
|
|
1589
1621
|
var NATIVE_CROP_REFUSED_DRAINING = "session draining — crop requested after teardown";
|
|
1590
1622
|
/**
|
|
1623
|
+
* The refusal for a `toBuffer` asked for once the worker has been told to exit
|
|
1624
|
+
* (D656). A read during a DRAIN is served — that is what the drain is for — but
|
|
1625
|
+
* after the kill no reply can come, and a raw rejection read as a worker fault.
|
|
1626
|
+
*/
|
|
1627
|
+
var TO_BUFFER_REFUSED_KILLED = "session killed — read requested after the worker was told to exit";
|
|
1628
|
+
/** The refusal for a `toBuffer` the worker is no longer there to answer. */
|
|
1629
|
+
var TO_BUFFER_REFUSED_EXITED = "worker already exited";
|
|
1630
|
+
/** A read the session declined to send, by name — never a worker fault. */
|
|
1631
|
+
var SessionDecodeReadRefusedError = class extends Error {
|
|
1632
|
+
reason;
|
|
1633
|
+
frameId;
|
|
1634
|
+
constructor(reason, frameId) {
|
|
1635
|
+
super(`session-decode-coordinator: ${reason} (frameId=${frameId})`);
|
|
1636
|
+
this.reason = reason;
|
|
1637
|
+
this.frameId = frameId;
|
|
1638
|
+
this.name = "SessionDecodeReadRefusedError";
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
/**
|
|
1591
1642
|
* How often the cold-start wait REPORTS that it is still waiting. Task 0
|
|
1592
1643
|
* live-measured cold-start (restream dial + hwaccel context + keyframe wait) at
|
|
1593
1644
|
* 5-8s, so this must tolerate a slow-but-healthy start.
|
|
@@ -1671,11 +1722,18 @@ var SessionDecodeSession = class {
|
|
|
1671
1722
|
/** Set when `kill()` is sent, so handleExit can tell an intentional stop from a death. */
|
|
1672
1723
|
killSent = false;
|
|
1673
1724
|
/**
|
|
1674
|
-
* Images yielded
|
|
1675
|
-
*
|
|
1676
|
-
*
|
|
1725
|
+
* Images yielded and not yet closed that a CONSUMER still holds — queued,
|
|
1726
|
+
* in flight, or being read. Each dies with the worker and somebody was going
|
|
1727
|
+
* to read it first. This is what a drain waits on.
|
|
1728
|
+
*/
|
|
1729
|
+
consumerImages = 0;
|
|
1730
|
+
/**
|
|
1731
|
+
* Images yielded and not yet closed that only a RETENTION holder keeps —
|
|
1732
|
+
* parked for a read that may come later (the on-demand JPEG fallback, D656).
|
|
1733
|
+
* They die with the worker too, but nobody reads them inside any bound worth
|
|
1734
|
+
* holding a worker for, so a drain does NOT wait on them.
|
|
1677
1735
|
*/
|
|
1678
|
-
|
|
1736
|
+
retainedImages = 0;
|
|
1679
1737
|
/** Armed while a drain waits; its expiry kills with frames still held. */
|
|
1680
1738
|
drainTimer = null;
|
|
1681
1739
|
sigkillTimer = null;
|
|
@@ -1729,7 +1787,8 @@ var SessionDecodeSession = class {
|
|
|
1729
1787
|
}
|
|
1730
1788
|
/** Send `toBuffer` for `frameId` and resolve with the matching `buffer` reply's bytes. */
|
|
1731
1789
|
requestBuffer(frameId, opts) {
|
|
1732
|
-
if (this.exited) return Promise.reject(
|
|
1790
|
+
if (this.exited) return Promise.reject(new SessionDecodeReadRefusedError(TO_BUFFER_REFUSED_EXITED, frameId));
|
|
1791
|
+
if (this.killSent) return Promise.reject(new SessionDecodeReadRefusedError(TO_BUFFER_REFUSED_KILLED, frameId));
|
|
1733
1792
|
return new Promise((resolve, reject) => {
|
|
1734
1793
|
const waiter = {
|
|
1735
1794
|
resolve,
|
|
@@ -1818,13 +1877,28 @@ var SessionDecodeSession = class {
|
|
|
1818
1877
|
frameId
|
|
1819
1878
|
});
|
|
1820
1879
|
}
|
|
1821
|
-
/** A yielded image the consumer now holds — see {@link
|
|
1880
|
+
/** A yielded image the consumer now holds — see {@link consumerImages}. */
|
|
1822
1881
|
noteImageOpened() {
|
|
1823
|
-
this.
|
|
1882
|
+
this.consumerImages += 1;
|
|
1824
1883
|
}
|
|
1825
|
-
/**
|
|
1826
|
-
|
|
1827
|
-
|
|
1884
|
+
/**
|
|
1885
|
+
* An open image moved between a consumer and a retention-only holder
|
|
1886
|
+
* (D656). Losing its last consumer may complete a waiting drain.
|
|
1887
|
+
*/
|
|
1888
|
+
noteImageHolder(pendingConsumer) {
|
|
1889
|
+
if (pendingConsumer) {
|
|
1890
|
+
this.retainedImages = Math.max(0, this.retainedImages - 1);
|
|
1891
|
+
this.consumerImages += 1;
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
this.consumerImages = Math.max(0, this.consumerImages - 1);
|
|
1895
|
+
this.retainedImages += 1;
|
|
1896
|
+
this.settleDrainIfDone();
|
|
1897
|
+
}
|
|
1898
|
+
/** A yielded image was closed; a waiting drain may now be done. */
|
|
1899
|
+
noteImageClosed(pendingConsumer) {
|
|
1900
|
+
if (pendingConsumer) this.consumerImages = Math.max(0, this.consumerImages - 1);
|
|
1901
|
+
else this.retainedImages = Math.max(0, this.retainedImages - 1);
|
|
1828
1902
|
this.settleDrainIfDone();
|
|
1829
1903
|
}
|
|
1830
1904
|
/**
|
|
@@ -1875,15 +1949,29 @@ var SessionDecodeSession = class {
|
|
|
1875
1949
|
this.tryKill("SIGKILL");
|
|
1876
1950
|
}
|
|
1877
1951
|
/**
|
|
1878
|
-
* Frames
|
|
1952
|
+
* Frames a CONSUMER still holds, reads still waiting on the worker, and
|
|
1879
1953
|
* native crops already REQUESTED. A crop asked for before the teardown
|
|
1880
1954
|
* request is waited for; one asked for after it is refused by name — post-analysis's
|
|
1881
1955
|
* ordinary crop arrives 2.2–9.9 s after the frame, past any bound worth
|
|
1882
1956
|
* holding a worker for, and is served from subject tiles that die with the
|
|
1883
1957
|
* worker either way.
|
|
1958
|
+
*
|
|
1959
|
+
* A frame only PARKED for such a late read ({@link retainedImages}) is not
|
|
1960
|
+
* counted, for the same reason (D656): counting it made nearly every
|
|
1961
|
+
* teardown wait the full bound for frames nobody was going to read. Its read,
|
|
1962
|
+
* once STARTED, is a pending read like any other and is waited for.
|
|
1884
1963
|
*/
|
|
1885
1964
|
unsettledCount() {
|
|
1886
|
-
return this.
|
|
1965
|
+
return this.consumerImages + this.pendingReadCount() + this.pendingNativeCrops.size;
|
|
1966
|
+
}
|
|
1967
|
+
/** What the session still holds, per category — every drain line carries it. */
|
|
1968
|
+
heldCounts() {
|
|
1969
|
+
return {
|
|
1970
|
+
consumerFrames: this.consumerImages,
|
|
1971
|
+
retainedFrames: this.retainedImages,
|
|
1972
|
+
pendingReads: this.pendingReadCount(),
|
|
1973
|
+
pendingCrops: this.pendingNativeCrops.size
|
|
1974
|
+
};
|
|
1887
1975
|
}
|
|
1888
1976
|
pendingReadCount() {
|
|
1889
1977
|
let n = 0;
|
|
@@ -1907,9 +1995,7 @@ var SessionDecodeSession = class {
|
|
|
1907
1995
|
this.logger.warn("session-decode-coordinator: drain bound expired — killing the worker with frames still held", {
|
|
1908
1996
|
tags: { deviceId: this.deviceId },
|
|
1909
1997
|
meta: {
|
|
1910
|
-
|
|
1911
|
-
pendingReads: this.pendingReadCount(),
|
|
1912
|
-
pendingCrops: this.pendingNativeCrops.size,
|
|
1998
|
+
...this.heldCounts(),
|
|
1913
1999
|
boundMs: SESSION_DECODE_DRAIN_BOUND_MS
|
|
1914
2000
|
}
|
|
1915
2001
|
});
|
|
@@ -1931,6 +2017,13 @@ var SessionDecodeSession = class {
|
|
|
1931
2017
|
killNow(mode) {
|
|
1932
2018
|
if (this.killSent) return;
|
|
1933
2019
|
this.killSent = true;
|
|
2020
|
+
if (this.retainedImages > 0) this.logger.debug("session-decode-coordinator: retained frames die with the worker", {
|
|
2021
|
+
tags: { deviceId: this.deviceId },
|
|
2022
|
+
meta: {
|
|
2023
|
+
mode,
|
|
2024
|
+
...this.heldCounts()
|
|
2025
|
+
}
|
|
2026
|
+
});
|
|
1934
2027
|
this.trySend({ kind: "stop" });
|
|
1935
2028
|
if (mode === "immediate") {
|
|
1936
2029
|
this.tryKill("SIGKILL");
|
|
@@ -2032,10 +2125,7 @@ var SessionDecodeSession = class {
|
|
|
2032
2125
|
meta: {
|
|
2033
2126
|
code: info?.code ?? null,
|
|
2034
2127
|
signal: info?.signal ?? null,
|
|
2035
|
-
...this.stopRequested ? {
|
|
2036
|
-
unsettledFrames: this.openImages,
|
|
2037
|
-
pendingReads: this.pendingReadCount()
|
|
2038
|
-
} : {}
|
|
2128
|
+
...this.stopRequested ? this.heldCounts() : {}
|
|
2039
2129
|
}
|
|
2040
2130
|
});
|
|
2041
2131
|
this.exited = true;
|
|
@@ -2233,6 +2323,7 @@ var SessionDecodeSession = class {
|
|
|
2233
2323
|
/** Build the {@link FrameImage} adapter for one `frame` reply. */
|
|
2234
2324
|
function buildFrameImage(session, reply) {
|
|
2235
2325
|
let closed = false;
|
|
2326
|
+
let pendingConsumer = true;
|
|
2236
2327
|
session.noteImageOpened();
|
|
2237
2328
|
return {
|
|
2238
2329
|
width: reply.width,
|
|
@@ -2247,10 +2338,15 @@ function buildFrameImage(session, reply) {
|
|
|
2247
2338
|
cutTiles: (bboxes) => session.cutTiles(reply.frameId, bboxes),
|
|
2248
2339
|
releaseNativeLease: () => session.releaseLease(reply.frameId),
|
|
2249
2340
|
markInferred: () => session.markInferred(reply.frameId),
|
|
2341
|
+
setPendingConsumer: (pending) => {
|
|
2342
|
+
if (closed || pending === pendingConsumer) return;
|
|
2343
|
+
pendingConsumer = pending;
|
|
2344
|
+
session.noteImageHolder(pending);
|
|
2345
|
+
},
|
|
2250
2346
|
close: () => {
|
|
2251
2347
|
if (closed) return;
|
|
2252
2348
|
closed = true;
|
|
2253
|
-
session.noteImageClosed();
|
|
2349
|
+
session.noteImageClosed(pendingConsumer);
|
|
2254
2350
|
}
|
|
2255
2351
|
};
|
|
2256
2352
|
}
|
|
@@ -3927,173 +4023,6 @@ async function runDetailSubtree(deps, input) {
|
|
|
3927
4023
|
return { details };
|
|
3928
4024
|
}
|
|
3929
4025
|
//#endregion
|
|
3930
|
-
//#region src/pipeline-runner/device-jump.ts
|
|
3931
|
-
/**
|
|
3932
|
-
* Step-tree device jump (same-node, phase 1) — the PURE resolution core.
|
|
3933
|
-
*
|
|
3934
|
-
* Given the camera's elected ("effective") inference device and the node's
|
|
3935
|
-
* enabled-device roster, decide which device ONE enrichment step should run on:
|
|
3936
|
-
*
|
|
3937
|
-
* 1. A manual `jumpDeviceKey` override wins when it names a candidate that can
|
|
3938
|
-
* run the step (bypasses the auto choice; operator intent).
|
|
3939
|
-
* 2. Otherwise, if the effective device's format can run the step's model →
|
|
3940
|
-
* NO jump (the step inherits the effective device — the common case).
|
|
3941
|
-
* 3. Otherwise AUTO-jump: among the node's OTHER enabled devices whose format
|
|
3942
|
-
* can run the step (and that are under their `maxSessions` cap), pick the
|
|
3943
|
-
* weighted-least-loaded one — mirroring the balancer's per-device core.
|
|
3944
|
-
* 4. If nothing on the node can run it → `no-candidate` (the caller emits the
|
|
3945
|
-
* loud once-per-(camera,step) warn that replaces today's silent degrade).
|
|
3946
|
-
*
|
|
3947
|
-
* Framework-free + side-effect-free so the whole decision table is unit-testable
|
|
3948
|
-
* without a live runner/catalog. The `canRun(format)` predicate is injected by
|
|
3949
|
-
* the runner (which owns the model catalog); this module never imports it.
|
|
3950
|
-
*/
|
|
3951
|
-
/** The `ModelFormat` of a `<backend>:<device>` key, or `undefined` for an unknown backend. */
|
|
3952
|
-
function formatOf(deviceKey) {
|
|
3953
|
-
const colon = deviceKey.indexOf(":");
|
|
3954
|
-
return require_dist.DEVICE_BACKEND_TO_FORMAT[colon >= 0 ? deviceKey.slice(0, colon) : deviceKey];
|
|
3955
|
-
}
|
|
3956
|
-
/** True when the step's model has a build for `deviceKey`'s format. */
|
|
3957
|
-
function canRunOn(deviceKey, canRun) {
|
|
3958
|
-
const format = formatOf(deviceKey);
|
|
3959
|
-
return format !== void 0 && canRun(format);
|
|
3960
|
-
}
|
|
3961
|
-
/** Effective weighted load of a device (session count ÷ weight); lower = preferred. */
|
|
3962
|
-
function weightedLoad(d) {
|
|
3963
|
-
return d.attachedCameras / (d.weight > 0 ? d.weight : 1);
|
|
3964
|
-
}
|
|
3965
|
-
/**
|
|
3966
|
-
* Weighted-least-loaded pick with a deterministic `deviceKey` tie-break —
|
|
3967
|
-
* mirrors the node/device balancer's decision core (least `load/weight`, ties
|
|
3968
|
-
* broken stably). `devices` is assumed non-empty.
|
|
3969
|
-
*/
|
|
3970
|
-
function pickWeightedLeastLoaded(devices) {
|
|
3971
|
-
return devices.reduce((best, d) => {
|
|
3972
|
-
const dScore = weightedLoad(d);
|
|
3973
|
-
const bScore = weightedLoad(best);
|
|
3974
|
-
if (dScore < bScore) return d;
|
|
3975
|
-
if (dScore > bScore) return best;
|
|
3976
|
-
return d.deviceKey < best.deviceKey ? d : best;
|
|
3977
|
-
});
|
|
3978
|
-
}
|
|
3979
|
-
/**
|
|
3980
|
-
* Build the `canRun` predicate for ONE enrichment dispatch. The step and its
|
|
3981
|
-
* enabled descendants ride the SAME `runPipeline` call — one pool, one format
|
|
3982
|
-
* (detail-subtree contract: "its inherited children ride the same call → same
|
|
3983
|
-
* device") — so a format can run the dispatch only when EVERY catalog-known
|
|
3984
|
-
* step in the subtree ships a build for it. Gating only the subtree TOP let a
|
|
3985
|
-
* `face-detection` with a tflite build stay on the Coral while its
|
|
3986
|
-
* `face-embedding` child (no tflite build anywhere) doomed the call.
|
|
3987
|
-
* Unknown/custom addonIds (`getStepDef` → null) have no catalog to gate on
|
|
3988
|
-
* and never block. `getStepDef` is injected — this module stays catalog-free.
|
|
3989
|
-
*/
|
|
3990
|
-
function buildSubtreeCanRun(step, getStepDef) {
|
|
3991
|
-
const defs = [];
|
|
3992
|
-
const walk = (s) => {
|
|
3993
|
-
if (s.enabled === false) return;
|
|
3994
|
-
const def = getStepDef(s.addonId);
|
|
3995
|
-
if (def !== null) defs.push(def);
|
|
3996
|
-
for (const child of s.children ?? []) walk(child);
|
|
3997
|
-
};
|
|
3998
|
-
walk(step);
|
|
3999
|
-
return (format) => defs.every((def) => def.models.some((m) => m.formats[format]));
|
|
4000
|
-
}
|
|
4001
|
-
/**
|
|
4002
|
-
* Resolve the device one enrichment step runs on. See the module header for the
|
|
4003
|
-
* decision table. Pure — never throws, no I/O.
|
|
4004
|
-
*/
|
|
4005
|
-
function resolveStepDevice(input) {
|
|
4006
|
-
const { effectiveDeviceKey, manualJumpDeviceKey, canRun } = input;
|
|
4007
|
-
if (manualJumpDeviceKey !== void 0 && manualJumpDeviceKey !== effectiveDeviceKey) {
|
|
4008
|
-
const target = input.devices.find((d) => d.deviceKey === manualJumpDeviceKey);
|
|
4009
|
-
if (target && canRunOn(target.deviceKey, canRun)) return {
|
|
4010
|
-
kind: "jumped",
|
|
4011
|
-
deviceKey: target.deviceKey,
|
|
4012
|
-
reason: "manual"
|
|
4013
|
-
};
|
|
4014
|
-
}
|
|
4015
|
-
if (canRunOn(effectiveDeviceKey, canRun)) return {
|
|
4016
|
-
kind: "no-jump",
|
|
4017
|
-
deviceKey: effectiveDeviceKey
|
|
4018
|
-
};
|
|
4019
|
-
const runnable = input.devices.filter((d) => d.deviceKey !== effectiveDeviceKey && (d.maxSessions === null || d.attachedCameras < d.maxSessions) && canRunOn(d.deviceKey, canRun));
|
|
4020
|
-
if (runnable.length === 0) return {
|
|
4021
|
-
kind: "no-candidate",
|
|
4022
|
-
deviceKey: effectiveDeviceKey
|
|
4023
|
-
};
|
|
4024
|
-
return {
|
|
4025
|
-
kind: "jumped",
|
|
4026
|
-
deviceKey: pickWeightedLeastLoaded(runnable).deviceKey,
|
|
4027
|
-
reason: "auto"
|
|
4028
|
-
};
|
|
4029
|
-
}
|
|
4030
|
-
/**
|
|
4031
|
-
* Re-provision a jumped subtree for its TARGET device (D650): every step's
|
|
4032
|
-
* `modelId` / `settings` are REPLACED by the target's entry — the orchestrator
|
|
4033
|
-
* computed it as what the step would carry had the camera been elected onto
|
|
4034
|
-
* that device (its per-(node,device) base with the camera's override for it).
|
|
4035
|
-
* A step absent from the map runs with neither, i.e. the target format's
|
|
4036
|
-
* default model. The elected device's provisioning never follows a step across:
|
|
4037
|
-
* it was chosen for a format this subtree does not run on.
|
|
4038
|
-
*/
|
|
4039
|
-
function provisionSubtreeForDevice(step, provisioning) {
|
|
4040
|
-
const own = provisioning[step.addonId];
|
|
4041
|
-
const { modelId: _electedModelId, settings: _electedSettings, ...rest } = step;
|
|
4042
|
-
return {
|
|
4043
|
-
...rest,
|
|
4044
|
-
...own?.modelId !== void 0 ? { modelId: own.modelId } : {},
|
|
4045
|
-
...own?.settings !== void 0 ? { settings: own.settings } : {},
|
|
4046
|
-
...step.children !== void 0 ? { children: step.children.map((c) => provisionSubtreeForDevice(c, provisioning)) } : {}
|
|
4047
|
-
};
|
|
4048
|
-
}
|
|
4049
|
-
/**
|
|
4050
|
-
* Place ONE detail subtree (D650): which same-node device runs it
|
|
4051
|
-
* ({@link resolveStepDevice}) and, when it jumps, the provisioning it runs
|
|
4052
|
-
* with there ({@link provisionSubtreeForDevice}). Pure — the caller logs.
|
|
4053
|
-
*
|
|
4054
|
-
* A roster entry without `steps` came from an orchestrator that predates D650:
|
|
4055
|
-
* the step keeps what it was sent with, as it did before.
|
|
4056
|
-
*/
|
|
4057
|
-
function placeDetailStep(input) {
|
|
4058
|
-
const { step, effectiveDeviceKey, roster } = input;
|
|
4059
|
-
if (!effectiveDeviceKey) return {
|
|
4060
|
-
deviceKey: void 0,
|
|
4061
|
-
step,
|
|
4062
|
-
decision: null,
|
|
4063
|
-
provisioned: false
|
|
4064
|
-
};
|
|
4065
|
-
if (!roster || roster.length < 2) return {
|
|
4066
|
-
deviceKey: effectiveDeviceKey,
|
|
4067
|
-
step,
|
|
4068
|
-
decision: null,
|
|
4069
|
-
provisioned: false
|
|
4070
|
-
};
|
|
4071
|
-
const decision = resolveStepDevice({
|
|
4072
|
-
effectiveDeviceKey,
|
|
4073
|
-
devices: roster.map((d) => ({
|
|
4074
|
-
deviceKey: d.deviceKey,
|
|
4075
|
-
weight: d.weight,
|
|
4076
|
-
maxSessions: d.maxSessions,
|
|
4077
|
-
attachedCameras: input.attachedByKey.get(d.deviceKey) ?? 0
|
|
4078
|
-
})),
|
|
4079
|
-
...step.jumpDeviceKey !== void 0 ? { manualJumpDeviceKey: step.jumpDeviceKey } : {},
|
|
4080
|
-
canRun: buildSubtreeCanRun(step, input.getStepDef)
|
|
4081
|
-
});
|
|
4082
|
-
if (decision.kind !== "jumped") return {
|
|
4083
|
-
deviceKey: decision.deviceKey,
|
|
4084
|
-
step,
|
|
4085
|
-
decision,
|
|
4086
|
-
provisioned: false
|
|
4087
|
-
};
|
|
4088
|
-
const provisioning = roster.find((d) => d.deviceKey === decision.deviceKey)?.steps;
|
|
4089
|
-
return {
|
|
4090
|
-
deviceKey: decision.deviceKey,
|
|
4091
|
-
step: provisioning === void 0 ? step : provisionSubtreeForDevice(step, provisioning),
|
|
4092
|
-
decision,
|
|
4093
|
-
provisioned: provisioning !== void 0
|
|
4094
|
-
};
|
|
4095
|
-
}
|
|
4096
|
-
//#endregion
|
|
4097
4026
|
//#region src/pipeline-runner/device-load.ts
|
|
4098
4027
|
/** The backend prefix of a `<backend>:<device>` key (bare `cpu` → `cpu`). */
|
|
4099
4028
|
function backendOf(deviceKey) {
|
|
@@ -5377,7 +5306,7 @@ var ON_DEMAND_JPEG_VIEW = {
|
|
|
5377
5306
|
format: "jpeg"
|
|
5378
5307
|
};
|
|
5379
5308
|
function createOnDemandJpegFallback(registry, ref) {
|
|
5380
|
-
const lease = registry.acquire(ref);
|
|
5309
|
+
const lease = registry.acquire(ref, "retention");
|
|
5381
5310
|
let cached;
|
|
5382
5311
|
let released = false;
|
|
5383
5312
|
const releaseLease = (reason) => {
|
|
@@ -5417,6 +5346,11 @@ function createOnDemandJpegFallback(registry, ref) {
|
|
|
5417
5346
|
},
|
|
5418
5347
|
release() {
|
|
5419
5348
|
releaseLease("eviction");
|
|
5349
|
+
},
|
|
5350
|
+
holdReader() {
|
|
5351
|
+
if (released || cached !== void 0) return null;
|
|
5352
|
+
const reader = registry.acquire(ref);
|
|
5353
|
+
return reader ? () => reader.release("success") : null;
|
|
5420
5354
|
}
|
|
5421
5355
|
};
|
|
5422
5356
|
}
|
|
@@ -7262,11 +7196,14 @@ async function runStatelessStep(deps, input) {
|
|
|
7262
7196
|
return refuse(deps, input, "execution-failed", errorMessage(err));
|
|
7263
7197
|
}
|
|
7264
7198
|
if (result === null) return refuse(deps, input, "execution-failed", "the executor returned no frame result for the crop");
|
|
7199
|
+
const ranModelId = result.rootModelId;
|
|
7200
|
+
if (ranModelId === void 0 || ranModelId === "") return refuse(deps, input, "execution-failed", `the executor result names no model — refusing rather than assuming "${input.modelId}" ran`);
|
|
7201
|
+
if (ranModelId !== input.modelId) return refuse(deps, input, "model-not-servable", `the executor ran "${ranModelId}" for a call pinned to "${input.modelId}" — its output would not be comparable with the pass`);
|
|
7265
7202
|
const details = result.detections.map((detection) => toDetailResult(detection, input.stepId, crop));
|
|
7266
7203
|
return {
|
|
7267
7204
|
kind: "ran",
|
|
7268
7205
|
nodeId: deps.nodeId,
|
|
7269
|
-
modelId:
|
|
7206
|
+
modelId: ranModelId,
|
|
7270
7207
|
details
|
|
7271
7208
|
};
|
|
7272
7209
|
}
|
|
@@ -7675,6 +7612,15 @@ function tryGetStepDefinition(stepId) {
|
|
|
7675
7612
|
}
|
|
7676
7613
|
}
|
|
7677
7614
|
/**
|
|
7615
|
+
* A detail-plane step: its catalog definition consumes a parent class
|
|
7616
|
+
* (`inputClasses` non-null). Unknown/custom addonIds are root-plane — the
|
|
7617
|
+
* same reading as the executor's frame-plane skip and `deriveDetailSteps`.
|
|
7618
|
+
*/
|
|
7619
|
+
function isDetailPlaneStepId(stepId) {
|
|
7620
|
+
const def = tryGetStepDefinition(stepId);
|
|
7621
|
+
return def !== null && def.inputClasses !== null;
|
|
7622
|
+
}
|
|
7623
|
+
/**
|
|
7678
7624
|
* Precomputes the `detailSteps` announce (two-plane design) for a camera's
|
|
7679
7625
|
* configured step tree: every ENABLED step whose catalog definition is a
|
|
7680
7626
|
* detail/child step (`inputClasses !== null` — a root step like
|
|
@@ -8507,14 +8453,16 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
8507
8453
|
height: cached.height,
|
|
8508
8454
|
format: cached.format
|
|
8509
8455
|
}, pin)).frameId;
|
|
8510
|
-
await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
|
|
8456
|
+
const benchModelId = (await api.pipelineExecutor.runPipeline.mutate({ ...buildBenchRunPipelineInput({
|
|
8511
8457
|
steps: stepsToRun,
|
|
8512
8458
|
frame: sharedFrame
|
|
8513
|
-
}) }, pin);
|
|
8459
|
+
}) }, pin)).rootModelId;
|
|
8460
|
+
if (benchModelId === void 0 || benchModelId === "") throw new Error(`synthetic bench: the warmup of "${rootStep.addonId}" named no model — refusing to time an unnamed variant`);
|
|
8514
8461
|
const warmupCount = input.warmup ?? 1;
|
|
8515
8462
|
for (let w = 0; w < warmupCount; w++) await api.pipelineExecutor.inferCached.mutate({
|
|
8516
8463
|
stepId: rootStep.addonId,
|
|
8517
|
-
frameId: poolFrameId
|
|
8464
|
+
frameId: poolFrameId,
|
|
8465
|
+
modelId: benchModelId
|
|
8518
8466
|
}, pin);
|
|
8519
8467
|
const wallTimings = [];
|
|
8520
8468
|
const inferTimings = [];
|
|
@@ -8532,7 +8480,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
8532
8480
|
const t0 = performance.now();
|
|
8533
8481
|
const result = await api.pipelineExecutor.inferCached.mutate({
|
|
8534
8482
|
stepId: rootStep.addonId,
|
|
8535
|
-
frameId: poolFrameId
|
|
8483
|
+
frameId: poolFrameId,
|
|
8484
|
+
modelId: benchModelId
|
|
8536
8485
|
}, pin);
|
|
8537
8486
|
const wallMs = performance.now() - t0;
|
|
8538
8487
|
const r = result;
|
|
@@ -9207,6 +9156,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
9207
9156
|
* resolve legitimately want the lower-RES honest frame).
|
|
9208
9157
|
*/
|
|
9209
9158
|
async getNativeCrop(input) {
|
|
9159
|
+
return withRetainedReader(this.retainedFrames, input.handle, () => this.resolveNativeCropBothRungs(input));
|
|
9160
|
+
}
|
|
9161
|
+
/** {@link getNativeCrop}'s two rungs, run under its reader hold. */
|
|
9162
|
+
async resolveNativeCropBothRungs(input) {
|
|
9210
9163
|
const deviceId = this.nativeCropRegistry.deviceIdFor(input.handle) ?? 0;
|
|
9211
9164
|
const isFullFrame = input.bbox.x <= .001 && input.bbox.y <= .001 && input.bbox.w >= .999 && input.bbox.h >= .999;
|
|
9212
9165
|
this.frameLazy.noteOnDemandRequest({
|
|
@@ -9722,6 +9675,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
9722
9675
|
*/
|
|
9723
9676
|
async runDetailSubtree(input) {
|
|
9724
9677
|
const convention = await this.cropConvention.refresh(this.ctxIfReady?.api);
|
|
9678
|
+
await this.customModels.refresh(this.ctxIfReady?.api);
|
|
9725
9679
|
const outcome = await runDetailSubtree({
|
|
9726
9680
|
resolveChildSteps: (deviceId, parentClassName, filter) => this.resolveDetailChildSteps(deviceId, parentClassName, filter),
|
|
9727
9681
|
getNativeCrop: (deviceId, handle, paddedBbox) => this.getNativeCropForDetail(deviceId, handle, paddedBbox),
|
|
@@ -9850,6 +9804,13 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
9850
9804
|
* global settings, NOT from the attach config. See `crop-convention-source.ts`
|
|
9851
9805
|
* for why eleven attach builders make the attach config the wrong carrier.
|
|
9852
9806
|
*/
|
|
9807
|
+
/**
|
|
9808
|
+
* The custom-model registry, merged into the catalog the device jump judges
|
|
9809
|
+
* (D657): a custom model with a tflite build, pinned on a Coral, runs there.
|
|
9810
|
+
* Refreshed on the async boundaries (`runInference`, `runDetailSubtree`),
|
|
9811
|
+
* read synchronously by the placement.
|
|
9812
|
+
*/
|
|
9813
|
+
customModels = new require_default_detection_model.CustomModelCatalog({ warn: (message, extras) => this.ctxIfReady?.logger.warn(message, extras) });
|
|
9853
9814
|
cropConvention = new CropConventionSource({ logger: {
|
|
9854
9815
|
info: (message, extras) => this.ctxIfReady?.logger.info(message, extras),
|
|
9855
9816
|
warn: (message, extras) => this.ctxIfReady?.logger.warn(message, extras)
|
|
@@ -9953,18 +9914,63 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
9953
9914
|
*/
|
|
9954
9915
|
placeDetailStepForCamera(deviceId, step) {
|
|
9955
9916
|
const config = this.attached.get(deviceId)?.config;
|
|
9956
|
-
const placement =
|
|
9957
|
-
step,
|
|
9958
|
-
effectiveDeviceKey: config?.deviceKey,
|
|
9959
|
-
roster: config?.inferenceDevices,
|
|
9960
|
-
attachedByKey: this.perDeviceAttachedCounts(),
|
|
9961
|
-
getStepDef: tryGetStepDefinition
|
|
9962
|
-
});
|
|
9917
|
+
const placement = this.placementOf(config, step, this.perDeviceAttachedCounts());
|
|
9963
9918
|
const decision = placement.decision;
|
|
9964
9919
|
if (decision?.kind === "no-candidate") this.warnStepHasNoRunnableDevice(deviceId, step.addonId, decision.deviceKey);
|
|
9965
9920
|
else if (decision?.kind === "jumped" && config?.deviceKey) this.logStepJumped(deviceId, config.deviceKey, decision, placement);
|
|
9966
9921
|
return placement;
|
|
9967
9922
|
}
|
|
9923
|
+
/**
|
|
9924
|
+
* Where one detail subtree of this camera runs — the ONE decision both the
|
|
9925
|
+
* detail plane (which dispatches it) and the frame plane (which decides what
|
|
9926
|
+
* the elected pool pre-warms, D657) read. Pure: nothing is logged here.
|
|
9927
|
+
*/
|
|
9928
|
+
placementOf(config, step, attachedByKey) {
|
|
9929
|
+
return require_default_detection_model.placeDetailStep({
|
|
9930
|
+
step,
|
|
9931
|
+
effectiveDeviceKey: config?.deviceKey,
|
|
9932
|
+
roster: config?.inferenceDevices,
|
|
9933
|
+
attachedByKey,
|
|
9934
|
+
getStepDef: this.customModels.stepDefinition
|
|
9935
|
+
});
|
|
9936
|
+
}
|
|
9937
|
+
/**
|
|
9938
|
+
* The tree this camera's frame-plane dispatch carries (D657): a detail
|
|
9939
|
+
* subtree the placement sends to another device (or to none) is left out,
|
|
9940
|
+
* so its model is never loaded on the elected pool — it loads on its
|
|
9941
|
+
* target, with the target's provisioning, when the detail plane dispatches
|
|
9942
|
+
* it there. Said once per (camera, elected device, set left out).
|
|
9943
|
+
*/
|
|
9944
|
+
stepsForElectedPoolOf(deviceId, steps) {
|
|
9945
|
+
const config = this.attached.get(deviceId)?.config;
|
|
9946
|
+
if (config?.deviceKey === void 0 || (config.inferenceDevices?.length ?? 0) < 2) {
|
|
9947
|
+
this.noteElectedPoolDelegation(deviceId, void 0, []);
|
|
9948
|
+
return steps;
|
|
9949
|
+
}
|
|
9950
|
+
const attachedByKey = this.perDeviceAttachedCounts();
|
|
9951
|
+
const result = require_default_detection_model.stepsForElectedPool(steps, isDetailPlaneStepId, (step) => this.placementOf(config, step, attachedByKey));
|
|
9952
|
+
this.noteElectedPoolDelegation(deviceId, config.deviceKey, result.delegated);
|
|
9953
|
+
return result.delegated.length === 0 ? steps : result.steps;
|
|
9954
|
+
}
|
|
9955
|
+
/**
|
|
9956
|
+
* Say once per STATE CHANGE which detail steps a camera's elected pool no
|
|
9957
|
+
* longer loads: a camera moving A → B → A says so again on A.
|
|
9958
|
+
*/
|
|
9959
|
+
noteElectedPoolDelegation(deviceId, electedDeviceKey, delegated) {
|
|
9960
|
+
const state = `${electedDeviceKey ?? ""}|${delegated.join(",")}`;
|
|
9961
|
+
if (this.electedPoolDelegationState.get(deviceId) === state) return;
|
|
9962
|
+
this.electedPoolDelegationState.set(deviceId, state);
|
|
9963
|
+
if (delegated.length === 0) return;
|
|
9964
|
+
this.ctx?.logger.info("detail steps not loaded on the elected pool — they run elsewhere", {
|
|
9965
|
+
tags: { deviceId },
|
|
9966
|
+
meta: {
|
|
9967
|
+
electedDeviceKey: electedDeviceKey ?? null,
|
|
9968
|
+
steps: [...delegated]
|
|
9969
|
+
}
|
|
9970
|
+
});
|
|
9971
|
+
}
|
|
9972
|
+
/** Per camera, the last (elected device, steps left out) state that was logged. */
|
|
9973
|
+
electedPoolDelegationState = /* @__PURE__ */ new Map();
|
|
9968
9974
|
/** `${deviceId}:${addonId}:${target}` triples whose jump has been logged. */
|
|
9969
9975
|
loggedStepJumps = /* @__PURE__ */ new Set();
|
|
9970
9976
|
logStepJumped(deviceId, electedDeviceKey, decision, placement) {
|
|
@@ -10201,6 +10207,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
10201
10207
|
const warnPrefix = `${deviceId}:`;
|
|
10202
10208
|
for (const key of this.warnedNoJumpDevice) if (key.startsWith(warnPrefix)) this.warnedNoJumpDevice.delete(key);
|
|
10203
10209
|
this.attached.delete(deviceId);
|
|
10210
|
+
this.electedPoolDelegationState.delete(deviceId);
|
|
10204
10211
|
this.lastMotionAt.delete(deviceId);
|
|
10205
10212
|
this.lastEmittedCameraMetrics.delete(deviceId);
|
|
10206
10213
|
this.retention?.forgetDevice(deviceId);
|
|
@@ -10911,6 +10918,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
10911
10918
|
}
|
|
10912
10919
|
const camConfig = this.attached.get(deviceId)?.config;
|
|
10913
10920
|
const steps = camConfig?.steps;
|
|
10921
|
+
await this.customModels.refresh(api);
|
|
10914
10922
|
if (!steps) {
|
|
10915
10923
|
log.warn("runInference: no steps in attach config — skipping frame (legacy attach?)");
|
|
10916
10924
|
return null;
|
|
@@ -10945,6 +10953,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
10945
10953
|
});
|
|
10946
10954
|
}
|
|
10947
10955
|
}
|
|
10956
|
+
dispatchSteps = this.stepsForElectedPoolOf(deviceId, dispatchSteps);
|
|
10948
10957
|
let image;
|
|
10949
10958
|
const executorCoLocated = this.executorCoLocated();
|
|
10950
10959
|
if (!executorCoLocated) {
|
|
@@ -11074,6 +11083,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
11074
11083
|
const cropResult = await api.pipelineExecutor.runPipeline.mutate({
|
|
11075
11084
|
steps: [step],
|
|
11076
11085
|
image: view.jpeg,
|
|
11086
|
+
deviceId,
|
|
11077
11087
|
plane: "frame",
|
|
11078
11088
|
...deviceKey ? { deviceKey } : {}
|
|
11079
11089
|
});
|