@camstack/addon-pipeline 1.2.19 → 1.2.28
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 +1 -1
- package/dist/audio-analyzer/index.mjs +3 -2
- package/dist/chunk-DnnnRqeS.mjs +26 -0
- package/dist/detection-pipeline/index.js +2 -2
- package/dist/detection-pipeline/index.mjs +4 -3
- package/dist/{dist-DZBePRjP.mjs → dist-CsKaXmP8.mjs} +415 -126
- package/dist/{dist-DKqOqH5j.js → dist-DUHlZcWd.js} +420 -131
- package/dist/{model-download-service-Cp9f4dk6-WO-foD7W.mjs → model-download-service-Cp9f4dk6-CwUb5v3Y.mjs} +1 -26
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +230 -28
- package/dist/pipeline-runner/index.mjs +230 -28
- package/dist/recorder/index.js +2604 -458
- package/dist/recorder/index.mjs +2599 -459
- package/dist/{step-definitions-zDq8cP8M.js → step-definitions-Bo1hJQQw.js} +1 -1
- package/dist/{step-definitions-BwB6TBMB.mjs → step-definitions-C8A78dBi.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Bwuv7iK_.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-tHahN-lK.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D1zJmtUe.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-DfnRAN5T.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CSWUWZ8c.mjs} +1 -1
- package/dist/stream-broker/{hostInit-DC0aArn1.mjs → hostInit-CIwilJUZ.mjs} +2 -2
- package/dist/stream-broker/index.js +7374 -250
- package/dist/stream-broker/index.mjs +7371 -249
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C2hbIwzB.js → MaskShapeCanvas-DI4BY7W2-B7VdNJlO.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BpJ-foJW.js → MotionZonesSettings-NcxxQN8r-YyB4uLdH.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BjLs9Z3w.js → PrivacyMaskSettings-APgPLF7p-DfqixEad.js} +1 -1
- package/embed-dist/assets/{index-Dv5H-32C.css → index-CAe6Dg-X.css} +1 -1
- package/embed-dist/assets/index-zVLLVdIS.js +343 -0
- package/embed-dist/index.html +2 -2
- package/package.json +5 -1
- package/dist/fmp4-keyframes-5s8T9Mng.js +0 -6711
- package/dist/fmp4-keyframes-CV0X1mGy.mjs +0 -6694
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BDeEvhV5.mjs +0 -26
- package/embed-dist/assets/index-_BRQTmdE.js +0 -116
|
@@ -1,33 +1,8 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
1
|
import * as fs from "node:fs";
|
|
3
2
|
import { createReadStream, promises } from "node:fs";
|
|
4
3
|
import * as path$1 from "node:path";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import "node:crypto";
|
|
7
|
-
//#region \0rolldown/runtime.js
|
|
8
|
-
var __create = Object.create;
|
|
9
|
-
var __defProp = Object.defineProperty;
|
|
10
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
-
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
15
|
-
var __copyProps = (to, from, except, desc) => {
|
|
16
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
17
|
-
key = keys[i];
|
|
18
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
19
|
-
get: ((k) => from[k]).bind(null, key),
|
|
20
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
return to;
|
|
24
|
-
};
|
|
25
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
26
|
-
value: mod,
|
|
27
|
-
enumerable: true
|
|
28
|
-
}) : target, mod));
|
|
29
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
-
//#endregion
|
|
31
6
|
//#region ../system/dist/model-download-service-Cp9f4dk6.mjs
|
|
32
7
|
/**
|
|
33
8
|
* Map a rel path to one candidate absolute path PER root, keeping only roots the
|
|
@@ -353,4 +328,4 @@ function deleteModelFromDisk(modelsDir, entry, format) {
|
|
|
353
328
|
return true;
|
|
354
329
|
}
|
|
355
330
|
//#endregion
|
|
356
|
-
export { ensureModel as a,
|
|
331
|
+
export { ensureModel as a, downloadFile as i, createFileDataPlaneHandler as n, isModelDownloaded as o, deleteModelFromDisk as r, parseRangeHeader as s, contentTypeFor as t };
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-DUHlZcWd.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as motionDetectionCapability,
|
|
1
|
+
import { A as motionDetectionCapability, E as evaluateZoneRules, K as hydrateSchema, V as BaseAddon, W as DeviceType } from "../dist-CsKaXmP8.mjs";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -3,11 +3,11 @@ 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-
|
|
6
|
+
const require_dist = require("../dist-DUHlZcWd.js");
|
|
7
7
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
8
8
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
9
9
|
const require_worker_protocol = require("../worker-protocol-DextwlTX.js");
|
|
10
|
-
const require_step_definitions = require("../step-definitions-
|
|
10
|
+
const require_step_definitions = require("../step-definitions-Bo1hJQQw.js");
|
|
11
11
|
let node_child_process = require("node:child_process");
|
|
12
12
|
let node_url = require("node:url");
|
|
13
13
|
let sharp = require("sharp");
|
|
@@ -575,6 +575,22 @@ var PipelineTimingSampler = class {
|
|
|
575
575
|
//#endregion
|
|
576
576
|
//#region src/pipeline-runner/runner.ts
|
|
577
577
|
/**
|
|
578
|
+
* Bound on one `processFrame` call. Chosen well above the slowest legitimate
|
|
579
|
+
* inference seen in production (8 617 ms, GPU contention on the hub) so the
|
|
580
|
+
* timeout only ever fires on a call that is genuinely never coming back.
|
|
581
|
+
*/
|
|
582
|
+
var DEFAULT_INFERENCE_TIMEOUT_MS = 2e4;
|
|
583
|
+
/** Grace period before a permit-starved scheduler with queued work warns. */
|
|
584
|
+
var DEFAULT_INFERENCE_STALL_WARN_MS = 6e4;
|
|
585
|
+
/** Re-warn cadence while a stall persists, so a wedge stays visible. */
|
|
586
|
+
var INFERENCE_STALL_REWARN_MS = 6e4;
|
|
587
|
+
/**
|
|
588
|
+
* Sentinel distinguishing "the bound expired" from a legitimate `null`
|
|
589
|
+
* (`processFrame` returning no result). A unique symbol, so it can never
|
|
590
|
+
* collide with a value the executor produces.
|
|
591
|
+
*/
|
|
592
|
+
var TIMED_OUT = Symbol("inference-timed-out");
|
|
593
|
+
/**
|
|
578
594
|
* Clone a {@link DetectionQueueEntry} for the queue's copy-on-retain
|
|
579
595
|
* boundary: the inner frame's pixel buffer is detached via
|
|
580
596
|
* {@link ownFrame}, the `FrameHandle` is carried through unchanged (it's
|
|
@@ -657,6 +673,10 @@ var PipelineRunner = class {
|
|
|
657
673
|
arrivalCounts = /* @__PURE__ */ new Map();
|
|
658
674
|
lastGovernorRecomputeAt = Date.now();
|
|
659
675
|
governorIntervalHandle = null;
|
|
676
|
+
/** When the scheduler first found itself with zero permits; null when healthy. */
|
|
677
|
+
inferenceStallSince = null;
|
|
678
|
+
/** Last stall warning, so a persistent wedge re-warns instead of going quiet. */
|
|
679
|
+
inferenceStallWarnedAt = null;
|
|
660
680
|
constructor(config) {
|
|
661
681
|
this.config = config;
|
|
662
682
|
this.logger = config.logger;
|
|
@@ -1047,7 +1067,12 @@ var PipelineRunner = class {
|
|
|
1047
1067
|
}
|
|
1048
1068
|
tick() {
|
|
1049
1069
|
this.drainMotionQueues();
|
|
1050
|
-
if (this.semaphore.available <= 0)
|
|
1070
|
+
if (this.semaphore.available <= 0) {
|
|
1071
|
+
this.noteInferenceStall();
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
this.inferenceStallSince = null;
|
|
1075
|
+
this.inferenceStallWarnedAt = null;
|
|
1051
1076
|
const picked = this.pickNextDetectionFrame();
|
|
1052
1077
|
if (!picked) return;
|
|
1053
1078
|
const { deviceId, entry, state } = picked;
|
|
@@ -1070,11 +1095,18 @@ var PipelineRunner = class {
|
|
|
1070
1095
|
const pickedAt = Date.now();
|
|
1071
1096
|
const { frame, handle } = entry;
|
|
1072
1097
|
const enqueuedAt = frame._enqueuedAt ?? pickedAt;
|
|
1073
|
-
const
|
|
1098
|
+
const rawRelease = await this.semaphore.acquire();
|
|
1099
|
+
let released = false;
|
|
1100
|
+
const release = () => {
|
|
1101
|
+
if (released) return;
|
|
1102
|
+
released = true;
|
|
1103
|
+
rawRelease();
|
|
1104
|
+
};
|
|
1074
1105
|
const semAcquiredAt = Date.now();
|
|
1075
1106
|
const executorHandle = state.detectionQueue.size <= HANDLE_PASSTHROUGH_MAX_QUEUE_DEPTH && this.semaphore.available > 0 ? handle : void 0;
|
|
1076
1107
|
try {
|
|
1077
|
-
const result = await this.
|
|
1108
|
+
const result = await this.awaitInferenceBounded(deviceId, frameInput, executorHandle, release);
|
|
1109
|
+
if (result === TIMED_OUT) return;
|
|
1078
1110
|
const inferDoneAt = Date.now();
|
|
1079
1111
|
const inferenceMs = inferDoneAt - semAcquiredAt;
|
|
1080
1112
|
state.inferenceTimes.push(inferenceMs);
|
|
@@ -1098,6 +1130,73 @@ var PipelineRunner = class {
|
|
|
1098
1130
|
release();
|
|
1099
1131
|
}
|
|
1100
1132
|
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Await `processFrame` with a hard bound. On expiry the permit is released
|
|
1135
|
+
* immediately (via the caller's release-once closure) and {@link TIMED_OUT}
|
|
1136
|
+
* is returned; the underlying call is left to settle into the void, since
|
|
1137
|
+
* the executor contract offers no cancellation.
|
|
1138
|
+
*/
|
|
1139
|
+
async awaitInferenceBounded(deviceId, frameInput, executorHandle, release) {
|
|
1140
|
+
const timeoutMs = this.config.inferenceTimeoutMs ?? DEFAULT_INFERENCE_TIMEOUT_MS;
|
|
1141
|
+
let timer = null;
|
|
1142
|
+
const expiry = new Promise((resolve) => {
|
|
1143
|
+
timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
|
|
1144
|
+
timer.unref?.();
|
|
1145
|
+
});
|
|
1146
|
+
try {
|
|
1147
|
+
const outcome = await Promise.race([this.config.processFrame(deviceId, frameInput, executorHandle), expiry]);
|
|
1148
|
+
if (outcome === TIMED_OUT) {
|
|
1149
|
+
release();
|
|
1150
|
+
this.logger?.warn("inference timed out — permit released, result discarded", {
|
|
1151
|
+
tags: { deviceId },
|
|
1152
|
+
meta: {
|
|
1153
|
+
timeoutMs,
|
|
1154
|
+
availablePermits: this.semaphore.available
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
return outcome;
|
|
1159
|
+
} finally {
|
|
1160
|
+
if (timer !== null) clearTimeout(timer);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Called on every tick that finds zero available permits. Warns once the
|
|
1165
|
+
* starvation outlives `inferenceStallWarnMs` WITH queued work — queued work
|
|
1166
|
+
* is the discriminator: a runner with no permits and no backlog is simply
|
|
1167
|
+
* busy, while one with a backlog it cannot touch is wedged.
|
|
1168
|
+
*/
|
|
1169
|
+
noteInferenceStall() {
|
|
1170
|
+
let queuedFrames = 0;
|
|
1171
|
+
for (const state of this.cameras.values()) queuedFrames += state.detectionQueue.size;
|
|
1172
|
+
if (queuedFrames === 0) {
|
|
1173
|
+
this.inferenceStallSince = null;
|
|
1174
|
+
this.inferenceStallWarnedAt = null;
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
const now = Date.now();
|
|
1178
|
+
if (this.inferenceStallSince === null) {
|
|
1179
|
+
this.inferenceStallSince = now;
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const stalledMs = now - this.inferenceStallSince;
|
|
1183
|
+
if (stalledMs < (this.config.inferenceStallWarnMs ?? DEFAULT_INFERENCE_STALL_WARN_MS)) return;
|
|
1184
|
+
if (this.inferenceStallWarnedAt !== null && now - this.inferenceStallWarnedAt < INFERENCE_STALL_REWARN_MS) return;
|
|
1185
|
+
this.inferenceStallWarnedAt = now;
|
|
1186
|
+
for (const [deviceId, state] of this.cameras) {
|
|
1187
|
+
if (state.detectionQueue.size === 0) continue;
|
|
1188
|
+
this.logger?.warn("no inference permit available while frames are queued", {
|
|
1189
|
+
tags: { deviceId },
|
|
1190
|
+
meta: {
|
|
1191
|
+
stalledMs,
|
|
1192
|
+
queuedFrames: state.detectionQueue.size,
|
|
1193
|
+
droppedFrames: state.detectionQueue.droppedFrames,
|
|
1194
|
+
phase: state.phase,
|
|
1195
|
+
concurrency: this.semaphore.concurrency
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1101
1200
|
async notifyCallbacks(deviceId, frame, result, streamType, handle) {
|
|
1102
1201
|
for (const callback of this.resultCallbacks) try {
|
|
1103
1202
|
await callback(deviceId, frame, result, streamType, handle);
|
|
@@ -1237,7 +1336,24 @@ function forkDecodeWorker(_source) {
|
|
|
1237
1336
|
//#endregion
|
|
1238
1337
|
//#region src/session-decode/guarded-session-decode.ts
|
|
1239
1338
|
/**
|
|
1240
|
-
*
|
|
1339
|
+
* 2s → 5s → 15s → 30s, then 30s forever.
|
|
1340
|
+
*
|
|
1341
|
+
* Indefinite is deliberate. The failure this recovers from is "the broker has
|
|
1342
|
+
* no profile bound yet" or "the broker call failed right now" — both of which
|
|
1343
|
+
* clear on their own, often minutes later (a source re-dialing, a runner
|
|
1344
|
+
* restarting). A bounded retry would turn a slow recovery back into the
|
|
1345
|
+
* permanent outage this exists to prevent, and teardown already stops it: a
|
|
1346
|
+
* detach aborts the loop, so nothing retries for a camera nobody wants.
|
|
1347
|
+
*/
|
|
1348
|
+
var DEFAULT_ACQUIRE_RETRY_DELAYS_MS = [
|
|
1349
|
+
2e3,
|
|
1350
|
+
5e3,
|
|
1351
|
+
15e3,
|
|
1352
|
+
3e4
|
|
1353
|
+
];
|
|
1354
|
+
/**
|
|
1355
|
+
* Guards the in-flight restream-acquire against a detach/unsubscribe race,
|
|
1356
|
+
* and retries an acquire that fails.
|
|
1241
1357
|
*
|
|
1242
1358
|
* `acquireSessionDecodeRestream` + forking the decode worker both take real
|
|
1243
1359
|
* time (a broker round-trip, then a process fork). If the caller's teardown
|
|
@@ -1249,29 +1365,71 @@ function forkDecodeWorker(_source) {
|
|
|
1249
1365
|
*
|
|
1250
1366
|
* This helper returns the teardown SYNCHRONOUSLY. The acquire + pump-start
|
|
1251
1367
|
* run in the background; an `aborted` flag recorded by the (possibly
|
|
1252
|
-
* already-called) teardown is checked right after
|
|
1253
|
-
* if set,
|
|
1368
|
+
* already-called) teardown is checked right after each acquire resolves —
|
|
1369
|
+
* if set, any restream is released immediately and the pump is NEVER
|
|
1254
1370
|
* started (no orphan worker). Otherwise the pump starts and the teardown,
|
|
1255
1371
|
* once called, stops the pump then releases the restream.
|
|
1372
|
+
*
|
|
1373
|
+
* **A failed acquire is retried, not swallowed.** The teardown handed back
|
|
1374
|
+
* here is stored by the caller (as `attachment.detectionUnsubscribe`) the
|
|
1375
|
+
* instant this returns, so a `return` on failure left that handle permanently
|
|
1376
|
+
* non-null with no pump behind it: `onEnded` could never fire because nothing
|
|
1377
|
+
* had ever started, the camera's phase stayed `active`, and not one frame
|
|
1378
|
+
* arrived again. The motion path was hardened against this shape by a frame
|
|
1379
|
+
* freshness window (`MOTION_FRESHNESS_WINDOW_MS`); this is the detection
|
|
1380
|
+
* path's half.
|
|
1256
1381
|
*/
|
|
1257
1382
|
function startGuardedSessionDecode(deps) {
|
|
1258
1383
|
let aborted = false;
|
|
1259
1384
|
let stopPump = null;
|
|
1260
1385
|
let release = null;
|
|
1386
|
+
let retryTimer = null;
|
|
1387
|
+
const delays = deps.acquireRetryDelaysMs && deps.acquireRetryDelaysMs.length > 0 ? deps.acquireRetryDelaysMs : DEFAULT_ACQUIRE_RETRY_DELAYS_MS;
|
|
1388
|
+
/** Resolves after the backoff for `attempt`, or immediately once aborted. */
|
|
1389
|
+
const waitBackoff = (attempt) => new Promise((resolve) => {
|
|
1390
|
+
const ms = delays[Math.min(attempt, delays.length - 1)] ?? delays[delays.length - 1] ?? 2e3;
|
|
1391
|
+
retryTimer = setTimeout(() => {
|
|
1392
|
+
retryTimer = null;
|
|
1393
|
+
resolve();
|
|
1394
|
+
}, ms);
|
|
1395
|
+
retryTimer.unref?.();
|
|
1396
|
+
});
|
|
1261
1397
|
(async () => {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1398
|
+
for (let attempt = 0; !aborted; attempt++) {
|
|
1399
|
+
let acquired = null;
|
|
1400
|
+
try {
|
|
1401
|
+
acquired = await deps.acquire();
|
|
1402
|
+
} catch (err) {
|
|
1403
|
+
(attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: acquire failed — retrying", { meta: {
|
|
1404
|
+
attempt: attempt + 1,
|
|
1405
|
+
error: require_dist.errMsg(err)
|
|
1406
|
+
} });
|
|
1407
|
+
await waitBackoff(attempt);
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
if (aborted) {
|
|
1411
|
+
if (acquired) acquired.release();
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
if (!acquired) {
|
|
1415
|
+
(attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: no broker profile bound yet — retrying", { meta: { attempt: attempt + 1 } });
|
|
1416
|
+
await waitBackoff(attempt);
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
if (attempt > 0) deps.logger.info("session-decode: acquire succeeded after retry", { meta: { attempts: attempt + 1 } });
|
|
1420
|
+
release = acquired.release;
|
|
1421
|
+
stopPump = deps.startPump(acquired.source);
|
|
1266
1422
|
return;
|
|
1267
1423
|
}
|
|
1268
|
-
release = acquired.release;
|
|
1269
|
-
stopPump = deps.startPump(acquired.source);
|
|
1270
1424
|
})().catch((err) => {
|
|
1271
1425
|
deps.logger.warn("session-decode: guarded start failed", { meta: { error: require_dist.errMsg(err) } });
|
|
1272
1426
|
});
|
|
1273
1427
|
return () => {
|
|
1274
1428
|
aborted = true;
|
|
1429
|
+
if (retryTimer !== null) {
|
|
1430
|
+
clearTimeout(retryTimer);
|
|
1431
|
+
retryTimer = null;
|
|
1432
|
+
}
|
|
1275
1433
|
stopPump?.();
|
|
1276
1434
|
if (release) release();
|
|
1277
1435
|
};
|
|
@@ -2063,10 +2221,14 @@ var NativeCropRegistry = class {
|
|
|
2063
2221
|
* Bind `request` to `handle`; evicts the oldest entry past the cap, releasing
|
|
2064
2222
|
* its worker-side RAM lease so a lease never outlives its registry entry.
|
|
2065
2223
|
*/
|
|
2066
|
-
register(handle, request) {
|
|
2224
|
+
register(handle, request, deviceId) {
|
|
2067
2225
|
const k = this.key(handle);
|
|
2068
2226
|
this.entries.delete(k);
|
|
2069
|
-
this.entries.set(k,
|
|
2227
|
+
this.entries.set(k, {
|
|
2228
|
+
request,
|
|
2229
|
+
deviceId,
|
|
2230
|
+
registeredAt: Date.now()
|
|
2231
|
+
});
|
|
2070
2232
|
while (this.entries.size > this.capacity) {
|
|
2071
2233
|
const oldest = this.entries.keys().next().value;
|
|
2072
2234
|
if (oldest === void 0) break;
|
|
@@ -2075,7 +2237,22 @@ var NativeCropRegistry = class {
|
|
|
2075
2237
|
}
|
|
2076
2238
|
/** The requester for `handle`, or `null` if never registered / already evicted. */
|
|
2077
2239
|
get(handle) {
|
|
2078
|
-
|
|
2240
|
+
const entry = this.entries.get(this.key(handle));
|
|
2241
|
+
if (entry === void 0) return null;
|
|
2242
|
+
return {
|
|
2243
|
+
request: entry.request,
|
|
2244
|
+
deviceId: entry.deviceId,
|
|
2245
|
+
ageMs: Date.now() - entry.registeredAt
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* The deviceId a handle was registered under, even AFTER its entry was
|
|
2250
|
+
* evicted — kept in a small parallel ring so a `no-registry-entry` miss can
|
|
2251
|
+
* still be attributed to a camera (a miss line without `deviceId` cannot
|
|
2252
|
+
* answer "why is 617 worse than 615?").
|
|
2253
|
+
*/
|
|
2254
|
+
deviceIdFor(handle) {
|
|
2255
|
+
return this.evictedDeviceIds.get(this.key(handle)) ?? this.entries.get(this.key(handle))?.deviceId ?? null;
|
|
2079
2256
|
}
|
|
2080
2257
|
/**
|
|
2081
2258
|
* Release the worker-side RAM native lease for `handle` and drop its entry —
|
|
@@ -2087,15 +2264,27 @@ var NativeCropRegistry = class {
|
|
|
2087
2264
|
}
|
|
2088
2265
|
/** Drop every entry, releasing each frame's worker-side lease (node/addon shutdown). */
|
|
2089
2266
|
clear() {
|
|
2090
|
-
for (const
|
|
2267
|
+
for (const entry of this.entries.values()) safeRelease(entry.request);
|
|
2091
2268
|
this.entries.clear();
|
|
2269
|
+
this.evictedDeviceIds.clear();
|
|
2092
2270
|
}
|
|
2093
|
-
/** Release + drop one entry by its map key. */
|
|
2271
|
+
/** Release + drop one entry by its map key, remembering its camera. */
|
|
2094
2272
|
releaseEntry(key) {
|
|
2095
|
-
const
|
|
2096
|
-
if (!
|
|
2273
|
+
const entry = this.entries.get(key);
|
|
2274
|
+
if (!entry) return;
|
|
2097
2275
|
this.entries.delete(key);
|
|
2098
|
-
|
|
2276
|
+
this.rememberEvicted(key, entry.deviceId);
|
|
2277
|
+
safeRelease(entry.request);
|
|
2278
|
+
}
|
|
2279
|
+
/** Bounded memory of evicted handles → deviceId, for miss attribution. */
|
|
2280
|
+
evictedDeviceIds = /* @__PURE__ */ new Map();
|
|
2281
|
+
rememberEvicted(key, deviceId) {
|
|
2282
|
+
this.evictedDeviceIds.set(key, deviceId);
|
|
2283
|
+
while (this.evictedDeviceIds.size > this.capacity) {
|
|
2284
|
+
const oldest = this.evictedDeviceIds.keys().next().value;
|
|
2285
|
+
if (oldest === void 0) break;
|
|
2286
|
+
this.evictedDeviceIds.delete(oldest);
|
|
2287
|
+
}
|
|
2099
2288
|
}
|
|
2100
2289
|
};
|
|
2101
2290
|
//#endregion
|
|
@@ -3301,16 +3490,29 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3301
3490
|
const isFullFrame = b.x <= .001 && b.y <= .001 && b.w >= .999 && b.h >= .999;
|
|
3302
3491
|
const isUnboundedFullFrame = isFullFrame && input.maxWidth === void 0;
|
|
3303
3492
|
let nativeResult = null;
|
|
3493
|
+
let missReason = null;
|
|
3304
3494
|
if (!isUnboundedFullFrame) {
|
|
3305
|
-
const
|
|
3306
|
-
if (
|
|
3307
|
-
const result = await
|
|
3495
|
+
const found = this.nativeCropRegistry.get(input.handle);
|
|
3496
|
+
if (found) {
|
|
3497
|
+
const result = await found.request(input.bbox, input.maxWidth);
|
|
3308
3498
|
if (result) nativeResult = {
|
|
3309
3499
|
bytes: result.bytes,
|
|
3310
3500
|
width: result.width,
|
|
3311
3501
|
height: result.height
|
|
3312
3502
|
};
|
|
3313
|
-
|
|
3503
|
+
else missReason = "worker-lease-gone";
|
|
3504
|
+
} else missReason = "no-registry-entry";
|
|
3505
|
+
if (missReason !== null) this.ctx.logger.debug("native crop miss", {
|
|
3506
|
+
tags: { deviceId: found?.deviceId ?? this.nativeCropRegistry.deviceIdFor(input.handle) ?? -1 },
|
|
3507
|
+
meta: {
|
|
3508
|
+
reason: missReason,
|
|
3509
|
+
handle: `${input.handle.shmId}#${input.handle.slot}#${input.handle.seq}`,
|
|
3510
|
+
handleNodeId: input.handle.nodeId,
|
|
3511
|
+
handleAgeMs: found?.ageMs ?? null,
|
|
3512
|
+
registrySize: this.nativeCropRegistry.size,
|
|
3513
|
+
registryCap: DEFAULT_NATIVE_CROP_REGISTRY_CAP
|
|
3514
|
+
}
|
|
3515
|
+
});
|
|
3314
3516
|
}
|
|
3315
3517
|
const tier = resolveNativeCropSource(isFullFrame, nativeResult !== null);
|
|
3316
3518
|
if (tier === "native" && nativeResult) return this.encodeNativeCropResult(nativeResult, tier, input.encodeJpeg);
|
|
@@ -3791,7 +3993,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3791
3993
|
onEnded: () => this.handleDetectionDecodeEnded(config.deviceId),
|
|
3792
3994
|
onDecodedFrame: (frame, nativeCrop) => {
|
|
3793
3995
|
const handle = retainedFrames?.write(config.deviceId, frame) ?? void 0;
|
|
3794
|
-
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop);
|
|
3996
|
+
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
|
|
3795
3997
|
this.enqueueSharedDetectionFrame(config.deviceId, frame, handle);
|
|
3796
3998
|
}
|
|
3797
3999
|
});
|
|
@@ -4304,7 +4506,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
4304
4506
|
onDecodedFrame: (frame, nativeCrop) => {
|
|
4305
4507
|
if (settled) return;
|
|
4306
4508
|
const handle = retainedFrames?.write(config.deviceId, frame) ?? void 0;
|
|
4307
|
-
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop);
|
|
4509
|
+
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
|
|
4308
4510
|
runner.enqueueOccupancyFrame(deviceId, frame, handle);
|
|
4309
4511
|
count++;
|
|
4310
4512
|
if (count >= frames) {
|