# How Arnis Tracks Generation Progress and Emits GUI Updates in Rust

> Learn how Arnis tracks generation progress and emits GUI updates using Tauri events and Rust. Get real time feedback on your frontend.

- Repository: [Louis Erbkamm/arnis](https://github.com/louis-e/arnis)
- Tags: internals
- Published: 2026-03-20

---

**Arnis uses a global `OnceCell` to store the Tauri main window and provides helper functions in [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs) that emit JSON payloads via Tauri’s event system to update the frontend progress bar in real time.**

Arnis is an open-source Rust application that generates Minecraft worlds from real-world geospatial data. To keep users informed during long-running generation tasks, the system implements a robust mechanism to track generation progress and emit GUI updates that bridge the Rust backend and the Tauri-based frontend.

## Core Progress Tracking Architecture

The progress reporting system lives primarily in [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs) and centers on a global reference to the Tauri webview window. This design allows deep pipeline functions to communicate status without threading window handles through every call stack.

### Global Window Registration with OnceCell

When the application initializes, the main Tauri window is stored in a global static called `MAIN_WINDOW` using `std::sync::OnceCell`. This pattern enables any background thread to access the window handle for emitting events.

According to [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs) (lines 7-15), registration occurs once during startup via `set_main_window(window)`. The global storage decouples the GUI layer from the generation logic while maintaining the ability to send real-time updates from anywhere in the codebase.

### Detecting GUI Mode at Runtime

The function `is_running_with_gui()` determines whether the application is running with an active GUI or in headless mode. As implemented in [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs) (lines 19-21), this check simply verifies whether the `MAIN_WINDOW` static has been initialized, allowing pipeline components to skip GUI calls when running via CLI.

## Emitting Progress Events to the Frontend

Communication between the Rust backend and JavaScript frontend relies on Tauri’s event emission system, serializing progress data as structured JSON payloads.

### Standard Progress Updates via emit_gui_progress_update

The primary function `emit_gui_progress_update(progress, message)` constructs a JSON object and emits it via the window’s `emit` method. In [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs) (lines 34-48), the implementation:

- Retrieves the global window reference from `MAIN_WINDOW`
- Builds the payload: `{"progress": 25.0, "message": "Processing terrain..."}`
- Emits the `"progress-update"` event to the frontend
- Logs telemetry if the emission fails

This function is called throughout the generation pipeline with floating-point percentages that reflect logical completion states.

### Error State Communication with emit_gui_error

When generation fails, `emit_gui_error(msg)` truncates the error message to prevent payload overflow and forwards a 0% progress event prefixed with `"Error!"`. Defined in [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs) (lines 51-58), this ensures the GUI receives a definitive terminal state even when the pipeline encounters unrecoverable errors.

## Pipeline Integration Across Modules

Progress updates are distributed throughout the generation pipeline to provide granular feedback. Each major processing stage calls `emit_gui_progress_update` with percentages matching its logical completion.

Key integration points include:

- **[`src/retrieve_data.rs`](https://github.com/louis-e/arnis/blob/main/src/retrieve_data.rs)** (line 28): Emits updates during network data fetching (e.g., "Fetching data…")
- **[`src/osm_parser.rs`](https://github.com/louis-e/arnis/blob/main/src/osm_parser.rs)**: Reports progress while parsing OpenStreetMap structures ("Parsing data…")
- **[`src/ground.rs`](https://github.com/louis-e/arnis/blob/main/src/ground.rs)** (line 14): Updates during elevation data retrieval ("Fetching elevation…")
- **[`src/data_processing.rs`](https://github.com/louis-e/arnis/blob/main/src/data_processing.rs)** (line 64): Reports terrain processing and ground generation status
- **[`src/gui.rs`](https://github.com/louis-e/arnis/blob/main/src/gui.rs)** (lines 927-932): Orchestrates the high-level flow and emits the final `emit_gui_progress_update(100.0, "Done! World generation completed.")` upon completion

The heavy world-generation work runs inside `tokio::task::spawn_blocking`, allowing the system to execute on worker threads while still accessing the global `MAIN_WINDOW` for status updates.

## Implementation Examples

### Register the Main Window at Startup

```rust
// src/main.rs – inside the Tauri builder setup
fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let window = app.get_window("main").unwrap();
            progress::set_main_window(window);
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

```

### Emit Progress from Background Threads

```rust
use crate::progress::emit_gui_progress_update;

// Somewhere deep inside the generation pipeline
emit_gui_progress_update(25.0, "Processing terrain...");

```

### Handle Errors Gracefully

```rust
use crate::progress::emit_gui_error;

if let Err(e) = risky_operation() {
    emit_gui_error(&e.to_string());
}

```

### Frontend Listener (JavaScript)

While not included in the Rust repository, the frontend consumes these events:

```javascript
window.ipcRenderer.on('progress-update', (_, payload) => {
  const { progress, message } = payload;
  progressBar.value = progress;
  statusLabel.textContent = message;
});

```

## Summary

- **Global State Management**: [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs) uses a `OnceCell` static (`MAIN_WINDOW`) to store the Tauri window, enabling any thread to emit updates
- **Event Emission**: The `emit_gui_progress_update` function sends JSON payloads via Tauri’s `"progress-update"` event channel with percentage and message data
- **Error Handling**: `emit_gui_error` truncates messages and sends 0% progress events to signal failure states to the GUI
- **Pipeline Coverage**: Progress calls are distributed across [`retrieve_data.rs`](https://github.com/louis-e/arnis/blob/main/retrieve_data.rs), [`osm_parser.rs`](https://github.com/louis-e/arnis/blob/main/osm_parser.rs), [`ground.rs`](https://github.com/louis-e/arnis/blob/main/ground.rs), [`data_processing.rs`](https://github.com/louis-e/arnis/blob/main/data_processing.rs), and the orchestration logic in [`gui.rs`](https://github.com/louis-e/arnis/blob/main/gui.rs)
- **Thread Safety**: The architecture supports `tokio::task::spawn_blocking` for heavy computation while maintaining real-time GUI feedback through the global window reference

## Frequently Asked Questions

### How does Arnis detect if it's running with a GUI?

The system uses the `is_running_with_gui()` function in [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs), which checks whether the global `MAIN_WINDOW` OnceCell has been initialized. This allows the same binary to operate in both headless CLI mode and GUI mode without requiring separate builds or conditional compilation flags.

### What happens if a progress update fails to emit?

According to the implementation in [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs) (lines 34-48), if `window.emit()` returns an error, the failure is logged via the telemetry subsystem but does not panic or halt the generation pipeline. This ensures that transient GUI disconnections do not corrupt the world generation process.

### Which generation stages report progress updates?

Progress updates are emitted throughout the entire pipeline: initial network data fetching in [`retrieve_data.rs`](https://github.com/louis-e/arnis/blob/main/retrieve_data.rs), OSM data parsing in [`osm_parser.rs`](https://github.com/louis-e/arnis/blob/main/osm_parser.rs), elevation retrieval in [`ground.rs`](https://github.com/louis-e/arnis/blob/main/ground.rs), terrain processing in [`data_processing.rs`](https://github.com/louis-e/arnis/blob/main/data_processing.rs), and final world writing in the world editor modules. The high-level coordinator in [`src/gui.rs`](https://github.com/louis-e/arnis/blob/main/src/gui.rs) handles the initial 0% state and final 100% completion emission.

### How are errors communicated to the GUI during generation?

Errors are handled by `emit_gui_error()` in [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs), which truncates the error message and emits a 0% progress event with the prefix `"Error!"`. This standardized format allows the frontend to immediately recognize failed states and display the error message while terminating the progress animation.