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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! `web_pub` — stream values out to connected WebSocket clients.
//!
//! Each call to [`web_pub`] (or the fluent [`WebPubOperators::web_pub`])
//! registers a topic on the [`WebServer`]. Upstream values are serialized
//! with the server's codec, wrapped in an [`Envelope`], and broadcast to
//! every WebSocket connection subscribed to that topic.
//!
//! Each envelope payload is a **burst** — the sequence of values that share
//! one `time_ns` — matching the [`Burst<T>`] the graph engine already deals
//! in. On the wire the payload therefore always decodes to an *array*, and
//! the browser client decides whether to collapse it to the latest value or
//! consume the whole group. In real time each frame carries a single-element
//! burst (emitted immediately, no batching latency); in historical mode
//! consecutive same-`time_ns` values are grouped into one atomic frame, so a
//! lossy drop can never split a timestamp and a replay stays coherent.
//!
//! Slow consumers **do not** back-pressure the graph: each client has a
//! bounded outbound queue and a lossy broadcast receiver, so a frozen
//! browser tab simply drops frames.
use Pin;
use Rc;
use Bytes;
use StreamExt;
use Serialize;
use ;
use WebServer;
use crate;
use crate*;
/// Publish every upstream value on `topic`.
///
/// Works identically under `RunMode::RealTime` and
/// `RunMode::HistoricalFrom` — a historical replay (or any finite
/// `RunFor`) streams its values out to subscribed clients just like a
/// live run, which is what powers browser-side visualisation of a
/// backtest / slow computation. Each value is sent immediately (no
/// batching latency). When the upstream source ends (source exhausted or
/// the `RunFor` limit reached) a [`ControlMessage::Complete`] is broadcast
/// so clients know the stream is finished rather than merely dropped.
///
/// # Bursts
///
/// The payload is the codec-serialized upstream value. A scalar `T`
/// (number, struct, …) is a single JSON/bincode value; the browser client
/// treats it as a one-element burst. Publishing a stream whose value is a
/// [`Burst<T>`] (e.g. straight from [`web_sub`](super::web_sub) or an
/// async source) serializes it as an **array**, which the client surfaces
/// as the whole same-`time_ns` group — atomic on the wire, so a lossy drop
/// can never split a timestamp. See `subscribe` / `subscribeBurst` in
/// `@wingfoil/client`.
///
/// The exception is a server built with
/// [`WebServerBuilder::start_historical`](super::WebServerBuilder::start_historical),
/// whose [`WebServer::is_historical_noop`] flag makes both `web_pub` and
/// `web_sub` no-ops: the consumer drains the source without touching the
/// network, so a backtest that does *not* want a server can run the same
/// graph unmodified.
///
/// Back-pressure note: subscribed clients never stall the graph — each
/// client has a bounded, lossy outbound path (see the server broadcast
/// buffer), so a client that cannot keep up drops frames. For a faithful,
/// loss-free replay, pace the graph so it does not outrun the client
/// (e.g. a genuinely compute-bound historical run).
/// Encode a [`ControlMessage::Complete`] as a control-topic [`Envelope`]
/// ready to broadcast on a publish topic's channel. It is addressed to
/// [`CONTROL_TOPIC`] so the browser client routes it through its control
/// handler, while riding the publish topic's broadcast so only clients
/// subscribed to that topic receive it.
/// Fluent `.web_pub(...)` on streams.