ros-mobile-bridge 0.1.1 → 0.1.2
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/CHANGELOG.md +15 -0
- package/dist/index.cjs +20 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.mjs +20 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to `ros-mobile-bridge` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.1.2] - 2026-06-01
|
|
8
|
+
|
|
9
|
+
### Performance
|
|
10
|
+
|
|
11
|
+
- **Zero-copy payload view on Foxglove WS binary topic ingest.** Previously every inbound `messageData` frame allocated a fresh `ArrayBuffer` (`ArrayBuffer.prototype.slice`) for the payload, costing roughly 10 MB/frame on raw 1080p image streams even on frames that the throttle subsequently dropped before parse. The library now uses a zero-copy `Uint8Array` view; downstream consumers (CDR reader, JSON decoder, byte-size accounting) see no behavior change. Verified end-to-end against a sustained 138 MB/s harness: `max` lag dropped from 96 ms to 29 ms (3.4× tail reduction), `mean` and `p50` unchanged.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Control-priority publishes now coalesce by destination.** When multiple `priority: 'control'` publishes for the same topic queue in the outbox during JS-thread saturation, only the latest drains rather than every queued tick in FIFO order. The release-the-joystick zero-Twist that follows N stale-value Twists on `/cmd_vel` now sends in one WebSocket frame, so the robot stops within one round-trip of release regardless of how deep the queue grew during the block. Insertion order across **distinct** topics is preserved (intra-topic conflation only). Reduced the worst-case stop latency by orders of magnitude under sustained-overload scenarios.
|
|
16
|
+
- **Tightened CircuitBreaker defaults for CDR-realistic workloads.** The breaker's `lagThresholdMs` (`250 → 150`), `tripDwellMs` (`5000 → 2000`), and the internal `WARMUP_MS` grace period (`2000 → 500`) were originally tuned against JSON-sim spike patterns (transient 100–333 ms spikes interleaved with healthy stretches). CDR sensor streams on real hardware fail in a different regime — sustained multi-second freezes during the cold-start window — which warrants faster detection. Total subscribe-to-unsubscribe floor on a genuinely saturating topic drops from approximately 7 s to approximately 2.5 s, comfortably below the threshold at which a user perceives the app as frozen.
|
|
17
|
+
|
|
18
|
+
### Documented
|
|
19
|
+
|
|
20
|
+
- **Zero-copy contract on `RosMessage.data` when it is a `Uint8Array`.** The byte values delivered to a subscriber callback as `Uint8Array` are now views into the inbound WebSocket frame's `ArrayBuffer`, not copies. The view's `byteOffset` is significant. Consumers handing `data` directly to native bindings that ignore `byteOffset` (some Skia binding paths, some FFI calls) must first materialize an owned copy via `new Uint8Array(data)`. A `materializeBytes(view)` helper that performs this copy conditionally is planned for the next release; until then the explicit copy is the recommended idiom. TSDoc on `RosMessage` carries the full contract.
|
|
21
|
+
|
|
7
22
|
## [0.1.1] - 2026-05-27
|
|
8
23
|
|
|
9
24
|
### Fixed
|
package/dist/index.cjs
CHANGED
|
@@ -10,7 +10,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
10
10
|
|
|
11
11
|
// src/CircuitBreaker.ts
|
|
12
12
|
var HEALTHY_DEBOUNCE_MS = 4e3;
|
|
13
|
-
var WARMUP_MS =
|
|
13
|
+
var WARMUP_MS = 500;
|
|
14
14
|
var CircuitBreaker = class {
|
|
15
15
|
constructor(config) {
|
|
16
16
|
__publicField(this, "config", config);
|
|
@@ -151,8 +151,8 @@ var CircuitBreaker = class {
|
|
|
151
151
|
}
|
|
152
152
|
};
|
|
153
153
|
var DEFAULT_BREAKER_CONFIG = {
|
|
154
|
-
lagThresholdMs:
|
|
155
|
-
tripDwellMs:
|
|
154
|
+
lagThresholdMs: 150,
|
|
155
|
+
tripDwellMs: 2e3,
|
|
156
156
|
recoveryDwellMs: 1e4,
|
|
157
157
|
cooldownsMs: [3e4, 6e4, 12e4, 3e5]
|
|
158
158
|
};
|
|
@@ -927,7 +927,12 @@ var _FoxgloveClient = class _FoxgloveClient {
|
|
|
927
927
|
return;
|
|
928
928
|
}
|
|
929
929
|
if (options?.priority === "control") {
|
|
930
|
-
this.controlOutbox.
|
|
930
|
+
const existing = this.controlOutbox.findIndex((e) => e.channelId === clientChannelId);
|
|
931
|
+
if (existing >= 0) {
|
|
932
|
+
this.controlOutbox[existing] = { channelId: clientChannelId, data };
|
|
933
|
+
} else {
|
|
934
|
+
this.controlOutbox.push({ channelId: clientChannelId, data });
|
|
935
|
+
}
|
|
931
936
|
this.scheduleControlFlush();
|
|
932
937
|
return;
|
|
933
938
|
}
|
|
@@ -1281,7 +1286,7 @@ var _FoxgloveClient = class _FoxgloveClient {
|
|
|
1281
1286
|
const sec = Math.floor(timestampNs / 1e9);
|
|
1282
1287
|
const nsec = timestampNs % 1e9;
|
|
1283
1288
|
const payloadOffset = 13;
|
|
1284
|
-
const payload = buffer
|
|
1289
|
+
const payload = new Uint8Array(buffer, payloadOffset);
|
|
1285
1290
|
const sub = this.subscriptions.get(subscriptionId);
|
|
1286
1291
|
if (!sub) return;
|
|
1287
1292
|
if (sub.isPaused) return;
|
|
@@ -1310,18 +1315,18 @@ var _FoxgloveClient = class _FoxgloveClient {
|
|
|
1310
1315
|
const text = TEXT_DECODER.decode(payload);
|
|
1311
1316
|
data = JSON.parse(text);
|
|
1312
1317
|
} catch {
|
|
1313
|
-
data =
|
|
1318
|
+
data = payload;
|
|
1314
1319
|
}
|
|
1315
1320
|
} else {
|
|
1316
1321
|
const reader = this.messageReaders.get(subscriptionId);
|
|
1317
1322
|
if (reader) {
|
|
1318
1323
|
try {
|
|
1319
|
-
data = reader.readMessage(
|
|
1324
|
+
data = reader.readMessage(payload);
|
|
1320
1325
|
} catch {
|
|
1321
|
-
data =
|
|
1326
|
+
data = payload;
|
|
1322
1327
|
}
|
|
1323
1328
|
} else {
|
|
1324
|
-
data =
|
|
1329
|
+
data = payload;
|
|
1325
1330
|
}
|
|
1326
1331
|
}
|
|
1327
1332
|
const rosMsg = {
|
|
@@ -1937,7 +1942,12 @@ var _RosbridgeClient = class _RosbridgeClient {
|
|
|
1937
1942
|
}
|
|
1938
1943
|
const payload = { op: "publish", topic, msg: data };
|
|
1939
1944
|
if (options?.priority === "control") {
|
|
1940
|
-
this.controlOutbox.
|
|
1945
|
+
const existing = this.controlOutbox.findIndex((e) => e.topic === topic);
|
|
1946
|
+
if (existing >= 0) {
|
|
1947
|
+
this.controlOutbox[existing] = payload;
|
|
1948
|
+
} else {
|
|
1949
|
+
this.controlOutbox.push(payload);
|
|
1950
|
+
}
|
|
1941
1951
|
this.scheduleControlFlush();
|
|
1942
1952
|
return;
|
|
1943
1953
|
}
|