dqsjqian/Aria

Aria

Modern C++20 MVVM framework — cross-platform, layered, coroutine-first. Reactive DAG (Property/Computed/Effect), Task<T>, and pluggable adapters (Qt6, AppKit, ...).

待分类coroutinescppcpp20

⚡ Aria

依赖更新完整指南 — 版本固定、选择性更新、离线、回退与提交步骤。

为工业级跨平台软件而生的现代 C++ MVVM 框架 · C++23 · 响应式 · 协程优先

一套 C++ 核心,六大平台。以优雅架构承载复杂业务,以工程契约支撑长期演进。

Windows / macOS / Linux / iOS / Android / Web

English | 简体中文

📚 从入门到精通:AriaTutorial

想系统地学 Aria?配套教程仓库 AriaTutorial 提供 20 章中英双语渐进式教程,每一章都配一个可以编译、可以运行的最小 demo。

  • 正文里每个代码块都与 demos/ 下的源文件逐字一致,不是手抄的示意代码;
  • 每段输出都是 demo 在 Windows / MSVC 下的真实 stdout,并由脚本自动校验;
  • 每章配一张结论图(数据流 / 依赖图 / 状态机 / 时间线),图里的数字同样取自 demo 的真实运行结果;
  • 教学路径覆盖:响应式核心 → 集合与表单 → 绑定与适配器 → 异步、诊断与测试 → 一个完整应用。

🌟 旗舰示例:AriaTools

想先看 Aria 如何落到真实应用?请从 AriaTools 开始。它是 Aria 唯一的旗舰跨平台示例,同一份 C++ ViewModel 驱动 Qt、iOS、Android 与 Web 四端。本仓库只保留框架、验收测试和文档中的最小代码片段。

🚀 30 秒看懂 Aria

Aria 把一个界面切成两半:ViewModel 是纯 C++,不认识任何 UI 库;View 是各平台原生控件。 中间由 BindingEngine 连接,它只认 IViewAdapter 这个接口,所以换平台只换适配器。

上半 —— ViewModel(纯 C++,可单元测试,所有平台共用这一份)

// AA 制账单:总额 ÷ 人数 = 每人多少
struct BillViewModel {
    aria::Property<double> bill{100.0};   // 可读写的状态
    aria::Property<int>    people{2};

    // Computed 是只读派生值。它的依赖不用手写:首次求值时读到了
    // bill 和 people,就自动记下这两个依赖。
    aria::Computed<double> per_person{[this] {
        return bill.get() / people.get();
    }};
};

这段代码里没有一行 UI,也没有 #include 任何界面库 —— 它在命令行下就能测。

下半 —— View 侧接线(每个平台十几行,界面本身仍用各平台原生方式写)

先说清最容易误会的一点:界面不用 C++ 写。 按钮、布局、动画照旧用 Qt Designer、 Storyboard、Compose、HTML 写。下面这十几行只是"把已经存在的控件交给 engine"的接线代码, 三步永远一样 —— ① 造平台适配器 ② 用它造 BindingEngine ③ 把控件和 Property 绑上。

Qt6(Windows / macOS / Linux · 纯 C++)

auto adapter = std::make_shared<aria::adapters::qt6::QtAdapter>();
aria::binding::BindingEngine engine{adapter};

BillViewModel vm;
// label_view 包住你在 Qt Designer 里拖出来的那个 QLabel
aria::adapters::qt6::QtView label_view{real_label};
engine.bind_text_projected(vm.per_person, label_view,
    [](double v) { return std::format("¥{:.2f}", v); });

iOS / UIKit(接线文件是 Objective-C++ .mm,界面仍是 Storyboard / SwiftUI)

#import "aria/adapters/uikit/UIKitAdapter.hpp"

auto adapter = std::make_shared<aria::adapters::uikit::UIKitAdapter>();
aria::binding::BindingEngine engine(adapter, ui_dispatcher,
    aria::binding::BindingEngine::DispatchPolicy::SmartMarshal);

// 把 Storyboard 里的 UILabel* 包一层,C++ 侧就能绑它
auto label = std::make_shared<aria::adapters::uikit::UIKitView>(self.totalLabel);
engine.bind_text_projected(vm.per_person, *label,
    [](double v) { return std::format("¥{:.2f}", v); });

UIKitView 用 ARC 强引用持有 UIView*,析构时会在原生 view 还活着的时候通知 BindingEngine 清理订阅,所以不会回调到已释放的控件上。

macOS / AppKit(同上,.mm + NSView)

#import "aria/adapters/appkit/AppKitAdapter.hpp"

auto adapter = std::make_shared<aria::adapters::appkit::AppKitAdapter>();
aria::binding::BindingEngine engine(adapter, ui_dispatcher,
    aria::binding::BindingEngine::DispatchPolicy::SmartMarshal);

auto label = std::make_shared<aria::adapters::appkit::AppKitView>(self.totalField);
engine.bind_text_projected(vm.per_person, *label, /* ... */);

Android(界面写 Kotlin / Compose,C++ 只接线)

// 在 Android UI 线程上调用,传进来的是真实的 android.view.View 对象
auto adapter = std::make_shared<aria::adapters::jni::JniAdapter>(env);
aria::binding::BindingEngine engine(adapter);

aria::adapters::jni::JniView total_view(env, total_text_view);
engine.bind_text_projected(vm.per_person, total_view,
    [](double v) { return std::format("¥{:.2f}", v); });   // → TextView

Kotlin 侧的监听器把原生事件转发回来(adapter->notify_text_changed(...) / notify_click(...)),监听器归属仍在 Android 侧,C++ 这边保持强类型。 Compose 没有可寻址的 view 对象,用文档里的 side-channel 形态。

Web(浏览器里没有 C++ —— 前端是 HTML/JS,C++ 跑在服务端)

aria::adapters::http::HttpAdapterConfig config;
config.port = 9090;
auto http = std::make_shared<aria::adapters::http::HttpAdapter>(config);

// "控件"在这里是字符串 ID,对应浏览器里的 DOM 元素
auto& total = http->register_view("total", "text");

aria::binding::BindingEngine engine{http};
engine.bind_text_projected(vm.per_person, total,
    [](double v) { return std::format("¥{:.2f}", v); });
http->start();  // Property 变化经 SSE 推给浏览器,用户操作经 REST 回来

这才是重点:五份接线代码长得几乎一样,而上面那个 BillViewModel 一个字都没改过。 你换平台换的是这十几行,不是业务逻辑。

接线之后 —— 只改数据,界面自己跟着变

上面每个平台绑完,剩下的事就跟平台无关了。下面这段在五个平台上行为完全一致, 没有一行手写的刷新代码:

BillViewModel vm;                       // per_person = 100/2 = ¥50.00
// ... 按上面任意一个平台绑定到 label ...

vm.people = 4;                          // label → ¥25.00
vm.bill   = 200.0;                      // label → ¥50.00

改 bill 或 people 任意一个,per_person 都会重算并推给 label —— 因为它的依赖是 Computed 首次求值时自动记下的,你没写过任何"people 变了要更新 label"这类代码。

连续改多个值时有一个细节值得知道:

// 逐个改 → 每次都推一次,label 会闪过中间值
vm.bill = 300.0;    // label → ¥150.00  ← 中间态
vm.people = 4;      // label → ¥75.00

// 包进 batch → 只在结束时推一次,不出现中间态
aria::reactive::batch([&] {
    vm.bill   = 1200.0;
    vm.people = 8;
});                 // label → ¥150.00(一次)

还有一条省心的默认行为:如果最终结果和当前值相同,一次通知都不会发。 比如上面之后再 batch 里设 bill=600, people=4(仍是 150),label 不会被打扰。

整个架构一张图看全——上半是纯 C++ 的 ViewModel,中间是 BindingEngine(只认 IViewAdapter 接口),下半是五个原生适配器:

继续阅读:绑定指南 · 各平台适配器指南 · Cookbook · AriaTools(Qt / iOS / Android / Web 四端完整应用)。

🎯 设计理念与工程实力

Aria 将响应式状态、异步协程与跨端绑定统一为独立于 UI 工具包的现代 C++ 架构,让复杂业务只实现一次,让各端界面充分发挥原生能力。

优雅来自清晰的分工:ViewModel 是普通 C++ 类,无需继承框架基类、编写宏或运行代码生成器; UI 通过 IViewAdapter 接入。Qt6 / AppKit / UIKit / JNI / HTTP 五个适配器共享同一套绑定协议,切换 UI 工具包无需改写 ViewModel。

以世界级工业软件的工程标准打造 C++ 框架:把生命周期、线程、错误处理与集合事件写成可追踪的工程契约,用自动化测试、模糊测试和可复现的性能基准检验实现。架构之美,落实到每一次状态更新、异步取消与跨端交付。

架构优势 · 工程设计 现代 C++ 基础 · C++23 基线(GCC 14+ / Clang 19+ / AppleClang 21+ / MSVC v145),使用完整的协程与 concepts 能力;集成项目需采用 C++23 或更高标准。 原生 UI 自由 · 控件、布局和动画交给所选 UI 工具包,Aria 统一状态与界面之间的单向/双向数据流;共享业务核心,各端保留原生体验。 分层兼容策略 · aria-abi / aria-runtime / aria-binding 在主版本号内保持 ABI 稳定,要求编译器、标准库与构建选项一致;Property 等模板及其宿主类型更新后需重新编译。 开放适配协议 · Qt6 / AppKit / UIKit / JNI / HTTP 开箱可用;其他 UI 工具包通过实现 IViewAdapter 接入(见适配器指南)。 应用实践与开发资料 · AriaTools、AriaAgent 与 AriaRead 展示跨端工作台、Agent GUI 和阅读引擎中的应用实践;指南、Cookbook 与工程契约覆盖从入门到扩展的开发路径。

为共享 C++ 业务核心、原生多端 UI 和长期维护而设计。 Aria 负责业务与状态层,控件渲染与界面复用由所选 UI 工具包负责;两层通过明确的适配协议协作。

✨ 核心特性

  • 📦 模板化响应式核心 —— Property / Computed / Effect / Command<> / ObservableList / Validator 共享同一个响应式依赖图引擎。Computed 自动跟踪依赖,reactive::batch / reactive::untracked 精确控制通知范围。
  • 🔌 共享基础库与 ABI 层 —— aria::core 自动链接 aria::abi,统一跨动态库的响应式图、诊断和信号存储。ABI 要求一致的编译器、标准库、构建选项和主版本;模板及其宿主类型在更新后需要重新编译。
  • ⚡ C++ 协程 —— Task、执行器、co_await schedule_on(pool),异步代码写起来像同步代码。
  • 🖥 适配器抽象 (IViewAdapter) —— Qt6 / AppKit / UIKit / JNI / HTTP,任何 UI 工具包都能用同一套业务逻辑驱动。

🏗 架构(10 个模块)

┌────────────────────────────────────────────────────────────────────────┐
│                         应用层 (Application)                            │
└────────────────────────────────┬───────────────────────────────────────┘
              ┌──────────────────┼──────────────────┐
              ▼                  ▼                  ▼
   ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
   │ Qt6 适配器    │    │ JNI 适配器    │    │ HTTP 适配器   │     (可选模块;
   │ (Win/Mac/Lin)│    │  (Android)   │    │ REST/SSE Web │      按需启用)
   │ AppKit/UIKit │    │              │    │ WASM 按需评估   │
   └──────┬───────┘    └──────┬───────┘    └──────┬───────┘
          └───────────────────┴───────────────────┘
                              ▼
              ┌─────────────────────────────────┐
              │    aria-binding  (SHARED)       │
              │  BindingEngine + IViewAdapter   │
              └────────────────┬────────────────┘
                               │
        ┌──────────────────────┴───────────────────────┐
        ▼                                              ▼
┌───────────────────┐                       ┌────────────────────┐
│ aria-runtime      │                       │  aria-async        │
│ (SHARED .dylib)   │                       │   (仅头文件)        │
│ EventBus          │                       │ Task<T>            │
│ Container         │                       │ Scheduler          │
│ Dispatcher        │                       │ Executor           │
│ Logger            │                       │ schedule_on        │
└───────┬───────────┘                       └─────────┬──────────┘
        └──────────────────┬─────────────────────────┘
                           ▼
              ┌─────────────────────────────┐
              │ aria-core  (仅头文件)        │
              │ Property / Computed / Cmd   │
              │ ObservableList / Validator  │
              │ Subscription                │
              └────────────┬──────────────┘
                             ▼
              ┌─────────────────────────────┐
              │  aria-abi  (SHARED)      │
              │ 类型擦除 Signal/Slot         │
              │ ABI 稳定,无模板              │
              └─────────────────────────────┘

模块 · 类型 · 依赖 · 说明 aria-abi · 默认 SHARED · Threads · 编译型基础库:信号/槽、共享响应式图、诊断存储、调度器基类与版本信息;支持静态构建。 aria-core · 仅头文件 · abi · 全部模板:Property、Computed、Command、ObservableList、Validator。仅源码兼容。 aria-async · 仅头文件 · core · 协程 Task、执行器。仅源码兼容。 aria-runtime · SHARED · core, abi · EventBus / Container / Dispatcher / Logger —— 单例统一放在一个动态库中。ABI 稳定。 aria-binding · SHARED · core, runtime · BindingEngine、IViewAdapter。ABI 稳定。 适配器 · SHARED/STATIC · binding · Qt6 / AppKit / UIKit / JNI / HTTP(按需启用);WASM 按需评估。

📋 环境要求

  • CMake >= 3.21
  • 完整支持 C++23 的编译器:
  • GCC >= 14(Windows 下可走 MSYS2 UCRT64 工具链)
  • Clang >= 19(macOS 上 AppleClang 21+ 即可)
  • MSVC v145 / Visual Studio 2026(当前 Windows CI 工具链,详见下文)
  • *(可选)* Qt6 >= 6.4(用于 Qt6 适配器)
Windows CI 验证 MSYS2 UCRT64(GCC)和 MSVC / Visual Studio 2026 两条工具链。 VS2026 使用 Visual Studio 18 2026 生成器和 v145 工具集,要求 CMake >= 4.2。自动构建脚本仍能发现已有 VS2022 安装;当前 MSVC 发布验收使用 VS2026。

🚀 快速开始

git clone https://github.com/dqsjqian/Aria.git
cd Aria
cmake -B build/flavors/release -DCMAKE_BUILD_TYPE=Release
cmake --build build/flavors/release -j
ctest --test-dir build/flavors/release --output-on-failure
build/ 是构建树的容器,不要直接配置进它。布局约定见 CMakeLists.txt 顶部的 build-tree guard。

跨平台统一入口

python scripts/build.py --test 统一配置、编译和本机 CTest;--platform qt|web|ios|android 选择适配器,--dry-run 输出完整计划且不写磁盘。默认按平台、工具链、配置和架构隔离目录;--build-dir 可显式复用现有缓存,--offline 复用已验证依赖。

macOS 的 --arch x86_64 / --arch arm64 选择实际目标架构;Visual Studio 使用 --generator-platform x64(或 ARM64)。额外定义使用 --cmake-arg=-DNAME[:TYPE]=VALUE,不能覆盖入口选择的构建配置和工具链。入口在获取依赖前检查已有缓存的编译器、工具链和架构,冲突时保留原目录并要求另选构建目录。

Android 需 --ndk;iOS 需 macOS/Xcode。移动端执行测试仍使用现有模拟器/设备脚本,统一入口不会把交叉编译冒充运行通过。Windows 默认 MSVC,显式 --toolchain mingw 使用独立缓存;使用 Ninja/MSVC 时先打开 Developer PowerShell。原有打包脚本仍负责 SDK 安装与归档。

常用组合:

python3 scripts/build.py --test                          # Release + 跑测试
python3 scripts/build.py --config Debug                  # Debug
python3 scripts/build.py --config Debug --cmake-arg=-DARIA_ENABLE_ASAN=ON --cmake-arg=-DARIA_ENABLE_UBSAN=ON --test
python3 scripts/build.py --config Debug --cmake-arg=-DARIA_ENABLE_TSAN=ON --test
python3 scripts/build.py --toolchain msvc --test         # Windows MSVC
python3 scripts/build.py --toolchain mingw --test        # Windows MSYS2 UCRT64

🛠 Windows 工具链

工具链 · 命令 · 备注 MSVC(VS2026;保留 VS2022 发现兼容) · python scripts/build.py --toolchain msvc · 默认工具链;根据安装版本选择 Visual Studio 生成器。 MSYS2 UCRT64(GCC 14+ / Clang 19+) · python scripts/build.py --toolchain mingw · 体积小(≈300 MB),大多数 CI 镜像已预装。

📖 MSVC 一次性配置

# 1. 安装 Visual Studio 2026 Build Tools(或完整 IDE)及 CMake >= 4.2,勾选
#    "Desktop development with C++" + "C++ CMake tools"。
# 2. (可选)安装 Qt 6 的 msvc2022_64 组件。
# 3. Developer PowerShell 里:
python scripts/build.py --toolchain msvc --test

📖 MSYS2 一次性配置

# 1. 从 https://www.msys2.org 安装 MSYS2
# 2. 打开 "MSYS2 UCRT64" 终端:
pacman -Syu
pacman -S --needed mingw-w64-ucrt-x86_64-toolchain \
                   mingw-w64-ucrt-x86_64-cmake \
                   mingw-w64-ucrt-x86_64-ninja git
# 3. (可选)把 C:\msys64\ucrt64\bin 加入 PATH
# 4. 从任意终端执行:
python scripts/build.py --toolchain mingw --test

📦 在自己的项目中使用

方式 A —— 先安装,再用 `find_package`(生产环境推荐):

cmake -S . -B build/flavors/release -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build/flavors/release -j && sudo cmake --install build/flavors/release
find_package(aria 3.0 CONFIG REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE aria::aria)
# 也可以按需选择模块:aria::core / ::async / ::runtime / ::binding

Linux 安装包中的 Aria 动态库从同目录解析其他 Aria 库。请将这些库一并保留; 移动 SDK 后,以新安装路径重新配置使用它的项目。

方式 B —— 使用独立源码目录(不安装):

set(ARIA_SOURCE_DIR "" CACHE PATH "Path to a separate Aria checkout")
add_subdirectory("${ARIA_SOURCE_DIR}" "${CMAKE_BINARY_DIR}/aria" EXCLUDE_FROM_ALL)
target_link_libraries(my_app PRIVATE aria::core aria::async)

先将 Aria 独立克隆到项目外;配置应用时传入绝对路径,例如 cmake -S . -B build -DARIA_SOURCE_DIR=/path/to/Aria。

💻 旗舰示例

AriaTools 是唯一的旗舰跨平台示例,同一份 C++ ViewModel 驱动 Qt、iOS、Android 与 Web 四端,四端均由 CI 把关。它也是 Android 两种集成形态(Compose side-channel 与 typed JniAdapter)的参考实现。Aria 仓库本身不再承载应用示例,框架行为由 tests/acceptance/ 和各模块测试固定,文档只保留聚焦单一概念的最小片段。

⚙️ 构建选项