Skip to content

Commit ca8bcc1

Browse files
author
dirkwa
committed
docs(plugins): recommend sourcePolicy:all for correction plugins
excludeSelf runs the priority cascade on the plugin's own input feed, so when the user ranks a correction plugin above its upstream source the real source is throttled to the fallback timeout instead of arriving at full rate. A correction plugin's input must be independent of prioritisation: subscribe with sourcePolicy:'all' and skip updates whose $source is the plugin id; prioritisation then applies only to consumers. Note that $source is a property of the update, not of each value.
1 parent c6de382 commit ca8bcc1

1 file changed

Lines changed: 15 additions & 7 deletions

File tree

docs/develop/plugins/deltas.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,11 @@ The query-string default applies to the bootstrap cache replay and to per-messag
185185

186186
#### Excluding Sources: `excludeSources` / `excludeSelf`
187187

188-
A derived-data plugin often consumes one or more upstream sources for a path and publishes an improved value on the same path under its own label. With the user's [source priority](../../setup/source-priority.md) ranking the plugin's output above the upstream sources, downstream consumers get the improved value. But the plugin itself cannot just subscribe with `sourcePolicy: 'preferred'` that returns the priority winner, which is the plugin's own output, creating a feedback loop. Subscribing with `sourcePolicy: 'all'` avoids the loop but loses the priority cascade across the upstream sources.
188+
A plugin may want a priority-resolved view of a path with one or more sources removed from the cascade — for example to see the preferred upstream source while ignoring a known-bad device. `excludeSources` / `excludeSelf` provide that: the subscription still receives a single priority-resolved value per path, but the cascade runs without the excluded refs.
189189

190-
`excludeSources` removes the listed source refs from the priority cascade's candidate set. The subscription still receives a single priority-resolved value per path; the cascade just runs without the excluded sources, with the same fallback semantics the user configured.
190+
> **Note:** Because the cascade runs on the subscription's feed, this delivers a single priority-resolved value — and if the user ranks the plugin itself above the upstream source, that source is held to the fallback timeout on the plugin's own input. That is the right behaviour for a plugin that wants _the preferred remaining upstream value_, but **not** for a correction/transform plugin that must see every raw sample at full rate — for that case see [Correction and transform plugins](#correction-and-transform-plugins) below, which uses `sourcePolicy: 'all'`.
191+
192+
The same fallback semantics the user configured still apply across the remaining sources:
191193

192194
```javascript
193195
let localSubscription = {
@@ -267,10 +269,13 @@ app.handleMessage(
267269

268270
A common pattern is a plugin that reads an upstream value on a path, applies a correction or transform, and publishes an improved value on the **same** path under its own label — a speed-through-water heel correction, a calibration offset, and so on. The user then ranks the plugin's output above the raw source in [source priority](../../setup/source-priority.md), so downstream consumers get the corrected value.
269271

270-
For this pattern, **subscribe with `excludeSelf`** as described in [Excluding Sources](#excluding-sources-excludesources--excludeself). Do **not** use `registerDeltaInputHandler`:
272+
A correction plugin needs its **input** at full rate, _independent_ of the priority ranking — you correct every reading from the upstream source, not just whichever one a cascade would currently prefer — while the user's ranking decides what _consumers_ see. Subscribe with **`sourcePolicy: 'all'`** and skip your own output in the callback:
273+
274+
- `sourcePolicy: 'all'` delivers every source at full rate with no cascade on your input. Skip updates whose `$source` is your own `plugin.id` so you don't reprocess your output. The global priority cascade still applies to consumers, so the user's ranking of your output works as expected.
275+
276+
Do **not** use `registerDeltaInputHandler` for this: delta input handlers run **after** source-priority filtering, so once the user ranks your output above the raw source the filter removes the raw value before your handler sees it — the handler stops firing, your value goes stale, the path falls back to the raw source, your handler fires once, and the path oscillates.
271277

272-
- Delta input handlers run **after** source-priority filtering. Once the user ranks your output above the raw source, the priority filter removes the raw source's value before your handler sees it. Your handler stops being triggered, your corrected value goes stale and falls back to the raw source, the raw source flows again, your handler fires once — and the path oscillates.
273-
- An `excludeSelf` subscription reads every source on the path regardless of ranking (so the raw source always reaches you) while masking out your own output (so you don't re-process it). The user's priority ranking still decides which value is canonical for everyone else.
278+
`excludeSelf` (see [Excluding Sources](#excluding-sources-excludesources--excludeself)) is a different tool and **not** what you want for a full-rate correction. It runs the priority cascade on your input feed, so it delivers a single ranked-and-throttled upstream value rather than every reading: with the user ranking your plugin rank-0, the real source is held as a lower-ranked fallback and only reaches you after the fallback timeout. `excludeSelf` fits a plugin that wants _the preferred remaining upstream value_ (e.g. "show source B, fall back to A, never my own C"), not one that corrects each raw sample.
274279

275280
A complete heel-correction example:
276281

@@ -281,18 +286,21 @@ plugin.start = () => {
281286
app.subscriptionmanager.subscribe(
282287
{
283288
context: 'vessels.self',
284-
excludeSelf: true,
289+
sourcePolicy: 'all',
285290
subscribe: [{ path: 'navigation.speedThroughWater' }]
286291
},
287292
unsubscribes,
288293
(err) => app.setPluginError(err),
289294
(delta) => {
290295
delta.updates.forEach((u) => {
296+
// $source is a property of the update, not of each value. Skip
297+
// our own output so we don't reprocess the corrected value.
298+
if (u.$source === plugin.id) return
291299
u.values.forEach((pv) => {
292300
if (pv.path !== 'navigation.speedThroughWater') return
293301
const corrected = applyHeelCorrection(pv.value)
294302
// No source object: $source defaults to plugin.id, so the
295-
// excludeSelf subscription masks this delta and does not loop.
303+
// guard above recognises and skips this delta on the way back.
296304
app.handleMessage(plugin.id, {
297305
context: 'vessels.self',
298306
updates: [

0 commit comments

Comments
 (0)