Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for in-app "Quick Start" guides #3813

Merged
merged 22 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion crates/re_data_store/src/store_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ impl EntityDb {

// ----------------------------------------------------------------------------

/// A in-memory database built from a stream of [`LogMsg`]es.
/// An in-memory database built from a stream of [`LogMsg`]es.
///
/// NOTE: all mutation is to be done via public functions!
pub struct StoreDb {
Expand All @@ -237,6 +237,27 @@ impl StoreDb {
}
}

/// Helper function to create a recording from a [`StoreInfo`] and a some [`DataRow`]s.
///
/// This is useful to programmatically create recordings from within the viewer, which cannot
/// use the `re_sdk`, which is not Wasm-compatible.
pub fn from_info_and_rows(
store_info: StoreInfo,
rows: impl IntoIterator<Item = DataRow>,
) -> Result<Self, Error> {
let mut store_db = StoreDb::new(store_info.store_id.clone());

store_db.set_store_info(SetStoreInfo {
row_id: RowId::random(),
info: store_info,
});
for row in rows {
store_db.add_data_row(&row)?;
}

Ok(store_db)
}

#[inline]
pub fn entity_db(&self) -> &EntityDb {
&self.entity_db
Expand Down
4 changes: 4 additions & 0 deletions crates/re_log_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ pub enum StoreSource {
file_source: FileSource,
},

/// Generated from the viewer itself.
Viewer,

/// Perhaps from some manual data ingestion?
Other(String),
}
Expand All @@ -362,6 +365,7 @@ impl std::fmt::Display for StoreSource {
FileSource::DragAndDrop => write!(f, "File via drag-and-drop"),
FileSource::FileDialog => write!(f, "File via file dialog"),
},
Self::Viewer => write!(f, "Viewer-generated"),
Self::Other(string) => format!("{string:?}").fmt(f), // put it in quotes
}
}
Expand Down
3 changes: 3 additions & 0 deletions crates/re_viewer/data/quick_start_guides/cpp_native.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## C++ Quick Start

TODO(ab): https://github.com/rerun-io/rerun/issues/3870
9 changes: 9 additions & 0 deletions crates/re_viewer/data/quick_start_guides/how_does_it_work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### How does it work?

Rerun's goal is to make handling and visualizing multimodal data streams easy and performant.

Rerun is made of two main building blocks: the SDK and the Viewer. The data provided by the user code is serialised by the SDK and transferred (via a log file, a TCP socket, a WebSocket, etc.) to the Viewer process for visualization. You can learn more about Rerun's operating modes [here](https://www.rerun.io/docs/reference/sdk-operating-modes).

In the example above, the SDK connects via a TCP socket to the present viewer.

The `log()` function logs _entities_ represented by the "entity path" provided as first argument. Entities are a collection of _components_, which hold the actual data such as position, color, or pixel data. _Archetypes_ such as `Points3D` are builder objects which help creating entities with a consistent set of components that are recognized by the Viewer (they can be entirely bypassed when required by advanced use-cases). You can learn more about Rerun's data model [here](https://www.rerun.io/docs/concepts/entity-component).
33 changes: 33 additions & 0 deletions crates/re_viewer/data/quick_start_guides/python_native.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Python Quick Start

### Installing the Rerun SDK

The Rerun SDK is available on [PyPI](https://pypi.org/) under the
[`rerun-sdk`](https://pypi.org/project/rerun-sdk/) name. It can be installed like any other
Python package:

```sh
pip install rerun-sdk
```

### Try out the viewer

The Rerun SDK comes with a demo that can be used to try the viewer. You can send a demo recording
to this viewer using the following command:

```sh
python -m rerun_sdk.demo --connect
```

This will open a new recording that looks like this:

![Demo recording](https://static.rerun.io/quickstart2_simple_cube/632a8f1c79f70a2355fad294fe085291fcf3a8ae/768w.png)


### Logging your own data

Instead of a pre-packaged demo, you can log your own data. Copy and paste the following snippet in a new Python file and execute it to create a new recording in this viewer:

```python
${EXAMPLE_CODE}
```
31 changes: 31 additions & 0 deletions crates/re_viewer/data/quick_start_guides/rust_native.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Rust Quick Start

### Installing Rerun

To use the Rerun SDK in your project, you need the [rerun crate](https://crates.io/crates/rerun) which you can add with `cargo add rerun`.

Let's try it out in a brand-new Rust project:

```sh
cargo init cube && cd cube && cargo add rerun --features native_viewer
```

Note that the Rerun SDK requires a working installation of Rust 1.72+.

### Logging your own data

Add the following code to your `main.rs` file:

```rust
${EXAMPLE_CODE}
```

You can now run your application:

```shell
cargo run
```

Once everything finishes compiling, you will see the points in this viewer:

![Demo recording](https://static.rerun.io/intro_rust_result/cc780eb9bf014d8b1a68fac174b654931f92e14f/768w.png)
6 changes: 6 additions & 0 deletions crates/re_viewer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ impl App {
}
}

SystemCommand::LoadStoreDb(store_db) => {
let store_id = store_db.store_id().clone();
store_hub.insert_recording(store_db);
store_hub.set_recording_id(store_id);
}

SystemCommand::ResetViewer => self.reset(store_hub, egui_ctx),
SystemCommand::UpdateBlueprint(blueprint_id, updates) => {
let blueprint_db = store_hub.store_db_mut(&blueprint_id);
Expand Down
5 changes: 4 additions & 1 deletion crates/re_viewer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ impl AppEnvironment {
llvm_version: llvm_version.clone(),
},

StoreSource::File { .. } | StoreSource::Unknown | StoreSource::Other(_) => {
StoreSource::File { .. }
| StoreSource::Unknown
| StoreSource::Viewer
| StoreSource::Other(_) => {
// We should not really get here

#[cfg(debug_assertions)]
Expand Down
8 changes: 8 additions & 0 deletions crates/re_viewer/src/store_hub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ impl StoreHub {
}
}

/// Insert a new recording into the [`StoreHub`].
///
/// Note that the recording is not automatically made active. Use [`StoreHub::set_recording_id`]
/// if needed.
pub fn insert_recording(&mut self, store_db: StoreDb) {
self.store_dbs.insert_recording(store_db);
}

/// Mutable access to a [`StoreDb`] by id
pub fn store_db_mut(&mut self, store_id: &StoreId) -> &mut StoreDb {
self.store_dbs.store_db_entry(store_id)
Expand Down
111 changes: 102 additions & 9 deletions crates/re_viewer/src/ui/welcome_screen/welcome_page.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use super::{large_text_button, status_strings, url_large_text_button, WelcomeScreenResponse};
use egui::{NumExt, Ui};
use re_log_types::LogMsg;
use itertools::Itertools;
use re_data_store::StoreDb;
use re_log_types::{
DataRow, EntityPath, LogMsg, RowId, StoreId, StoreInfo, StoreKind, StoreSource, Time, TimePoint,
};
use re_smart_channel::ReceiveSet;
use re_ui::UICommandSender;
use re_viewer_context::{SystemCommand, SystemCommandSender};

//const CPP_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/cpp";
const PYTHON_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/python";
const RUST_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/rust";
const SPACE_VIEWS_HELP: &str = "https://www.rerun.io/docs/getting-started/viewer-walkthrough";

/// Show the welcome page.
Expand Down Expand Up @@ -58,10 +60,50 @@ fn onboarding_content_ui(
Visualize synchronized data from multiple processes, locally or over a network.",
image: &re_ui::icons::WELCOME_SCREEN_LIVE_DATA,
add_buttons: Box::new(|ui: &mut egui::Ui| {
// TODO(ab): activate when C++ is ready!
// url_large_text_button(ui, "C++", CPP_QUICKSTART);
url_large_text_button(ui, "Python", PYTHON_QUICKSTART);
url_large_text_button(ui, "Rust", RUST_QUICKSTART);
//TODO(#3870): enable with C++ guides are completed
#[allow(clippy::collapsible_if)]
if false {
if large_text_button(ui, "C++").clicked() {
open_quick_start(
command_sender,
[
include_str!("../../../data/quick_start_guides/cpp_native.md"),
include_str!(
"../../../data/quick_start_guides/how_does_it_work.md"
),
],
include_str!(
"../../../data/quick_start_guides/quick_start_connect.cpp"
),
"C++ Quick Start",
"cpp_quick_start",
);
}
}
if large_text_button(ui, "Python").clicked() {
open_quick_start(
command_sender,
[
include_str!("../../../data/quick_start_guides/python_native.md"),
include_str!("../../../data/quick_start_guides/how_does_it_work.md"),
],
include_str!("../../../data/quick_start_guides/quick_start_connect.py"),
"Python Quick Start",
"python_quick_start",
);
}
if large_text_button(ui, "Rust").clicked() {
open_quick_start(
command_sender,
[
include_str!("../../../data/quick_start_guides/rust_native.md"),
include_str!("../../../data/quick_start_guides/how_does_it_work.md"),
],
include_str!("../../../data/quick_start_guides/quick_start_connect.rs"),
"Rust Quick Start",
"rust_quick_start",
);
}

false
}),
Expand Down Expand Up @@ -102,7 +144,7 @@ fn onboarding_content_ui(
},
];

// Shrink images if needed so user can see all of the content buttons
// Shrink images if needed so user can see all the content buttons
let max_image_height = ui.available_height() - 300.0;

let centering_vspace = (ui.available_height() - 650.0) / 2.0;
Expand Down Expand Up @@ -231,3 +273,54 @@ fn image_banner(ui: &mut egui::Ui, icon: &re_ui::Icon, column_width: f32, max_im
);
});
}

/// Open a Quick Start recording
///
/// The `parts` are joined with newlines to form the markdown, and the spacial tag
/// `"${EXAMPLE_CODE}"` is replaced with the content of th `example_code` variable.
fn open_quick_start<'a>(
command_sender: &re_viewer_context::CommandSender,
parts: impl IntoIterator<Item = &'a str>,
example_code: &str,
app_id: &str,
entity_path: &str,
) {
let mut markdown = parts.into_iter().join("\n");
markdown = markdown.replace("${EXAMPLE_CODE}", example_code);

let res = open_markdown_recording(command_sender, markdown.as_str(), app_id, entity_path);
if let Err(err) = res {
re_log::error!("Failed to load quick start: {}", err);
}
}

fn open_markdown_recording(
command_sender: &re_viewer_context::CommandSender,
markdown: &str,
app_id: &str,
entity_path: &str,
) -> anyhow::Result<()> {
let text_doc = re_types::archetypes::TextDocument::new(markdown)
.with_media_type(re_types::components::MediaType::markdown());

let row = DataRow::from_archetype(
RowId::random(),
TimePoint::timeless(),
EntityPath::from(entity_path),
&text_doc,
)?;

let store_info = StoreInfo {
application_id: app_id.into(),
store_id: StoreId::random(StoreKind::Recording),
is_official_example: true,
started: Time::now(),
store_source: StoreSource::Viewer,
store_kind: StoreKind::Recording,
};

let store_db = StoreDb::from_info_and_rows(store_info, [row])?;
command_sender.send_system(SystemCommand::LoadStoreDb(store_db));

Ok(())
}
3 changes: 2 additions & 1 deletion crates/re_viewer/src/viewer_analytics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ impl ViewerAnalytics {
re_log_types::FileSource::DragAndDrop => "file_drag_and_drop".to_owned(),
re_log_types::FileSource::FileDialog => "file_dialog".to_owned(),
},
StoreSource::Viewer => "viewer".to_owned(),
StoreSource::Other(other) => other.clone(),
};

Expand Down Expand Up @@ -210,7 +211,7 @@ impl ViewerAnalytics {
self.deregister("llvm_version"); // can't be both!
}
StoreSource::CSdk => {} // TODO(andreas): Send version and set it.
StoreSource::Unknown | StoreSource::Other(_) => {}
StoreSource::Unknown | StoreSource::Viewer | StoreSource::Other(_) => {}
}

self.register("store_source", store_source);
Expand Down
4 changes: 4 additions & 0 deletions crates/re_viewer_context/src/command_sender.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use re_data_source::DataSource;
use re_data_store::StoreDb;
use re_log_types::{DataRow, StoreId};
use re_ui::{UICommand, UICommandSender};

Expand All @@ -10,6 +11,9 @@ pub enum SystemCommand {
/// Load some data.
LoadDataSource(DataSource),

/// Load some log messages.
LoadStoreDb(StoreDb),

/// Reset the `Viewer` to the default state
ResetViewer,

Expand Down
8 changes: 6 additions & 2 deletions docs/code-examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ path = "asset3d_simple.rs"
name = "asset3d_out_of_tree"
path = "asset3d_out_of_tree.rs"

[[bin]]
name = "box2d_simple"
path = "box2d_simple.rs"

[[bin]]
name = "box3d_simple"
path = "box3d_simple.rs"
Expand Down Expand Up @@ -132,8 +136,8 @@ name = "point3d_simple"
path = "point3d_simple.rs"

[[bin]]
name = "box2d_simple"
path = "box2d_simple.rs"
name = "quick_start_connect"
path = "quick_start_connect.rs"

[[bin]]
name = "scalar_simple"
Expand Down
4 changes: 4 additions & 0 deletions docs/code-examples/quick_start_connect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// TODO(ab): https://github.com/rerun-io/rerun/issues/3870

void main() {
}
Loading
Loading