Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-demo/src/main.rs

//! **facett-demo** binary — wrap the headless [`DemoState`](facett_demo::DemoState)
//! in an eframe (glow) window with a live toolbar:
//!
//! - a **palette switcher** over every [`Theme::ALL`](facett::Theme) palette (the
//!   whole deck + every facet re-themes live),
//! - the deck **fx**: a **glow** toggle (blooms the active facet) and a
//!   **send-raven** button (a `RavenSprite` swoops onto a row),
//! - a **command palette** overlay (facett-menu, `Ctrl+P` / the toolbar button),
//! - an **about / legend** panel listing every component this app showcases.
//!
//! This is the living gallery for the D4 compliance proof: one window, every
//! facett component, fake-but-realistic data, switchable across all palettes.

use eframe::egui;
use facett::LookTheme;
use facett_demo::DemoState;

#[cfg(not(target_arch = "wasm32"))]
fn main() -> eframe::Result<()> {
    // ── `--version` / `-V`: print the build stamp and EXIT before the window opens. ──
    // The nordisk LAW: an operator must always be able to tell WHICH build is running.
    // This runs before eframe brings up any viewport so `--version` never boots the GUI.
    if std::env::args().skip(1).any(|a| a == "--version" || a == "-V") {
        println!("{}", facett_demo::version_line());
        return Ok(());
    }

    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_inner_size([1280.0, 800.0])
            .with_min_inner_size([900.0, 560.0])
            .with_title("facett-demo — every component, every palette"),
        // Force the eframe wgpu backend onto the DISCRETE GPU: Vulkan-only
        // backends (never a GL/llvmpipe fallback), HighPerformance power
        // preference, and a custom adapter selector that rejects the `Cpu`
        // software rasteriser (llvmpipe/lavapipe) and prefers the NVIDIA/RTX
        // `DiscreteGpu`. The chosen adapter is logged at startup. Without this
        // the demo has been landing on software/iGPU (~0.1 fps) on Loki.
        wgpu_options: facett_demo::gpu_select::discrete_gpu_wgpu_options(),
        ..Default::default()
    };
    eframe::run_native(
        "facett-demo",
        native_options,
        Box::new(|cc| {
            // Start on the look preset matching the running OS (Linux → Windows-dark,
            // the documented default); the switcher re-themes live. `apply` installs
            // the full Style AND publishes the legacy palette bridge.
            LookTheme::from_os(cc.egui_ctx.os()).apply(&cc.egui_ctx);
            // Install the ported wgpu map renderer into the eframe RenderState so
            // the map tab's GPU vector-basemap callback has a GpuMapRenderer to
            // draw with (it gracefully falls back to the CPU painter without one).
            if let Some(rs) = cc.wgpu_render_state.as_ref() {
                facett::map::gpu::install_renderer(rs);
                #[cfg(feature = "flow-wgpu")]
                facett::flow::gpu::install_renderer(rs);
                // Install the depth-tested 3D-map renderer too, so the OSM 3D tab
                // draws the extruded city through a real GPU Z-buffer (not the CPU
                // painter's depth-sort). Graceful fallback without a RenderState.
                #[cfg(feature = "map3d-wgpu")]
                facett::map3d::gpu::install_renderer(rs);
                // Install the INSTANCED 3D-GRAPH CLOUD renderer. Without it the
                // graph3d / warehouse-deck / codevault panes tessellate every node
                // disc and edge line on ONE CPU core each frame — the measured wall
                // in `.nornir/graph3d-rotate-perf-design.md` (projection 0.02 ms/frame
                // at 30k nodes, but 2.47M triangles of egui tessellation = 33.75
                // ms/frame single-threaded). With it installed, `with_gpu(true)`
                // draws the cloud as GPU instances. The lane is fail-safe: a pane that
                // opted in without this call falls back to the CPU painter rather than
                // rendering blank (facett-graph3d::graph_gpu::cloud).
                #[cfg(any(feature = "gpu", feature = "graph-wgpu"))]
                facett::core::render::gpu::graphcloud::cloud::install_cloud_renderer(rs);
                // ARM THE GFX v2 LANES. This one call is the difference between the
                // `GFX v2` tab showing the renderer track and showing a CPU
                // imitation of it: it hands the device over and installs the item-8
                // pick host, and the item-4 label collider installs itself on first
                // draw. Without it `pick_at` returns `None` (the CPU lane answers),
                // `LabelLane::draw` returns false (the CPU painter draws the names),
                // and the tab says "device: NOT ARMED" on screen.
                //
                // It is here, beside the other three installs, for the reason the
                // `gpu` feature's own comment records: the instanced GPU cloud
                // existed and was proven by tests, but no host armed it, so the live
                // GPU path was theoretical and the shipped demo ran the CPU painter
                // on an RTX 4090. An un-armed showcase of the renderer track would
                // be that same bug wearing this tab's name.
                #[cfg(feature = "gfx-v2-gpu")]
                facett_demo::gfx_v2::arm(rs);
                // LEND THE LAYOUT LANE THE DEVICE. Same defect, fifth instance: the
                // compute-shader force layout existed, was measured (100k nodes: 23.7 s
                // per CPU step vs 983 ms for fifty GPU steps) and no host lent it a
                // device — so the shipped demo laid graphs out on the CPU. With the
                // loan, a graph at scale (>= BH_THRESHOLD) runs as a job across a few
                // frames instead of converging inside `positions()` on the paint path.
                // Below that size, and if this returns false, the CPU lane is unchanged.
                #[cfg(feature = "graph-layout-gpu")]
                facett_graphnav::install_layout_device(&rs.device, &rs.queue);
            }
            // NATIVE DEEP-LINK: `$FACETT_DEMO_TAB="GFX v2"` opens that tab on a cold
            // start — the desktop twin of the wasm `#<tab>` URL fragment `wasm_start`
            // below already honours, and the half that was missing. Without it the
            // only ways onto a given tab are a human click or the auto-demo tour, and
            // the tour rotates a fixed showcase subset that never reaches `GFX v2`.
            let mut app = DemoApp::new();
            app.demo.apply_initial_tab(std::env::var("FACETT_DEMO_TAB").ok().as_deref());
            // `$FACETT_DEMO_GEN="sbm:100000:1"`: the size + generator for that tab (the twin
            // of the web page's `?gen=`).
            app.demo.apply_gen_link(std::env::var("FACETT_DEMO_GEN").ok().as_deref());
            Ok(Box::new(app))
        }),
    )
}

// On wasm the real entry point is `wasm_start` below (run by the wasm-bindgen
// `start` shim once the module loads). `main` still has to exist for the bin to
// link, but it does nothing — eframe drives the canvas from `wasm_start`.
#[cfg(target_arch = "wasm32")]
fn main() {}

// ── wasm: the LIVE WEB DEMO entry point ─────────────────────────────────────
// The browser counterpart of `main`: eframe's `WebRunner` mounts the same
// `DemoApp` (the full 27-component deck, incl. the Sverige showcase) onto the
// `<canvas id="facett_demo">` in
// `index.html`, on eframe's **wgpu / WebGPU** backend — the GPU map, 3D-map, graph
// cloud and layout lanes are installed below. The demo is built
// `--no-default-features --features <DEMO_WASM_FEATURES>` for wasm, so
// `region-clip`/osm-katana is dropped and `osm_tabs::load()` uses its synthetic
// in-memory sample (there is no filesystem in the browser).
//
// `#[wasm_bindgen(start)]` runs this automatically when the module is imported by
// the loader snippet in `index.html`.
/// The wasm twin of the install block in `main`: one call per GPU feature the
/// `facett-demo-wasm` row turns on (`facett_build_thing::DEMO_WASM_FEATURES`), and
/// nothing for a feature it does not — installing a lane the bundle cannot dispatch to
/// is the reverse lie. The guard reads this function's body against that axis.
#[cfg(target_arch = "wasm32")]
fn wasm_install_gpu_lanes(rs: &eframe::egui_wgpu::RenderState) {
    #[cfg(feature = "flow-wgpu")]
    facett::flow::gpu::install_renderer(rs);
    #[cfg(feature = "map-wgpu")]
    facett::map::gpu::install_renderer(rs);
    #[cfg(feature = "map3d-wgpu")]
    facett::map3d::gpu::install_renderer(rs);
    // ONE renderer, two lanes: `gpu` (graph3d) and `graph-wgpu` (the 2-D GraphView)
    // both draw through facett-core `graphcloud`.
    #[cfg(any(feature = "gpu", feature = "graph-wgpu"))]
    facett::core::render::gpu::graphcloud::cloud::install_cloud_renderer(rs);
    // The LOAN, not a renderer: without it every graph >= BH_THRESHOLD converges on
    // wasm32's single thread (row 13, `facet-wrapped-wheel.md`).
    #[cfg(feature = "graph-layout-gpu")]
    facett_graphnav::install_layout_device(&rs.device, &rs.queue);
}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen::prelude::wasm_bindgen(start)]
pub fn wasm_start() {
    // Pretty panics in the browser console (otherwise wasm panics are opaque).
    console_error_panic_hook::set_once();

    // Deep-link: `…/facett/#OSM 3D` opens that tab on load — so the headless
    // screenshotter (and a shared link) can land on any tab without clicking.
    // `?gen=<source>:<size>:<seed>` sets the deep-linked tab's size + generator
    // (`DemoState::apply_gen_link`), e.g. `?gen=community:100000#Graphs`.
    let initial_gen: Option<String> = web_sys::window()
        .and_then(|w| w.location().search().ok())
        .and_then(|s| {
            s.trim_start_matches('?')
                .split('&')
                .find_map(|kv| kv.strip_prefix("gen=").map(|v| v.replace("%3A", ":").replace("%3a", ":")))
        });
    let initial_tab: Option<String> = web_sys::window()
        .and_then(|w| w.location().hash().ok())
        .map(|h| {
            h.trim_start_matches('#').replace("%20", " ").replace('+', " ").trim().to_string()
        })
        .filter(|s| !s.is_empty());
    wasm_bindgen_futures::spawn_local(async move {
        use facett::core::adapter_retry::{self, Next};
        // NO GPU IS SAID PLAINLY, never a black canvas (owner, 2026-10-01: "loading,
        // then black" — the console said only "No available adapters."). Before eframe
        // touches the canvas: `navigator.gpu` + `isSecureContext` (never retried), then
        // `requestAdapter()` with facett-core's bounded retry. Same policy and the same
        // words as every facet-wrapped mount (`facett_core::adapter_retry`).
        adapter_retry::record_reason("");
        adapter_retry::record_attempts(0);
        if let Some(p) = browser_preflight() {
            show_no_gpu(adapter_retry::preflight_message(p));
            return;
        }
        let mut attempt = 0;
        let adapter = loop {
            attempt += 1;
            adapter_retry::record_attempts(attempt);
            if let Some(a) = request_adapter().await {
                break a;
            }
            match adapter_retry::after_failure(attempt, "requestAdapter() returned null (NotFound)") {
                Next::RetryAfter(ms) => {
                    log::warn!("facett-demo: no WebGPU adapter on attempt {attempt}; retrying in {ms} ms");
                    sleep_ms(ms).await;
                }
                Next::Fail(msg) => {
                    show_no_gpu(&msg);
                    return;
                }
            }
        };
        // A SOFTWARE ADAPTER IS NOT A GPU (owner, 2026-10-02). SwiftShader answers
        // requestAdapter() — on loki under VK_LOADER_DRIVERS_SELECT='*nvidia*', on any
        // headless Chrome without --enable-gpu — and the lanes installed on it panicked
        // in the first resident upload. Refused here, before eframe or any lane exists,
        // with facett-core's predicate (the probes refuse runs with the same one).
        if let Some(what) = software_adapter_of(&adapter) {
            show_no_gpu(&adapter_retry::software_adapter_message(&what));
            return;
        }

        // Grab the canvas the loader created (id="facett_demo").
        let Some(canvas) = web_sys::window()
            .and_then(|w| w.document())
            .and_then(|d| d.get_element_by_id("facett_demo"))
            .and_then(|e| e.dyn_into::<web_sys::HtmlCanvasElement>().ok())
        else {
            show_no_gpu("facett-demo: the page has no <canvas id=\"facett_demo\">");
            return;
        };

        let start_result = eframe::WebRunner::new()
            .start(
                canvas,
                eframe::WebOptions::default(),
                Box::new(move |cc| {
                    // Same look bridge as native: install the OS-matched preset; the
                    // in-app switcher re-themes live.
                    LookTheme::from_os(cc.egui_ctx.os()).apply(&cc.egui_ctx);
                    // PUT SOMETHING ON THE LANES THIS BUNDLE SELECTS. The pages build has
                    // been `--features map-wgpu,map3d-wgpu,…` (`DEMO_WASM_FEATURES`) since
                    // the WebGPU backend landed, and until 2026-09-19 this closure still
                    // carried the glow-era comment "no wgpu RenderState on the web path"
                    // and made NO install call — the select-without-install defect
                    // `facet-wrapped::install_gpu_lanes` exists to end, alive in the
                    // sibling entry point. Same block as `main` above, same guard
                    // (`xtask/tests/wasm_bundle_installs_the_lanes_it_selects.rs`).
                    if let Some(rs) = cc.wgpu_render_state.as_ref() {
                        wasm_install_gpu_lanes(rs);
                    }
                    // navgraph (dev mode): `?nav=labels` starts the overlay, and the
                    // browser's own Ctrl+G is stopped so the chord can be typed.
                    facett::core::navgraph::web::install(&cc.egui_ctx);
                    let mut app = DemoApp::new();
                    // `apply_initial_tab` PINS the deep-linked tab (the attract tour would
                    // otherwise leave it after 5 s idle) — the same call native makes.
                    app.demo.apply_initial_tab(initial_tab.as_deref());
                    app.demo.apply_gen_link(initial_gen.as_deref());
                    Ok(Box::new(app))
                }),
            )
            .await;

        if let Err(e) = start_result {
            // The adapter was there a moment ago; whatever eframe hit, say it on the page.
            let err = format!("{e:?}");
            let msg = match adapter_retry::after_failure(adapter_retry::MAX_ATTEMPTS, &err) {
                Next::Fail(m) => m,
                Next::RetryAfter(_) => format!("WebGPU renderer failed to start: {err}"),
            };
            show_no_gpu(&msg);
        }
    });
}

/// `navigator.gpu` present and `isSecureContext`, read reflectively (web-sys's WebGPU
/// bindings sit behind `web_sys_unstable_apis`; only existence is needed).
#[cfg(target_arch = "wasm32")]
fn browser_preflight() -> Option<facett::core::adapter_retry::Preflight> {
    use wasm_bindgen::JsValue;
    let window = web_sys::window()?;
    let has_gpu = js_sys::Reflect::get(window.navigator().as_ref(), &JsValue::from_str("gpu"))
        .map(|v| !v.is_undefined() && !v.is_null())
        .unwrap_or(false);
    let secure = js_sys::Reflect::get(window.as_ref(), &JsValue::from_str("isSecureContext"))
        .ok()
        .and_then(|v| v.as_bool())
        .unwrap_or(false);
    facett::core::adapter_retry::preflight(has_gpu, secure)
}

/// `await navigator.gpu.requestAdapter()` → the adapter, or `None` for null / a throw.
#[cfg(target_arch = "wasm32")]
async fn request_adapter() -> Option<wasm_bindgen::JsValue> {
    use wasm_bindgen::JsValue;
    let window = web_sys::window()?;
    let gpu = js_sys::Reflect::get(window.navigator().as_ref(), &JsValue::from_str("gpu")).ok()?;
    let f = js_sys::Reflect::get(&gpu, &JsValue::from_str("requestAdapter")).ok()?;
    let f = f.dyn_into::<js_sys::Function>().ok()?;
    let p = f.call0(&gpu).ok()?.dyn_into::<js_sys::Promise>().ok()?;
    let a = wasm_bindgen_futures::JsFuture::from(p).await.ok()?;
    (!a.is_null() && !a.is_undefined()).then_some(a)
}

/// `Some(what)` when `adapter` (a `GPUAdapter`) is a CPU rasteriser: its `info`
/// strings and `isFallbackAdapter` (on `info` in current Chrome, on the adapter in
/// older ones) through `facett_core::adapter_retry::software_adapter`.
#[cfg(target_arch = "wasm32")]
fn software_adapter_of(adapter: &wasm_bindgen::JsValue) -> Option<String> {
    use wasm_bindgen::JsValue;
    let info = js_sys::Reflect::get(adapter, &JsValue::from_str("info")).unwrap_or(JsValue::UNDEFINED);
    let s = |k: &str| js_sys::Reflect::get(&info, &JsValue::from_str(k)).ok().and_then(|v| v.as_string()).unwrap_or_default();
    let flag = |o: &JsValue| js_sys::Reflect::get(o, &JsValue::from_str("isFallbackAdapter")).ok().and_then(|v| v.as_bool());
    let fallback = flag(&info).or_else(|| flag(adapter)).unwrap_or(false);
    let what = facett::core::adapter_retry::software_adapter(&s("vendor"), &s("architecture"), &s("device"), &s("description"), fallback);
    if let Some(w) = &what {
        log::error!("facett-demo: the WebGPU adapter is a software rasteriser ({w}) — refusing it");
    }
    what
}

/// `setTimeout` as a future (the retry backoff).
#[cfg(target_arch = "wasm32")]
async fn sleep_ms(ms: u32) {
    let p = js_sys::Promise::new(&mut |resolve, _| {
        if let Some(w) = web_sys::window() {
            let _ = w.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, ms as i32);
        }
    });
    let _ = wasm_bindgen_futures::JsFuture::from(p).await;
}

/// Say "no GPU" ON THE PAGE: hide the canvas, drop the spinner, and put the plain
/// message where the demo would have been. Also recorded for a headless driver:
/// `window.__facett_state()` answers `{"gpu":"no GPU","gpu_reason":…}`.
#[cfg(target_arch = "wasm32")]
fn show_no_gpu(msg: &str) {
    log::error!("facett-demo: {msg}");
    facett::core::adapter_retry::record_reason(msg);
    wasm_state::publish(facett::core::adapter_retry::no_gpu_state(msg).to_string());
    let Some(doc) = web_sys::window().and_then(|w| w.document()) else { return };
    if let Some(c) = doc.get_element_by_id("facett_demo") {
        let _ = c.set_attribute("style", "display:none");
    }
    if let Some(l) = doc.get_element_by_id("loading") {
        l.remove();
    }
    if let Ok(div) = doc.create_element("div") {
        div.set_id("facett_no_gpu");
        let _ = div.set_attribute(
            "style",
            "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;\
             padding:2em;box-sizing:border-box;color:#e8b04a;background:#0b0d10;\
             font:500 16px/1.5 system-ui,sans-serif;text-align:center;white-space:pre-wrap",
        );
        div.set_text_content(Some(msg));
        if let Some(b) = doc.body() {
            let _ = b.append_child(&div);
        }
    }
}

// `JsCast::dyn_into` is needed for the canvas downcast above (wasm only).
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast as _;

// ── wasm: the BROWSER-READABLE state hook (`window.__facett_state()`) ───────────
// The "verify the SHIPPED artifact without a human" seam that was missing. There is
// no filesystem in the browser, so the native `$FACETT_DEMO_STATE` dump is a no-op
// on wasm. Instead the running app publishes its whole `state_json` (every tab +
// control + the `runtrace` "what RAN" ledger) into a process-global string each
// frame, and `__facett_state` — exported to JS — returns it. A headless Chromium
// drive (or the browser console) reads `window.__facett_state()`, parses the JSON,
// and sees exactly what executed in the LIVE wasm bundle: e.g. that the Sverige tab
// rendered (`trace.ran["deck.render:Sverige"] > 0`) and its `11M` / `⚡ select`
// handlers fired (`trace.ran["PopGraphTab::ctl:11M"]`). This proves the shipped
// surface RAN, as data.
#[cfg(target_arch = "wasm32")]
mod wasm_state {
    use std::sync::Mutex;
    static LATEST: Mutex<String> = Mutex::new(String::new());

    /// Publish the latest whole-demo `state_json` string (called once per frame).
    pub fn publish(json: String) {
        if let Ok(mut g) = LATEST.lock() {
            *g = json;
        }
    }

    /// Read the latest published state (the JS hook's backing store).
    pub fn latest() -> String {
        LATEST.lock().map(|g| g.clone()).unwrap_or_default()
    }
}

/// `window.__facett_state()` — return the live demo's whole `state_json` (incl. the
/// `runtrace` "what RAN" ledger) as a JSON string, for a headless drive / the
/// browser console to read what actually executed in the shipped wasm bundle.
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen::prelude::wasm_bindgen(js_name = __facett_state)]
pub fn facett_state() -> String {
    wasm_state::latest()
}

/// The eframe app: a [`DemoState`] plus the live toolbar/menu wiring.
struct DemoApp {
    demo: DemoState,
    /// Whether to write the whole-demo `state_json` to the `$FACETT_DEMO_STATE`
    /// channel every frame. This is a **robot-UI test seam**, not interactive work —
    /// it deep-serialises all ~27 facets (a tree that GROWS as the live sim accrues
    /// warehouse rows + iceberg snapshots) and `fs::write`s it to disk EVERY frame.
    /// Paying that on a normal interactive run was the reported lag/stutter (a 5080
    /// idles while the CPU frame-serialises + fsyncs). We only turn it on when an
    /// external driver actually asked for the channel by setting the env var.
    #[cfg(not(target_arch = "wasm32"))]
    dump_state: bool,
    /// The per-tab state copies the page's every-frame publish serves inactive tabs from.
    #[cfg(target_arch = "wasm32")]
    state_cache: facett::core::DeckStateCache,
}

impl DemoApp {
    fn new() -> Self {
        let mut demo = DemoState::new();
        // Opt the deck into fx so the toolbar's glow toggle has something to drive.
        demo.deck.fx_mut().glow = false;
        // Turn on the AUTO-DEMO / attract mode (#82) for the LIVE binary — the SAME
        // `DemoApp::new` constructs both the native window and the wasm web canvas, so
        // the kiosk loop (auto-advance after 5 s idle, cancel on interaction, re-arm)
        // runs on desktop AND in the browser. Headless robot drives build `DemoState`
        // directly and leave it off, so they stay deterministic.
        demo.attract_enabled = true;
        Self {
            demo,
            // Only dump when the state channel is explicitly requested (a robot drive /
            // external tool set `$FACETT_DEMO_STATE`). A human interactive run leaves it
            // unset → zero per-frame serialise + disk write → the frame is GPU-bound again.
            #[cfg(not(target_arch = "wasm32"))]
            dump_state: std::env::var_os("FACETT_DEMO_STATE").is_some(),
            #[cfg(target_arch = "wasm32")]
            state_cache: facett::core::DeckStateCache::default(),
        }
    }
}

impl eframe::App for DemoApp {
    fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
        #[cfg(feature = "flow-wgpu")]
        if let Some(rs) = _frame.wgpu_render_state() {
            facett::flow::gpu::evict_idle(rs, ui.ctx().cumulative_frame_nr());
        }
        // ── Robot-UI drive-half (EPIC-R parity with nornir's viz) ──
        // Poll the control channel ($FACETT_DEMO_CMD) BEFORE drawing, so a switched
        // tab / palette / command is reflected in THIS frame. Consume-once.
        if self.demo.poll_control_channel() {
            ui.ctx().request_repaint();
        }

        // One render path shared with the robot-UI test (`DemoState::draw_app`),
        // so the binary and the robot drive exactly the same UI (CLI/GUI parity).
        self.demo.draw_app(ui);

        // ── Robot-UI read-half ──
        // Dump the whole demo's aggregated state_json to $FACETT_DEMO_STATE so an
        // external driver sees what's on screen (every control + tab + component).
        // On wasm there's no filesystem, so we ALSO publish the state into the
        // `window.__facett_state()`-backed global so a headless browser drive reads
        // exactly what ran in the SHIPPED bundle.
        #[cfg(target_arch = "wasm32")]
        {
            // Rolling: the tab on screen every frame, one inactive tab per frame in
            // turn (`DemoState::state_json_rolling`) — not all 81 tabs every frame.
            wasm_state::publish(self.demo.publish_rolling(&mut self.state_cache));
        }
        #[cfg(not(target_arch = "wasm32"))]
        if self.dump_state {
            self.demo.dump_state();
        }
    }
}