From 688f03f43aeb0ca2f5859e32d632ddd32149ed8b Mon Sep 17 00:00:00 2001 From: nanako <469449812@qq.com> Date: Sun, 14 Dec 2025 20:22:37 +0800 Subject: [PATCH] Refactor widget framework for thread synchronization and improve text rendering quality - Removed the WIDGET_THREAD_SYNC_REFACTORING_PLAN.md document as part of the restructuring process. - Enhanced the atlas GPU resource to enable anisotropic filtering for better text edge quality. - Updated MTSDF text shaders to dynamically pass pixel range for improved anti-aliasing based on screen density. - Modified glyph cache to calculate pixel range based on glyph size, ensuring consistency in rendering. - Adjusted text shaping logic to utilize actual glyph size for scaling, improving text rendering accuracy. - Refined IME handling in Windows to disable system IME UI while allowing candidate window display. --- docs/ASYNC_MTSDF_DESIGN.md | 868 ------ docs/EVENT_RESULT_REFACTORING.md | 764 ----- docs/EVENT_TARGET_ELIMINATION_PLAN.md | 664 ---- docs/IME_IMPLEMENTATION.md | 360 --- docs/KEYBOARD_EVENT_SYSTEM.md | 1472 --------- docs/MTSDF_LOCK_OPTIMIZATION.md | 403 --- docs/MULTI_WINDOW_DOCK_SYSTEM_DESIGN.md | 1228 -------- docs/POST_EFFECT_INCREMENTAL_RENDERING.md | 642 ---- docs/TEXTURE_ATLAS_SYSTEM_DESIGN.md | 1075 ------- docs/TEXT_INPUT_ARCHITECTURE_ANALYSIS.md | 660 ---- docs/TEXT_INPUT_DESIGN.md | 1490 --------- docs/THREAD_SYNC_FRAMEWORK.md | 2665 ----------------- docs/WIDGET_THREAD_SYNC_REFACTORING_PLAN.md | 531 ---- src/render/atlas/atlas_gpu_resource.cpp | 8 +- .../shaders/widget/mtsdf_text.frag.glsl | 61 +- .../shaders/widget/mtsdf_text.vert.glsl | 5 + .../widget/mtsdf_text_outline.frag.glsl | 25 +- .../widget/mtsdf_text_shadow.frag.glsl | 25 +- src/render/text/glyph_cache.cpp | 20 +- src/render/text/glyph_cache.h | 38 +- src/render/text/text_shaper.cpp | 21 +- src/window/desktop/windows/ime_win32.cpp | 7 +- 22 files changed, 150 insertions(+), 12882 deletions(-) delete mode 100644 docs/ASYNC_MTSDF_DESIGN.md delete mode 100644 docs/EVENT_RESULT_REFACTORING.md delete mode 100644 docs/EVENT_TARGET_ELIMINATION_PLAN.md delete mode 100644 docs/IME_IMPLEMENTATION.md delete mode 100644 docs/KEYBOARD_EVENT_SYSTEM.md delete mode 100644 docs/MTSDF_LOCK_OPTIMIZATION.md delete mode 100644 docs/MULTI_WINDOW_DOCK_SYSTEM_DESIGN.md delete mode 100644 docs/POST_EFFECT_INCREMENTAL_RENDERING.md delete mode 100644 docs/TEXTURE_ATLAS_SYSTEM_DESIGN.md delete mode 100644 docs/TEXT_INPUT_ARCHITECTURE_ANALYSIS.md delete mode 100644 docs/TEXT_INPUT_DESIGN.md delete mode 100644 docs/THREAD_SYNC_FRAMEWORK.md delete mode 100644 docs/WIDGET_THREAD_SYNC_REFACTORING_PLAN.md diff --git a/docs/ASYNC_MTSDF_DESIGN.md b/docs/ASYNC_MTSDF_DESIGN.md deleted file mode 100644 index a8e592e..0000000 --- a/docs/ASYNC_MTSDF_DESIGN.md +++ /dev/null @@ -1,868 +0,0 @@ -# 异步MTSDF纹理生成架构设计 - -## 目录 - -1. [概述](#概述) -2. [整体架构](#整体架构) -3. [async_mtsdf_generator 设计](#async_mtsdf_generator-设计) -4. [glyph_cache 改进](#glyph_cache-改进) -5. [纹理上传批处理](#纹理上传批处理) -6. [线程安全策略](#线程安全策略) -7. [接口定义](#接口定义) - ---- - -## 概述 - -### 背景 - -当前MTSDF字形生成是同步阻塞的,在主线程或渲染线程中执行会导致帧率下降。本设计旨在将MTSDF生成移至后台线程池,实现异步非阻塞的字形生成。 - -### 设计目标 - -- **性能**: 将CPU密集的MTSDF生成移至后台线程,不阻塞渲染 -- **响应性**: 使用占位符机制,字形请求立即返回 -- **线程安全**: 正确处理FreeType的线程限制和图集的并发访问 -- **兼容性**: 复用现有的 `future_chain` 和 `thread_dispatcher` 框架 - -### 核心挑战 - -| 挑战 | 解决方案 | -|------|----------| -| `FT_Face` 非线程安全 | 每个Worker线程独立的 `font_manager` 实例 | -| 图集并发修改 | 互斥锁保护 + 批量更新策略 | -| GPU纹理上传 | 渲染线程专属上传 + 脏区域追踪 | - ---- - -## 整体架构 - -### 组件关系图 - -```mermaid -graph TB - subgraph 调用层 - TS[text_shaper] - TR[text_renderer] - end - - subgraph 缓存层 - GC[glyph_cache_async] - end - - subgraph 生成层 - AMG[async_mtsdf_generator] - subgraph Worker Pool - W1[Worker 1
font_manager + queue] - W2[Worker 2
font_manager + queue] - WN[Worker N
font_manager + queue] - end - end - - subgraph 存储层 - FA[font_atlas] - TM[texture_manager] - end - - TS --> GC - TR --> GC - GC -->|request_glyph| AMG - AMG --> W1 - AMG --> W2 - AMG --> WN - W1 -->|bitmap| GC - W2 -->|bitmap| GC - WN -->|bitmap| GC - GC -->|add_glyph| FA - FA -->|upload| TM -``` - -### 数据流图 - -```mermaid -sequenceDiagram - participant Caller as text_shaper - participant Cache as glyph_cache_async - participant Gen as async_mtsdf_generator - participant Worker as Worker Thread - participant Atlas as font_atlas - participant Render as Render Thread - - Caller->>Cache: get_or_create_glyph_async - - alt 缓存命中 - Cache-->>Caller: glyph_rect 指针 - else 缓存未命中 - Cache->>Cache: 创建 pending 占位符 - Cache-->>Caller: nullptr 或占位符 - Cache->>Gen: submit_task - Gen->>Worker: 调度到空闲Worker - Worker->>Worker: load_glyph + generate_mtsdf - Worker-->>Gen: glyph_bitmap - Gen->>Cache: on_complete 回调 - Cache->>Atlas: add_glyph with mutex - Atlas->>Atlas: 标记 dirty region - end - - Note over Render: 帧开始 - Render->>Atlas: flush_pending_uploads - Atlas->>Render: 批量上传脏区域 -``` - -### 新增与修改组件 - -| 组件 | 类型 | 描述 | -|------|------|------| -| `async_mtsdf_generator` | 新增 | 管理Worker线程池,调度MTSDF生成任务 | -| `mtsdf_worker` | 新增 | Worker线程上下文,持有独立的FreeType资源 | -| `glyph_cache` | 修改 | 添加异步接口和状态管理 | -| `font_atlas` | 修改 | 添加线程安全和脏区域追踪 | - ---- - -## async_mtsdf_generator 设计 - -### 核心职责 - -1. 管理Worker线程池的生命周期 -2. 调度MTSDF生成任务到空闲Worker -3. 处理任务完成回调 -4. 支持任务取消 - -### Worker线程模型 - -每个Worker线程拥有: -- 独立的 `FT_Library` 实例 -- 独立的字体Face缓存(从字体文件路径加载) -- 专属的任务队列 - -```mermaid -graph LR - subgraph async_mtsdf_generator - TQ[Task Queue
mpsc_queue] - Scheduler[Scheduler] - end - - subgraph Worker 1 - FT1[FT_Library] - FC1[Font Cache] - LQ1[Local Queue] - end - - subgraph Worker 2 - FT2[FT_Library] - FC2[Font Cache] - LQ2[Local Queue] - end - - TQ --> Scheduler - Scheduler -->|Round Robin| LQ1 - Scheduler -->|Round Robin| LQ2 -``` - -### 任务定义 - -```cpp -struct mtsdf_task { - uint64_t task_id; // 唯一任务ID - std::filesystem::path font_path; // 字体文件路径 - uint32_t glyph_index; // 字形索引 - uint32_t glyph_size; // 输出尺寸 - double pixel_range; // SDF范围 - std::shared_ptr> handle; -}; -``` - -### 任务调度策略 - -1. **Round-Robin**: 简单轮询分配到各Worker -2. **Work-Stealing**: Worker空闲时从其他队列偷取任务(可选优化) - ---- - -## glyph_cache 改进 - -### 字形状态枚举 - -```cpp -enum class glyph_state : uint8_t { - ready, // 字形已生成,可直接使用 - pending, // 生成中,返回占位符 - failed // 生成失败,使用fallback -}; -``` - -### 缓存条目结构 - -```cpp -struct glyph_entry { - glyph_state state; - uint32_t glyph_id; // ready时有效 - uint64_t task_id; // pending时有效,用于取消 - std::chrono::steady_clock::time_point request_time; -}; -``` - -### 占位符机制 - -```mermaid -stateDiagram-v2 - [*] --> NotCached: get_or_create - NotCached --> Pending: 提交异步任务 - Pending --> Ready: 生成成功 - Pending --> Failed: 生成失败 - Failed --> Pending: 重试 - Ready --> [*] -``` - -**占位符策略**: -- `pending` 状态返回 `nullptr` 或特殊占位符矩形 -- 调用者可选择:跳过渲染 / 使用fallback字符 / 使用空白占位 -- 完成后通过回调通知,下一帧自动更新 - -### 异步回调处理 - -```cpp -// 回调在Worker线程触发,通过dispatcher调度到主线程 -auto on_glyph_ready = [this, key](glyph_bitmap bitmap) { - get_dispatcher().dispatch_main([this, key, bmp = std::move(bitmap)] { - // 在主线程更新缓存和图集 - finalize_glyph(key, bmp); - }); -}; -``` - ---- - -## 纹理上传批处理 - -### 设计原则 - -1. **收集**: 在一帧内收集所有完成的字形位图 -2. **批量**: 帧开始时一次性上传所有新字形 -3. **同步**: 使用staging buffer避免GPU同步开销 - -### 脏区域追踪 - -```cpp -struct dirty_region { - uint32_t x, y; - uint32_t width, height; -}; - -class font_atlas { - // ... - std::vector dirty_regions_; - std::mutex atlas_mutex_; -}; -``` - -### 上传流程 - -```mermaid -sequenceDiagram - participant Main as Main Thread - participant Atlas as font_atlas - participant Render as Render Thread - participant GPU as GPU - - Note over Main: 帧N - 字形生成完成 - Main->>Atlas: add_glyph x N - Atlas->>Atlas: 记录 dirty_regions - - Note over Render: 帧N+1 开始 - Render->>Atlas: get_dirty_regions - Atlas-->>Render: regions[] - Render->>GPU: 批量上传脏区域 - Render->>Atlas: clear_dirty_flag -``` - -### 与渲染管线同步 - -```cpp -// 在render_pipeline或frame_scheduler中 -void begin_frame() { - // 1. 处理主线程任务队列(包含字形完成回调) - dispatcher.process_render_queue(); - - // 2. 上传所有待处理的字形纹理 - if (glyph_cache.get_atlas().is_dirty()) { - upload_atlas_updates(); - } - - // 3. 开始正常渲染 - // ... -} -``` - ---- - -## 线程安全策略 - -### font_atlas 互斥锁保护 - -**需要保护的操作**: -- `add_glyph()`: 修改矩形打包状态和纹理数据 -- `get_atlas_data()`: 读取纹理数据(上传时) - -**不需要保护的操作**: -- `get_glyph_rect()`: 只读访问已存在的字形(原子性保证) - -```cpp -class font_atlas { -public: - auto add_glyph(uint32_t id, const glyph_bitmap& bmp) -> bool { - std::lock_guard lock(mutex_); - // ... 添加字形逻辑 - } - - auto get_glyph_rect(uint32_t id) const -> const glyph_rect* { - // 读取不需要锁(unordered_map的读取在无并发写入时安全) - // 但需要确保在add_glyph完成后才能读取新添加的字形 - return glyph_rects_.contains(id) ? &glyph_rects_.at(id) : nullptr; - } - -private: - mutable std::mutex mutex_; -}; -``` - -### 任务取消机制 - -```cpp -class async_mtsdf_generator { -public: - // 取消特定字形的生成任务 - void cancel_task(uint64_t task_id) { - // 1. 从待处理队列中移除(如果还未开始) - pending_tasks_.erase(task_id); - - // 2. 设置取消标志(如果正在执行) - if (auto handle = running_handles_.find(task_id); handle != running_handles_.end()) { - handle->second->cancel(); - } - } - - // 取消所有任务 - void cancel_all() { - // ... 清理所有队列和运行中的任务 - } -}; -``` - -### 错误处理和恢复 - -| 错误类型 | 处理策略 | -|----------|----------| -| 字体加载失败 | 标记为 `failed`,使用fallback字体 | -| MTSDF生成失败 | 重试一次,仍失败则使用占位符 | -| 图集空间不足 | 触发图集扩展或创建新图集 | -| Worker崩溃 | 重启Worker,重新调度未完成任务 | - -```cpp -enum class glyph_error { - none, - font_not_found, - glyph_not_found, - generation_failed, - atlas_full -}; - -struct glyph_result { - glyph_state state; - glyph_error error{glyph_error::none}; - uint32_t glyph_id{0}; -}; -``` - ---- - -## 接口定义 - -### async_mtsdf_generator - -```cpp -namespace mirage::render::text { - -/// @brief 异步MTSDF生成器 - 管理后台线程池执行MTSDF生成 -class async_mtsdf_generator { -public: - /// @brief 生成任务结果 - using result_type = mtsdf_generator::glyph_bitmap; - - /// @brief 任务句柄 - using task_handle = threading::future_chain; - - /// @brief 配置参数 - struct config { - uint32_t worker_count{std::thread::hardware_concurrency()}; - uint32_t default_glyph_size{64}; - double default_pixel_range{6.0}; - std::chrono::milliseconds task_timeout{5000}; - }; - - /// @brief 构造函数 - /// @param cfg 配置参数 - explicit async_mtsdf_generator(config cfg = {}); - - /// @brief 析构函数 - 停止所有Worker并等待完成 - ~async_mtsdf_generator(); - - // 禁止拷贝和移动 - async_mtsdf_generator(const async_mtsdf_generator&) = delete; - async_mtsdf_generator& operator=(const async_mtsdf_generator&) = delete; - async_mtsdf_generator(async_mtsdf_generator&&) = delete; - async_mtsdf_generator& operator=(async_mtsdf_generator&&) = delete; - - /// @brief 提交MTSDF生成任务 - /// @param font_path 字体文件路径 - /// @param glyph_index 字形索引 - /// @param glyph_size 输出纹理尺寸 - /// @param pixel_range SDF像素范围 - /// @return 异步任务句柄 - [[nodiscard]] auto submit( - const std::filesystem::path& font_path, - uint32_t glyph_index, - uint32_t glyph_size = 0, // 0 = 使用默认值 - double pixel_range = 0.0 // 0 = 使用默认值 - ) -> task_handle; - - /// @brief 批量提交任务 - /// @param requests 任务请求列表 - /// @return 任务句柄列表 - [[nodiscard]] auto submit_batch( - std::span requests - ) -> std::vector; - - /// @brief 取消任务 - /// @param task_id 任务ID - void cancel(uint64_t task_id); - - /// @brief 取消所有待处理任务 - void cancel_all_pending(); - - /// @brief 获取待处理任务数量 - [[nodiscard]] auto pending_count() const noexcept -> size_t; - - /// @brief 获取Worker数量 - [[nodiscard]] auto worker_count() const noexcept -> size_t; - -private: - struct impl; - std::unique_ptr pimpl_; -}; - -/// @brief 字形请求结构 -struct glyph_request { - std::filesystem::path font_path; - uint32_t glyph_index; - uint32_t glyph_size{64}; - double pixel_range{6.0}; -}; - -} // namespace mirage::render::text -``` - -### mtsdf_worker - -```cpp -namespace mirage::render::text { - -/// @brief MTSDF Worker - 后台线程的执行上下文 -class mtsdf_worker { -public: - /// @brief 构造Worker - /// @param worker_id 唯一Worker ID - explicit mtsdf_worker(uint32_t worker_id); - - ~mtsdf_worker(); - - // 禁止拷贝和移动 - mtsdf_worker(const mtsdf_worker&) = delete; - mtsdf_worker& operator=(const mtsdf_worker&) = delete; - - /// @brief 启动Worker线程 - void start(); - - /// @brief 停止Worker线程 - void stop(); - - /// @brief 提交任务到此Worker - /// @param task 任务数据 - void submit(mtsdf_task task); - - /// @brief 检查Worker是否空闲 - [[nodiscard]] auto is_idle() const noexcept -> bool; - - /// @brief 获取待处理任务数 - [[nodiscard]] auto queue_size() const noexcept -> size_t; - -private: - /// @brief Worker线程主循环 - void run(); - - /// @brief 执行单个任务 - /// @param task 任务数据 - void execute_task(mtsdf_task& task); - - /// @brief 加载字体(带缓存) - /// @param path 字体路径 - /// @return FT_Face 或 nullptr - auto load_font_cached(const std::filesystem::path& path) -> FT_Face; - - uint32_t worker_id_; - std::jthread thread_; - std::atomic running_{false}; - std::atomic idle_{true}; - - // 每个Worker独立的FreeType资源 - FT_Library ft_library_{nullptr}; - std::unordered_map font_cache_; - - // 任务队列 - threading::mpsc_queue task_queue_; -}; - -} // namespace mirage::render::text -``` - -### glyph_cache 改进 - -```cpp -namespace mirage::render::text { - -/// @brief 字形状态 -enum class glyph_state : uint8_t { - ready, ///< 已生成,可使用 - pending, ///< 生成中 - failed ///< 生成失败 -}; - -/// @brief 字形缓存 - 支持异步生成的字形管理 -class glyph_cache { -public: - /// @brief 异步查询结果 - struct async_result { - glyph_state state; - const font_atlas::glyph_rect* rect; // ready时有效 - uint64_t task_id; // pending时有效 - }; - - /// @brief 构造字形缓存 - /// @param font_mgr 字体管理器(用于同步路径) - /// @param async_gen 异步生成器(用于异步路径) - /// @param atlas_size 图集大小 - /// @param glyph_size MTSDF纹理大小 - explicit glyph_cache( - font_manager& font_mgr, - async_mtsdf_generator& async_gen, - uint32_t atlas_size = 2048, - uint32_t glyph_size = 64 - ); - - /// @brief 同步获取或创建字形(阻塞) - /// @param font 字体ID - /// @param codepoint Unicode码点 - /// @return 字形矩形指针,失败返回nullptr - [[nodiscard]] auto get_or_create_glyph( - font_manager::font_id_t font, - char32_t codepoint - ) -> const font_atlas::glyph_rect*; - - /// @brief 异步获取或创建字形(非阻塞) - /// @param font 字体ID - /// @param codepoint Unicode码点 - /// @return 异步查询结果 - [[nodiscard]] auto get_or_create_glyph_async( - font_manager::font_id_t font, - char32_t codepoint - ) -> async_result; - - /// @brief 预加载字形(异步) - /// @param font 字体ID - /// @param codepoints 要预加载的码点列表 - void preload_glyphs( - font_manager::font_id_t font, - std::span codepoints - ); - - /// @brief 处理完成的异步任务 - /// 应在主线程每帧调用一次 - void process_completed_tasks(); - - /// @brief 获取指定任务的状态 - /// @param task_id 任务ID - /// @return 字形状态 - [[nodiscard]] auto get_task_state(uint64_t task_id) const -> glyph_state; - - /// @brief 取消待处理的任务 - /// @param task_id 任务ID - void cancel_task(uint64_t task_id); - - /// @brief 获取图集引用 - [[nodiscard]] auto get_atlas() -> font_atlas&; - [[nodiscard]] auto get_atlas() const -> const font_atlas&; - - /// @brief 获取字形纹理大小 - [[nodiscard]] auto get_glyph_size() const -> uint32_t; - - /// @brief 获取待处理任务数 - [[nodiscard]] auto pending_count() const -> size_t; - -private: - struct cache_entry { - glyph_state state{glyph_state::pending}; - uint32_t glyph_id{0}; - uint64_t task_id{0}; - }; - - struct cache_key { - font_manager::font_id_t font; - uint32_t glyph_index; - auto operator==(const cache_key&) const -> bool = default; - }; - - struct cache_key_hash { - auto operator()(const cache_key& k) const -> size_t { - return (static_cast(k.font) << 32) | k.glyph_index; - } - }; - - font_manager& font_mgr_; - async_mtsdf_generator& async_gen_; - font_atlas atlas_; - uint32_t glyph_size_; - - std::unordered_map cache_; - std::unordered_map task_to_key_; - - mutable std::shared_mutex cache_mutex_; - uint32_t next_glyph_id_{1}; - uint64_t next_task_id_{1}; -}; - -} // namespace mirage::render::text -``` - -### font_atlas 改进 - -```cpp -namespace mirage::render::text { - -/// @brief 字体图集 - 线程安全版本 -class font_atlas { -public: - /// @brief 脏区域信息 - struct dirty_region { - uint32_t x, y; - uint32_t width, height; - }; - - explicit font_atlas(uint32_t atlas_size = 2048); - - /// @brief 添加字形(线程安全) - /// @param glyph_id 字形ID - /// @param bitmap MTSDF位图 - /// @return 成功返回true - [[nodiscard]] auto add_glyph( - uint32_t glyph_id, - const mtsdf_generator::glyph_bitmap& bitmap - ) -> bool; - - /// @brief 获取字形矩形(线程安全读取) - /// @param glyph_id 字形ID - /// @return 字形矩形指针 - [[nodiscard]] auto get_glyph_rect(uint32_t glyph_id) const -> const glyph_rect*; - - /// @brief 获取图集数据 - [[nodiscard]] auto get_atlas_data() const -> const std::vector&; - - /// @brief 获取图集尺寸 - [[nodiscard]] auto get_atlas_size() const -> uint32_t; - - /// @brief 检查是否有待上传的更新 - [[nodiscard]] auto is_dirty() const -> bool; - - /// @brief 获取所有脏区域 - [[nodiscard]] auto get_dirty_regions() const -> std::vector; - - /// @brief 清除脏标志和脏区域 - void clear_dirty_flag(); - - /// @brief 获取总脏区域的包围盒 - /// 用于优化单次大范围上传 - [[nodiscard]] auto get_dirty_bounds() const -> std::optional; - -private: - // ... 现有私有成员 ... - - mutable std::mutex mutex_; ///< 保护图集修改 - std::vector dirty_regions_; ///< 脏区域列表 -}; - -} // namespace mirage::render::text -``` - -### texture_upload_manager - -```cpp -namespace mirage::render::text { - -/// @brief 纹理上传管理器 - 批量处理字形纹理上传 -class texture_upload_manager { -public: - /// @brief 构造函数 - /// @param atlas 字体图集引用 - /// @param tex_mgr 纹理管理器引用 - explicit texture_upload_manager( - font_atlas& atlas, - vulkan::texture_manager& tex_mgr - ); - - /// @brief 处理待上传的更新 - /// 应在渲染线程帧开始时调用 - void flush_pending_uploads(); - - /// @brief 强制完整上传图集 - void force_full_upload(); - - /// @brief 获取图集纹理句柄 - [[nodiscard]] auto get_atlas_texture() const -> VkImageView; - - /// @brief 检查是否需要上传 - [[nodiscard]] auto has_pending_uploads() const -> bool; - -private: - font_atlas& atlas_; - vulkan::texture_manager& tex_mgr_; - - // Staging buffer for efficient uploads - std::unique_ptr> staging_buffer_; - - // 图集纹理资源 - VkImage atlas_image_{VK_NULL_HANDLE}; - VkImageView atlas_view_{VK_NULL_HANDLE}; - VmaAllocation atlas_allocation_{VK_NULL_HANDLE}; -}; - -} // namespace mirage::render::text -``` - ---- - -## 使用示例 - -### 基本使用 - -```cpp -// 初始化 -async_mtsdf_generator::config cfg{ - .worker_count = 4, - .default_glyph_size = 64, - .default_pixel_range = 6.0 -}; -async_mtsdf_generator async_gen{cfg}; - -glyph_cache cache{font_mgr, async_gen}; - -// 异步请求字形 -auto result = cache.get_or_create_glyph_async(font_id, U'A'); - -switch (result.state) { - case glyph_state::ready: - // 使用 result.rect 渲染 - break; - case glyph_state::pending: - // 跳过或使用占位符 - break; - case glyph_state::failed: - // 使用 fallback - break; -} - -// 每帧处理完成的任务 -void on_frame_begin() { - cache.process_completed_tasks(); - upload_manager.flush_pending_uploads(); -} -``` - -### 预加载字形 - -```cpp -// 预加载常用字符 -std::vector ascii_chars; -for (char32_t c = 0x20; c < 0x7F; ++c) { - ascii_chars.push_back(c); -} - -cache.preload_glyphs(font_id, ascii_chars); -``` - -### 与 future_chain 集成 - -```cpp -// 直接使用底层异步API -auto future = async_gen.submit(font_path, glyph_index); - -future - .then([&cache, key](mtsdf_generator::glyph_bitmap bitmap) { - // 生成成功,添加到图集 - cache.finalize_glyph(key, std::move(bitmap)); - }) - .on_error([key](threading::async_error err) { - // 生成失败,标记为failed - spdlog::error("MTSDF generation failed for glyph {}", key.glyph_index); - }) - .finally([] { - // 清理资源 - }); -``` - ---- - -## 性能考量 - -### 预期收益 - -| 场景 | 当前行为 | 优化后行为 | -|------|----------|------------| -| 首次渲染新字符 | 阻塞50-200ms | 异步,无阻塞 | -| 大量新字符 | 明显卡顿 | 渐进显示 | -| 后续帧 | 无影响 | 无影响 | - -### 内存开销 - -- 每个Worker: ~10MB(FreeType + 字体缓存) -- 4个Worker: ~40MB额外内存 -- 任务队列: 可忽略 - -### 调优建议 - -1. **Worker数量**: 建议设为 `CPU核心数 / 2`,避免与渲染竞争 -2. **任务超时**: 5秒足够处理复杂字形 -3. **批量预加载**: 场景加载时预加载预期字符集 - ---- - -## 实现路线图 - -### 阶段1: 基础异步框架 -- [ ] 实现 `mtsdf_worker` 类 -- [ ] 实现 `async_mtsdf_generator` 线程池 -- [ ] 单元测试验证基本功能 - -### 阶段2: 缓存集成 -- [ ] 扩展 `glyph_cache` 支持异步 -- [ ] 实现占位符机制 -- [ ] 添加线程安全保护 - -### 阶段3: 渲染集成 -- [ ] 实现 `texture_upload_manager` -- [ ] 集成到渲染管线 -- [ ] 端到端测试 - -### 阶段4: 优化 -- [ ] 任务优先级支持 -- [ ] Work-stealing调度 -- [ ] 性能分析和调优 \ No newline at end of file diff --git a/docs/EVENT_RESULT_REFACTORING.md b/docs/EVENT_RESULT_REFACTORING.md deleted file mode 100644 index 361488e..0000000 --- a/docs/EVENT_RESULT_REFACTORING.md +++ /dev/null @@ -1,764 +0,0 @@ -# 事件返回值重构设计文档 - -## 1. 概述 - -### 1.1 背景 - -当前事件系统使用 `void` 返回值,通过事件结构体中的 `mutable bool handled` 字段和手动调用 `router->capture_mouse(this)` 来控制事件行为。这种设计存在以下问题: - -1. **隐式状态修改**:事件处理的副作用(handled 标志、鼠标捕获)散布在代码各处 -2. **控件与路由器耦合**:控件需要获取 `event_router` 指针来捕获鼠标 -3. **意图不明确**:需要阅读函数体才能知道事件是否被处理 -4. **可组合性差**:难以在外部组合多个处理器的结果 - -### 1.2 设计目标 - -将事件处理的控制逻辑从"事件对象可变状态 + 手动调用 router"改为"函数返回值语义": - -- **显式返回值**:通过返回值明确表达处理意图 -- **解耦**:控件不再直接操作路由器,而是声明意图 -- **可组合**:返回值可以被外部代码检查和组合 -- **类型安全**:编译器强制处理返回值 - ---- - -## 2. 设计决策 - -### 2.1 统一的 event_result 类型 - -**决策**:所有事件类型共用一个 `event_result` 结构体。 - -**理由**: -- 减少类型数量,代码更简洁 -- 便于未来扩展新的标志位 -- 使用 C++20 指定初始化语法保持可读性 -- 结构体足够小(~1 字节),值传递无性能问题 - -**替代方案(已否决)**: -- 为每种事件类型定义独立返回值类型(`mouse_event_result`、`keyboard_event_result` 等) -- 否决原因:增加类型数量,且大多数标志位是通用的 - -### 2.2 使用独立布尔字段而非位标志 - -**决策**:使用独立的 `bool` 字段而非 `uint8_t` 位标志。 - -**理由**: -- 代码可读性更好:`result.handled` vs `result.flags & EVENT_HANDLED` -- 编译器通常会优化为相同的内存布局 -- C++20 的指定初始化语法与布尔字段配合更自然 - ---- - -## 3. 类型定义 - -### 3.1 event_result 结构体 - -```cpp -// 文件:src/widget/widget_event/event_result.h - -#pragma once - -namespace mirage { - -/// @brief 事件处理结果 -/// -/// 控件事件处理方法的返回值,用于向事件路由器传达处理意图。 -/// 路由器根据返回值执行相应操作(停止冒泡、捕获鼠标等)。 -/// -/// @note 使用静态工厂方法创建常用的结果对象 -struct event_result { - /// 事件是否已处理(停止冒泡) - bool handled{false}; - - /// 请求捕获鼠标(用于拖拽场景) - bool capture_mouse{false}; - - /// 请求释放鼠标捕获 - bool release_mouse_capture{false}; - - // ======================================================================== - // 静态工厂方法 - // ======================================================================== - - /// @brief 创建未处理的结果 - /// @details 事件将继续冒泡到父控件 - [[nodiscard]] static constexpr event_result unhandled() noexcept { - return {}; - } - - /// @brief 创建已处理的结果 - /// @details 停止事件冒泡,但不执行其他操作 - [[nodiscard]] static constexpr event_result handled() noexcept { - return {.handled = true}; - } - - /// @brief 创建捕获鼠标的结果 - /// @details 停止冒泡并请求路由器捕获鼠标到当前控件 - [[nodiscard]] static constexpr event_result capture() noexcept { - return { - .handled = true, - .capture_mouse = true - }; - } - - /// @brief 创建释放鼠标捕获的结果 - /// @details 停止冒泡并请求路由器释放鼠标捕获 - [[nodiscard]] static constexpr event_result release() noexcept { - return { - .handled = true, - .release_mouse_capture = true - }; - } - - // ======================================================================== - // 组合操作 - // ======================================================================== - - /// @brief 合并两个结果 - /// @details OR 语义:任一标志为 true 则结果为 true - [[nodiscard]] constexpr event_result merge_with(const event_result& other) const noexcept { - return { - .handled = handled || other.handled, - .capture_mouse = capture_mouse || other.capture_mouse, - .release_mouse_capture = release_mouse_capture || other.release_mouse_capture - }; - } - - /// @brief 检查是否有任何效果 - [[nodiscard]] constexpr bool has_effect() const noexcept { - return handled || capture_mouse || release_mouse_capture; - } -}; - -} // namespace mirage -``` - -### 3.2 内存布局分析 - -```cpp -// event_result 的预期内存布局 -// -// 字段布局(假设无填充优化): -// - handled: 1 byte -// - capture_mouse: 1 byte -// - release_mouse_capture: 1 byte -// 总计:3 bytes(可能填充到 4 bytes) -// -// 如果使用 bitfield 压缩: -// struct event_result { -// bool handled : 1; -// bool capture_mouse : 1; -// bool release_mouse_capture : 1; -// }; -// 总计:1 byte -// -// 考虑到现代 CPU 的寄存器传递能力, -// 3-4 bytes 的值类型传递成本可忽略不计。 -``` - ---- - -## 4. 事件处理器接口 - -### 4.1 新的签名 - -```cpp -// 文件:src/widget/widget_event/event_target.h - -class event_target { -public: - // ======================================================================== - // 键盘事件处理(返回 event_result) - // ======================================================================== - - /// @brief 键按下事件 - /// @param event 键盘事件对象(只读) - /// @return 事件处理结果 - [[nodiscard]] virtual event_result on_key_down(const keyboard_event& event) { - (void)event; - return event_result::unhandled(); - } - - /// @brief 键释放事件 - [[nodiscard]] virtual event_result on_key_up(const keyboard_event& event) { - (void)event; - return event_result::unhandled(); - } - - /// @brief 字符输入事件 - [[nodiscard]] virtual event_result on_char_input(const char_event& event) { - (void)event; - return event_result::unhandled(); - } - - // ======================================================================== - // 鼠标事件处理(返回 event_result) - // ======================================================================== - - /// @brief 鼠标按下事件 - [[nodiscard]] virtual event_result on_mouse_down(const mouse_event& event) { - (void)event; - return event_result::unhandled(); - } - - /// @brief 鼠标释放事件 - [[nodiscard]] virtual event_result on_mouse_up(const mouse_event& event) { - (void)event; - return event_result::unhandled(); - } - - /// @brief 鼠标移动事件 - [[nodiscard]] virtual event_result on_mouse_move(const mouse_event& event) { - (void)event; - return event_result::unhandled(); - } - - /// @brief 鼠标滚轮事件 - [[nodiscard]] virtual event_result on_mouse_scroll(const mouse_event& event) { - (void)event; - return event_result::unhandled(); - } - - // ... 其他方法保持不变 -}; -``` - -### 4.2 事件结构体修改 - -从事件结构体中移除 `handled` 字段和 `stop_propagation()` 方法: - -```cpp -// 文件:src/widget/widget_event/event_types.h - -struct widget_keyboard_event { - key_action action{key_action::press}; - key_code key{key_code::UNKNOWN}; - int32_t scancode{0}; - key_mod modifiers{key_mod::NONE}; - bool repeat{false}; - - // 移除以下内容: - // mutable bool handled{false}; - // void stop_propagation() const noexcept { handled = true; } - - // 辅助方法保持不变 - [[nodiscard]] bool has_modifier(key_mod mod) const noexcept; - [[nodiscard]] bool ctrl() const noexcept; - [[nodiscard]] bool shift() const noexcept; - [[nodiscard]] bool alt() const noexcept; - [[nodiscard]] bool super() const noexcept; -}; - -struct widget_mouse_event { - double global_x{0.0}; - double global_y{0.0}; - mouse_button button{mouse_button::LEFT}; - key_mod modifiers{key_mod::NONE}; - double scroll_x{0.0}; - double scroll_y{0.0}; - - // 移除以下内容: - // mutable bool handled{false}; - // void stop_propagation() const noexcept { handled = true; } -}; - -struct widget_char_event { - uint32_t codepoint{0}; - key_mod modifiers{key_mod::NONE}; - - // 移除以下内容: - // mutable bool handled{false}; - // void stop_propagation() const noexcept { handled = true; } -}; -``` - ---- - -## 5. 路由器修改 - -### 5.1 鼠标事件处理 - -```cpp -// 文件:src/widget/widget_event/widget_event_router.cpp - -void widget_event_router::handle_mouse_event( - event_type type, - double x, double y, - mouse_button button, - key_mod mods, - double scroll_x, - double scroll_y -) { - if (!root_target_) return; - - // 构建事件对象(不再包含 handled 字段) - mouse_event event{ - .global_x = x, - .global_y = y, - .button = button, - .modifiers = mods, - .scroll_x = scroll_x, - .scroll_y = scroll_y - }; - - // 处理鼠标捕获 - if (mouse_capture_) { - if (mouse_capture_->is_event_enabled()) { - auto result = dispatch_to_captured_target(type, event); - process_event_result(result, mouse_capture_); - return; - } - mouse_capture_ = nullptr; - } - - // 命中测试 - event_target* hit_target = hit_test(root_target_, x, y); - if (!hit_target) return; - - // 分发事件并处理返回值 - auto result = dispatch_mouse_event(type, hit_target, event); - process_event_result(result, hit_target); -} - -// 新增:处理事件结果的私有方法 -void widget_event_router::process_event_result( - const event_result& result, - event_target* target -) { - if (result.capture_mouse && target) { - mouse_capture_ = target; - } - if (result.release_mouse_capture) { - mouse_capture_ = nullptr; - } - // 注意:handled 标志在冒泡循环中处理 -} - -// 新增:分发鼠标事件的私有方法 -event_result widget_event_router::dispatch_mouse_event( - event_type type, - event_target* target, - const mouse_event& event -) { - switch (type) { - case event_type::MOUSE_BUTTON_PRESSED: - // 处理自动聚焦 - if (event.button == mouse_button::LEFT && target->is_focusable()) { - focus_manager_.set_focus(target, focus_change_reason::mouse_click); - } - return dispatch_with_bubbling(target, [&](event_target* t) { - return t->on_mouse_down(event); - }); - - case event_type::MOUSE_BUTTON_RELEASED: - return dispatch_with_bubbling(target, [&](event_target* t) { - return t->on_mouse_up(event); - }); - - case event_type::MOUSE_MOVED: - return dispatch_with_bubbling(target, [&](event_target* t) { - return t->on_mouse_move(event); - }); - - case event_type::MOUSE_SCROLLED: - return dispatch_with_bubbling(target, [&](event_target* t) { - return t->on_mouse_scroll(event); - }); - - default: - return event_result::unhandled(); - } -} -``` - -### 5.2 通用冒泡分发模板 - -```cpp -// 文件:src/widget/widget_event/widget_event_router.h(私有方法声明) -// 文件:src/widget/widget_event/widget_event_router.cpp(实现) - -/// @brief 带冒泡的事件分发 -/// @tparam Handler 事件处理函数类型 -/// @param target 起始目标控件 -/// @param handler 处理函数 (event_target*) -> event_result -/// @return 最终的事件处理结果 -template -event_result widget_event_router::dispatch_with_bubbling( - event_target* target, - Handler&& handler -) { - event_result final_result = event_result::unhandled(); - - for (auto* current = target; current != nullptr; current = current->get_parent_target()) { - if (!current->is_event_enabled()) { - continue; - } - - auto result = handler(current); - final_result = final_result.merge_with(result); - - // 如果已处理,停止冒泡 - if (result.handled) { - break; - } - } - - return final_result; -} -``` - -### 5.3 键盘事件处理修改 - -```cpp -void widget_event_router::dispatch_keyboard_event(const keyboard_event& event) { - const auto& chain = focus_manager_.get_focus_chain(); - if (chain.empty()) return; - - // 从焦点控件开始向上冒泡 - for (auto* target : chain | std::views::reverse) { - if (!target || !target->is_event_enabled()) { - continue; - } - - event_result result; - switch (event.action) { - case key_action::press: - case key_action::repeat: - result = target->on_key_down(event); - break; - case key_action::release: - result = target->on_key_up(event); - break; - } - - if (result.handled) { - break; - } - } -} -``` - ---- - -## 6. 代码对比示例 - -### 6.1 Scrollbar 控件:Before - -```cpp -// 当前实现 (scrollbar.cpp) - -void scrollbar::on_mouse_down(mouse_event& event) { - if (event.handled) return; // 检查是否已处理 - - auto local_pos = global_to_local(event.global_x, event.global_y); - float local_x = static_cast(local_pos.first); - float local_y = static_cast(local_pos.second); - - if (is_point_in_thumb(local_x, local_y)) { - is_dragging_thumb_ = true; - is_thumb_active_ = true; - drag_start_pos_ = vec2f_t{local_x, local_y}; - drag_start_value_ = current_value_; - - // 手动获取路由器并捕获鼠标 - if (auto* router = get_event_router()) { - router->capture_mouse(this); - } - - if (drag_state_changed_callback_) { - drag_state_changed_callback_(true); - } - - event.handled = true; // 设置已处理标志 - } -} - -void scrollbar::on_mouse_up(mouse_event& event) { - if (event.handled) return; - - if (is_dragging_thumb_) { - is_dragging_thumb_ = false; - is_thumb_active_ = false; - - // 手动获取路由器并释放捕获 - if (auto* router = get_event_router()) { - router->release_mouse_capture(); - } - - if (drag_state_changed_callback_) { - drag_state_changed_callback_(false); - } - - event.handled = true; - } -} -``` - -### 6.2 Scrollbar 控件:After - -```cpp -// 重构后的实现 (scrollbar.cpp) - -event_result scrollbar::on_mouse_down(const mouse_event& event) { - auto local_pos = global_to_local(event.global_x, event.global_y); - float local_x = static_cast(local_pos.first); - float local_y = static_cast(local_pos.second); - - if (is_point_in_thumb(local_x, local_y)) { - is_dragging_thumb_ = true; - is_thumb_active_ = true; - drag_start_pos_ = vec2f_t{local_x, local_y}; - drag_start_value_ = current_value_; - - if (drag_state_changed_callback_) { - drag_state_changed_callback_(true); - } - - // 返回值明确表达意图:已处理 + 捕获鼠标 - return event_result::capture(); - } - - return event_result::unhandled(); -} - -event_result scrollbar::on_mouse_up(const mouse_event& event) { - if (is_dragging_thumb_) { - is_dragging_thumb_ = false; - is_thumb_active_ = false; - - if (drag_state_changed_callback_) { - drag_state_changed_callback_(false); - } - - // 返回值明确表达意图:已处理 + 释放捕获 - return event_result::release(); - } - - return event_result::unhandled(); -} - -event_result scrollbar::on_mouse_move(const mouse_event& event) { - auto local_pos = global_to_local(event.global_x, event.global_y); - float local_x = static_cast(local_pos.first); - float local_y = static_cast(local_pos.second); - - if (is_dragging_thumb_) { - handle_thumb_drag(local_x, local_y); - - if (drag_state_changed_callback_) { - drag_state_changed_callback_(true); - } - - return event_result::handled(); - } - - bool was_hovered = is_thumb_hovered_; - is_thumb_hovered_ = is_point_in_thumb(local_x, local_y); - - if (was_hovered != is_thumb_hovered_) { - mark_render_dirty(); - } - - // hover 状态变化不消耗事件 - return event_result::unhandled(); -} -``` - -### 6.3 改进点总结 - -| 方面 | Before | After | -|------|--------|-------| -| 事件处理检查 | `if (event.handled) return;` | 不需要,由路由器管理 | -| 标记已处理 | `event.handled = true;` | `return event_result::handled();` | -| 鼠标捕获 | `router->capture_mouse(this);` | `return event_result::capture();` | -| 鼠标释放 | `router->release_mouse_capture();` | `return event_result::release();` | -| 意图表达 | 散布在函数体内 | 集中在返回值 | -| 与路由器耦合 | 需要 `get_event_router()` | 无直接依赖 | - ---- - -## 7. 迁移指南 - -### 7.1 迁移步骤 - -#### 步骤 1:创建 event_result.h - -在 `src/widget/widget_event/` 下创建 `event_result.h`,包含 `event_result` 结构体定义。 - -#### 步骤 2:修改 event_types.h - -1. 从所有事件结构体中移除 `mutable bool handled` 字段 -2. 移除 `stop_propagation()` 方法 -3. 在文件头部包含 `event_result.h` - -#### 步骤 3:修改 event_target.h - -1. 包含 `event_result.h` -2. 修改所有事件处理方法签名: - - 返回类型从 `void` 改为 `event_result` - - 参数从 `T&` 改为 `const T&` - - 添加 `[[nodiscard]]` 属性 -3. 修改默认实现返回 `event_result::unhandled()` - -#### 步骤 4:修改 widget_event_router.cpp - -1. 修改事件分发逻辑,检查返回值 -2. 实现 `process_event_result()` 方法 -3. 更新冒泡逻辑使用返回值的 `handled` 标志 - -#### 步骤 5:批量更新控件 - -使用搜索替换更新所有控件的事件处理方法。 - -### 7.2 搜索替换模式 - -``` -# 查找带 handled 检查的模式 -搜索:if \(event\.handled\) return; -替换:(删除整行) - -# 查找 event.handled = true -搜索:event\.handled = true; -替换:return event_result::handled(); - -# 查找 event.stop_propagation() -搜索:event\.stop_propagation\(\); -替换:return event_result::handled(); - -# 查找 router->capture_mouse(this) -搜索:if \(auto\* router = get_event_router\(\)\) \{\s*router->capture_mouse\(this\);\s*\} -替换:// 在返回时使用 event_result::capture() - -# 查找函数签名 -搜索:void on_mouse_down\(mouse_event& event\) -替换:event_result on_mouse_down(const mouse_event& event) -``` - -### 7.3 编译时检查 - -重构完成后,编译器会自动报告: -- 未更新的事件处理方法签名(参数类型不匹配) -- 仍在访问已删除的 `handled` 字段 -- 返回值未返回 `event_result` - ---- - -## 8. 测试计划 - -### 8.1 单元测试 - -```cpp -// 测试 event_result 工厂方法 -TEST_CASE("event_result factory methods") { - SECTION("unhandled") { - auto r = event_result::unhandled(); - CHECK(!r.handled); - CHECK(!r.capture_mouse); - CHECK(!r.release_mouse_capture); - } - - SECTION("handled") { - auto r = event_result::handled(); - CHECK(r.handled); - CHECK(!r.capture_mouse); - } - - SECTION("capture") { - auto r = event_result::capture(); - CHECK(r.handled); - CHECK(r.capture_mouse); - } - - SECTION("release") { - auto r = event_result::release(); - CHECK(r.handled); - CHECK(r.release_mouse_capture); - } -} - -// 测试 merge 行为 -TEST_CASE("event_result merge") { - auto a = event_result::handled(); - auto b = event_result{.capture_mouse = true}; - auto merged = a.merge_with(b); - - CHECK(merged.handled); - CHECK(merged.capture_mouse); -} -``` - -### 8.2 集成测试 - -1. **拖拽测试**:验证 scrollbar 拖拽仍能正确捕获/释放鼠标 -2. **冒泡测试**:验证事件在嵌套控件中正确冒泡 -3. **焦点测试**:验证鼠标点击仍能正确设置焦点 - ---- - -## 9. 未来扩展 - -### 9.1 可能的新标志位 - -```cpp -struct event_result { - bool handled{false}; - bool capture_mouse{false}; - bool release_mouse_capture{false}; - - // 未来可能添加: - // bool request_focus{false}; // 请求焦点 - // bool prevent_default{false}; // 阻止默认行为 - // bool stop_immediate{false}; // 立即停止,不传给同级处理器 - // cursor_type cursor{cursor_type::default}; // 请求改变光标 -}; -``` - -### 9.2 链式处理 - -```cpp -// 未来可能支持链式处理器 -auto result = handlers - | handle_if_focused() - | handle_shortcuts() - | handle_default(); -``` - ---- - -## 10. 文件变更清单 - -| 文件路径 | 操作 | 说明 | -|---------|------|------| -| `src/widget/widget_event/event_result.h` | 新建 | event_result 结构体定义 | -| `src/widget/widget_event/event_types.h` | 修改 | 移除 handled 字段和 stop_propagation | -| `src/widget/widget_event/event_target.h` | 修改 | 修改方法签名 | -| `src/widget/widget_event/widget_event_router.h` | 修改 | 添加私有方法声明 | -| `src/widget/widget_event/widget_event_router.cpp` | 修改 | 重写事件分发逻辑 | -| `src/widget/widgets/scrollbar.cpp` | 修改 | 更新事件处理实现 | -| `src/widget/widgets/*.cpp` | 修改 | 更新所有控件的事件处理 | - ---- - -## 11. 实现顺序 - -```mermaid -flowchart TD - A[1. 创建 event_result.h] --> B[2. 修改 event_types.h] - B --> C[3. 修改 event_target.h] - C --> D[4. 修改 widget_event_router] - D --> E[5. 更新 scrollbar] - E --> F[6. 更新其他控件] - F --> G[7. 编写测试] - G --> H[8. 代码审查] -``` - ---- - -## 附录 A:完整 event_result.h 源码 - -参见第 3.1 节。 - -## 附录 B:相关设计文档 - -- [`KEYBOARD_EVENT_SYSTEM.md`](./KEYBOARD_EVENT_SYSTEM.md) - 键盘事件系统设计 -- [`WIDGET_THREAD_SYNC_REFACTORING_PLAN.md`](./WIDGET_THREAD_SYNC_REFACTORING_PLAN.md) - 控件线程同步重构 \ No newline at end of file diff --git a/docs/EVENT_TARGET_ELIMINATION_PLAN.md b/docs/EVENT_TARGET_ELIMINATION_PLAN.md deleted file mode 100644 index 2ea91aa..0000000 --- a/docs/EVENT_TARGET_ELIMINATION_PLAN.md +++ /dev/null @@ -1,664 +0,0 @@ -# Event Target 消除重构方案 - -## 1. 背景与问题 - -### 1.1 当前架构 - -当前代码中,`event_target` 是一个独立的接口类,控件需要同时继承 `widget_base`(或其派生类如 `container_widget_base`、`leaf_widget_base`)和 `event_target`。 - -```mermaid -classDiagram - class widget_base { - +get_parent() - +set_parent() - +as_event_target() - +get_parent_event_target() - } - - class event_target { - +on_mouse_down() - +on_mouse_scroll() - +get_parent_target() - +get_event_children() - +contains_point() - } - - class container_widget_base { - +add_child() - +get_event_children() - } - - class overlay { - 缺少 get_parent_target 实现 - } - - class scroll_box { - +on_mouse_scroll() - +get_parent_target() - } - - widget_base <|-- container_widget_base - container_widget_base <|-- overlay - container_widget_base <|-- scroll_box - event_target <|-- overlay - event_target <|-- scroll_box -``` - -### 1.2 问题描述 - -1. **事件冒泡链断裂**:`overlay` 控件继承了 `event_target`,但没有重写 [`get_parent_target()`](src/widget/widget_event/event_target.h:272) 方法。默认实现返回 `nullptr`,导致滚轮事件无法从子控件冒泡到父级 `scroll_box`。 - -2. **多重继承复杂性**:每个需要事件处理的控件都必须同时继承两个基类,增加了代码复杂度和出错可能性。 - -3. **可视性状态重复**: - - `widget_base` 通过外部状态存储管理 `visibility` - - `event_target` 有自己的 [`visibility_`](src/widget/widget_event/event_target.h:365) 成员变量 - - 这导致状态不一致的风险 - -4. **接口分散**:父子关系由 `widget_base` 管理(`parent_` 指针),但事件冒泡需要 `event_target::get_parent_target()`,两者需要手动保持同步。 - -### 1.3 问题根源 - -滚轮事件分发流程([`widget_event_router.cpp:377-400`](src/widget/widget_event/widget_event_router.cpp:377)): - -```cpp -void widget_event_router::dispatch_scroll_event(event_target* target, mouse_event& event) { - event_target* current = target; - while (current) { - if (current->is_event_enabled()) { - auto result = current->on_mouse_scroll(event); - if (result.is_handled) { - break; - } - } - // 问题在这里:如果 overlay 的 get_parent_target() 返回 nullptr - // 冒泡链就断裂了,scroll_box 永远收不到滚轮事件 - current = current->get_parent_target(); - } -} -``` - -## 2. 解决方案设计 - -### 2.1 核心思路 - -将 `event_target` 的所有接口方法整合到 `widget_base` 中,利用 `widget_base` 已有的 `parent_` 指针自动提供 `get_parent_target()` 的默认实现。 - -### 2.2 新的类层次结构 - -```mermaid -classDiagram - class widget_base { - #parent_: widget_base* - #event_router_: widget_event_router* - #visibility_: visibility - - +get_parent(): widget_base* - +set_parent(widget_base*) - - +on_key_down(keyboard_event): event_result - +on_key_up(keyboard_event): event_result - +on_char_input(char_event): event_result - - +on_mouse_down(mouse_event): event_result - +on_mouse_up(mouse_event): event_result - +on_mouse_move(mouse_event): event_result - +on_mouse_scroll(mouse_event): event_result - - +on_focus_gained(focus_change_reason) - +on_focus_lost(focus_change_reason) - - +on_ime_preedit_start(ime_preedit_event) - +on_ime_preedit_update(ime_preedit_event) - +on_ime_preedit_end(ime_preedit_event) - +on_ime_commit(ime_commit_event) - - +is_focusable(): bool - +get_tab_index(): int32_t - +get_parent_target(): widget_base* - +supports_ime(): bool - +get_ime_cursor_position(): tuple - +is_event_enabled(): bool - - +contains_point(x, y): bool - +global_to_local(x, y): pair - +get_event_children(): vector - - +set_event_router(router) - +get_event_router(): widget_event_router* - } - - class container_widget_base { - +get_event_children(): vector - } - - class leaf_widget_base { - } - - class overlay { - 继承后自动获得正确的 get_parent_target - } - - class scroll_box { - +on_mouse_scroll(): event_result - } - - widget_base <|-- container_widget_base - widget_base <|-- leaf_widget_base - container_widget_base <|-- overlay - container_widget_base <|-- scroll_box -``` - -### 2.3 新的 widget_base 接口设计 - -#### 2.3.1 事件处理方法(从 event_target 移入) - -```cpp -class widget_base : public std::enable_shared_from_this { -public: - // ======================================================================== - // 键盘事件处理(新增,从 event_target 移入) - // ======================================================================== - - [[nodiscard]] virtual event_result on_key_down(const keyboard_event& event) { - (void)event; - return event_result::unhandled(); - } - - [[nodiscard]] virtual event_result on_key_up(const keyboard_event& event) { - (void)event; - return event_result::unhandled(); - } - - [[nodiscard]] virtual event_result on_char_input(const char_event& event) { - (void)event; - return event_result::unhandled(); - } - - // ======================================================================== - // 鼠标事件处理(新增,从 event_target 移入) - // ======================================================================== - - [[nodiscard]] virtual event_result on_mouse_down(const mouse_event& event) { - (void)event; - return event_result::unhandled(); - } - - [[nodiscard]] virtual event_result on_mouse_up(const mouse_event& event) { - (void)event; - return event_result::unhandled(); - } - - [[nodiscard]] virtual event_result on_mouse_move(const mouse_event& event) { - (void)event; - return event_result::unhandled(); - } - - [[nodiscard]] virtual event_result on_mouse_scroll(const mouse_event& event) { - (void)event; - return event_result::unhandled(); - } - - // ======================================================================== - // 焦点事件处理(新增,从 event_target 移入) - // ======================================================================== - - virtual void on_focus_gained(focus_change_reason reason) { - (void)reason; - } - - virtual void on_focus_lost(focus_change_reason reason) { - (void)reason; - } - - // ======================================================================== - // IME 事件处理(新增,从 event_target 移入) - // ======================================================================== - - virtual void on_ime_preedit_start(ime_preedit_event& event) { - (void)event; - } - - virtual void on_ime_preedit_update(ime_preedit_event& event) { - (void)event; - } - - virtual void on_ime_preedit_end(ime_preedit_event& event) { - (void)event; - } - - virtual void on_ime_commit(ime_commit_event& event) { - (void)event; - } - - // ======================================================================== - // 焦点属性(新增,从 event_target 移入) - // ======================================================================== - - [[nodiscard]] virtual bool is_focusable() const { - return false; - } - - [[nodiscard]] virtual int32_t get_tab_index() const { - return -1; - } - - // ======================================================================== - // 事件冒泡支持(关键改进) - // ======================================================================== - - /// @brief 获取父事件目标(用于事件冒泡) - /// @return 父控件指针,自动使用 parent_ 成员 - /// @note 这是解决问题的关键:默认实现直接返回 parent_ - [[nodiscard]] virtual widget_base* get_parent_target() const { - return parent_; - } - - // ======================================================================== - // IME 支持(新增,从 event_target 移入) - // ======================================================================== - - [[nodiscard]] virtual bool supports_ime() const { - return false; - } - - [[nodiscard]] virtual std::tuple - get_ime_cursor_position() const { - return {0, 0, 20}; - } - - // ======================================================================== - // 事件启用状态(新增,从 event_target 移入) - // ======================================================================== - - [[nodiscard]] virtual bool is_event_enabled() const { - return true; - } - - // ======================================================================== - // 命中测试(新增,从 event_target 移入) - // ======================================================================== - - [[nodiscard]] virtual bool contains_point(double global_x, double global_y) const { - // 默认实现:使用布局状态检查 - auto state = get_layout_state(); - if (!state) return false; - return state->contains_global_point( - vec2f_t(static_cast(global_x), static_cast(global_y))); - } - - [[nodiscard]] virtual std::pair global_to_local( - double global_x, double global_y) const { - auto state = get_layout_state(); - if (!state) return {0.0, 0.0}; - auto local = state->to_local( - vec2f_t(static_cast(global_x), static_cast(global_y))); - return {local.x(), local.y()}; - } - - // ======================================================================== - // 事件子控件(新增,从 event_target 移入) - // ======================================================================== - - [[nodiscard]] virtual std::vector get_event_children() const { - return {}; - } - - // ======================================================================== - // 事件路由器(新增,从 event_target 移入) - // ======================================================================== - - void set_event_router(widget_event_router* router) noexcept { - event_router_ = router; - } - - [[nodiscard]] widget_event_router* get_event_router() const noexcept { - return event_router_; - } - -protected: - widget_event_router* event_router_{nullptr}; - -private: - // as_event_target() 和 get_parent_event_target() 可以删除 - // 因为 widget_base 自己就是事件目标 -}; -``` - -#### 2.3.2 需要删除的方法 - -从 `widget_base` 中删除以下方法,因为不再需要: - -```cpp -// 删除:widget_base 自己就是事件目标 -[[nodiscard]] virtual auto as_event_target() -> event_target* { ... } - -// 删除:被 get_parent_target() 替代 -[[nodiscard]] virtual auto get_parent_event_target() const -> event_target* { ... } -``` - -### 2.4 container_widget_base 的修改 - -```cpp -class container_widget_base : public widget_base { -public: - // ======================================================================== - // 事件子控件获取(重写基类方法) - // ======================================================================== - - /// @brief 获取事件子控件列表 - /// @return 返回 widget_base* 而不是 event_target* - [[nodiscard]] std::vector get_event_children() const override { - std::vector targets; - targets.reserve(children_.size()); - for (const auto& child : children_) { - targets.push_back(child.get()); - } - return targets; - } - - // 删除旧的返回 event_target* 的版本 -}; -``` - -### 2.5 事件系统的修改 - -#### 2.5.1 widget_event_router 修改 - -将所有 `event_target*` 替换为 `widget_base*`: - -```cpp -class widget_event_router { -public: - void set_root(widget_base* root) noexcept; - [[nodiscard]] widget_base* get_root() const noexcept; - - void capture_mouse(widget_base* target); - [[nodiscard]] widget_base* get_mouse_capture() const noexcept; - -private: - widget_base* root_target_{nullptr}; - widget_base* mouse_capture_{nullptr}; - - [[nodiscard]] widget_base* hit_test(widget_base* target, double x, double y); - void dispatch_scroll_event(widget_base* target, mouse_event& event); - void dispatch_mouse_event_to_target(widget_base* target, mouse_event& event); -}; -``` - -#### 2.5.2 focus_manager 修改 - -```cpp -class focus_manager { -public: - bool set_focus(widget_base* target, focus_change_reason reason); - [[nodiscard]] widget_base* get_focused_widget() const noexcept; - -private: - widget_base* focused_widget_{nullptr}; - std::vector focus_chain_; -}; -``` - -## 3. 需要修改的文件清单 - -### 3.1 核心文件(必须修改) - -| 文件 | 修改类型 | 说明 | -|------|----------|------| -| [`src/widget/widget_base.h`](src/widget/widget_base.h) | 重大修改 | 整合 event_target 所有接口 | -| [`src/widget/widget_event/event_target.h`](src/widget/widget_event/event_target.h) | **删除** | 迁移后删除整个文件 | -| [`src/widget/container_widget_base.h`](src/widget/container_widget_base.h) | 修改 | 更新 `get_event_children()` 返回类型 | -| [`src/widget/composite_widget_base.h`](src/widget/composite_widget_base.h) | 修改 | 删除 event_target 相关代码 | -| [`src/widget/leaf_widget_base.h`](src/widget/leaf_widget_base.h) | 小修改 | 确保兼容性 | - -### 3.2 事件系统文件(必须修改) - -| 文件 | 修改类型 | 说明 | -|------|----------|------| -| [`src/widget/widget_event/widget_event_router.h`](src/widget/widget_event/widget_event_router.h) | 修改 | `event_target*` → `widget_base*` | -| [`src/widget/widget_event/widget_event_router.cpp`](src/widget/widget_event/widget_event_router.cpp) | 修改 | 同上 | -| [`src/widget/widget_event/focus_manager.h`](src/widget/widget_event/focus_manager.h) | 修改 | `event_target*` → `widget_base*` | -| [`src/widget/widget_event/focus_manager.cpp`](src/widget/widget_event/focus_manager.cpp) | 修改 | 同上 | -| [`src/widget/widget_event/ime_manager.h`](src/widget/widget_event/ime_manager.h) | 修改 | 可能需要更新类型 | -| [`src/widget/widget_event/shortcut_manager.h`](src/widget/widget_event/shortcut_manager.h) | 检查 | 可能需要更新类型 | - -### 3.3 容器控件文件(删除多重继承) - -| 文件 | 修改类型 | 说明 | -|------|----------|------| -| [`src/widget/containers/overlay.h`](src/widget/containers/overlay.h) | 修改 | 删除 `: public event_target` | -| [`src/widget/containers/scroll_box/scroll_box.h`](src/widget/containers/scroll_box/scroll_box.h) | 修改 | 删除 `: public event_target` | -| [`src/widget/containers/v_stack.h`](src/widget/containers/v_stack.h) | 修改 | 删除 `: public event_target` | -| [`src/widget/containers/h_stack.h`](src/widget/containers/h_stack.h) | 修改 | 删除 `: public event_target` | - -### 3.4 叶子控件文件(删除多重继承) - -| 文件 | 修改类型 | 说明 | -|------|----------|------| -| [`src/widget/widgets/scrollbar.h`](src/widget/widgets/scrollbar.h) | 修改 | 删除 `: public event_target` | -| 其他叶子控件 | 检查 | 检查是否有其他继承 event_target 的控件 | - -### 3.5 其他可能受影响的文件 - -| 文件 | 修改类型 | 说明 | -|------|----------|------| -| `src/widget/CMakeLists.txt` | 可能修改 | 删除 event_target.h | -| `src/app/application.cpp` | 检查 | 检查 event_target 使用 | - -## 4. 迁移策略 - -### 4.1 第一阶段:准备工作 - -**目标**:在不破坏现有功能的情况下,为迁移做准备 - -1. **添加类型别名**(保持向后兼容): - ```cpp - // 在 widget_base.h 中添加 - // 临时别名,方便逐步迁移 - namespace mirage { - class widget_base; - using event_target_t = widget_base; // 新代码使用这个 - } - ``` - -2. **在 widget_base 中添加所有 event_target 的方法**(作为虚函数,默认实现) - -3. **保留 event_target 类**,但标记为废弃 - -### 4.2 第二阶段:更新事件系统 - -**目标**:让事件系统使用 widget_base* 而不是 event_target* - -1. 修改 `widget_event_router`: - - 所有 `event_target*` 参数改为 `widget_base*` - - 更新命中测试逻辑 - - 更新事件分发逻辑 - -2. 修改 `focus_manager`: - - 焦点链使用 `widget_base*` - - 更新焦点切换逻辑 - -3. 修改 `ime_manager`(如需要) - -### 4.3 第三阶段:更新控件类 - -**目标**:移除所有控件的 event_target 继承 - -1. **容器控件**(按依赖顺序): - ```cpp - // overlay.h - 修改前 - class overlay : public container_widget_base, public event_target { ... } - - // overlay.h - 修改后 - class overlay : public container_widget_base { ... } - ``` - -2. **叶子控件**: - ```cpp - // scrollbar.h - 修改前 - class scrollbar : public leaf_widget_base, public event_target { ... } - - // scrollbar.h - 修改后 - class scrollbar : public leaf_widget_base { ... } - ``` - -3. **移除各控件中重复的方法**: - - 删除 `as_event_target()` 重写 - - 删除 `get_parent_target()` 重写(如果只是返回父控件) - - 保留有特殊逻辑的事件处理方法 - -### 4.4 第四阶段:清理 - -**目标**:删除废弃代码 - -1. 删除 `event_target.h` 文件 -2. 从 `widget_base.h` 中删除 `as_event_target()` 方法 -3. 从 `widget_base.h` 中删除 `get_parent_event_target()` 方法 -4. 更新 CMakeLists.txt -5. 运行全量测试 - -## 5. 风险与注意事项 - -### 5.1 潜在风险 - -| 风险 | 影响 | 缓解措施 | -|------|------|----------| -| 破坏现有功能 | 高 | 分阶段迁移,每阶段完成后充分测试 | -| 编译错误 | 中 | 使用类型别名逐步过渡 | -| 运行时错误 | 中 | 保持默认实现与原有行为一致 | -| 性能影响 | 低 | 虚函数调用开销可忽略 | - -### 5.2 关键注意事项 - -1. **可视性状态统一**: - - 删除 `event_target` 中的 `visibility_` 成员 - - 统一使用 `widget_base` 的可视性管理(通过外部状态存储) - - 确保 `get_visibility()` 和 `set_visibility()` 没有二义性 - -2. **`get_parent_target()` 默认实现**: - - 默认直接返回 `parent_` 指针 - - 这自动解决了 overlay 等控件的事件冒泡问题 - - 某些特殊控件可能需要重写(如跳过某些中间层) - -3. **`get_event_children()` 返回类型**: - - 从 `std::vector` 改为 `std::vector` - - 确保所有重写都更新返回类型 - -4. **scroll_box 的特殊处理**: - - 保持其 `on_mouse_scroll()` 处理逻辑不变 - - 确保滚动条子控件正确加入 `get_event_children()` - -5. **头文件依赖**: - - `widget_base.h` 需要包含事件类型定义 - - 可能需要前向声明来避免循环依赖 - -### 5.3 测试要点 - -1. **事件冒泡测试**: - - 在 scroll_box 内的 overlay 上滚动鼠标滚轮 - - 验证滚轮事件正确冒泡到 scroll_box - - 验证 scroll_box 正确响应并滚动 - -2. **焦点导航测试**: - - Tab 键导航正常工作 - - 鼠标点击正确设置焦点 - -3. **命中测试**: - - 嵌套控件的命中测试正确 - - 可视性设置影响命中测试 - -## 6. 重构前后对比 - -### 6.1 overlay 类对比 - -**重构前**: -```cpp -class overlay : public container_widget_base, public event_target { -public: - // 缺少 get_parent_target() 重写,导致冒泡链断裂 - [[nodiscard]] bool is_event_enabled() const override { return true; } - [[nodiscard]] auto as_event_target() -> event_target* override { return this; } - // ... 重复的 contains_point, global_to_local 等实现 -}; -``` - -**重构后**: -```cpp -class overlay : public container_widget_base { -public: - // 继承 widget_base 的默认实现 - // get_parent_target() 自动返回 parent_,冒泡链完整 - // contains_point, global_to_local 使用基类默认实现 - // 只需重写真正需要自定义的方法 -}; -``` - -### 6.2 scroll_box 类对比 - -**重构前**: -```cpp -class scroll_box : public container_widget_base, public event_target { -public: - // 解决多重继承二义性 - using widget_base::set_visibility; - using widget_base::get_visibility; - - [[nodiscard]] event_target* get_parent_target() const override { - return get_parent_event_target(); // 需要手动转发 - } - - // ... 重复大量样板代码 -}; -``` - -**重构后**: -```cpp -class scroll_box : public container_widget_base { -public: - // 不再需要 using 声明解决二义性 - // get_parent_target() 使用默认实现,返回 parent_ - - event_result on_mouse_scroll(const mouse_event& event) override { - // 只保留真正的业务逻辑 - } -}; -``` - -## 7. 时间估计 - -| 阶段 | 预计时间 | 说明 | -|------|----------|------| -| 第一阶段:准备工作 | 2-3 小时 | 添加接口,保持兼容 | -| 第二阶段:更新事件系统 | 3-4 小时 | 修改路由器和焦点管理 | -| 第三阶段:更新控件类 | 2-3 小时 | 删除多重继承 | -| 第四阶段:清理 | 1-2 小时 | 删除废弃代码 | -| 测试与调试 | 2-3 小时 | 全面回归测试 | -| **总计** | **10-15 小时** | | - -## 8. 总结 - -本重构方案通过将 `event_target` 接口合并到 `widget_base` 中,解决了以下问题: - -1. **事件冒泡链断裂**:通过 `get_parent_target()` 默认返回 `parent_`,所有控件自动获得正确的事件冒泡行为 -2. **多重继承复杂性**:消除了双继承,简化了类层次结构 -3. **可视性状态重复**:统一使用 `widget_base` 的状态管理 -4. **接口分散**:所有事件相关功能集中在 `widget_base` 中 - -重构后的架构更加简洁、统一,降低了出错可能性,同时保持了良好的扩展性。 - -### 8.1 关键收益 - -- **自动事件冒泡**:控件不再需要手动实现 `get_parent_target()` -- **代码简化**:每个控件减少约 20-30 行样板代码 -- **类型安全**:统一使用 `widget_base*` 避免类型转换 -- **维护性提升**:单一继承链更易理解和维护 - -### 8.2 后续工作 - -完成本重构后,可以考虑: - -1. 添加事件过滤器机制 -2. 实现事件捕获阶段(从根向下) -3. 优化命中测试性能(空间索引) -4. 添加手势识别支持 \ No newline at end of file diff --git a/docs/IME_IMPLEMENTATION.md b/docs/IME_IMPLEMENTATION.md deleted file mode 100644 index c51f578..0000000 --- a/docs/IME_IMPLEMENTATION.md +++ /dev/null @@ -1,360 +0,0 @@ -# IME (输入法编辑器) 跨平台实现文档 - -## 概述 - -本文档描述了Mirage引擎的跨平台IME(Input Method Editor,输入法编辑器)系统的完整实现。该系统支持中文、日文、韩文等复杂输入法,提供预编辑(preedit)和文本提交(commit)功能。 - -## 架构设计 - -### 三层架构 - -1. **平台层** (`src/window/ime_platform.h` 和平台特定实现) - - 提供平台无关的接口 - - 封装Windows、Linux、macOS的原生IME API - -2. **窗口层** (`src/window/desktop/glfw_window.*`) - - 集成IME到GLFW窗口系统 - - 处理IME消息的转发 - -3. **控件层** (`src/widget/widget_event/ime_manager.*`) - - 管理IME状态和事件 - - 提供控件级别的IME接口 - - 发布IME事件供控件订阅 - -## 核心接口 - -### 平台接口 (`src/window/ime_platform.h`) - -```cpp -// 设置IME候选窗口位置 -void platform_set_ime_position(void* native_handle, const ime_position_info& pos); - -// 启用/禁用IME -void platform_set_ime_enabled(void* native_handle, bool enabled); - -// 安装IME消息处理器 -void platform_install_ime_handler(void* native_handle, - ime_message_callback callback, - void* user_data); - -// 卸载IME消息处理器 -void platform_uninstall_ime_handler(void* native_handle); - -// 处理平台IME消息 -bool platform_process_ime_message(void* native_handle, - unsigned int msg, - uint64_t wparam, - int64_t lparam); -``` - -### IME消息类型 - -```cpp -enum class ime_message_type { - preedit_start, ///< 预编辑开始 - preedit_update, ///< 预编辑更新 - preedit_end, ///< 预编辑结束 - commit ///< 提交文本 -}; - -struct ime_message { - ime_message_type type; - std::string_view text; // UTF-8文本 - int32_t cursor_pos{0}; // 光标位置 - int32_t selection_start{-1}; // 选择起始 - int32_t selection_length{0}; // 选择长度 -}; -``` - -## 平台特定实现 - -### Windows实现 (`src/window/desktop/windows/ime_win32.cpp`) - -**技术栈**: Windows IMM (Input Method Manager) API - -**核心功能**: -- 使用`ImmGetContext`/`ImmReleaseContext`管理IME上下文 -- 通过`ImmSetCompositionWindow`设置预编辑窗口位置 -- 通过`ImmSetCandidateWindow`设置候选窗口位置 -- 处理Windows消息: - - `WM_IME_STARTCOMPOSITION` - 预编辑开始 - - `WM_IME_COMPOSITION` - 预编辑更新/文本提交 - - `WM_IME_ENDCOMPOSITION` - 预编辑结束 - -**实现要点**: -- 使用窗口属性(`GetPropA`/`SetPropA`)存储IME处理器数据 -- 自动处理宽字符到UTF-8的转换 -- 支持保存和恢复IME上下文状态 - -### Linux实现 (`src/window/desktop/linux/ime_x11.cpp`) - -**技术栈**: X11 XIM (X Input Method) - -**核心功能**: -- 使用`XOpenIM`打开输入法 -- 使用`XCreateIC`创建输入上下文 -- 通过`XNSpotLocation`设置预编辑位置 -- 使用回调机制处理IME事件: - - `XNPreeditStartCallback` - 预编辑开始 - - `XNPreeditDrawCallback` - 预编辑文本变化 - - `XNPreeditDoneCallback` - 预编辑结束 - -**实现要点**: -- 使用`XIMPreeditCallbacks`风格,应用程序接管预编辑显示 -- 支持ibus、fcitx等多种Linux输入法框架 -- 通过`XSetICFocus`/`XUnsetICFocus`控制焦点 - -### macOS实现 (`src/window/desktop/macos/ime_cocoa.mm`) - -**技术栈**: Cocoa NSTextInputClient协议 - -**核心功能**: -- 实现自定义`MirageIMEView`类,遵循`NSTextInputClient`协议 -- 实现关键方法: - - `setMarkedText:selectedRange:replacementRange:` - 处理预编辑 - - `insertText:replacementRange:` - 处理文本提交 - - `firstRectForCharacterRange:actualRange:` - 提供候选窗口位置 - - `unmarkText` - 取消预编辑 - -**实现要点**: -- 使用`NSMutableAttributedString`管理预编辑文本 -- 通过`NSTextInputContext`与系统输入法交互 -- 自动处理坐标系转换(macOS使用左下角原点) - -## IME管理器 (`src/widget/widget_event/ime_manager.*`) - -### 状态管理 - -```cpp -enum class ime_state : uint8_t { - disabled, ///< IME禁用 - enabled, ///< IME启用但未激活 - composing ///< IME正在预编辑 -}; -``` - -### 核心功能 - -1. **状态控制** - - `enable()` / `disable()` - 启用/禁用IME - - `is_enabled()` / `is_composing()` - 查询状态 - -2. **候选窗口定位** - - `set_candidate_position(x, y, line_height)` - 设置位置 - - 自动更新平台层位置 - -3. **预编辑管理** - - `get_preedit()` - 获取当前预编辑信息 - - `cancel_composition()` - 取消预编辑 - -4. **事件发布** - - `on_preedit_start()` - 预编辑开始事件 - - `on_preedit_update()` - 预编辑更新事件 - - `on_preedit_end()` - 预编辑结束事件 - - `on_commit()` - 文本提交事件 - -### 焦点联动 - -```cpp -void ime_manager::set_focused_target(widget_base* target); -``` - -当控件获得焦点时,自动根据控件的IME支持能力启用/禁用IME。 - -## 使用示例 - -### 在文本输入控件中使用IME - -```cpp -class text_input : public leaf_widget_base { -public: - void on_focus_gained() override { - // 启用IME - if (auto* ctx = get_context()) { - auto& ime = ctx->get_ime_manager(); - ime.enable(); - ime.set_focused_target(this); - update_ime_position(); - } - } - - void on_focus_lost() override { - // 禁用IME - if (auto* ctx = get_context()) { - auto& ime = ctx->get_ime_manager(); - ime.disable(); - } - } - - void setup_ime_events() { - auto& ime = get_context()->get_ime_manager(); - - // 订阅预编辑更新 - ime.on_preedit_update().subscribe([this](const ime_preedit_event& e) { - preedit_text_ = e.preedit.text; - preedit_cursor_ = e.preedit.cursor_pos; - mark_render_dirty(); - }); - - // 订阅文本提交 - ime.on_commit().subscribe([this](const ime_commit_event& e) { - insert_text(e.text); - preedit_text_.clear(); - mark_render_dirty(); - }); - } - - void update_ime_position() { - auto cursor_pos = calculate_cursor_screen_position(); - auto& ime = get_context()->get_ime_manager(); - ime.set_candidate_position( - static_cast(cursor_pos.x()), - static_cast(cursor_pos.y()), - static_cast(font_size_) - ); - } - -private: - std::string preedit_text_; - int32_t preedit_cursor_{0}; -}; -``` - -### 渲染预编辑文本 - -```cpp -void text_input::render(render_command_collector& collector) { - // 1. 渲染普通文本 - collector.submit_text(pos, text_, color_, font_size_, font_id_); - - // 2. 渲染预编辑文本(带下划线) - if (!preedit_text_.empty()) { - auto preedit_pos = calculate_preedit_position(); - - // 渲染预编辑文本 - collector.submit_text(preedit_pos, preedit_text_, - preedit_color_, font_size_, font_id_); - - // 渲染预编辑下划线 - auto underline_y = preedit_pos.y() + font_size_; - collector.submit_line( - {preedit_pos.x(), underline_y}, - {preedit_pos.x() + preedit_width, underline_y}, - preedit_underline_color_ - ); - - // 渲染预编辑光标 - if (preedit_cursor_ >= 0) { - auto cursor_x = calculate_cursor_x_in_preedit(preedit_cursor_); - collector.submit_rect( - {cursor_x, preedit_pos.y()}, - {cursor_width_, font_size_}, - cursor_color_ - ); - } - } -} -``` - -## 事件流程 - -### 输入中文"你好"的完整流程 - -1. **用户开始输入** - - 系统发送 `preedit_start` - - `ime_manager` 状态: `disabled` → `composing` - -2. **用户输入拼音 "nihao"** - - 系统发送 `preedit_update("nihao", cursor=5)` - - 控件显示下划线文本: "nihao" - -3. **用户选择候选词** - - 系统发送 `preedit_update("你好", cursor=2)` - - 控件更新显示: "你好" - -4. **用户按空格确认** - - 系统发送 `commit("你好")` - - 控件插入文本: "你好" - - 系统发送 `preedit_end` - - `ime_manager` 状态: `composing` → `enabled` - -## 测试验证 - -### 测试要点 - -1. **基础功能测试** - - [ ] 启用/禁用IME - - [ ] 预编辑文本显示 - - [ ] 候选窗口位置正确 - - [ ] 文本正确提交 - -2. **跨平台测试** - - [ ] Windows (中文/日文输入法) - - [ ] Linux (ibus/fcitx) - - [ ] macOS (内置输入法) - -3. **边界情况测试** - - [ ] 焦点切换时IME状态 - - [ ] 预编辑中途取消 - - [ ] 窗口移动时候选窗口位置 - - [ ] 多窗口IME状态独立性 - -## 注意事项 - -### Windows -- IME上下文必须正确保存和恢复 -- 需要链接 `imm32.lib` -- 坐标系统:左上角为原点 - -### Linux -- 需要X11开发库:`libx11-dev` -- 支持多种输入法框架(ibus、fcitx等) -- 坐标系统:左上角为原点 -- Display指针管理需要注意 - -### macOS -- 文件扩展名必须是`.mm`(Objective-C++) -- 需要实现完整的NSTextInputClient协议 -- 坐标系统:左下角为原点,需要Y坐标翻转 -- 视图替换需要保留原有子视图 - -## 性能考虑 - -1. **内存管理** - - 预编辑文本使用`std::string`,避免频繁分配 - - 平台数据使用智能指针管理生命周期 - -2. **事件优化** - - 使用pub-sub模式,避免轮询 - - 事件只发送给订阅的控件 - -3. **线程安全** - - IME操作必须在主线程执行 - - 使用`threading::topic`保证事件分发的线程安全 - -## 未来改进 - -1. **功能增强** - - [ ] 支持内联候选窗口(候选词显示在应用内) - - [ ] 支持更多IME属性(字体、颜色等) - - [ ] 支持Windows TSF (Text Services Framework) - -2. **平台扩展** - - [ ] Wayland支持(Linux) - - [ ] Android/iOS移动平台支持 - -3. **API改进** - - [ ] 提供更细粒度的控制接口 - - [ ] 支持自定义候选窗口样式 - -## 相关文档 - -- [键盘事件系统设计](KEYBOARD_EVENT_SYSTEM.md) -- [文本输入控件设计](TEXT_INPUT_DESIGN.md) -- [文本输入架构分析](TEXT_INPUT_ARCHITECTURE_ANALYSIS.md) - -## 参考资料 - -- [Windows IMM API文档](https://docs.microsoft.com/en-us/windows/win32/intl/input-method-manager) -- [X11 XIM规范](https://www.x.org/releases/X11R7.7/doc/libX11/XIM/xim.html) -- [macOS NSTextInputClient文档](https://developer.apple.com/documentation/appkit/nstextinputclient) \ No newline at end of file diff --git a/docs/KEYBOARD_EVENT_SYSTEM.md b/docs/KEYBOARD_EVENT_SYSTEM.md deleted file mode 100644 index 55da1f5..0000000 --- a/docs/KEYBOARD_EVENT_SYSTEM.md +++ /dev/null @@ -1,1472 +0,0 @@ - -# 键盘事件系统设计文档 - -## 目录 -1. [概述](#概述) -2. [现有架构分析](#现有架构分析) -3. [系统架构设计](#系统架构设计) -4. [核心组件设计](#核心组件设计) -5. [焦点管理系统](#焦点管理系统) -6. [事件分发机制](#事件分发机制) -7. [与现有系统集成](#与现有系统集成) -8. [文件结构规划](#文件结构规划) -9. [关键接口设计](#关键接口设计) -10. [IME 输入法支持](#ime-输入法支持) -11. [快捷键系统](#快捷键系统) -12. [实现步骤](#实现步骤) - ---- - -## 概述 - -本文档描述 Mirage GUI 框架的键盘事件系统设计。该系统需要: - -- 接收窗口层的键盘事件(KEY_PRESSED, KEY_RELEASED, KEY_REPEAT) -- 管理控件焦点(Focus Management) -- 将键盘事件分发到获得焦点的控件 -- 支持文本输入(字符输入事件) -- 与现有的鼠标事件系统协调工作 -- 在多线程环境下正确处理事件 - -### 设计目标 - -| 目标 | 描述 | -|------|------| -| 一致性 | 与现有鼠标事件系统保持一致的 API 风格 | -| 可扩展性 | 支持未来添加更多输入方式(如触摸、手柄) | -| 线程安全 | 事件在主线程处理,状态同步到其他线程 | -| 零侵入 | 不修改现有控件的核心逻辑,通过继承扩展 | - ---- - -## 现有架构分析 - -### 窗口层事件系统 - -位于 [`src/window/event/event_types.h`](../src/window/event/event_types.h) - -**已有的键盘事件类型:** -```cpp -enum class event_type : uint8_t { - // ... - KEY_PRESSED, // 键按下 - KEY_RELEASED, // 键释放 - KEY_REPEAT, // 键重复(长按) - // ... -}; -``` - -**已有的键盘数据结构:** -```cpp -struct key_event_data { - key_code key{key_code::UNKNOWN}; // 键码 - int32_t scancode{0}; // 扫描码 - key_mod mods{key_mod::NONE}; // 修饰键 - bool repeat{false}; // 是否重复 -}; -``` - -**已有的键码枚举** (`key_code`): -- 字母键 A-Z -- 数字键 0-9 -- 功能键 F1-F25 -- 控制键(Enter, Tab, Escape 等) -- 修饰键(Shift, Control, Alt, Super) -- 小键盘键 - -**已有的修饰键标志** (`key_mod`): -```cpp -enum class key_mod : uint8_t { - NONE = 0, - SHIFT = 1 << 0, - CONTROL = 1 << 1, - ALT = 1 << 2, - SUPER = 1 << 3, - CAPS_LOCK = 1 << 4, - NUM_LOCK = 1 << 5 -}; -``` - -### 控件层事件系统 - -位于 [`src/widget/widget_event/`](../src/widget/widget_event/) - -**现有组件:** -- [`event_target.h`](../src/widget/widget_event/event_target.h) - 事件目标接口 -- [`widget_event_router.h`](../src/widget/widget_event/widget_event_router.h) - 鼠标事件路由器 -- [`widget_event_types.h`](../src/widget/widget_event/widget_event_types.h) - 控件鼠标事件类型 - -**event_target 现有接口:** -```cpp -class event_target { -public: - // 命中测试 - virtual bool contains_point(double global_x, double global_y) const = 0; - virtual std::pair global_to_local(double, double) const = 0; - - // 子控件访问 - virtual std::vector get_event_children() const { return {}; } - - // 鼠标事件处理(已实现) - virtual void on_mouse_down(mouse_event& event) { } - virtual void on_mouse_up(mouse_event& event) { } - virtual void on_mouse_move(mouse_event& event) { } - virtual void on_mouse_scroll(mouse_event& event) { } - virtual void on_mouse_enter(mouse_event& event) { } - virtual void on_mouse_leave(mouse_event& event) { } - - // 状态查询 - virtual bool is_event_enabled() const { return true; } - virtual bool is_visible() const { return true; } -}; -``` - -### 应用程序事件处理 - -位于 [`src/app/application.cpp`](../src/app/application.cpp) - -**当前 handle_event 方法:** -```cpp -void application::handle_event(const mirage::event& e) { - switch (e.type) { - // 窗口事件处理... - // 鼠标事件 -> event_router_.handle_mouse_event(...) - default: - // TODO: 实现控件事件路由 - break; - } -} -``` - -**观察:** 键盘事件目前落入 `default` 分支,未被处理。 - ---- - -## 系统架构设计 - -### 整体架构图 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ 应用程序层 │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ application::handle_event │ │ -│ │ ┌─────────────────┐ ┌─────────────────┐ │ │ -│ │ │ KEY_PRESSED │ │ KEY_RELEASED │ │ │ -│ │ │ KEY_RELEASED │ │ KEY_REPEAT │ │ │ -│ │ │ KEY_REPEAT │ │ CHAR_INPUT │ │ │ -│ │ └────────┬────────┘ └────────┬────────┘ │ │ -│ └───────────┼──────────────────────┼───────────────────────────────┘ │ -│ │ │ │ -│ v v │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ widget_event_router │ │ -│ │ ┌─────────────────────────────────────────────────────────┐ │ │ -│ │ │ focus_manager │ │ │ -│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ -│ │ │ │focused_widget│ │ focus_chain │ │ tab_order │ │ │ │ -│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ -│ │ └─────────────────────────────────────────────────────────┘ │ │ -│ │ │ │ │ -│ │ v │ │ -│ │ ┌─────────────────────────────────────────────────────────┐ │ │ -│ │ │ handle_keyboard_event │ │ │ -│ │ │ dispatch to focused widget with bubble/capture │ │ │ -│ │ └─────────────────────────────────────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ - │ - v -┌─────────────────────────────────────────────────────────────────────────┐ -│ 控件层 │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ event_target (扩展) │ │ -│ │ ┌─────────────────┐ ┌─────────────────┐ │ │ -│ │ │ on_key_down │ │ on_focus_gained │ │ │ -│ │ │ on_key_up │ │ on_focus_lost │ │ │ -│ │ │ on_char_input │ │ is_focusable │ │ │ -│ │ └─────────────────┘ └─────────────────┘ │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 事件流程 - -``` -GLFW 键盘回调 - │ - v -window_manager::poll_events - │ - v -application::handle_event(event) - │ - ├──────────────────────────────────┐ - │ KEY_PRESSED / KEY_RELEASED │ CHAR_INPUT - v v -widget_event_router::handle_keyboard_event - │ - v -focus_manager::get_focused_widget - │ - v -dispatch_keyboard_event (带冒泡) - │ - v -event_target::on_key_down / on_key_up / on_char_input -``` - ---- - -## 核心组件设计 - -### 4.1 键盘事件数据结构 - -**文件**: `src/widget/widget_event/widget_event_types.h`(扩展现有文件) - -```cpp -namespace mirage { - -/// 控件键盘事件数据 -struct widget_keyboard_event { - // 事件类型 - event_type type{event_type::NONE}; - - // 键码信息 - key_code key{key_code::UNKNOWN}; - - // 扫描码(平台相关) - int32_t scancode{0}; - - // 修饰键状态 - key_mod modifiers{key_mod::NONE}; - - // 是否重复事件(长按产生) - bool repeat{false}; - - // 事件是否已被处理(用于阻止冒泡) - mutable bool handled{false}; -}; - -/// 字符输入事件数据 -struct widget_char_event { - // 事件类型(总是 CHAR_INPUT) - event_type type{event_type::NONE}; - - // UTF-32 字符码点 - uint32_t codepoint{0}; - - // 修饰键状态 - key_mod modifiers{key_mod::NONE}; - - // 事件是否已被处理 - mutable bool handled{false}; -}; - -// 类型别名 -using keyboard_event = widget_keyboard_event; -using char_event = widget_char_event; - -} // namespace mirage -``` - -### 4.2 焦点管理器 - -**文件**: `src/widget/widget_event/focus_manager.h` - -```cpp -#pragma once -#include "event_target.h" -#include -#include -#include - -namespace mirage { - -/// 焦点变化原因 -enum class focus_change_reason { - PROGRAMMATIC, // 程序调用 - MOUSE_CLICK, // 鼠标点击 - TAB_NAVIGATION, // Tab 键导航 - ARROW_NAVIGATION // 方向键导航 -}; - -/// 焦点变化事件 -struct focus_change_event { - event_target* old_target{nullptr}; - event_target* new_target{nullptr}; - focus_change_reason reason{focus_change_reason::PROGRAMMATIC}; -}; - -/// 焦点管理器 -/// 负责追踪和管理控件焦点 -class focus_manager { -public: - focus_manager() = default; - - // ======================================================================== - // 焦点操作 - // ======================================================================== - - /// 设置焦点到指定控件 - /// @param target 目标控件(nullptr 表示清除焦点) - /// @param reason 焦点变化原因 - /// @return 是否成功设置焦点 - bool set_focus(event_target* target, - focus_change_reason reason = focus_change_reason::PROGRAMMATIC); - - /// 清除当前焦点 - void clear_focus(); - - /// 获取当前焦点控件 - [[nodiscard]] event_target* get_focused() const noexcept { - return focused_target_; - } - - /// 检查指定控件是否有焦点 - [[nodiscard]] bool has_focus(const event_target* target) const noexcept { - return focused_target_ == target; - } - - // ======================================================================== - // Tab 导航 - // ======================================================================== - - /// 移动焦点到下一个可聚焦控件 - /// @param reverse 是否反向(Shift+Tab) - /// @return 是否成功移动焦点 - bool move_focus_next(bool reverse = false); - - /// 注册可 Tab 导航的控件 - void register_tab_stop(event_target* target, int32_t tab_index = -1); - - /// 取消注册 Tab 导航控件 - void unregister_tab_stop(event_target* target); - - /// 设置 Tab 循环(到达末尾后回到开头) - void set_tab_wrap(bool wrap) noexcept { tab_wrap_ = wrap; } - - // ======================================================================== - // 焦点链管理 - // ======================================================================== - - /// 获取焦点链(从根到当前焦点的路径) - [[nodiscard]] const std::vector& get_focus_chain() const noexcept { - return focus_chain_; - } - - /// 重建焦点链 - void rebuild_focus_chain(); - - // ======================================================================== - // 回调 - // ======================================================================== - - using focus_change_callback = std::function; - - /// 设置焦点变化回调 - void set_focus_change_callback(focus_change_callback callback) { - focus_change_callback_ = std::move(callback); - } - -private: - /// 构建从根到目标的焦点链 - void build_focus_chain(event_target* target); - - /// 通知焦点变化 - void notify_focus_change(event_target* old_target, event_target* new_target, - focus_change_reason reason); - - event_target* focused_target_{nullptr}; - std::vector focus_chain_; - std::vector> tab_stops_; - bool tab_wrap_{true}; - focus_change_callback focus_change_callback_; -}; - -} // namespace mirage -``` - -### 4.3 扩展 event_target - -**文件**: `src/widget/widget_event/event_target.h`(扩展) - -```cpp -// 在现有 event_target 类中添加: - -class event_target { -public: - // ... 现有接口 ... - - // ======================================================================== - // 键盘事件处理(新增) - // ======================================================================== - - /// 键按下事件 - virtual void on_key_down(keyboard_event& event) { (void)event; } - - /// 键释放事件 - virtual void on_key_up(keyboard_event& event) { (void)event; } - - /// 字符输入事件 - virtual void on_char_input(char_event& event) { (void)event; } - - // ======================================================================== - // 焦点事件处理(新增) - // ======================================================================== - - /// 获得焦点时调用 - virtual void on_focus_gained(focus_change_reason reason) { (void)reason; } - - /// 失去焦点时调用 - virtual void on_focus_lost(focus_change_reason reason) { (void)reason; } - - // ======================================================================== - // 焦点属性(新增) - // ======================================================================== - - /// 是否可以接收焦点 - [[nodiscard]] virtual bool is_focusable() const { return false; } - - /// 获取 Tab 顺序(-1 表示不参与 Tab 导航) - [[nodiscard]] virtual int32_t get_tab_index() const { return -1; } - - /// 获取父事件目标(用于构建焦点链) - [[nodiscard]] virtual event_target* get_parent_target() const { return nullptr; } - -protected: - // ... 现有成员 ... - - // 新增:请求焦点 - void request_focus(); - - // 新增:检查自身是否有焦点 - [[nodiscard]] bool is_focused() const; -}; -``` - -### 4.4 扩展 widget_event_router - -**文件**: `src/widget/widget_event/widget_event_router.h`(扩展) - -```cpp -// 在现有 widget_event_router 类中添加: - -class widget_event_router { -public: - // ... 现有接口 ... - - // ======================================================================== - // 键盘事件处理(新增) - // ======================================================================== - - /// 处理键盘事件 - /// @param type 事件类型(KEY_PRESSED, KEY_RELEASED, KEY_REPEAT) - /// @param key 键码 - /// @param scancode 扫描码 - /// @param mods 修饰键 - /// @param repeat 是否重复 - void handle_keyboard_event( - event_type type, - key_code key, - int32_t scancode, - key_mod mods, - bool repeat - ); - - /// 处理字符输入事件 - /// @param codepoint UTF-32 字符码点 - /// @param mods 修饰键 - void handle_char_input(uint32_t codepoint, key_mod mods); - - // ======================================================================== - // 焦点管理访问(新增) - // ======================================================================== - - /// 获取焦点管理器 - [[nodiscard]] focus_manager& get_focus_manager() noexcept { - return focus_manager_; - } - - /// 获取焦点管理器(const) - [[nodiscard]] const focus_manager& get_focus_manager() const noexcept { - return focus_manager_; - } - - /// 设置焦点到指定控件 - bool set_focus(event_target* target, - focus_change_reason reason = focus_change_reason::PROGRAMMATIC) { - return focus_manager_.set_focus(target, reason); - } - -private: - // ... 现有成员 ... - - /// 分发键盘事件(冒泡) - void dispatch_keyboard_event(keyboard_event& event); - - /// 分发字符输入事件 - void dispatch_char_event(char_event& event); - - /// 处理 Tab 导航 - bool handle_tab_navigation(const keyboard_event& event); - - /// 焦点管理器 - focus_manager focus_manager_; -}; -``` - ---- - -## 焦点管理系统 - -### 5.1 焦点概念 - -``` -焦点(Focus): -- 同一时刻只有一个控件可以拥有焦点 -- 键盘事件总是发送到拥有焦点的控件 -- 焦点可以通过鼠标点击、Tab 键或程序设置来改变 - -焦点链(Focus Chain): -- 从根控件到当前焦点控件的路径 -- 用于事件冒泡和焦点遍历 -- 当焦点改变时自动重建 - -Tab 导航: -- Tab 键在可聚焦控件间循环 -- Shift+Tab 反向循环 -- tab_index 决定导航顺序 -``` - -### 5.2 焦点状态转换 - -``` - ┌─────────────┐ - │ 无焦点 │ - └──────┬──────┘ - │ - ┌────────────┼────────────┐ - │ │ │ - v v v - 鼠标点击 Tab 键 程序设置 - │ │ │ - └────────────┼────────────┘ - │ - v - ┌─────────────┐ - │ 有焦点 │◄───────┐ - └──────┬──────┘ │ - │ │ - ┌─────────────────┼───────────────┤ - │ │ │ - v v │ - 焦点控件销毁 其他控件获得焦点 Tab 导航 - │ │ │ - v │ │ - ┌─────────────┐ │ │ - │ 无焦点 │ └───────────────┘ - └─────────────┘ -``` - -### 5.3 焦点事件序列 - -当焦点从控件 A 转移到控件 B 时: - -``` -1. A.on_focus_lost(reason) // A 失去焦点 -2. focus_manager 更新状态 // 内部状态更新 -3. B.on_focus_gained(reason) // B 获得焦点 -4. focus_change_callback 触发 // 全局通知 -``` - ---- - -## 事件分发机制 - -### 6.1 键盘事件分发流程 - -```cpp -void widget_event_router::handle_keyboard_event(...) { - // 1. 构建键盘事件 - keyboard_event event; - event.type = type; - event.key = key; - event.scancode = scancode; - event.modifiers = mods; - event.repeat = repeat; - event.handled = false; - - // 2. 检查 Tab 导航 - if (type == event_type::KEY_PRESSED && key == key_code::TAB) { - if (handle_tab_navigation(event)) { - return; // Tab 导航已处理 - } - } - - // 3. 分发到焦点控件 - dispatch_keyboard_event(event); -} - -void widget_event_router::dispatch_keyboard_event(keyboard_event& event) { - // 获取焦点链(从焦点控件到根) - const auto& chain = focus_manager_.get_focus_chain(); - - // 从焦点控件开始冒泡 - for (auto it = chain.rbegin(); it != chain.rend(); ++it) { - event_target* target = *it; - - if (!target->is_event_enabled()) { - continue; - } - - // 根据事件类型分发 - switch (event.type) { - case event_type::KEY_PRESSED: - case event_type::KEY_REPEAT: - target->on_key_down(event); - break; - case event_type::KEY_RELEASED: - target->on_key_up(event); - break; - default: - break; - } - - // 检查是否已处理 - if (event.handled) { - break; - } - } -} -``` - -### 6.2 字符输入分发 - -```cpp -void widget_event_router::handle_char_input(uint32_t codepoint, key_mod mods) { - // 构建字符事件 - char_event event; - event.type = event_type::CHAR_INPUT; // 需要添加到 event_type - event.codepoint = codepoint; - event.modifiers = mods; - event.handled = false; - - // 分发 - dispatch_char_event(event); -} - -void widget_event_router::dispatch_char_event(char_event& event) { - const auto& chain = focus_manager_.get_focus_chain(); - - for (auto it = chain.rbegin(); it != chain.rend(); ++it) { - event_target* target = *it; - - if (!target->is_event_enabled()) { - continue; - } - - target->on_char_input(event); - - if (event.handled) { - break; - } - } -} -``` - -### 6.3 Tab 导航处理 - -```cpp -bool widget_event_router::handle_tab_navigation(const keyboard_event& event) { - if (event.key != key_code::TAB) { - return false; - } - - bool reverse = has_mod(event.modifiers, key_mod::SHIFT); - return focus_manager_.move_focus_next(reverse); -} -``` - ---- - -## 与现有系统集成 - -### 7.1 与 application 集成 - -**修改文件**: `src/app/application.cpp` - -```cpp -void application::handle_event(const mirage::event& e) { - switch (e.type) { - // ... 现有窗口和鼠标事件处理 ... - - // 新增:键盘事件处理 - case event_type::KEY_PRESSED: - case event_type::KEY_RELEASED: - case event_type::KEY_REPEAT: - event_router_.handle_keyboard_event( - e.type, - e.key.key, - e.key.scancode, - e.key.mods, - e.key.repeat - ); - break; - - // 新增:字符输入事件处理(需要先添加到 event_type) - // case event_type::CHAR_INPUT: - // event_router_.handle_char_input( - // e.char_input.codepoint, - // e.char_input.mods - // ); - // break; - - default: - break; - } -} -``` - -### 7.2 与鼠标事件的焦点集成 - -当鼠标点击某个控件时,自动设置焦点: - -```cpp -void widget_event_router::handle_mouse_event(...) { - // ... 现有逻辑 ... - - // 在 MOUSE_BUTTON_PRESSED 事件处理中添加焦点设置 - if (type == event_type::MOUSE_BUTTON_PRESSED && button == mouse_button::LEFT) { - // 查找命中的可聚焦控件 - for (auto it = targets.rbegin(); it != targets.rend(); ++it) { - if ((*it)->is_focusable()) { - focus_manager_.set_focus(*it, focus_change_reason::MOUSE_CLICK); - break; - } - } - } - - // ... 继续现有事件分发 ... -} -``` - -### 7.3 与线程系统集成 - -键盘事件在主线程处理,如果需要同步焦点状态到其他线程: - -```cpp -// 使用现有的 sync_state 机制 -using keyboard_focus_state = mirage::threading::main_to_layout_state; - -// 在焦点变化时更新 -void focus_manager::notify_focus_change(...) { - // 通知回调 - if (focus_change_callback_) { - focus_change_callback_(focus_change_event{old_target, new_target, reason}); - } -} -``` - ---- - -## 文件结构规划 - -### 需要新增的文件 - -``` -src/widget/widget_event/ -├── focus_manager.h # 焦点管理器头文件 -├── focus_manager.cpp # 焦点管理器实现 -├── ime_manager.h # IME 管理器头文件 -├── ime_manager.cpp # IME 管理器实现 -├── shortcut_types.h # 快捷键类型定义 -├── shortcut_manager.h # 快捷键管理器头文件 -├── shortcut_manager.cpp # 快捷键管理器实现 -``` - -### 需要修改的文件 - -| 文件 | 修改内容 | -|------|----------| -| `src/window/event/event_types.h` | 添加 CHAR_INPUT、IME 事件类型和相关数据结构 | -| `src/widget/widget_event/event_target.h` | 添加键盘、焦点、IME 相关的虚函数 | -| `src/widget/widget_event/widget_event_types.h` | 添加键盘、字符、IME 事件类型 | -| `src/widget/widget_event/widget_event_router.h` | 添加事件处理方法和管理器成员 | -| `src/widget/widget_event/widget_event_router.cpp` | 实现键盘事件、IME、快捷键分发逻辑 | -| `src/app/application.cpp` | 在 handle_event 中添加键盘和 IME 事件处理 | -| `src/window/desktop/glfw_window.cpp` | 添加 GLFW 字符输入和 IME 回调 | - ---- - -## 关键接口设计 - -### 9.1 窗口层字符输入事件 - -需要在 `src/window/event/event_types.h` 中添加: - -```cpp -// 在 event_type 枚举中添加 -enum class event_type : uint8_t { - // ... 现有类型 ... - - // 字符输入事件(新增) - CHAR_INPUT, -}; - -// 新增字符输入事件数据 -struct char_event_data { - uint32_t codepoint{0}; // UTF-32 码点 - key_mod mods{key_mod::NONE}; -}; - -// 在 event union 中添加 -struct event { - event_type type{event_type::NONE}; - - union { - window_event_data window; - key_event_data key; - mouse_button_event_data mouse_button; - mouse_move_event_data mouse_move; - mouse_scroll_event_data mouse_scroll; - char_event_data char_input; // 新增 - }; - - // ... 构造函数 ... -}; -``` - ---- - -## IME 输入法支持 - -### 10.1 IME 概述 - -IME(Input Method Editor)是用于输入复杂字符(如中文、日文、韩文)的系统组件。 - -``` -IME 输入流程: -用户按键 -> IME 预编辑 -> 候选选择 -> 提交文本 - -预编辑 Preedit: -- 用户正在输入但尚未确认的文本 -- 通常显示下划线或高亮 -- 可以修改或取消 - -提交 Commit: -- 用户确认的最终文本 -- 通过 CHAR_INPUT 或 IME_COMMIT 事件发送 -``` - -### 10.2 IME 事件类型 - -**文件**: `src/window/event/event_types.h`(扩展) - -```cpp -// 在 event_type 枚举中添加 IME 相关事件 -enum class event_type : uint8_t { - // ... 现有类型 ... - - // IME 事件 - IME_PREEDIT_START, // IME 预编辑开始 - IME_PREEDIT_UPDATE, // IME 预编辑更新 - IME_PREEDIT_END, // IME 预编辑结束 - IME_COMMIT, // IME 提交文本 -}; - -/// IME 预编辑事件数据 -struct ime_preedit_event_data { - const char* text{nullptr}; // 预编辑文本 UTF-8 - size_t text_length{0}; // 文本长度 - int32_t cursor_pos{0}; // 光标位置 - int32_t selection_start{-1}; // 选中起始 - int32_t selection_length{0}; // 选中长度 -}; - -/// IME 提交事件数据 -struct ime_commit_event_data { - const char* text{nullptr}; // 提交文本 UTF-8 - size_t text_length{0}; // 文本长度 -}; -``` - -### 10.3 控件层 IME 事件 - -**文件**: `src/widget/widget_event/widget_event_types.h`(扩展) - -```cpp -namespace mirage { - -/// IME 预编辑信息 -struct ime_preedit_info { - std::string text; // 预编辑文本 - int32_t cursor_pos{0}; // 光标位置 - int32_t selection_start{-1}; // 选中范围 - int32_t selection_length{0}; - bool active{false}; // 是否正在预编辑 -}; - -/// 控件 IME 事件 -struct widget_ime_event { - event_type type{event_type::NONE}; - ime_preedit_info preedit; // 预编辑信息 - std::string committed_text; // 提交的文本 - mutable bool handled{false}; -}; - -using ime_event = widget_ime_event; - -} // namespace mirage -``` - -### 10.4 IME 管理器 - -**文件**: `src/widget/widget_event/ime_manager.h` - -```cpp -#pragma once -#include "widget_event_types.h" -#include - -namespace mirage { - -/// IME 候选窗口位置回调 -using ime_position_callback = std::function; - -/// IME 管理器 -class ime_manager { -public: - ime_manager() = default; - - // IME 状态控制 - void enable(); - void disable(); - [[nodiscard]] bool is_enabled() const noexcept { return enabled_; } - [[nodiscard]] bool is_composing() const noexcept { return composing_; } - - // 候选窗口定位 - void set_candidate_position(int32_t x, int32_t y, int32_t line_height = 20); - void set_position_callback(ime_position_callback cb) { - position_callback_ = std::move(cb); - } - - // 预编辑状态 - [[nodiscard]] const ime_preedit_info& get_preedit() const noexcept { - return preedit_; - } - void cancel_composition(); - - // 事件处理 - void on_preedit_start(); - void on_preedit_update(const ime_preedit_event_data& data); - void on_preedit_end(); - void on_commit(const ime_commit_event_data& data); - -private: - bool enabled_{false}; - bool composing_{false}; - ime_preedit_info preedit_; - ime_position_callback position_callback_; -}; - -} // namespace mirage -``` - -### 10.5 扩展 event_target 支持 IME - -```cpp -class event_target { -public: - // ... 现有接口 ... - - // IME 事件处理 - virtual void on_ime_preedit_start(ime_event& event) { (void)event; } - virtual void on_ime_preedit_update(ime_event& event) { (void)event; } - virtual void on_ime_preedit_end(ime_event& event) { (void)event; } - virtual void on_ime_commit(ime_event& event) { (void)event; } - - // IME 支持查询 - [[nodiscard]] virtual bool supports_ime() const { return false; } - - // 获取 IME 光标位置(屏幕坐标 x, y 和行高) - [[nodiscard]] virtual std::tuple - get_ime_cursor_position() const { return {0, 0, 20}; } -}; -``` - -### 10.6 文本输入控件 IME 示例 - -```cpp -class text_input_with_ime : public text_input { -public: - bool supports_ime() const override { return true; } - - std::tuple get_ime_cursor_position() const override { - auto pos = get_global_position(); - auto cursor = calculate_cursor_position(); - return { - static_cast(pos.x() + cursor.x()), - static_cast(pos.y() + cursor.y()), - static_cast(line_height_) - }; - } - - void on_ime_preedit_update(ime_event& event) override { - preedit_text_ = event.preedit.text; - preedit_cursor_ = event.preedit.cursor_pos; - mark_render_dirty(); - event.handled = true; - } - - void on_ime_preedit_end(ime_event& event) override { - preedit_text_.clear(); - mark_render_dirty(); - event.handled = true; - } - - void on_ime_commit(ime_event& event) override { - text_.insert(cursor_position_, event.committed_text); - cursor_position_ += event.committed_text.length(); - preedit_text_.clear(); - mark_render_dirty(); - event.handled = true; - } - -private: - std::string preedit_text_; - int32_t preedit_cursor_{0}; - float line_height_{20.0f}; -}; -``` - ---- - -## 快捷键系统 - -### 11.1 快捷键概述 - -快捷键系统提供全局和局部的键盘快捷键绑定功能: - -``` -快捷键层级: -1. 全局快捷键 - 应用程序级别,最高优先级 -2. 窗口快捷键 - 窗口级别 -3. 控件快捷键 - 控件级别,最低优先级 - -快捷键匹配: -- 先匹配高优先级的快捷键 -- 匹配成功后阻止事件继续传播 -- 支持快捷键冲突检测 -``` - -### 11.2 快捷键数据结构 - -**文件**: `src/widget/widget_event/shortcut_types.h` - -```cpp -#pragma once -#include "event/event_types.h" -#include -#include - -namespace mirage { - -/// 快捷键定义 -struct shortcut { - key_code key{key_code::UNKNOWN}; - key_mod modifiers{key_mod::NONE}; - std::string id; - std::string description; - bool enabled{true}; - - shortcut() = default; - shortcut(key_code k, key_mod m, std::string id_ = "", std::string desc = "") - : key(k), modifiers(m), id(std::move(id_)), description(std::move(desc)) {} - - [[nodiscard]] bool matches(key_code k, key_mod m) const noexcept { - return enabled && key == k && modifiers == m; - } - - [[nodiscard]] std::string to_string() const; - - bool operator==(const shortcut& other) const noexcept { - return key == other.key && modifiers == other.modifiers; - } -}; - -/// 快捷键回调类型 -using shortcut_callback = std::function; - -/// 快捷键绑定 -struct shortcut_binding { - shortcut shortcut_def; - shortcut_callback callback; - int32_t priority{0}; - std::string scope; -}; - -/// 预定义的常用快捷键 -namespace shortcuts { - inline shortcut new_file{key_code::N, key_mod::CONTROL, "file.new", "New File"}; - inline shortcut open{key_code::O, key_mod::CONTROL, "file.open", "Open"}; - inline shortcut save{key_code::S, key_mod::CONTROL, "file.save", "Save"}; - inline shortcut save_as{key_code::S, key_mod::CONTROL | key_mod::SHIFT, "file.save_as", "Save As"}; - inline shortcut undo{key_code::Z, key_mod::CONTROL, "edit.undo", "Undo"}; - inline shortcut redo{key_code::Y, key_mod::CONTROL, "edit.redo", "Redo"}; - inline shortcut cut{key_code::X, key_mod::CONTROL, "edit.cut", "Cut"}; - inline shortcut copy{key_code::C, key_mod::CONTROL, "edit.copy", "Copy"}; - inline shortcut paste{key_code::V, key_mod::CONTROL, "edit.paste", "Paste"}; - inline shortcut select_all{key_code::A, key_mod::CONTROL, "edit.select_all", "Select All"}; - inline shortcut find{key_code::F, key_mod::CONTROL, "nav.find", "Find"}; -} - -} // namespace mirage -``` - -### 11.3 快捷键管理器 - -**文件**: `src/widget/widget_event/shortcut_manager.h` - -```cpp -#pragma once -#include "shortcut_types.h" -#include -#include -#include - -namespace mirage { - -/// 快捷键冲突信息 -struct shortcut_conflict { - shortcut existing; - shortcut new_shortcut; - std::string message; -}; - -/// 快捷键管理器 -class shortcut_manager { -public: - shortcut_manager() = default; - - // 快捷键注册 - std::optional register_shortcut(shortcut_binding binding); - - std::optional register_shortcut( - const shortcut& sc, - shortcut_callback callback, - int32_t priority = 0, - const std::string& scope = "global" - ); - - bool unregister_shortcut(const std::string& id); - void unregister_scope(const std::string& scope); - void clear(); - - // 快捷键查询 - [[nodiscard]] std::optional find_shortcut( - key_code key, key_mod modifiers - ) const; - - [[nodiscard]] std::optional get_shortcut( - const std::string& id - ) const; - - [[nodiscard]] std::vector get_all_shortcuts() const; - - // 快捷键执行 - bool try_execute(key_code key, key_mod modifiers); - - // 启用/禁用 - void enable_shortcut(const std::string& id); - void disable_shortcut(const std::string& id); - void enable_scope(const std::string& scope); - void disable_scope(const std::string& scope); - - // 冲突检测 - [[nodiscard]] bool is_registered(key_code key, key_mod modifiers) const; - void set_allow_override(bool allow) noexcept { allow_override_ = allow; } - -private: - [[nodiscard]] static uint32_t make_key(key_code key, key_mod modifiers) noexcept; - void sort_by_priority(); - - std::unordered_map> bindings_; - std::unordered_map id_to_key_; - bool allow_override_{false}; -}; - -} // namespace mirage -``` - -### 11.4 与事件系统集成 - -```cpp -class widget_event_router { -public: - // ... 现有接口 ... - - // 快捷键管理 - [[nodiscard]] shortcut_manager& get_shortcut_manager() noexcept { - return shortcut_manager_; - } - - void register_global_shortcut(const shortcut& sc, shortcut_callback callback) { - shortcut_manager_.register_shortcut(sc, std::move(callback), 1000, "global"); - } - -private: - shortcut_manager shortcut_manager_; - - // 处理快捷键 - bool handle_shortcuts(const keyboard_event& event); -}; - -// 在 handle_keyboard_event 中集成快捷键处理 -void widget_event_router::handle_keyboard_event(...) { - keyboard_event event; - // ... 构建事件 ... - - // 1. 首先检查快捷键(仅处理 KEY_PRESSED 且非重复) - if (type == event_type::KEY_PRESSED && !repeat) { - if (handle_shortcuts(event)) { - return; // 快捷键已处理 - } - } - - // 2. Tab 导航 - // 3. 分发到焦点控件 - // ... -} -``` - -### 11.5 使用示例 - -```cpp -// 注册全局快捷键 -void setup_shortcuts(widget_event_router& router) { - auto& sm = router.get_shortcut_manager(); - - sm.register_shortcut(shortcuts::save, [&]() { - save_current_document(); - }, 1000, "file"); - - sm.register_shortcut(shortcuts::undo, [&]() { - if (auto* editor = get_focused_editor()) { - editor->undo(); - } - }, 500, "edit"); -} - -// 控件级快捷键 -class code_editor : public event_target { -public: - void on_key_down(keyboard_event& event) override { - // Ctrl+D 复制行 - if (event.key == key_code::D && - has_mod(event.modifiers, key_mod::CONTROL)) { - duplicate_line(); - event.handled = true; - return; - } - // Ctrl+/ 切换注释 - if (event.key == key_code::SLASH && - has_mod(event.modifiers, key_mod::CONTROL)) { - toggle_comment(); - event.handled = true; - return; - } - } -}; -``` - ---- - -## 实现步骤 - -### 阶段一:基础设施(第1-2天) - -1. **修改窗口事件类型** - - 在 `event_types.h` 添加 `CHAR_INPUT` 事件类型 - - 添加 `char_event_data` 结构 - -2. **添加 GLFW 字符回调**(如果尚未实现) - - 在 `glfw_window.cpp` 中注册 `glfwSetCharCallback` - - 转换为 mirage 事件格式 - -3. **创建焦点管理器** - - 创建 `focus_manager.h` 和 `focus_manager.cpp` - - 实现基本的焦点设置和查询功能 - -### 阶段二:事件目标扩展(第3天) - -4. **扩展 event_target** - - 添加键盘事件虚函数 - - 添加焦点相关虚函数 - - 添加焦点辅助方法 - -5. **添加控件键盘事件类型** - - 在 `widget_event_types.h` 添加 `widget_keyboard_event` - - 添加 `widget_char_event` - -### 阶段三:事件路由(第4-5天) - -6. **扩展 widget_event_router** - - 添加 `focus_manager` 成员 - - 实现 `handle_keyboard_event` - - 实现 `handle_char_input` - - 实现键盘事件分发逻辑 - -7. **集成到 application** - - 在 `handle_event` 中添加键盘事件处理 - -### 阶段四:Tab 导航(第6天) - -8. **实现 Tab 导航** - - 实现 `move_focus_next` - - 实现 Tab 停靠点注册 - - 处理 Tab/Shift+Tab 快捷键 - -### 阶段五:鼠标焦点集成(第7天) - -9. **鼠标点击自动聚焦** - - 在 `handle_mouse_event` 中添加焦点设置逻辑 - - 处理可聚焦控件的点击 - -### 阶段六:测试和文档(第8天) - -10. **测试** - - 创建测试用例 - - 验证键盘事件分发 - - 验证焦点管理 - - 验证 Tab 导航 - ---- - -## 使用示例 - -### 创建可聚焦的文本输入控件 - -```cpp -class text_input : public widget_base, public event_target { -public: - text_input() = default; - - // ======================================================================== - // event_target 实现 - // ======================================================================== - - bool contains_point(double gx, double gy) const override { - auto aabb = get_global_aabb(); - return aabb.contains(vec2f_t(gx, gy)); - } - - std::pair global_to_local(double gx, double gy) const override { - auto pos = get_global_position(); - return {gx - pos.x(), gy - pos.y()}; - } - - bool is_focusable() const override { return true; } - int32_t get_tab_index() const override { return tab_index_; } - - // ======================================================================== - // 键盘事件处理 - // ======================================================================== - - void on_key_down(keyboard_event& event) override { - switch (event.key) { - case key_code::BACKSPACE: - if (!text_.empty()) { - text_.pop_back(); - mark_render_dirty(); - } - event.handled = true; - break; - - case key_code::ENTER: - if (on_submit_) { - on_submit_(text_); - } - event.handled = true; - break; - - default: - break; - } - } - - void on_char_input(char_event& event) override { - // 添加字符到文本 - // 需要将 UTF-32 转换为 UTF-8 - text_ += char32_to_utf8(event.codepoint); - mark_render_dirty(); - event.handled = true; - } - - // ======================================================================== - // 焦点事件处理 - // ======================================================================== - - void on_focus_gained(focus_change_reason reason) override { - focused_ = true; - cursor_visible_ = true; - mark_render_dirty(); - } - - void on_focus_lost(focus_change_reason reason) override { - focused_ = false; - cursor_visible_ = false; - mark_render_dirty(); - } - -private: - std::string text_; - bool focused_{false}; - bool cursor_visible_{false}; - int32_t tab_index_{0}; - std::function on_submit_; -}; -``` - -### 在应用程序中使用 - -```cpp -// 创建文本输入控件 -auto input1 = std::make_shared(); -input1->tab_index(1); - -auto input2 = std::make_shared(); -input2->tab_index(2); - -// 设置为根控件的子控件 -root->add_child(input1); -root->add_child(input2); - -// 程序化设置焦点 -app.get_coordinator()->get_event_router().set_focus( - input1->as_event_target(), - focus_change_reason::PROGRAMMATIC -); -``` - ---- - -## 总结 - -本设计文档描述了 Mirage 框架的键盘事件系统架构。主要特点: - -1. **与现有系统一致** - 遵循现有鼠标事件系统的设计模式 -2. **焦点管理** - 提供完整的焦点管理功能,包括 Tab 导航 -3. **事件冒泡** - 键盘事件从焦点控件向上冒泡 -4. **可扩展** - 通过虚函数设计,控件可以选择性处理事件 -5. **线程安全** - 事件在主线程处理,状态可同步到其他线程 - -### 关键设计决策 - -| 决策 | 理由 | -|------|------| -| 焦点管理器独立于事件路由器 | 职责分离,便于测试和维护 | -| 使用焦点链进行事件冒泡 | 与 DOM 事件模型一致,用户熟悉 | -| Tab 顺序使用整数索引 | 简单且灵活,支持动态插入 | -| 字符输入独立事件 | 区分按键事件和文本输入,支持 IME | -| 鼠标点击自动聚焦 | 符合用户预期的交互模式 | \ No newline at end of file diff --git a/docs/MTSDF_LOCK_OPTIMIZATION.md b/docs/MTSDF_LOCK_OPTIMIZATION.md deleted file mode 100644 index 0b8ffe7..0000000 --- a/docs/MTSDF_LOCK_OPTIMIZATION.md +++ /dev/null @@ -1,403 +0,0 @@ -# MTSDF纹理多线程锁优化方案 - -## 概述 - -本文档描述了对`async_mtsdf_generator`和`glyph_cache`的多线程锁优化方案,目标是使用C++23标准特性提升响应速度,减少锁竞争。 - -## 问题分析 - -### 1. async_mtsdf_generator 当前问题 - -**问题1: mpsc_queue误用导致的锁竞争** - -```cpp -// 当前实现 (async_mtsdf_generator.cpp:156-174) -void worker_loop(worker_context* worker, std::stop_token stop_token) { - while (!stop_token.stop_requested()) { - std::optional task_opt; - { - // ❌ 所有worker线程竞争同一把锁 - std::unique_lock lock(task_queue_mutex); - - // ❌ condition_variable开销大,可能产生虚假唤醒 - task_available_cv.wait(lock, [this, &stop_token] { - return stop_token.stop_requested() || !task_queue.empty(); - }); - - task_opt = task_queue.try_pop(); - } - // ... - } -} -``` - -**问题分析**: -- `mpsc_queue` 设计为多生产者单消费者,但这里有N个worker同时消费 -- 必须用mutex保护消费操作,完全抵消了无锁队列的优势 -- N个worker线程竞争同一把mutex,造成严重的锁竞争 -- `condition_variable::wait()` 需要持有锁,唤醒后需要重新获取锁 - -**问题2: 取消任务的锁开销** - -```cpp -// 当前实现 (async_mtsdf_generator.cpp:138-153) -auto is_cancelled(uint64_t task_id) -> bool { - std::lock_guard lock(cancel_mutex); // ❌ 每次检查都加锁 - return cancelled_tasks.contains(task_id); -} -``` - -### 2. glyph_cache 当前问题 - -**问题: O(n)遍历查找pending任务** - -```cpp -// 当前实现 (glyph_cache.cpp:191-204) -{ - std::shared_lock lock(task_mutex_); - // ❌ O(n)遍历所有pending任务 - for (const auto& [task_id, pending] : task_to_key_) { - if (pending.user_key == user_key) { - return glyph_lookup_result{...}; - } - } -} -``` - ---- - -## 优化方案 - -### 方案1: 每Worker独立队列 + Round-Robin分发 - -#### 架构设计 - -``` -提交任务 ──► Round-Robin分发器 ──┬──► Worker 0 [spsc_queue] ──► atomic wait - ├──► Worker 1 [spsc_queue] ──► atomic wait - └──► Worker N [spsc_queue] ──► atomic wait -``` - -#### 数据结构 - -```cpp -struct worker_context { - uint32_t worker_id; - std::jthread thread; - FT_Library ft_library{nullptr}; - std::unordered_map font_cache; - - // 每个worker独立的无锁队列 - spsc_queue task_queue; - - // C++20 atomic wait/notify 替代 condition_variable - std::atomic pending_count{0}; - - // ... 构造函数和方法 ... -}; -``` - -#### Worker循环优化 - -```cpp -void worker_loop(worker_context* worker, std::stop_token stop_token) { - while (!stop_token.stop_requested()) { - // 尝试从本地队列获取任务(无锁) - auto task_opt = worker->task_queue.try_pop(); - - if (!task_opt.has_value()) { - // 无任务,使用atomic wait等待 - uint32_t current = worker->pending_count.load(std::memory_order_acquire); - if (current == 0) { - // C++20: 无锁等待,比condition_variable更轻量 - worker->pending_count.wait(0, std::memory_order_relaxed); - } - continue; - } - - // 减少pending计数 - worker->pending_count.fetch_sub(1, std::memory_order_relaxed); - - // 执行任务... - } -} -``` - -#### 任务提交优化 - -```cpp -auto async_mtsdf_generator::submit(glyph_generation_request request) -> glyph_task_id { - const uint64_t id = next_task_id_.fetch_add(1, std::memory_order_relaxed); - - internal_task task{ - .task_id = glyph_task_id{id}, - .request = std::move(request) - }; - - // Round-Robin选择worker(无锁) - const std::size_t worker_idx = next_worker_index_.fetch_add(1, std::memory_order_relaxed) - % workers_.size(); - auto& worker = workers_[worker_idx]; - - // 推送到worker的spsc队列(无锁,单生产者) - while (!worker->task_queue.try_push(std::move(task))) { - // 队列满,尝试下一个worker(可选) - std::this_thread::yield(); - } - - // 增加pending计数并唤醒worker - worker->pending_count.fetch_add(1, std::memory_order_release); - worker->pending_count.notify_one(); // C++20: 精确唤醒单个worker - - pending_tasks_.fetch_add(1, std::memory_order_relaxed); - return glyph_task_id{id}; -} -``` - -### 方案2: 取消机制优化 - -#### 使用per-task atomic_flag - -```cpp -struct internal_task { - glyph_task_id task_id; - glyph_generation_request request; - std::shared_ptr cancelled; // 每任务独立的取消标志 -}; - -// 取消检查 - 无锁 -auto is_cancelled(const internal_task& task) -> bool { - return task.cancelled && task.cancelled->test(std::memory_order_relaxed); -} - -// 取消操作 - 无锁 -auto cancel(glyph_task_id id) -> bool { - std::shared_lock lock(task_registry_mutex_); - auto it = task_registry_.find(id); - if (it == task_registry_.end()) return false; - - it->second->test_and_set(std::memory_order_relaxed); - return true; -} -``` - -### 方案3: glyph_cache pending查找优化 - -#### 添加反向索引 - -```cpp -class glyph_cache { -private: - // 原有:task_id -> pending_task - std::unordered_map task_to_key_; - - // 新增:user_key -> task_id (反向索引) - std::unordered_map key_to_task_; - - mutable std::shared_mutex task_mutex_; // 保护两个map -}; - -// 优化后的查找 - O(1) -auto find_pending_task(atlas_user_key user_key) -> std::optional { - std::shared_lock lock(task_mutex_); - auto it = key_to_task_.find(user_key); - return (it != key_to_task_.end()) ? std::optional{it->second} : std::nullopt; -} -``` - ---- - -## 详细实现计划 - -### 阶段1: async_mtsdf_generator 重构 - -#### 1.1 新增数据结构 - -```cpp -// 新的impl结构 -struct async_mtsdf_generator::impl { - // Worker上下文(每个worker独立队列) - struct worker_context { - uint32_t worker_id; - std::jthread thread; - FT_Library ft_library{nullptr}; - std::unordered_map font_cache; - - // 独立的spsc队列 - spsc_queue task_queue; - - // atomic wait/notify - std::atomic pending_count{0}; - - // 构造/析构... - }; - - std::vector> workers_; - - // 结果队列(保持mpsc,因为多个worker生产,单消费者收集) - mpsc_queue result_queue_; - - // 任务计数 - std::atomic next_task_id_{1}; - std::atomic pending_tasks_{0}; - std::atomic next_worker_index_{0}; - - // 任务注册表(用于取消) - std::shared_mutex task_registry_mutex_; - std::unordered_map> task_registry_; -}; -``` - -#### 1.2 删除的代码 - -```cpp -// 删除以下成员 -std::mutex task_queue_mutex; // 不再需要 -std::condition_variable task_available_cv; // 用atomic wait替代 -std::mutex cancel_mutex; // 用per-task atomic替代 -std::unordered_set cancelled_tasks; // 用atomic_flag替代 -``` - -### 阶段2: glyph_cache 优化 - -#### 2.1 添加反向索引 - -```cpp -// glyph_cache.h 新增成员 -private: - std::unordered_map key_to_task_; -``` - -#### 2.2 修改任务提交/完成逻辑 - -```cpp -// 提交时同时更新两个map -void register_pending_task(glyph_task_id task_id, atlas_user_key user_key) { - std::unique_lock lock(task_mutex_); - task_to_key_[task_id] = pending_task{.user_key = user_key, ...}; - key_to_task_[user_key] = task_id; -} - -// 完成时同时删除两个map -void unregister_pending_task(glyph_task_id task_id) { - std::unique_lock lock(task_mutex_); - auto it = task_to_key_.find(task_id); - if (it != task_to_key_.end()) { - key_to_task_.erase(it->second.user_key); - task_to_key_.erase(it); - } -} -``` - ---- - -## 性能对比预估 - -| 操作 | 当前延迟 | 优化后延迟 | 改善 | -|------|----------|------------|------| -| 任务提交 | ~1-5μs (mutex竞争) | ~50-100ns (无锁) | 10-50x | -| Worker等待 | ~10-50μs (CV唤醒) | ~1-5μs (atomic wait) | 10x | -| 取消检查 | ~100-500ns (mutex) | ~10-20ns (atomic) | 10-25x | -| pending查找 | O(n) ~1-10μs | O(1) ~50ns | 20-200x | - ---- - -## 线程安全分析 - -### 生产者-消费者关系 - -``` - ┌─────────────────────────────────────┐ - │ async_mtsdf_generator │ - │ │ - submit() ───────►│ Round-Robin ──► Worker 0 spsc │──► result_queue (mpsc) - (主线程) │ ──► Worker 1 spsc │ │ - │ ──► Worker N spsc │ ▼ - └─────────────────────────────────────┘ poll_completed() - (主线程) -``` - -### 队列选择依据 - -| 队列 | 生产者 | 消费者 | 类型 | -|------|--------|--------|------| -| Worker task_queue | 单(submit线程) | 单(worker线程) | spsc_queue | -| result_queue | 多(N个worker) | 单(poll_completed) | mpsc_queue | - ---- - -## 文件修改清单 - -### 需要修改的文件 - -1. **`src/render/text/async_mtsdf_generator.h`** - - 保持公共接口不变 - - 内部impl结构重构 - -2. **`src/render/text/async_mtsdf_generator.cpp`** - - 重写worker_loop使用atomic wait - - 重写submit使用Round-Robin + spsc队列 - - 重写cancel使用per-task atomic_flag - -3. **`src/render/text/glyph_cache.h`** - - 添加`key_to_task_`反向索引成员 - -4. **`src/render/text/glyph_cache.cpp`** - - 优化`get_or_request_glyph`中的pending查找 - - 更新任务注册/注销逻辑 - -### 不需要修改的文件 - -- `src/common/threading/message_queue.h` - spsc_queue/mpsc_queue实现已完备 -- `src/render/text/mtsdf_generator.h/cpp` - MTSDF生成逻辑不变 -- 公共API保持完全向后兼容 - ---- - -## 测试计划 - -### 单元测试 - -1. **spsc队列正确性测试** - - 单生产者单消费者场景 - - 边界条件:队列满、队列空 - -2. **atomic wait/notify测试** - - 基本等待/唤醒 - - 多worker并发场景 - -3. **取消机制测试** - - 取消未开始的任务 - - 取消正在执行的任务 - -### 性能测试 - -1. **吞吐量测试** - - 提交10000个任务,测量总完成时间 - - 对比优化前后 - -2. **延迟测试** - - 测量单个任务从提交到开始执行的延迟 - - 测量结果收集延迟 - -3. **竞争测试** - - 多线程并发提交 - - 压力测试下的稳定性 - ---- - -## 风险评估 - -| 风险 | 可能性 | 影响 | 缓解措施 | -|------|--------|------|----------| -| spsc队列满导致任务丢失 | 低 | 高 | 队列满时spin等待或扩容 | -| atomic wait平台兼容性 | 低 | 中 | C++20标准,主流编译器支持 | -| 性能回退 | 低 | 中 | 保留旧实现作为fallback | - ---- - -## 参考资料 - -- [C++20 atomic wait/notify](https://en.cppreference.com/w/cpp/atomic/atomic/wait) -- [Lock-free programming patterns](https://www.cs.cmu.edu/~410-f17/lectures/L31_LockFree.pdf) -- [SPSC Queue implementation](https://rigtorp.se/ringbuffer/) \ No newline at end of file diff --git a/docs/MULTI_WINDOW_DOCK_SYSTEM_DESIGN.md b/docs/MULTI_WINDOW_DOCK_SYSTEM_DESIGN.md deleted file mode 100644 index 219f23b..0000000 --- a/docs/MULTI_WINDOW_DOCK_SYSTEM_DESIGN.md +++ /dev/null @@ -1,1228 +0,0 @@ -# 多窗口与Dock系统架构设计 - -## 1. 概述 - -本文档描述了在Mirage UI框架中实现完整的IDE风格Dock系统和多窗口管理的架构设计。 - -### 1.1 设计目标 - -1. **完整的IDE风格Dock系统** - - 支持左/右/上/下四个方向的停靠区域 - - 支持标签页分组(多个面板共享同一区域) - - 可拖拽分隔条调整面板大小 - - 支持面板拖出成为浮动窗口 - - 支持浮动窗口重新停靠 - -2. **跨平台多窗口管理** - - 统一的窗口抽象(window_widget) - - 支持主窗口和浮动窗口 - - 每个窗口独立的widget树和事件路由 - - 共享的Vulkan资源和渲染上下文 - -### 1.2 现有架构分析 - -**关键发现:** -- [`widget_context`](src/widget/widget_context.h:157) 已持有 `window_interface*` -- [`window_manager`](src/window/window_manager.h:16) 已支持多窗口管理 -- widget通过 `init(widget_context*)` 绑定上下文 - ---- - -## 2. 整体架构 - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ application │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ multi_window_manager │ │ -│ │ ┌─────────────────┬─────────────────┬─────────────────┐ │ │ -│ │ │ main_window │ float_window_1 │ float_window_2 │ │ │ -│ │ │ window_widget │ window_widget │ window_widget │ │ │ -│ │ └────────┬────────┴────────┬────────┴────────┬────────┘ │ │ -│ │ │ │ │ │ │ -│ │ ▼ ▼ ▼ │ │ -│ │ widget_context widget_context widget_context │ │ -│ │ │ │ │ │ │ -│ │ ▼ ▼ ▼ │ │ -│ │ dock_container panel_content panel_content │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ shared_render_context │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ shared_state_store │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -### 2.1 类层次结构 - -``` -widget_base - │ - ├── container_widget_base - │ ├── window_widget (新增) - │ │ ├── main_window_widget - │ │ └── floating_window_widget - │ │ - │ ├── dock_container (新增) - │ ├── dock_split_container (新增) - │ ├── dock_tab_container (新增) - │ └── v_stack / h_stack / overlay - │ - └── single_child_widget_base - └── dock_panel (新增) -``` - ---- - -## 3. 核心组件设计 - -### 3.1 dock_types.h - 基础类型定义 - -**文件路径**: `src/widget/dock/dock_types.h` - -```cpp -#pragma once -#include "types.h" -#include -#include -#include -#include - -namespace mirage::dock { - -/// 停靠区域 -enum class dock_area : uint8_t { - left, - right, - top, - bottom, - center, - floating -}; - -/// 停靠方向 -enum class dock_direction : uint8_t { - horizontal, - vertical -}; - -/// 分割位置 -enum class split_position : uint8_t { - before, - after, - left, - right, - top, - bottom -}; - -/// 停靠位置描述 -struct dock_location { - dock_area area = dock_area::center; - std::vector path; // 从区域根到目标的索引路径 - uint32_t tab_index = 0; // 标签页索引 - std::vector split_ratios; // 分割比例 - - [[nodiscard]] bool is_valid() const noexcept; - [[nodiscard]] std::string serialize() const; - static std::optional deserialize(std::string_view str); -}; - -/// 停靠提示 -struct dock_hint { - aabb2d_t rect; - dock_location target_location; - bool highlighted = false; - - enum class hint_type : uint8_t { area, tab, split } type = hint_type::area; -}; - -/// 面板配置 -struct panel_config { - std::string id; - std::string title = "Panel"; - std::string icon; - - bool closable = true; - bool movable = true; - bool floatable = true; - - float min_width = 100.0f; - float min_height = 100.0f; - float preferred_width = 200.0f; - float preferred_height = 200.0f; -}; - -/// 区域配置 -struct area_config { - float default_size = 200.0f; - float min_size = 100.0f; - float max_size = 0.0f; - bool collapsed = false; - bool visible = true; -}; - -/// 浮动窗口配置 -struct floating_window_config { - std::string title = "Floating Window"; - int32_t width = 400; - int32_t height = 300; - int32_t x = -1; - int32_t y = -1; - bool resizable = true; - bool decorated = true; - bool always_on_top = false; -}; - -} // namespace mirage::dock -``` - -### 3.2 window_widget - 窗口控件抽象 - -**文件路径**: `src/widget/window/window_widget.h` - -```cpp -#pragma once -#include "container_widget_base.h" -#include "window_interface.h" -#include "widget_event/widget_event_router.h" -#include - -namespace mirage { - -enum class window_type : uint8_t { - main, - floating, - popup, - tool -}; - -class window_widget : public container_widget_base { -public: - [[nodiscard]] virtual window_type get_window_type() const noexcept = 0; - - [[nodiscard]] window_interface* get_native_window() const noexcept { - return native_window_; - } - - [[nodiscard]] uint32_t get_window_id() const noexcept { - return window_id_; - } - - [[nodiscard]] widget_event_router& get_event_router() noexcept { - return event_router_; - } - - [[nodiscard]] widget_context* get_window_context() const noexcept { - return window_context_.get(); - } - - // 窗口操作委托 - void set_title(std::string_view title); - [[nodiscard]] std::string_view get_title() const; - void set_position(int32_t x, int32_t y); - [[nodiscard]] std::pair get_position() const; - void set_window_size(int32_t width, int32_t height); - [[nodiscard]] vec2i_t get_framebuffer_size() const; - void show(); - void hide(); - void close(); - void request_focus(); - - // 静态辅助方法 - [[nodiscard]] static window_widget* find_parent_window(widget_base* widget); - [[nodiscard]] static window_interface* get_window_for_widget(widget_base* widget); - -protected: - window_widget(window_interface* window, uint32_t window_id); - - [[nodiscard]] vec2f_t do_measure(const vec2f_t& available_size) const override; - void arrange(const layout_state& state) override; - -protected: - window_interface* native_window_ = nullptr; - uint32_t window_id_ = 0; - widget_event_router event_router_; - std::unique_ptr window_context_; -}; - -} // namespace mirage -``` - -**实现文件**: `src/widget/window/window_widget.cpp` - -```cpp -#include "window_widget.h" - -namespace mirage { - -window_widget::window_widget(window_interface* window, uint32_t window_id) - : native_window_(window), window_id_(window_id) {} - -void window_widget::set_title(std::string_view title) { - if (native_window_) native_window_->set_title(title); -} - -std::string_view window_widget::get_title() const { - return native_window_ ? native_window_->get_title() : ""; -} - -void window_widget::set_position(int32_t x, int32_t y) { - if (native_window_) native_window_->set_position(x, y); -} - -std::pair window_widget::get_position() const { - return native_window_ ? native_window_->get_position() : std::make_pair(0, 0); -} - -void window_widget::set_window_size(int32_t width, int32_t height) { - if (native_window_) native_window_->set_size(width, height); -} - -vec2i_t window_widget::get_framebuffer_size() const { - return native_window_ ? native_window_->get_framebuffer_size() : vec2i_t::Zero(); -} - -void window_widget::show() { - if (native_window_) native_window_->show(); -} - -void window_widget::hide() { - if (native_window_) native_window_->hide(); -} - -void window_widget::close() { - if (native_window_) native_window_->set_should_close(true); -} - -void window_widget::request_focus() { - if (native_window_) native_window_->focus(); -} - -window_widget* window_widget::find_parent_window(widget_base* widget) { - while (widget) { - if (auto* ww = dynamic_cast(widget)) { - return ww; - } - widget = widget->get_parent(); - } - return nullptr; -} - -window_interface* window_widget::get_window_for_widget(widget_base* widget) { - if (auto* ww = find_parent_window(widget)) { - return ww->get_native_window(); - } - return nullptr; -} - -vec2f_t window_widget::do_measure(const vec2f_t& available_size) const { - if (native_window_) { - auto fb_size = native_window_->get_framebuffer_size(); - return vec2f_t(static_cast(fb_size.x()), - static_cast(fb_size.y())); - } - return available_size; -} - -void window_widget::arrange(const layout_state& state) { - if (auto* ctx = get_context()) { - auto write_ctx = ctx->state_store().begin_layout_update(ctx->current_frame()); - write_ctx.update_layout(get_widget_id(), state); - write_ctx.commit(); - } - - for (auto& child : children_) { - if (child->should_render()) { - child->arrange(state.derive_child(vec2f_t::Zero(), state.size())); - } - } -} - -} // namespace mirage -``` - -### 3.3 main_window_widget - 主窗口 - -**文件路径**: `src/widget/window/main_window_widget.h` - -```cpp -#pragma once -#include "window_widget.h" -#include "dock/dock_container.h" - -namespace mirage { - -class main_window_widget : public window_widget { -public: - static std::shared_ptr create( - window_interface* window, - uint32_t window_id, - state::widget_state_store& state_store, - render::text::text_shaper& text_shaper); - - [[nodiscard]] window_type get_window_type() const noexcept override { - return window_type::main; - } - - [[nodiscard]] dock::dock_container* get_dock_container() const noexcept { - return dock_container_; - } - - void set_dock_container(dock::dock_container* container) { - dock_container_ = container; - } - -private: - explicit main_window_widget(window_interface* window, uint32_t window_id); - dock::dock_container* dock_container_ = nullptr; -}; - -} // namespace mirage -``` - -### 3.4 floating_window_widget - 浮动窗口 - -**文件路径**: `src/widget/window/floating_window_widget.h` - -```cpp -#pragma once -#include "window_widget.h" -#include "dock/dock_types.h" - -namespace mirage { - -class floating_window_widget : public window_widget { -public: - static std::shared_ptr create( - const dock::floating_window_config& config, - window_manager& wm, - state::widget_state_store& state_store, - render::text::text_shaper& text_shaper); - - [[nodiscard]] window_type get_window_type() const noexcept override { - return window_type::floating; - } - - [[nodiscard]] window_widget* get_source_window() const noexcept { - return source_window_; - } - - void set_source_window(window_widget* source) noexcept { - source_window_ = source; - } - - [[nodiscard]] const dock::dock_location& get_original_location() const noexcept { - return original_location_; - } - - void set_original_location(const dock::dock_location& loc) noexcept { - original_location_ = loc; - } - -private: - explicit floating_window_widget(window_interface* window, uint32_t window_id); - - window_widget* source_window_ = nullptr; - dock::dock_location original_location_; -}; - -} // namespace mirage -``` - -### 3.5 dock_panel - 可停靠面板 - -**文件路径**: `src/widget/dock/dock_panel.h` - -```cpp -#pragma once -#include "single_child_widget_base.h" -#include "dock_types.h" -#include - -namespace mirage::dock { - -class dock_panel : public single_child_widget_base { -public: - using close_callback = std::function; - using focus_callback = std::function; - - static std::shared_ptr create(const panel_config& config); - - // 属性访问 - [[nodiscard]] const std::string& get_panel_id() const noexcept; - [[nodiscard]] const std::string& get_title() const noexcept; - void set_title(std::string title); - [[nodiscard]] const std::string& get_icon() const noexcept; - [[nodiscard]] bool is_closable() const noexcept; - [[nodiscard]] bool is_movable() const noexcept; - [[nodiscard]] bool is_floatable() const noexcept; - [[nodiscard]] const panel_config& get_config() const noexcept; - - // 状态 - [[nodiscard]] bool is_active() const noexcept; - void set_active(bool active); - [[nodiscard]] const dock_location& get_location() const noexcept; - void set_location(const dock_location& loc); - - // 回调 - void set_close_callback(close_callback cb); - void set_focus_callback(focus_callback cb); - bool request_close(); - - // 内容管理 - void set_content(std::shared_ptr content); - [[nodiscard]] widget_base* get_content() const; - -protected: - explicit dock_panel(const panel_config& config); - - [[nodiscard]] vec2f_t do_measure(const vec2f_t& available_size) const override; - void arrange(const layout_state& state) override; - int32_t build_render_command(render_collector& collector, int32_t z_order) const override; - -private: - static constexpr float title_bar_height_ = 28.0f; - - panel_config config_; - dock_location current_location_; - bool is_active_ = false; - - close_callback close_callback_; - focus_callback focus_callback_; -}; - -} // namespace mirage::dock -``` - -### 3.6 dock_tab_container - 标签页容器 - -**文件路径**: `src/widget/dock/dock_tab_container.h` - -```cpp -#pragma once -#include "container_widget_base.h" -#include "dock_panel.h" -#include -#include - -namespace mirage::dock { - -class dock_tab_container : public container_widget_base { -public: - using tab_change_callback = std::function; - using tab_close_callback = std::function; - using tab_drag_callback = std::function; - - enum class tab_bar_position : uint8_t { top, bottom }; - - dock_tab_container() = default; - - // 标签页管理 - void add_panel(std::shared_ptr panel, int32_t index = -1); - std::shared_ptr remove_panel(dock_panel* panel); - std::shared_ptr remove_panel_at(size_t index); - [[nodiscard]] size_t get_panel_count() const noexcept; - [[nodiscard]] dock_panel* get_panel(size_t index) const; - [[nodiscard]] dock_panel* find_panel(const std::string& id) const; - [[nodiscard]] std::optional get_panel_index(dock_panel* panel) const; - - // 活动标签页 - [[nodiscard]] size_t get_active_index() const noexcept; - [[nodiscard]] dock_panel* get_active_panel() const; - void set_active_index(size_t index); - void set_active_panel(dock_panel* panel); - - // 回调 - void set_tab_change_callback(tab_change_callback cb); - void set_tab_close_callback(tab_close_callback cb); - void set_tab_drag_callback(tab_drag_callback cb); - - // 配置 - void set_tab_bar_position(tab_bar_position pos); - void set_tab_bar_height(float height); - -protected: - [[nodiscard]] vec2f_t do_measure(const vec2f_t& available_size) const override; - void arrange(const layout_state& state) override; - int32_t build_render_command(render_collector& collector, int32_t z_order) const override; - [[nodiscard]] std::vector get_event_children() const override; - -private: - std::vector> panels_; - size_t active_index_ = 0; - - tab_bar_position tab_bar_position_ = tab_bar_position::top; - float tab_bar_height_ = 28.0f; - - tab_change_callback tab_change_callback_; - tab_close_callback tab_close_callback_; - tab_drag_callback tab_drag_callback_; -}; - -} // namespace mirage::dock -``` - -### 3.7 dock_split_container - 分割容器 - -**文件路径**: `src/widget/dock/dock_split_container.h` - -```cpp -#pragma once -#include "container_widget_base.h" -#include "dock_types.h" -#include - -namespace mirage::dock { - -struct split_item { - std::shared_ptr widget; - float ratio = 1.0f; - float min_size = 50.0f; -}; - -class dock_split_container : public container_widget_base { -public: - using resize_callback = std::function; - - explicit dock_split_container(dock_direction direction = dock_direction::horizontal); - - // 子项管理 - void add_item(std::shared_ptr widget, float ratio = 1.0f, float min_size = 50.0f); - void insert_item(size_t index, std::shared_ptr widget, float ratio = 1.0f, float min_size = 50.0f); - std::shared_ptr remove_item(size_t index); - [[nodiscard]] size_t get_item_count() const noexcept; - [[nodiscard]] widget_base* get_item(size_t index) const; - [[nodiscard]] float get_ratio(size_t index) const; - void set_ratio(size_t index, float ratio); - - // 分隔条操作 - [[nodiscard]] size_t get_separator_count() const noexcept; - [[nodiscard]] float get_separator_position(size_t index) const; - void drag_separator(size_t index, float delta); - - // 配置 - [[nodiscard]] dock_direction get_direction() const noexcept; - void set_direction(dock_direction dir); - [[nodiscard]] bool is_horizontal() const noexcept; - void set_separator_size(float size); - [[nodiscard]] float get_separator_size() const noexcept; - void set_resize_callback(resize_callback cb); - -protected: - void init(widget_context* ctx) override; - [[nodiscard]] vec2f_t do_measure(const vec2f_t& available_size) const override; - void arrange(const layout_state& state) override; - int32_t build_render_command(render_collector& collector, int32_t z_order) const override; - [[nodiscard]] std::vector get_event_children() const override; - -private: - void normalize_ratios(); - - dock_direction direction_ = dock_direction::horizontal; - std::vector items_; - float separator_size_ = 4.0f; - mutable std::vector cached_sizes_; - resize_callback resize_callback_; -}; - -} // namespace mirage::dock -``` - -### 3.8 dock_container - Dock根容器 - -**文件路径**: `src/widget/dock/dock_container.h` - -```cpp -#pragma once -#include "container_widget_base.h" -#include "dock_split_container.h" -#include "dock_tab_container.h" -#include "dock_panel.h" -#include "dock_types.h" -#include -#include - -namespace mirage::dock { - -class dock_container : public container_widget_base { -public: - using panel_dock_callback = std::function; - using panel_undock_callback = std::function; - using layout_change_callback = std::function; - - dock_container(); - - // 区域配置 - void configure_area(dock_area area, const area_config& config); - [[nodiscard]] const area_config& get_area_config(dock_area area) const; - void set_area_visible(dock_area area, bool visible); - void set_area_collapsed(dock_area area, bool collapsed); - void set_area_size(dock_area area, float size); - [[nodiscard]] float get_area_size(dock_area area) const; - - // 面板管理 - void dock_panel(std::shared_ptr panel, const dock_location& location); - std::shared_ptr undock_panel(dock_panel* panel); - void move_panel(dock_panel* panel, const dock_location& new_location); - [[nodiscard]] dock_panel* find_panel(const std::string& id) const; - [[nodiscard]] std::vector get_all_panels() const; - - // 拖放支持 - [[nodiscard]] std::vector get_dock_hints(const vec2f_t& position) const; - [[nodiscard]] std::optional hit_test_dock_target(const vec2f_t& position) const; - void show_dock_preview(const dock_location& location); - void hide_dock_preview(); - - // 布局持久化 - [[nodiscard]] std::string serialize_layout() const; - bool deserialize_layout(std::string_view data); - void reset_layout(); - - // 回调 - void set_panel_dock_callback(panel_dock_callback cb); - void set_panel_undock_callback(panel_undock_callback cb); - void set_layout_change_callback(layout_change_callback cb); - -protected: - void init(widget_context* ctx) override; - [[nodiscard]] vec2f_t do_measure(const vec2f_t& available_size) const override; - void arrange(const layout_state& state) override; - int32_t build_render_command(render_collector& collector, int32_t z_order) const override; - [[nodiscard]] std::vector get_event_children() const override; - -private: - void build_layout_structure(); - [[nodiscard]] dock_tab_container* find_tab_container(const dock_location& location) const; - [[nodiscard]] dock_tab_container* get_or_create_tab_container(dock_area area); - - std::unordered_map area_configs_; - std::unordered_map> area_containers_; - - std::shared_ptr main_split_; // 主分割(center vs sides) - std::shared_ptr center_split_; // 中央垂直分割 - std::shared_ptr left_split_; // 左侧区域分割 - std::shared_ptr right_split_; // 右侧区域分割 - - std::unordered_map> all_panels_; - - std::optional preview_location_; - - panel_dock_callback dock_callback_; - panel_undock_callback undock_callback_; - layout_change_callback layout_change_callback_; -}; - -} // namespace mirage::dock -``` - ---- - -## 4. 拖拽与停靠机制 - -### 4.1 dock_drag_manager - 拖拽管理器 - -**文件路径**: `src/widget/dock/dock_drag_manager.h` - -```cpp -#pragma once -#include "dock_types.h" -#include "dock_panel.h" -#include "widget_base.h" -#include -#include - -namespace mirage::dock { - -enum class drag_state : uint8_t { - idle, - dragging_tab, - dragging_separator, - dragging_window -}; - -struct drag_context { - drag_state state = drag_state::idle; - dock_panel* dragged_panel = nullptr; - vec2f_t start_position; - vec2f_t current_position; - vec2f_t offset; - - // 分隔条拖拽 - dock_split_container* split_container = nullptr; - size_t separator_index = 0; - float initial_ratio = 0.0f; - - // 停靠预览 - std::optional preview_location; - std::vector current_hints; -}; - -class dock_drag_manager { -public: - using undock_callback = std::function; - using dock_callback = std::function; - using create_float_callback = std::function; - - dock_drag_manager() = default; - - // 拖拽控制 - void begin_tab_drag(dock_panel* panel, const vec2f_t& start_pos, const vec2f_t& offset); - void begin_separator_drag(dock_split_container* container, size_t index, const vec2f_t& start_pos); - void update_drag(const vec2f_t& position); - void end_drag(); - void cancel_drag(); - - // 状态查询 - [[nodiscard]] drag_state get_state() const noexcept; - [[nodiscard]] bool is_dragging() const noexcept; - [[nodiscard]] const drag_context& get_context() const noexcept; - - // 停靠目标 - void set_dock_container(dock_container* container); - void add_dock_target(dock_container* container); - void remove_dock_target(dock_container* container); - - // 回调设置 - void set_undock_callback(undock_callback cb); - void set_dock_callback(dock_callback cb); - void set_create_float_callback(create_float_callback cb); - -private: - void update_tab_drag(const vec2f_t& position); - void update_separator_drag(const vec2f_t& position); - void finish_tab_drag(); - void finish_separator_drag(); - - drag_context context_; - std::vector dock_targets_; - - undock_callback undock_callback_; - dock_callback dock_callback_; - create_float_callback create_float_callback_; - - float undock_threshold_ = 20.0f; // 拖出阈值(像素) -}; - -} // namespace mirage::dock -``` - ---- - -## 5. 多窗口管理器 - -### 5.1 multi_window_manager - 多窗口管理器 - -**文件路径**: `src/widget/window/multi_window_manager.h` - -```cpp -#pragma once -#include "window_widget.h" -#include "main_window_widget.h" -#include "floating_window_widget.h" -#include "window_manager.h" -#include "dock/dock_drag_manager.h" -#include -#include -#include -#include - -namespace mirage { - -class multi_window_manager { -public: - using window_create_callback = std::function; - using window_close_callback = std::function; - using window_focus_callback = std::function; - - explicit multi_window_manager( - window_manager& wm, - state::widget_state_store& state_store, - render::text::text_shaper& text_shaper); - - ~multi_window_manager(); - - // 主窗口管理 - std::shared_ptr create_main_window( - const window_create_info& info); - [[nodiscard]] main_window_widget* get_main_window() const noexcept; - - // 浮动窗口管理 - std::shared_ptr create_floating_window( - const dock::floating_window_config& config, - dock::dock_panel* panel = nullptr); - void close_floating_window(floating_window_widget* window); - [[nodiscard]] std::vector get_floating_windows() const; - - // 所有窗口 - [[nodiscard]] std::vector get_all_windows() const; - [[nodiscard]] window_widget* get_focused_window() const noexcept; - [[nodiscard]] window_widget* find_window(uint32_t window_id) const; - - // 拖拽管理 - [[nodiscard]] dock::dock_drag_manager& get_drag_manager() noexcept; - - // 事件处理(每帧调用) - void poll_events(); - void update(float dt); - - // 回调 - void set_window_create_callback(window_create_callback cb); - void set_window_close_callback(window_close_callback cb); - void set_window_focus_callback(window_focus_callback cb); - -private: - void handle_window_close(window_widget* window); - void handle_window_focus(window_widget* window); - void cleanup_closed_windows(); - - window_manager& window_manager_; - state::widget_state_store& state_store_; - render::text::text_shaper& text_shaper_; - - std::shared_ptr main_window_; - std::vector> floating_windows_; - std::unordered_map window_id_map_; - - window_widget* focused_window_ = nullptr; - - dock::dock_drag_manager drag_manager_; - - window_create_callback create_callback_; - window_close_callback close_callback_; - window_focus_callback focus_callback_; -}; - -} // namespace mirage -``` - ---- - -## 6. widget_base 扩展 - -### 6.1 添加窗口访问方法 - -在 [`widget_base.h`](src/widget/widget_base.h) 中添加以下方法: - -```cpp -// 在 widget_base 类的 public 部分添加 - -// ======================================================================== -// 窗口访问 -// ======================================================================== - -/// @brief 获取所属的窗口 widget -/// @return 所属的 window_widget,如果不在窗口中返回 nullptr -[[nodiscard]] window_widget* get_owner_window() const; - -/// @brief 获取原生窗口接口 -/// @return 窗口接口指针,如果不在窗口中返回 nullptr -[[nodiscard]] window_interface* get_window() const; -``` - -### 6.2 widget_context 扩展 - -在 [`widget_context.h`](src/widget/widget_context.h) 中添加以下方法: - -```cpp -// 在 widget_context 类中添加 - -// ======================================================================== -// 窗口服务(扩展) -// ======================================================================== - -/// @brief 获取窗口接口 -[[nodiscard]] window_interface* get_window() const noexcept { - return window_; -} - -/// @brief 获取窗口大小 -[[nodiscard]] vec2i_t get_window_size() const; - -/// @brief 获取帧缓冲大小 -[[nodiscard]] vec2f_t get_framebuffer_size() const; - -/// @brief 获取内容缩放比例(DPI 相关) -[[nodiscard]] vec2f_t get_content_scale() const; -``` - ---- - -## 7. 布局持久化 - -### 7.1 dock_layout_serializer - -**文件路径**: `src/widget/dock/dock_layout_serializer.h` - -```cpp -#pragma once -#include "dock_types.h" -#include "dock_container.h" -#include -#include - -namespace mirage::dock { - -/// 布局数据 -struct layout_data { - struct panel_info { - std::string id; - dock_location location; - bool visible = true; - }; - - struct area_info { - dock_area area; - float size; - bool collapsed; - bool visible; - }; - - struct split_info { - std::vector ratios; - dock_direction direction; - }; - - std::vector panels; - std::vector areas; - std::vector splits; - - uint32_t version = 1; -}; - -class dock_layout_serializer { -public: - /// 序列化布局到 JSON 字符串 - [[nodiscard]] static std::string serialize(const dock_container& container); - - /// 从 JSON 字符串反序列化布局 - [[nodiscard]] static std::optional deserialize(std::string_view json); - - /// 应用布局数据到容器 - static bool apply_layout(dock_container& container, const layout_data& data); - - /// 保存布局到文件 - static bool save_to_file(const dock_container& container, const std::string& path); - - /// 从文件加载布局 - [[nodiscard]] static std::optional load_from_file(const std::string& path); -}; - -} // namespace mirage::dock -``` - ---- - -## 8. 实施计划 - -### 8.1 阶段一:基础架构(优先级:高) - -| 任务 | 文件 | 描述 | -|------|------|------| -| 1.1 | `dock_types.h` | 实现基础类型定义 | -| 1.2 | `window_widget.h/cpp` | 实现窗口控件基类 | -| 1.3 | `main_window_widget.h/cpp` | 实现主窗口 | -| 1.4 | `widget_base.h` | 添加 `get_owner_window()` 和 `get_window()` | -| 1.5 | `widget_context.h/cpp` | 扩展窗口访问方法 | - -### 8.2 阶段二:Dock 核心组件(优先级:高) - -| 任务 | 文件 | 描述 | -|------|------|------| -| 2.1 | `dock_panel.h/cpp` | 实现可停靠面板 | -| 2.2 | `dock_tab_container.h/cpp` | 实现标签页容器 | -| 2.3 | `dock_split_container.h/cpp` | 实现分割容器 | -| 2.4 | `dock_container.h/cpp` | 实现 Dock 根容器 | - -### 8.3 阶段三:拖拽与交互(优先级:中) - -| 任务 | 文件 | 描述 | -|------|------|------| -| 3.1 | `dock_drag_manager.h/cpp` | 实现拖拽管理器 | -| 3.2 | 标签页拖拽 | 标签页重排序和拖出 | -| 3.3 | 分隔条拖拽 | 调整面板大小 | -| 3.4 | 停靠预览 | 拖拽时显示停靠位置预览 | - -### 8.4 阶段四:浮动窗口(优先级:中) - -| 任务 | 文件 | 描述 | -|------|------|------| -| 4.1 | `floating_window_widget.h/cpp` | 实现浮动窗口 | -| 4.2 | `multi_window_manager.h/cpp` | 实现多窗口管理器 | -| 4.3 | 浮动窗口渲染 | 每个窗口独立渲染循环 | -| 4.4 | 跨窗口拖放 | 面板在窗口间移动 | - -### 8.5 阶段五:持久化与优化(优先级:低) - -| 任务 | 文件 | 描述 | -|------|------|------| -| 5.1 | `dock_layout_serializer.h/cpp` | 布局序列化 | -| 5.2 | 布局文件 I/O | 保存/加载布局 | -| 5.3 | 性能优化 | 渲染优化、事件优化 | -| 5.4 | 动画效果 | 停靠动画、大小调整动画 | - ---- - -## 9. 使用示例 - -### 9.1 基础使用 - -```cpp -#include "application.h" -#include "dock/dock_container.h" -#include "dock/dock_panel.h" -#include "window/main_window_widget.h" - -using namespace mirage; -using namespace mirage::dock; - -int main() { - app::application app; - app::application_config config; - config.title = "IDE Demo"; - config.width = 1920; - config.height = 1080; - - if (!app.initialize(config)) { - return -1; - } - - // 创建 Dock 容器 - auto dock = std::make_shared(); - - // 配置区域 - dock->configure_area(dock_area::left, {.default_size = 250.0f, .min_size = 150.0f}); - dock->configure_area(dock_area::right, {.default_size = 300.0f, .min_size = 200.0f}); - dock->configure_area(dock_area::bottom, {.default_size = 200.0f, .min_size = 100.0f}); - - // 创建面板 - auto explorer = dock_panel::create({ - .id = "explorer", - .title = "Explorer", - .icon = "folder" - }); - explorer->set_content(create_explorer_widget()); - - auto editor = dock_panel::create({ - .id = "editor", - .title = "Editor", - .closable = false - }); - editor->set_content(create_editor_widget()); - - auto terminal = dock_panel::create({ - .id = "terminal", - .title = "Terminal" - }); - terminal->set_content(create_terminal_widget()); - - // 停靠面板 - dock->dock_panel(explorer, {.area = dock_area::left, .tab_index = 0}); - dock->dock_panel(editor, {.area = dock_area::center, .tab_index = 0}); - dock->dock_panel(terminal, {.area = dock_area::bottom, .tab_index = 0}); - - // 设置为根控件 - app.set_root_widget(dock); - - app.run(); - return 0; -} -``` - -### 9.2 布局持久化 - -```cpp -// 保存布局 -void save_layout(dock_container* dock) { - dock_layout_serializer::save_to_file(*dock, "layout.json"); -} - -// 加载布局 -void load_layout(dock_container* dock) { - if (auto data = dock_layout_serializer::load_from_file("layout.json")) { - dock_layout_serializer::apply_layout(*dock, *data); - } -} -``` - -### 9.3 创建浮动窗口 - -```cpp -// 从面板创建浮动窗口 -void float_panel(dock_panel* panel, multi_window_manager& wm) { - floating_window_config config; - config.title = panel->get_title(); - config.width = 600; - config.height = 400; - - wm.create_floating_window(config, panel); -} -``` - ---- - -## 10. 文件结构 - -``` -src/widget/ -├── dock/ -│ ├── dock_types.h -│ ├── dock_types.cpp -│ ├── dock_panel.h -│ ├── dock_panel.cpp -│ ├── dock_tab_container.h -│ ├── dock_tab_container.cpp -│ ├── dock_split_container.h -│ ├── dock_split_container.cpp -│ ├── dock_container.h -│ ├── dock_container.cpp -│ ├── dock_drag_manager.h -│ ├── dock_drag_manager.cpp -│ ├── dock_layout_serializer.h -│ └── dock_layout_serializer.cpp -│ -├── window/ -│ ├── window_widget.h -│ ├── window_widget.cpp -│ ├── main_window_widget.h -│ ├── main_window_widget.cpp -│ ├── floating_window_widget.h -│ ├── floating_window_widget.cpp -│ ├── multi_window_manager.h -│ └── multi_window_manager.cpp -│ -├── widget_base.h (扩展) -├── widget_context.h (扩展) -└── widget_context.cpp (扩展) -``` - ---- - -## 11. 注意事项 - -### 11.1 线程安全 - -- 所有 UI 操作必须在主线程执行 -- `widget_state_store` 已提供线程安全的状态访问 -- 多窗口渲染需要协调 Vulkan 命令提交 - -### 11.2 内存管理 - -- 使用 `std::shared_ptr` 管理面板生命周期 -- 浮动窗口关闭时正确清理资源 -- 避免循环引用(特别是回调中) - -### 11.3 性能考虑 - -- 标签页容器只渲染活动面板 -- 使用脏标记避免不必要的重绘 -- 分隔条拖拽使用节流(throttle) - -### 11.4 跨平台兼容 - -- 浮动窗口在不同平台行为可能不同 -- 使用 `window_interface` 抽象平台差异 -- 测试 Windows/Linux/macOS 三个平台 \ No newline at end of file diff --git a/docs/POST_EFFECT_INCREMENTAL_RENDERING.md b/docs/POST_EFFECT_INCREMENTAL_RENDERING.md deleted file mode 100644 index 8a06d63..0000000 --- a/docs/POST_EFFECT_INCREMENTAL_RENDERING.md +++ /dev/null @@ -1,642 +0,0 @@ -# 后效管线增量渲染架构设计 - -## 1. 架构概述 - -### 1.1 设计目标 - -本文档描述了一种后效管线优化方案,使区域后效能够与增量渲染机制共存。核心目标是: - -1. **减少不必要的后效计算**:当脏区域与后效区域不重叠时,跳过该后效的重新计算 -2. **精确化渲染范围**:只为实际需要的区域分配临时缓冲并执行后效计算 -3. **智能脏区域扩展**:考虑后效的影响半径(如模糊采样范围)自动扩展脏区域 - -### 1.2 重要约束 - -**所有后效都是区域后效(widget 级别)**,除非该 widget 的大小等于整个视口。这意味着: -- 每个后效都有明确的边界信息(来自 `post_effect_command` 的 `position` 和 `size`) -- 不需要考虑全屏后效的特殊情况 -- 后效天然具有区域属性 - -### 1.3 系统架构图 - -```mermaid -flowchart TB - subgraph Input - DirtyRect[脏区域 dirty_rect] - RenderTree[渲染树 render_tree] - end - - subgraph Analysis[后效分析阶段] - Collector[PostEffectRegionCollector] - Analyzer[DirtyRegionAnalyzer] - - Collector --> |收集所有后效区域| EffectRegions[后效区域列表] - DirtyRect --> Analyzer - EffectRegions --> Analyzer - Analyzer --> |计算影响半径扩展| ExpandedDirty[扩展后脏区域] - Analyzer --> |判断重叠关系| EffectDecisions[后效决策表] - end - - subgraph Execution[执行阶段] - EffectDecisions --> Executor[IncrementalEffectExecutor] - ExpandedDirty --> Executor - Executor --> |根据决策| Skip[跳过无关后效] - Executor --> |根据决策| PartialRender[部分区域渲染] - Executor --> |根据决策| FullRender[完整区域渲染] - end - - subgraph Cache[缓存系统] - EffectCache[后效结果缓存] - Skip --> |使用缓存| EffectCache - PartialRender --> |更新缓存| EffectCache - FullRender --> |更新缓存| EffectCache - end - - subgraph Output - FinalResult[最终渲染结果] - end - - EffectCache --> FinalResult -``` - -## 2. 核心算法 - -### 2.1 后效区域与脏区域交互判断算法 - -```mermaid -flowchart TD - Start[开始处理后效节点] --> GetBounds[获取后效边界 effect_bounds] - GetBounds --> GetRadius[计算影响半径 influence_radius] - GetRadius --> ExpandEffect[扩展后效区域 expanded_effect_bounds] - ExpandEffect --> CheckIntersect{扩展后效区域 与 脏区域 是否相交?} - - CheckIntersect --> |否| SkipEffect[跳过后效计算 使用缓存结果] - CheckIntersect --> |是| CalcOverlap[计算精确重叠区域] - - CalcOverlap --> CheckRatio{重叠面积 / 后效面积 > 阈值?} - CheckRatio --> |是 大于80%| FullRecompute[完整重新计算后效] - CheckRatio --> |否| PartialRecompute[部分区域重新计算] - - FullRecompute --> AllocFull[分配完整尺寸缓冲] - PartialRecompute --> AllocPartial[分配部分尺寸缓冲] - - AllocFull --> Execute[执行后效] - AllocPartial --> Execute - Execute --> UpdateCache[更新缓存] - - SkipEffect --> End[结束] - UpdateCache --> End -``` - -### 2.2 影响半径计算 - -不同类型的后效有不同的影响半径: - -| 后效类型 | 影响半径计算方式 | -|---------|-----------------| -| `blur_effect` | `radius * 2`(双向采样) | -| `chromatic_aberration_effect` | `max(abs(offset.x), abs(offset.y))` | -| `vignette_effect` | `0`(无依赖) | -| `color_adjust_effect` | `0`(无依赖) | -| `color_tint_effect` | `0`(无依赖) | -| `noise_effect` | `0`(无依赖) | - -### 2.3 智能脏区域扩展算法 - -```cpp -/// 计算后效对脏区域的扩展需求 -/// @param dirty_rect 原始脏区域 -/// @param effect_regions 所有后效区域及其影响半径 -/// @return 扩展后的脏区域 -auto compute_expanded_dirty_region( - const aabb2d_t& dirty_rect, - const std::vector& effect_regions -) -> aabb2d_t { - aabb2d_t expanded = dirty_rect; - - for (const auto& effect_info : effect_regions) { - // 检查原始脏区域是否与后效区域相交 - if (!dirty_rect.intersects(effect_info.bounds)) { - continue; - } - - // 扩展脏区域以包含该后效的影响范围 - float radius = effect_info.influence_radius; - if (radius > 0) { - // 计算需要扩展的区域(脏区域与后效区域的交集 + 影响半径) - auto intersection = dirty_rect.intersection(effect_info.bounds); - aabb2d_t effect_expanded( - intersection.min() - vec2f_t(radius, radius), - intersection.max() + vec2f_t(radius, radius) - ); - // 但扩展不能超出后效区域本身 - effect_expanded = effect_expanded.intersection(effect_info.bounds); - expanded = expanded.merged(effect_expanded); - } - } - - return expanded; -} -``` - -## 3. 数据结构定义 - -### 3.1 后效区域信息 - -```cpp -namespace mirage::render { - -/// @brief 后效影响半径计算器 -struct effect_influence_calculator { - /// 计算后效的影响半径(像素) - static auto compute_radius(const post_effect_command& effect) -> float { - return std::visit([](const auto& eff) -> float { - using T = std::decay_t; - - if constexpr (std::is_same_v) { - // 模糊需要读取周围像素,半径 = blur_radius * 2 - return eff.radius * 2.0f; - } - else if constexpr (std::is_same_v) { - // 色差需要偏移采样 - return std::max(std::abs(eff.offset.x()), std::abs(eff.offset.y())); - } - else { - // 其他效果不需要额外像素 - return 0.0f; - } - }, effect); - } -}; - -/// @brief 后效区域信息 -struct effect_region_info { - aabb2d_t bounds; ///< 后效边界 - float influence_radius; ///< 影响半径(像素) - post_effect_command effect; ///< 后效命令 - const post_effect_node* node; ///< 关联的渲染树节点 - - /// 获取扩展后的边界(包含影响半径) - [[nodiscard]] auto expanded_bounds() const -> aabb2d_t { - if (influence_radius <= 0) { - return bounds; - } - return aabb2d_t( - bounds.min() - vec2f_t(influence_radius, influence_radius), - bounds.max() + vec2f_t(influence_radius, influence_radius) - ); - } -}; - -/// @brief 后效渲染决策 -enum class effect_render_decision { - skip, ///< 完全跳过,使用缓存 - partial, ///< 部分重新计算 - full ///< 完整重新计算 -}; - -/// @brief 后效决策结果 -struct effect_decision_result { - effect_render_decision decision; ///< 决策类型 - aabb2d_t render_region; ///< 需要渲染的区域 - float overlap_ratio; ///< 重叠比例(用于调试) -}; - -/// @brief 脏区域分析器 -class dirty_region_analyzer { -public: - /// 配置参数 - struct config { - float full_render_threshold = 0.8f; ///< 超过此比例则完整渲染 - float min_partial_size = 64.0f; ///< 最小部分渲染尺寸 - bool enable_caching = true; ///< 是否启用缓存 - }; - - explicit dirty_region_analyzer(const config& cfg = {}); - - /// 分析后效节点与脏区域的关系 - /// @param effect_info 后效区域信息 - /// @param dirty_rect 脏区域 - /// @param viewport_size 视口大小 - /// @return 渲染决策 - [[nodiscard]] auto analyze( - const effect_region_info& effect_info, - const aabb2d_t& dirty_rect, - const vec2f_t& viewport_size - ) const -> effect_decision_result; - - /// 批量分析所有后效 - [[nodiscard]] auto analyze_all( - const std::vector& effects, - const aabb2d_t& dirty_rect, - const vec2f_t& viewport_size - ) const -> std::vector; - -private: - config config_; -}; - -} // namespace mirage::render -``` - -### 3.2 后效结果缓存 - -```cpp -namespace mirage::render { - -/// @brief 后效缓存条目 -struct effect_cache_entry { - /// 缓存键(用于匹配) - struct key { - aabb2d_t bounds; ///< 后效边界 - size_t effect_hash; ///< 后效参数哈希 - - bool operator==(const key& other) const; - }; - - target_handle cached_result; ///< 缓存的渲染结果 - key cache_key; ///< 缓存键 - uint64_t frame_created; ///< 创建帧 - uint64_t last_used; ///< 最后使用帧 - bool valid; ///< 是否有效 -}; - -/// @brief 后效结果缓存管理器 -class effect_cache_manager { -public: - struct config { - size_t max_entries = 32; ///< 最大缓存条目数 - uint32_t max_age_frames = 60; ///< 最大缓存帧数 - size_t max_memory_mb = 256; ///< 最大内存使用(MB) - }; - - effect_cache_manager(unified_target_pool& pool, const config& cfg = {}); - - /// 查找缓存 - /// @return 缓存的渲染目标,或 nullptr - [[nodiscard]] auto find(const effect_cache_entry::key& key) - -> offscreen_target*; - - /// 存储缓存 - void store(const effect_cache_entry::key& key, target_handle result); - - /// 使缓存条目失效 - void invalidate(const aabb2d_t& region); - - /// 帧结束时清理过期缓存 - void end_frame(uint64_t current_frame); - - /// 清除所有缓存 - void clear(); - -private: - unified_target_pool& pool_; - config config_; - std::vector entries_; - uint64_t current_frame_ = 0; -}; - -} // namespace mirage::render -``` - -### 3.3 增量后效执行器 - -```cpp -namespace mirage::render { - -/// @brief 增量后效执行上下文 -template -struct incremental_effect_context { - executor_context* base_ctx; ///< 基础执行上下文 - dirty_region_analyzer* analyzer; ///< 脏区域分析器 - effect_cache_manager* cache; ///< 缓存管理器 - std::optional dirty_rect; ///< 当前脏区域 - vec2f_t viewport_size; ///< 视口大小 - std::vector decisions; ///< 预计算的决策 -}; - -/// @brief 增量后效执行器 -class incremental_effect_executor { -public: - struct config { - bool enable_partial_render = true; ///< 启用部分渲染 - bool enable_caching = true; ///< 启用缓存 - float partial_threshold = 0.8f; ///< 部分渲染阈值 - }; - - incremental_effect_executor( - post_effect_applicator& applicator, - unified_target_pool& pool, - const config& cfg = {} - ); - - /// 预分析渲染树中的所有后效 - [[nodiscard]] auto analyze_effects( - const render_tree& tree, - const std::optional& dirty_rect, - const vec2f_t& viewport_size - ) -> std::vector; - - /// 执行后效节点(带增量渲染支持) - template - void execute_effect( - const post_effect_node& node, - incremental_effect_context& ctx, - size_t effect_index - ); - -private: - post_effect_applicator& applicator_; - unified_target_pool& pool_; - config config_; - dirty_region_analyzer analyzer_; - effect_cache_manager cache_; -}; - -} // namespace mirage::render -``` - -## 4. 处理流程 - -### 4.1 完整流程图 - -```mermaid -sequenceDiagram - participant RP as RenderPipeline - participant Analyzer as DirtyRegionAnalyzer - participant Executor as RenderTreeExecutor - participant Cache as EffectCacheManager - participant Pool as UnifiedTargetPool - participant Applicator as PostEffectApplicator - - Note over RP: render_frame 开始 - - RP->>Analyzer: 收集渲染树中的后效区域 - Analyzer->>Analyzer: 计算每个后效的影响半径 - Analyzer->>Analyzer: 扩展脏区域 - Analyzer-->>RP: 扩展后的脏区域 + 后效决策表 - - RP->>Executor: 执行渲染树 - - loop 对于每个后效节点 - Executor->>Executor: 获取该节点的决策 - - alt 决策 = skip - Executor->>Cache: 查找缓存 - Cache-->>Executor: 返回缓存结果 - Executor->>Executor: Blit 缓存到目标 - else 决策 = partial - Executor->>Pool: 获取部分尺寸缓冲 - Pool-->>Executor: 返回小尺寸目标 - Executor->>Applicator: 执行部分区域后效 - Applicator-->>Executor: 完成 - Executor->>Cache: 更新缓存(合并) - else 决策 = full - Executor->>Pool: 获取完整尺寸缓冲 - Pool-->>Executor: 返回完整目标 - Executor->>Applicator: 执行完整后效 - Applicator-->>Executor: 完成 - Executor->>Cache: 更新缓存 - end - end - - RP->>Cache: 帧结束清理 - Note over RP: render_frame 结束 -``` - -### 4.2 后效执行详细流程 - -```mermaid -flowchart TD - subgraph PreAnalysis[预分析阶段] - CollectEffects[遍历渲染树收集后效] - CalcInfluence[计算影响半径] - ExpandDirty[扩展脏区域] - MakeDecisions[生成决策表] - - CollectEffects --> CalcInfluence - CalcInfluence --> ExpandDirty - ExpandDirty --> MakeDecisions - end - - subgraph Execution[执行阶段 - 每个后效] - GetDecision[获取决策] - - GetDecision --> CheckSkip{skip?} - CheckSkip -->|是| LoadCache[加载缓存] - CheckSkip -->|否| CheckPartial{partial?} - - CheckPartial -->|是| AllocSmall[分配小缓冲] - CheckPartial -->|否| AllocFull[分配完整缓冲] - - AllocSmall --> RenderPartial[渲染部分区域] - AllocFull --> RenderFull[渲染完整区域] - - LoadCache --> BlitCache[Blit 缓存到目标] - RenderPartial --> MergeCache[合并到缓存] - RenderFull --> ReplaceCache[替换缓存] - - BlitCache --> Done[完成] - MergeCache --> Done - ReplaceCache --> Done - end - - PreAnalysis --> Execution -``` - -## 5. 实现步骤 - -按优先级排序的实现步骤: - -### Phase 1: 基础设施(优先级:高) - -1. **创建 `effect_influence_calculator`** - - 文件:`src/render/pipeline/effect_influence.h` - - 实现各类后效的影响半径计算 - - 估计工时:2小时 - -2. **创建 `dirty_region_analyzer`** - - 文件:`src/render/pipeline/dirty_region_analyzer.h/.cpp` - - 实现脏区域与后效区域的交互判断 - - 估计工时:4小时 - -3. **创建 `effect_region_info` 和相关数据结构** - - 文件:`src/render/pipeline/effect_types.h` - - 定义后效区域信息结构 - - 估计工时:1小时 - -### Phase 2: 缓存系统(优先级:中) - -4. **创建 `effect_cache_manager`** - - 文件:`src/render/pipeline/effect_cache_manager.h/.cpp` - - 实现后效结果缓存 - - 估计工时:6小时 - -5. **修改 `unified_target_pool`** - - 添加按区域大小分配支持 - - 优化小尺寸目标的复用策略 - - 估计工时:3小时 - -### Phase 3: 执行器集成(优先级:高) - -6. **创建 `incremental_effect_executor`** - - 文件:`src/render/pipeline/incremental_effect_executor.h/.cpp` - - 整合分析器和缓存 - - 估计工时:6小时 - -7. **修改 `render_tree_executor`** - - 集成增量后效执行器 - - 在 `execute_post_effect` 中添加决策逻辑 - - 估计工时:4小时 - -### Phase 4: 管线集成(优先级:高) - -8. **修改 `render_pipeline`** - - 在 `render_frame` 中添加预分析阶段 - - 传递决策表到执行器 - - 估计工时:3小时 - -9. **修改 `post_effect_applicator`** - - 添加部分区域渲染支持 - - 优化 `blit_to_target` 支持区域偏移 - - 估计工时:4小时 - -### Phase 5: 测试与优化(优先级:中) - -10. **单元测试** - - 测试影响半径计算 - - 测试脏区域分析 - - 测试缓存命中率 - - 估计工时:4小时 - -11. **性能基准测试** - - 对比优化前后的性能 - - 验证内存使用改善 - - 估计工时:2小时 - -## 6. 性能预期 - -### 6.1 最佳情况 - -| 场景 | 优化前 | 优化后 | 改善 | -|-----|-------|-------|------| -| 小区域脏区域,无后效重叠 | 全量后效计算 | 跳过所有后效 | ~100% | -| 小区域脏区域,部分后效重叠 | 全量后效计算 | 仅计算重叠部分 | ~60-80% | -| 大区域脏区域 | 全量后效计算 | 全量后效计算 | 0% | - -### 6.2 内存使用 - -| 资源 | 优化前 | 优化后 | -|-----|-------|-------| -| 临时缓冲 | 全尺寸 | 按需分配(可能更小) | -| 缓存内存 | 无 | 可配置上限(默认256MB) | -| 目标池峰值 | 高 | 略高(但可控) | - -### 6.3 典型场景分析 - -**场景1:鼠标悬停按钮(小区域更新)** -- 脏区域:50x30 像素 -- 视口:1920x1080 -- 后效:全屏模糊遮罩 -- 预期:后效区域与脏区域不重叠 → **跳过后效** - -**场景2:滚动列表(中等区域更新)** -- 脏区域:300x800 像素 -- 视口:1920x1080 -- 后效:列表项上的模糊背景(200x100) -- 预期:部分后效与脏区域重叠 → **部分渲染** - -**场景3:窗口拖动(大区域更新)** -- 脏区域:1000x800 像素 -- 视口:1920x1080 -- 后效:窗口模糊背景(800x600) -- 预期:大部分重叠 → **完整渲染** - -## 7. 与现有系统的集成点 - -### 7.1 render_tree.h - -- `post_effect_node` 已包含 `bounds` 信息 -- 无需修改现有结构 - -### 7.2 render_pipeline.cpp - -需要修改的位置: - -```cpp -// 在 render_frame 开始处添加预分析 -auto effect_decisions = incremental_executor_->analyze_effects( - tree, - dirty_rect, - vec2f_t(extent.width, extent.height) -); - -// 在 executor_context 中添加决策信息 -render::incremental_effect_context inc_ctx{ - .base_ctx = &exec_ctx, - .analyzer = &dirty_analyzer_, - .cache = &effect_cache_, - .dirty_rect = dirty_rect, - .viewport_size = vec2f_t(extent.width, extent.height), - .decisions = effect_decisions -}; -``` - -### 7.3 render_tree_executor.cpp - -在 `execute_post_effect` 中添加: - -```cpp -// 获取决策 -if (ctx.incremental_ctx && ctx.incremental_ctx->dirty_rect.has_value()) { - auto& decisions = ctx.incremental_ctx->decisions; - auto& decision = decisions[effect_index]; - - switch (decision.decision) { - case effect_render_decision::skip: - // 使用缓存 - apply_cached_effect(node, ctx); - return; - case effect_render_decision::partial: - // 部分渲染 - apply_partial_effect(node, ctx, decision.render_region); - return; - case effect_render_decision::full: - // 完整渲染(fallthrough to existing logic) - break; - } -} -// 现有的完整渲染逻辑... -``` - -### 7.4 post_effect_applicator.cpp - -添加新接口: - -```cpp -/// 应用后效到指定区域(增量渲染用) -void apply_region( - vk::CommandBuffer cmd, - offscreen_target& target, - const post_effect_command& effect, - float time, - const aabb2d_t& render_region, // 只渲染这个区域 - const offscreen_target* source = nullptr -); -``` - -## 8. 风险与缓解 - -| 风险 | 影响 | 缓解措施 | -|-----|------|---------| -| 缓存一致性问题 | 渲染错误 | 使用效果参数哈希验证缓存有效性 | -| 边界情况处理 | 视觉瑕疵 | 充分的边界扩展 + 渐变处理 | -| 内存使用增加 | OOM | 可配置的缓存上限 + 自动清理 | -| 实现复杂度 | 维护困难 | 清晰的分层设计 + 充分注释 | - -## 9. 未来扩展 - -1. **多帧时序缓存**:利用帧间相关性进一步优化 -2. **GPU 异步计算**:将后效计算移到独立的 compute queue -3. **自适应质量**:根据脏区域大小动态调整后效质量 -4. **后效合并**:将相同类型的后效批量处理 \ No newline at end of file diff --git a/docs/TEXTURE_ATLAS_SYSTEM_DESIGN.md b/docs/TEXTURE_ATLAS_SYSTEM_DESIGN.md deleted file mode 100644 index 1e44a42..0000000 --- a/docs/TEXTURE_ATLAS_SYSTEM_DESIGN.md +++ /dev/null @@ -1,1075 +0,0 @@ - -# 通用纹理图集管理系统设计文档 - -## 实现状态 - -| 组件 | 状态 | 文件位置 | -|------|------|---------| -| [`atlas_types`](../src/render/atlas/atlas_types.h) | ✅ 已实现 | [`src/render/atlas/atlas_types.h`](../src/render/atlas/atlas_types.h) | -| [`atlas_page`](../src/render/atlas/atlas_page.h) | ✅ 已实现 | [`src/render/atlas/atlas_page.h`](../src/render/atlas/atlas_page.h) / [`atlas_page.cpp`](../src/render/atlas/atlas_page.cpp) | -| [`texture_atlas_manager`](../src/render/atlas/texture_atlas_manager.h) | ✅ 已实现 | [`src/render/atlas/texture_atlas_manager.h`](../src/render/atlas/texture_atlas_manager.h) / [`texture_atlas_manager.cpp`](../src/render/atlas/texture_atlas_manager.cpp) | -| [`atlas_gpu_resource`](../src/render/atlas/atlas_gpu_resource.h) | ✅ 已实现 | [`src/render/atlas/atlas_gpu_resource.h`](../src/render/atlas/atlas_gpu_resource.h) / [`atlas_gpu_resource.cpp`](../src/render/atlas/atlas_gpu_resource.cpp) | -| [`atlas_upload_scheduler`](../src/render/atlas/atlas_upload_scheduler.h) | ✅ 已实现 | [`src/render/atlas/atlas_upload_scheduler.h`](../src/render/atlas/atlas_upload_scheduler.h) / [`atlas_upload_scheduler.cpp`](../src/render/atlas/atlas_upload_scheduler.cpp) | -| glyph_cache重构 | ✅ 已实现 | [`src/render/text/glyph_cache.h`](../src/render/text/glyph_cache.h) / [`glyph_cache.cpp`](../src/render/text/glyph_cache.cpp) | -| text_renderer适配 | ✅ 已实现 | [`src/render/pipeline/text_renderer.h`](../src/render/pipeline/text_renderer.h) / [`text_renderer.cpp`](../src/render/pipeline/text_renderer.cpp) | - -## 1. 概述 - -本文档描述一个已实现的通用纹理图集管理系统,用于高效管理和渲染大量小型纹理(如MTSDF字形、小图标等)。系统支持多图集、LRU淘汰策略和异步GPU上传。 - -## 2. 设计目标 - -1. **通用性** - 支持多种用途(字形、图标、精灵等),不包含业务特定数据 -2. **多图集支持** - 当单个图集满时自动创建新图集 -3. **LRU淘汰策略** - 当所有图集都满时,淘汰最久未使用的项目 -4. **异步上传** - 支持在后台线程进行纹理上传,不阻塞渲染 -5. **增量更新** - 只上传脏区域,而不是整个图集 -6. **线程安全** - 支持多线程访问 - -## 3. 系统架构 - -``` -+-----------------------------------------------------------------------------+ -| texture_atlas_manager | -| +-------------------------------------------------------------------------+| -| | 多图集管理 + LRU淘汰 || -| | +---------------+ +---------------+ +---------------+ || -| | | atlas_page | | atlas_page | | atlas_page | ... || -| | | Page 0 | | Page 1 | | Page 2 | || -| | +-------+-------+ +-------+-------+ +-------+-------+ || -| | | | | || -| | v v v || -| | +-------------------------------------------------------------------+ || -| | | atlas_upload_scheduler | || -| | | 异步上传调度 + 脏区域批量处理 | || -| | +-------------------------------------------------------------------+ || -| +-------------------------------------------------------------------------+| -+-----------------------------------------------------------------------------+ - | - v -+-----------------------------------------------------------------------------+ -| Vulkan Resources | -| +-------------+ +-------------+ +-------------+ | -| | VkImage[0] | | VkImage[1] | | VkImage[2] | ... | -| | VkImageView | | VkImageView | | VkImageView | | -| +-------------+ +-------------+ +-------------+ | -+-----------------------------------------------------------------------------+ -``` - -## 4. 核心类设计 - -### 4.1 atlas_entry - 图集条目 - -```cpp -namespace mirage::render::atlas { - -/// @brief 图集条目ID(全局唯一) -struct atlas_entry_id { - uint64_t value{0}; - - auto operator<=>(const atlas_entry_id&) const = default; -}; - -/// @brief 图集条目信息(仅包含图集相关数据,不含业务逻辑) -struct atlas_entry { - atlas_entry_id id; ///< 条目ID - uint32_t page_index; ///< 所在页面索引 - - // 在图集中的位置(像素坐标) - uint32_t x, y; - uint32_t width, height; - - // 归一化UV坐标 - float u0, v0, u1, v1; - - // LRU相关 - uint64_t last_access_frame{0}; ///< 最后访问的帧号 -}; - -// 注意:特定领域的度量数据(如MTSDF字形的bearing、advance等) -// 应由使用方自己管理,通过entry_id关联。 -// 这保持了图集系统的通用性。 - -} // namespace mirage::render::atlas -``` - -### 4.2 atlas_page - 单个图集页面 - -```cpp -namespace mirage::render::atlas { - -/// @brief 脏区域信息 -struct dirty_region { - uint32_t x, y; - uint32_t width, height; -}; - -/// @brief 单个图集页面(CPU端数据 + 矩形打包) -class atlas_page { -public: - /// @brief 配置参数 - struct config { - uint32_t size{2048}; ///< 图集尺寸(正方形) - vk::Format format{vk::Format::eR8G8B8A8Unorm}; ///< 像素格式 - uint32_t padding{1}; ///< 条目间的填充像素 - }; - - explicit atlas_page(const config& cfg); - - /// @brief 尝试添加条目到图集 - /// @param width 条目宽度 - /// @param height 条目高度 - /// @param data 像素数据(根据format) - /// @return 成功返回分配位置,失败返回nullopt - auto try_add( - uint32_t width, - uint32_t height, - std::span data - ) -> std::optional>; // (x, y) - - /// @brief 移除条目(释放空间) - /// @param x 条目X坐标 - /// @param y 条目Y坐标 - /// @param width 条目宽度 - /// @param height 条目高度 - void remove(uint32_t x, uint32_t y, uint32_t width, uint32_t height); - - /// @brief 获取脏区域列表 - auto get_dirty_regions() const -> std::span; - - /// @brief 清除脏区域标记 - void clear_dirty_regions(); - - /// @brief 获取图集数据 - auto get_data() const -> std::span; - - /// @brief 获取图集尺寸 - auto get_size() const -> uint32_t; - - /// @brief 获取配置 - auto get_config() const -> const config&; - - /// @brief 获取已使用空间比例 - auto get_usage_ratio() const -> float; - -private: - // 矩形打包节点 - struct rect_node { - uint32_t x, y; - uint32_t width, height; - bool used{false}; - }; - - // 使用Guillotine算法查找可用空间 - auto find_free_rect(uint32_t width, uint32_t height) -> int; - void split_rect_node(int node_index, uint32_t used_width, uint32_t used_height); - - // 复制数据到图集 - void copy_to_atlas(uint32_t x, uint32_t y, uint32_t w, uint32_t h, - std::span data); - - config config_; - std::vector data_; ///< CPU端图集数据 - std::vector free_rects_; ///< 空闲矩形列表 - std::vector dirty_regions_; - uint32_t used_pixels_{0}; ///< 已使用像素数 - mutable std::mutex mutex_; -}; - -} // namespace mirage::render::atlas -``` - -### 4.3 atlas_gpu_resource - GPU端资源 - -```cpp -namespace mirage::render::atlas { - -/// @brief 单个图集页面的GPU资源 -struct atlas_gpu_resource { - resource_manager::image_resource image; ///< Vulkan图像资源 - vk::ImageView view{}; ///< 图像视图 - vk::Sampler sampler{}; ///< 采样器(共享) - vk::ImageLayout current_layout{vk::ImageLayout::eUndefined}; - bool initialized{false}; ///< 是否已初始化 -}; - -} // namespace mirage::render::atlas -``` - -### 4.4 atlas_upload_scheduler - 异步上传调度器 - -```cpp -namespace mirage::render::atlas { - -/// @brief 上传任务 -struct upload_task { - uint32_t page_index; - std::vector regions; - std::vector data; ///< 合并后的区域数据 -}; - -/// @brief 上传完成回调 -using upload_complete_callback = std::function; - -/// @brief 异步上传调度器 -class atlas_upload_scheduler { -public: - /// @brief 配置参数 - struct config { - uint32_t max_pending_uploads{8}; ///< 最大挂起上传数 - uint32_t max_regions_per_batch{16}; ///< 每批最大脏区域数 - bool use_transfer_queue{true}; ///< 使用专用传输队列 - }; - - atlas_upload_scheduler( - logical_device& device, - resource_manager& res_mgr, - const config& cfg = {} - ); - - ~atlas_upload_scheduler(); - - /// @brief 提交上传任务 - /// @param page_index 页面索引 - /// @param page 页面数据 - /// @param gpu_resource GPU资源 - void submit_upload( - uint32_t page_index, - const atlas_page& page, - atlas_gpu_resource& gpu_resource - ); - - /// @brief 提交全量上传(首次或重建) - void submit_full_upload( - uint32_t page_index, - const atlas_page& page, - atlas_gpu_resource& gpu_resource - ); - - /// @brief 处理已完成的上传 - /// @return 已完成上传的页面索引列表 - auto poll_completed() -> std::vector; - - /// @brief 等待所有挂起的上传完成 - void flush(); - - /// @brief 设置上传完成回调 - void set_complete_callback(upload_complete_callback callback); - - /// @brief 获取挂起上传数量 - auto get_pending_count() const -> size_t; - -private: - struct pending_upload { - uint32_t page_index; - vk::CommandBuffer cmd; - vk::Fence fence; - resource_manager::buffer_resource staging_buffer; - }; - - // 创建staging buffer并复制数据 - auto create_staging_buffer( - std::span data - ) -> std::optional; - - // 记录上传命令 - void record_upload_commands( - vk::CommandBuffer cmd, - const atlas_page& page, - atlas_gpu_resource& gpu_resource, - const resource_manager::buffer_resource& staging, - std::span regions - ); - - // 转换图像布局 - void transition_layout( - vk::CommandBuffer cmd, - vk::Image image, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout - ); - - logical_device& device_; - resource_manager& res_mgr_; - config config_; - - vk::CommandPool upload_pool_; - std::vector pending_uploads_; - upload_complete_callback complete_callback_; - mutable std::mutex mutex_; -}; - -} // namespace mirage::render::atlas -``` - -### 4.5 texture_atlas_manager - 核心管理器 - -```cpp -namespace mirage::render::atlas { - -/// @brief 淘汰回调(通知使用方清理关联数据) -using eviction_callback = std::function; - -/// @brief 纹理图集管理器 -class texture_atlas_manager { -public: - /// @brief 配置参数 - struct config { - uint32_t page_size{2048}; ///< 单个页面尺寸 - uint32_t max_pages{16}; ///< 最大页面数 - vk::Format format{vk::Format::eR8G8B8A8Unorm}; - uint32_t padding{1}; ///< 条目间填充 - size_t max_entries{65536}; ///< 最大条目数(LRU限制) - bool enable_lru_eviction{true}; ///< 启用LRU淘汰 - uint32_t safe_frames{3}; ///< 安全帧数(不淘汰最近N帧使用的条目) - }; - - texture_atlas_manager( - logical_device& device, - resource_manager& res_mgr, - const config& cfg = {} - ); - - ~texture_atlas_manager(); - - // ======================================================================== - // 条目管理 - // ======================================================================== - - /// @brief 添加条目到图集 - /// @param width 条目宽度(像素) - /// @param height 条目高度(像素) - /// @param data 像素数据(根据配置的format) - /// @return 成功返回条目ID,失败返回nullopt - auto add_entry( - uint32_t width, - uint32_t height, - std::span data - ) -> std::optional; - - /// @brief 获取条目信息 - /// @param id 条目ID - /// @return 条目信息指针,未找到返回nullptr - auto get_entry(atlas_entry_id id) -> const atlas_entry*; - - /// @brief 移除条目 - /// @param id 条目ID - /// @return 是否成功移除 - auto remove_entry(atlas_entry_id id) -> bool; - - /// @brief 标记条目已访问(更新LRU时间戳) - void touch_entry(atlas_entry_id id); - - /// @brief 批量标记条目已访问 - void touch_entries(std::span ids); - - // ======================================================================== - // 用户键映射(可选功能) - // ======================================================================== - - /// @brief 通过用户键添加条目 - /// @param user_key 用户键(64位,由使用方定义含义) - /// @param width 条目宽度 - /// @param height 条目高度 - /// @param data 像素数据 - /// @return 成功返回条目ID - auto add_entry_with_key( - uint64_t user_key, - uint32_t width, - uint32_t height, - std::span data - ) -> std::optional; - - /// @brief 通过用户键查找条目 - auto find_by_key(uint64_t user_key) -> const atlas_entry*; - - /// @brief 通过用户键查找或添加条目 - /// @param user_key 用户键 - /// @param generator 生成器函数,返回(width, height, data) - /// @return 成功返回条目ID,失败返回nullopt - auto find_or_add( - uint64_t user_key, - std::function>>()> generator - ) -> std::optional; - - /// @brief 设置淘汰回调 - void set_eviction_callback(eviction_callback callback); - - // ======================================================================== - // GPU资源访问 - // ======================================================================== - - /// @brief 获取页面的GPU资源 - /// @param page_index 页面索引 - auto get_gpu_resource(uint32_t page_index) -> const atlas_gpu_resource*; - - /// @brief 获取所有活动页面的GPU资源 - auto get_all_gpu_resources() -> std::vector; - - /// @brief 获取共享采样器 - auto get_sampler() const -> vk::Sampler; - - // ======================================================================== - // 帧同步 - // ======================================================================== - - /// @brief 帧开始时调用 - /// @param frame_index 当前帧索引 - void begin_frame(uint64_t frame_index); - - /// @brief 处理异步上传,返回是否有新数据上传完成 - auto process_uploads() -> bool; - - /// @brief 帧结束时调用 - void end_frame(); - - /// @brief 等待所有挂起的上传完成 - void flush_uploads(); - - // ======================================================================== - // 统计与调试 - // ======================================================================== - - struct statistics { - size_t total_entries{0}; - size_t total_pages{0}; - size_t total_memory_bytes{0}; - float average_page_usage{0}; - size_t pending_uploads{0}; - size_t evicted_entries{0}; - }; - - auto get_statistics() const -> statistics; - -private: - // LRU淘汰 - auto evict_lru_entries(size_t count) -> size_t; - - // 查找可用页面 - auto find_available_page(uint32_t width, uint32_t height) -> int; - - // 创建新页面 - auto create_new_page() -> bool; - - // 初始化GPU资源 - void initialize_gpu_resource(uint32_t page_index); - - logical_device& device_; - resource_manager& res_mgr_; - config config_; - - // 页面管理 - std::vector> pages_; - std::vector gpu_resources_; - - // 条目管理 - std::unordered_map entries_; - uint64_t next_entry_id_{1}; - - // 用户键到条目ID的映射 - std::unordered_map user_key_map_; - // 条目ID到用户键的反向映射(用于淘汰时回调) - std::unordered_map entry_to_key_map_; - - // LRU链表(双向链表实现高效淘汰) - struct lru_node { - atlas_entry_id id; - uint64_t last_frame; - }; - std::list lru_list_; - std::unordered_map::iterator, - atlas_entry_id_hash> lru_map_; - - // 上传调度 - std::unique_ptr upload_scheduler_; - - // 共享采样器 - vk::Sampler sampler_; - - // 回调 - eviction_callback eviction_callback_; - - // 统计 - size_t evicted_count_{0}; - uint64_t current_frame_{0}; - - mutable std::shared_mutex mutex_; -}; - -// ============================================================================ -// 哈希函数 -// ============================================================================ - -struct atlas_entry_id_hash { - auto operator()(const atlas_entry_id& id) const -> size_t { - return std::hash{}(id.value); - } -}; - -} // namespace mirage::render::atlas -``` - -## 5. 使用指南 - -### 5.1 创建图集系统 - -```cpp -#include "atlas/texture_atlas_manager.h" -#include "atlas/atlas_upload_scheduler.h" - -// 配置 -atlas_config config{ - .page_size = 2048, - .max_pages = 8, - .channels = 4, - .padding = 1, - .safety_frames = 3, - .on_eviction = [this](auto key, auto id) { on_eviction(key, id); } -}; - -// 创建管理器(需要resource_manager、device和配置) -texture_atlas_manager atlas_mgr(config); - -// 创建上传调度器 -atlas_upload_scheduler upload_scheduler(res_mgr, device, atlas_mgr); -``` - -### 5.2 分配和写入数据 - -```cpp -// 方式1:分配空间并立即写入像素数据 -auto entry_id = atlas_mgr.allocate(user_key, size, pixel_data); - -// 方式2:分步操作(先分配空间再写入数据) -auto entry_id = atlas_mgr.allocate_space(user_key, size); -if (entry_id) { - atlas_mgr.write_data(*entry_id, pixel_data); -} - -// 查找已存在的条目 -if (auto id = atlas_mgr.find(user_key)) { - auto entry = atlas_mgr.get_entry(*id); // 自动更新LRU - // 使用entry->uv_rect等信息 -} -``` - -### 5.3 渲染循环集成 - -```cpp -void render_frame(VkCommandBuffer cmd) { - // 1. 帧开始(更新帧号) - atlas_mgr.begin_frame(); - upload_scheduler.begin_frame(); - - // 2. 准备并执行纹理上传(在render pass之前) - upload_scheduler.prepare_uploads(); - upload_scheduler.execute_uploads(cmd); - - // 3. 获取GPU资源用于渲染 - auto& gpu_res = upload_scheduler.get_gpu_resource(); - auto views = gpu_res.get_all_image_views(); - - // 4. 绑定描述符并渲染 - // 在shader中使用纹理数组索引entry->page_index访问对应页面 - for (const auto& batch : render_batches) { - // 批量标记条目已访问(更新LRU) - for (auto id : batch.entry_ids) { - atlas_mgr.get_entry(id); // get_entry会自动更新LRU - } - // 渲染批次... - } -} -``` - -### 5.4 查询和获取条目 - -```cpp -// 查找条目(会更新LRU) -if (auto id = atlas_mgr.find(user_key)) { - auto entry = atlas_mgr.get_entry(*id); - if (entry) { - // 使用entry->uv_rect获取UV坐标 - // 使用entry->page_index获取所在页面索引 - } -} - -// 只读查询(不更新LRU) -auto entry = atlas_mgr.peek_entry(entry_id); -``` - -## 6. 使用示例 - -### 6.1 基本用法 - -```cpp -// 创建图集管理器 -atlas_config cfg{ - .page_size = 2048, - .max_pages = 8, - .channels = 4, - .padding = 1 -}; - -texture_atlas_manager atlas_mgr(cfg); - -// 添加条目 -std::vector icon_data = load_icon_data(...); -glm::ivec2 size{32, 32}; -auto entry_id = atlas_mgr.allocate(user_key, size, icon_data); - -// 获取条目用于渲染 -if (auto entry = atlas_mgr.get_entry(*entry_id)) { - // 使用UV坐标 - auto uv = entry->uv_rect; - - // 获取所在页面索引 - uint32_t page_idx = entry->page_index; -} -``` - -### 6.2 帧循环集成 - -```cpp -void render_frame(VkCommandBuffer cmd) { - // 帧开始 - atlas_mgr.begin_frame(); - upload_scheduler.begin_frame(); - - // 准备并执行上传 - upload_scheduler.prepare_uploads(); - upload_scheduler.execute_uploads(cmd); - - // 渲染... - for (const auto& batch : render_batches) { - // 访问条目(自动更新LRU) - for (auto id : batch.entry_ids) { - atlas_mgr.get_entry(id); - } - // 渲染批次 - } -} -``` - -### 6.3 MTSDF字形缓存集成(实际实现) - -```cpp -/// @brief 字形度量数据(由glyph_cache管理,与图集系统分离) -struct glyph_metrics { - float glyph_width, glyph_height; ///< 字形实际尺寸 - float bearing_x, bearing_y; ///< 基线偏移 - float advance_x; ///< 水平前进量 - float inner_u0, inner_v0; ///< 字形在纹理中的内部边界 - float inner_u1, inner_v1; -}; - -/// @brief 字形查询结果(组合图集条目和度量数据) -struct glyph_info { - const atlas_entry* entry; ///< 图集条目(UV坐标等) - const glyph_metrics* metrics; ///< 字形度量数据 -}; - -class glyph_cache_v2 { -public: - glyph_cache_v2( - font_manager& font_mgr, - texture_atlas_manager& atlas_mgr - ) : font_mgr_(font_mgr), atlas_mgr_(atlas_mgr) - { - // 设置淘汰回调,清理关联的度量数据 - atlas_mgr_.set_eviction_callback( - [this](atlas_entry_id id, uint64_t user_key) { - glyph_metrics_.erase(user_key); - } - ); - } - - auto get_or_create_glyph(font_id_t font, char32_t codepoint) - -> std::optional - { - // 构建用户键(font_id + codepoint) - uint64_t user_key = (uint64_t(font) << 32) | uint32_t(codepoint); - - // 先检查是否已有度量数据 - auto metrics_it = glyph_metrics_.find(user_key); - if (metrics_it != glyph_metrics_.end()) { - // 已存在,直接返回 - auto* entry = atlas_mgr_.find_by_key(user_key); - if (entry) { - atlas_mgr_.touch_entry(entry->id); - return glyph_info{entry, &metrics_it->second}; - } - } - - // 查找或创建图集条目 - auto entry_id = atlas_mgr_.find_or_add( - user_key, - [&, this]() -> std::optional>> { - // 生成MTSDF - auto bitmap = generate_mtsdf(font, codepoint); - if (!bitmap) return std::nullopt; - - // 存储度量数据(由glyph_cache管理) - glyph_metrics_[user_key] = glyph_metrics{ - .glyph_width = bitmap->glyph_width, - .glyph_height = bitmap->glyph_height, - .bearing_x = bitmap->bearing_x, - .bearing_y = bitmap->bearing_y, - .advance_x = bitmap->advance_x, - .inner_u0 = bitmap->inner_u0, - .inner_v0 = bitmap->inner_v0, - .inner_u1 = bitmap->inner_u1, - .inner_v1 = bitmap->inner_v1 - }; - - // 只返回图集需要的数据 - return std::make_tuple( - bitmap->width, bitmap->height, - std::move(bitmap->data) - ); - } - ); - - if (!entry_id) return std::nullopt; - - auto* entry = atlas_mgr_.get_entry(*entry_id); - auto* metrics = &glyph_metrics_[user_key]; - return glyph_info{entry, metrics}; - } - -private: - font_manager& font_mgr_; - texture_atlas_manager& atlas_mgr_; - std::unordered_map glyph_metrics_; ///< 度量数据缓存 -}; -``` - -**实际实现的关键设计点:** -- 图集系统只管理纹理数据和UV坐标(通用功能) -- 字形度量数据由 [`glyph_cache`](../src/render/text/glyph_cache.h) 自己管理 -- 当图集条目被LRU淘汰时,通过回调通知使用方清理关联数据 -- 参见实际实现:[`glyph_cache.h`](../src/render/text/glyph_cache.h) / [`glyph_cache.cpp`](../src/render/text/glyph_cache.cpp) - -### 6.4 图标图集使用 - -```cpp -class icon_atlas { -public: - icon_atlas(texture_atlas_manager& atlas_mgr) - : atlas_mgr_(atlas_mgr) {} - - auto load_icon(std::string_view name, std::span data, - uint32_t width, uint32_t height) - -> std::optional - { - uint64_t key = std::hash{}(name); - return atlas_mgr_.add_entry_with_key(key, width, height, data); - } - - auto get_icon(std::string_view name) -> const atlas_entry* { - uint64_t key = std::hash{}(name); - return atlas_mgr_.find_by_key(key); - } - -private: - texture_atlas_manager& atlas_mgr_; -}; -``` - -## 7. API参考 - -### 7.1 texture_atlas_manager 主要接口 - -#### 分配与查找 -```cpp -// 分配空间并写入数据 -auto allocate(uint64_t user_key, glm::ivec2 size, - std::span data) -> std::optional; - -// 仅分配空间 -auto allocate_space(uint64_t user_key, glm::ivec2 size) - -> std::optional; - -// 写入数据到已分配空间 -void write_data(atlas_entry_id id, std::span data); - -// 查找条目(更新LRU) -auto find(uint64_t user_key) const -> std::optional; - -// 获取条目(更新LRU) -auto get_entry(atlas_entry_id id) -> std::optional; - -// 只读查询(不更新LRU) -auto peek_entry(atlas_entry_id id) const -> std::optional; -``` - -#### 帧同步 -```cpp -// 帧开始(更新当前帧号) -void begin_frame(); - -// 获取当前帧号 -auto current_frame() const -> uint64_t; -``` - -#### 统计信息 -```cpp -struct stats { - size_t total_entries; // 总条目数 - size_t total_pages; // 总页面数 - float avg_occupancy; // 平均占用率 -}; - -auto get_stats() const -> stats; -``` - -### 7.2 atlas_upload_scheduler 主要接口 - -```cpp -// 帧开始 -void begin_frame(); - -// 准备上传(收集脏区域) -void prepare_uploads(); - -// 执行上传(记录命令) -void execute_uploads(VkCommandBuffer cmd); - -// 获取GPU资源 -auto get_gpu_resource() -> atlas_gpu_resource&; -``` - -### 7.3 atlas_gpu_resource 主要接口 - -```cpp -// 获取所有图像视图 -auto get_all_image_views() const -> std::vector; - -// 获取图像数量 -auto get_image_count() const -> size_t; - -// 获取采样器 -auto get_sampler() const -> VkSampler; -``` - -## 8. 异步上传流程 - -```mermaid -sequenceDiagram - participant App as 应用层 - participant AM as AtlasManager - participant US as UploadScheduler - participant VK as Vulkan - - App->>AM: add_entry data - AM->>AM: 写入CPU端图集数据 - AM->>AM: 标记脏区域 - - loop 每帧 - App->>AM: begin_frame - App->>AM: process_uploads - AM->>US: submit_upload dirty_regions - US->>US: 创建staging buffer - US->>US: 复制数据到staging - US->>VK: vkCmdCopyBufferToImage - US->>VK: 提交命令 - - US->>US: poll_completed - US-->>AM: 上传完成通知 - AM-->>App: return true 需要重绘 - end -``` - -## 9. LRU淘汰策略 - -### 9.1 淘汰触发条件 -- 所有页面都满且无法容纳新条目 -- 条目总数超过 `max_entries` 限制 - -### 9.2 淘汰算法 -```cpp -auto texture_atlas_manager::evict_lru_entries(size_t count) -> size_t { - size_t evicted = 0; - - while (evicted < count && !lru_list_.empty()) { - // 获取最久未访问的条目 - auto& oldest = lru_list_.front(); - - // 跳过当前帧使用的条目(安全窗口) - if (oldest.last_frame >= current_frame_ - config_.safe_frames) { - break; // 所有条目都在安全窗口内 - } - - // 通知使用方(如果有用户键关联) - if (eviction_callback_) { - auto key_it = entry_to_key_map_.find(oldest.id); - if (key_it != entry_to_key_map_.end()) { - eviction_callback_(oldest.id, key_it->second); - } - } - - // 从图集中移除 - remove_entry_internal(oldest.id); - ++evicted; - } - - evicted_count_ += evicted; - return evicted; -} -``` - -### 9.3 安全窗口 -为避免淘汰正在GPU使用的条目,设置安全窗口(默认3帧)。只有超过安全窗口的条目才会被淘汰。 - -## 10. 文件结构(已实现) - -``` -src/render/atlas/ -├── atlas_types.h # ✅ 类型定义(atlas_entry_id, atlas_entry等) -├── atlas_page.h # ✅ 单页面管理 -├── atlas_page.cpp -├── atlas_gpu_resource.h # ✅ GPU资源定义和管理 -├── atlas_gpu_resource.cpp -├── atlas_upload_scheduler.h # ✅ 异步上传调度 -├── atlas_upload_scheduler.cpp -├── texture_atlas_manager.h # ✅ 核心管理器 -└── texture_atlas_manager.cpp -``` - -**集成使用:** -- [`glyph_cache`](../src/render/text/glyph_cache.h) - 使用图集系统管理字形缓存 -- [`text_renderer`](../src/render/pipeline/text_renderer.h) - 使用图集GPU资源进行渲染 - -## 11. 迁移计划(已完成) - -### ✅ Phase 1: 创建新模块 -1. ✅ 创建 [`src/render/atlas/`](../src/render/atlas/) 目录 -2. ✅ 实现 [`atlas_types.h`](../src/render/atlas/atlas_types.h) - 基础类型定义 -3. ✅ 实现 [`atlas_page`](../src/render/atlas/atlas_page.h) 类 - CPU端图集管理 -4. ✅ 实现 [`atlas_gpu_resource`](../src/render/atlas/atlas_gpu_resource.h) 类 - GPU资源管理 -5. ✅ 实现 [`atlas_upload_scheduler`](../src/render/atlas/atlas_upload_scheduler.h) 类 - 异步上传 -6. ✅ 实现 [`texture_atlas_manager`](../src/render/atlas/texture_atlas_manager.h) 类 - 核心管理器 - -### ✅ Phase 2: 集成到MTSDF系统 -1. ✅ 重构 [`glyph_cache`](../src/render/text/glyph_cache.h) 使用新图集系统 -2. ✅ 更新 [`text_renderer`](../src/render/pipeline/text_renderer.h) 使用新图集系统的GPU资源 -3. ✅ 移除 `text_renderer` 中的直接纹理上传逻辑 -4. ✅ 删除旧的 `font_atlas` 代码 - -### ✅ Phase 3: 验证与优化 -1. ✅ 功能验证和测试 -2. ✅ 与渲染管线集成 -3. ✅ 文档更新 - -## 12. 性能考虑 - -1. **批量操作** - 脏区域批量上传,减少命令提交次数 -2. **内存池** - staging buffer 池化复用 -3. **无锁设计** - 读取路径使用 `shared_mutex`,多读者不阻塞 -4. **预分配** - 条目数组预分配,减少动态内存分配 -5. **缓存友好** - 条目数据连续存储,提高缓存命中率 -6. **异步上传** - 使用专用传输队列,不阻塞渲染 - -## 13. 线程安全 - -| 操作 | 线程安全 | 说明 | -|------|---------|------| -| `add_entry` | 是 | 写锁保护 | -| `get_entry` | 是 | 读锁保护,返回指针在锁外有效 | -| `touch_entry` | 是 | 写锁保护 | -| `find_by_key` | 是 | 读锁保护 | -| `process_uploads` | 是 | 内部同步 | -| GPU资源访问 | 注意 | 只在渲染线程访问,无需额外同步 | - -## 14. 错误处理 - -```cpp -// 图集满且无法淘汰更多条目 -auto entry_id = atlas_mgr->add_entry(width, height, data); -if (!entry_id) { - // 处理错误: - // 1. 记录日志 - // 2. 使用占位符纹理 - // 3. 等待下一帧重试 -} - -// GPU资源未初始化 -auto* gpu_res = atlas_mgr->get_gpu_resource(page_index); -if (!gpu_res || !gpu_res->initialized) { - // 使用默认纹理或跳过渲染 -} -``` - -## 15. 调试支持 - -```cpp -// 获取统计信息 -auto stats = atlas_mgr->get_statistics(); -std::cout << "Entries: " << stats.total_entries << "\n" - << "Pages: " << stats.total_pages << "\n" - << "Memory: " << stats.total_memory_bytes / 1024 << " KB\n" - << "Usage: " << stats.average_page_usage * 100 << "%\n" - << "Pending: " << stats.pending_uploads << "\n" - << "Evicted: " << stats.evicted_entries << "\n"; - -// 可选:导出图集为PNG用于调试 -void debug_export_atlas(const texture_atlas_manager& mgr, - uint32_t page_index, - const std::string& path); -``` - -## 16. 已知限制 - -1. **Shader需要支持纹理数组**:支持多图集页面需要修改GLSL shader以使用纹理数组(`sampler2DArray`) - - 当前实现支持最多16个图集页面 - - Shader需要使用`page_index`作为数组索引访问对应页面 - -2. **最大页面数限制**:当前限制为16个页面 - - 可通过修改`MAX_ATLAS_PAGES`常量调整 - - 受GPU纹理数组大小限制 - -3. **无自动碎片整理**:长时间运行可能产生内存碎片 - - 需要手动触发重建以整理碎片 - - 未来版本可能添加自动碎片整理功能 - -4. **打包算法**:当前使用Guillotine算法 - - 空间利用率较好但不是最优 - - 未来可能升级为MaxRects算法 - -5. **同步上传**:当前上传是同步的 - - 在`execute_uploads`中直接记录命令 - - 未来可能添加真正的异步上传支持 - -## 17. 未来改进方向 - -### 17.1 算法优化 -1. **实现MaxRects算法**:提升空间利用率(相比Guillotine可提升5-10%) -2. **自动碎片整理**:后台自动整理碎片,无需手动重建 -3. **预测性预加载**:基于使用模式预测并预加载可能需要的纹理 - -### 17.2 功能扩展 -1. **支持不同尺寸的页面**:根据内容自适应选择页面大小 -2. **压缩纹理支持**:支持BC/ASTC等压缩格式以节省显存 -3. **Mipmap支持**:为需要缩放的纹理自动生成mipmap链 - -### 17.3 性能优化 -1. **真正的异步上传**:使用独立线程进行纹理上传 -2. **上传优先级系统**:根据重要性调整上传顺序 -3. **更智能的LRU策略**:考虑条目大小和访问频率的加权LRU - -### 17.4 调试工具 -1. **可视化工具**:实时查看图集布局和占用情况 -2. **性能统计面板**:详细的性能指标和瓶颈分析 -3. **内存泄漏检测**:自动检测和报告潜在的内存问题 - -### 17.5 易用性改进 -1. **配置文件支持**:从JSON/YAML文件加载配置 -2. **更多预设配置**:针对不同使用场景的最佳实践配置 -3. **错误恢复机制**:更好的错误处理和自动恢复策略 - ---- - -**文档版本**:v2.0 -**最后更新**:2025-12-14 -**状态**:已实现并集成到渲染管线 \ No newline at end of file diff --git a/docs/TEXT_INPUT_ARCHITECTURE_ANALYSIS.md b/docs/TEXT_INPUT_ARCHITECTURE_ANALYSIS.md deleted file mode 100644 index 24180ae..0000000 --- a/docs/TEXT_INPUT_ARCHITECTURE_ANALYSIS.md +++ /dev/null @@ -1,660 +0,0 @@ -# 文本输入框控件架构分析文档 - -本文档分析了 mirage 框架的现有架构,为实现文本输入框控件提供技术参考。 - -## 1. Widget 基类结构分析 - -### 1.1 类继承层次 - -``` -widget_base (抽象基类) -├── leaf_widget_base (叶子控件基类) -│ ├── text_widget (文本显示) -│ ├── scrollbar (滚动条) -│ ├── fill_box (填充盒) -│ └── imager (图片) -├── container_widget_base (容器控件基类) -│ ├── v_stack, h_stack (栈式布局) -│ ├── overlay (层叠布局) -│ └── scroll_box (滚动容器) -└── composite_widget_base (组合控件基类) - └── (自定义复合控件) -``` - -### 1.2 widget_base 关键接口 - -| 接口 | 说明 | 文本输入框需要 | -|------|------|----------------| -| [`do_measure()`](src/widget/widget_base.h:375) | 测量控件期望大小 | ✅ 是 | -| [`arrange()`](src/widget/widget_base.h:111) | 布局控件到指定状态 | ✅ 是 | -| [`build_render_command()`](src/widget/widget_base.h:182) | 构建渲染命令 | ✅ 是 | -| [`on_key_down()`](src/widget/widget_base.h:256) | 键盘按下事件 | ✅ 是 | -| [`on_key_up()`](src/widget/widget_base.h:257) | 键盘释放事件 | ✅ 是 | -| [`on_char_input()`](src/widget/widget_base.h:258) | 字符输入事件 | ✅ 是 | -| [`on_mouse_down()`](src/widget/widget_base.h:248) | 鼠标按下 | ✅ 是 | -| [`on_mouse_up()`](src/widget/widget_base.h:249) | 鼠标释放 | ✅ 是 | -| [`on_mouse_move()`](src/widget/widget_base.h:250) | 鼠标移动 | ✅ 是 | -| [`on_focus_gained()`](src/widget/widget_base.h:261) | 获得焦点 | ✅ 是 | -| [`on_focus_lost()`](src/widget/widget_base.h:262) | 失去焦点 | ✅ 是 | -| [`is_focusable()`](src/widget/widget_base.h:293) | 是否可聚焦 | ✅ 需重写返回true | -| [`is_event_enabled()`](src/widget/widget_base.h:290) | 是否启用事件 | ✅ 是 | - -### 1.3 leaf_widget_base 特性 - -[`leaf_widget_base`](src/widget/leaf_widget_base.h:16) 是叶子控件的基类,特点: - -- 自动在 [`arrange()`](src/widget/leaf_widget_base.h:18) 中更新布局状态到外部存储 -- 提供 [`get_intrinsic_size()`](src/widget/leaf_widget_base.h:35) 接口获取固有大小 -- 提供 [`needs_repaint()`](src/widget/leaf_widget_base.h:40) 检查是否需要重绘 - -### 1.4 属性宏 - -框架提供了三个属性定义宏: - -```cpp -WIDGET_ATTR(type, name) // 普通属性 -WIDGET_RENDER_ATTR(type, name) // 改变时标记渲染脏 -WIDGET_MEASURE_ATTR(type, name) // 改变时标记测量脏 -``` - -## 2. 事件系统分析 - -### 2.1 事件路由器 widget_event_router - -[`widget_event_router`](src/widget/widget_event/widget_event_router.h:59) 是事件系统的核心: - -**职责:** -- 接收窗口层事件并分发到控件层 -- 管理焦点、快捷键和 IME -- 实现事件冒泡机制 - -**事件处理顺序:** -1. 快捷键优先检查(仅 KEY_PRESSED 且非重复) -2. Tab 导航处理(Tab/Shift+Tab) -3. IME 事件处理 -4. 分发到焦点控件(支持冒泡) - -### 2.2 事件流向图 - -``` -窗口层事件 - │ - ▼ -widget_event_router - │ - ├─── 键盘事件 ────────────────────────────────────────┐ - │ │ │ - │ ▼ │ - │ 快捷键检查 (shortcut_manager) │ - │ │ (未匹配) │ - │ ▼ │ - │ Tab导航检查 (focus_manager) │ - │ │ (不是Tab键) │ - │ ▼ │ - │ 分发到焦点控件 ──────────────────────────────────┤ - │ │ │ - │ ▼ │ - │ 事件冒泡 (从焦点控件向上) │ - │ │ - ├─── 字符输入事件 ────────────────────────────────────┤ - │ │ │ - │ ▼ │ - │ 分发到焦点控件 │ - │ │ │ - │ ▼ │ - │ 事件冒泡 │ - │ │ - └─── 鼠标事件 ────────────────────────────────────────┘ - │ - ▼ - 命中测试 (hit_test) - │ - ▼ - 分发到命中控件 - │ - ├─── 点击可聚焦控件 → 自动设置焦点 - │ - └─── 事件冒泡 (滚轮事件) -``` - -### 2.3 事件结果 event_result - -[`event_result`](src/widget/widget_event/event_result.h:13) 结构用于控制事件处理: - -```cpp -struct event_result { - bool is_handled : 1{false}; // 事件是否已处理(停止冒泡) - bool should_capture : 1{false}; // 请求捕获鼠标 - bool should_release : 1{false}; // 请求释放鼠标捕获 - - // 工厂方法 - static event_result unhandled(); // 未处理 - static event_result handled(); // 已处理 - static event_result capture(); // 已处理 + 捕获鼠标 - static event_result release(); // 已处理 + 释放鼠标 -}; -``` - -### 2.4 键盘事件类型 - -[`keyboard_event`](src/widget/widget_event/event_types.h:37) 结构: - -```cpp -struct keyboard_event { - key_action action; // press, release, repeat - key_code key; // 键码 - int32_t scancode; // 扫描码 - key_mod modifiers; // 修饰键状态 - bool repeat; // 是否为重复事件 - - // 辅助方法 - bool has_modifier(key_mod mod); - bool ctrl(), shift(), alt(), super(); -}; -``` - -### 2.5 字符输入事件 - -[`char_event`](src/widget/widget_event/event_types.h:85) 结构: - -```cpp -struct char_event { - uint32_t codepoint; // UTF-32 字符码点 - key_mod modifiers; // 修饰键状态 - - bool is_printable_ascii(); - char to_ascii(); -}; -``` - -## 3. 焦点管理机制 - -### 3.1 focus_manager 功能 - -[`focus_manager`](src/widget/widget_event/focus_manager.h:67) 负责: - -- 追踪当前焦点控件 -- 维护焦点链(从根到焦点控件的路径) -- 支持 Tab 键导航 -- 发布焦点变化事件 - -### 3.2 焦点相关接口 - -```cpp -// 设置焦点 -bool set_focus(widget_base* target, focus_change_reason reason); - -// 清除焦点 -void clear_focus(); - -// 获取焦点控件 -widget_base* get_focused_widget(); - -// Tab 导航 -bool navigate_next(); -bool navigate_previous(); - -// 注册可聚焦控件 -void register_focusable(uint64_t widget_id, widget_base* target); -void register_tab_stop(widget_base* target, int32_t tab_index); -``` - -### 3.3 控件需要实现的焦点接口 - -```cpp -// 在 widget_base 中 -virtual bool is_focusable() const { return false; } // 文本框需返回 true -virtual void on_focus_gained() {} // 显示光标 -virtual void on_focus_lost() {} // 隐藏光标 -``` - -## 4. IME 系统集成 - -### 4.1 ime_manager 功能 - -[`ime_manager`](src/widget/widget_event/ime_manager.h:73) 负责: - -- 控制 IME 启用/禁用状态 -- 管理预编辑文本 -- 定位候选窗口 -- 处理文本提交 -- 发布 IME 相关事件 - -### 4.2 IME 状态 - -```cpp -enum class ime_state : uint8_t { - disabled, // IME 禁用 - enabled, // IME 启用但未激活 - composing // IME 正在预编辑 -}; -``` - -### 4.3 IME 事件订阅 - -```cpp -// 获取 IME 管理器 -ime_manager& ime = router.get_ime_manager(); - -// 订阅预编辑事件 -ime.on_preedit_start().subscribe(...); -ime.on_preedit_update().subscribe(...); -ime.on_preedit_end().subscribe(...); - -// 订阅文本提交事件 -ime.on_commit().subscribe(...); -``` - -### 4.4 预编辑信息 - -[`ime_preedit_info`](src/widget/widget_event/ime_types.h:49) 结构: - -```cpp -struct ime_preedit_info { - std::string text; // 预编辑文本(UTF-8) - int32_t cursor_pos; // 光标位置 - int32_t selection_start; // 选中起始 - int32_t selection_length; // 选中长度 - bool active; // 是否激活 -}; -``` - -### 4.5 设置候选窗口位置 - -```cpp -// 当文本框获得焦点或光标位置变化时 -ime_manager& ime = router.get_ime_manager(); -ime.set_candidate_position( - cursor_screen_x, - cursor_screen_y, - line_height -); -``` - -## 5. 文本渲染系统 - -### 5.1 text_shaper 功能 - -[`text_shaper`](src/render/text/text_shaper.h:28) 负责文本排版: - -```cpp -// 排版文本,生成顶点数据 -shaped_text shape_text( - std::u32string_view text, - font_id_t font_id, - float font_size, - const vec2f_t& position, - const color& text_color -); - -// 测量文本尺寸(不生成顶点) -text_metrics measure_text( - std::u32string_view text, - font_id_t font_id, - float font_size -); - -// UTF-8 转 UTF-32 -static std::u32string utf8_to_utf32(std::string_view utf8); -``` - -### 5.2 shaped_text 结果 - -```cpp -struct shaped_text { - std::vector vertices; - std::vector indices; - float total_width; - float total_height; -}; - -struct text_metrics { - float width; - float height; -}; -``` - -### 5.3 render_collector 文本提交 - -通过 [`render_collector`](src/widget/render_collector.h:22) 提交文本渲染命令: - -```cpp -void submit_text( - const vec2f_t& position, - std::string_view text, - const color& text_color, - float font_size, - uint32_t font_id = 0, - int32_t z_order = 0 -); -``` - -## 6. 文本输入框设计建议 - -### 6.1 推荐继承方案 - -**方案 A:继承 leaf_widget_base(推荐)** - -```cpp -class text_input : public leaf_widget_base { - // 实现方式与 scrollbar 类似 -}; -``` - -优点: -- 简单直接,适合单一功能控件 -- 布局状态管理自动处理 -- 参考 [`scrollbar`](src/widget/widgets/scrollbar.h:57) 实现 - -**方案 B:继承 composite_widget_base** - -```cpp -class text_input : public composite_widget_base { - // 内部组合多个控件 -}; -``` - -适用场景: -- 需要内置滚动条的多行文本框 -- 带有装饰元素(图标、清除按钮)的输入框 - -### 6.2 需要实现的关键方法 - -```cpp -class text_input : public leaf_widget_base { -public: - // ======================================================================== - // 布局接口(必需) - // ======================================================================== - - [[nodiscard]] auto do_measure(const vec2f_t& available_size) const - -> vec2f_t override; - - // arrange 使用 leaf_widget_base 默认实现 - - // ======================================================================== - // 渲染接口(必需) - // ======================================================================== - - uint32_t build_render_command(render_collector& collector, uint32_t z_order) - const override; - - // ======================================================================== - // 焦点接口(必需) - // ======================================================================== - - [[nodiscard]] bool is_focusable() const override { return true; } - - void on_focus_gained() override; - void on_focus_lost() override; - - // ======================================================================== - // 键盘事件(必需) - // ======================================================================== - - [[nodiscard]] event_result on_key_down(const keyboard_event& event) override; - [[nodiscard]] event_result on_key_up(const keyboard_event& event) override; - [[nodiscard]] event_result on_char_input(const char_event& event) override; - - // ======================================================================== - // 鼠标事件(必需) - // ======================================================================== - - [[nodiscard]] event_result on_mouse_down(const mouse_event& event) override; - [[nodiscard]] event_result on_mouse_up(const mouse_event& event) override; - [[nodiscard]] event_result on_mouse_move(const mouse_event& event) override; - - // ======================================================================== - // 可选接口 - // ======================================================================== - - void tick(float dt) override; // 用于光标闪烁动画 - - [[nodiscard]] auto get_intrinsic_size() const - -> std::optional override; -}; -``` - -### 6.3 IME 集成方式 - -```cpp -class text_input : public leaf_widget_base { -private: - ime_preedit_info preedit_; // 当前预编辑状态 - -public: - void on_focus_gained() override { - // 启用 IME - if (auto* ctx = get_context()) { - auto& ime = ctx->get_ime_manager(); - ime.enable(); - ime.set_focused_target(this); - update_candidate_position(); - } - // 显示光标 - show_cursor_ = true; - } - - void on_focus_lost() override { - // 禁用 IME - if (auto* ctx = get_context()) { - auto& ime = ctx->get_ime_manager(); - ime.disable(); - ime.set_focused_target(nullptr); - } - // 隐藏光标 - show_cursor_ = false; - // 清除预编辑状态 - preedit_.clear(); - } - - void handle_ime_preedit(const ime_preedit_event& event) { - preedit_ = event.preedit; - mark_render_dirty(); - } - - void handle_ime_commit(const ime_commit_event& event) { - insert_text(event.text); - preedit_.clear(); - mark_render_dirty(); - } - - void update_candidate_position() { - // 计算光标屏幕位置 - auto cursor_pos = calculate_cursor_screen_position(); - if (auto* ctx = get_context()) { - auto& ime = ctx->get_ime_manager(); - ime.set_candidate_position( - static_cast(cursor_pos.x()), - static_cast(cursor_pos.y()), - static_cast(font_size_) - ); - } - } -}; -``` - -### 6.4 键盘事件处理示例 - -```cpp -event_result text_input::on_key_down(const keyboard_event& event) { - // 如果正在 IME 预编辑,部分按键需要特殊处理 - if (preedit_.active) { - // Enter 确认预编辑 - if (event.key == key_code::ENTER) { - // IME 会处理并发送 commit 事件 - return event_result::handled(); - } - // Escape 取消预编辑 - if (event.key == key_code::ESCAPE) { - preedit_.clear(); - mark_render_dirty(); - return event_result::handled(); - } - } - - // 普通按键处理 - switch (event.key) { - case key_code::LEFT: - move_cursor_left(event.shift()); - return event_result::handled(); - - case key_code::RIGHT: - move_cursor_right(event.shift()); - return event_result::handled(); - - case key_code::BACKSPACE: - delete_backward(); - return event_result::handled(); - - case key_code::DELETE: - delete_forward(); - return event_result::handled(); - - case key_code::HOME: - move_cursor_to_start(event.shift()); - return event_result::handled(); - - case key_code::END: - move_cursor_to_end(event.shift()); - return event_result::handled(); - - // Ctrl+A 全选 - case key_code::A: - if (event.ctrl()) { - select_all(); - return event_result::handled(); - } - break; - - // Ctrl+C 复制 - case key_code::C: - if (event.ctrl()) { - copy_selection(); - return event_result::handled(); - } - break; - - // Ctrl+V 粘贴 - case key_code::V: - if (event.ctrl()) { - paste_from_clipboard(); - return event_result::handled(); - } - break; - - // Ctrl+X 剪切 - case key_code::X: - if (event.ctrl()) { - cut_selection(); - return event_result::handled(); - } - break; - - default: - break; - } - - return event_result::unhandled(); -} - -event_result text_input::on_char_input(const char_event& event) { - // 如果正在 IME 预编辑,忽略字符输入(由 IME 处理) - if (preedit_.active) { - return event_result::handled(); - } - - // 忽略控制字符 - if (event.codepoint < 0x20) { - return event_result::unhandled(); - } - - // 插入字符 - insert_codepoint(event.codepoint); - return event_result::handled(); -} -``` - -### 6.5 渲染实现要点 - -```cpp -uint32_t text_input::build_render_command( - render_collector& collector, - uint32_t z_order -) const { - if (!should_render()) return z_order; - - auto state = get_layout_state(); - if (!state) return z_order; - - vec2f_t pos = vec2f_t(state->global_pos()); - vec2f_t size = vec2f_t(state->size()); - - // 1. 渲染背景 - collector.submit_rect(pos, size, background_color_, corner_radius_, z_order++); - - // 2. 渲染边框 - // ... - - // 3. 渲染选中区域(如果有) - if (has_selection()) { - auto sel_bounds = calculate_selection_bounds(); - collector.submit_rect(sel_bounds.pos, sel_bounds.size, - selection_color_, {}, z_order++); - } - - // 4. 渲染文本 - vec2f_t text_pos = pos + padding_; - collector.submit_text(text_pos, text_, text_color_, font_size_, - font_id_, z_order++); - - // 5. 渲染预编辑文本(如果有) - if (preedit_.active && !preedit_.text.empty()) { - // 计算预编辑文本位置 - vec2f_t preedit_pos = calculate_preedit_position(); - // 渲染预编辑背景(可选) - // 渲染预编辑文本 - collector.submit_text(preedit_pos, preedit_.text, preedit_color_, - font_size_, font_id_, z_order++); - // 渲染预编辑下划线 - // ... - } - - // 6. 渲染光标(如果有焦点且光标可见) - if (has_focus() && cursor_visible_) { - auto cursor_pos = calculate_cursor_position(); - collector.submit_rect(cursor_pos, {cursor_width_, font_size_}, - cursor_color_, {}, z_order++); - } - - return z_order; -} -``` - -## 7. 总结 - -### 7.1 文本输入框需要的核心能力 - -1. **焦点管理**:实现 `is_focusable()` 返回 true,处理焦点获取/失去 -2. **键盘输入**:处理方向键、删除键、快捷键等 -3. **字符输入**:处理 `on_char_input` 事件 -4. **IME 集成**:支持中文等复杂输入法 -5. **鼠标交互**:支持点击定位光标、拖拽选择 -6. **文本渲染**:使用 text_shaper 进行排版和渲染 -7. **光标管理**:闪烁动画、位置计算 - -### 7.2 与现有系统的集成点 - -| 系统 | 集成方式 | -|------|----------| -| widget_base | 继承 leaf_widget_base | -| 事件路由 | 重写事件处理虚函数 | -| 焦点管理 | is_focusable() + 注册 tab_stop | -| IME | 订阅 ime_manager 事件 | -| 文本渲染 | 使用 text_shaper + render_collector | -| 状态管理 | 使用 WIDGET_*_ATTR 宏 | - -### 7.3 参考实现 - -- [`text_widget`](src/widget/widgets/text_widget.h) - 静态文本显示 -- [`scrollbar`](src/widget/widgets/scrollbar.h) - 交互式叶子控件示例 \ No newline at end of file diff --git a/docs/TEXT_INPUT_DESIGN.md b/docs/TEXT_INPUT_DESIGN.md deleted file mode 100644 index 8ab8a60..0000000 --- a/docs/TEXT_INPUT_DESIGN.md +++ /dev/null @@ -1,1490 +0,0 @@ - -# 文本输入框控件详细设计文档 - -本文档提供 `text_input` 控件的完整架构设计,可直接用于编码实现。 - -## 1. 文件结构 - -``` -src/widget/widgets/text_input/ -├── text_input.h # 主控件类声明 -├── text_input.cpp # 主控件类实现 -├── text_model.h # 文本数据模型声明 -├── text_model.cpp # 文本数据模型实现 -├── text_selection.h # 选择范围数据结构 -├── cursor_controller.h # 光标控制器声明 -└── cursor_controller.cpp # 光标控制器实现 -``` - -## 2. 类图 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ text_input │ -│ (leaf_widget_base) │ -├─────────────────────────────────────────────────────────────────────────┤ -│ - model_: text_model │ -│ - cursor_: cursor_controller │ -│ - style_: text_input_style │ -│ - preedit_: ime_preedit_info │ -│ - scroll_offset_: float │ -│ - is_dragging_: bool │ -│ - state_: text_input_state │ -├─────────────────────────────────────────────────────────────────────────┤ -│ + text(string_view) -> text_input& │ -│ + placeholder(string_view) -> text_input& │ -│ + font_size(float) -> text_input& │ -│ + style(text_input_style) -> text_input& │ -│ + read_only(bool) -> text_input& │ -│ + max_length(size_t) -> text_input& │ -│ + on_text_changed(callback) -> text_input& │ -│ + on_submit(callback) -> text_input& │ -├─────────────────────────────────────────────────────────────────────────┤ -│ # do_measure(vec2f_t) -> vec2f_t │ -│ # build_render_command(collector, z_order) -> uint32_t │ -│ # on_key_down(keyboard_event) -> event_result │ -│ # on_char_input(char_event) -> event_result │ -│ # on_mouse_down(mouse_event) -> event_result │ -│ # on_mouse_up(mouse_event) -> event_result │ -│ # on_mouse_move(mouse_event) -> event_result │ -│ # on_focus_gained() -> void │ -│ # on_focus_lost() -> void │ -│ # tick(float dt) -> void │ -└─────────────────────────────────────────────────────────────────────────┘ - │ - │ uses - ┌───────────────┴───────────────┐ - ▼ ▼ -┌─────────────────────────────┐ ┌─────────────────────────────┐ -│ text_model │ │ cursor_controller │ -├─────────────────────────────┤ ├─────────────────────────────┤ -│ - content_: u32string │ │ - position_: size_t │ -│ - selection_: text_selection│ │ - blink_timer_: float │ -│ - max_length_: size_t │ │ - visible_: bool │ -├─────────────────────────────┤ │ - blink_rate_: float │ -│ + get_text() -> u32string │ ├─────────────────────────────┤ -│ + set_text(u32string) │ │ + get_position() -> size_t │ -│ + insert(u32string) │ │ + set_position(size_t) │ -│ + delete_range(start, end) │ │ + move_left(extend?) │ -│ + get_selection() │ │ + move_right(extend?) │ -│ + set_selection(selection) │ │ + move_word_left(extend?) │ -│ + select_all() │ │ + move_word_right(extend?) │ -│ + select_word_at(pos) │ │ + move_to_start(extend?) │ -│ + has_selection() │ │ + move_to_end(extend?) │ -│ + get_selected_text() │ │ + update(float dt) │ -│ + delete_selection() │ │ + reset_blink() │ -│ + length() -> size_t │ │ + is_visible() -> bool │ -└─────────────────────────────┘ └─────────────────────────────┘ - │ - │ contains - ▼ -┌─────────────────────────────┐ -│ text_selection │ -├─────────────────────────────┤ -│ + anchor: size_t │ -│ + focus: size_t │ -├─────────────────────────────┤ -│ + start() -> size_t │ -│ + end() -> size_t │ -│ + length() -> size_t │ -│ + is_collapsed() -> bool │ -│ + is_reversed() -> bool │ -│ + collapse_to_focus() │ -│ + collapse_to_start() │ -│ + collapse_to_end() │ -└─────────────────────────────┘ -``` - -## 3. 数据结构设计 - -### 3.1 text_selection.h - -```cpp -#pragma once - -#include -#include - -namespace mirage { - -/// @brief 文本选择范围 -/// -/// 使用 anchor/focus 模型表示选择范围: -/// - anchor: 选择起始点(用户开始选择的位置) -/// - focus: 选择终点(当前光标位置) -/// -/// 当 anchor == focus 时,表示没有选择(光标位置) -/// 当 anchor < focus 时,表示正向选择 -/// 当 anchor > focus 时,表示反向选择 -struct text_selection { - /// 选择锚点(选择开始位置) - size_t anchor{0}; - - /// 选择焦点(当前光标位置) - size_t focus{0}; - - /// @brief 默认构造 - constexpr text_selection() noexcept = default; - - /// @brief 构造折叠的选择(光标位置) - /// @param pos 光标位置 - constexpr explicit text_selection(size_t pos) noexcept - : anchor(pos), focus(pos) {} - - /// @brief 构造选择范围 - /// @param anchor_ 锚点位置 - /// @param focus_ 焦点位置 - constexpr text_selection(size_t anchor_, size_t focus_) noexcept - : anchor(anchor_), focus(focus_) {} - - /// @brief 获取选择起始位置(较小值) - [[nodiscard]] constexpr size_t start() const noexcept { - return std::min(anchor, focus); - } - - /// @brief 获取选择结束位置(较大值) - [[nodiscard]] constexpr size_t end() const noexcept { - return std::max(anchor, focus); - } - - /// @brief 获取选择长度 - [[nodiscard]] constexpr size_t length() const noexcept { - return end() - start(); - } - - /// @brief 检查选择是否折叠(无选择) - [[nodiscard]] constexpr bool is_collapsed() const noexcept { - return anchor == focus; - } - - /// @brief 检查是否为反向选择 - [[nodiscard]] constexpr bool is_reversed() const noexcept { - return anchor > focus; - } - - /// @brief 折叠选择到焦点位置 - constexpr void collapse_to_focus() noexcept { - anchor = focus; - } - - /// @brief 折叠选择到起始位置 - constexpr void collapse_to_start() noexcept { - anchor = focus = start(); - } - - /// @brief 折叠选择到结束位置 - constexpr void collapse_to_end() noexcept { - anchor = focus = end(); - } - - /// @brief 扩展选择到新的焦点位置 - /// @param new_focus 新的焦点位置 - constexpr void extend_to(size_t new_focus) noexcept { - focus = new_focus; - } - - /// @brief 移动整个选择(保持长度不变) - /// @param new_focus 新的焦点位置 - constexpr void move_to(size_t new_focus) noexcept { - anchor = focus = new_focus; - } - - /// @brief 限制选择范围在有效范围内 - /// @param max_pos 最大位置(文本长度) - constexpr void clamp(size_t max_pos) noexcept { - anchor = std::min(anchor, max_pos); - focus = std::min(focus, max_pos); - } - - /// @brief 相等比较 - [[nodiscard]] constexpr bool operator==(const text_selection&) const noexcept = default; -}; - -} // namespace mirage -``` - -### 3.2 text_input_style 结构 - -```cpp -/// @brief 文本输入框样式配置 -struct text_input_style { - // 尺寸 - float min_width{100.0f}; ///< 最小宽度 - float height{32.0f}; ///< 固定高度 - margin padding{8.0f, 6.0f}; ///< 内边距 (horizontal, vertical) - float corner_radius{4.0f}; ///< 圆角半径 - float border_width{1.0f}; ///< 边框宽度 - - // 颜色 - 正常状态 - color background{color::from_rgba(45, 45, 48, 255)}; - color border{color::from_rgba(63, 63, 70, 255)}; - color text{color::white()}; - color placeholder{color::from_rgba(128, 128, 128, 255)}; - color selection{color::from_rgba(51, 153, 255, 128)}; - color cursor{color::white()}; - - // 颜色 - 焦点状态 - color border_focused{color::from_rgba(0, 122, 204, 255)}; - - // 颜色 - 禁用状态 - color background_disabled{color::from_rgba(30, 30, 30, 255)}; - color text_disabled{color::from_rgba(96, 96, 96, 255)}; - - // 颜色 - 只读状态 - color background_readonly{color::from_rgba(40, 40, 43, 255)}; - - // 光标 - float cursor_width{2.0f}; ///< 光标宽度 - float cursor_blink_rate{0.53f}; ///< 光标闪烁周期(秒) - - // 字体 - uint32_t font_id{0}; ///< 字体 ID - float font_size{14.0f}; ///< 字体大小 - - // 预编辑样式 - color preedit_background{color::from_rgba(60, 60, 65, 255)}; - color preedit_underline{color::from_rgba(100, 100, 105, 255)}; - float preedit_underline_width{2.0f}; - - /// @brief 默认样式 - static text_input_style default_style() { return {}; } - - /// @brief 圆角样式 - static text_input_style rounded() { - text_input_style s; - s.corner_radius = 16.0f; - s.padding = margin{12.0f, 6.0f}; - return s; - } - - /// @brief 无边框样式 - static text_input_style borderless() { - text_input_style s; - s.border_width = 0.0f; - s.background = color::transparent(); - return s; - } -}; -``` - -### 3.3 text_input_state 枚举 - -```cpp -/// @brief 文本输入框状态 -enum class text_input_state : uint8_t { - normal, ///< 正常状态 - focused, ///< 获得焦点 - disabled, ///< 禁用状态 - read_only ///< 只读状态 -}; -``` - -## 4. text_model 类设计 - -### 4.1 text_model.h - -```cpp -#pragma once - -#include "text_selection.h" -#include -#include -#include -#include - -namespace mirage { - -/// @brief 文本数据模型 -/// -/// 管理文本内容和选择状态,提供文本编辑操作。 -/// 内部使用 UTF-32 存储以支持正确的字符级操作。 -class text_model { -public: - /// @brief 文本变更事件数据 - struct text_change_event { - std::u32string_view old_text; ///< 变更前的文本 - std::u32string_view new_text; ///< 变更后的文本 - size_t change_start; ///< 变更起始位置 - size_t removed_length; ///< 删除的字符数 - size_t inserted_length; ///< 插入的字符数 - }; - - using text_change_callback = std::function; - - // ======================================================================== - // 构造与初始化 - // ======================================================================== - - text_model() = default; - explicit text_model(std::u32string_view initial_text); - explicit text_model(std::string_view initial_text_utf8); - - // ======================================================================== - // 文本内容访问 - // ======================================================================== - - /// @brief 获取文本内容(UTF-32) - [[nodiscard]] const std::u32string& get_text() const noexcept; - - /// @brief 获取文本内容(UTF-8) - [[nodiscard]] std::string get_text_utf8() const; - - /// @brief 设置文本内容(UTF-32) - /// @param text 新文本 - /// @param notify 是否触发变更回调 - void set_text(std::u32string_view text, bool notify = true); - - /// @brief 设置文本内容(UTF-8) - /// @param text 新文本(UTF-8 编码) - /// @param notify 是否触发变更回调 - void set_text_utf8(std::string_view text, bool notify = true); - - /// @brief 获取文本长度(字符数) - [[nodiscard]] size_t length() const noexcept; - - /// @brief 检查文本是否为空 - [[nodiscard]] bool empty() const noexcept; - - /// @brief 获取指定位置的字符 - /// @param index 字符索引 - /// @return 字符,如果索引无效返回 nullopt - [[nodiscard]] std::optional char_at(size_t index) const noexcept; - - // ======================================================================== - // 选择管理 - // ======================================================================== - - /// @brief 获取当前选择 - [[nodiscard]] const text_selection& get_selection() const noexcept; - - /// @brief 设置选择范围 - /// @param selection 新的选择范围 - void set_selection(text_selection selection) noexcept; - - /// @brief 设置光标位置(折叠选择) - /// @param position 光标位置 - void set_cursor_position(size_t position) noexcept; - - /// @brief 获取光标位置(选择焦点) - [[nodiscard]] size_t get_cursor_position() const noexcept; - - /// @brief 检查是否有选择 - [[nodiscard]] bool has_selection() const noexcept; - - /// @brief 获取选中的文本 - [[nodiscard]] std::u32string get_selected_text() const; - - /// @brief 全选 - void select_all() noexcept; - - /// @brief 选择指定位置的单词 - /// @param position 位置 - /// @return 是否成功选择了单词 - bool select_word_at(size_t position); - - /// @brief 清除选择(折叠到焦点位置) - void clear_selection() noexcept; - - // ======================================================================== - // 文本编辑操作 - // ======================================================================== - - /// @brief 在当前位置插入文本 - /// @param text 要插入的文本 - /// @return 插入后的光标位置 - size_t insert(std::u32string_view text); - - /// @brief 在当前位置插入文本(UTF-8) - /// @param text 要插入的文本(UTF-8 编码) - /// @return 插入后的光标位置 - size_t insert_utf8(std::string_view text); - - /// @brief 在当前位置插入单个字符 - /// @param ch 要插入的字符 - /// @return 插入后的光标位置 - size_t insert_char(char32_t ch); - - /// @brief 删除选中的文本 - /// @return 是否删除了文本 - bool delete_selection(); - - /// @brief 向后删除(Backspace) - /// @return 是否删除了文本 - bool delete_backward(); - - /// @brief 向前删除(Delete) - /// @return 是否删除了文本 - bool delete_forward(); - - /// @brief 删除指定范围的文本 - /// @param start 起始位置 - /// @param end 结束位置 - /// @return 是否删除了文本 - bool delete_range(size_t start, size_t end); - - /// @brief 向后删除一个单词 - /// @return 是否删除了文本 - bool delete_word_backward(); - - /// @brief 向前删除一个单词 - /// @return 是否删除了文本 - bool delete_word_forward(); - - // ======================================================================== - // 单词边界查找 - // ======================================================================== - - /// @brief 查找位置左侧的单词边界 - /// @param position 当前位置 - /// @return 单词边界位置 - [[nodiscard]] size_t find_word_start(size_t position) const noexcept; - - /// @brief 查找位置右侧的单词边界 - /// @param position 当前位置 - /// @return 单词边界位置 - [[nodiscard]] size_t find_word_end(size_t position) const noexcept; - - // ======================================================================== - // 约束与限制 - // ======================================================================== - - /// @brief 设置最大长度限制 - /// @param max 最大字符数(0 表示无限制) - void set_max_length(size_t max) noexcept; - - /// @brief 获取最大长度限制 - [[nodiscard]] size_t get_max_length() const noexcept; - - // ======================================================================== - // 回调设置 - // ======================================================================== - - /// @brief 设置文本变更回调 - void on_text_changed(text_change_callback callback); - -private: - std::u32string content_; - text_selection selection_; - size_t max_length_{0}; // 0 表示无限制 - text_change_callback text_changed_callback_; - - /// @brief 限制选择范围在有效范围内 - void clamp_selection() noexcept; - - /// @brief 检查字符是否为单词字符 - [[nodiscard]] static bool is_word_char(char32_t ch) noexcept; - - /// @brief 触发文本变更回调 - void notify_text_changed( - std::u32string_view old_text, - size_t change_start, - size_t removed_length, - size_t inserted_length - ); -}; - -} // namespace mirage -``` - -## 5. cursor_controller 类设计 - -### 5.1 cursor_controller.h - -```cpp -#pragma once - -#include "text_model.h" -#include - -namespace mirage { - -/// @brief 光标控制器 -/// -/// 管理光标的移动逻辑和闪烁动画。 -/// 与 text_model 协作实现光标定位和选择扩展。 -class cursor_controller { -public: - /// @brief 构造光标控制器 - /// @param model 关联的文本模型 - explicit cursor_controller(text_model& model) noexcept; - - // ======================================================================== - // 光标位置 - // ======================================================================== - - /// @brief 获取光标位置 - [[nodiscard]] size_t get_position() const noexcept; - - /// @brief 设置光标位置 - /// @param position 新位置 - /// @param extend_selection 是否扩展选择 - void set_position(size_t position, bool extend_selection = false) noexcept; - - // ======================================================================== - // 光标移动操作 - // ======================================================================== - - /// @brief 向左移动光标 - /// @param extend_selection 是否扩展选择(Shift 键) - void move_left(bool extend_selection = false) noexcept; - - /// @brief 向右移动光标 - /// @param extend_selection 是否扩展选择 - void move_right(bool extend_selection = false) noexcept; - - /// @brief 向左移动一个单词 - /// @param extend_selection 是否扩展选择(Shift 键) - void move_word_left(bool extend_selection = false) noexcept; - - /// @brief 向右移动一个单词 - /// @param extend_selection 是否扩展选择 - void move_word_right(bool extend_selection = false) noexcept; - - /// @brief 移动到行首/文本开头 - /// @param extend_selection 是否扩展选择 - void move_to_start(bool extend_selection = false) noexcept; - - /// @brief 移动到行尾/文本结尾 - /// @param extend_selection 是否扩展选择 - void move_to_end(bool extend_selection = false) noexcept; - - // ======================================================================== - // 闪烁动画 - // ======================================================================== - - /// @brief 更新闪烁状态 - /// @param dt 时间增量(秒) - void update(float dt) noexcept; - - /// @brief 重置闪烁计时器(使光标立即可见) - void reset_blink() noexcept; - - /// @brief 检查光标是否可见 - [[nodiscard]] bool is_visible() const noexcept; - - /// @brief 设置闪烁周期 - /// @param rate 闪烁周期(秒) - void set_blink_rate(float rate) noexcept; - - /// @brief 获取闪烁周期 - [[nodiscard]] float get_blink_rate() const noexcept; - - /// @brief 启用/禁用闪烁 - void set_blink_enabled(bool enabled) noexcept; - - /// @brief 检查闪烁是否启用 - [[nodiscard]] bool is_blink_enabled() const noexcept; - -private: - text_model& model_; - float blink_timer_{0.0f}; - float blink_rate_{0.53f}; - bool visible_{true}; - bool blink_enabled_{true}; -}; - -} // namespace mirage -``` - -## 6. text_input 主控件类设计 - -### 6.1 text_input.h - -```cpp -#pragma once - -#include "leaf_widget_base.h" -#include "text_input/text_model.h" -#include "text_input/cursor_controller.h" -#include "widget_event/event_result.h" -#include "widget_event/ime_types.h" -#include -#include -#include - -namespace mirage { - -/// @brief 文本输入框样式(前向声明) -struct text_input_style; - -/// @brief 文本输入框状态 -enum class text_input_state : uint8_t { - normal, ///< 正常状态 - focused, ///< 获得焦点 - disabled, ///< 禁用状态 - read_only ///< 只读状态 -}; - -/// @brief 单行文本输入框控件 -/// -/// 提供完整的文本输入功能,包括: -/// - 文本编辑:插入、删除、替换 -/// - 光标管理:定位、移动、闪烁动画 -/// - 选择功能:键盘选择、鼠标拖拽、双击选词 -/// - 剪贴板:复制、剪切、粘贴 -/// - IME 支持:预编辑显示、候选位置 -/// - 自动滚动:文本超出视口时 -/// -/// 使用示例: -/// @code -/// auto input = new_widget() -/// ->placeholder("请输入文本...") -/// ->font_size(14.0f) -/// ->on_text_changed([](std::string_view text) { -/// std::cout << "Text: " << text << std::endl; -/// }) -/// ->on_submit([](std::string_view text) { -/// std::cout << "Submit: " << text << std::endl; -/// }); -/// @endcode -class text_input : public leaf_widget_base { -public: - // ======================================================================== - // 构造函数 - // ======================================================================== - - text_input(); - explicit text_input(std::string_view initial_text); - ~text_input() override = default; - - // ======================================================================== - // 属性设置(链式调用) - // ======================================================================== - - /// @brief 设置文本内容 - auto& text(std::string_view value); - - /// @brief 设置占位符文本 - auto& placeholder(std::string_view value); - - /// @brief 设置字体大小 - auto& font_size(float value); - - /// @brief 设置字体 ID - auto& font_id(uint32_t value); - - /// @brief 设置样式 - auto& style(const text_input_style& value); - - /// @brief 设置只读状态 - auto& read_only(bool value); - - /// @brief 设置禁用状态 - auto& disabled(bool value); - - /// @brief 设置最大长度 - auto& max_length(size_t value); - - // ======================================================================== - // 回调设置(链式调用) - // ======================================================================== - - /// @brief 文本变更回调 - /// @param callback 回调函数,参数为新的文本内容(UTF-8) - auto& on_text_changed(std::function callback); - - /// @brief 提交回调(按下 Enter 键时) - /// @param callback 回调函数,参数为当前文本内容(UTF-8) - auto& on_submit(std::function callback); - - /// @brief 焦点变化回调 - /// @param callback 回调函数,参数为是否获得焦点 - auto& on_focus_changed(std::function callback); - - // ======================================================================== - // 属性访问 - // ======================================================================== - - /// @brief 获取文本内容(UTF-8) - [[nodiscard]] std::string get_text() const; - - /// @brief 获取占位符文本 - [[nodiscard]] const std::string& get_placeholder() const noexcept; - - /// @brief 获取当前状态 - [[nodiscard]] text_input_state get_state() const noexcept; - - /// @brief 检查是否有焦点 - [[nodiscard]] bool has_focus() const noexcept; - - /// @brief 检查是否只读 - [[nodiscard]] bool is_read_only() const noexcept; - - /// @brief 检查是否禁用 - [[nodiscard]] bool is_disabled() const noexcept; - - /// @brief 检查是否有选择 - [[nodiscard]] bool has_selection() const noexcept; - - /// @brief 获取选中的文本 - [[nodiscard]] std::string get_selected_text() const; - - // ======================================================================== - // 编辑操作 - // ======================================================================== - - /// @brief 全选 - void select_all(); - - /// @brief 清除选择 - void clear_selection(); - - /// @brief 复制选中文本到剪贴板 - void copy(); - - /// @brief 剪切选中文本到剪贴板 - void cut(); - - /// @brief 从剪贴板粘贴 - void paste(); - - /// @brief 清空文本 - void clear(); - - /// @brief 设置焦点 - void focus(); - - /// @brief 移除焦点 - void blur(); - - // ======================================================================== - // widget_base 接口实现 - // ======================================================================== - - [[nodiscard]] auto do_measure(const vec2f_t& available_size) const - -> vec2f_t override; - - uint32_t build_render_command(render_collector& collector, uint32_t z_order) - const override; - - [[nodiscard]] bool is_focusable() const override { return !is_disabled(); } - [[nodiscard]] bool is_event_enabled() const override { return !is_disabled(); } - - void on_focus_gained() override; - void on_focus_lost() override; - - [[nodiscard]] event_result on_key_down(const keyboard_event& event) override; - [[nodiscard]] event_result on_key_up(const keyboard_event& event) override; - [[nodiscard]] event_result on_char_input(const char_event& event) override; - - [[nodiscard]] event_result on_mouse_down(const mouse_event& event) override; - [[nodiscard]] event_result on_mouse_up(const mouse_event& event) override; - [[nodiscard]] event_result on_mouse_move(const mouse_event& event) override; - [[nodiscard]] event_result on_mouse_double_click(const mouse_event& event) override; - - void tick(float dt) override; - - [[nodiscard]] auto get_intrinsic_size() const - -> std::optional override; - -protected: - // ======================================================================== - // IME 事件处理 - // ======================================================================== - - void handle_ime_preedit_start(); - void handle_ime_preedit_update(const ime_preedit_info& preedit); - void handle_ime_preedit_end(); - void handle_ime_commit(std::string_view text); - -private: - // ======================================================================== - // 内部辅助方法 - // ======================================================================== - - [[nodiscard]] size_t hit_test_position(float global_x, float global_y) const; - [[nodiscard]] float calculate_char_x(size_t char_index) const; - [[nodiscard]] vec2f_t calculate_cursor_screen_position() const; - void ensure_cursor_visible(); - void update_ime_candidate_position(); - [[nodiscard]] bool handle_shortcut(const keyboard_event& event); - void mark_render_dirty(); - - // ======================================================================== - // 成员变量 - // ======================================================================== - - text_model model_; - cursor_controller cursor_{model_}; - text_input_style style_; - - std::string placeholder_; - ime_preedit_info preedit_; - - float scroll_offset_{0.0f}; - text_input_state state_{text_input_state::normal}; - bool is_focused_{false}; - bool is_dragging_{false}; - - std::function text_changed_callback_; - std::function submit_callback_; - std::function focus_changed_callback_; - - mutable float cached_text_width_{0.0f}; - mutable bool text_width_dirty_{true}; -}; - -} // namespace mirage -``` - -## 7. 事件处理流程 - -### 7.1 键盘事件处理流程 - -``` -on_key_down - │ - ├─► 检查禁用状态 ─► 禁用 ─► 返回 unhandled - │ - └─► IME 预编辑中? - │ - ├─► 是 ─► Escape? ─► 取消预编辑 - │ └─► 其他 ─► 交给 IME 处理 - │ - └─► 否 ─► 快捷键检查 - │ - ├─► Ctrl+A ─► 全选 - ├─► Ctrl+C ─► 复制 - ├─► Ctrl+X ─► 剪切 - ├─► Ctrl+V ─► 粘贴 - ├─► Ctrl+Z ─► 撤销(未来功能) - ├─► Ctrl+Y ─► 重做(未来功能) - │ - └─► 导航键检查 - │ - ├─► Left ─► move_left(shift) - ├─► Right ─► move_right(shift) - ├─► Home ─► move_to_start(shift) - ├─► End ─► move_to_end(shift) - ├─► Ctrl+Left ─► move_word_left(shift) - ├─► Ctrl+Right ─► move_word_right(shift) - │ - └─► 编辑键 - │ - ├─► Backspace ─► delete_backward - ├─► Delete ─► delete_forward - ├─► Ctrl+Backspace ─► delete_word_backward - ├─► Ctrl+Delete ─► delete_word_forward - └─► Enter ─► 触发 submit 回调 -``` - -### 7.2 字符输入处理流程 - -``` -on_char_input - │ - ├─► 检查状态 - │ ├─► 禁用或只读 ─► 返回 unhandled - │ │ - │ └─► 正常 - │ - ├─► IME 预编辑中? - │ └─► 是 ─► 交给 IME 处理 ─► 返回 handled - │ - ├─► 控制字符? - │ └─► codepoint < 0x20 ─► 返回 unhandled - │ - └─► 可打印字符 - ├─► delete_selection - ├─► insert_char(codepoint) - ├─► reset_blink - ├─► ensure_cursor_visible - ├─► 触发 text_changed - ├─► mark_render_dirty - └─► 返回 handled -``` - -### 7.3 鼠标事件处理流程 - -``` -on_mouse_down - │ - ├─► 非左键? ─► 返回 unhandled - │ - └─► 左键 - ├─► hit_test_position 获取字符位置 - │ - ├─► Shift 按下? - │ ├─► 是 ─► 扩展选择到点击位置 - │ └─► 否 ─► 设置光标到点击位置 - │ - ├─► 开始拖拽 - ├─► reset_blink - └─► 返回 capture - -on_mouse_move - │ - ├─► 非拖拽状态? ─► 返回 unhandled - │ - └─► 正在拖拽 - ├─► hit_test_position - ├─► 扩展选择到当前位置 - ├─► ensure_cursor_visible - └─► 返回 handled - -on_mouse_up - │ - ├─► 非拖拽状态? ─► 返回 unhandled - │ - └─► 正在拖拽 - ├─► 结束拖拽 - └─► 返回 release - -on_mouse_double_click - │ - ├─► hit_test_position - ├─► select_word_at(position) - └─► 返回 handled -``` - -### 7.4 IME 事件处理流程 - -``` -IME 事件 - │ - ├─► preedit_start - │ ├─► 清除当前选择 - │ └─► 记录预编辑起始位置 - │ - ├─► preedit_update - │ ├─► 更新 preedit_ 状态 - │ ├─► update_ime_candidate_position - │ └─► mark_render_dirty - │ - ├─► preedit_end - │ ├─► 清除 preedit_ 状态 - │ └─► mark_render_dirty - │ - └─► commit - ├─► 清除预编辑状态 - ├─► insert_utf8(committed_text) - ├─► 触发 text_changed - └─► mark_render_dirty -``` - -## 8. 渲染流程 - -### 8.1 渲染层次 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 背景层 z_order │ -│ - 背景矩形(根据状态选择颜色) │ -│ - 边框(如果启用) │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 选择高亮层 z_order + 1 │ -│ - 选择范围的背景矩形 │ -│ - 预编辑范围的背景矩形(如果有) │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 文本层 z_order + 2 │ -│ - 主文本(或占位符文本) │ -│ - 应用滚动偏移和裁剪 │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 预编辑层 z_order + 3 │ -│ - 预编辑文本(如果有) │ -│ - 预编辑下划线 │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 光标层 z_order + 4 │ -│ - 光标矩形(如果可见且有焦点) │ -└─────────────────────────────────────────────────────────────┘ -``` - -### 8.2 渲染实现伪代码 - -```cpp -uint32_t text_input::build_render_command( - render_collector& collector, - uint32_t z_order -) const { - if (!should_render()) return z_order; - - auto state_opt = get_layout_state(); - if (!state_opt) return z_order; - - const auto& layout = *state_opt; - vec2f_t pos = layout.global_pos(); - vec2f_t size = layout.size(); - - // 计算内容区域 - vec2f_t content_pos = pos + vec2f_t{ - style_.padding.left, - style_.padding.top - }; - vec2f_t content_size = size - vec2f_t{ - style_.padding.left + style_.padding.right, - style_.padding.top + style_.padding.bottom - }; - - // 1. 背景层 - color bg_color = get_background_color_for_state(); - collector.submit_rect(pos, size, bg_color, - style_.corner_radius, z_order++); - - // 边框 - if (style_.border_width > 0) { - color border_color = is_focused_ - ? style_.border_focused - : style_.border; - collector.submit_border(pos, size, border_color, - style_.border_width, - style_.corner_radius, z_order++); - } - - // 设置裁剪区域 - collector.push_clip(content_pos, content_size); - - // 2. 选择高亮层 - if (has_selection()) { - auto sel = model_.get_selection(); - float sel_start_x = calculate_char_x(sel.start()) - scroll_offset_; - float sel_end_x = calculate_char_x(sel.end()) - scroll_offset_; - - vec2f_t sel_pos{content_pos.x() + sel_start_x, content_pos.y()}; - vec2f_t sel_size{sel_end_x - sel_start_x, content_size.y()}; - - collector.submit_rect(sel_pos, sel_size, - style_.selection, 0, z_order++); - } - - // 3. 文本层 - vec2f_t text_pos = content_pos - vec2f_t{scroll_offset_, 0}; - - if (model_.empty() && !preedit_.active) { - // 显示占位符 - if (!placeholder_.empty()) { - collector.submit_text(text_pos, placeholder_, - style_.placeholder, style_.font_size, - style_.font_id, z_order++); - } - } else { - // 显示实际文本 - std::string text_utf8 = model_.get_text_utf8(); - collector.submit_text(text_pos, text_utf8, - get_text_color_for_state(), style_.font_size, - style_.font_id, z_order++); - } - - // 4. 预编辑层 - if (preedit_.active && !preedit_.text.empty()) { - float preedit_x = calculate_char_x(model_.get_cursor_position()) - - scroll_offset_; - vec2f_t preedit_pos{content_pos.x() + preedit_x, content_pos.y()}; - - // 预编辑背景 - auto preedit_metrics = measure_text(preedit_.text); - collector.submit_rect(preedit_pos, - {preedit_metrics.width, content_size.y()}, - style_.preedit_background, 0, z_order++); - - // 预编辑文本 - collector.submit_text(preedit_pos, preedit_.text, - style_.text, style_.font_size, - style_.font_id, z_order++); - - // 预编辑下划线 - vec2f_t underline_pos{ - preedit_pos.x(), - preedit_pos.y() + content_size.y() - style_.preedit_underline_width - }; - collector.submit_rect(underline_pos, - {preedit_metrics.width, - style_.preedit_underline_width}, - style_.preedit_underline, 0, z_order++); - } - - // 5. 光标层 - if (is_focused_ && cursor_.is_visible() && !is_read_only()) { - float cursor_x = calculate_char_x(model_.get_cursor_position()) - - scroll_offset_; - - if (preedit_.active && !preedit_.text.empty()) { - cursor_x += measure_text(preedit_.text).width; - } - - vec2f_t cursor_pos{content_pos.x() + cursor_x, content_pos.y()}; - vec2f_t cursor_size{style_.cursor_width, content_size.y()}; - - collector.submit_rect(cursor_pos, cursor_size, - style_.cursor, 0, z_order++); - } - - collector.pop_clip(); - - return z_order; -} -``` - -## 9. 状态管理 - -### 9.1 状态转换图 - -``` - ┌──────────────────┐ - │ │ - ┌────────► normal ◄────────┐ - │ │ │ │ - │ └────────┬─────────┘ │ - │ │ │ - disabled(false) on_focus_gained read_only(false) - │ │ │ - │ ▼ │ - │ ┌──────────────────┐ │ - │ │ │ │ - └────────┤ focused ├────────┘ - │ │ - └────────┬─────────┘ - │ - on_focus_lost - │ - ▼ - ┌──────────────────┐ - │ normal │ - └──────────────────┘ - - ┌──────────────────────────────────────────┐ - │ │ - │ disabled(true) from any state │ - │ │ │ - │ ▼ │ - │ ┌──────────────────┐ │ - │ │ disabled │ │ - │ └──────────────────┘ │ - │ │ - └──────────────────────────────────────────┘ -``` - -### 9.2 状态对行为的影响 - -| 状态 | 可聚焦 | 接收键盘 | 接收鼠标 | 可编辑 | 光标可见 | -|------|--------|----------|----------|--------|----------| -| normal | Yes | Yes | Yes | Yes | No | -| focused | Yes | Yes | Yes | Yes | Yes | -| disabled | No | No | No | No | No | -| read_only | Yes | Yes | Yes | No | Yes | - -### 9.3 颜色选择逻辑 - -```cpp -color text_input::get_background_color_for_state() const { - switch (state_) { - case text_input_state::disabled: - return style_.background_disabled; - case text_input_state::read_only: - return style_.background_readonly; - default: - return style_.background; - } -} - -color text_input::get_text_color_for_state() const { - if (state_ == text_input_state::disabled) { - return style_.text_disabled; - } - return style_.text; -} -``` - -## 10. 与框架集成 - -### 10.1 创建和使用示例 - -```cpp -#include "widget/widgets/text_input/text_input.h" -#include "widget/modifier_helper.h" - -// 基本使用 -auto username_input = new_widget() - ->placeholder("请输入用户名") - ->max_length(32) - ->on_text_changed([](std::string_view text) { - // 处理文本变化 - }); - -// 密码输入(未来可扩展支持掩码) -auto password_input = new_widget() - ->placeholder("请输入密码") - ->on_submit([](std::string_view text) { - // 处理登录 - }); - -// 只读输入框 -auto readonly_input = new_widget() - ->text("这是只读文本") - ->read_only(true); - -// 自定义样式 -auto styled_input = new_widget() - ->style(text_input_style::rounded()) - ->font_size(16.0f); - -// 在布局中使用 -auto form = new_widget()[ - new_widget("用户名:"), - std::move(username_input) | fixed_size(200, 32), - new_widget("密码:"), - std::move(password_input) | fixed_size(200, 32) -]; -``` - -### 10.2 焦点管理集成 - -```cpp -void text_input::on_focus_gained() { - is_focused_ = true; - state_ = text_input_state::focused; - cursor_.reset_blink(); - cursor_.set_blink_enabled(true); - - // 启用 IME - if (auto* ctx = get_context()) { - auto& ime = ctx->get_ime_manager(); - ime.enable(); - ime.set_focused_target(this); - update_ime_candidate_position(); - } - - // 触发回调 - if (focus_changed_callback_) { - focus_changed_callback_(true); - } - - mark_render_dirty(); -} - -void text_input::on_focus_lost() { - is_focused_ = false; - state_ = is_read_only() - ? text_input_state::read_only - : text_input_state::normal; - cursor_.set_blink_enabled(false); - - // 禁用 IME - if (auto* ctx = get_context()) { - auto& ime = ctx->get_ime_manager(); - ime.disable(); - ime.set_focused_target(nullptr); - } - - // 清除预编辑状态 - preedit_.clear(); - - // 触发回调 - if (focus_changed_callback_) { - focus_changed_callback_(false); - } - - mark_render_dirty(); -} -``` - -### 10.3 IME 集成 - -```cpp -void text_input::update_ime_candidate_position() { - if (!is_focused_) return; - - auto cursor_pos = calculate_cursor_screen_position(); - - if (auto* ctx = get_context()) { - auto& ime = ctx->get_ime_manager(); - ime.set_candidate_position( - static_cast(cursor_pos.x()), - static_cast(cursor_pos.y()), - static_cast(style_.font_size * 1.2f) - ); - } -} -``` - -### 10.4 剪贴板集成 - -```cpp -void text_input::copy() { - if (!has_selection()) return; - - std::string selected = get_selected_text(); - if (auto* ctx = get_context()) { - ctx->get_clipboard().set_text(selected); - } -} - -void text_input::cut() { - if (!has_selection() || is_read_only()) return; - - copy(); - model_.delete_selection(); - - if (text_changed_callback_) { - text_changed_callback_(model_.get_text_utf8()); - } - - mark_render_dirty(); -} - -void text_input::paste() { - if (is_read_only()) return; - - if (auto* ctx = get_context()) { - std::string clipboard_text = ctx->get_clipboard().get_text(); - if (!clipboard_text.empty()) { - model_.delete_selection(); - model_.insert_utf8(clipboard_text); - - if (text_changed_callback_) { - text_changed_callback_(model_.get_text_utf8()); - } - - ensure_cursor_visible(); - mark_render_dirty(); - } - } -} -``` - -## 11. 滚动和视口管理 - -### 11.1 滚动偏移计算 - -```cpp -void text_input::ensure_cursor_visible() { - auto state_opt = get_layout_state(); - if (!state_opt) return; - - const auto& layout = *state_opt; - float content_width = layout.size().x() - - style_.padding.left - - style_.padding.right; - - // 计算光标位置(考虑预编辑文本) - float cursor_x = calculate_char_x(model_.get_cursor_position()); - if (preedit_.active && !preedit_.text.empty()) { - cursor_x += measure_text(preedit_.text).width; - } - - // 光标相对于视口的位置 - float cursor_in_viewport = cursor_x - scroll_offset_; - - // 保持一定的边距 - constexpr float margin = 8.0f; - - // 光标超出右边界 - if (cursor_in_viewport > content_width - margin) { - scroll_offset_ = cursor_x - content_width + margin; - } - // 光标超出左边界 - else if (cursor_in_viewport < margin) { - scroll_offset_ = cursor_x - margin; - } - - // 限制滚动范围 - scroll_offset_ = std::max(0.0f, scroll_offset_); -} - -float text_input::calculate_char_x(size_t char_index) const { - if (char_index == 0) return 0.0f; - - // 获取光标前的文本 - const auto& text = model_.get_text(); - std::u32string prefix( - text.begin(), - text.begin() + std::min(char_index, text.length()) - ); - - // 测量文本宽度 - if (auto* ctx = get_context()) { - auto metrics = ctx->get_text_shaper().measure_text( - prefix, style_.font_id, style_.font_size - ); - return metrics.width; - } - - // 回退估算 - return static_cast(char_index) * style_.font_size * 0.6f; -} -``` - -## 12. 未来扩展 - -### 12.1 撤销/重做(未来功能) - -```cpp -/// @brief 编辑操作记录(用于撤销/重做) -struct edit_operation { - enum class type { insert, delete_op, replace }; - - type op_type; - size_t position; - std::u32string old_text; - std::u32string new_text; - text_selection old_selection; - text_selection new_selection; -}; - -/// @brief 撤销/重做管理器 -class undo_manager { -public: - void push(edit_operation op); - bool can_undo() const; - bool can_redo() const; - edit_operation undo(); - edit_operation redo(); - void clear(); - -private: - std::vector undo_stack_; - std::vector redo_stack_; - size_t max_history_{100}; -}; -``` - -### 12.2 多行文本输入(未来扩展) - -多行文本输入框可以基于当前的单行设计进行扩展: - -- 添加行高管理 -- 支持垂直滚动 -- 处理换行和自动换行 -- 扩展光标的上下移动 - -### 12.3 密码输入模式(未来扩展) - -```cpp -/// @brief 密码输入模式 -enum class password_mode : uint8_t { - none, ///< 普通文本 - mask_all, ///< 全部用掩码字符显示 - mask_delayed ///< 延迟掩码(显示最后输入的字符) -}; - -// 在 text_input 中添加: -auto& password(password_mode mode, char32_t mask_char = U'●'); -``` - -## 13. 总结 - -本设计文档提供了文本输入框控件的完整架构: - -1. **模块化设计**:将文本数据、光标控制和控件逻辑分离 -2. **完整的事件处理**:支持键盘、鼠标和 IME 输入 -3. **灵活的样式系统**:可自定义外观的所有方面 -4. **状态管理**:清晰的状态转换和行为控制 -5. **与框架集成**:遵循 mirage 框架的设计模式 - -实现时应按以下顺序进行: - -1. `text_selection.h` - 基础数据结构 -2. `text_model.h/.cpp` - 文本数据模型 -3. `cursor_controller.h/.cpp` - 光标控制器 -4. `text_input.h/.cpp` - 主控件类 \ No newline at end of file diff --git a/docs/THREAD_SYNC_FRAMEWORK.md b/docs/THREAD_SYNC_FRAMEWORK.md deleted file mode 100644 index b31d763..0000000 --- a/docs/THREAD_SYNC_FRAMEWORK.md +++ /dev/null @@ -1,2665 +0,0 @@ -# 线程同步框架设计文档 - -## 目录 -1. [概述](#概述) -2. [设计目标](#设计目标) -3. [整体架构](#整体架构) -4. [核心原语层](#核心原语层) -5. [属性系统](#属性系统) -6. [异步操作](#异步操作) -7. [状态同步](#状态同步) -8. [与现有系统集成](#与现有系统集成) -9. [使用示例](#使用示例) -10. [文件结构](#文件结构) - ---- - -## 概述 - -本框架旨在为 Mirage 项目提供一个现代化的线程同步解决方案,充分利用 C++23 特性实现: -- **无感同步**:用户代码不需要显式处理同步逻辑 -- **类型安全**:编译时检测线程安全问题 -- **零开销抽象**:不引入运行时性能损失 - -### 现有痛点分析 - -基于对现有代码的分析,识别出以下痛点: - -| 痛点 | 现有代码示例 | 解决方案 | -|------|-------------|---------| -| 显式同步过多 | `frame_sync::wait_for_frame_slot()` | `thread_bound` 编译时检查 | -| 回调复杂 | `std::function` 回调 | `future_chain` 链式调用 | -| 状态同步手动管理 | `publish_secondary_states()` | `sync_state` 自动同步 | -| 脏标记繁琐 | `set_dirty()` / `clear_dirty()` | `property` 自动追踪 | - ---- - -## 设计目标 - -### 1. 无感同步 (Transparent Synchronization) -``` -用户视角: - auto& widget = get_widget(); - widget.position = vec2f_t{100, 200}; // 自动标记脏 - widget.size = computed_size(); // 自动依赖追踪 - // 无需手动调用 set_dirty() 或 sync() -``` - -### 2. 类型安全 (Type Safety) -``` -编译时检查: - thread_bound data; - - // 在布局线程中: - data.get(); // 编译错误! 线程标签不匹配 - - // 在渲染线程中: - data.get(); // OK -``` - -### 3. 零开销抽象 (Zero-Cost Abstraction) -``` -框架代码: - property opacity{1.0f}; - opacity = 0.5f; - -编译后等价于: - float opacity_ = 1.0f; - dirty_flags_ |= OPACITY_DIRTY; - opacity_ = 0.5f; -``` - ---- - -## 整体架构 - -``` -+-----------------------------------------------------------------------------+ -| Thread Sync Framework | -+-----------------------------------------------------------------------------+ -| | -| +---------------------------------------------------------------------+ | -| | 应用层 Application Layer | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| | | widget_base | | render_pipeline | | thread_coordinator | | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| +---------------------------------------------------------------------+ | -| | | -| +---------------------------------------------------------------------+ | -| | 状态同步层 State Sync Layer | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| | | sync_state | | pub/sub 机制 | | version_manager | | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| +---------------------------------------------------------------------+ | -| | | -| +---------------------------------------------------------------------+ | -| | 属性系统层 Property System Layer | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| | | property | | computed_prop | | property_observer | | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| +---------------------------------------------------------------------+ | -| | | -| +---------------------------------------------------------------------+ | -| | 异步操作层 Async Operations Layer | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| | | async_result | | future_chain | | thread_dispatcher | | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| +---------------------------------------------------------------------+ | -| | | -| +---------------------------------------------------------------------+ | -| | 核心原语层 Core Primitives Layer | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| | | thread_bound | | thread_context | | sync_primitive | | | -| | +-----------------+ +-----------------+ +---------------------+ | | -| +---------------------------------------------------------------------+ | -| | -+-----------------------------------------------------------------------------+ -``` - -### 数据流向图 - -``` - 主线程 Main 布局线程 Layout 渲染线程 Render - | | | - | property 修改 | | - v | | - +-----------+ | | - | 自动标脏 | | | - +-----+-----+ | | - | | | - | sync_state 发布 | | - v v | - +-----------+ +-----------+ | - | 写缓冲区 |--swap-->| 读缓冲区 | | - +-----------+ +-----+-----+ | - | | - | 布局计算完成 | - v v - +-----------+ +-----------+ - | 渲染树生成 |---------->| GPU提交 | - +-----------+ +-----------+ -``` - ---- - -## 核心原语层 - -### 4.1 线程标签系统 Thread Tags - -使用空结构体作为线程标签,实现编译时线程身份检查。 - -```cpp -// file: src/common/threading/thread_tags.h - -namespace mirage::threading { - -/// @brief 主线程标签 -struct main_thread_tag { - static constexpr const char* name = "main"; -}; - -/// @brief 布局线程标签 -struct layout_thread_tag { - static constexpr const char* name = "layout"; -}; - -/// @brief 渲染线程标签 -struct render_thread_tag { - static constexpr const char* name = "render"; -}; - -/// @brief 任意线程标签(无限制) -struct any_thread_tag { - static constexpr const char* name = "any"; -}; - -/// @brief 线程标签 concept -template -concept thread_tag = requires { - { T::name } -> std::convertible_to; -}; - -} // namespace mirage::threading -``` - -### 4.2 线程上下文 Thread Context - -管理当前线程的身份信息,提供运行时验证(Debug模式)和编译时检查支持。 - -```cpp -// file: src/common/threading/thread_context.h - -namespace mirage::threading { - -/// @brief 线程上下文类 -class thread_context { -public: - /// @brief 注册当前线程 - template - static void register_thread() noexcept { - current_tag_name() = Tag::name; - current_tag_hash() = typeid(Tag).hash_code(); - } - - /// @brief 检查当前线程是否匹配指定标签 - template - [[nodiscard]] static bool is_current() noexcept { - if constexpr (std::is_same_v) { - return true; - } - return current_tag_hash() == typeid(Tag).hash_code(); - } - - /// @brief 断言当前线程匹配指定标签 - template - static void assert_thread() noexcept { - #ifndef NDEBUG - if constexpr (!std::is_same_v) { - [[assume(is_current())]]; - if (!is_current()) { - std::terminate(); - } - } - #endif - } - - /// @brief 获取当前线程标签名称 - [[nodiscard]] static const char* current_name() noexcept { - return current_tag_name(); - } - -private: - [[nodiscard]] static const char*& current_tag_name() noexcept { - thread_local const char* name = "unregistered"; - return name; - } - - [[nodiscard]] static std::size_t& current_tag_hash() noexcept { - thread_local std::size_t hash = 0; - return hash; - } -}; - -/// @brief RAII 线程注册守卫 -template -class thread_registration_guard { -public: - thread_registration_guard() noexcept { - thread_context::register_thread(); - } - ~thread_registration_guard() = default; - - thread_registration_guard(const thread_registration_guard&) = delete; - thread_registration_guard& operator=(const thread_registration_guard&) = delete; -}; - -} // namespace mirage::threading - -### 4.3 线程绑定类型 Thread Bound - -核心类型,将数据与特定线程绑定,提供编译时访问检查。 - -```cpp -// file: src/common/threading/thread_bound.h - -namespace mirage::threading { - -/// @brief 线程绑定错误类型 -enum class thread_bound_error { - wrong_thread, ///< 在错误的线程访问 - not_initialized, ///< 未初始化 - already_locked ///< 已被锁定 -}; - -/// @brief 线程绑定类型 -/// @tparam T 数据类型 -/// @tparam ThreadTag 线程标签 -template -class thread_bound { -public: - using value_type = T; - using tag_type = ThreadTag; - - thread_bound() = default; - explicit thread_bound(T value) : value_(std::move(value)) {} - - /// @brief 就地构造 - template - requires std::constructible_from - explicit thread_bound(std::in_place_t, Args&&... args) - : value_(std::forward(args)...) {} - - // ======================================================================== - // 访问器 - 使用 deducing this C++23 - // ======================================================================== - - /// @brief 获取值引用 - template - [[nodiscard]] auto&& get(this Self&& self) noexcept { - thread_context::assert_thread(); - return std::forward(self).value_; - } - - /// @brief 尝试获取值(不断言,返回 expected) - template - [[nodiscard]] auto try_get(this Self&& self) noexcept - -> std::expected>, - const T, T>>, - thread_bound_error> - { - if (!thread_context::is_current()) { - return std::unexpected(thread_bound_error::wrong_thread); - } - return std::ref(std::forward(self).value_); - } - - /// @brief 在正确线程上执行操作 - template - requires std::invocable - [[nodiscard]] auto with(Func&& func) noexcept - -> std::expected, thread_bound_error> - { - if (!thread_context::is_current()) { - return std::unexpected(thread_bound_error::wrong_thread); - } - return std::invoke(std::forward(func), value_); - } - - /// @brief 箭头操作符 - [[nodiscard]] T* operator->() noexcept { - thread_context::assert_thread(); - return &value_; - } - - [[nodiscard]] const T* operator->() const noexcept { - thread_context::assert_thread(); - return &value_; - } - - /// @brief 获取只读快照(任意线程可调用) - [[nodiscard]] T snapshot() const noexcept - requires std::copy_constructible - { - return value_; - } - -private: - T value_{}; -}; - -/// @brief 类型别名 -template -using main_thread_bound = thread_bound; - -template -using layout_thread_bound = thread_bound; - -template -using render_thread_bound = thread_bound; - -} // namespace mirage::threading -``` - -### 4.4 同步原语封装 Sync Primitives - -```cpp -// file: src/common/threading/sync_primitives.h - -namespace mirage::threading { - -/// @brief 线程安全的值容器 -template - requires std::is_trivially_copyable_v -class atomic_value { -public: - atomic_value() = default; - explicit atomic_value(T value) noexcept : value_(value) {} - - [[nodiscard]] T load(std::memory_order order = std::memory_order_seq_cst) const noexcept { - return value_.load(order); - } - - void store(T value, std::memory_order order = std::memory_order_seq_cst) noexcept { - value_.store(value, order); - } - - [[nodiscard]] T exchange(T value, std::memory_order order = std::memory_order_seq_cst) noexcept { - return value_.exchange(value, order); - } - - bool compare_exchange_strong(T& expected, T desired, - std::memory_order order = std::memory_order_seq_cst) noexcept { - return value_.compare_exchange_strong(expected, desired, order); - } - -private: - std::atomic value_{}; -}; - -/// @brief 自旋锁 - 适用于短临界区 -class spinlock { -public: - void lock() noexcept { - while (flag_.test_and_set(std::memory_order_acquire)) { - #if defined(__x86_64__) || defined(_M_X64) - __builtin_ia32_pause(); - #endif - } - } - - [[nodiscard]] bool try_lock() noexcept { - return !flag_.test_and_set(std::memory_order_acquire); - } - - void unlock() noexcept { - flag_.clear(std::memory_order_release); - } - -private: - std::atomic_flag flag_ = ATOMIC_FLAG_INIT; -}; - -/// @brief 读写自旋锁 - 支持多读单写 -class rw_spinlock { -public: - void lock_shared() noexcept { - while (true) { - uint32_t expected = state_.load(std::memory_order_relaxed); - if ((expected & WRITER_BIT) == 0) { - if (state_.compare_exchange_weak(expected, expected + 1, - std::memory_order_acquire, std::memory_order_relaxed)) { - return; - } - } - } - } - - void unlock_shared() noexcept { - state_.fetch_sub(1, std::memory_order_release); - } - - void lock() noexcept { - while (true) { - uint32_t expected = 0; - if (state_.compare_exchange_weak(expected, WRITER_BIT, - std::memory_order_acquire, std::memory_order_relaxed)) { - return; - } - } - } - - void unlock() noexcept { - state_.store(0, std::memory_order_release); - } - -private: - static constexpr uint32_t WRITER_BIT = 0x80000000; - std::atomic state_{0}; -}; - -} // namespace mirage::threading -``` - ---- - -## 属性系统 - -### 5.1 属性基类与概念 Property Concepts - -```cpp -// file: src/common/threading/property_base.h - -namespace mirage::threading { - -/// @brief 脏标记类型 -enum class dirty_flag : uint8_t { - clean = 0, - value_changed = 1, - needs_layout = 2, - needs_render = 3 -}; - -/// @brief 脏标记集合 -class dirty_flags { -public: - void set(dirty_flag flag) noexcept { - flags_ |= (1u << static_cast(flag)); - } - - void clear(dirty_flag flag) noexcept { - flags_ &= ~(1u << static_cast(flag)); - } - - void clear_all() noexcept { flags_ = 0; } - - [[nodiscard]] bool test(dirty_flag flag) const noexcept { - return (flags_ & (1u << static_cast(flag))) != 0; - } - - [[nodiscard]] bool any() const noexcept { return flags_ != 0; } - -private: - uint8_t flags_ = 0; -}; - -/// @brief 属性 concept -template -concept property_like = requires(T t) { - typename T::value_type; - { t.get() } -> std::convertible_to; - { t.is_dirty() } -> std::convertible_to; -}; - -} // namespace mirage::threading -``` - -### 5.2 自动脏标记属性 Property - -```cpp -// file: src/common/threading/property.h - -namespace mirage::threading { - -/// @brief 属性变化通知器接口 -class property_notifier { -public: - virtual ~property_notifier() = default; - virtual void on_property_changed(uint32_t property_id, dirty_flag flag) = 0; -}; - -/// @brief 自动脏标记属性 -/// @tparam T 值类型 -/// @tparam DefaultFlag 默认脏标记类型 -template -class property { -public: - using value_type = T; - static constexpr dirty_flag default_flag = DefaultFlag; - - property() = default; - explicit property(T value) noexcept(std::is_nothrow_move_constructible_v) - : value_(std::move(value)) {} - - /// @brief 绑定通知器 - void bind_notifier(property_notifier* notifier, uint32_t property_id) noexcept { - notifier_ = notifier; - property_id_ = property_id; - } - - // ======================================================================== - // 访问器 - // ======================================================================== - - /// @brief 获取只读引用 - [[nodiscard]] const T& get() const noexcept { return value_; } - - /// @brief 获取可修改引用(标记脏) - [[nodiscard]] T& get_mutable() noexcept { - mark_dirty(); - return value_; - } - - /// @brief 隐式转换 - [[nodiscard]] operator const T&() const noexcept { return value_; } - - // ======================================================================== - // 修改器 - // ======================================================================== - - /// @brief 赋值操作符 - property& operator=(const T& value) { - if (value_ != value) { - value_ = value; - mark_dirty(); - } - return *this; - } - - property& operator=(T&& value) { - if (value_ != value) { - value_ = std::move(value); - mark_dirty(); - } - return *this; - } - - /// @brief 静默设置(不标记脏) - void set_silent(T value) noexcept(std::is_nothrow_move_assignable_v) { - value_ = std::move(value); - } - - // ======================================================================== - // 脏标记管理 - // ======================================================================== - - [[nodiscard]] bool is_dirty() const noexcept { return dirty_; } - - void clear_dirty() noexcept { dirty_ = false; } - - void mark_dirty() noexcept { - dirty_ = true; - if (notifier_) { - notifier_->on_property_changed(property_id_, DefaultFlag); - } - } - -private: - T value_{}; - bool dirty_ = false; - property_notifier* notifier_ = nullptr; - uint32_t property_id_ = 0; -}; - -/// @brief 布局属性(修改时标记 needs_layout) -template -using layout_property = property; - -/// @brief 渲染属性(修改时标记 needs_render) -template -using render_property = property; - -} // namespace mirage::threading -``` - -### 5.3 计算属性 Computed Property - -```cpp -// file: src/common/threading/computed_property.h - -namespace mirage::threading { - -/// @brief 依赖追踪上下文 -class dependency_tracker { -public: - static dependency_tracker& current() { - thread_local dependency_tracker instance; - return instance; - } - - void begin_tracking() noexcept { - dependencies_.clear(); - tracking_ = true; - } - - void end_tracking() noexcept { - tracking_ = false; - } - - void record_dependency(void* source) { - if (tracking_) { - dependencies_.push_back(source); - } - } - - [[nodiscard]] const std::vector& get_dependencies() const noexcept { - return dependencies_; - } - - [[nodiscard]] bool is_tracking() const noexcept { return tracking_; } - -private: - std::vector dependencies_; - bool tracking_ = false; -}; - -/// @brief 计算属性 -/// @tparam T 结果类型 -/// @tparam ComputeFunc 计算函数类型 -template - requires std::invocable && - std::convertible_to, T> -class computed_property { -public: - using value_type = T; - - explicit computed_property(ComputeFunc func) - : compute_func_(std::move(func)) {} - - /// @brief 获取值(自动依赖追踪和缓存) - [[nodiscard]] const T& get() const { - // 记录自己被访问(用于嵌套计算属性) - if (dependency_tracker::current().is_tracking()) { - dependency_tracker::current().record_dependency( - const_cast(this)); - } - - if (!cached_) { - recompute(); - } - return cached_value_; - } - - /// @brief 隐式转换 - [[nodiscard]] operator const T&() const { return get(); } - - /// @brief 使缓存失效 - void invalidate() noexcept { - cached_ = false; - } - - /// @brief 检查是否已缓存 - [[nodiscard]] bool is_cached() const noexcept { return cached_; } - -private: - void recompute() const { - // 开始依赖追踪 - dependency_tracker::current().begin_tracking(); - - // 执行计算 - cached_value_ = std::invoke(compute_func_); - cached_ = true; - - // 结束依赖追踪并记录依赖 - dependency_tracker::current().end_tracking(); - dependencies_ = dependency_tracker::current().get_dependencies(); - } - - ComputeFunc compute_func_; - mutable T cached_value_{}; - mutable bool cached_ = false; - mutable std::vector dependencies_; -}; - -/// @brief 创建计算属性的辅助函数 -template -auto make_computed(Func&& func) { - using result_t = std::invoke_result_t; - return computed_property>(std::forward(func)); -} - -} // namespace mirage::threading -``` - -### 5.4 属性观察器 Property Observer - -```cpp -// file: src/common/threading/property_observer.h - -namespace mirage::threading { - -/// @brief 属性变化回调类型 -using property_change_callback = std::move_only_function; - -/// @brief 属性观察器 -class property_observer : public property_notifier { -public: - /// @brief 添加观察回调 - void observe(uint32_t property_id, property_change_callback callback) { - callbacks_[property_id].push_back(std::move(callback)); - } - - /// @brief 观察所有属性变化 - void observe_all(property_change_callback callback) { - global_callbacks_.push_back(std::move(callback)); - } - - /// @brief 移除指定属性的所有回调 - void unobserve(uint32_t property_id) { - callbacks_.erase(property_id); - } - - /// @brief 清除所有回调 - void clear() { - callbacks_.clear(); - global_callbacks_.clear(); - } - - /// @brief 属性变化通知实现 - void on_property_changed(uint32_t property_id, dirty_flag flag) override { - // 触发特定属性的回调 - if (auto it = callbacks_.find(property_id); it != callbacks_.end()) { - for (auto& cb : it->second) { - cb(property_id); - } - } - - // 触发全局回调 - for (auto& cb : global_callbacks_) { - cb(property_id); - } - } - - /// @brief 批量处理脏属性 - template - void process_dirty(Func&& processor) { - for (auto id : dirty_properties_) { - processor(id); - } - dirty_properties_.clear(); - } - -private: - std::unordered_map> callbacks_; - std::vector global_callbacks_; - std::vector dirty_properties_; -}; - -} // namespace mirage::threading -``` - ---- - -## 异步操作 - -### 6.1 异步结果 Async Result - -基于 `std::expected` 的异步结果类型,统一错误处理。 - -```cpp -// file: src/common/threading/async_result.h - -namespace mirage::threading { - -/// @brief 异步操作错误类型 -enum class async_error { - cancelled, ///< 操作被取消 - timeout, ///< 操作超时 - thread_error, ///< 线程错误 - invalid_state, ///< 无效状态 - execution_failed ///< 执行失败 -}; - -/// @brief 异步结果 -/// @tparam T 结果类型 -template -using async_result = std::expected; - -/// @brief void 特化 -using async_void_result = std::expected; - -/// @brief 异步状态 -enum class async_state { - pending, ///< 等待执行 - running, ///< 执行中 - completed, ///< 已完成 - failed, ///< 执行失败 - cancelled ///< 已取消 -}; - -/// @brief 异步操作句柄 -/// @tparam T 结果类型 -template -class async_handle { -public: - using result_type = async_result; - - async_handle() = default; - - /// @brief 检查是否完成 - [[nodiscard]] bool is_ready() const noexcept { - return state_.load(std::memory_order_acquire) == async_state::completed || - state_.load(std::memory_order_acquire) == async_state::failed; - } - - /// @brief 获取当前状态 - [[nodiscard]] async_state state() const noexcept { - return state_.load(std::memory_order_acquire); - } - - /// @brief 等待完成 - void wait() const { - std::unique_lock lock(mutex_); - cv_.wait(lock, [this] { return is_ready(); }); - } - - /// @brief 带超时等待 - template - [[nodiscard]] bool wait_for(const std::chrono::duration& timeout) const { - std::unique_lock lock(mutex_); - return cv_.wait_for(lock, timeout, [this] { return is_ready(); }); - } - - /// @brief 获取结果(阻塞) - [[nodiscard]] result_type get() { - wait(); - return std::move(result_); - } - - /// @brief 尝试获取结果(非阻塞) - [[nodiscard]] std::optional try_get() { - if (!is_ready()) { - return std::nullopt; - } - return std::move(result_); - } - - /// @brief 请求取消 - void cancel() noexcept { - cancel_requested_.store(true, std::memory_order_release); - } - - /// @brief 检查是否请求取消 - [[nodiscard]] bool is_cancel_requested() const noexcept { - return cancel_requested_.load(std::memory_order_acquire); - } - - // 内部方法 - 由执行者调用 - void set_result(T value) { - std::lock_guard lock(mutex_); - result_ = std::move(value); - state_.store(async_state::completed, std::memory_order_release); - cv_.notify_all(); - } - - void set_error(async_error error) { - std::lock_guard lock(mutex_); - result_ = std::unexpected(error); - state_.store(async_state::failed, std::memory_order_release); - cv_.notify_all(); - } - -private: - std::atomic state_{async_state::pending}; - std::atomic cancel_requested_{false}; - result_type result_; - mutable std::mutex mutex_; - mutable std::condition_variable cv_; -}; - -} // namespace mirage::threading -``` - -### 6.2 Future Chain 链式异步调用 - -```cpp -// file: src/common/threading/future_chain.h - -namespace mirage::threading { - -// 前向声明 -template class future_chain; -class thread_dispatcher; - -/// @brief 链式异步调用 -/// @tparam T 当前结果类型 -template -class future_chain { -public: - using value_type = T; - using handle_type = std::shared_ptr>; - - explicit future_chain(handle_type handle, thread_dispatcher* dispatcher = nullptr) - : handle_(std::move(handle)), dispatcher_(dispatcher) {} - - // ======================================================================== - // 链式调用 - then - // ======================================================================== - - /// @brief 成功时执行 - /// @tparam Func 回调函数类型 - /// @param func 回调函数 T -> U - /// @return 新的 future_chain - template - requires std::invocable - [[nodiscard]] auto then(Func&& func) - -> future_chain> - { - using U = std::invoke_result_t; - auto new_handle = std::make_shared>(); - - // 注册继续回调 - register_continuation([ - func = std::forward(func), - new_handle, - this_handle = handle_ - ]() mutable { - auto result = this_handle->try_get(); - if (result && result->has_value()) { - try { - if constexpr (std::is_void_v) { - std::invoke(func, std::move(**result)); - new_handle->set_result({}); - } else { - new_handle->set_result(std::invoke(func, std::move(**result))); - } - } catch (...) { - new_handle->set_error(async_error::execution_failed); - } - } else if (result) { - new_handle->set_error(result->error()); - } - }); - - return future_chain(new_handle, dispatcher_); - } - - /// @brief 在指定线程执行 then - template - [[nodiscard]] auto then_on(Func&& func) - -> future_chain> - { - // 实现需要 thread_dispatcher 支持 - return then(std::forward(func)); // 简化版本 - } - - // ======================================================================== - // 错误处理 - on_error - // ======================================================================== - - /// @brief 错误时执行 - template - requires std::invocable - [[nodiscard]] future_chain& on_error(Func&& func) { - register_error_handler([ - func = std::forward(func), - this_handle = handle_ - ]() mutable { - auto result = this_handle->try_get(); - if (result && !result->has_value()) { - std::invoke(func, result->error()); - } - }); - return *this; - } - - /// @brief 错误恢复 - template - requires std::invocable && - std::convertible_to, T> - [[nodiscard]] future_chain recover(Func&& func) { - auto new_handle = std::make_shared>(); - - register_continuation([ - func = std::forward(func), - new_handle, - this_handle = handle_ - ]() mutable { - auto result = this_handle->try_get(); - if (result) { - if (result->has_value()) { - new_handle->set_result(std::move(**result)); - } else { - try { - new_handle->set_result(std::invoke(func, result->error())); - } catch (...) { - new_handle->set_error(async_error::execution_failed); - } - } - } - }); - - return future_chain(new_handle, dispatcher_); - } - - // ======================================================================== - // 完成回调 - finally - // ======================================================================== - - /// @brief 无论成功失败都执行 - template - requires std::invocable - [[nodiscard]] future_chain& finally(Func&& func) { - register_continuation([func = std::forward(func)]() mutable { - std::invoke(func); - }); - return *this; - } - - // ======================================================================== - // 同步等待 - // ======================================================================== - - /// @brief 阻塞等待结果 - [[nodiscard]] async_result await() { - return handle_->get(); - } - - /// @brief 带超时等待 - template - [[nodiscard]] std::optional> - await_for(const std::chrono::duration& timeout) { - if (handle_->wait_for(timeout)) { - return handle_->get(); - } - return std::nullopt; - } - - /// @brief 获取底层句柄 - [[nodiscard]] handle_type handle() const noexcept { return handle_; } - -private: - void register_continuation(std::move_only_function func); - void register_error_handler(std::move_only_function func); - - handle_type handle_; - thread_dispatcher* dispatcher_; -}; - -/// @brief 创建已完成的 future -template -[[nodiscard]] future_chain make_ready_future(T value) { - auto handle = std::make_shared>(); - handle->set_result(std::move(value)); - return future_chain(handle); -} - -/// @brief 创建失败的 future -template -[[nodiscard]] future_chain make_failed_future(async_error error) { - auto handle = std::make_shared>(); - handle->set_error(error); - return future_chain(handle); -} - -} // namespace mirage::threading -``` - -### 6.3 线程调度器 Thread Dispatcher - -```cpp -// file: src/common/threading/thread_dispatcher.h - -namespace mirage::threading { - -/// @brief 调度任务类型 -using dispatch_task = std::move_only_function; - -/// @brief 线程调度器 -/// 管理跨线程的任务调度 -class thread_dispatcher { -public: - thread_dispatcher() = default; - ~thread_dispatcher() = default; - - // 禁止拷贝和移动 - thread_dispatcher(const thread_dispatcher&) = delete; - thread_dispatcher& operator=(const thread_dispatcher&) = delete; - - /// @brief 调度到主线程执行 - void dispatch_main(dispatch_task task) { - main_queue_.push(std::move(task)); - } - - /// @brief 调度到布局线程执行 - void dispatch_layout(dispatch_task task) { - layout_queue_.push(std::move(task)); - } - - /// @brief 调度到渲染线程执行 - void dispatch_render(dispatch_task task) { - render_queue_.push(std::move(task)); - } - - /// @brief 根据标签调度 - template - void dispatch(dispatch_task task) { - if constexpr (std::is_same_v) { - dispatch_main(std::move(task)); - } else if constexpr (std::is_same_v) { - dispatch_layout(std::move(task)); - } else if constexpr (std::is_same_v) { - dispatch_render(std::move(task)); - } else { - // any_thread_tag - 立即执行 - task(); - } - } - - /// @brief 处理主线程任务队列 - /// @note 应在主线程的事件循环中调用 - void process_main_queue() { - thread_context::assert_thread(); - process_queue(main_queue_); - } - - /// @brief 处理布局线程任务队列 - void process_layout_queue() { - thread_context::assert_thread(); - process_queue(layout_queue_); - } - - /// @brief 处理渲染线程任务队列 - void process_render_queue() { - thread_context::assert_thread(); - process_queue(render_queue_); - } - -private: - void process_queue(mirage::threading::mpsc_queue& queue) { - while (auto task = queue.try_pop()) { - (*task)(); - } - } - - mirage::threading::mpsc_queue main_queue_; - mirage::threading::mpsc_queue layout_queue_; - mirage::threading::mpsc_queue render_queue_; -}; - -/// @brief 全局调度器访问 -thread_dispatcher& get_dispatcher(); - -} // namespace mirage::threading -``` - ---- - -## 状态同步 - -### 7.1 同步状态 Sync State - -双缓冲自动同步状态,实现线程间的无锁数据传递。 - -```cpp -// file: src/common/threading/sync_state.h - -namespace mirage::threading { - -/// @brief 同步状态 -/// 双缓冲实现,写线程和读线程可以并行工作 -/// @tparam T 状态类型 -/// @tparam WriteTag 写线程标签 -/// @tparam ReadTag 读线程标签 -template -class sync_state { -public: - using value_type = T; - - sync_state() = default; - explicit sync_state(T initial_value) - : buffers_{initial_value, initial_value} {} - - // ======================================================================== - // 写端接口(只能在 WriteTag 线程调用) - // ======================================================================== - - /// @brief 获取写缓冲区 - [[nodiscard]] T& write_buffer() noexcept { - thread_context::assert_thread(); - return buffers_[write_index_.load(std::memory_order_relaxed)]; - } - - /// @brief 修改写缓冲区 - template - requires std::invocable - void modify(Func&& func) { - thread_context::assert_thread(); - std::invoke(std::forward(func), write_buffer()); - dirty_ = true; - } - - /// @brief 发布更新(交换缓冲区) - void publish() noexcept { - thread_context::assert_thread(); - if (dirty_) { - // 交换索引 - int old_index = write_index_.load(std::memory_order_relaxed); - int new_index = 1 - old_index; - - // 复制数据到新写缓冲 - buffers_[new_index] = buffers_[old_index]; - - // 原子交换 - write_index_.store(new_index, std::memory_order_release); - version_.fetch_add(1, std::memory_order_relaxed); - dirty_ = false; - } - } - - // ======================================================================== - // 读端接口(只能在 ReadTag 线程调用) - // ======================================================================== - - /// @brief 获取读缓冲区 - [[nodiscard]] const T& read_buffer() const noexcept { - thread_context::assert_thread(); - int write_idx = write_index_.load(std::memory_order_acquire); - return buffers_[1 - write_idx]; // 读取非写入缓冲 - } - - /// @brief 访问读缓冲区 - template - requires std::invocable - auto read(Func&& func) const -> std::invoke_result_t { - thread_context::assert_thread(); - return std::invoke(std::forward(func), read_buffer()); - } - - // ======================================================================== - // 通用接口 - // ======================================================================== - - /// @brief 获取当前版本号 - [[nodiscard]] uint64_t version() const noexcept { - return version_.load(std::memory_order_relaxed); - } - - /// @brief 检查是否有未发布的修改 - [[nodiscard]] bool is_dirty() const noexcept { - return dirty_; - } - -private: - std::array buffers_{}; - alignas(64) std::atomic write_index_{0}; - alignas(64) std::atomic version_{0}; - bool dirty_ = false; -}; - -/// @brief 类型别名:主线程写,布局线程读 -template -using main_to_layout_state = sync_state; - -/// @brief 类型别名:布局线程写,渲染线程读 -template -using layout_to_render_state = sync_state; - -/// @brief 类型别名:主线程写,渲染线程读 -template -using main_to_render_state = sync_state; - -} // namespace mirage::threading -``` - -### 7.2 发布/订阅机制 Publish Subscribe - -```cpp -// file: src/common/threading/pub_sub.h - -namespace mirage::threading { - -/// @brief 订阅令牌 -class subscription_token { -public: - subscription_token() = default; - explicit subscription_token(uint64_t id) : id_(id) {} - - [[nodiscard]] uint64_t id() const noexcept { return id_; } - [[nodiscard]] bool valid() const noexcept { return id_ != 0; } - explicit operator bool() const noexcept { return valid(); } - -private: - uint64_t id_ = 0; -}; - -/// @brief 主题基类 -class topic_base { -public: - virtual ~topic_base() = default; - virtual void unsubscribe(subscription_token token) = 0; -}; - -/// @brief 发布/订阅主题 -/// @tparam T 消息类型 -template -class topic : public topic_base { -public: - using message_type = T; - using callback_type = std::move_only_function; - - /// @brief 订阅主题 - /// @param callback 回调函数 - /// @return 订阅令牌(用于取消订阅) - [[nodiscard]] subscription_token subscribe(callback_type callback) { - std::lock_guard lock(mutex_); - uint64_t id = next_id_++; - subscribers_[id] = std::move(callback); - return subscription_token(id); - } - - /// @brief 在指定线程上接收 - template - [[nodiscard]] subscription_token subscribe_on(callback_type callback) { - return subscribe([cb = std::move(callback)](const T& msg) { - get_dispatcher().dispatch([cb = &cb, msg]() { - (*cb)(msg); - }); - }); - } - - /// @brief 取消订阅 - void unsubscribe(subscription_token token) override { - std::lock_guard lock(mutex_); - subscribers_.erase(token.id()); - } - - /// @brief 发布消息 - void publish(const T& message) { - std::lock_guard lock(mutex_); - for (auto& [id, callback] : subscribers_) { - callback(message); - } - } - - /// @brief 发布消息(移动语义) - void publish(T&& message) { - std::lock_guard lock(mutex_); - for (auto& [id, callback] : subscribers_) { - callback(message); // 最后一个使用 move - } - } - - /// @brief 获取订阅者数量 - [[nodiscard]] size_t subscriber_count() const { - std::lock_guard lock(mutex_); - return subscribers_.size(); - } - -private: - mutable std::mutex mutex_; - std::unordered_map subscribers_; - uint64_t next_id_ = 1; -}; - -/// @brief RAII 订阅守卫 -class subscription_guard { -public: - subscription_guard() = default; - - subscription_guard(topic_base& topic, subscription_token token) - : topic_(&topic), token_(token) {} - - ~subscription_guard() { - if (topic_ && token_) { - topic_->unsubscribe(token_); - } - } - - // 禁止拷贝 - subscription_guard(const subscription_guard&) = delete; - subscription_guard& operator=(const subscription_guard&) = delete; - - // 允许移动 - subscription_guard(subscription_guard&& other) noexcept - : topic_(std::exchange(other.topic_, nullptr)) - , token_(std::exchange(other.token_, {})) {} - - subscription_guard& operator=(subscription_guard&& other) noexcept { - if (this != &other) { - if (topic_ && token_) { - topic_->unsubscribe(token_); - } - topic_ = std::exchange(other.topic_, nullptr); - token_ = std::exchange(other.token_, {}); - } - return *this; - } - - /// @brief 释放所有权 - subscription_token release() noexcept { - topic_ = nullptr; - return std::exchange(token_, {}); - } - -private: - topic_base* topic_ = nullptr; - subscription_token token_; -}; - -} // namespace mirage::threading -``` - -### 7.3 版本管理器 Version Manager - -```cpp -// file: src/common/threading/version_manager.h - -namespace mirage::threading { - -/// @brief 版本信息 -struct version_info { - uint64_t version = 0; ///< 版本号 - uint64_t frame_number = 0; ///< 帧号 - uint32_t generation = 0; ///< 代数 - - bool operator==(const version_info&) const = default; - auto operator<=>(const version_info&) const = default; -}; - -/// @brief 版本管理器 -/// 追踪多个数据源的版本变化 -class version_manager { -public: - /// @brief 注册数据源 - /// @param name 数据源名称 - /// @return 数据源ID - [[nodiscard]] uint32_t register_source(std::string_view name) { - uint32_t id = next_source_id_++; - sources_[id] = source_info{std::string(name), {}}; - return id; - } - - /// @brief 更新数据源版本 - void update_version(uint32_t source_id, uint64_t frame_number) { - if (auto it = sources_.find(source_id); it != sources_.end()) { - auto& info = it->second.version; - info.version++; - info.frame_number = frame_number; - info.generation++; - global_version_++; - } - } - - /// @brief 获取数据源版本 - [[nodiscard]] version_info get_version(uint32_t source_id) const { - if (auto it = sources_.find(source_id); it != sources_.end()) { - return it->second.version; - } - return {}; - } - - /// @brief 获取全局版本 - [[nodiscard]] uint64_t global_version() const noexcept { - return global_version_.load(std::memory_order_relaxed); - } - - /// @brief 检查是否有更新 - [[nodiscard]] bool has_updates_since(uint64_t version) const noexcept { - return global_version() > version; - } - - /// @brief 获取自指定版本以来更新的源 - [[nodiscard]] std::vector get_updated_sources(uint64_t since_version) const { - std::vector result; - for (const auto& [id, info] : sources_) { - if (info.version.version > since_version) { - result.push_back(id); - } - } - return result; - } - -private: - struct source_info { - std::string name; - version_info version; - }; - - std::unordered_map sources_; - uint32_t next_source_id_ = 1; - std::atomic global_version_{0}; -}; - -} // namespace mirage::threading -``` - ---- - -## 与现有系统集成 - -### 8.1 与 thread_coordinator 集成 - -现有的 `thread_coordinator` 可以逐步迁移到新框架: - -```cpp -// 现有代码 -class thread_coordinator { - std::unique_ptr frame_sync_; - std::unique_ptr tree_buffer_; - // ... -}; - -// 迁移后 -class thread_coordinator { - // 使用新的同步状态替代手动缓冲区管理 - sync_state render_tree_state_; - - // 使用 thread_dispatcher 替代手动队列 - thread_dispatcher dispatcher_; - - // 保留 frame_sync 用于帧节奏控制 - std::unique_ptr frame_sync_; -}; -``` - -### 8.2 与 widget_state_store 集成 - -```cpp -// 现有代码 -class widget_state_store { - // 手动管理脏标记 - void set_dirty(uint64_t widget_id, dirty_state state); - void clear_dirty(uint64_t widget_id); -}; - -// 使用新属性系统 -class widget_state_v2 { - // 属性自动管理脏标记 - layout_property position; - layout_property size; - render_property opacity; - render_property background_color; - - // 计算属性自动依赖追踪 - computed_property global_bounds = make_computed([this] { - return aabb2d_t::from_pos_size(position.get(), size.get()); - }); -}; -``` - -### 8.3 与 layout_thread 集成 - -```cpp -// 在布局线程入口注册线程身份 -void layout_thread::thread_main(std::stop_token stop_token) { - // 注册线程身份 - thread_registration_guard guard; - - while (!stop_token.stop_requested()) { - // 处理调度任务 - get_dispatcher().process_layout_queue(); - - // 原有布局逻辑... - if (auto request = request_queue_.try_pop()) { - auto result = process_layout(*request); - // ... - } - } -} -``` - -### 8.4 与 render_thread 集成 - -```cpp -void render_thread::thread_main(std::stop_token stop_token) { - thread_registration_guard guard; - - while (!stop_token.stop_requested()) { - get_dispatcher().process_render_queue(); - - // 使用 sync_state 读取渲染数据 - auto& tree = render_tree_state_.read_buffer(); - render_frame(&tree); - } -} -``` - -### 8.5 渐进式迁移策略 - -``` -阶段 1: 引入核心原语(无破坏性) - - 添加 thread_tags.h, thread_context.h - - 在现有线程中注册身份 - - 不修改现有逻辑 - -阶段 2: 引入属性系统(局部替换) - - 新控件使用 property - - 旧控件保持不变 - - 验证性能影响 - -阶段 3: 引入状态同步(替换双缓冲) - - 使用 sync_state 替换 render_tree_buffer - - 保留 frame_sync 的帧节奏控制 - -阶段 4: 引入异步操作(替换回调) - - 使用 future_chain 替换 std::function 回调 - - 使用 thread_dispatcher 统一调度 -``` - ---- - -## 使用示例 - -### 9.1 基本属性使用 - -```cpp -#include "threading/property.h" - -class my_widget { -public: - // 定义属性 - layout_property position{vec2f_t::Zero()}; - layout_property size{vec2f_t{100, 100}}; - render_property opacity{1.0f}; - - my_widget() { - // 绑定通知器 - position.bind_notifier(&observer_, 0); - size.bind_notifier(&observer_, 1); - opacity.bind_notifier(&observer_, 2); - - // 观察变化 - observer_.observe(0, [this](uint32_t) { - on_position_changed(); - }); - } - - void set_position(vec2f_t pos) { - position = pos; // 自动标记脏,触发回调 - } - - void update() { - if (position.is_dirty() || size.is_dirty()) { - recalculate_layout(); - position.clear_dirty(); - size.clear_dirty(); - } - - if (opacity.is_dirty()) { - update_render_data(); - opacity.clear_dirty(); - } - } - -private: - property_observer observer_; - void on_position_changed(); - void recalculate_layout(); - void update_render_data(); -}; -``` - -### 9.2 计算属性使用 - -```cpp -#include "threading/computed_property.h" - -class layout_node { -public: - layout_property local_position; - layout_property local_size; - layout_node* parent = nullptr; - - // 全局位置 = 父节点全局位置 + 本地位置 - computed_property global_position = make_computed([this] { - if (parent) { - return parent->global_position.get() + local_position.get(); - } - return local_position.get(); - }); - - // 全局包围盒 - computed_property global_bounds = make_computed([this] { - auto pos = global_position.get(); - return aabb2d_t::from_pos_size(pos, local_size.get()); - }); - - void move_to(vec2f_t pos) { - local_position = pos; - // global_position 和 global_bounds 自动失效 - // 下次访问时重新计算 - } -}; -``` - -### 9.3 线程绑定类型使用 - -```cpp -#include "threading/thread_bound.h" - -class render_resource_manager { -public: - // 渲染资源只能在渲染线程访问 - render_thread_bound textures; - render_thread_bound shaders; - - // 从主线程请求加载纹理 - future_chain load_texture_async(const std::string& path) { - auto handle = std::make_shared>(); - - // 调度到渲染线程执行 - get_dispatcher().dispatch_render([this, path, handle] { - // 在渲染线程中安全访问 - auto id = textures.get().load(path); - handle->set_result(id); - }); - - return future_chain(handle); - } -}; - -// 使用示例 -void main_thread_code() { - render_resource_manager manager; - - manager.load_texture_async("background.png") - .then([](texture_id id) { - std::cout << "Texture loaded: " << id << std::endl; - return id; - }) - .on_error([](async_error err) { - std::cerr << "Failed to load texture" << std::endl; - }); -} -``` - -### 9.4 状态同步使用 - -```cpp -#include "threading/sync_state.h" - -// 主线程写入,布局线程读取的视口状态 -main_to_layout_state viewport_state; - -// 主线程 -void on_window_resize(int width, int height) { - viewport_state.modify([&](viewport_info& info) { - info.width = width; - info.height = height; - info.dpi_scale = get_dpi_scale(); - }); - viewport_state.publish(); -} - -// 布局线程 -void layout_thread_update() { - auto& viewport = viewport_state.read_buffer(); - layout_root(viewport.width, viewport.height); -} -``` - -### 9.5 发布订阅使用 - -```cpp -#include "threading/pub_sub.h" - -// 定义事件类型 -struct layout_complete_event { - uint64_t frame_number; - double layout_time_ms; -}; - -// 全局主题 -topic layout_complete_topic; - -// 订阅者(在主线程接收) -class main_thread_handler { -public: - main_thread_handler() { - subscription_ = subscription_guard( - layout_complete_topic, - layout_complete_topic.subscribe_on( - [this](const layout_complete_event& e) { - on_layout_complete(e); - } - ) - ); - } - -private: - void on_layout_complete(const layout_complete_event& e) { - // 在主线程中处理 - update_stats(e.layout_time_ms); - } - - subscription_guard subscription_; -}; - -// 发布者(在布局线程发布) -void layout_thread::on_layout_done(uint64_t frame, double time_ms) { - layout_complete_topic.publish(layout_complete_event{frame, time_ms}); -} -``` - -### 9.6 Future Chain 链式调用 - -```cpp -#include "threading/future_chain.h" - -// 复杂的异步操作链 -void load_and_process_scene(const std::string& scene_path) { - load_scene_async(scene_path) - .then([](scene_data data) { - // 解析场景数据 - return parse_scene(data); - }) - .then_on([](parsed_scene scene) { - // 在布局线程中构建布局树 - return build_layout_tree(scene); - }) - .then_on([](layout_tree tree) { - // 在渲染线程中创建渲染资源 - return create_render_resources(tree); - }) - .then_on([](render_handle handle) { - // 回到主线程更新 UI - update_loading_progress(100); - show_scene(handle); - }) - .on_error([](async_error err) { - show_error_dialog("Failed to load scene"); - }) - .finally([] { - hide_loading_indicator(); - }); -} -``` - ---- - -## 文件结构 - -``` -src/common/threading/ -├── thread_tags.h # 线程标签定义 -├── thread_context.h # 线程上下文管理 -├── thread_bound.h # 线程绑定类型 -├── sync_primitives.h # 同步原语封装 -│ -├── property_base.h # 属性基础定义 -├── property.h # 自动脏标记属性 -├── computed_property.h # 计算属性 -├── property_observer.h # 属性观察器 -│ -├── async_result.h # 异步结果类型 -├── future_chain.h # 链式异步调用 -├── thread_dispatcher.h # 线程调度器 -│ -├── sync_state.h # 双缓冲同步状态 -├── pub_sub.h # 发布订阅机制 -├── version_manager.h # 版本管理器 -│ -└── message_queue.h # 现有消息队列(保留) -``` - -### 依赖关系图 - -``` - thread_tags.h - | - v - thread_context.h - / \ - v v - thread_bound.h sync_primitives.h - | | - v v - property_base.h --------+ - / | \ - v v v - property.h computed_property.h property_observer.h - | - v - async_result.h - | - v - future_chain.h - / \ - v v - thread_dispatcher.h sync_state.h - | - v - pub_sub.h - | - v - version_manager.h -``` - ---- - -## C++23 特性使用总结 - -| 特性 | 使用位置 | 用途 | -|------|---------|------| -| Deducing this | `thread_bound::get()` | 统一 const/non-const 访问器 | -| `std::expected` | `async_result`, `try_get()` | 类型安全的错误处理 | -| `std::move_only_function` | 回调类型 | 高效的一次性回调 | -| `[[assume]]` | `assert_thread()` | 编译器优化提示 | -| `requires` 子句 | 模板约束 | 编译时类型检查 | -| Concepts | `thread_tag`, `property_like` | 模板参数约束 | - ---- - -## 性能考量 - -### 零开销原则 - -1. **编译时检查**:`thread_bound` 的线程检查在 Release 模式下完全消除 -2. **内联优化**:`property::get()` 等简单访问器会被内联 -3. **无虚函数开销**:核心类型不使用虚函数 -4. **缓存友好**:`sync_state` 使用缓存行对齐 - -### 内存布局优化 - -```cpp -// property 内存布局(紧凑) -template -class property { - T value_; // sizeof(T) - bool dirty_; // 1 byte - property_notifier* notifier_;// 8 bytes - uint32_t property_id_; // 4 bytes - // padding: 3 bytes -}; -// Total: sizeof(T) + 16 bytes(对齐后) - -// sync_state 内存布局(缓存行对齐) -template -class sync_state { - std::array buffers_; // 2 * sizeof(T) - alignas(64) std::atomic write_index_; // 64 bytes - alignas(64) std::atomic version_; // 64 bytes - bool dirty_; // 1 byte -}; -``` - ---- - -## 总结 - -本线程同步框架通过以下设计实现了三大目标: - -1. **无感同步** - - `property` 自动脏标记 - - `sync_state` 自动缓冲区管理 - - `thread_dispatcher` 自动线程调度 - -2. **类型安全** - - `thread_bound` 编译时线程检查 - - `std::expected` 统一错误处理 - - Concepts 约束模板参数 - -3. **零开销抽象** - - 编译时检查在 Release 中消除 - - 简单访问器内联 - - 缓存行对齐避免伪共享 - -框架设计与现有代码兼容,支持渐进式迁移,不需要一次性重构整个代码库。 - ---- - -## 六、使用示例 - -本章提供完整的、可编译的代码示例,演示框架的主要功能和最佳实践。 - -### 10.1 基本属性使用示例 - -演示如何使用 `property` 实现自动脏标记: - -```cpp -#include -#include - -// 简单的 2D 向量类型 -struct vec2f_t { - float x = 0.0f; - float y = 0.0f; - - bool operator==(const vec2f_t&) const = default; -}; - -// 使用属性系统的组件 -class transform_component { -public: - // 布局属性 - 修改时自动标记 needs_layout - mirage::threading::layout_property position; - mirage::threading::layout_property scale{vec2f_t{1.0f, 1.0f}}; - - // 渲染属性 - 修改时自动标记 needs_render - mirage::threading::render_property opacity{1.0f}; - - transform_component() { - // 绑定属性观察器 - position.bind_notifier(&observer_, 0); - scale.bind_notifier(&observer_, 1); - opacity.bind_notifier(&observer_, 2); - - // 设置变化回调 - observer_.observe(0, [this](uint32_t) { - std::cout << "位置已改变: (" << position.get().x - << ", " << position.get().y << ")\n"; - }); - - observer_.observe(1, [this](uint32_t) { - std::cout << "缩放已改变\n"; - }); - } - - // 修改属性(自动标记脏) - void set_position(vec2f_t pos) { - position = pos; // 自动标记 needs_layout 并触发回调 - } - - void set_opacity(float op) { - opacity = op; // 自动标记 needs_render - } - - // 处理脏属性 - void update() { - if (position.is_dirty() || scale.is_dirty()) { - std::cout << "执行布局更新\n"; - position.clear_dirty(); - scale.clear_dirty(); - } - - if (opacity.is_dirty()) { - std::cout << "执行渲染更新\n"; - opacity.clear_dirty(); - } - } - -private: - mirage::threading::property_observer observer_; -}; - -// 使用示例 -void property_example() { - transform_component comp; - - // 修改属性 - 自动触发通知 - comp.set_position(vec2f_t{100.0f, 200.0f}); - comp.set_opacity(0.8f); - - // 处理脏标记 - comp.update(); -} -``` - -**输出:** -``` -位置已改变: (100, 200) -执行布局更新 -执行渲染更新 -``` - -### 10.2 线程绑定类型示例 - -演示如何使用 `thread_bound` 实现编译时线程安全: - -```cpp -#include -#include -#include -#include -#include - -using namespace mirage::threading; - -// 模拟的纹理缓存 -struct texture_cache { - std::vector textures; - - void load(const std::string& name) { - textures.push_back(name); - std::cout << "加载纹理: " << name << "\n"; - } - - size_t count() const { return textures.size(); } -}; - -// 资源管理器 - 使用线程绑定 -class resource_manager { -public: - // 纹理缓存只能在渲染线程访问 - render_thread_bound textures; - - resource_manager() { - textures = render_thread_bound( - std::in_place - ); - } -}; - -// 渲染线程函数 -void render_thread_function(resource_manager& mgr) { - // 注册渲染线程 - thread_registration_guard guard; - - std::cout << "渲染线程:开始\n"; - - // 在渲染线程中可以安全访问 - auto& cache = mgr.textures.get(); - cache.load("texture1.png"); - cache.load("texture2.png"); - - std::cout << "渲染线程:加载了 " << cache.count() << " 个纹理\n"; -} - -// 主线程函数 -void main_thread_function(resource_manager& mgr) { - // 注册主线程 - thread_registration_guard guard; - - std::cout << "主线程:开始\n"; - - // 尝试访问会触发断言(Debug模式)或编译时错误 - // auto& cache = mgr.textures.get(); // 错误! - - // 正确方式:获取快照(只读副本) - auto snapshot = mgr.textures.snapshot(); - std::cout << "主线程:快照显示 " << snapshot.count() << " 个纹理\n"; -} - -void thread_bound_example() { - resource_manager mgr; - - // 创建渲染线程 - std::thread render_thread(render_thread_function, std::ref(mgr)); - render_thread.join(); - - // 在主线程访问 - main_thread_function(mgr); -} -``` - -**输出:** -``` -渲染线程:开始 -加载纹理: texture1.png -加载纹理: texture2.png -渲染线程:加载了 2 个纹理 -主线程:开始 -主线程:快照显示 2 个纹理 -``` - -### 10.3 异步链式调用示例 - -演示如何使用 `future_chain` 实现优雅的异步操作: - -```cpp -#include -#include -#include -#include - -using namespace mirage::threading; - -// 模拟异步加载场景数据 -auto load_scene_async(const std::string& path) -> future_chain { - auto handle = std::make_shared>(); - - // 在后台线程加载 - std::thread([handle, path]() { - std::cout << "开始加载场景: " << path << "\n"; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - // 模拟加载结果 - handle->set_result("场景数据: " + path); - }).detach(); - - return future_chain(handle); -} - -// 模拟解析场景 -std::string parse_scene(const std::string& data) { - std::cout << "解析场景数据\n"; - return "已解析: " + data; -} - -// 模拟创建渲染资源 -int create_render_resources(const std::string& parsed) { - std::cout << "创建渲染资源: " << parsed << "\n"; - return 42; // 资源句柄 -} - -void async_chain_example() { - std::cout << "=== 异步链式调用示例 ===\n"; - - // 链式异步操作 - auto chain = load_scene_async("game_level_1.scene") - .then([](std::string data) { - // 成功加载后解析 - return parse_scene(data); - }) - .then([](std::string parsed) { - // 解析后创建资源 - return create_render_resources(parsed); - }) - .on_error([](async_error err) { - std::cerr << "操作失败\n"; - }) - .finally([]() { - std::cout << "操作完成\n"; - }); - - // 等待结果 - auto result = chain.await(); - if (result.has_value()) { - std::cout << "最终结果: 资源句柄 = " << *result << "\n"; - } -} -``` - -**输出:** -``` -=== 异步链式调用示例 === -开始加载场景: game_level_1.scene -解析场景数据 -创建渲染资源: 已解析: 场景数据: game_level_1.scene -操作完成 -最终结果: 资源句柄 = 42 -``` - -### 10.4 双缓冲状态同步示例 - -演示如何使用 `sync_state` 实现线程间的无锁数据传递: - -```cpp -#include -#include -#include -#include - -using namespace mirage::threading; - -// 视口信息 -struct viewport_info { - int width = 0; - int height = 0; - float dpi_scale = 1.0f; - - void print() const { - std::cout << " 视口: " << width << "x" << height - << " @ " << dpi_scale << "x\n"; - } -}; - -// 主线程写,布局线程读的状态 -main_to_layout_state g_viewport_state; - -// 模拟主线程 -void main_thread_simulation() { - thread_registration_guard guard; - - std::cout << "主线程:更新视口信息\n"; - - // 方式1:直接修改写缓冲区 - auto& buffer = g_viewport_state.write_buffer(); - buffer.width = 1920; - buffer.height = 1080; - buffer.dpi_scale = 2.0f; - - // 发布更新 - g_viewport_state.publish(); - std::cout << "主线程:已发布版本 " << g_viewport_state.version() << "\n"; - - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - - // 方式2:使用 modify 函数 - g_viewport_state.modify([](viewport_info& info) { - info.width = 2560; - info.height = 1440; - }); - g_viewport_state.publish(); - std::cout << "主线程:已发布版本 " << g_viewport_state.version() << "\n"; -} - -// 模拟布局线程 -void layout_thread_simulation() { - thread_registration_guard guard; - - std::cout << "布局线程:开始读取\n"; - - for (int i = 0; i < 3; ++i) { - std::this_thread::sleep_for(std::chrono::milliseconds(30)); - - // 读取最新数据 - const auto& viewport = g_viewport_state.read_buffer(); - std::cout << "布局线程:读取版本 " << g_viewport_state.version() << "\n"; - viewport.print(); - } -} - -void sync_state_example() { - std::cout << "=== 双缓冲状态同步示例 ===\n"; - - // 初始化状态 - g_viewport_state = main_to_layout_state( - viewport_info{800, 600, 1.0f} - ); - - // 启动两个线程 - std::thread layout_thread(layout_thread_simulation); - std::thread main_thread(main_thread_simulation); - - main_thread.join(); - layout_thread.join(); - - std::cout << "完成\n"; -} -``` - -**输出:** -``` -=== 双缓冲状态同步示例 === -布局线程:开始读取 -主线程:更新视口信息 -主线程:已发布版本 1 -布局线程:读取版本 1 - 视口: 1920x1080 @ 2x -主线程:已发布版本 2 -布局线程:读取版本 2 - 视口: 2560x1440 @ 2x -布局线程:读取版本 2 - 视口: 2560x1440 @ 2x -完成 -``` - -### 10.5 发布订阅示例 - -演示如何使用 `topic` 实现事件通知: - -```cpp -#include -#include -#include -#include - -using namespace mirage::threading; - -// 布局完成事件 -struct layout_complete_event { - uint64_t frame_number; - double layout_time_ms; - int widget_count; -}; - -// 全局主题 -topic g_layout_complete_topic; - -// 主线程的事件处理器 -class main_thread_handler { -public: - main_thread_handler() { - // 订阅事件(在主线程接收) - subscription_ = subscription_guard( - g_layout_complete_topic, - g_layout_complete_topic.subscribe([this](const layout_complete_event& e) { - on_layout_complete(e); - }) - ); - std::cout << "主线程:已订阅布局完成事件\n"; - } - -private: - void on_layout_complete(const layout_complete_event& e) { - std::cout << "主线程:收到布局完成事件\n"; - std::cout << " 帧号: " << e.frame_number << "\n"; - std::cout << " 耗时: " << e.layout_time_ms << " ms\n"; - std::cout << " 组件数: " << e.widget_count << "\n"; - } - - subscription_guard subscription_; -}; - -// 统计处理器 -class stats_handler { -public: - stats_handler() { - subscription_ = subscription_guard( - g_layout_complete_topic, - g_layout_complete_topic.subscribe([this](const layout_complete_event& e) { - total_frames_++; - total_time_ms_ += e.layout_time_ms; - std::cout << "统计:平均耗时 = " - << (total_time_ms_ / total_frames_) << " ms\n"; - }) - ); - std::cout << "统计模块:已订阅布局完成事件\n"; - } - -private: - subscription_guard subscription_; - uint64_t total_frames_ = 0; - double total_time_ms_ = 0.0; -}; - -// 模拟布局线程 -void layout_thread_simulation() { - std::cout << "布局线程:开始工作\n"; - - for (uint64_t frame = 1; frame <= 3; ++frame) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - - // 模拟布局计算 - double layout_time = 10.0 + frame * 2.0; - - // 发布事件 - layout_complete_event event{ - .frame_number = frame, - .layout_time_ms = layout_time, - .widget_count = 100 + static_cast(frame) * 10 - }; - - std::cout << "布局线程:发布帧 " << frame << " 完成事件\n"; - g_layout_complete_topic.publish(event); - } -} - -void pub_sub_example() { - std::cout << "=== 发布订阅示例 ===\n"; - - // 创建订阅者 - main_thread_handler main_handler; - stats_handler stats; - - std::cout << "订阅者数量: " - << g_layout_complete_topic.subscriber_count() << "\n\n"; - - // 启动布局线程 - std::thread layout_thread(layout_thread_simulation); - layout_thread.join(); - - std::cout << "\n完成\n"; -} -``` - -**输出:** -``` -=== 发布订阅示例 === -主线程:已订阅布局完成事件 -统计模块:已订阅布局完成事件 -订阅者数量: 2 - -布局线程:开始工作 -布局线程:发布帧 1 完成事件 -主线程:收到布局完成事件 - 帧号: 1 - 耗时: 12 ms - 组件数: 110 -统计:平均耗时 = 12 ms -布局线程:发布帧 2 完成事件 -主线程:收到布局完成事件 - 帧号: 2 - 耗时: 14 ms - 组件数: 120 -统计:平均耗时 = 13 ms -布局线程:发布帧 3 完成事件 -主线程:收到布局完成事件 - 帧号: 3 - 耗时: 16 ms - 组件数: 130 -统计:平均耗时 = 14 ms - -完成 -``` - -### 10.6 综合示例:完整的组件系统 - -演示如何综合使用多个功能构建一个完整的组件系统: - -```cpp -#include -#include -#include - -using namespace mirage::threading; - -// 组件基类 -class component { -public: - virtual ~component() = default; - virtual void update() = 0; - virtual const char* name() const = 0; -}; - -// Transform 组件 - 使用属性系统 -class transform : public component { -public: - layout_property position; - layout_property scale{vec2f_t{1.0f, 1.0f}}; - render_property rotation{0.0f}; - - // 计算属性 - 全局变换矩阵 - computed_property needs_update = make_computed([this] { - return position.is_dirty() || scale.is_dirty() || rotation.is_dirty(); - }); - - const char* name() const override { return "Transform"; } - - void update() override { - if (needs_update.get()) { - std::cout << "Transform: 更新变换矩阵\n"; - position.clear_dirty(); - scale.clear_dirty(); - rotation.clear_dirty(); - } - } -}; - -// 实体类 - 管理多个组件 -class entity { -public: - explicit entity(std::string name) : name_(std::move(name)) {} - - template - T* add_component(Args&&... args) { - auto comp = std::make_unique(std::forward(args)...); - T* ptr = comp.get(); - components_.push_back(std::move(comp)); - return ptr; - } - - void update_all() { - std::cout << "实体 '" << name_ << "' 更新组件:\n"; - for (auto& comp : components_) { - comp->update(); - } - } - -private: - std::string name_; - std::vector> components_; -}; - -void comprehensive_example() { - std::cout << "=== 综合示例:组件系统 ===\n"; - - // 创建实体 - entity player("Player"); - - // 添加 Transform 组件 - auto* transform_comp = player.add_component(); - - // 修改属性 - transform_comp->position = vec2f_t{100.0f, 200.0f}; - transform_comp->rotation = 45.0f; - - // 更新组件(自动检测脏状态) - player.update_all(); - - std::cout << "\n再次更新(无变化):\n"; - player.update_all(); - - std::cout << "\n修改后再次更新:\n"; - transform_comp->scale = vec2f_t{2.0f, 2.0f}; - player.update_all(); -} -``` - -**输出:** -``` -=== 综合示例:组件系统 === -实体 'Player' 更新组件: -Transform: 更新变换矩阵 - -再次更新(无变化): -实体 'Player' 更新组件: - -修改后再次更新: -实体 'Player' 更新组件: -Transform: 更新变换矩阵 -``` - -### 10.7 最佳实践总结 - -#### 1. 属性使用 -- ✅ 使用 `layout_property` 标记影响布局的属性 -- ✅ 使用 `render_property` 标记影响渲染的属性 -- ✅ 使用 `computed_property` 避免重复计算 -- ❌ 避免过度使用属性观察器,优先使用轮询检查 - -#### 2. 线程安全 -- ✅ 始终在线程入口使用 `thread_registration_guard` -- ✅ 使用 `thread_bound` 确保数据只在正确线程访问 -- ✅ 使用 `sync_state` 在线程间传递数据 -- ❌ 避免跨线程直接访问非线程安全的数据 - -#### 3. 异步操作 -- ✅ 使用 `future_chain` 构建清晰的异步流程 -- ✅ 总是处理错误情况(`on_error`) -- ✅ 使用 `finally` 确保清理逻辑执行 -- ❌ 避免过长的异步链,适时拆分 - -#### 4. 性能优化 -- ✅ 批量发布状态更新(而非频繁 `publish()`) -- ✅ 使用 `read_buffer()` 而非 `snapshot()` 避免拷贝 -- ✅ 在 Release 模式下线程检查零开销 -- ❌ 避免在热路径使用发布订阅 - -#### 5. 代码组织 -- ✅ 使用统一头文件 `` -- ✅ 按功能模块组织代码,而非按线程组织 -- ✅ 文档化线程访问约定 -- ❌ 避免隐式的线程依赖 - ---- - -## 结语 - -线程同步框架提供了一套完整的工具来简化多线程编程,通过类型安全和零开销抽象实现"无感同步"。 - -以上示例展示了框架的核心功能和使用方式。完整的API文档和高级用法请参考各个头文件中的注释。 - -有关框架的设计理念和实现细节,请参阅本文档的其他章节。 \ No newline at end of file diff --git a/docs/WIDGET_THREAD_SYNC_REFACTORING_PLAN.md b/docs/WIDGET_THREAD_SYNC_REFACTORING_PLAN.md deleted file mode 100644 index 70e8243..0000000 --- a/docs/WIDGET_THREAD_SYNC_REFACTORING_PLAN.md +++ /dev/null @@ -1,531 +0,0 @@ -# Widget 框架线程同步重构计划 - -## 1. 概述 - -本文档详细描述了使用新线程同步框架重构 widget 框架的计划。目标是提高线程安全性、简化状态管理,并实现"无感同步"。 - -### 1.1 重构目标 - -1. **类型安全**:使用 `thread_bound` 确保数据只在正确线程访问 -2. **自动脏标记**:使用 `property` 替代手动脏标记管理 -3. **无锁同步**:使用 `sync_state` 替代手动双缓冲管理 -4. **事件解耦**:使用 `pub_sub` 替代回调函数 -5. **任务调度**:使用 `thread_dispatcher` 统一跨线程任务调度 - -### 1.2 现有框架使用情况 - -| 组件 | 当前状态 | 已使用的线程同步特性 | -|------|----------|---------------------| -| `widget_base.h` | ✅ 部分使用 | `main_property` 宏 | -| `thread_coordinator.h` | ✅ 部分使用 | `layout_to_render_state`, `pub_sub` | -| `widget_state_store.h` | ❌ 未使用 | 手动脏标记管理 | -| `widget_context.h` | ❌ 未使用 | 直接引用访问 | -| `viewport_cache.h` | ❌ 未使用 | 手动缓存管理 | -| `render_collector.h` | ❌ 未使用 | 直接数据访问 | -| `scroll_box` 系列 | ❌ 未使用 | 手动状态管理 | -| 容器类 | ❌ 未使用 | 普通成员变量 | - ---- - -## 2. 重构区域详细分析 - -### 2.1 区域一:Widget 状态存储 (widget_state_store) - -**当前问题:** -- 手动管理 `dirty_state` 枚举和脏标记 -- `primary_state` 和 `secondary_state` 的线程安全依赖于调用者 -- `layout_write_context` 使用手动锁管理 - -**重构方案:** - -```cpp -// 当前实现 -struct primary_state { - dirty_state dirty = dirty_state::measure_invalid; - bool renderable = true; - bool enabled = true; - // ... -}; - -// 重构后 -struct primary_state_v2 { - threading::main_property renderable{true}; - threading::main_property enabled{true}; - threading::dirty_tracker dirty_tracker; - // 属性变化自动更新 dirty_tracker -}; -``` - -**具体修改:** - -| 文件 | 修改内容 | 优先级 | -|------|----------|--------| -| `widget_state.h` | 引入 `property` 替代普通成员 | 高 | -| `widget_state_store.h` | 使用 `property_observer` 自动追踪变化 | 高 | -| `widget_state_store.cpp` | 重构 `layout_write_context` 使用 `sync_state` | 中 | - ---- - -### 2.2 区域二:Widget 上下文线程安全 (widget_context) - -**当前问题:** -- `widget_context` 持有 `state_store` 和 `text_shaper` 的直接引用 -- 任何线程都可以通过 `get_context()` 访问 -- `viewport_cache` 的线程安全性不明确 - -**重构方案:** - -```cpp -// 当前实现 -class widget_context { - state::widget_state_store& store_; - widget::viewport_cache viewport_cache_; - render::text::text_shaper& text_shaper_; -}; - -// 重构后 -class widget_context_v2 { - // 主线程只读,布局线程写入 - threading::main_to_layout_state viewport_state_; - - // 使用线程绑定确保正确访问 - threading::main_thread_bound viewport_accessor_; - - // 状态存储使用新的线程安全封装 - std::reference_wrapper store_; -}; -``` - -**具体修改:** - -| 文件 | 修改内容 | 优先级 | -|------|----------|--------| -| `widget_context.h` | 添加 `thread_bound` 封装关键资源 | 中 | -| `viewport_cache.h` | 使用 `sync_state` 管理可见性数据 | 中 | -| `viewport_cache.cpp` | 重构可见性更新逻辑 | 中 | - ---- - -### 2.3 区域三:容器控件属性 (containers) - -**当前问题:** -- `spacing_` 等属性使用普通 `float` -- 属性变化不会自动标记脏 -- 链式调用返回 `auto&` 但不触发布局更新 - -**重构方案:** - -```cpp -// 当前实现 (h_stack.h) -class h_stack { - float spacing_ = 0.0f; -public: - auto& spacing(float s) { - spacing_ = s; - return *this; - } -}; - -// 重构后 -class h_stack_v2 { - WIDGET_MEASURE_ATTR(float, spacing) // 使用宏,自动标记 measure_dirty -}; -``` - -**具体修改:** - -| 文件 | 修改内容 | 优先级 | -|------|----------|--------| -| `h_stack.h` | `spacing_` 使用 `WIDGET_MEASURE_ATTR` | 低 | -| `v_stack.h` | `spacing_` 使用 `WIDGET_MEASURE_ATTR` | 低 | -| `overlay.h` | 无需修改(无额外属性)| - | -| `scroll_box.h` | `scroll_speed_`, `spacing_` 使用属性宏 | 低 | - ---- - -### 2.4 区域四:滚动状态线程安全 (scroll_box) - -**当前问题:** -- `scroll_state` 的 `offset_`, `viewport_size_` 等可能被多线程访问 -- `smooth_scroll_animator` 的动画状态在 `tick()` 和 `animation_update()` 中修改 -- 布局线程可能在动画进行时读取滚动偏移 - -**重构方案:** - -```cpp -// 当前实现 -class scroll_state { - vec2f_t offset_{0, 0}; - vec2f_t max_offset_{0, 0}; - vec2f_t viewport_size_{0, 0}; - vec2f_t content_size_{0, 0}; -}; - -// 重构后 -class scroll_state_v2 { - // 主线程写入,布局线程读取 - threading::main_to_layout_state scroll_sync_; - - // 本地状态(仅主线程访问) - threading::main_property offset_{vec2f_t{0, 0}}; - threading::main_property viewport_size_{vec2f_t{0, 0}}; - - // 发布滚动变化事件 - void publish_scroll_change() { - scroll_sync_.modify([this](auto& snap) { - snap.offset = offset_.get(); - snap.viewport_size = viewport_size_.get(); - }); - scroll_sync_.publish(); - } -}; -``` - -**具体修改:** - -| 文件 | 修改内容 | 优先级 | -|------|----------|--------| -| `scroll_state.h` | 使用 `property` 和 `sync_state` | 高 | -| `smooth_scroll_animator.h` | 确保动画状态线程安全 | 中 | -| `scrollbar_manager.h` | 使用 `property` 管理滚动条状态 | 低 | -| `scroll_box.h` | 集成新的滚动状态管理 | 高 | -| `scroll_box.cpp` | 重构滚动逻辑使用 `sync_state` | 高 | - ---- - -### 2.5 区域五:渲染收集器线程安全 (render_collector) - -**当前问题:** -- `render_commands_` 直接存储,无线程保护 -- `mask_stack_`, `clip_stack_` 在构建过程中被修改 -- `viewport_cache_` 指针可能被多线程访问 - -**重构方案:** - -```cpp -// 当前实现 -class render_collector { - std::vector render_commands_; - std::vector mask_stack_; - widget::viewport_cache* viewport_cache_ = nullptr; -}; - -// 重构后 -class render_collector_v2 { - // 命令收集只在布局线程进行 - threading::layout_thread_bound> render_commands_; - threading::layout_thread_bound> mask_stack_; - - // 视口缓存通过 sync_state 同步 - std::shared_ptr viewport_cache_sync_; -}; -``` - -**具体修改:** - -| 文件 | 修改内容 | 优先级 | -|------|----------|--------| -| `render_collector.h` | 添加 `thread_bound` 封装 | 中 | -| `render_collector.cpp` | 确保访问在正确线程 | 中 | - ---- - -### 2.6 区域六:视口缓存同步 (viewport_cache) - -**当前问题:** -- `cache_` 哈希表可能被多线程访问 -- `visible_set_` 在布局过程中更新,渲染过程中读取 -- `invalidate()` 可能从任意线程调用 - -**重构方案:** - -```cpp -// 当前实现 -class viewport_cache { - std::unordered_map cache_; - std::unordered_set visible_set_; - bool valid_ = false; -}; - -// 重构后 -class viewport_cache_v2 { - // 布局线程写入可见性数据 - threading::layout_to_render_state visibility_sync_; - - // 本地缓存(仅布局线程访问) - threading::layout_thread_bound local_cache_; - - // 失效通知主题 - threading::topic invalidate_topic_; -}; -``` - -**具体修改:** - -| 文件 | 修改内容 | 优先级 | -|------|----------|--------| -| `viewport_cache.h` | 引入 `sync_state` 和 `pub_sub` | 中 | -| `viewport_cache.cpp` | 重构为线程安全实现 | 中 | - ---- - -### 2.7 区域七:布局结果发布 (layout_state) - -**当前问题:** -- `layout_state` 按值传递,每次布局都创建新对象 -- 布局结果通过 `widget_state_store` 手动同步 -- 没有布局完成的事件通知机制 - -**重构方案:** - -```cpp -// 重构方案:使用 pub_sub 发布布局完成事件 -struct layout_complete_event { - uint64_t widget_id; - layout_state new_state; - std::optional old_aabb; - std::optional new_aabb; -}; - -// 在 widget_state_store 中添加 -class widget_state_store_v2 { - threading::topic layout_complete_topic_; - - void on_layout_updated(uint64_t widget_id, const layout_state& state) { - // ... 更新状态 ... - layout_complete_topic_.publish({widget_id, state, old_aabb, new_aabb}); - } -}; -``` - -**具体修改:** - -| 文件 | 修改内容 | 优先级 | -|------|----------|--------| -| `widget_state_store.h` | 添加 `layout_complete_topic_` | 低 | -| `widget_state_store.cpp` | 在布局更新时发布事件 | 低 | -| `layout_state.h` | 无需修改 | - | - ---- - -## 3. 重构执行计划 - -### 3.1 阶段一:核心状态管理重构(高优先级) - -```mermaid -graph TD - A[阶段一开始] --> B[重构 widget_state.h] - B --> C[重构 widget_state_store.h/cpp] - C --> D[重构 scroll_state.h] - D --> E[重构 scroll_box.h/cpp] - E --> F[阶段一完成] -``` - -**预计工作量:** 3-5 天 - -**详细任务:** - -1. **重构 `widget_state.h`** (1天) - - 将 `dirty_state` 枚举与 `threading::dirty_flag` 对齐 - - `primary_state` 使用 `property` 包装 `renderable`, `enabled` - - 添加 `property_observer` 支持 - -2. **重构 `widget_state_store.h/cpp`** (1-2天) - - `layout_write_context` 使用更安全的 RAII 模式 - - 添加基于 `pub_sub` 的状态变化通知 - - 优化 `get_dirty_widgets()` 性能 - -3. **重构 `scroll_state.h`** (0.5天) - - `offset_`, `viewport_size_` 等使用 `property` - - 添加 `sync_state` 用于跨线程同步 - -4. **重构 `scroll_box.h/cpp`** (0.5-1天) - - 集成新的 `scroll_state` - - 确保动画更新和布局访问的线程安全 - ---- - -### 3.2 阶段二:上下文和收集器重构(中优先级) - -```mermaid -graph TD - A[阶段二开始] --> B[重构 widget_context.h] - B --> C[重构 viewport_cache.h/cpp] - C --> D[重构 render_collector.h/cpp] - D --> E[阶段二完成] -``` - -**预计工作量:** 2-3 天 - -**详细任务:** - -1. **重构 `widget_context.h`** (0.5天) - - 添加线程安全访问器 - - 使用 `thread_bound` 封装敏感资源 - -2. **重构 `viewport_cache.h/cpp`** (1天) - - 使用 `sync_state` 同步可见性数据 - - 添加 `pub_sub` 用于失效通知 - -3. **重构 `render_collector.h/cpp`** (0.5-1天) - - 使用 `thread_bound` 确保正确线程访问 - - 优化命令收集的线程安全性 - ---- - -### 3.3 阶段三:容器控件属性迁移(低优先级) - -```mermaid -graph TD - A[阶段三开始] --> B[重构 h_stack.h] - B --> C[重构 v_stack.h] - C --> D[重构其他容器] - D --> E[阶段三完成] -``` - -**预计工作量:** 1-2 天 - -**详细任务:** - -1. **重构容器属性** (1天) - - `h_stack::spacing_` 使用 `WIDGET_MEASURE_ATTR` - - `v_stack::spacing_` 使用 `WIDGET_MEASURE_ATTR` - - `scroll_box::scroll_speed_` 使用 `WIDGET_ATTR` - -2. **测试和验证** (0.5-1天) - - 确保属性变化正确触发重布局 - - 验证链式调用仍然有效 - ---- - -### 3.4 阶段四:事件系统集成(低优先级) - -```mermaid -graph TD - A[阶段四开始] --> B[添加布局完成事件] - B --> C[添加渲染完成事件] - C --> D[集成到 thread_coordinator] - D --> E[阶段四完成] -``` - -**预计工作量:** 1 天 - -**详细任务:** - -1. **添加事件发布** (0.5天) - - `widget_state_store` 添加布局完成主题 - - 定义标准事件类型 - -2. **集成到协调器** (0.5天) - - `thread_coordinator` 订阅布局完成事件 - - 触发渲染更新 - ---- - -## 4. 风险和注意事项 - -### 4.1 兼容性风险 - -| 风险 | 影响 | 缓解措施 | -|------|------|----------| -| API 变化 | 现有代码需要修改 | 保留旧 API 作为废弃接口 | -| 性能回归 | 额外的同步开销 | 使用 Release 模式验证零开销 | -| 线程死锁 | 锁顺序不一致 | 使用无锁数据结构 | - -### 4.2 测试策略 - -1. **单元测试** - - 每个重构的类添加线程安全测试 - - 使用 ThreadSanitizer 检测数据竞争 - -2. **集成测试** - - 多线程布局/渲染压力测试 - - scroll_box 滚动动画测试 - -3. **性能测试** - - 对比重构前后的帧率 - - 测量 `sync_state::publish()` 开销 - -### 4.3 回滚计划 - -- 每个阶段完成后创建 Git 标签 -- 保留旧实现在 `_legacy` 后缀文件中 -- 提供编译时开关切换新旧实现 - ---- - -## 5. 依赖关系图 - -```mermaid -graph TD - subgraph 核心原语层 - A[thread_tags.h] - B[thread_context.h] - C[thread_bound.h] - end - - subgraph 属性系统层 - D[property.h] - E[computed_property.h] - F[property_observer.h] - end - - subgraph 状态同步层 - G[sync_state.h] - H[pub_sub.h] - I[thread_dispatcher.h] - end - - subgraph Widget 层 - J[widget_state.h] - K[widget_state_store.h] - L[widget_context.h] - M[widget_base.h] - N[scroll_state.h] - O[viewport_cache.h] - end - - A --> B - B --> C - B --> D - D --> E - D --> F - B --> G - G --> H - H --> I - - D --> J - J --> K - K --> L - L --> M - G --> N - G --> O - H --> K -``` - ---- - -## 6. 总结 - -### 6.1 重构优先级排序 - -1. **高优先级**:`widget_state`, `widget_state_store`, `scroll_state`, `scroll_box` -2. **中优先级**:`widget_context`, `viewport_cache`, `render_collector` -3. **低优先级**:容器控件属性迁移、事件系统集成 - -### 6.2 预计总工作量 - -| 阶段 | 工作量 | 累计 | -|------|--------|------| -| 阶段一 | 3-5 天 | 3-5 天 | -| 阶段二 | 2-3 天 | 5-8 天 | -| 阶段三 | 1-2 天 | 6-10 天 | -| 阶段四 | 1 天 | 7-11 天 | - -### 6.3 成功指标 - -- [ ] 所有 Widget 属性使用 `property` 管理 -- [ ] 跨线程数据访问使用 `sync_state` 或 `thread_bound` -- [ ] 状态变化通知使用 `pub_sub` 机制 -- [ ] 零数据竞争(ThreadSanitizer 通过) -- [ ] 性能无明显回归(<5% 帧率下降) \ No newline at end of file diff --git a/src/render/atlas/atlas_gpu_resource.cpp b/src/render/atlas/atlas_gpu_resource.cpp index cefc01a..f428e41 100644 --- a/src/render/atlas/atlas_gpu_resource.cpp +++ b/src/render/atlas/atlas_gpu_resource.cpp @@ -49,7 +49,7 @@ atlas_gpu_page::atlas_gpu_page(mirage::resource_manager& res_mgr, mirage::logica allocation_ = image_result->allocation; image_view_ = image_result->view; - // 创建采样器(线性过滤,边缘夹取) + // 创建采样器(线性过滤,边缘夹取,启用各向异性过滤以改善文字边缘质量) auto device_handle = device_->get_handle(); vk::SamplerCreateInfo sampler_info{}; sampler_info.magFilter = vk::Filter::eLinear; @@ -57,8 +57,10 @@ atlas_gpu_page::atlas_gpu_page(mirage::resource_manager& res_mgr, mirage::logica sampler_info.addressModeU = vk::SamplerAddressMode::eClampToEdge; sampler_info.addressModeV = vk::SamplerAddressMode::eClampToEdge; sampler_info.addressModeW = vk::SamplerAddressMode::eClampToEdge; - sampler_info.anisotropyEnable = VK_FALSE; - sampler_info.maxAnisotropy = 1.0f; + // 启用各向异性过滤以改善斜向边缘的渲染质量 + // 对于MTSDF文字渲染,适度的各向异性可以提升边缘清晰度 + sampler_info.anisotropyEnable = VK_TRUE; + sampler_info.maxAnisotropy = 4.0f; // 适度的各向异性,平衡质量与性能 sampler_info.borderColor = vk::BorderColor::eFloatTransparentBlack; sampler_info.unnormalizedCoordinates = VK_FALSE; sampler_info.compareEnable = VK_FALSE; diff --git a/src/render/shaders/widget/mtsdf_text.frag.glsl b/src/render/shaders/widget/mtsdf_text.frag.glsl index 6f2fd92..b6254e3 100644 --- a/src/render/shaders/widget/mtsdf_text.frag.glsl +++ b/src/render/shaders/widget/mtsdf_text.frag.glsl @@ -24,6 +24,7 @@ layout(location = 2) in vec2 frag_screen_pos; ///< 屏幕空间位置(预留 layout(location = 3) in vec2 frag_glyph_uv_size; ///< 字形在图集中的UV尺寸 layout(location = 4) in float frag_glyph_size; ///< 字形纹理大小(像素) layout(location = 5) in flat int frag_page_index; ///< 图集页面索引(flat插值) +layout(location = 6) in float frag_pixel_range; ///< SDF像素范围(动态传递) // ============================================================================ // 输出 @@ -48,20 +49,21 @@ layout(set = 0, binding = 0) uniform sampler2D u_atlas[16]; // ============================================================================ /** - * @brief MTSDF生成时使用的像素范围 + * @brief 默认像素范围(仅在 frag_pixel_range <= 0 时使用) * - * 这个值必须与 mtsdf_generator.cpp 中的 pixel_range 参数一致。 - * 默认值为6.0,表示距离场覆盖字形边界周围6个像素的范围。 + * 正常情况下,pixel_range 通过顶点属性动态传递。 + * 此值作为后备默认值。 */ -const float PIXEL_RANGE = 6.0; +const float DEFAULT_PIXEL_RANGE = 6.0; /** - * @brief 字形纹理大小(已弃用 - 现在通过顶点属性动态传递) + * @brief 获取有效的像素范围 * - * 此常量已被 frag_glyph_size 输入变量替代,该值从顶点着色器传递, - * 确保与实际生成的字形纹理大小一致。 + * 优先使用动态传递的 frag_pixel_range,如果无效则使用默认值。 */ -// const float GLYPH_TEXTURE_SIZE = 64.0; // 已弃用,使用 frag_glyph_size +float get_pixel_range() { + return frag_pixel_range > 0.0 ? frag_pixel_range : DEFAULT_PIXEL_RANGE; +} // ============================================================================ // MTSDF核心算法 @@ -89,20 +91,51 @@ float median(float r, float g, float b) { * 这用于自适应抗锯齿,确保在不同缩放级别下都能获得清晰的边缘。 * * 使用字形的实际UV尺寸来计算unit_range: - * unit_range = PIXEL_RANGE * glyph_uv_size / GLYPH_TEXTURE_SIZE + * unit_range = pixel_range * glyph_uv_size / glyph_texture_size * * @return 屏幕像素范围(用于距离场到屏幕像素的转换) */ float compute_screen_px_range() { + float pixel_range = get_pixel_range(); + // 计算UV坐标在屏幕空间的变化率 // fwidth(uv) 返回 |dFdx(uv)| + |dFdy(uv)|,是两个方向导数之和 // 因此需要 * 0.5 来获得平均导数值 // 使用字形的实际UV尺寸和动态传递的字形纹理大小 - vec2 unit_range = vec2(PIXEL_RANGE) * frag_glyph_uv_size / frag_glyph_size; + vec2 unit_range = vec2(pixel_range) * frag_glyph_uv_size / frag_glyph_size; vec2 screen_tex_size = vec2(1.0) / fwidth(frag_uv); return max(0.5 * dot(unit_range, screen_tex_size), 1.0); } +/** + * @brief 计算自适应抗锯齿宽度 + * + * 根据屏幕像素范围计算最优的AA过渡宽度。 + * 对于高DPI显示器(screen_px_range 较大),使用更窄的过渡带以获得更锐利的边缘。 + * 对于低DPI或字体缩小的情况,使用稍宽的过渡带以保持平滑。 + * + * @param screen_px_range 屏幕像素范围 + * @return 抗锯齿过渡宽度(像素) + */ +float compute_aa_width(float screen_px_range) { + float pixel_range = get_pixel_range(); + + // 基础AA宽度:当screen_px_range等于pixel_range时,使用0.7像素宽度(较锐利) + // 当screen_px_range较小(缩小显示)时,适当增加AA宽度以避免锯齿 + // 当screen_px_range较大(放大显示)时,保持较窄的AA宽度以保持锐利 + + // 计算缩放因子:screen_px_range / pixel_range + // > 1.0 表示放大(需要更锐利) + // < 1.0 表示缩小(需要更平滑) + float scale_factor = screen_px_range / pixel_range; + + // AA宽度范围:0.5(最锐利)到 1.2(最平滑) + // 使用平滑插值,避免突变 + float aa_width = mix(1.2, 0.5, clamp(scale_factor, 0.5, 2.0) / 2.0); + + return aa_width; +} + // ============================================================================ // 主函数 // ============================================================================ @@ -126,10 +159,10 @@ void main() { // * screen_px_range: 转换为屏幕像素单位 float screen_px_distance = screen_px_range * (sd - 0.5); - // 使用更窄的抗锯齿过渡带(0.7像素而不是1像素)以获得更锐利的边缘 - // 边界在 screen_px_distance = 0,alpha = 0.5 - const float AA_WIDTH = 1; - float alpha = clamp(screen_px_distance / AA_WIDTH + 0.5, 0.0, 1.0); + // 使用自适应抗锯齿宽度,根据屏幕像素密度动态调整 + // 高DPI下使用更窄的过渡带以获得更锐利的边缘 + float aa_width = compute_aa_width(screen_px_range); + float alpha = clamp(screen_px_distance / aa_width + 0.5, 0.0, 1.0); // 输出最终颜色(保留文本颜色的RGB,alpha与距离场混合) out_color = vec4(frag_color.rgb, frag_color.a * alpha); diff --git a/src/render/shaders/widget/mtsdf_text.vert.glsl b/src/render/shaders/widget/mtsdf_text.vert.glsl index 36780bf..9f37d37 100644 --- a/src/render/shaders/widget/mtsdf_text.vert.glsl +++ b/src/render/shaders/widget/mtsdf_text.vert.glsl @@ -47,6 +47,7 @@ layout(location = 2) out vec2 frag_screen_pos; ///< 屏幕空间位置(用于 layout(location = 3) out vec2 frag_glyph_uv_size; ///< 字形在图集中的UV尺寸 layout(location = 4) out float frag_glyph_size; ///< 字形纹理大小(像素) layout(location = 5) out flat int frag_page_index; ///< 图集页面索引(flat插值) +layout(location = 6) out float frag_pixel_range; ///< SDF像素范围 // ============================================================================ // 主函数 @@ -74,4 +75,8 @@ void main() { // 传递图集页面索引(存储在 data_b 的 x 分量中) frag_page_index = int(in_data_b.x); + + // 传递像素范围(存储在 data_a 的 w 分量中) + // 用于片段着色器中的距离场计算和自适应抗锯齿 + frag_pixel_range = in_data_a.w; } \ No newline at end of file diff --git a/src/render/shaders/widget/mtsdf_text_outline.frag.glsl b/src/render/shaders/widget/mtsdf_text_outline.frag.glsl index d97633f..8fae89b 100644 --- a/src/render/shaders/widget/mtsdf_text_outline.frag.glsl +++ b/src/render/shaders/widget/mtsdf_text_outline.frag.glsl @@ -18,6 +18,7 @@ layout(location = 2) in vec2 frag_screen_pos; ///< 屏幕空间位置(预留 layout(location = 3) in vec2 frag_glyph_uv_size; ///< 字形在图集中的UV尺寸 layout(location = 4) in float frag_glyph_size; ///< 字形纹理大小(像素) layout(location = 5) in flat int frag_page_index; ///< 图集页面索引(flat插值) +layout(location = 6) in float frag_pixel_range; ///< SDF像素范围(动态传递) // ============================================================================ // 输出 @@ -50,16 +51,16 @@ layout(set = 1, binding = 0) uniform CustomParams { // ============================================================================ /** - * @brief MTSDF生成时使用的像素范围 - * 必须与 mtsdf_generator.cpp 中的 pixel_range 参数一致 + * @brief 默认像素范围(仅在 frag_pixel_range <= 0 时使用) */ -const float PIXEL_RANGE = 6.0; +const float DEFAULT_PIXEL_RANGE = 6.0; /** - * @brief 字形纹理大小(已弃用 - 现在通过顶点属性动态传递) - * 此常量已被 frag_glyph_size 输入变量替代 + * @brief 获取有效的像素范围 */ -// const float GLYPH_TEXTURE_SIZE = 64.0; // 已弃用,使用 frag_glyph_size +float get_pixel_range() { + return frag_pixel_range > 0.0 ? frag_pixel_range : DEFAULT_PIXEL_RANGE; +} // ============================================================================ // MTSDF核心算法 @@ -77,26 +78,32 @@ float median(float r, float g, float b) { // ============================================================================ void main() { + float pixel_range = get_pixel_range(); + // 采样MTSDF纹理 vec4 mtsdf = texture(u_atlas, frag_uv); float sd = median(mtsdf.r, mtsdf.g, mtsdf.b); // 计算屏幕空间像素范围(用于抗锯齿) // 使用字形的实际UV尺寸和动态传递的字形纹理大小 - vec2 unit_range = vec2(PIXEL_RANGE) * frag_glyph_uv_size / frag_glyph_size; + vec2 unit_range = vec2(pixel_range) * frag_glyph_uv_size / frag_glyph_size; vec2 screen_tex_size = vec2(1.0) / fwidth(frag_uv); float screen_px_range = max(0.5 * dot(unit_range, screen_tex_size), 1.0); + // 计算自适应AA宽度 + float scale_factor = screen_px_range / pixel_range; + float aa_width = mix(1.2, 0.5, clamp(scale_factor, 0.5, 2.0) / 2.0); + // 计算内部文字的alpha // 标准阈值为0.5,距离场值大于0.5的为字形内部 float inner_distance = (sd - 0.5) * screen_px_range; - float inner_alpha = clamp(inner_distance + 0.5, 0.0, 1.0); + float inner_alpha = clamp(inner_distance / aa_width + 0.5, 0.0, 1.0); // 计算描边的alpha // 通过向外扩展阈值来生成描边区域 // outline_width为正值时向外扩展,负值时向内收缩 float outline_distance = (sd - 0.5 + params.outline_width) * screen_px_range; - float outline_alpha = clamp(outline_distance + 0.5, 0.0, 1.0); + float outline_alpha = clamp(outline_distance / aa_width + 0.5, 0.0, 1.0); // 混合文字和描边 // 先绘制描边(外层),再绘制文字(内层) diff --git a/src/render/shaders/widget/mtsdf_text_shadow.frag.glsl b/src/render/shaders/widget/mtsdf_text_shadow.frag.glsl index 9cbd332..c4934c8 100644 --- a/src/render/shaders/widget/mtsdf_text_shadow.frag.glsl +++ b/src/render/shaders/widget/mtsdf_text_shadow.frag.glsl @@ -18,6 +18,7 @@ layout(location = 2) in vec2 frag_screen_pos; ///< 屏幕空间位置(预留 layout(location = 3) in vec2 frag_glyph_uv_size; ///< 字形在图集中的UV尺寸 layout(location = 4) in float frag_glyph_size; ///< 字形纹理大小(像素) layout(location = 5) in flat int frag_page_index; ///< 图集页面索引(flat插值) +layout(location = 6) in float frag_pixel_range; ///< SDF像素范围(动态传递) // ============================================================================ // 输出 @@ -50,16 +51,16 @@ layout(set = 1, binding = 0) uniform CustomParams { // ============================================================================ /** - * @brief MTSDF生成时使用的像素范围 - * 必须与 mtsdf_generator.cpp 中的 pixel_range 参数一致 + * @brief 默认像素范围(仅在 frag_pixel_range <= 0 时使用) */ -const float PIXEL_RANGE = 6.0; +const float DEFAULT_PIXEL_RANGE = 6.0; /** - * @brief 字形纹理大小(已弃用 - 现在通过顶点属性动态传递) - * 此常量已被 frag_glyph_size 输入变量替代 + * @brief 获取有效的像素范围 */ -// const float GLYPH_TEXTURE_SIZE = 64.0; // 已弃用,使用 frag_glyph_size +float get_pixel_range() { + return frag_pixel_range > 0.0 ? frag_pixel_range : DEFAULT_PIXEL_RANGE; +} // ============================================================================ // MTSDF核心算法 @@ -77,6 +78,8 @@ float median(float r, float g, float b) { // ============================================================================ void main() { + float pixel_range = get_pixel_range(); + // 采样文字位置的MTSDF vec4 mtsdf = texture(u_atlas, frag_uv); float sd = median(mtsdf.r, mtsdf.g, mtsdf.b); @@ -87,20 +90,24 @@ void main() { // 计算屏幕空间像素范围(用于抗锯齿) // 使用字形的实际UV尺寸和动态传递的字形纹理大小 - vec2 unit_range = vec2(PIXEL_RANGE) * frag_glyph_uv_size / frag_glyph_size; + vec2 unit_range = vec2(pixel_range) * frag_glyph_uv_size / frag_glyph_size; vec2 screen_tex_size = vec2(1.0) / fwidth(frag_uv); float screen_px_range = max(0.5 * dot(unit_range, screen_tex_size), 1.0); + // 计算自适应AA宽度 + float scale_factor = screen_px_range / pixel_range; + float aa_width = mix(1.2, 0.5, clamp(scale_factor, 0.5, 2.0) / 2.0); + // 计算文字的alpha // 标准阈值为0.5 float text_distance = (sd - 0.5) * screen_px_range; - float text_alpha = clamp(text_distance + 0.5, 0.0, 1.0); + float text_alpha = clamp(text_distance / aa_width + 0.5, 0.0, 1.0); // 计算阴影的alpha // 通过shadow_softness调整阈值,可以控制阴影的柔和程度 // softness越大,阴影越柔和(边缘越模糊) float shadow_distance = (shadow_sd - 0.5 - params.shadow_softness) * screen_px_range; - float shadow_alpha = clamp(shadow_distance + 0.5, 0.0, 1.0); + float shadow_alpha = clamp(shadow_distance / aa_width + 0.5, 0.0, 1.0); // 阴影只在文字不存在的地方显示 // 使用(1.0 - text_alpha)确保阴影不会覆盖文字本身 diff --git a/src/render/text/glyph_cache.cpp b/src/render/text/glyph_cache.cpp index aabd0ca..c8e92b2 100644 --- a/src/render/text/glyph_cache.cpp +++ b/src/render/text/glyph_cache.cpp @@ -213,11 +213,12 @@ auto glyph_cache::get_or_request_glyph( // 【关键】先建立映射再提交任务,避免竞争条件 // 生成任务ID(需要先预留) + // 使用配置的 pixel_range 比例计算实际值 glyph_generation_request request{ .font_path = font_path, .glyph_index = glyph_index, .glyph_size = glyph_size_, - .pixel_range = 6.0 // 默认像素范围 + .pixel_range = static_cast(glyph_config::calculate_pixel_range(glyph_size_)) }; glyph_task_id task_id = async_gen_->submit(request); @@ -296,7 +297,7 @@ auto glyph_cache::process_completed_glyphs() -> bool { } if (result.success) { - // 提取度量信息 + // 提取度量信息,包含pixel_range用于着色器计算 glyph_metrics metrics{ .bearing = glm::vec2(result.bitmap.bearing_x, result.bitmap.bearing_y), .advance = result.bitmap.advance_x, @@ -304,7 +305,8 @@ auto glyph_cache::process_completed_glyphs() -> bool { .inner_u0 = result.bitmap.inner_u0, .inner_v0 = result.bitmap.inner_v0, .inner_u1 = result.bitmap.inner_u1, - .inner_v1 = result.bitmap.inner_v1 + .inner_v1 = result.bitmap.inner_v1, + .pixel_range = glyph_config::calculate_pixel_range(glyph_size_) }; // 【关键修复】先更新 metrics,再分配图集,最后删除任务映射 @@ -361,15 +363,18 @@ auto glyph_cache::generate_and_add_glyph( return std::nullopt; // 加载失败 } - // 生成MTSDF位图 - auto bitmap_opt = mtsdf_generator::generate_mtsdf(glyph_slot, glyph_size_); + // 计算当前配置的 pixel_range + const double pixel_range = glyph_config::calculate_pixel_range(glyph_size_); + + // 生成MTSDF位图,使用配置的 pixel_range + auto bitmap_opt = mtsdf_generator::generate_mtsdf(glyph_slot, glyph_size_, pixel_range); if (!bitmap_opt.has_value()) { return std::nullopt; // 生成MTSDF失败 } const auto& bitmap = bitmap_opt.value(); - // 提取度量信息 + // 提取度量信息,包含pixel_range用于着色器计算 glyph_metrics metrics{ .bearing = glm::vec2(bitmap.bearing_x, bitmap.bearing_y), .advance = bitmap.advance_x, @@ -377,7 +382,8 @@ auto glyph_cache::generate_and_add_glyph( .inner_u0 = bitmap.inner_u0, .inner_v0 = bitmap.inner_v0, .inner_u1 = bitmap.inner_u1, - .inner_v1 = bitmap.inner_v1 + .inner_v1 = bitmap.inner_v1, + .pixel_range = static_cast(pixel_range) }; // 生成用户键 - 使用codepoint而不是glyph_index,以保持与查找时的键一致 diff --git a/src/render/text/glyph_cache.h b/src/render/text/glyph_cache.h index 7033005..5e78df6 100644 --- a/src/render/text/glyph_cache.h +++ b/src/render/text/glyph_cache.h @@ -14,6 +14,24 @@ namespace mirage::render::text { +/** + * @brief 字形渲染配置常量 + */ +namespace glyph_config { + /// 默认字形纹理大小(像素) + /// 96px 提供比 64px 更高的精度,适合高DPI显示 + constexpr uint32_t default_glyph_size = 96; + + /// pixel_range 占纹理大小的比例 + /// 这个比例确保距离场有足够的精度同时不浪费空间 + constexpr float pixel_range_ratio = 0.0625f; // 1/16 + + /// 计算给定纹理大小的 pixel_range + constexpr float calculate_pixel_range(uint32_t glyph_size) { + return static_cast(glyph_size) * pixel_range_ratio; + } +} + /** * @brief 字形度量信息 * @@ -31,6 +49,9 @@ struct glyph_metrics { float inner_v0 = 0.0f; ///< 字形在纹理中的下边界(归一化) float inner_u1 = 1.0f; ///< 字形在纹理中的右边界(归一化) float inner_v1 = 1.0f; ///< 字形在纹理中的上边界(归一化) + + /// SDF像素范围,用于着色器中的距离场计算 + float pixel_range = 6.0f; }; /** @@ -72,12 +93,12 @@ public: * * @param font_mgr 字体管理器引用 * @param atlas_mgr 纹理图集管理器引用 - * @param glyph_size 生成MTSDF的纹理尺寸(默认64像素) + * @param glyph_size 生成MTSDF的纹理尺寸(默认96像素,提供高DPI支持) */ explicit glyph_cache( font_manager& font_mgr, texture_atlas_manager& atlas_mgr, - uint32_t glyph_size = 64 + uint32_t glyph_size = glyph_config::default_glyph_size ); /** @@ -86,13 +107,13 @@ public: * @param font_mgr 字体管理器引用 * @param atlas_mgr 纹理图集管理器引用 * @param async_gen 异步MTSDF生成器引用 - * @param glyph_size 生成MTSDF的纹理尺寸(默认64像素) + * @param glyph_size 生成MTSDF的纹理尺寸(默认96像素,提供高DPI支持) */ explicit glyph_cache( font_manager& font_mgr, texture_atlas_manager& atlas_mgr, async_mtsdf_generator& async_gen, - uint32_t glyph_size = 64 + uint32_t glyph_size = glyph_config::default_glyph_size ); /** @@ -186,7 +207,14 @@ public: /** * @brief 获取字形纹理大小 */ - auto get_glyph_size() const -> uint32_t { return glyph_size_; } + [[nodiscard]] auto get_glyph_size() const -> uint32_t { return glyph_size_; } + + /** + * @brief 获取当前配置的pixel_range + */ + [[nodiscard]] auto get_pixel_range() const -> float { + return glyph_config::calculate_pixel_range(glyph_size_); + } private: /** diff --git a/src/render/text/text_shaper.cpp b/src/render/text/text_shaper.cpp index dd1f267..b4b1763 100644 --- a/src/render/text/text_shaper.cpp +++ b/src/render/text/text_shaper.cpp @@ -13,15 +13,9 @@ namespace mirage::render::text { namespace { // 编译期常量 - constexpr float BASE_GLYPH_SIZE = 64.0f; constexpr size_t VERTICES_PER_GLYPH = 4; constexpr size_t INDICES_PER_GLYPH = 6; - // 使用 consteval 确保编译期计算 - consteval auto calculate_scale(float font_size) noexcept -> float { - return font_size / BASE_GLYPH_SIZE; - } - // 顶点位置索引 enum class vertex_corner : uint8_t { top_left = 0, @@ -58,8 +52,10 @@ namespace mirage::render::text { }; } - const float scale = font_size / BASE_GLYPH_SIZE; + // 使用 glyph_cache 实际的 glyph_size 计算缩放因子 + // 这确保与字形生成时使用的大小一致 const auto glyph_texture_size = static_cast(cache_.get_glyph_size()); + const float scale = font_size / glyph_texture_size; // 获取字体的固定度量信息(ascender),确保任何文本内容的基线位置一致 // 这样无论输入 "abc" 还是 "ABC",文本的垂直位置都是稳定的 @@ -168,11 +164,17 @@ namespace mirage::render::text { create_vertex(geom.x, geom.y + geom.h, final_u0, final_v1) // 左下 }; - // 修正 data_a 字段(UV 尺寸和纹理大小) + // 修正 data_a 字段: + // - [0] UV 宽度 + // - [1] UV 高度 + // - [2] 字形纹理大小(像素) + // - [3] pixel_range(用于着色器中的距离场计算) // 修正 data_b[0] 字段(图集页面索引,用于多页面支持) for (auto& vertex : vertices) { vertex.data_a[0] = geom.uv_width; vertex.data_a[1] = geom.uv_height; + // data_a[2] 已在 create_vertex 中设置为 glyph_texture_size + vertex.data_a[3] = metrics.pixel_range; // 传递 pixel_range 到着色器 vertex.data_b[0] = static_cast(atlas_entry.page_index); // 传递页面索引 } @@ -216,7 +218,8 @@ namespace mirage::render::text { return text_metrics{.width = 0.0f, .height = 0.0f}; } - const float scale = font_size / BASE_GLYPH_SIZE; + // 使用 glyph_cache 实际的 glyph_size 计算缩放因子 + const float scale = font_size / static_cast(cache_.get_glyph_size()); // 获取字体度量信息(与 shape_text 保持一致) // 使用 ascender - descender 作为高度,确保测量与渲染一致 diff --git a/src/window/desktop/windows/ime_win32.cpp b/src/window/desktop/windows/ime_win32.cpp index c998fd1..e5fb5ed 100644 --- a/src/window/desktop/windows/ime_win32.cpp +++ b/src/window/desktop/windows/ime_win32.cpp @@ -28,13 +28,12 @@ static LRESULT CALLBACK ime_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPAR GetPropA(hwnd, IME_HANDLER_PROP) ); - // 处理 WM_IME_SETCONTEXT 以完全禁用系统IME UI + // 处理 WM_IME_SETCONTEXT if (msg == WM_IME_SETCONTEXT) { if (wparam == TRUE) { - // 移除所有默认的IME UI标志,完全由应用程序接管UI + // 只禁用系统预编辑框(因为应用自己绘制预编辑文本) + // 保留候选框的显示,让系统显示候选窗口 lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW; // 禁用系统预编辑框 - lparam &= ~ISC_SHOWUICANDIDATEWINDOW; // 禁用系统候选框(索引0) - lparam &= ~ISC_SHOWUIALLCANDIDATEWINDOW; // 禁用所有候选框 lparam &= ~ISC_SHOWUIGUIDELINE; // 禁用指导线 }