From 2819bb1bd4dd3e0375fcff84035a5d08d7dbba4b Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Fri, 20 Oct 2023 18:10:55 +0200 Subject: [PATCH 01/10] Add script to generate RRD vs. screenshots comparison --- .gitignore | 3 + .../assets/static/caret-down.svg | 3 + .../assets/static/caret-right.svg | 3 + .../assets/static/github-mark-white.svg | 1 + .../assets/static/index.css | 329 ++++++++++++++++++ .../screenshot_compare/assets/static/index.js | 246 +++++++++++++ .../assets/static/manifest.json | 16 + .../screenshot_compare/assets/static/sw.js | 27 ++ .../assets/templates/example.html | 83 +++++ .../assets/templates/index.html | 23 ++ .../build_screenshot_compare.py | 294 ++++++++++++++++ 11 files changed, 1028 insertions(+) create mode 100644 scripts/screenshot_compare/assets/static/caret-down.svg create mode 100644 scripts/screenshot_compare/assets/static/caret-right.svg create mode 100644 scripts/screenshot_compare/assets/static/github-mark-white.svg create mode 100644 scripts/screenshot_compare/assets/static/index.css create mode 100644 scripts/screenshot_compare/assets/static/index.js create mode 100644 scripts/screenshot_compare/assets/static/manifest.json create mode 100644 scripts/screenshot_compare/assets/static/sw.js create mode 100644 scripts/screenshot_compare/assets/templates/example.html create mode 100644 scripts/screenshot_compare/assets/templates/index.html create mode 100755 scripts/screenshot_compare/build_screenshot_compare.py diff --git a/.gitignore b/.gitignore index 32151af5c0eb..04cd8a798fb2 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,8 @@ screenshot*.png # Web demo app build web_demo +# Screenshot comparison build +/compare_screenshot + .nox/ *.rrd diff --git a/scripts/screenshot_compare/assets/static/caret-down.svg b/scripts/screenshot_compare/assets/static/caret-down.svg new file mode 100644 index 000000000000..f14b63e5e4e6 --- /dev/null +++ b/scripts/screenshot_compare/assets/static/caret-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/scripts/screenshot_compare/assets/static/caret-right.svg b/scripts/screenshot_compare/assets/static/caret-right.svg new file mode 100644 index 000000000000..2f01ce021bd8 --- /dev/null +++ b/scripts/screenshot_compare/assets/static/caret-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/scripts/screenshot_compare/assets/static/github-mark-white.svg b/scripts/screenshot_compare/assets/static/github-mark-white.svg new file mode 100644 index 000000000000..2cee3ca7accb --- /dev/null +++ b/scripts/screenshot_compare/assets/static/github-mark-white.svg @@ -0,0 +1 @@ + diff --git a/scripts/screenshot_compare/assets/static/index.css b/scripts/screenshot_compare/assets/static/index.css new file mode 100644 index 000000000000..fbb6eb05bdf6 --- /dev/null +++ b/scripts/screenshot_compare/assets/static/index.css @@ -0,0 +1,329 @@ +html { + /* Remove touch delay: */ + touch-action: manipulation; +} + +body { + background: #0d1011; +} + +/* Allow canvas to fill entire web page: */ +html, +body { + overflow: hidden; + margin: 0 !important; + padding: 0 !important; + height: 100%; + width: 100%; +} + +/* When used, the screen splitter is a 50/50 split between the canvas and the image. */ +.screen_splitter { + position: absolute; + height: 100vh; + width: 50vw; +} + +.screen_splitter img { + position: absolute; + top: 0%; + left: 50vw; + width: 50vw; + /*transform: translate(-50%, 0%);*/ +} + +/* Position canvas in center-top: */ +canvas { + margin-right: auto; + margin-left: auto; + display: block; + position: absolute; + box-sizing: border-box; + top: 0; + left: 0; + + /* canvas must be on top when visible */ + z-index: 1000; +} + +* { + font-family: "Inter", sans-serif; + color: #cad8de; +} + +/* Match the Rerun header bar. */ +.header { + width: 100%; + height: 44px; + /* Actual value from Rerun Viewer */ + background: #141414; +} + +/* Create a container filling the remaining space. */ +.container { + position: absolute; + height: calc(100vh - 44px); + width: 100vw; +} + +/* Centered div inside the container. */ +.centered { + margin-right: auto; + margin-left: auto; + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; +} + +/* Rerun styling for normal text. */ +p { + /*Gray-775*/ + color: #cad8de; + letter-spacing: -0.15px; + font-weight: 500; +} + +/* Rerun styling for strong text. Matches `TextStyle::Heading`. */ +.strong { + /*Gray-775*/ + color: #cad8de; + /* Match current rerun header value */ + font-size: 16px; +} + +/* Rerun styling for subdued text.*/ +.subdued { + /*Gray-550*/ + color: #7d8c92; + /* Larger subdued size to match current rerun font */ + font-size: 12px; + letter-spacing: -0.12px; +} + +pre { + /*Gray-550*/ + color: #7d8c92; + font-size: 10px; + letter-spacing: -0.12px; +} + +a .button { + display: inline-block; + background: white; + color: black; + padding: 0.75rem 1rem; + border-radius: 8px; + text-decoration: none; + font-weight: 500; +} + +/* Transition to hidden */ +.hidden { + opacity: 0; + transition: opacity 0.2s ease-out; + visibility: hidden; +} + +/* Transition to visible */ +.visible { + opacity: 100; + transition: opacity 0.2s ease-in; + visibility: visible; +} + +.demo_header { + z-index: 1100; + position: absolute; + transform: translate(-50%, 0); + left: 50%; + height: 44px; + + display: flex; + flex-direction: row; + gap: 4px; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.no-select { + user-select: none; +} + +.examples { + position: relative; + top: 11px; +} + +.example-description { + margin-top: 6px; + height: 40px; + border: none; + border-radius: 33%; +} + +.example-description:hover > .example-description-body { + display: flex; +} + +.example-description:hover > .example-description-icon { + background: #404040; + border: none; + border-radius: 5px; +} + +.example-description-icon { + display: flex; + justify-content: center; + margin: 4px; + padding: 2px; + width: 20px; + height: 20px; + user-select: none; +} + +.example-description-body { + display: none; + + flex-direction: column; + gap: 8px; + + position: absolute; + top: 100%; + + background: #262626; + border: none; + border-radius: 6px; + padding: 12px; + + font-size: 13px; + line-height: 1.33; + letter-spacing: 0; + + width: 280px; +} + +.example-description-body > p { + margin: 0; +} + +a.icon-link { + margin: 5px; + padding: 5px; + width: 24.5px; + height: 24px; +} + +a.icon-link:hover { + background: #404040; + border: none; + border-radius: 50%; +} + +img.icon { + max-height: 100%; +} + +.dropdown { + position: relative; +} + +.dropdown-title { + display: flex; + gap: 2px; + user-select: none; + cursor: pointer; + + background: #141414; + border: none; + border-radius: 6px; + padding: 2px 5px 2px 8px; + + font-style: normal; + font-weight: 435; + font-size: 14px; + line-height: 16px; +} + +.dropdown-title > img { + width: 16px; + height: 16px; +} + +.dropdown-title:hover { + background: #404040; +} + +.dropdown-title:active { + background: #404040; +} + +.dropdown-body { + display: none; + + flex-direction: column; + gap: 3px; + + position: absolute; + top: 100%; + left: 0; + + background: #262626; + border: none; + border-radius: 6px; + padding: 6px; + + user-select: none; + font-size: 12px; +} + +.dropdown-body.visible { + display: flex; +} + +.dropdown-entry { + padding: 2px 5px; + border: none; + border-radius: 6px; + font-size: 13px; + display: flex; + justify-content: space-between; + white-space: nowrap; + overflow: hidden; +} + +.dropdown-entry:hover { + background: #404040; +} + +a.flat-link { + text-decoration: none; +} + +.centered { + margin-right: auto; + margin-left: auto; + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #cad8de; + font-size: 24px; + font-family: Ubuntu-Light, Helvetica, sans-serif; + text-align: center; +} + +a.button { + display: inline-block; + background: white; + color: black; + padding: 0.75rem 1rem; + border-radius: 8px; + text-decoration: none; + font-weight: 500; +} + diff --git a/scripts/screenshot_compare/assets/static/index.js b/scripts/screenshot_compare/assets/static/index.js new file mode 100644 index 000000000000..acd2c36bcecf --- /dev/null +++ b/scripts/screenshot_compare/assets/static/index.js @@ -0,0 +1,246 @@ +function show_center_html(html) { + center_text_elem = document.getElementById("center_text"); + center_text_elem.innerHTML = html; + center_text_elem.classList.remove("hidden"); + center_text_elem.classList.add("visible"); +} +function hide_center_html(html) { + center_text_elem = document.getElementById("center_text"); + center_text_elem.innerHTML = html; + center_text_elem.classList.remove("visible"); + center_text_elem.classList.add("hidden"); +} +function show_canvas(html) { + canvas_elem = document.getElementById("the_canvas_id"); + canvas_elem.classList.remove("hidden"); + canvas_elem.classList.add("visible"); + demo_header_elem = document.getElementById("header_bar"); + demo_header_elem.classList.add("visible"); + demo_header_elem.classList.remove("hidden"); +} +function hide_canvas(html) { + canvas_elem = document.getElementById("the_canvas_id"); + canvas_elem.classList.remove("visible"); + canvas_elem.classList.add("hidden"); + demo_header_elem = document.getElementById("header_bar"); + demo_header_elem.classList.add("hidden"); + demo_header_elem.classList.remove("visible"); +} + +// On mobile platforms show a warning, but provide a link to try anyways +if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { + show_center_html(` +

+ Rerun is not yet supported on mobile browsers. +

+

+ Try anyways +

`); + document.querySelector("#try_anyways").addEventListener("click", function (event) { + event.preventDefault(); + load_wasm(); + }); +} else { + load_wasm(); +} + +function load_wasm() { + // We'll defer our execution until the wasm is ready to go. + // Here we tell bindgen the path to the wasm file so it can start + // initialization and return to us a promise when it's done. + + document.getElementById("center_text").innerHTML = ` +

+ Loading Application Bundle… +

+

+

`; + + const status_element = document.getElementById("status"); + function progress({ loaded, total_bytes }) { + if (total_bytes != null) { + status_element.innerHTML = Math.round(Math.min((loaded / total_bytes) * 100, 100)) + "%"; + } else { + status_element.innerHTML = (loaded / (1024 * 1024)).toFixed(1) + "MiB"; + } + } + + var timeoutId = setTimeout(function () { + document.getElementById("center_text").classList.remove("hidden"); + document.getElementById("center_text").classList.add("visible"); + }, 1500); + + async function wasm_with_progress() { + const response = await fetch("./re_viewer_bg.wasm"); + // Use the uncompressed size + var content_length; + var content_multiplier = 1; + // If the content is gzip encoded, try to get the uncompressed size. + if (response.headers.get("content-encoding") == "gzip") { + content_length = response.headers.get("x-goog-meta-uncompressed-size"); + + // If the uncompressed size wasn't found 3 seems to be a very good approximation + if (content_length == null) { + content_length = response.headers.get("content-length"); + content_multiplier = 3; + } + } else { + content_length = response.headers.get("content-length"); + } + + const total_bytes = parseInt(content_length, 10) * content_multiplier; + let loaded = 0; + + const res = new Response( + new ReadableStream({ + async start(controller) { + const reader = response.body.getReader(); + for (;;) { + const { done, value } = await reader.read(); + if (done) break; + loaded += value.byteLength; + progress({ loaded, total_bytes }); + controller.enqueue(value); + } + controller.close(); + }, + }), + { + status: response.status, + statusText: response.statusText, + } + ); + + for (const [key, value] of response.headers.entries()) { + res.headers.set(key, value); + } + + wasm_bindgen(res) + .then(() => (clearTimeout(timeoutId), on_wasm_loaded())) + .catch(on_wasm_error); + } + + wasm_with_progress(); +} + +function on_wasm_loaded() { + window.set_email = (value) => wasm_bindgen.set_email(value); + + // WebGPU version is currently only supported on browsers with WebGPU support, there is no dynamic fallback to WebGL. + if (wasm_bindgen.is_webgpu_build() && typeof navigator.gpu === "undefined") { + console.debug("`navigator.gpu` is undefined. This indicates lack of WebGPU support."); + show_center_html(` +

+ Missing WebGPU support. +

+

+ This version of Rerun requires WebGPU support which is not available in your browser. + Either try a different browser or use the WebGL version of Rerun. +

`); + return; + } + + console.debug("Wasm loaded. Starting app…"); + + let handle = new wasm_bindgen.WebHandle(); + + function check_for_panic() { + if (handle.has_panicked()) { + console.error("Rerun has crashed"); + + document.getElementById("the_canvas_id").remove(); + + show_center_html(` +

+ Rerun has crashed. +

+
${handle.panic_message()}
+

+ See the console for details. +

+

+ Reload the page to try again. +

`); + } else { + let delay_ms = 1000; + setTimeout(check_for_panic, delay_ms); + } + } + + check_for_panic(); + + let url = determine_url(); + handle + .start("the_canvas_id", url) + .then(on_app_started) + .catch(on_wasm_error); +} + +function on_app_started(handle) { + // Call `handle.destroy()` to stop. Uncomment to quick result: + // setTimeout(() => { handle.destroy(); handle.free()) }, 2000) + + console.debug("App started."); + + hide_center_html(); + show_canvas(); + + if (window.location !== window.parent.location) { + window.parent.postMessage("READY", "*"); + } +} + +function determine_url() { + const base = window.location.pathname.endsWith("/") + ? window.location.pathname.slice(0, -1) + : window.location.pathname; + return base + "/data.rrd"; +} + +function on_wasm_error(error) { + console.error("Failed to start: " + error); + + let render_backend_name = "WebGPU/WebGL"; + try { + render_backend_name = wasm_bindgen.is_webgpu_build() ? "WebGPU" : "WebGL"; + } catch (e) { + // loading the wasm probably failed. + } + + hide_canvas(); + show_center_html(` +

+ An error occurred during loading: +

+

+ ${error} +

+

+ Make sure you use a modern browser with ${render_backend_name} and Wasm enabled. +

`); +} + +// open/close dropdown +document.querySelector("#examples").addEventListener("click", () => { + const body = document.querySelector(".dropdown-body"); + if (!body) return; + if (body.classList.contains("visible")) { + body.classList.remove("visible"); + } else { + body.classList.add("visible"); + } +}); + +// close dropdowns by clicking outside of it +document.body.addEventListener("click", (event) => { + const body = document.querySelector(".dropdown-body"); + if (!body) return; + + const is_dropdown = (element) => + element instanceof HTMLElement && element.classList.contains("dropdown"); + + if (!event.composedPath().find(is_dropdown)) { + body.classList.remove("visible"); + } +}); + diff --git a/scripts/screenshot_compare/assets/static/manifest.json b/scripts/screenshot_compare/assets/static/manifest.json new file mode 100644 index 000000000000..5f31d7b55c51 --- /dev/null +++ b/scripts/screenshot_compare/assets/static/manifest.json @@ -0,0 +1,16 @@ +{ + "name": "Rerun Viewer", + "short_name": "rerun-viewer-pwa", + "icons": [ + { + "src": "./icon-256.png", + "sizes": "256x256", + "type": "image/png" + } + ], + "lang": "en-US", + "start_url": "./index.html", + "display": "standalone", + "background_color": "white", + "theme_color": "white" +} diff --git a/scripts/screenshot_compare/assets/static/sw.js b/scripts/screenshot_compare/assets/static/sw.js new file mode 100644 index 000000000000..afdf8339e24a --- /dev/null +++ b/scripts/screenshot_compare/assets/static/sw.js @@ -0,0 +1,27 @@ +var cacheName = 'rerun-viewer-pwa'; +var filesToCache = [ + './', + './index.html', + './index.js', + './index.css', + './re_viewer.js', + './re_viewer_bg.wasm', +]; + +/* Start the service worker and cache all of the app's content */ +self.addEventListener('install', function (e) { + e.waitUntil( + caches.open(cacheName).then(function (cache) { + return cache.addAll(filesToCache); + }) + ); +}); + +/* Serve cached content when offline */ +self.addEventListener('fetch', function (e) { + e.respondWith( + caches.match(e.request).then(function (response) { + return response || fetch(e.request); + }) + ); +}); diff --git a/scripts/screenshot_compare/assets/templates/example.html b/scripts/screenshot_compare/assets/templates/example.html new file mode 100644 index 000000000000..6f285f598f72 --- /dev/null +++ b/scripts/screenshot_compare/assets/templates/example.html @@ -0,0 +1,83 @@ + + + + + + + + + + Compare Screenshot + + + + + + + +
+ +
+ + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + + diff --git a/scripts/screenshot_compare/assets/templates/index.html b/scripts/screenshot_compare/assets/templates/index.html new file mode 100644 index 000000000000..e42ab977b613 --- /dev/null +++ b/scripts/screenshot_compare/assets/templates/index.html @@ -0,0 +1,23 @@ + + + + + + + + + Screenshot compare + + + + + + + + diff --git a/scripts/screenshot_compare/build_screenshot_compare.py b/scripts/screenshot_compare/build_screenshot_compare.py new file mode 100755 index 000000000000..9d34dcb1b053 --- /dev/null +++ b/scripts/screenshot_compare/build_screenshot_compare.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python3 + +""" +Generate comparison between examples and their related screenshots. + +This script builds/gather RRDs and corresponding screenshots and displays +them side-by-side. It pulls from the following sources: + +- The screenshots listed in .fbs files (crates/re_types/definitions/rerun/**/*.fbs), + and the corresponding code examples in the docs (docs/code-examples/*.rs) +- The `demo.rerun.io` examples, as built by the `build_demo_app.py` script. +""" + +from __future__ import annotations + +import argparse +import http.server +import json +import os +import shutil +import subprocess +import threading +from dataclasses import dataclass +from functools import partial +from io import BytesIO +from pathlib import Path +from typing import Any, Iterable + +import requests +from jinja2 import Template +from PIL import Image + +BASE_PATH = Path("compare_screenshot") + + +SCRIPT_DIR_PATH = Path(__file__).parent +STATIC_ASSETS = SCRIPT_DIR_PATH / "assets" / "static" +TEMPLATE_DIR = SCRIPT_DIR_PATH / "assets" / "templates" +INDEX_TEMPLATE = Template((TEMPLATE_DIR / "index.html").read_text()) +EXAMPLE_TEMPLATE = Template((TEMPLATE_DIR / "example.html").read_text()) +RERUN_DIR = SCRIPT_DIR_PATH.parent.parent +CODE_EXAMPLE_DIR = RERUN_DIR / "docs" / "code-examples" + + +def measure_thumbnail(url: str) -> Any: + """Downloads `url` and returns its width and height.""" + response = requests.get(url) + response.raise_for_status() + image = Image.open(BytesIO(response.content)) + return image.size + + +def run( + args: list[str], *, env: dict[str, str] | None = None, timeout: int | None = None, cwd: str | Path | None = None +) -> None: + print(f"> {subprocess.list2cmdline(args)}") + result = subprocess.run(args, env=env, cwd=cwd, timeout=timeout, check=False, capture_output=True, text=True) + assert ( + result.returncode == 0 + ), f"{subprocess.list2cmdline(args)} failed with exit-code {result.returncode}. Output:\n{result.stdout}\n{result.stderr}" + + +@dataclass +class Example: + name: str + title: str + rrd: Path + screenshot_url: str + description_html: str = "" + source_url: str = "" + + +def copy_static_assets(examples: list[Example]) -> None: + # copy root + dst = BASE_PATH + print(f"\nCopying static assets from {STATIC_ASSETS} to {dst}") + shutil.copytree(STATIC_ASSETS, dst, dirs_exist_ok=True) + + # copy examples + for example in examples: + dst = os.path.join(BASE_PATH, f"examples/{example.name}") + shutil.copytree( + STATIC_ASSETS, + dst, + dirs_exist_ok=True, + ignore=shutil.ignore_patterns("index.html"), + ) + + +def build_python_sdk() -> None: + print("Building Python SDK…") + run( + [ + "maturin", + "develop", + "--manifest-path", + "rerun_py/Cargo.toml", + '--extras="tests"', + "--quiet", + ] + ) + + +def build_wasm() -> None: + print("") + run(["cargo", "r", "-p", "re_build_web_viewer", "--", "--release"]) + + +def copy_wasm(examples: list[Example]) -> None: + files = ["re_viewer_bg.wasm", "re_viewer.js"] + for example in examples: + for file in files: + shutil.copyfile( + os.path.join("web_viewer", file), + os.path.join(BASE_PATH, f"examples/{example.name}", file), + ) + + +# ==================================================================================================== +# CODE EXAMPLES +# +# We scrape FBS for screenshot URL and generate the corresponding code examples RRD with roundtrips.py +# ==================================================================================================== + + +def extract_code_example_urls_from_fbs() -> dict[str, str]: + fbs_path = SCRIPT_DIR_PATH.parent.parent / "crates" / "re_types" / "definitions" / "rerun" + + urls = {} + for fbs in fbs_path.glob("**/*.fbs"): + for line in fbs.read_text().splitlines(): + if line.startswith(r"/// \example"): + if "!api" in line: + continue + + name = line.split()[2] + + idx = line.find('image="') + if idx != -1: + end_idx = line.find('"', idx + 8) + if end_idx == -1: + end_idx = len(line) + urls[name] = line[idx + 7 : end_idx] + + return urls + + +CODE_EXAMPLE_URLS = extract_code_example_urls_from_fbs() + + +def build_code_examples() -> None: + cmd = [ + str(CODE_EXAMPLE_DIR / "roundtrips.py"), + "--no-py", + "--no-cpp", + "--no-py-build", + "--no-cpp-build", + ] + + for name in CODE_EXAMPLE_URLS.keys(): + run(cmd + [name], cwd=RERUN_DIR) + + +def collect_code_examples() -> Iterable[Example]: + for name in sorted(CODE_EXAMPLE_URLS.keys()): + rrd = CODE_EXAMPLE_DIR / f"{name}_rust.rrd" + assert rrd.exists(), f"Missing {rrd} for {name}" + yield Example(name=name, title=name, rrd=rrd, screenshot_url=CODE_EXAMPLE_URLS[name]) + + +# ==================================================================================================== +# DEMO EXAMPLES +# +# We run the `build_demo_app.py` script and scrap the output "web_demo" directory. +# ==================================================================================================== + + +BUILD_DEMO_APP_SCRIPT = RERUN_DIR / "scripts" / "ci" / "build_demo_app.py" + + +def build_demo_examples(skip_example_build: bool = False) -> None: + cmd = [ + str(BUILD_DEMO_APP_SCRIPT), + "--skip-build", # we handle that ourselves + ] + + if skip_example_build: + cmd.append("--skip-example-build") + + run(cmd, cwd=RERUN_DIR) + + +def collect_demo_examples() -> Iterable[Example]: + web_demo_example_dir = SCRIPT_DIR_PATH.parent.parent / "web_demo" / "examples" + assert web_demo_example_dir.exists(), "Web demos have not been built yet." + + manifest = json.loads((web_demo_example_dir / "manifest.json").read_text()) + + for example in manifest: + name = example["name"] + rrd = web_demo_example_dir / f"{name}" / "data.rrd" + assert rrd.exists(), f"Missing {rrd} for {name}" + + yield Example( + name=name, + title=example["title"], + rrd=rrd, + screenshot_url=example["thumbnail"]["url"], + ) + + +def collect_examples() -> Iterable[Example]: + yield from collect_code_examples() + yield from collect_demo_examples() + + +def render_index(examples: list[Example]) -> None: + index_path = BASE_PATH / "index.html" + print(f"Rendering index.html -> {index_path}") + index_path.write_text(INDEX_TEMPLATE.render(examples=examples)) + + +def render_examples(examples: list[Example]) -> None: + print("Rendering examples") + + for example in examples: + target_path = BASE_PATH / "examples" / example.name + target_path.mkdir(parents=True, exist_ok=True) + index_path = target_path / "index.html" + print(f"{example.name} -> {index_path}") + index_path.write_text(EXAMPLE_TEMPLATE.render(example=example, examples=examples)) + + shutil.copy(example.rrd, target_path / "data.rrd") + + +def serve_files() -> None: + def serve() -> None: + print("\nServing examples at http://127.0.0.1:8080/\n") + server = http.server.HTTPServer( + server_address=("127.0.0.1", 8080), + RequestHandlerClass=partial( + http.server.SimpleHTTPRequestHandler, + directory=BASE_PATH, + ), + ) + server.serve_forever() + + threading.Thread(target=serve, daemon=True).start() + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + "--serve", + action="store_true", + help="Serve the app on this port after building [default: 8080]", + ) + parser.add_argument("--skip-build", action="store_true", help="Skip building the Python SDK and web viewer Wasm.") + parser.add_argument("--skip-example-build", action="store_true", help="Skip building the RRDs.") + + args = parser.parse_args() + + if not args.skip_build: + build_python_sdk() + build_wasm() + + if not args.skip_example_build: + build_code_examples() + build_demo_examples() + + examples = list(collect_examples()) + assert len(examples) > 0, "No examples found" + + render_index(examples) + render_examples(examples) + copy_static_assets(examples) + copy_wasm(examples) + + if args.serve: + serve_files() + + while True: + try: + print("Press enter to reload static files") + input() + render_examples(examples) + copy_static_assets(examples) + copy_wasm(examples) + except KeyboardInterrupt: + break + + +if __name__ == "__main__": + main() From a56257bfd68f0d4a9287dcc6a1bda247ffcbc35d Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Fri, 20 Oct 2023 18:19:45 +0200 Subject: [PATCH 02/10] Improved docstring --- scripts/screenshot_compare/build_screenshot_compare.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/screenshot_compare/build_screenshot_compare.py b/scripts/screenshot_compare/build_screenshot_compare.py index 9d34dcb1b053..75aabbab5bcf 100755 --- a/scripts/screenshot_compare/build_screenshot_compare.py +++ b/scripts/screenshot_compare/build_screenshot_compare.py @@ -9,6 +9,9 @@ - The screenshots listed in .fbs files (crates/re_types/definitions/rerun/**/*.fbs), and the corresponding code examples in the docs (docs/code-examples/*.rs) - The `demo.rerun.io` examples, as built by the `build_demo_app.py` script. + +The comparison is generated in the `compare_screenshot` directory. Use the `--serve` +option to show them in a browser. """ from __future__ import annotations From e031f77a9ae192975ddd084d6ab59bae1a8cc902 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Fri, 20 Oct 2023 18:20:00 +0200 Subject: [PATCH 03/10] Improved docstring moar --- scripts/screenshot_compare/build_screenshot_compare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/screenshot_compare/build_screenshot_compare.py b/scripts/screenshot_compare/build_screenshot_compare.py index 75aabbab5bcf..4db8e433395d 100755 --- a/scripts/screenshot_compare/build_screenshot_compare.py +++ b/scripts/screenshot_compare/build_screenshot_compare.py @@ -10,7 +10,7 @@ and the corresponding code examples in the docs (docs/code-examples/*.rs) - The `demo.rerun.io` examples, as built by the `build_demo_app.py` script. -The comparison is generated in the `compare_screenshot` directory. Use the `--serve` +The comparisons are generated in the `compare_screenshot` directory. Use the `--serve` option to show them in a browser. """ From ee466a0b8c1b71fcae7ec62676d954190ac70a95 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 23 Oct 2023 10:45:08 +0200 Subject: [PATCH 04/10] Don't duplicate the wasm binary everywhere (1GiB -> 250MiB) The remaining disk size is primarily explained by the demo examples' RRD (50-70MiB each). --- .../screenshot_compare/assets/static/index.js | 2 +- .../assets/templates/example.html | 2 +- .../build_screenshot_compare.py | 19 +++++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/scripts/screenshot_compare/assets/static/index.js b/scripts/screenshot_compare/assets/static/index.js index acd2c36bcecf..fd2937c5a11b 100644 --- a/scripts/screenshot_compare/assets/static/index.js +++ b/scripts/screenshot_compare/assets/static/index.js @@ -71,7 +71,7 @@ function load_wasm() { }, 1500); async function wasm_with_progress() { - const response = await fetch("./re_viewer_bg.wasm"); + const response = await fetch("../re_viewer_bg.wasm"); // Use the uncompressed size var content_length; var content_multiplier = 1; diff --git a/scripts/screenshot_compare/assets/templates/example.html b/scripts/screenshot_compare/assets/templates/example.html index 6f285f598f72..6a690b47966c 100644 --- a/scripts/screenshot_compare/assets/templates/example.html +++ b/scripts/screenshot_compare/assets/templates/example.html @@ -73,7 +73,7 @@ - + diff --git a/scripts/screenshot_compare/build_screenshot_compare.py b/scripts/screenshot_compare/build_screenshot_compare.py index 4db8e433395d..5103fd3d1a88 100755 --- a/scripts/screenshot_compare/build_screenshot_compare.py +++ b/scripts/screenshot_compare/build_screenshot_compare.py @@ -109,14 +109,15 @@ def build_wasm() -> None: run(["cargo", "r", "-p", "re_build_web_viewer", "--", "--release"]) -def copy_wasm(examples: list[Example]) -> None: +def copy_wasm() -> None: + """Copies the wasm/js files at the root of the `examples` subdirectory.""" files = ["re_viewer_bg.wasm", "re_viewer.js"] - for example in examples: - for file in files: - shutil.copyfile( - os.path.join("web_viewer", file), - os.path.join(BASE_PATH, f"examples/{example.name}", file), - ) + + for file in files: + shutil.copyfile( + os.path.join("web_viewer", file), + os.path.join(BASE_PATH, "examples", file), + ) # ==================================================================================================== @@ -218,6 +219,8 @@ def collect_examples() -> Iterable[Example]: def render_index(examples: list[Example]) -> None: + BASE_PATH.mkdir(exist_ok=True) + index_path = BASE_PATH / "index.html" print(f"Rendering index.html -> {index_path}") index_path.write_text(INDEX_TEMPLATE.render(examples=examples)) @@ -277,7 +280,7 @@ def main() -> None: render_index(examples) render_examples(examples) copy_static_assets(examples) - copy_wasm(examples) + copy_wasm() if args.serve: serve_files() From 7e1c645fb5bf8faa7726152cad40a1fd48e4a2b3 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 23 Oct 2023 11:14:08 +0200 Subject: [PATCH 05/10] Do _not_ skip on `!api`, these are important too. --- scripts/screenshot_compare/build_screenshot_compare.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/screenshot_compare/build_screenshot_compare.py b/scripts/screenshot_compare/build_screenshot_compare.py index 5103fd3d1a88..dbe039db9610 100755 --- a/scripts/screenshot_compare/build_screenshot_compare.py +++ b/scripts/screenshot_compare/build_screenshot_compare.py @@ -134,9 +134,6 @@ def extract_code_example_urls_from_fbs() -> dict[str, str]: for fbs in fbs_path.glob("**/*.fbs"): for line in fbs.read_text().splitlines(): if line.startswith(r"/// \example"): - if "!api" in line: - continue - name = line.split()[2] idx = line.find('image="') From 394ca4bbd883e9eb32dede322eefaec910b59486 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 23 Oct 2023 14:57:01 +0200 Subject: [PATCH 06/10] Fixed bug with reload --- scripts/screenshot_compare/build_screenshot_compare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/screenshot_compare/build_screenshot_compare.py b/scripts/screenshot_compare/build_screenshot_compare.py index dbe039db9610..c4aa08324f4f 100755 --- a/scripts/screenshot_compare/build_screenshot_compare.py +++ b/scripts/screenshot_compare/build_screenshot_compare.py @@ -288,7 +288,7 @@ def main() -> None: input() render_examples(examples) copy_static_assets(examples) - copy_wasm(examples) + copy_wasm() except KeyboardInterrupt: break From 2048a600f84f509f8cfc54c5c780c1cac8aa460a Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 23 Oct 2023 17:27:58 +0200 Subject: [PATCH 07/10] Use `rr.serve()` and iframe instead of a canvas --- .../assets/static/caret-down.svg | 3 - .../assets/static/caret-right.svg | 3 - .../assets/static/github-mark-white.svg | 1 - .../assets/static/index.css | 292 +----------------- .../screenshot_compare/assets/static/index.js | 4 +- .../assets/static/manifest.json | 16 - .../screenshot_compare/assets/static/sw.js | 27 -- .../assets/templates/example.html | 63 +--- .../build_screenshot_compare.py | 49 +-- 9 files changed, 30 insertions(+), 428 deletions(-) delete mode 100644 scripts/screenshot_compare/assets/static/caret-down.svg delete mode 100644 scripts/screenshot_compare/assets/static/caret-right.svg delete mode 100644 scripts/screenshot_compare/assets/static/github-mark-white.svg delete mode 100644 scripts/screenshot_compare/assets/static/manifest.json delete mode 100644 scripts/screenshot_compare/assets/static/sw.js diff --git a/scripts/screenshot_compare/assets/static/caret-down.svg b/scripts/screenshot_compare/assets/static/caret-down.svg deleted file mode 100644 index f14b63e5e4e6..000000000000 --- a/scripts/screenshot_compare/assets/static/caret-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/scripts/screenshot_compare/assets/static/caret-right.svg b/scripts/screenshot_compare/assets/static/caret-right.svg deleted file mode 100644 index 2f01ce021bd8..000000000000 --- a/scripts/screenshot_compare/assets/static/caret-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/scripts/screenshot_compare/assets/static/github-mark-white.svg b/scripts/screenshot_compare/assets/static/github-mark-white.svg deleted file mode 100644 index 2cee3ca7accb..000000000000 --- a/scripts/screenshot_compare/assets/static/github-mark-white.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/scripts/screenshot_compare/assets/static/index.css b/scripts/screenshot_compare/assets/static/index.css index fbb6eb05bdf6..10bdb96c6722 100644 --- a/scripts/screenshot_compare/assets/static/index.css +++ b/scripts/screenshot_compare/assets/static/index.css @@ -7,7 +7,6 @@ body { background: #0d1011; } -/* Allow canvas to fill entire web page: */ html, body { overflow: hidden; @@ -17,7 +16,6 @@ body { width: 100%; } -/* When used, the screen splitter is a 50/50 split between the canvas and the image. */ .screen_splitter { position: absolute; height: 100vh; @@ -26,14 +24,11 @@ body { .screen_splitter img { position: absolute; - top: 0%; left: 50vw; width: 50vw; - /*transform: translate(-50%, 0%);*/ } -/* Position canvas in center-top: */ -canvas { +iframe { margin-right: auto; margin-left: auto; display: block; @@ -41,289 +36,16 @@ canvas { box-sizing: border-box; top: 0; left: 0; + width: 50vw; + height: 100vh; + + border: none; /* canvas must be on top when visible */ z-index: 1000; } * { - font-family: "Inter", sans-serif; - color: #cad8de; -} - -/* Match the Rerun header bar. */ -.header { - width: 100%; - height: 44px; - /* Actual value from Rerun Viewer */ - background: #141414; -} - -/* Create a container filling the remaining space. */ -.container { - position: absolute; - height: calc(100vh - 44px); - width: 100vw; -} - -/* Centered div inside the container. */ -.centered { - margin-right: auto; - margin-left: auto; - display: block; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - text-align: center; -} - -/* Rerun styling for normal text. */ -p { - /*Gray-775*/ - color: #cad8de; - letter-spacing: -0.15px; - font-weight: 500; -} - -/* Rerun styling for strong text. Matches `TextStyle::Heading`. */ -.strong { - /*Gray-775*/ - color: #cad8de; - /* Match current rerun header value */ - font-size: 16px; -} - -/* Rerun styling for subdued text.*/ -.subdued { - /*Gray-550*/ - color: #7d8c92; - /* Larger subdued size to match current rerun font */ - font-size: 12px; - letter-spacing: -0.12px; -} - -pre { - /*Gray-550*/ - color: #7d8c92; - font-size: 10px; - letter-spacing: -0.12px; -} - -a .button { - display: inline-block; - background: white; - color: black; - padding: 0.75rem 1rem; - border-radius: 8px; - text-decoration: none; - font-weight: 500; -} - -/* Transition to hidden */ -.hidden { - opacity: 0; - transition: opacity 0.2s ease-out; - visibility: hidden; -} - -/* Transition to visible */ -.visible { - opacity: 100; - transition: opacity 0.2s ease-in; - visibility: visible; -} - -.demo_header { - z-index: 1100; - position: absolute; - transform: translate(-50%, 0); - left: 50%; - height: 44px; - - display: flex; - flex-direction: row; - gap: 4px; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.no-select { - user-select: none; -} - -.examples { - position: relative; - top: 11px; -} - -.example-description { - margin-top: 6px; - height: 40px; - border: none; - border-radius: 33%; -} - -.example-description:hover > .example-description-body { - display: flex; -} - -.example-description:hover > .example-description-icon { - background: #404040; - border: none; - border-radius: 5px; -} - -.example-description-icon { - display: flex; - justify-content: center; - margin: 4px; - padding: 2px; - width: 20px; - height: 20px; - user-select: none; -} - -.example-description-body { - display: none; - - flex-direction: column; - gap: 8px; - - position: absolute; - top: 100%; - - background: #262626; - border: none; - border-radius: 6px; - padding: 12px; - - font-size: 13px; - line-height: 1.33; - letter-spacing: 0; - - width: 280px; -} - -.example-description-body > p { - margin: 0; -} - -a.icon-link { - margin: 5px; - padding: 5px; - width: 24.5px; - height: 24px; -} - -a.icon-link:hover { - background: #404040; - border: none; - border-radius: 50%; -} - -img.icon { - max-height: 100%; -} - -.dropdown { - position: relative; -} - -.dropdown-title { - display: flex; - gap: 2px; - user-select: none; - cursor: pointer; - - background: #141414; - border: none; - border-radius: 6px; - padding: 2px 5px 2px 8px; - - font-style: normal; - font-weight: 435; - font-size: 14px; - line-height: 16px; -} - -.dropdown-title > img { - width: 16px; - height: 16px; -} - -.dropdown-title:hover { - background: #404040; -} - -.dropdown-title:active { - background: #404040; -} - -.dropdown-body { - display: none; - - flex-direction: column; - gap: 3px; - - position: absolute; - top: 100%; - left: 0; - - background: #262626; - border: none; - border-radius: 6px; - padding: 6px; - - user-select: none; - font-size: 12px; -} - -.dropdown-body.visible { - display: flex; -} - -.dropdown-entry { - padding: 2px 5px; - border: none; - border-radius: 6px; - font-size: 13px; - display: flex; - justify-content: space-between; - white-space: nowrap; - overflow: hidden; -} - -.dropdown-entry:hover { - background: #404040; -} - -a.flat-link { - text-decoration: none; -} - -.centered { - margin-right: auto; - margin-left: auto; - display: block; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + font-family: sans-serif; color: #cad8de; - font-size: 24px; - font-family: Ubuntu-Light, Helvetica, sans-serif; - text-align: center; -} - -a.button { - display: inline-block; - background: white; - color: black; - padding: 0.75rem 1rem; - border-radius: 8px; - text-decoration: none; - font-weight: 500; -} - +} \ No newline at end of file diff --git a/scripts/screenshot_compare/assets/static/index.js b/scripts/screenshot_compare/assets/static/index.js index fd2937c5a11b..71b494026ba3 100644 --- a/scripts/screenshot_compare/assets/static/index.js +++ b/scripts/screenshot_compare/assets/static/index.js @@ -38,10 +38,10 @@ if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(naviga

`); document.querySelector("#try_anyways").addEventListener("click", function (event) { event.preventDefault(); - load_wasm(); + //load_wasm(); }); } else { - load_wasm(); + //load_wasm(); } function load_wasm() { diff --git a/scripts/screenshot_compare/assets/static/manifest.json b/scripts/screenshot_compare/assets/static/manifest.json deleted file mode 100644 index 5f31d7b55c51..000000000000 --- a/scripts/screenshot_compare/assets/static/manifest.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "Rerun Viewer", - "short_name": "rerun-viewer-pwa", - "icons": [ - { - "src": "./icon-256.png", - "sizes": "256x256", - "type": "image/png" - } - ], - "lang": "en-US", - "start_url": "./index.html", - "display": "standalone", - "background_color": "white", - "theme_color": "white" -} diff --git a/scripts/screenshot_compare/assets/static/sw.js b/scripts/screenshot_compare/assets/static/sw.js deleted file mode 100644 index afdf8339e24a..000000000000 --- a/scripts/screenshot_compare/assets/static/sw.js +++ /dev/null @@ -1,27 +0,0 @@ -var cacheName = 'rerun-viewer-pwa'; -var filesToCache = [ - './', - './index.html', - './index.js', - './index.css', - './re_viewer.js', - './re_viewer_bg.wasm', -]; - -/* Start the service worker and cache all of the app's content */ -self.addEventListener('install', function (e) { - e.waitUntil( - caches.open(cacheName).then(function (cache) { - return cache.addAll(filesToCache); - }) - ); -}); - -/* Serve cached content when offline */ -self.addEventListener('fetch', function (e) { - e.respondWith( - caches.match(e.request).then(function (response) { - return response || fetch(e.request); - }) - ); -}); diff --git a/scripts/screenshot_compare/assets/templates/example.html b/scripts/screenshot_compare/assets/templates/example.html index 6a690b47966c..ac434c419f0f 100644 --- a/scripts/screenshot_compare/assets/templates/example.html +++ b/scripts/screenshot_compare/assets/templates/example.html @@ -1,5 +1,4 @@ - @@ -8,76 +7,24 @@ Compare Screenshot - - - -
- -
- - -
- -
-
- - +
- - - - - - diff --git a/scripts/screenshot_compare/build_screenshot_compare.py b/scripts/screenshot_compare/build_screenshot_compare.py index c4aa08324f4f..9c2eb0256947 100755 --- a/scripts/screenshot_compare/build_screenshot_compare.py +++ b/scripts/screenshot_compare/build_screenshot_compare.py @@ -69,8 +69,6 @@ class Example: title: str rrd: Path screenshot_url: str - description_html: str = "" - source_url: str = "" def copy_static_assets(examples: list[Example]) -> None: @@ -92,32 +90,7 @@ def copy_static_assets(examples: list[Example]) -> None: def build_python_sdk() -> None: print("Building Python SDK…") - run( - [ - "maturin", - "develop", - "--manifest-path", - "rerun_py/Cargo.toml", - '--extras="tests"', - "--quiet", - ] - ) - - -def build_wasm() -> None: - print("") - run(["cargo", "r", "-p", "re_build_web_viewer", "--", "--release"]) - - -def copy_wasm() -> None: - """Copies the wasm/js files at the root of the `examples` subdirectory.""" - files = ["re_viewer_bg.wasm", "re_viewer.js"] - - for file in files: - shutil.copyfile( - os.path.join("web_viewer", file), - os.path.join(BASE_PATH, "examples", file), - ) + run(["just", "py-build", "--features", "web_viewer"]) # ==================================================================================================== @@ -236,19 +209,32 @@ def render_examples(examples: list[Example]) -> None: shutil.copy(example.rrd, target_path / "data.rrd") +class CORSRequestHandler(http.server.SimpleHTTPRequestHandler): + def end_headers(self): + self.send_header("Access-Control-Allow-Origin", "*") + super().end_headers() + + def serve_files() -> None: def serve() -> None: print("\nServing examples at http://127.0.0.1:8080/\n") server = http.server.HTTPServer( server_address=("127.0.0.1", 8080), RequestHandlerClass=partial( - http.server.SimpleHTTPRequestHandler, + CORSRequestHandler, directory=BASE_PATH, ), ) server.serve_forever() + def serve_wasm() -> None: + import rerun as rr + + rr.init("Screenshot compare") + rr.serve(open_browser=False) + threading.Thread(target=serve, daemon=True).start() + threading.Thread(target=serve_wasm, daemon=True).start() def main() -> None: @@ -258,14 +244,13 @@ def main() -> None: action="store_true", help="Serve the app on this port after building [default: 8080]", ) - parser.add_argument("--skip-build", action="store_true", help="Skip building the Python SDK and web viewer Wasm.") + parser.add_argument("--skip-build", action="store_true", help="Skip building the Python SDK.") parser.add_argument("--skip-example-build", action="store_true", help="Skip building the RRDs.") args = parser.parse_args() if not args.skip_build: build_python_sdk() - build_wasm() if not args.skip_example_build: build_code_examples() @@ -277,7 +262,6 @@ def main() -> None: render_index(examples) render_examples(examples) copy_static_assets(examples) - copy_wasm() if args.serve: serve_files() @@ -288,7 +272,6 @@ def main() -> None: input() render_examples(examples) copy_static_assets(examples) - copy_wasm() except KeyboardInterrupt: break From 2a274d072ff1a94ba717c8ad4537dfdeffd7cb0f Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 23 Oct 2023 17:31:53 +0200 Subject: [PATCH 08/10] Suppressed useless file. --- .../assets/static/index.css | 2 +- .../screenshot_compare/assets/static/index.js | 246 ------------------ 2 files changed, 1 insertion(+), 247 deletions(-) delete mode 100644 scripts/screenshot_compare/assets/static/index.js diff --git a/scripts/screenshot_compare/assets/static/index.css b/scripts/screenshot_compare/assets/static/index.css index 10bdb96c6722..0e6f7ff389ba 100644 --- a/scripts/screenshot_compare/assets/static/index.css +++ b/scripts/screenshot_compare/assets/static/index.css @@ -48,4 +48,4 @@ iframe { * { font-family: sans-serif; color: #cad8de; -} \ No newline at end of file +} diff --git a/scripts/screenshot_compare/assets/static/index.js b/scripts/screenshot_compare/assets/static/index.js deleted file mode 100644 index 71b494026ba3..000000000000 --- a/scripts/screenshot_compare/assets/static/index.js +++ /dev/null @@ -1,246 +0,0 @@ -function show_center_html(html) { - center_text_elem = document.getElementById("center_text"); - center_text_elem.innerHTML = html; - center_text_elem.classList.remove("hidden"); - center_text_elem.classList.add("visible"); -} -function hide_center_html(html) { - center_text_elem = document.getElementById("center_text"); - center_text_elem.innerHTML = html; - center_text_elem.classList.remove("visible"); - center_text_elem.classList.add("hidden"); -} -function show_canvas(html) { - canvas_elem = document.getElementById("the_canvas_id"); - canvas_elem.classList.remove("hidden"); - canvas_elem.classList.add("visible"); - demo_header_elem = document.getElementById("header_bar"); - demo_header_elem.classList.add("visible"); - demo_header_elem.classList.remove("hidden"); -} -function hide_canvas(html) { - canvas_elem = document.getElementById("the_canvas_id"); - canvas_elem.classList.remove("visible"); - canvas_elem.classList.add("hidden"); - demo_header_elem = document.getElementById("header_bar"); - demo_header_elem.classList.add("hidden"); - demo_header_elem.classList.remove("visible"); -} - -// On mobile platforms show a warning, but provide a link to try anyways -if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { - show_center_html(` -

- Rerun is not yet supported on mobile browsers. -

-

- Try anyways -

`); - document.querySelector("#try_anyways").addEventListener("click", function (event) { - event.preventDefault(); - //load_wasm(); - }); -} else { - //load_wasm(); -} - -function load_wasm() { - // We'll defer our execution until the wasm is ready to go. - // Here we tell bindgen the path to the wasm file so it can start - // initialization and return to us a promise when it's done. - - document.getElementById("center_text").innerHTML = ` -

- Loading Application Bundle… -

-

-

`; - - const status_element = document.getElementById("status"); - function progress({ loaded, total_bytes }) { - if (total_bytes != null) { - status_element.innerHTML = Math.round(Math.min((loaded / total_bytes) * 100, 100)) + "%"; - } else { - status_element.innerHTML = (loaded / (1024 * 1024)).toFixed(1) + "MiB"; - } - } - - var timeoutId = setTimeout(function () { - document.getElementById("center_text").classList.remove("hidden"); - document.getElementById("center_text").classList.add("visible"); - }, 1500); - - async function wasm_with_progress() { - const response = await fetch("../re_viewer_bg.wasm"); - // Use the uncompressed size - var content_length; - var content_multiplier = 1; - // If the content is gzip encoded, try to get the uncompressed size. - if (response.headers.get("content-encoding") == "gzip") { - content_length = response.headers.get("x-goog-meta-uncompressed-size"); - - // If the uncompressed size wasn't found 3 seems to be a very good approximation - if (content_length == null) { - content_length = response.headers.get("content-length"); - content_multiplier = 3; - } - } else { - content_length = response.headers.get("content-length"); - } - - const total_bytes = parseInt(content_length, 10) * content_multiplier; - let loaded = 0; - - const res = new Response( - new ReadableStream({ - async start(controller) { - const reader = response.body.getReader(); - for (;;) { - const { done, value } = await reader.read(); - if (done) break; - loaded += value.byteLength; - progress({ loaded, total_bytes }); - controller.enqueue(value); - } - controller.close(); - }, - }), - { - status: response.status, - statusText: response.statusText, - } - ); - - for (const [key, value] of response.headers.entries()) { - res.headers.set(key, value); - } - - wasm_bindgen(res) - .then(() => (clearTimeout(timeoutId), on_wasm_loaded())) - .catch(on_wasm_error); - } - - wasm_with_progress(); -} - -function on_wasm_loaded() { - window.set_email = (value) => wasm_bindgen.set_email(value); - - // WebGPU version is currently only supported on browsers with WebGPU support, there is no dynamic fallback to WebGL. - if (wasm_bindgen.is_webgpu_build() && typeof navigator.gpu === "undefined") { - console.debug("`navigator.gpu` is undefined. This indicates lack of WebGPU support."); - show_center_html(` -

- Missing WebGPU support. -

-

- This version of Rerun requires WebGPU support which is not available in your browser. - Either try a different browser or use the WebGL version of Rerun. -

`); - return; - } - - console.debug("Wasm loaded. Starting app…"); - - let handle = new wasm_bindgen.WebHandle(); - - function check_for_panic() { - if (handle.has_panicked()) { - console.error("Rerun has crashed"); - - document.getElementById("the_canvas_id").remove(); - - show_center_html(` -

- Rerun has crashed. -

-
${handle.panic_message()}
-

- See the console for details. -

-

- Reload the page to try again. -

`); - } else { - let delay_ms = 1000; - setTimeout(check_for_panic, delay_ms); - } - } - - check_for_panic(); - - let url = determine_url(); - handle - .start("the_canvas_id", url) - .then(on_app_started) - .catch(on_wasm_error); -} - -function on_app_started(handle) { - // Call `handle.destroy()` to stop. Uncomment to quick result: - // setTimeout(() => { handle.destroy(); handle.free()) }, 2000) - - console.debug("App started."); - - hide_center_html(); - show_canvas(); - - if (window.location !== window.parent.location) { - window.parent.postMessage("READY", "*"); - } -} - -function determine_url() { - const base = window.location.pathname.endsWith("/") - ? window.location.pathname.slice(0, -1) - : window.location.pathname; - return base + "/data.rrd"; -} - -function on_wasm_error(error) { - console.error("Failed to start: " + error); - - let render_backend_name = "WebGPU/WebGL"; - try { - render_backend_name = wasm_bindgen.is_webgpu_build() ? "WebGPU" : "WebGL"; - } catch (e) { - // loading the wasm probably failed. - } - - hide_canvas(); - show_center_html(` -

- An error occurred during loading: -

-

- ${error} -

-

- Make sure you use a modern browser with ${render_backend_name} and Wasm enabled. -

`); -} - -// open/close dropdown -document.querySelector("#examples").addEventListener("click", () => { - const body = document.querySelector(".dropdown-body"); - if (!body) return; - if (body.classList.contains("visible")) { - body.classList.remove("visible"); - } else { - body.classList.add("visible"); - } -}); - -// close dropdowns by clicking outside of it -document.body.addEventListener("click", (event) => { - const body = document.querySelector(".dropdown-body"); - if (!body) return; - - const is_dropdown = (element) => - element instanceof HTMLElement && element.classList.contains("dropdown"); - - if (!event.composedPath().find(is_dropdown)) { - body.classList.remove("visible"); - } -}); - From e37cab4ab9f363ffcbdcd15dfd7c40de83ae43e8 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Mon, 23 Oct 2023 18:36:26 +0200 Subject: [PATCH 09/10] Update scripts/screenshot_compare/build_screenshot_compare.py Co-authored-by: Jeremy Leibs --- scripts/screenshot_compare/build_screenshot_compare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/screenshot_compare/build_screenshot_compare.py b/scripts/screenshot_compare/build_screenshot_compare.py index 9c2eb0256947..ff2037c0ad9c 100755 --- a/scripts/screenshot_compare/build_screenshot_compare.py +++ b/scripts/screenshot_compare/build_screenshot_compare.py @@ -227,7 +227,7 @@ def serve() -> None: ) server.serve_forever() - def serve_wasm() -> None: + def serve_rerun() -> None: import rerun as rr rr.init("Screenshot compare") From 69d73db71693a32d77b3c89a5dfc3b432484f179 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 23 Oct 2023 18:47:39 +0200 Subject: [PATCH 10/10] Silence logging from `rr.serve()` --- scripts/screenshot_compare/build_screenshot_compare.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/screenshot_compare/build_screenshot_compare.py b/scripts/screenshot_compare/build_screenshot_compare.py index ff2037c0ad9c..d6514b5e898f 100755 --- a/scripts/screenshot_compare/build_screenshot_compare.py +++ b/scripts/screenshot_compare/build_screenshot_compare.py @@ -19,6 +19,7 @@ import argparse import http.server import json +import multiprocessing import os import shutil import subprocess @@ -230,11 +231,16 @@ def serve() -> None: def serve_rerun() -> None: import rerun as rr + os.environ["RUST_LOG"] = "rerun=warn" + rr.init("Screenshot compare") rr.serve(open_browser=False) threading.Thread(target=serve, daemon=True).start() - threading.Thread(target=serve_wasm, daemon=True).start() + + # use a sub-process so the target can change env variables without affecting the parent + process = multiprocessing.Process(target=serve_rerun, daemon=True) + process.run() def main() -> None: