The problem being solved
Reticulum::loop() only runs Transport::jobs() once Type::Reticulum::JOB_INTERVAL (60 s in Type.h) has elapsed since the previous pass. That pass is where the time-sensitive transport work lives:
- retransmission of announces held in
_announce_table — i.e. forwarding an announce received on one interface to the others, which is the core job of a transport node;
- link and pending-link timeouts, including expiring the path and re-requesting it when a link never activates;
- resource watchdogs on active links (retransmit/timeout of dropped parts);
- packet-receipt timeouts and culling.
With a 60 s cadence every one of these waits up to a minute. The reference Python implementation runs the equivalent Transport.jobs() every 0.25 s.
Observed on an ESP32-S3 node (microReticulum 0.5.0, Arduino core 2.0.17 / ESP-IDF 4.4.7, Transport enabled, one LoRa and one TCP interface, loop() called continuously from a dedicated FreeRTOS task): an announce arriving on one interface was forwarded on the other anywhere between 0 and 60 s later depending on where in the interval it landed, and link setups from a phone client across the node showed the same latency on the timeout/retry path.
Why existing functionality is insufficient
The interval is a compile-time constant with no API to change it. The only workaround is for the application to call Transport::jobs() itself on its own schedule, which duplicates what loop() already does, relies on knowledge of library internals, and — since loop() keeps its own _jobs_last_run — still leaves the library's pass running on top of it.
Proposed solution
Expose the interval as a runtime setting on Reticulum, defaulting to the current JOB_INTERVAL so nothing changes unless an application opts in:
// seconds; default Type::Reticulum::JOB_INTERVAL (60)
static float Reticulum::jobs_interval();
static void Reticulum::jobs_interval(float seconds);
loop() compares against the configured value instead of the constant. An application that needs prompt propagation sets it once at startup, e.g. RNS::Reticulum::jobs_interval(1.0f);. 1 s has been running for hours on an ESP32-S3 with no measurable load increase; announces now cross the node in ≤ 1 s.
Possible alternatives
- Lower
JOB_INTERVAL itself (to 1 s, or 0.25 s like RNS). Simpler, but it changes behaviour for every existing user and the right value depends on the platform's budget — better handled as a separate decision once the knob exists.
- Run the announce-retransmit part of
jobs() on every loop() and keep the slower cadence for the culling work. Better latency for the common case, but a larger change to Transport and harder to reason about.
- Leave it to applications to call
Transport::jobs() (status quo) — see above.
Expected impact on existing users
None unless the setter is called; the default remains 60 s.
Compatibility concerns
Additive API only (one getter, one setter, one static member); no protocol, storage or configuration-format changes. The JOB_INTERVAL constant stays in place for anything that references it.
I have a small, focused PR ready (tested on ESP32-S3; native build unaffected) and will link it to this issue.
The problem being solved
Reticulum::loop()only runsTransport::jobs()onceType::Reticulum::JOB_INTERVAL(60 s inType.h) has elapsed since the previous pass. That pass is where the time-sensitive transport work lives:_announce_table— i.e. forwarding an announce received on one interface to the others, which is the core job of a transport node;With a 60 s cadence every one of these waits up to a minute. The reference Python implementation runs the equivalent
Transport.jobs()every 0.25 s.Observed on an ESP32-S3 node (microReticulum 0.5.0, Arduino core 2.0.17 / ESP-IDF 4.4.7, Transport enabled, one LoRa and one TCP interface,
loop()called continuously from a dedicated FreeRTOS task): an announce arriving on one interface was forwarded on the other anywhere between 0 and 60 s later depending on where in the interval it landed, and link setups from a phone client across the node showed the same latency on the timeout/retry path.Why existing functionality is insufficient
The interval is a compile-time constant with no API to change it. The only workaround is for the application to call
Transport::jobs()itself on its own schedule, which duplicates whatloop()already does, relies on knowledge of library internals, and — sinceloop()keeps its own_jobs_last_run— still leaves the library's pass running on top of it.Proposed solution
Expose the interval as a runtime setting on
Reticulum, defaulting to the currentJOB_INTERVALso nothing changes unless an application opts in:loop()compares against the configured value instead of the constant. An application that needs prompt propagation sets it once at startup, e.g.RNS::Reticulum::jobs_interval(1.0f);. 1 s has been running for hours on an ESP32-S3 with no measurable load increase; announces now cross the node in ≤ 1 s.Possible alternatives
JOB_INTERVALitself (to 1 s, or 0.25 s like RNS). Simpler, but it changes behaviour for every existing user and the right value depends on the platform's budget — better handled as a separate decision once the knob exists.jobs()on everyloop()and keep the slower cadence for the culling work. Better latency for the common case, but a larger change toTransportand harder to reason about.Transport::jobs()(status quo) — see above.Expected impact on existing users
None unless the setter is called; the default remains 60 s.
Compatibility concerns
Additive API only (one getter, one setter, one static member); no protocol, storage or configuration-format changes. The
JOB_INTERVALconstant stays in place for anything that references it.I have a small, focused PR ready (tested on ESP32-S3; native build unaffected) and will link it to this issue.