1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! Shared "dashboard request -> node resend" pattern.
//!
//! The websocket relay that forwards pub/sub topics to the dashboard never replays old
//! messages to a late-subscribing client - a dashboard tab opened (or reconnected) after a node
//! already published its one-shot/startup-only state just never sees it. Several nodes solve
//! this the same way: the dashboard publishes an empty "please resend" request on a dedicated
//! request topic, and the node republishes its latest state on its own (separate, already
//! `relay_topics`-gated) response topic in reply.
//!
//! Before this module, each node hand-rolled an identical empty request struct plus the
//! subscribe boilerplate around it (`behavior-executor`'s `ListBehaviorsRequest`,
//! `object-detector`'s `ClassesRequest`, `teleop`'s empty status-request struct). This module
//! factors out just the request side - [`OnDemandRequest`] and [`subscribe_on_demand`] - so new
//! nodes don't need to declare their own empty struct and raw subscribe call to join the
//! pattern.
//!
//! What stays node-specific, deliberately:
//! - **The response topic and type.** `relay_topics` gates outbound (node -> dashboard) topics
//! per-topic for type safety, so there's no shared "dashboard state" channel to collapse onto.
//! - **The request topic path.** Each node still defines its own `Topic<OnDemandRequest>`
//! constant (e.g. `/behavior/list`, `/vision/object/classes/request`,
//! `/teleop/status/request`) and still independently opts into relaying its response topic.
//! - **What happens on receipt.** Receiving an [`OnDemandRequest`] just means "resend your
//! current state" - the node decides what that state is and how to republish it.
//!
//! # Example
//!
//! ```rust,no_run
//! use mecha10_core::on_demand::{subscribe_on_demand, OnDemandRequest};
//! use mecha10_core::prelude::*;
//! use mecha10_core::topics::Topic;
//!
//! const CLASSES_REQUEST: Topic<OnDemandRequest> = Topic::new("/vision/object/classes/request");
//!
//! # async fn example(ctx: &Context, publish_classes: impl Fn() -> Result<()>) -> Result<()> {
//! let mut requests = subscribe_on_demand(ctx, CLASSES_REQUEST).await?;
//!
//! while requests.recv().await.is_some() {
//! publish_classes()?;
//! }
//! # Ok(())
//! # }
//! ```
use crateMessage;
use ;
/// Empty-payload request asking a node to resend its latest on-demand state.
///
/// Carries no data - receiving one at all is the entire signal. A dashboard publishes this
/// (typically once at connect, or on every `isConnected` transition to also cover reconnects)
/// on a node's dedicated request topic to ask it to republish whatever state it would otherwise
/// have only sent once at startup or on change.
;
pub use subscribe_on_demand;