Install any skill in seconds. Free to start, no credit card required.
Get Started Free →CRITICAL: Use for Robius app architecture patterns. Triggers on: Tokio, async, submit_async_request, 异步, 架构, SignalToUI, Cx::post_action, worker task, app structure, MatchEvent, handle_startup
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 13% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 26% | 0% |
Best practices for structuring Makepad applications based on the Robrix and Moly codebases - production applications built with Makepad and Robius framework.
Source codebases:
Use this skill when:
For production-ready async patterns, see the _base/ directory:
| Pattern | Description | |---------|-------------| | 08-async-loading | Async data loading with loading states | | 09-streaming-results | Incremental results with SignalToUI | | 13-tokio-integration | Full tokio runtime integration |
┌─────────────────────────────────────────────────────────────┐
│ UI Thread (Makepad) │
│ ┌─────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ App │────▶│ WidgetRef │────▶│ Widget Tree (View) │ │
│ │ State │ │ ui │ │ Scope::with_data() │ │
│ └────┬────┘ └──────────┘ └──────────────────────┘ │
│ │ │
│ │ submit_async_request() │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────────────┐ │
│ │ REQUEST_SENDER │─────────▶│ Crossbeam SegQueue │ │
│ │ (MPSC Channel) │ │ (Lock-free Updates) │ │
│ └─────────────────┘ └─────────────────────────┘ │
└───────────────────────────────────┬─────────────────────────┘
│
SignalToUI::set_ui_signal()
│
┌───────────────────────────────────┴─────────────────────────┐
│ Tokio Runtime (Async) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ worker_task (Request Handler) │ │
│ │ - Receives Request from UI │ │
│ │ - Spawns async tasks per request │ │
│ │ - Posts actions back via Cx::post_action() │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Per-Item Subscriber Tasks │ │
│ │ - Listens to external data stream │ │
│ │ - Sends Update via crossbeam channel │ │
│ │ - Calls SignalToUI::set_ui_signal() to wake UI │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘rustuse makepad_widgets::*; live_design! { use link::theme::*; use link::widgets::*; App = {{App}} { ui: <Root>{ main_window = <Window> { window: {inner_size: vec2(1280, 800), title: "MyApp"}, body = { // Main content here } } } } } app_main!(App); #[derive(Live)] pub struct App { #[live] ui: WidgetRef, #[rust] app_state: AppState, } impl LiveRegister for App { fn live_register(cx: &mut Cx) { // Order matters: register base widgets first makepad_widgets::live_design(cx); // Then shared/common widgets crate::shared::live_design(cx); // Then feature modules crate::home::live_design(cx); } } impl LiveHook for App { fn after_new_from_doc(&mut self, cx: &mut Cx) { // One-time initialization after widget tree is created } }
rustimpl AppMain for App { fn handle_event(&mut self, cx: &mut Cx, event: &Event) { // Forward to MatchEvent trait self.match_event(cx, event); // Pass AppState through widget tree via Scope let scope = &mut Scope::with_data(&mut self.app_state); self.ui.handle_event(cx, event, scope); } }
rustuse std::sync::Mutex; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; static TOKIO_RUNTIME: Mutex<Option<tokio::runtime::Runtime>> = Mutex::new(None); static REQUEST_SENDER: Mutex<Option<UnboundedSender<AppRequest>>> = Mutex::new(None); pub fn start_async_runtime() -> Result<tokio::runtime::Handle> { let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel(); let rt_handle = TOKIO_RUNTIME.lock().unwrap() .get_or_insert_with(|| { tokio::runtime::Runtime::new() .expect("Failed to create Tokio runtime") }) .handle() .clone(); // Store sender for UI thread to use *REQUEST_SENDER.lock().unwrap() = Some(request_sender); // Spawn the main worker task rt_handle.spawn(worker_task(request_receiver)); Ok(rt_handle) }
rustpub enum AppRequest { FetchData { id: String }, SendMessage { content: String }, // ... other request types } /// Submit a request from UI thread to async runtime pub fn submit_async_request(req: AppRequest) { if let Some(sender) = REQUEST_SENDER.lock().unwrap().as_ref() { sender.send(req) .expect("BUG: worker task receiver has died!"); } }
rustasync fn worker_task(mut request_receiver: UnboundedReceiver<AppRequest>) -> Result<()> { while let Some(request) = request_receiver.recv().await { match request { AppRequest::FetchData { id } => { // Spawn a new task for each request let _task = tokio::spawn(async move { let result = fetch_data(&id).await; // Post result back to UI thread Cx::post_action(DataFetchedAction { id, result }); }); } AppRequest::SendMessage { content } => { let _task = tokio::spawn(async move { match send_message(&content).await { Ok(()) => Cx::post_action(MessageSentAction::Success), Err(e) => Cx::post_action(MessageSentAction::Failed(e)), } }); } } } Ok(()) }
For high-frequency updates from background tasks:
rustuse crossbeam_queue::SegQueue; use makepad_widgets::SignalToUI; pub enum DataUpdate { NewItem { item: Item }, ItemChanged { id: String, changes: Changes }, Status { message: String }, } static PENDING_UPDATES: SegQueue<DataUpdate> = SegQueue::new(); /// Called from background async tasks pub fn enqueue_update(update: DataUpdate) { PENDING_UPDATES.push(update); SignalToUI::set_ui_signal(); // Wake UI thread } // In widget's handle_event: impl Widget for MyWidget { fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { // Poll for updates on Signal events if let Event::Signal = event { while let Some(update) = PENDING_UPDATES.pop() { match update { DataUpdate::NewItem { item } => { self.items.push(item); self.redraw(cx); } // ... handle other updates } } } } }
rustimpl MatchEvent for App { fn handle_startup(&mut self, cx: &mut Cx) { // 1. Initialize logging let _ = tracing_subscriber::fmt::try_init(); // 2. Initialize app data directory let _app_data_dir = crate::app_data_dir(); // 3. Load persisted state if let Err(e) = persistence::load_window_state( self.ui.window(ids!(main_window)), cx ) { error!("Failed to load window state: {}", e); } // 4. Update UI based on loaded state self.update_ui_visibility(cx); // 5. Start async runtime let _rt_handle = crate::start_async_runtime().unwrap(); } }
rustimpl AppMain for App { fn handle_event(&mut self, cx: &mut Cx, event: &Event) { if let Event::Shutdown = event { // Save window geometry let window_ref = self.ui.window(ids!(main_window)); if let Err(e) = persistence::save_window_state(window_ref, cx) { error!("Failed to save window state: {e}"); } // Save app state if let Some(user_id) = current_user_id() { if let Err(e) = persistence::save_app_state( self.app_state.clone(), user_id ) { error!("Failed to save app state: {e}"); } } } // ... rest of event handling } }
crossbeam::SegQueue for high-frequency background updatesSignalToUI::set_ui_signal() after enqueueing updateslive_register()references/tokio-integration.md - Detailed Tokio runtime patterns (Robrix)references/channel-patterns.md - Channel communication patterns (Robrix)references/moly-async-patterns.md - Cross-platform async patterns (Moly)PlatformSend trait for native/WASM compatibilityUiRunner for async defer operationsAbortOnDropHandle for task cancellationThreadToken for non-Send types on WASMspawn() platform-agnostic functionOther measured skills in the registry, with their headline benchmark lift.