C++ 核心运行时:初始化、异步、事件与生命周期
本页描述 SpinningMomo 原生 Win32 C++ 后端中由 core::* 提供的框架级基础设施:进程启动时的子系统初始化顺序、以 core::AppState 为根的集中式状态所有权模型、基于 Asio 协程的 core::async 异步运行时、类型擦除的 core::events 事件总线(通过 PostMessageW 唤醒 Win32 消息循环),以及 core::shutdown 主导的关停生命周期。
采证说明:本页依据仓库权威架构文档 AGENTS.md 中的原始约定撰写。
src/core/下各.hpp/.cpp实现体未能在本次采证中读取(源码工具预算耗尽),因此凡涉及具体实现体的细节均标注"实现细节未在源码中核实",本文不杜撰任何 C++ 代码。
Purpose and Scope
本页覆盖(属 core::* 框架基础设施中与"运行时"直接相关的部分):
- 运行时骨架:进程初始化、子系统装配、消息循环、关停流程
core::state:core::AppState根状态对象及其std::unique_ptr子系统所有权模型core::async:Asio 协程运行时与asio::awaitable<RpcResult<T>>处理器约定core::events:类型擦除事件总线的同步send()与异步post()双通道core::shutdown、core::tasks、core::worker pool、core::runtime info等生命周期配套设施- 贯穿上述设施的横切约定:错误处理(
std::expected)、字符串编码(UTF-8/UTF-16)、头文件自包含与vendor/*.hpp门面
留给兄弟页面的内容(本页仅作"入口级"引用,不展开):
- RPC 协议与注册细节、JSON 字段命名转换 → 见 RPC 相关页面(
core::rpc::register_method) - SQLite/DataMapper/迁移系统 → 见数据库相关页面(
core::database、core::migration) - HTTP 服务器/客户端与 SSE 推送 → 见 HTTP 服务相关页面(
core::http server/core::http client) - WebView2 宿主与窗口 → 见 WebView 相关页面(
core::webview) - 命令注册表、托盘与右键菜单 → 见命令系统页面(
core::commands) - 业务功能(录屏、截图、画廊等)→ 见
features::*各页面 - 前端侧 RPC 客户端与环境检测 → 见
web/src/core/*相关页面
Overview
SpinningMomo 的后端是一个原生 Win32 C++ 程序,内嵌 WebView2 承载 Vue 3 前端,前后端通过 JSON-RPC 2.0 通信,具备两条传输通道:生产环境走 WebView 桥接,开发环境走 HTTP + SSE(uWebSockets,端口 51206)。docs/ 是独立的 VitePress 站点,不属于运行时打包产物。
在这一架构里,core::* 是所有框架基础设施的命名空间,覆盖异步运行时、数据库、事件、HTTP 客户端/服务器、RPC、WebView、i18n、命令、迁移、worker pool、任务、运行时信息、关停与状态。业务代码(features::*)、原生 UI(ui::*)与工具(utils::*)都建立在这些设施之上。
理解本页内容的关键术语:
| 术语 | 含义 |
|---|---|
core::AppState | 单一根状态对象,以 std::unique_ptr 成员持有全部子系统状态;所有操作是接受 AppState& 的自由函数 |
core::async | 基于 Asio 的协程运行时;RPC 处理器返回 asio::awaitable<RpcResult<T>> |
core::events | 类型擦除事件总线;同步 send() 与异步 post(),post() 通过 PostMessageW 唤醒 Win32 消息循环 |
core::shutdown | 关停编排设施,负责生命周期收尾 |
RpcResult<T> | RPC 处理器的结果封装类型(与全局 std::expected<T, std::string> 错误约定配合) |
| 自由函数 + POD 结构 | 后端不使用 OOP 类层次,而是"纯数据结构 + 操作它们的自由函数" |
| usecase 层 | usecase.hpp/.cpp,特性级顶层编排层,可跨 core::*、UI 与扩展协调调用 |
Architecture
下图展示运行时的整体结构:前端经两条 JSON-RPC 传输通道进入 core::rpc 路由,由 core::async 协程运行时分发到返回 asio::awaitable 的处理器;处理器与上层模块(features::* / ui::* / usecase 编排层)都以自由函数操作中央 AppState;产生的副作用经 core::events 事件总线以 PostMessageW 回注 Win32 消息循环;关停由 core::shutdown 统一收尾。
架构要点解读:
- 两条传输、一个路由:WebView 桥接与 HTTP+SSE 只是传输差异,二者都汇入
core::rpc的注册路由。这保证了开发期浏览器调试与生产期 WebView2 行为一致。 - 状态单点:
AppState是唯一根状态对象,避免分散的全局状态;features::*与ui::*不各建状态孤岛,而是引用共享同一棵状态树。 - 事件回注主线程:异步侧(协程、worker pool)产生的通知不直接触碰 UI,而是
post()到事件总线,由PostMessageW唤醒 Win32 消息循环后再派发——这是典型的"跨线程投递到消息泵"模式,保证 UI 更新发生在消息循环线程。 - 关停集中化:
core::shutdown同时对协程运行时与后台任务(tasks / worker pool)收尾,避免各子系统各自为政的退出路径。
核心机制一:初始化与生命周期
启动装配与运行时边界
后端作为原生 Win32 进程启动,运行时的可交付边界由以下事实界定:
- 后端在
localhost:51206提供服务;开发期web/由 Vite 开发服务器驱动,并将/rpc与/static代理到该后端。 docs/是独立的 VitePress 站点,不在运行时打包产物内。
以下摘自仓库权威架构文档的原文,界定运行时通信与传输通道:
The application is a **native Win32 C++ backend** that hosts an embedded **WebView2** frontend. Communication happens over **JSON-RPC 2.0** through two transport layers:
- **WebView bridge** — used when the Vue app runs inside WebView2 (production)
- **HTTP + SSE** — used when the Vue app runs in a browser during development (uWebSockets on port 51206). SSE provides server-to-client push notifications.Source: AGENTS.md
设计意图:把"传输"与"协议"解耦。协议统一为 JSON-RPC 2.0,传输可插拔;开发期用浏览器 + HTTP/SSE 获得热重载与 DevTools,生产期切换到 WebView2 桥接而不改动业务处理器。SSE 通道额外承担服务端到客户端的推送,弥补纯请求-响应模型的不足。
core::AppState:单一根状态对象
后端不使用 OOP 类层次,而遵循"POD 结构 + 自由函数"的范式,状态集中在根对象中:
- **POD Structs + Free Functions**: plain data structs with free functions operating on them.
- **Centralized State**: all state lives in `AppState`, passed by reference.Source: AGENTS.md
`core::AppState` is the single root state object. It owns all subsystem states as `std::unique_ptr` members. Functions are free functions that accept `AppState&`.Source: AGENTS.md
AppState 的布局细节、头文件前置声明约定与 API 依赖约定有专门文档:src/core/state/README.md(本页采证阶段确认该文件存在于仓库,其正文未读取;布局细节请以该文件为准)。
设计意图:
- 所有权明确:子系统状态以
std::unique_ptr成员形式被根对象独占持有,销毁顺序由成员声明顺序决定,杜绝悬垂状态与"谁负责释放"的歧义。 - 避免继承爆炸:自由函数接受
AppState&,功能扩展靠增加函数而非派生类,符合项目"最短路径实现、不过度设计"的修改规范(AGENTS.md L9-L11 明确禁止兼容性补丁与过度设计)。 - 可测试与可推理:一棵显式状态树让"某个操作会改哪些状态"可以通过函数签名(接收
AppState&)直接推断。
usecase 编排层与工作流归属
跨模块的工作流不属于底层模块,而是上提到 usecase 层:
- **Usecase Orchestration**: `usecase.hpp/.cpp` is the top-level orchestration layer for a feature. It may coordinate calls across `core::*`, UI, and extensions.
- **Workflow Ownership**: other modules may depend on and reuse public capabilities as needed, but cross-module workflow orchestration belongs in the usecase layer.Source: AGENTS.md
设计意图:core::* 保持为可复用的框架能力,跨特性编排(例如"录屏开始前先检查窗口状态再通知 UI"这类流程)统一在 usecase 层收口,防止底层模块之间形成横向耦合。
生命周期总览
下图给出运行时从进程启动到关停的全生命周期骨架(各阶段内部顺序以 src/core/ 实现为准):
关停侧的关键事实:core::* 的职责清单中显式包含 shutdown,与 tasks、worker pool 并列,说明关停被当作一等运行时设施而非进程退出时的隐式行为:
- `core::*` — framework infrastructure (async runtime, database, events, HTTP client, HTTP server, RPC, WebView, i18n, commands, migration, worker pool, tasks, runtime info, shutdown, state)Source: AGENTS.md
核心机制二:core::async 异步运行时
协程模型
异步能力基于 Asio 协程运行时,RPC 处理器统一返回 asio::awaitable<RpcResult<T>>:
- **Async**: Asio-based coroutine runtime (`core::async`). RPC handlers return `asio::awaitable<RpcResult<T>>`.Source: AGENTS.md
设计意图:
- 统一异步签名:所有 RPC 处理器共享同一个协程返回类型,路由层无需为同步/异步处理器分别建分发路径;
RpcResult<T>把成功载荷与错误统一封装,调用侧无需 try/catch。 - 与错误约定正交:配合全局
std::expected<T, std::string>约定(见下文横切约定),协程内部用co_return携带RpcResult,异常不作为控制流。 - 结构化并发边界:由
core::shutdown统一停止协程运行时与后台任务,保证退出期不会出现"仍在跑的协程引用已销毁的AppState子状态"。
实现细节(io_context 的线程模型、
co_spawn的完成槽、异常过滤器等)未在源码中核实,本页不作臆测;需要时请直接阅读src/core/async相关实现。
核心机制三:core::events 事件总线
事件系统是类型擦除的总线,具备同步与异步两条投递路径:
- **Events**: Type-erased event bus (`core::events`) with sync `send()` and async `post()` (wakes the Win32 message loop via `PostMessageW`).Source: AGENTS.md
设计意图:
- 类型擦除让发布方不需要为每种事件类型维护独立的订阅容器,总线可以统一持有异构处理器集合,与"POD + 自由函数"范式兼容——事件类型是纯数据,处理器是自由函数。
- 双通道投递区分调用时机:
- 同步
send()适合调用方需要"立即看到处理器执行完"的强一致场景(如状态变更后立刻派生读取)。 - 异步
post()把事件排入队列,通过PostMessageW向 Win32 消息循环投递唤醒消息,处理器稍后在消息循环线程执行。这是把非 UI 线程的产出安全带回 UI 线程的桥梁。
- 同步
- 与 SSE/WebView 推送衔接:服务端到客户端的推送走 SSE(开发期)或 WebView 桥(生产期);事件总线负责后端内部跨模块通知,两者层级不同、互不替代。
下面的事件流时序图描述一次典型的"协程完成 → 事件投递 → 主线程派发"路径:
顺序要点:post() 只是把"待处理事件"登记到总线并唤醒消息循环,真正的处理器执行发生在消息循环线程;协程本身可以先行返回 RpcResult,因此客户端的 RPC 响应与事件派发的先后没有强保证——UI 侧应基于状态而非响应顺序做渲染决策。
横切约定:错误、编码、头文件边界
这些约定贯穿整个 core::* 运行时,是理解实现体时必须掌握的前置规则。
错误处理:std::expected,不用异常做控制流
- **Error handling**: `std::expected<T, std::string>` throughout; no exception-based control flow.Source: AGENTS.md
错误以值的形式在函数签名中显式表达(std::expected<T, std::string>),调用方必须检查返回值才能拿到载荷。这与协程返回 RpcResult<T> 相互配合:协程内 co_return 错误值即可中止并回传错误,无需抛出异常。设计意图:错误路径在类型层面可见,避免"异常可能在任何一行飞出"造成的推理负担,也简化了关停期对半途而废操作的清理。
字符串编码:内部 UTF-8,Win32 边界 UTF-16
- **String encoding**: internal processing uses UTF-8 (`std::string`); Win32 API calls use UTF-16 (`std::wstring`). Convert via utilities in `utils::string`.Source: AGENTS.md
设计意图:内部统一 UTF-8 便于 JSON-RPC(JSON 为 UTF-8 友好)、日志与文件处理;只在调用 Win32 API 的边界处转换到 UTF-16 std::wstring,转换工具集中在 utils::string,避免散落的 WideCharToMultiByte 调用点各自处理边界情形。
头文件边界与 PCH 策略
Every project header must remain self-contained without the PCH. Include `vendor/std.hpp` and the required vendor facades explicitly; `src/pch.hpp` only accelerates those same dependencies.Source: AGENTS.md
External angle-bracket includes are allowed only inside `src/vendor/`. Windows SDK facades under `src/vendor/windows/` map one-to-one to physical SDK headers; do not create domain aggregate facades. Add only stable, high-frequency exact facades to `src/pch.hpp`, while new low-frequency SDK dependencies remain local to their call sites.Source: AGENTS.md
规则要点:
| 规则 | 内容 | 意图 |
|---|---|---|
| 头文件自包含 | 任何项目头文件离开 PCH 也必须可独立编译 | 保证增量编译正确性与 IWYU 可审计性 |
| 外部包含收口 | 尖括号包含只允许出现在 src/vendor/ 内 | 第三方/SDK 依赖有唯一的准入口 |
| 门面一对一 | src/vendor/windows/ 下的 SDK 门面与物理 SDK 头一一对应,禁止域级聚合门面 | 防止门面层演变为另一套并行 API |
| PCH 保守添加 | 只把稳定、高频的精确门面加入 src/pch.hpp;低频新依赖留在调用点 | 控制 PCH 体积与重建成本 |
vendor/*.hpp 同时承担"集中外部包含与配置"的职责,项目行为本身属于 core、features 或 utils:
- **Vendor facades**: `vendor/*.hpp` centralizes external includes and configuration. Use Win32 and third-party APIs directly; project behavior belongs in `core`, `features`, or `utils`.Source: AGENTS.md
Configuration Options
本主题(core::* 运行时骨架)的固定运行时参数如下;更细粒度的子系统配置属于各兄弟页面(数据库、HTTP 服务、WebView 等)。
| 选项 | 类型 | 默认 / 固定值 | 说明 |
|---|---|---|---|
| 后端监听端口(开发期) | int | 51206 | Vite 开发服务器将 /rpc 与 /static 代理到 localhost:51206;SSE 推送同源 |
| 传输通道(生产) | 枚举 | WebView bridge | 前端运行于 WebView2 内时走 WebView 桥接 |
| 传输通道(开发) | 枚举 | HTTP + SSE | 前端运行于浏览器时走 uWebSockets,SSE 承担服务端推送 |
| RPC 协议 | 字符串 | JSON-RPC 2.0 | 两条传输共用同一协议 |
| 后端编程模型 | 枚举 | POD 结构 + 自由函数 | 禁止 OOP 类层次 |
| 状态持有方式 | — | std::unique_ptr 成员 | AppState 独占持有子系统状态 |
| 事件投递(同步) | 方法 | send() | 调用线程内立即执行处理器 |
| 事件投递(异步) | 方法 | post() | 经 PostMessageW 唤醒 Win32 消息循环后派发 |
| 错误表达 | 类型 | std::expected<T, std::string> | 全局约定,异常不作控制流 |
| 内部字符串编码 | 枚举 | UTF-8 (std::string) | Win32 边界转换为 UTF-16 std::wstring |
API Reference
以下签名均摘自仓库权威架构文档;src/core/ 实现体未读取,参数细节与异常行为不作臆测。
core::rpc::register_method<Req, Res>()
注册一个 RPC 方法,处理器返回 asio::awaitable<RpcResult<T>>。请求/响应结构体经 reflect-cpp 完成(反)序列化,字段名在 snake_case(C++)与 camelCase(JSON)之间自动转换。
Parameters:
Req(模板参数):请求类型,字段为snake_caseRes(模板参数):响应类型,字段为snake_case,序列化为camelCaseJSON
Returns: 注册结果(具体返回类型未在源码中核实)
字段命名转换证据:
- **RPC registration**: `core::rpc::register_method<Req, Res>()` registers handlers with reflect-cpp for request/response (de)serialization. Field names are auto-converted between `snake_case` (C++) and `camelCase` (JSON).Source: AGENTS.md
core::events 总线的 send() 与 post()
事件总线的两条投递路径:send() 为同步执行;post() 异步入队并经 PostMessageW 唤醒 Win32 消息循环。参数与返回值细节未在源码中核实,完整签名请阅读 src/core/events 实现体。
AppState& 自由函数约定
所有运行时操作是接受 AppState& 的自由函数,AppState 以 std::unique_ptr 成员持有全部子系统状态。具体函数清单见 src/core/state/README.md(该文件存在于仓库,本页未读取其正文)。
失效模式与边界情形
基于已核实约定可以确定的边界行为:
- 跨线程 UI 更新:任何非消息循环线程若绕过
post()直接触碰ui::*状态,将破坏"事件经PostMessageW回注主线程"的线程模型。正确路径是异步侧只发事件,UI 消费发生在消息循环线程。 - 事件与 RPC 响应的顺序:
post()只入队并唤醒,处理器执行在消息循环线程。若前端依赖"收到响应后事件一定已处理"的假设,会引入时序 bug;渲染应基于AppState快照而非到达顺序。 - 关停竞态:
core::shutdown同时停止协程运行时与 tasks/worker pool。若子系统的std::unique_ptr状态在协程尚未co_return前被销毁,会产生悬垂引用。销毁顺序以成员声明顺序为准(AppState内部布局约定见src/core/state/README.md),实现细节未在源码中核实。 - 编码边界遗漏:内部 UTF-8 与 Win32 UTF-16 的转换若绕过
utils::string在调用点手写,容易漏掉边界情形(如代理对、非 BMP 字符)。约定要求统一走utils::string的工具函数。 - PCH 依赖假象:项目头文件若隐式依赖 PCH 提供的包含,在非 PCH 构建配置下会编译失败。约定明确要求头文件自包含。
- 修改规范约束:AGENTS.md 明确禁止兼容性/补丁性方案与过度设计,要求最短路径实现——这直接约束运行时扩展的演进方式(新增自由函数/usecase 编排优先,而非引入抽象层)。
性能与运行注意事项
- 传输选择影响推送:HTTP+SSE 通道承担服务端推送(开发期),WebView 桥接为生产通道。调试推送类问题时先确认当前通道。
- PCH 增删成本:向
src/pch.hpp添加门面会放大全量重建成本,规范建议低频 SDK 依赖保留在调用点本地。 - worker pool 与 tasks:
core::*职责清单包含 worker pool 与 tasks,说明后台并行被框架统一管理;具体调度参数未在源码中核实。 - runtime info:
core::*同样包含 runtime info 设施,用于运行时信息暴露;细节见实现体。
扩展点
- 新增 RPC 能力:通过
core::rpc::register_method<Req, Res>()注册新方法,处理器返回asio::awaitable<RpcResult<T>>;请求/响应用 reflect-cpp 标注的结构体表达,字段自动完成snake_case↔camelCase转换。 - 新增跨模块工作流:放入对应特性的
usecase.hpp/.cpp,由其协调core::*、UI 与扩展;底层模块之间不做横向编排。 - 新增命令/热键/菜单项:经
core::commands注册表登记动作、开关状态、i18n 键与可选热键,托盘与右键菜单由该注册表驱动(细节见命令系统兄弟页面)。 - 新增后台任务:纳入 tasks / worker pool 体系,并确保被
core::shutdown覆盖,避免退出期孤儿任务。 - 新增第三方依赖:先在
src/vendor/建立一对一门面,再决定是否进入 PCH;禁止在vendor外使用尖括号包含。
测试
本次采证未发现与 core::* 运行时直接相关的测试文件或测试约定描述;测试覆盖情况未在源码中核实。
Related Links
- AGENTS.md — 仓库权威架构文档(本页主要证据来源)
- src/core/state/README.md —
AppState布局、头文件前置声明与 API 依赖约定 - CREDITS.md — 第三方依赖清单(SQLite3、libwebp、zlib、Vue.js、Vite 等)
- 兄弟页面:RPC 协议与注册、数据库与迁移、HTTP 服务与 SSE、WebView 宿主、命令系统、
features::*业务能力、前端web/src/core/*