use std::path::PathBuf;
use re_sdk::{RecordingStream, RecordingStreamBuilder};
#[derive(Debug, Clone, PartialEq, Eq)]
enum RerunBehavior {
Connect(String),
Save(PathBuf),
Stdout,
#[cfg(feature = "web_viewer")]
Serve,
Spawn,
}
#[derive(Clone, Debug, clap::Args)]
#[clap(author, version, about)]
pub struct RerunArgs {
#[clap(long, default_value = "true")]
spawn: bool,
#[clap(long)]
save: Option<PathBuf>,
#[clap(long, short = 'o')]
stdout: bool,
#[clap(long)]
#[expect(clippy::option_option)]
connect: Option<Option<String>>,
#[cfg(feature = "web_viewer")]
#[clap(long)]
serve: bool,
#[clap(long, default_value = "25%")]
server_memory_limit: String,
#[clap(long)]
newest_first: bool,
#[clap(long, default_value = "0.0.0.0")]
bind: String,
}
#[doc(hidden)]
#[derive(Default)]
pub struct ServeGuard {
block_on_drop: bool,
}
impl Drop for ServeGuard {
fn drop(&mut self) {
if self.block_on_drop {
eprintln!("Sleeping indefinitely while serving web viewer… Press ^C when done.");
std::thread::sleep(std::time::Duration::from_secs(u64::MAX));
}
}
}
impl RerunArgs {
#[track_caller] pub fn init(&self, application_id: &str) -> anyhow::Result<(RecordingStream, ServeGuard)> {
self.init_with_blueprint_opts(application_id, None)
}
#[track_caller]
pub fn init_with_blueprint(
&self,
application_id: &str,
blueprint: re_sdk::blueprint::Blueprint,
) -> anyhow::Result<(RecordingStream, ServeGuard)> {
let activation = re_sdk::blueprint::BlueprintActivation {
make_active: true,
make_default: true,
};
self.init_with_blueprint_opts(
application_id,
Some(re_sdk::blueprint::BlueprintOpts {
blueprint,
activation,
}),
)
}
#[track_caller]
pub fn init_with_default_blueprint(
&self,
application_id: &str,
blueprint: re_sdk::blueprint::Blueprint,
) -> anyhow::Result<(RecordingStream, ServeGuard)> {
let activation = re_sdk::blueprint::BlueprintActivation {
make_active: false,
make_default: true,
};
self.init_with_blueprint_opts(
application_id,
Some(re_sdk::blueprint::BlueprintOpts {
blueprint,
activation,
}),
)
}
#[track_caller]
fn init_with_blueprint_opts(
&self,
application_id: &str,
blueprint_opts: Option<re_sdk::blueprint::BlueprintOpts>,
) -> anyhow::Result<(RecordingStream, ServeGuard)> {
let mut builder = RecordingStreamBuilder::new(application_id);
if let Some(re_sdk::blueprint::BlueprintOpts {
blueprint,
activation,
}) = blueprint_opts
{
builder = if activation.make_active {
builder.with_blueprint(blueprint)
} else {
builder.with_default_blueprint(blueprint)
};
}
match self.to_behavior()? {
RerunBehavior::Stdout => Ok((builder.stdout()?, Default::default())),
RerunBehavior::Connect(url) => {
Ok((builder.connect_grpc_opts(url)?, Default::default()))
}
RerunBehavior::Save(path) => Ok((builder.save(path)?, Default::default())),
RerunBehavior::Spawn => Ok((builder.spawn()?, Default::default())),
#[cfg(feature = "web_viewer")]
RerunBehavior::Serve => {
let server_options = re_sdk::ServerOptions {
playback_behavior: re_sdk::PlaybackBehavior::from_newest_first(
self.newest_first,
),
memory_limit: re_sdk::MemoryLimit::parse(&self.server_memory_limit)
.map_err(|err| anyhow::format_err!("Bad --server-memory-limit: {err}"))?,
};
let rec = builder.serve_grpc_opts(
&self.bind,
crate::DEFAULT_SERVER_PORT,
server_options,
)?;
crate::serve_web_viewer(crate::web_viewer::WebViewerConfig {
open_browser: true,
connect_to: vec!["rerun+http://localhost:9876/proxy".to_owned()],
..Default::default()
})?
.detach();
let sleep_guard = ServeGuard {
block_on_drop: true,
};
Ok((rec, sleep_guard))
}
}
}
#[expect(clippy::unnecessary_wraps)] fn to_behavior(&self) -> anyhow::Result<RerunBehavior> {
if self.stdout {
return Ok(RerunBehavior::Stdout);
}
if let Some(path) = self.save.as_ref() {
return Ok(RerunBehavior::Save(path.clone()));
}
#[cfg(feature = "web_viewer")]
if self.serve {
return Ok(RerunBehavior::Serve);
}
match &self.connect {
Some(Some(url)) => return Ok(RerunBehavior::Connect(url.clone())),
Some(None) => {
return Ok(RerunBehavior::Connect(
re_sdk::DEFAULT_CONNECT_URL.to_owned(),
));
}
None => {}
}
Ok(RerunBehavior::Spawn)
}
}