Skip to content

Commit

Permalink
Merge pull request #143 from thomasantony/web-support
Browse files Browse the repository at this point in the history
Add support for running the ANISE GUI in the browser
  • Loading branch information
ChristopherRabotin authored Dec 11, 2023
2 parents 7206d6a + 1281857 commit 7acdf5c
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 16 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/dist
/target
Cargo.lock
*.anis*
Expand All @@ -10,4 +11,4 @@ cspice.tar.Z
.venv
*.pca
*.sca
*.epa
*.epa
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ exclude = [
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
hifitime = "3.8"
hifitime = "3.8.6"
memmap2 = "=0.9.0"
crc32fast = "=1.3.2"
der = { version = "0.7.8", features = ["derive", "alloc", "real"] }
Expand All @@ -52,6 +52,10 @@ egui_extras = { version = "0.24.0", features = [
egui-toast = { version = "0.10.0", optional = true }
rfd = { version = "0.12.1", optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures = "0.4"
poll-promise = { version = "0.3.0", features = ["web"] }

[dev-dependencies]
rust-spice = "0.7.6"
parquet = "49.0.0"
Expand Down
11 changes: 11 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<html>
<head>
<link data-trunk rel="rust" data-bin="anise-gui" />
</head>
<body>
<!-- The WASM code will resize the canvas dynamically -->
<!-- the id is hardcoded in main.rs . so, make sure both match. -->
<canvas id="anise_canvas"></canvas>

</body>
</html>
9 changes: 7 additions & 2 deletions src/almanac/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Documentation: https://nyxspace.com/
*/

use bytes::Bytes;
use log::info;
use snafu::ResultExt;
use std::fs::File;
Expand Down Expand Up @@ -94,7 +95,11 @@ impl Almanac {
let bytes = file2heap!(path).with_context(|_| LoadingSnafu {
path: path.to_string(),
})?;
info!("Loading almanac from {path}");
self.load_from_bytes(bytes)
}

pub fn load_from_bytes(&self, bytes: Bytes) -> Result<Self, AlmanacError> {
// Try to load as a SPICE DAF first (likely the most typical use case)

// Load the header only
Expand All @@ -103,7 +108,7 @@ impl Almanac {
if let Ok(fileid) = file_record.identification() {
match fileid {
"PCK" => {
info!("Loading {path} as DAF/PCK");
info!("Loading as DAF/PCK");
let bpc = BPC::parse(bytes)
.with_context(|_| BPCSnafu {
action: "parsing bytes",
Expand All @@ -116,7 +121,7 @@ impl Almanac {
})
}
"SPK" => {
info!("Loading {path:?} as DAF/SPK");
info!("Loading as DAF/SPK");
let spk = SPK::parse(bytes)
.with_context(|_| SPKSnafu {
action: "parsing bytes",
Expand Down
28 changes: 25 additions & 3 deletions src/bin/anise-gui/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use pretty_env_logger;
use std::env::{set_var, var};

#[allow(dead_code)]
const LOG_VAR: &str = "ANISE_LOG";

mod ui;

use ui::UiApp;

#[cfg(not(target_arch = "wasm32"))]
fn main() {
use std::env::{set_var, var};

if var(LOG_VAR).is_err() {
set_var(LOG_VAR, "INFO");
}
Expand All @@ -20,3 +21,24 @@ fn main() {
Box::new(|cc| Box::new(UiApp::new(cc))),
);
}

// Entrypoint for WebAssembly
#[cfg(target_arch = "wasm32")]
fn main() {
use log::info;

eframe::WebLogger::init(log::LevelFilter::Debug).ok();
let web_options = eframe::WebOptions::default();

info!("Starting ANISE in WebAssembly mode");
wasm_bindgen_futures::spawn_local(async {
eframe::WebRunner::new()
.start(
"anise_canvas",
web_options,
Box::new(|cc| Box::new(UiApp::new(cc))),
)
.await
.expect("failed to start eframe");
});
}
79 changes: 70 additions & 9 deletions src/bin/anise-gui/ui.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anise::{
almanac::Almanac, constants::orientations::orientation_name_from_id,
almanac::Almanac, constants::orientations::orientation_name_from_id, errors::AlmanacError,
naif::daf::NAIFSummaryRecord,
};
use eframe::egui;
Expand All @@ -8,12 +8,26 @@ use egui_extras::{Column, TableBuilder};
use egui_toast::{Toast, ToastKind, ToastOptions, Toasts};
use hifitime::TimeScale;

#[cfg(target_arch = "wasm32")]
use poll_promise::Promise;

#[cfg(target_arch = "wasm32")]
type AlmanacFile = Option<(String, Vec<u8>)>;

#[derive(Default)]
pub struct UiApp {
selected_time_scale: TimeScale,
show_unix: bool,
almanac: Almanac,
path: Option<String>,
#[cfg(target_arch = "wasm32")]
promise: Option<Promise<AlmanacFile>>,
}

enum FileLoadResult {
NoFileSelectedYet,
Ok((String, Almanac)),
Error(AlmanacError),
}

impl UiApp {
Expand All @@ -24,6 +38,43 @@ impl UiApp {
// for e.g. egui::PaintCallback.
Self::default()
}

#[cfg(target_arch = "wasm32")]
fn load_almanac(&mut self) -> FileLoadResult {
if let Some(promise) = self.promise.as_ref() {
// We are already waiting for a file, so we don't need to show the dialog again
if let Some(result) = promise.ready() {
let (file_name, data) = result.as_ref().map(|x| x.clone()).unwrap();
self.promise = None;
match self.almanac.load_from_bytes(bytes::Bytes::from(data)) {
Ok(almanac) => FileLoadResult::Ok((file_name, almanac)),
Err(e) => FileLoadResult::Error(e),
}
} else {
FileLoadResult::NoFileSelectedYet
}
} else {
// Show the dialog and start loading the file
self.promise = Some(Promise::spawn_local(async move {
let fh = rfd::AsyncFileDialog::new().pick_file().await?;
Some((fh.file_name(), fh.read().await))
}));
FileLoadResult::NoFileSelectedYet
}
}

#[cfg(not(target_arch = "wasm32"))]
fn load_almanac(&mut self) -> FileLoadResult {
if let Some(path_buf) = rfd::FileDialog::new().pick_file() {
let path = path_buf.to_str().unwrap().to_string();
match self.almanac.load(&path) {
Ok(almanac) => FileLoadResult::Ok((path, almanac)),
Err(e) => FileLoadResult::Error(e),
}
} else {
FileLoadResult::NoFileSelectedYet
}
}
}

impl eframe::App for UiApp {
Expand Down Expand Up @@ -53,12 +104,24 @@ impl eframe::App for UiApp {
ui.vertical_centered(|ui| {
match &self.path {
None => {
let mut trigger_file_load = false;
trigger_file_load |= ui.button("Select file to inspect...").clicked();

// If we are in the browser, we need to also check if the file
// is ready to be loaded instead of just checking if the button
// was clicked
#[cfg(target_arch = "wasm32")]
{
trigger_file_load |= self.promise.is_some();
}

// Show the open file dialog
if ui.button("Select file to inspect...").clicked() {
if let Some(path) = rfd::FileDialog::new().pick_file() {
if trigger_file_load {
// Try to load this file
match self.almanac.load(path.to_str().unwrap()) {
Ok(almanac) => {
match self.load_almanac() {
FileLoadResult::NoFileSelectedYet => {
}
FileLoadResult::Ok((path, almanac)) => {
toasts.add(Toast {
text: format!("Loaded {path:?}").into(),
kind: ToastKind::Success,
Expand All @@ -67,10 +130,9 @@ impl eframe::App for UiApp {
.show_progress(true),
});
self.almanac = almanac;
self.path =
Some(path.to_str().unwrap().to_string());
self.path = Some(path);
}
Err(e) => {
FileLoadResult::Error(e) => {
toasts.add(Toast {
text: format!("{e}").into(),
kind: ToastKind::Error,
Expand All @@ -80,7 +142,6 @@ impl eframe::App for UiApp {
});
}
}
}
}
}
Some(path) => {
Expand Down

0 comments on commit 7acdf5c

Please sign in to comment.