Robius应用程序架构技能Skill robius-app-architecture

这个技能提供了基于 Robius 框架和 Makepad 构建应用程序的架构模式最佳实践,专注于异步集成、Tokio 运行时、UI 和后台任务通信,适用于开发高效异步处理的生产级跨平台应用。关键词包括:Robius、Makepad、异步架构、Tokio、应用程序设计、跨平台开发、UI 线程、后台任务、SignalToUI、Cx::post_action。

架构设计 0 次安装 0 次浏览 更新于 3/13/2026

name: robius-app-architecture description: | 关键:用于 Robius 应用架构模式。触发关键词: Tokio, async, submit_async_request, 异步, 架构, SignalToUI, Cx::post_action, worker task, app structure, MatchEvent, handle_startup

Robius 应用架构技能

基于 Robrix 和 Moly 代码库的 Makepad 应用程序结构最佳实践——这些是使用 Makepad 和 Robius 框架构建的生产应用程序。

源代码库:

  • Robrix:Matrix 聊天客户端——具有后台订阅的复杂同步/异步
  • Moly:AI 聊天应用程序——跨平台(原生 + WASM)支持流式 API

触发条件

在以下情况使用此技能:

  • 使用异步后端集成构建 Makepad 应用程序
  • 在 Makepad 中设计同步/异步通信模式
  • 构建 Robius 风格的应用结构
  • 关键词:robrix, robius, makepad app structure, async makepad, tokio makepad

生产模式

有关生产就绪的异步模式,请参阅 _base/ 目录:

模式 描述
08-async-loading 带加载状态的异步数据加载
09-streaming-results 使用 SignalToUI 的增量结果
13-tokio-integration 完整的 Tokio 运行时集成

核心架构模式

┌─────────────────────────────────────────────────────────────┐
│                     UI 线程 (Makepad)                     │
│  ┌─────────┐     ┌──────────┐     ┌──────────────────────┐ │
│  │   应用   │────▶│ WidgetRef │────▶│ Widget 树 (视图)  │ │
│  │   状态   │     │    ui     │     │ Scope::with_data()  │ │
│  └────┬────┘     └──────────┘     └──────────────────────┘ │
│       │                                                     │
│       │ submit_async_request()                              │
│       ▼                                                     │
│  ┌─────────────────┐          ┌─────────────────────────┐  │
│  │ REQUEST_SENDER  │─────────▶│  Crossbeam SegQueue     │  │
│  │ (MPSC 通道)     │          │  (无锁更新)             │  │
│  └─────────────────┘          └─────────────────────────┘  │
└───────────────────────────────────┬─────────────────────────┘
                                    │
                    SignalToUI::set_ui_signal()
                                    │
┌───────────────────────────────────┴─────────────────────────┐
│                   Tokio 运行时 (异步)                      │
│  ┌──────────────────────────────────────────────────────┐   │
│  │           worker_task (请求处理器)               │   │
│  │  - 从 UI 接收请求                           │   │
│  │  - 为每个请求生成异步任务                     │   │
│  │  - 通过 Cx::post_action() 回传动作           │   │
│  └──────────────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────┐   │
│  │         每项订阅者任务                     │   │
│  │  - 监听外部数据流                    │   │
│  │  - 通过 crossbeam 通道发送更新                 │   │
│  │  - 调用 SignalToUI::set_ui_signal() 唤醒 UI       │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

应用结构

顶层应用定义

use 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 = {
                    // 主内容在此
                }
            }
        }
    }
}

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) {
        // 顺序重要:先注册基础 widgets
        makepad_widgets::live_design(cx);
        // 然后是共享/通用 widgets
        crate::shared::live_design(cx);
        // 然后是功能模块
        crate::home::live_design(cx);
    }
}

impl LiveHook for App {
    fn after_new_from_doc(&mut self, cx: &mut Cx) {
        // Widget 树创建后的一次性初始化
    }
}

AppMain 实现

impl AppMain for App {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
        // 转发到 MatchEvent trait
        self.match_event(cx, event);

        // 通过 Scope 将 AppState 传递到 widget 树
        let scope = &mut Scope::with_data(&mut self.app_state);
        self.ui.handle_event(cx, event, scope);
    }
}

Tokio 运行时集成

静态运行时初始化

use 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("创建 Tokio 运行时失败")
        })
        .handle()
        .clone();

    // 存储发送器供 UI 线程使用
    *REQUEST_SENDER.lock().unwrap() = Some(request_sender);

    // 生成主工作器任务
    rt_handle.spawn(worker_task(request_receiver));

    Ok(rt_handle)
}

请求提交模式

pub enum AppRequest {
    FetchData { id: String },
    SendMessage { content: String },
    // ... 其他请求类型
}

/// 从 UI 线程提交请求到异步运行时
pub fn submit_async_request(req: AppRequest) {
    if let Some(sender) = REQUEST_SENDER.lock().unwrap().as_ref() {
        sender.send(req)
            .expect("BUG: 工作器任务接收器已失效!");
    }
}

工作器任务模式

async fn worker_task(mut request_receiver: UnboundedReceiver<AppRequest>) -> Result<()> {
    while let Some(request) = request_receiver.recv().await {
        match request {
            AppRequest::FetchData { id } => {
                // 为每个请求生成新任务
                let _task = tokio::spawn(async move {
                    let result = fetch_data(&id).await;
                    // 将结果回传到 UI 线程
                    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(())
}

无锁更新队列模式

用于后台任务的高频更新:

use 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();

/// 从后台异步任务调用
pub fn enqueue_update(update: DataUpdate) {
    PENDING_UPDATES.push(update);
    SignalToUI::set_ui_signal();  // 唤醒 UI 线程
}

// 在 widget 的 handle_event 中:
impl Widget for MyWidget {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
        // 在 Signal 事件中轮询更新
        if let Event::Signal = event {
            while let Some(update) = PENDING_UPDATES.pop() {
                match update {
                    DataUpdate::NewItem { item } => {
                        self.items.push(item);
                        self.redraw(cx);
                    }
                    // ... 处理其他更新
                }
            }
        }
    }
}

启动序列

impl MatchEvent for App {
    fn handle_startup(&mut self, cx: &mut Cx) {
        // 1. 初始化日志
        let _ = tracing_subscriber::fmt::try_init();

        // 2. 初始化应用数据目录
        let _app_data_dir = crate::app_data_dir();

        // 3. 加载持久化状态
        if let Err(e) = persistence::load_window_state(
            self.ui.window(ids!(main_window)), cx
        ) {
            error!("加载窗口状态失败:{}", e);
        }

        // 4. 基于加载的状态更新 UI
        self.update_ui_visibility(cx);

        // 5. 启动异步运行时
        let _rt_handle = crate::start_async_runtime().unwrap();
    }
}

关闭序列

impl AppMain for App {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
        if let Event::Shutdown = event {
            // 保存窗口几何信息
            let window_ref = self.ui.window(ids!(main_window));
            if let Err(e) = persistence::save_window_state(window_ref, cx) {
                error!("保存窗口状态失败:{e}");
            }

            // 保存应用状态
            if let Some(user_id) = current_user_id() {
                if let Err(e) = persistence::save_app_state(
                    self.app_state.clone(), user_id
                ) {
                    error!("保存应用状态失败:{e}");
                }
            }
        }
        // ... 其余事件处理
    }
}

最佳实践

  1. 关注点分离:主线程保留 UI 逻辑,异步操作在 Tokio 运行时中
  2. 请求/响应模式:使用类型化枚举表示请求和动作
  3. 无锁更新:使用 crossbeam::SegQueue 进行高频后台更新
  4. SignalToUI:入队更新后始终调用 SignalToUI::set_ui_signal()
  5. Cx::post_action():用于需要动作处理的异步任务结果
  6. Scope::with_data():通过 widget 树传递共享状态
  7. 模块注册顺序:在 live_register() 中先注册基础 widgets,再注册依赖模块

参考文件

  • references/tokio-integration.md - 详细的 Tokio 运行时模式(Robrix)
  • references/channel-patterns.md - 通道通信模式(Robrix)
  • references/moly-async-patterns.md - 跨平台异步模式(Moly)
    • 用于原生/WASM 兼容性的 PlatformSend trait
    • 用于异步延迟操作的 UiRunner
    • 用于任务取消的 AbortOnDropHandle
    • 用于 WASM 上非 Send 类型的 ThreadToken
    • 平台无关的 spawn() 函数