How Arnis Tracks Generation Progress and Emits GUI Updates in Rust

Arnis uses a global OnceCell to store the Tauri main window and provides helper functions in 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 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 (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 (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 (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 (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 (line 28): Emits updates during network data fetching (e.g., "Fetching data…")
  • src/osm_parser.rs: Reports progress while parsing OpenStreetMap structures ("Parsing data…")
  • src/ground.rs (line 14): Updates during elevation data retrieval ("Fetching elevation…")
  • src/data_processing.rs (line 64): Reports terrain processing and ground generation status
  • 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

// 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

use crate::progress::emit_gui_progress_update;

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

Handle Errors Gracefully

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:

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

Summary

  • Global State Management: 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, osm_parser.rs, ground.rs, data_processing.rs, and the orchestration logic in 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, 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 (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, OSM data parsing in osm_parser.rs, elevation retrieval in ground.rs, terrain processing in data_processing.rs, and final world writing in the world editor modules. The high-level coordinator in 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →