@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,8 +1,8 @@
|
|
|
1
|
-
import { B as errMsg, G as createEvent, M as pipelineRunnerCapability, S as defineCustomActions, V as BaseAddon, X as nodePin, at as lazy, b as customAction, ct as object, ft as EventCategory, nt as array, rt as boolean, s as DEVICE_BACKEND_TO_FORMAT, st as number, tt as _enum, ut as string } from "../dist-
|
|
1
|
+
import { B as errMsg, G as createEvent, M as pipelineRunnerCapability, S as defineCustomActions, V as BaseAddon, X as nodePin, at as lazy, b as customAction, ct as object, ft as EventCategory, nt as array, rt as boolean, s as DEVICE_BACKEND_TO_FORMAT, st as number, tt as _enum, ut as string } from "../dist-CsKaXmP8.mjs";
|
|
2
2
|
import { n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-BeHi78PZ.mjs";
|
|
3
3
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
4
4
|
import { t as isWorkerReply } from "../worker-protocol-D7RzZIla.mjs";
|
|
5
|
-
import { a as getStepDefinition } from "../step-definitions-
|
|
5
|
+
import { a as getStepDefinition } from "../step-definitions-C8A78dBi.mjs";
|
|
6
6
|
import { fork } from "node:child_process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import sharp from "sharp";
|
|
@@ -569,6 +569,22 @@ var PipelineTimingSampler = class {
|
|
|
569
569
|
//#endregion
|
|
570
570
|
//#region src/pipeline-runner/runner.ts
|
|
571
571
|
/**
|
|
572
|
+
* Bound on one `processFrame` call. Chosen well above the slowest legitimate
|
|
573
|
+
* inference seen in production (8 617 ms, GPU contention on the hub) so the
|
|
574
|
+
* timeout only ever fires on a call that is genuinely never coming back.
|
|
575
|
+
*/
|
|
576
|
+
var DEFAULT_INFERENCE_TIMEOUT_MS = 2e4;
|
|
577
|
+
/** Grace period before a permit-starved scheduler with queued work warns. */
|
|
578
|
+
var DEFAULT_INFERENCE_STALL_WARN_MS = 6e4;
|
|
579
|
+
/** Re-warn cadence while a stall persists, so a wedge stays visible. */
|
|
580
|
+
var INFERENCE_STALL_REWARN_MS = 6e4;
|
|
581
|
+
/**
|
|
582
|
+
* Sentinel distinguishing "the bound expired" from a legitimate `null`
|
|
583
|
+
* (`processFrame` returning no result). A unique symbol, so it can never
|
|
584
|
+
* collide with a value the executor produces.
|
|
585
|
+
*/
|
|
586
|
+
var TIMED_OUT = Symbol("inference-timed-out");
|
|
587
|
+
/**
|
|
572
588
|
* Clone a {@link DetectionQueueEntry} for the queue's copy-on-retain
|
|
573
589
|
* boundary: the inner frame's pixel buffer is detached via
|
|
574
590
|
* {@link ownFrame}, the `FrameHandle` is carried through unchanged (it's
|
|
@@ -651,6 +667,10 @@ var PipelineRunner = class {
|
|
|
651
667
|
arrivalCounts = /* @__PURE__ */ new Map();
|
|
652
668
|
lastGovernorRecomputeAt = Date.now();
|
|
653
669
|
governorIntervalHandle = null;
|
|
670
|
+
/** When the scheduler first found itself with zero permits; null when healthy. */
|
|
671
|
+
inferenceStallSince = null;
|
|
672
|
+
/** Last stall warning, so a persistent wedge re-warns instead of going quiet. */
|
|
673
|
+
inferenceStallWarnedAt = null;
|
|
654
674
|
constructor(config) {
|
|
655
675
|
this.config = config;
|
|
656
676
|
this.logger = config.logger;
|
|
@@ -1041,7 +1061,12 @@ var PipelineRunner = class {
|
|
|
1041
1061
|
}
|
|
1042
1062
|
tick() {
|
|
1043
1063
|
this.drainMotionQueues();
|
|
1044
|
-
if (this.semaphore.available <= 0)
|
|
1064
|
+
if (this.semaphore.available <= 0) {
|
|
1065
|
+
this.noteInferenceStall();
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
this.inferenceStallSince = null;
|
|
1069
|
+
this.inferenceStallWarnedAt = null;
|
|
1045
1070
|
const picked = this.pickNextDetectionFrame();
|
|
1046
1071
|
if (!picked) return;
|
|
1047
1072
|
const { deviceId, entry, state } = picked;
|
|
@@ -1064,11 +1089,18 @@ var PipelineRunner = class {
|
|
|
1064
1089
|
const pickedAt = Date.now();
|
|
1065
1090
|
const { frame, handle } = entry;
|
|
1066
1091
|
const enqueuedAt = frame._enqueuedAt ?? pickedAt;
|
|
1067
|
-
const
|
|
1092
|
+
const rawRelease = await this.semaphore.acquire();
|
|
1093
|
+
let released = false;
|
|
1094
|
+
const release = () => {
|
|
1095
|
+
if (released) return;
|
|
1096
|
+
released = true;
|
|
1097
|
+
rawRelease();
|
|
1098
|
+
};
|
|
1068
1099
|
const semAcquiredAt = Date.now();
|
|
1069
1100
|
const executorHandle = state.detectionQueue.size <= HANDLE_PASSTHROUGH_MAX_QUEUE_DEPTH && this.semaphore.available > 0 ? handle : void 0;
|
|
1070
1101
|
try {
|
|
1071
|
-
const result = await this.
|
|
1102
|
+
const result = await this.awaitInferenceBounded(deviceId, frameInput, executorHandle, release);
|
|
1103
|
+
if (result === TIMED_OUT) return;
|
|
1072
1104
|
const inferDoneAt = Date.now();
|
|
1073
1105
|
const inferenceMs = inferDoneAt - semAcquiredAt;
|
|
1074
1106
|
state.inferenceTimes.push(inferenceMs);
|
|
@@ -1092,6 +1124,73 @@ var PipelineRunner = class {
|
|
|
1092
1124
|
release();
|
|
1093
1125
|
}
|
|
1094
1126
|
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Await `processFrame` with a hard bound. On expiry the permit is released
|
|
1129
|
+
* immediately (via the caller's release-once closure) and {@link TIMED_OUT}
|
|
1130
|
+
* is returned; the underlying call is left to settle into the void, since
|
|
1131
|
+
* the executor contract offers no cancellation.
|
|
1132
|
+
*/
|
|
1133
|
+
async awaitInferenceBounded(deviceId, frameInput, executorHandle, release) {
|
|
1134
|
+
const timeoutMs = this.config.inferenceTimeoutMs ?? DEFAULT_INFERENCE_TIMEOUT_MS;
|
|
1135
|
+
let timer = null;
|
|
1136
|
+
const expiry = new Promise((resolve) => {
|
|
1137
|
+
timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
|
|
1138
|
+
timer.unref?.();
|
|
1139
|
+
});
|
|
1140
|
+
try {
|
|
1141
|
+
const outcome = await Promise.race([this.config.processFrame(deviceId, frameInput, executorHandle), expiry]);
|
|
1142
|
+
if (outcome === TIMED_OUT) {
|
|
1143
|
+
release();
|
|
1144
|
+
this.logger?.warn("inference timed out — permit released, result discarded", {
|
|
1145
|
+
tags: { deviceId },
|
|
1146
|
+
meta: {
|
|
1147
|
+
timeoutMs,
|
|
1148
|
+
availablePermits: this.semaphore.available
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
return outcome;
|
|
1153
|
+
} finally {
|
|
1154
|
+
if (timer !== null) clearTimeout(timer);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Called on every tick that finds zero available permits. Warns once the
|
|
1159
|
+
* starvation outlives `inferenceStallWarnMs` WITH queued work — queued work
|
|
1160
|
+
* is the discriminator: a runner with no permits and no backlog is simply
|
|
1161
|
+
* busy, while one with a backlog it cannot touch is wedged.
|
|
1162
|
+
*/
|
|
1163
|
+
noteInferenceStall() {
|
|
1164
|
+
let queuedFrames = 0;
|
|
1165
|
+
for (const state of this.cameras.values()) queuedFrames += state.detectionQueue.size;
|
|
1166
|
+
if (queuedFrames === 0) {
|
|
1167
|
+
this.inferenceStallSince = null;
|
|
1168
|
+
this.inferenceStallWarnedAt = null;
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
const now = Date.now();
|
|
1172
|
+
if (this.inferenceStallSince === null) {
|
|
1173
|
+
this.inferenceStallSince = now;
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const stalledMs = now - this.inferenceStallSince;
|
|
1177
|
+
if (stalledMs < (this.config.inferenceStallWarnMs ?? DEFAULT_INFERENCE_STALL_WARN_MS)) return;
|
|
1178
|
+
if (this.inferenceStallWarnedAt !== null && now - this.inferenceStallWarnedAt < INFERENCE_STALL_REWARN_MS) return;
|
|
1179
|
+
this.inferenceStallWarnedAt = now;
|
|
1180
|
+
for (const [deviceId, state] of this.cameras) {
|
|
1181
|
+
if (state.detectionQueue.size === 0) continue;
|
|
1182
|
+
this.logger?.warn("no inference permit available while frames are queued", {
|
|
1183
|
+
tags: { deviceId },
|
|
1184
|
+
meta: {
|
|
1185
|
+
stalledMs,
|
|
1186
|
+
queuedFrames: state.detectionQueue.size,
|
|
1187
|
+
droppedFrames: state.detectionQueue.droppedFrames,
|
|
1188
|
+
phase: state.phase,
|
|
1189
|
+
concurrency: this.semaphore.concurrency
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1095
1194
|
async notifyCallbacks(deviceId, frame, result, streamType, handle) {
|
|
1096
1195
|
for (const callback of this.resultCallbacks) try {
|
|
1097
1196
|
await callback(deviceId, frame, result, streamType, handle);
|
|
@@ -1231,7 +1330,24 @@ function forkDecodeWorker(_source) {
|
|
|
1231
1330
|
//#endregion
|
|
1232
1331
|
//#region src/session-decode/guarded-session-decode.ts
|
|
1233
1332
|
/**
|
|
1234
|
-
*
|
|
1333
|
+
* 2s → 5s → 15s → 30s, then 30s forever.
|
|
1334
|
+
*
|
|
1335
|
+
* Indefinite is deliberate. The failure this recovers from is "the broker has
|
|
1336
|
+
* no profile bound yet" or "the broker call failed right now" — both of which
|
|
1337
|
+
* clear on their own, often minutes later (a source re-dialing, a runner
|
|
1338
|
+
* restarting). A bounded retry would turn a slow recovery back into the
|
|
1339
|
+
* permanent outage this exists to prevent, and teardown already stops it: a
|
|
1340
|
+
* detach aborts the loop, so nothing retries for a camera nobody wants.
|
|
1341
|
+
*/
|
|
1342
|
+
var DEFAULT_ACQUIRE_RETRY_DELAYS_MS = [
|
|
1343
|
+
2e3,
|
|
1344
|
+
5e3,
|
|
1345
|
+
15e3,
|
|
1346
|
+
3e4
|
|
1347
|
+
];
|
|
1348
|
+
/**
|
|
1349
|
+
* Guards the in-flight restream-acquire against a detach/unsubscribe race,
|
|
1350
|
+
* and retries an acquire that fails.
|
|
1235
1351
|
*
|
|
1236
1352
|
* `acquireSessionDecodeRestream` + forking the decode worker both take real
|
|
1237
1353
|
* time (a broker round-trip, then a process fork). If the caller's teardown
|
|
@@ -1243,29 +1359,71 @@ function forkDecodeWorker(_source) {
|
|
|
1243
1359
|
*
|
|
1244
1360
|
* This helper returns the teardown SYNCHRONOUSLY. The acquire + pump-start
|
|
1245
1361
|
* run in the background; an `aborted` flag recorded by the (possibly
|
|
1246
|
-
* already-called) teardown is checked right after
|
|
1247
|
-
* if set,
|
|
1362
|
+
* already-called) teardown is checked right after each acquire resolves —
|
|
1363
|
+
* if set, any restream is released immediately and the pump is NEVER
|
|
1248
1364
|
* started (no orphan worker). Otherwise the pump starts and the teardown,
|
|
1249
1365
|
* once called, stops the pump then releases the restream.
|
|
1366
|
+
*
|
|
1367
|
+
* **A failed acquire is retried, not swallowed.** The teardown handed back
|
|
1368
|
+
* here is stored by the caller (as `attachment.detectionUnsubscribe`) the
|
|
1369
|
+
* instant this returns, so a `return` on failure left that handle permanently
|
|
1370
|
+
* non-null with no pump behind it: `onEnded` could never fire because nothing
|
|
1371
|
+
* had ever started, the camera's phase stayed `active`, and not one frame
|
|
1372
|
+
* arrived again. The motion path was hardened against this shape by a frame
|
|
1373
|
+
* freshness window (`MOTION_FRESHNESS_WINDOW_MS`); this is the detection
|
|
1374
|
+
* path's half.
|
|
1250
1375
|
*/
|
|
1251
1376
|
function startGuardedSessionDecode(deps) {
|
|
1252
1377
|
let aborted = false;
|
|
1253
1378
|
let stopPump = null;
|
|
1254
1379
|
let release = null;
|
|
1380
|
+
let retryTimer = null;
|
|
1381
|
+
const delays = deps.acquireRetryDelaysMs && deps.acquireRetryDelaysMs.length > 0 ? deps.acquireRetryDelaysMs : DEFAULT_ACQUIRE_RETRY_DELAYS_MS;
|
|
1382
|
+
/** Resolves after the backoff for `attempt`, or immediately once aborted. */
|
|
1383
|
+
const waitBackoff = (attempt) => new Promise((resolve) => {
|
|
1384
|
+
const ms = delays[Math.min(attempt, delays.length - 1)] ?? delays[delays.length - 1] ?? 2e3;
|
|
1385
|
+
retryTimer = setTimeout(() => {
|
|
1386
|
+
retryTimer = null;
|
|
1387
|
+
resolve();
|
|
1388
|
+
}, ms);
|
|
1389
|
+
retryTimer.unref?.();
|
|
1390
|
+
});
|
|
1255
1391
|
(async () => {
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1392
|
+
for (let attempt = 0; !aborted; attempt++) {
|
|
1393
|
+
let acquired = null;
|
|
1394
|
+
try {
|
|
1395
|
+
acquired = await deps.acquire();
|
|
1396
|
+
} catch (err) {
|
|
1397
|
+
(attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: acquire failed — retrying", { meta: {
|
|
1398
|
+
attempt: attempt + 1,
|
|
1399
|
+
error: errMsg(err)
|
|
1400
|
+
} });
|
|
1401
|
+
await waitBackoff(attempt);
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
if (aborted) {
|
|
1405
|
+
if (acquired) acquired.release();
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (!acquired) {
|
|
1409
|
+
(attempt === 0 ? deps.logger.warn : deps.logger.debug).call(deps.logger, "session-decode: no broker profile bound yet — retrying", { meta: { attempt: attempt + 1 } });
|
|
1410
|
+
await waitBackoff(attempt);
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
if (attempt > 0) deps.logger.info("session-decode: acquire succeeded after retry", { meta: { attempts: attempt + 1 } });
|
|
1414
|
+
release = acquired.release;
|
|
1415
|
+
stopPump = deps.startPump(acquired.source);
|
|
1260
1416
|
return;
|
|
1261
1417
|
}
|
|
1262
|
-
release = acquired.release;
|
|
1263
|
-
stopPump = deps.startPump(acquired.source);
|
|
1264
1418
|
})().catch((err) => {
|
|
1265
1419
|
deps.logger.warn("session-decode: guarded start failed", { meta: { error: errMsg(err) } });
|
|
1266
1420
|
});
|
|
1267
1421
|
return () => {
|
|
1268
1422
|
aborted = true;
|
|
1423
|
+
if (retryTimer !== null) {
|
|
1424
|
+
clearTimeout(retryTimer);
|
|
1425
|
+
retryTimer = null;
|
|
1426
|
+
}
|
|
1269
1427
|
stopPump?.();
|
|
1270
1428
|
if (release) release();
|
|
1271
1429
|
};
|
|
@@ -2057,10 +2215,14 @@ var NativeCropRegistry = class {
|
|
|
2057
2215
|
* Bind `request` to `handle`; evicts the oldest entry past the cap, releasing
|
|
2058
2216
|
* its worker-side RAM lease so a lease never outlives its registry entry.
|
|
2059
2217
|
*/
|
|
2060
|
-
register(handle, request) {
|
|
2218
|
+
register(handle, request, deviceId) {
|
|
2061
2219
|
const k = this.key(handle);
|
|
2062
2220
|
this.entries.delete(k);
|
|
2063
|
-
this.entries.set(k,
|
|
2221
|
+
this.entries.set(k, {
|
|
2222
|
+
request,
|
|
2223
|
+
deviceId,
|
|
2224
|
+
registeredAt: Date.now()
|
|
2225
|
+
});
|
|
2064
2226
|
while (this.entries.size > this.capacity) {
|
|
2065
2227
|
const oldest = this.entries.keys().next().value;
|
|
2066
2228
|
if (oldest === void 0) break;
|
|
@@ -2069,7 +2231,22 @@ var NativeCropRegistry = class {
|
|
|
2069
2231
|
}
|
|
2070
2232
|
/** The requester for `handle`, or `null` if never registered / already evicted. */
|
|
2071
2233
|
get(handle) {
|
|
2072
|
-
|
|
2234
|
+
const entry = this.entries.get(this.key(handle));
|
|
2235
|
+
if (entry === void 0) return null;
|
|
2236
|
+
return {
|
|
2237
|
+
request: entry.request,
|
|
2238
|
+
deviceId: entry.deviceId,
|
|
2239
|
+
ageMs: Date.now() - entry.registeredAt
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* The deviceId a handle was registered under, even AFTER its entry was
|
|
2244
|
+
* evicted — kept in a small parallel ring so a `no-registry-entry` miss can
|
|
2245
|
+
* still be attributed to a camera (a miss line without `deviceId` cannot
|
|
2246
|
+
* answer "why is 617 worse than 615?").
|
|
2247
|
+
*/
|
|
2248
|
+
deviceIdFor(handle) {
|
|
2249
|
+
return this.evictedDeviceIds.get(this.key(handle)) ?? this.entries.get(this.key(handle))?.deviceId ?? null;
|
|
2073
2250
|
}
|
|
2074
2251
|
/**
|
|
2075
2252
|
* Release the worker-side RAM native lease for `handle` and drop its entry —
|
|
@@ -2081,15 +2258,27 @@ var NativeCropRegistry = class {
|
|
|
2081
2258
|
}
|
|
2082
2259
|
/** Drop every entry, releasing each frame's worker-side lease (node/addon shutdown). */
|
|
2083
2260
|
clear() {
|
|
2084
|
-
for (const
|
|
2261
|
+
for (const entry of this.entries.values()) safeRelease(entry.request);
|
|
2085
2262
|
this.entries.clear();
|
|
2263
|
+
this.evictedDeviceIds.clear();
|
|
2086
2264
|
}
|
|
2087
|
-
/** Release + drop one entry by its map key. */
|
|
2265
|
+
/** Release + drop one entry by its map key, remembering its camera. */
|
|
2088
2266
|
releaseEntry(key) {
|
|
2089
|
-
const
|
|
2090
|
-
if (!
|
|
2267
|
+
const entry = this.entries.get(key);
|
|
2268
|
+
if (!entry) return;
|
|
2091
2269
|
this.entries.delete(key);
|
|
2092
|
-
|
|
2270
|
+
this.rememberEvicted(key, entry.deviceId);
|
|
2271
|
+
safeRelease(entry.request);
|
|
2272
|
+
}
|
|
2273
|
+
/** Bounded memory of evicted handles → deviceId, for miss attribution. */
|
|
2274
|
+
evictedDeviceIds = /* @__PURE__ */ new Map();
|
|
2275
|
+
rememberEvicted(key, deviceId) {
|
|
2276
|
+
this.evictedDeviceIds.set(key, deviceId);
|
|
2277
|
+
while (this.evictedDeviceIds.size > this.capacity) {
|
|
2278
|
+
const oldest = this.evictedDeviceIds.keys().next().value;
|
|
2279
|
+
if (oldest === void 0) break;
|
|
2280
|
+
this.evictedDeviceIds.delete(oldest);
|
|
2281
|
+
}
|
|
2093
2282
|
}
|
|
2094
2283
|
};
|
|
2095
2284
|
//#endregion
|
|
@@ -3295,16 +3484,29 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3295
3484
|
const isFullFrame = b.x <= .001 && b.y <= .001 && b.w >= .999 && b.h >= .999;
|
|
3296
3485
|
const isUnboundedFullFrame = isFullFrame && input.maxWidth === void 0;
|
|
3297
3486
|
let nativeResult = null;
|
|
3487
|
+
let missReason = null;
|
|
3298
3488
|
if (!isUnboundedFullFrame) {
|
|
3299
|
-
const
|
|
3300
|
-
if (
|
|
3301
|
-
const result = await
|
|
3489
|
+
const found = this.nativeCropRegistry.get(input.handle);
|
|
3490
|
+
if (found) {
|
|
3491
|
+
const result = await found.request(input.bbox, input.maxWidth);
|
|
3302
3492
|
if (result) nativeResult = {
|
|
3303
3493
|
bytes: result.bytes,
|
|
3304
3494
|
width: result.width,
|
|
3305
3495
|
height: result.height
|
|
3306
3496
|
};
|
|
3307
|
-
|
|
3497
|
+
else missReason = "worker-lease-gone";
|
|
3498
|
+
} else missReason = "no-registry-entry";
|
|
3499
|
+
if (missReason !== null) this.ctx.logger.debug("native crop miss", {
|
|
3500
|
+
tags: { deviceId: found?.deviceId ?? this.nativeCropRegistry.deviceIdFor(input.handle) ?? -1 },
|
|
3501
|
+
meta: {
|
|
3502
|
+
reason: missReason,
|
|
3503
|
+
handle: `${input.handle.shmId}#${input.handle.slot}#${input.handle.seq}`,
|
|
3504
|
+
handleNodeId: input.handle.nodeId,
|
|
3505
|
+
handleAgeMs: found?.ageMs ?? null,
|
|
3506
|
+
registrySize: this.nativeCropRegistry.size,
|
|
3507
|
+
registryCap: DEFAULT_NATIVE_CROP_REGISTRY_CAP
|
|
3508
|
+
}
|
|
3509
|
+
});
|
|
3308
3510
|
}
|
|
3309
3511
|
const tier = resolveNativeCropSource(isFullFrame, nativeResult !== null);
|
|
3310
3512
|
if (tier === "native" && nativeResult) return this.encodeNativeCropResult(nativeResult, tier, input.encodeJpeg);
|
|
@@ -3785,7 +3987,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3785
3987
|
onEnded: () => this.handleDetectionDecodeEnded(config.deviceId),
|
|
3786
3988
|
onDecodedFrame: (frame, nativeCrop) => {
|
|
3787
3989
|
const handle = retainedFrames?.write(config.deviceId, frame) ?? void 0;
|
|
3788
|
-
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop);
|
|
3990
|
+
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
|
|
3789
3991
|
this.enqueueSharedDetectionFrame(config.deviceId, frame, handle);
|
|
3790
3992
|
}
|
|
3791
3993
|
});
|
|
@@ -4298,7 +4500,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
4298
4500
|
onDecodedFrame: (frame, nativeCrop) => {
|
|
4299
4501
|
if (settled) return;
|
|
4300
4502
|
const handle = retainedFrames?.write(config.deviceId, frame) ?? void 0;
|
|
4301
|
-
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop);
|
|
4503
|
+
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
|
|
4302
4504
|
runner.enqueueOccupancyFrame(deviceId, frame, handle);
|
|
4303
4505
|
count++;
|
|
4304
4506
|
if (count >= frames) {
|