How iloader Provides Real-Time Progress Updates for Long-Running Operations
iloader leverages Tauri's cross-process event system to stream structured progress data from Rust to React, enabling instantaneous UI updates without polling.
iloader is a desktop application built with a React frontend and a Rust backend powered by Tauri. The application implements real-time progress updates for device operations by establishing an event-driven bridge that transmits state changes from long-running Rust tasks directly to the renderer process.
Backend Event Emission in Rust
The progress mechanism originates in src-tauri/src/operation.rs, where the backend defines a serializable data structure and emits events through Tauri's event system.
The ProgressUpdate Struct
The payload structure carries all necessary state for UI rendering:
use serde::Serialize;
#[derive(Serialize)]
struct ProgressUpdate {
step: u32,
total_steps: u32,
percent: f32,
message: Option<String>,
}
This struct is instantiated within asynchronous operations to capture the current iteration, total workload, calculated percentage, and optional status text.
Emitting Events with app.emit_all
During execution of long-running tasks, the backend broadcasts updates using app.emit_all. This method transmits the payload to all connected frontend listeners on the specified channel:
use tauri::{Manager, Runtime};
pub async fn run_long_task<R: Runtime>(app: tauri::AppHandle<R>) {
let total = 10u32;
for i in 1..=total {
// Perform device I/O or processing work here
let payload = ProgressUpdate {
step: i,
total_steps: total,
percent: (i as f32 / total as f32) * 100.0,
message: Some(format!("Processing step {}", i)),
};
// Broadcast to all frontend windows
app.emit_all("progress", payload).unwrap();
// Yield control to prevent blocking
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
}
The emit_all call targets the "progress" channel, which the frontend monitors. Because the operation runs inside tauri::async_runtime::spawn (invoked via a Tauri command), the main thread remains unblocked, keeping the application window responsive.
Frontend Event Consumption in React
The frontend implementation in src/App.tsx subscribes to the Rust-emitted events and drives UI components through React state.
Listening for Progress Events
The @tauri-apps/api/event module provides the listen function, which registers a callback for the "progress" channel:
import { listen } from '@tauri-apps/api/event';
import { useEffect, useState } from 'react';
import { Progress } from 'antd';
interface ProgressUpdate {
step: number;
total_steps: number;
percent: number;
message?: string;
}
export default function App() {
const [progress, setProgress] = useState<ProgressUpdate>({
step: 0,
total_steps: 0,
percent: 0,
message: '',
});
useEffect(() => {
const unlisten = listen<ProgressUpdate>('progress', (event) => {
setProgress(event.payload);
});
return () => {
unlisten.then((f) => f());
};
}, []);
// Render UI...
}
The generic listen<ProgressUpdate> ensures type safety across the Rust-to-TypeScript boundary. The cleanup function removes the listener when the component unmounts, preventing memory leaks.
Rendering Real-Time Feedback
The captured state feeds directly into UI components:
return (
<div>
<h2>Current Operation</h2>
<Progress
percent={progress.percent}
status={progress.percent < 100 ? 'active' : 'success'}
format={() => `${progress.step} of ${progress.total_steps}`}
/>
<p>{progress.message}</p>
</div>
);
React batches these state updates efficiently, ensuring smooth progress bar animation even when the backend emits events rapidly.
Operation Flow and Threading
The complete lifecycle of a progress-tracked operation follows this sequence:
- Invocation: The frontend calls
invoke('run_long_task'), which is registered insrc-tauri/src/lib.rsas a Tauri command. - Async Execution: The Rust handler spawns the operation on Tauri's async runtime using
tauri::async_runtime::spawn, isolating heavy I/O from the main thread. - Periodic Emission: As the task advances, it calculates completion percentages and calls
app.emit_all("progress", payload). - Instant Delivery: Tauri's event system serializes the payload and transmits it to the renderer process via WebSocket-style messaging.
- UI Reaction: The frontend listener receives the payload, updates React state, and triggers a re-render of the progress indicators.
This architecture ensures that users receive millisecond-level feedback on operation status without the latency or resource overhead of polling mechanisms.
Summary
- iloader implements real-time progress updates through Tauri's bidirectional event system, connecting Rust backend operations to React frontend state.
- The
ProgressUpdatestruct insrc-tauri/src/operation.rsstandardizes payload structure with fields forstep,total_steps,percent, andmessage. - Backend code uses
app.emit_all("progress", payload)to broadcast updates from asynchronous tasks spawned viatauri::async_runtime::spawn. - The frontend consumes events using
listen<ProgressUpdate>('progress', ...)from@tauri-apps/api/event, updating React state to drive progress bars and status text. - This event-driven approach eliminates UI blocking and provides instantaneous visual feedback during device operations.
Frequently Asked Questions
How does iloader prevent the UI from freezing during long operations?
iloader executes all heavy processing within tauri::async_runtime::spawn in the Rust backend. This spawns the work onto a separate thread pool, while the main thread continues running the Tauri event loop. The frontend remains responsive because the Rust code never blocks the main thread, and progress updates arrive asynchronously through the event bridge.
What specific data fields does iloader transmit for each progress update?
According to the source code in src-tauri/src/operation.rs, each payload contains a step (current iteration), total_steps (overall count), percent (calculated completion percentage as f32), and an optional message string for human-readable status text. These fields enable both precise progress bars and descriptive status labels in the UI.
Which Tauri API method transmits progress data from Rust to the frontend?
The backend uses app.emit_all("progress", payload) where app is a tauri::AppHandle. This method broadcasts the serialized ProgressUpdate struct to all active frontend windows listening on the "progress" channel. The frontend receives this through the listen function imported from @tauri-apps/api/event.
Can iloader track multiple concurrent operations with separate progress bars?
While the provided source demonstrates a single "progress" channel, the architecture supports expansion by emitting to dynamically named channels (e.g., format!("progress-{}", operation_id)) or by including an operation identifier within the ProgressUpdate struct. The current implementation focuses on sequential operations, but the event system itself supports concurrent event streams without cross-contamination when channels are properly namespaced.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →