1use std::path::PathBuf;
4
5use re_sdk::{RecordingStream, RecordingStreamBuilder};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
10enum RerunBehavior {
11 Connect(String),
12
13 Save(PathBuf),
14
15 Stdout,
16
17 #[cfg(feature = "web_viewer")]
18 Serve,
19
20 Spawn,
21}
22
23#[derive(Clone, Debug, clap::Args)]
42#[clap(author, version, about)]
43pub struct RerunArgs {
44 #[clap(long, default_value = "true")]
46 spawn: bool,
47
48 #[clap(long)]
50 save: Option<PathBuf>,
51
52 #[clap(long, short = 'o')]
54 stdout: bool,
55
56 #[clap(long)]
65 #[expect(clippy::option_option)]
66 connect: Option<Option<String>>,
67
68 #[cfg(feature = "web_viewer")]
70 #[clap(long)]
71 serve: bool,
72
73 #[clap(long, default_value = "25%")]
82 server_memory_limit: String,
83
84 #[clap(long)]
86 newest_first: bool,
87
88 #[clap(long, default_value = "0.0.0.0")]
90 bind: String,
91}
92
93#[doc(hidden)]
97#[derive(Default)]
98pub struct ServeGuard {
99 block_on_drop: bool,
100}
101
102impl Drop for ServeGuard {
103 fn drop(&mut self) {
104 if self.block_on_drop {
105 eprintln!("Sleeping indefinitely while serving web viewer… Press ^C when done.");
106 std::thread::sleep(std::time::Duration::from_secs(u64::MAX));
108 }
109 }
110}
111
112impl RerunArgs {
113 #[track_caller] pub fn init(&self, application_id: &str) -> anyhow::Result<(RecordingStream, ServeGuard)> {
116 self.init_with_blueprint_opts(application_id, None)
117 }
118
119 #[track_caller]
123 pub fn init_with_blueprint(
124 &self,
125 application_id: &str,
126 blueprint: re_sdk::blueprint::Blueprint,
127 ) -> anyhow::Result<(RecordingStream, ServeGuard)> {
128 let activation = re_sdk::blueprint::BlueprintActivation {
129 make_active: true,
130 make_default: true,
131 };
132 self.init_with_blueprint_opts(
133 application_id,
134 Some(re_sdk::blueprint::BlueprintOpts {
135 blueprint,
136 activation,
137 }),
138 )
139 }
140
141 #[track_caller]
145 pub fn init_with_default_blueprint(
146 &self,
147 application_id: &str,
148 blueprint: re_sdk::blueprint::Blueprint,
149 ) -> anyhow::Result<(RecordingStream, ServeGuard)> {
150 let activation = re_sdk::blueprint::BlueprintActivation {
151 make_active: false,
152 make_default: true,
153 };
154 self.init_with_blueprint_opts(
155 application_id,
156 Some(re_sdk::blueprint::BlueprintOpts {
157 blueprint,
158 activation,
159 }),
160 )
161 }
162
163 #[track_caller]
165 fn init_with_blueprint_opts(
166 &self,
167 application_id: &str,
168 blueprint_opts: Option<re_sdk::blueprint::BlueprintOpts>,
169 ) -> anyhow::Result<(RecordingStream, ServeGuard)> {
170 let mut builder = RecordingStreamBuilder::new(application_id);
171
172 if let Some(re_sdk::blueprint::BlueprintOpts {
174 blueprint,
175 activation,
176 }) = blueprint_opts
177 {
178 builder = if activation.make_active {
179 builder.with_blueprint(blueprint)
180 } else {
181 builder.with_default_blueprint(blueprint)
182 };
183 }
184
185 match self.to_behavior()? {
186 RerunBehavior::Stdout => Ok((builder.stdout()?, Default::default())),
187
188 RerunBehavior::Connect(url) => {
189 Ok((builder.connect_grpc_opts(url)?, Default::default()))
190 }
191
192 RerunBehavior::Save(path) => Ok((builder.save(path)?, Default::default())),
193
194 RerunBehavior::Spawn => Ok((builder.spawn()?, Default::default())),
195
196 #[cfg(feature = "web_viewer")]
197 RerunBehavior::Serve => {
198 let server_options = re_sdk::ServerOptions {
199 playback_behavior: re_sdk::PlaybackBehavior::from_newest_first(
200 self.newest_first,
201 ),
202
203 memory_limit: re_sdk::MemoryLimit::parse(&self.server_memory_limit)
204 .map_err(|err| anyhow::format_err!("Bad --server-memory-limit: {err}"))?,
205
206 cors_allowed_origins: vec![],
207 };
208
209 let rec = builder.serve_grpc_opts(
210 &self.bind,
211 crate::DEFAULT_SERVER_PORT,
212 server_options,
213 )?;
214
215 crate::serve_web_viewer(crate::web_viewer::WebViewerConfig {
216 open_browser: true,
217 connect_to: vec!["rerun+http://localhost:9876/proxy".to_owned()],
218 ..Default::default()
219 })?
220 .detach();
221
222 let sleep_guard = ServeGuard {
224 block_on_drop: true,
225 };
226
227 Ok((rec, sleep_guard))
228 }
229 }
230 }
231
232 #[expect(clippy::unnecessary_wraps)] fn to_behavior(&self) -> anyhow::Result<RerunBehavior> {
234 if self.stdout {
235 return Ok(RerunBehavior::Stdout);
236 }
237
238 if let Some(path) = self.save.as_ref() {
239 return Ok(RerunBehavior::Save(path.clone()));
240 }
241
242 #[cfg(feature = "web_viewer")]
243 if self.serve {
244 return Ok(RerunBehavior::Serve);
245 }
246
247 match &self.connect {
248 Some(Some(url)) => return Ok(RerunBehavior::Connect(url.clone())),
249 Some(None) => {
250 return Ok(RerunBehavior::Connect(
251 re_sdk::DEFAULT_CONNECT_URL.to_owned(),
252 ));
253 }
254 None => {}
255 }
256
257 Ok(RerunBehavior::Spawn)
258 }
259}