本文件适用于整个仓库。它是开发者与自动化智能体修改 taskmgr-rs 时的工程约束,不是当前实现的逐行说明。
taskmgr-rs 是一个使用 Rust 和原生 Win32 API 实现的经典 Windows Task Manager。项目追求以下结果:
- 保持经典任务管理器的视觉、交互和菜单结构。
- 对进程、窗口、会话、网络和系统计数给出可信结果。
- UI 线程始终可响应,首次打开页面与持续刷新都不应出现明显停顿。
- Win32、GDI、COM、Direct2D、线程和菜单资源具有清晰且可验证的所有权。
- 失败必须可观察、可定位,不以静默 fallback 掩盖错误。
- 优化必须解决真实复杂度、热点或资源问题,不为了“优化”而增加抽象。
正确性、安全边界和可维护性优先于局部代码短小;有测量依据的算法与数据结构改进优先于微优化。
- 必须:违反后可能导致错误对象操作、数据污染、资源泄漏、UI 状态损坏或不可维护行为。
- 应当:默认遵循;偏离时需要在代码或变更说明中给出具体理由。
- 可以:在需求、测量或现有结构支持时采用,不作为统一重构的理由。
- 目标平台是 Windows,目标工具链由
rust-toolchain.toml决定。 - 使用当前仓库支持的最新稳定 Rust、Cargo 和依赖版本;升级必须更新
Cargo.lock并通过完整验证。 - 程序式 Win32 调用优先使用
windows-sys;COM 和 Direct2D 继续使用windows,不要无理由混用绑定风格。 - 优先使用 Windows 官方 API、系统组件和系统格式化能力。只有外部库能显著提高核心领域正确性或减少已证实复杂度时才增加依赖。
- 新依赖必须说明用途、生命周期、许可证影响和为什么标准库或现有依赖不足。
- 不为旧系统偷偷加入替代实现。若产品需要兼容旧 Windows,应先明确最低版本与测试矩阵,再设计受支持的实现路径。
文件头用于标识手写源码的模块身份和本次创建或结构性重构的维护主体。本仓库不照搬 Web 项目的文件类型,而按 Rust/Win32 工程采用以下规则。
新建的 src/**/*.rs、tests/**/*.rs、build.rs、scripts/*.ps1,以及未来新增的手写源码和测试脚本,必须使用与文件语言一致的行注释头。Rust 文件使用以下格式:
// +-------------------------------------------------------------------------
//
// taskmgr-rs - 模块中文名称
//
// 文件: src/file_name.rs
//
// 日期: YYYY年MM月DD日
// 环境: OS 版本/架构;Linux 内核版本(如适用);Rust 版本;目标工具链;运行兼容层(如适用)
// 作者: Author Name
// --------------------------------------------------------------------------PowerShell 脚本使用同样字段和布局,但把 // 换成 #。其它未来语言使用其原生行注释前缀。
- 模块名称必须描述职责,例如“进程身份与句柄验证”,不能只重复文件名。
- 文件字段使用仓库相对路径,避免同名文件产生歧义。
- 日期记录文件创建、重命名或模块职责发生结构性重构的日期,不因普通缺陷修复或局部优化反复更新。
- 环境字段简洁记录创建或结构性重构该文件时实际使用的开发环境与目标工具链。Windows 环境必须记录具体 Windows 版本和构建号;Linux 环境必须记录实际内核版本;如在非原生运行环境完成关键验证,则同时记录验证环境。所有版本必须从当前环境读取,不能使用推测值。例如:
Windows <实测版本>(Build <实测构建号>)x86_64;Rust <实测版本>;MSVC <实测版本>。 - 环境字段与日期字段遵循相同的更新边界:普通局部修改不更新,只有新建、重命名或模块职责发生结构性重构时才更新。
- 作者填写实际创建或执行本次结构性重构的主体。Codex 创建或实质重构时填写
OpenAI Codex,不得借用用户或历史作者姓名。 - Rust 文件在横幅后继续使用
//!模块文档,说明职责、关键不变量、线程模型或资源所有权。横幅不能替代可维护的模块说明、类型约束和测试。 tests/下的独立测试文件需要文件头;源码内部的#[cfg(test)] mod tests不重复添加。- 重命名文件或根本改变模块职责时必须更新模块名称、相对路径、日期、环境和作者。
- 对没有文件头的既有文件做普通局部修改时,不得只为补头制造无关 diff。只有新建、重命名或真正改变模块边界时才补充或更新。
Cargo.toml、Cargo.lock、本地化 TOML、XML manifest、Visual Studio 工程、图片、图标及构建生成文件遵从各自原生格式,不添加这类源码横幅。
当前模块按所有权和线程边界组织:
| 模块 | 主要职责 |
|---|---|
src/app/mod.rs |
组合根、主窗口消息循环、启动与有序关闭、页面协调 |
src/app/controllers.rs |
托盘、运行时统计、菜单状态和窗口模式等长期控制器 |
src/app/page_host.rs, src/app/page_registry.rs |
公共页面对话框宿主,以及稳定 PageId、页面顺序和描述表 |
src/infrastructure/worker.rs |
有界 single-flight worker、请求合并、完成排空和有序关闭 |
src/infrastructure/native/* |
句柄 RAII、错误域、安全检查和通用 Win32 UI 边界 |
src/system/* |
系统级 CPU/内存采样、group-aware 拓扑和稳定进程身份 |
src/pages/applications/* |
应用程序窗口列表、窗口采样和固定图标线程池 |
src/pages/processes/* |
虚拟进程列表、采样模型,以及经 ProcIdentity 验证的危险操作 |
src/pages/performance/* |
经典性能页状态、布局和图表绘制 |
src/pages/cpu/* |
CPU 页面、RelationAll 原生拓扑、PDH 动态值和 WMI 固件来源 |
src/pages/gpu/* |
多 GPU 页面、DXGI inventory、PDH counter 和适配器 metadata |
src/pages/network.rs, src/pages/users.rs |
网卡快照与会话快照;职责尚单一时不继续细拆 |
src/ui/* |
图表、绘制、对话框、菜单、资源 ID 和本地化 |
src/config/options.rs |
注册表二进制配置、校验、迁移、规范化和保存 |
build.rs, build_support/* |
资源清单、PNG 到临时 ICO 管线、manifest 和本地化代码生成 |
依赖方向应当从组合层指向页面层,再指向基础设施层。基础模块不得反向依赖具体页面或 App。
新增或拆分模块必须至少满足一个条件:
- 拥有独立资源或线程生命周期。
- 被多个页面复用,并承载一致性或安全不变量。
- 当前文件包含两个可独立测试、独立演进的职责。
- 拆分能消除实际依赖环、反向依赖或显著认知负担。
不要按“一个 struct 一个文件”拆分。只被单一页面使用的小型状态、列定义和局部算法通常应留在页面模块内。
- UI 线程只负责消息分发、轻量状态提交、布局和绘制。
- 可能阻塞的进程、WTS、IP Helper、token、图标或系统采样必须在持久后台 worker 中执行。
- 不要为每个 tick 或每批图标创建线程。使用
SingleFlightWorker或职责明确的长期有界线程池。 SingleFlightWorker的请求和完成通道容量均为 1。UI 侧只保留一个 pending 请求,并通过构造时提供的合并函数形成一次后续采样。- CPU/GPU 的来源状态、topology key、generation 和乱序结果校验留在功能模块,基础 worker 不猜测业务语义。
- worker 必须产出候选快照。只有整轮必要查询成功后,UI 才原子提交它。
- 全局采样失败时保留上一轮可信快照,记录明确错误;不得清空列表,也不得在 UI 线程同步重采样。
- 单行附加信息失败时,保留其他有效行并通过结构化
row_error报告;不得因一个不可访问对象丢弃整页。 - 旧快照、乱序完成和页面销毁后的完成通知不得重新污染当前状态。
- worker 停止必须有序,不能留下可访问已销毁 HWND 或页面状态的线程。
页面首次切换不得同步执行重采样、菜单重建或大规模资源初始化。可预热的页面应异步预热;页面拥有的菜单应缓存并在销毁时释放。
Windows 会复用 PID、窗口句柄和会话 ID。跨越一次枚举保存的对象必须带足以重新验证的身份信息。
- 进程身份必须使用
ProcIdentity { pid, creation_time_100ns }。 - CPU delta、缓存键、选中项和“转到进程”不得只以 PID 标识进程。
- 结束进程、结束树、修改优先级、修改亲和性、附加调试器和打开文件位置前,必须重新校验创建时间。
- 需要进程句柄时使用
open_process_for_identity,并请求完成操作所需的最小访问权限。 - 不得用 PID-only fallback 绕过身份校验。无法验证时,该操作应明确失败。
- 窗口操作必须重新验证 HWND、线程 ID 和进程身份;仅
IsWindow不足以证明仍是同一任务。 - 用户会话操作必须重新验证会话身份;无法证明身份连续性时不得对该会话执行破坏性或冒充性操作。
- 结束进程树应先验证并打开全部目标,再开始终止。执行阶段仍可能部分失败,必须如实报告,不能声称事务已经回滚。
- 不允许静默同步 fallback、伪造成功、返回陈旧值却标成新值,或用空列表代表采样失败。
- 合理的降级必须是产品语义的一部分,并在类型、状态名或 UI 中可见。例如“上一轮可信快照 + 当前错误状态”。
- Win32 API 失败后,应在任何可能改变线程 last-error 的清理调用之前立即读取
GetLastError。 - 只有文档说明 API 通过 last-error 报错时才读取它。零错误码需要转换为明确的通用失败码。
HRESULT、NTSTATUS和 Win32 error code 必须保持各自错误域,必要时使用官方转换函数。- 不使用宽泛的
Err(_)丢失诊断信息,除非该错误仅表示通道已关闭且关闭本身就是控制流。 - 后台重复错误应去重记录,避免每个 tick 刷屏;用户主动操作失败应给出可理解的即时反馈。
- 不添加无限重试、隐式休眠、魔法阈值或“试另一个 API 看看”的路径来掩盖根因。
- 配置损坏可以确定性规范化为合法配置,但规则必须集中、可测试,并在之后保存规范化结果。
每个原生资源在创建点就必须明确“拥有”还是“借用”。拥有的资源优先封装成 RAII 类型。
HANDLE使用OwnedHandle或等价 RAII;不要把裸句柄释放责任扩散给多个调用者。HICON、HBITMAP、HDC、HMENU、HACCEL、ImageList、region 和 COM 对象必须有唯一所有者。GetDC/GetDCEx对应ReleaseDC,CreateCompatibleDC对应DeleteDC。- 自建 GDI 对象用
DeleteObject;系统 stock object 和借用对象不得删除。 - 删除被选入 DC 的 bitmap 前,必须先选回原对象。
BeginDraw/EndDraw必须由 guard 保证配对,包括提前返回和错误路径。- ImageList 复制图标后,未进入 ImageList 的 owned
HICON必须销毁;窗口或类返回的借用图标不得直接销毁。 - 附加给窗口的页面菜单由页面拥有。主窗口切页时只借用它,页面销毁前必须先从主窗口解绑。
- accelerator table、主窗口图标、托盘图标和通知区域注册必须在退出路径成对清理。
- 新资源应先在局部变量中完整创建和验证,再原子替换旧资源。不要先销毁旧对象后尝试创建新对象。
- 部分初始化失败必须自动清理已经创建的资源。
检查 API 文档中的所有权转移规则。例如某些 GetDCEx flags 会把 region 所有权交给系统,不能按普通本地 region 再删除。
unsafe范围应尽量小,优先提供具有 Rust 不变量的安全封装。- 每个非显然的
unsafe块应说明指针有效期、线程约束、缓冲区长度或所有权前提。 - 不用注释复述调用本身;注释解释为什么当前调用满足 API 前提。
- FFI 输出缓冲区必须满足 API 要求的大小和对齐。对话框模板使用 DWORD 对齐的存储。
- 所有长度、索引和句柄转换必须考虑 32/64 位和符号范围。
- 区分
NULL、INVALID_HANDLE_VALUE、HGDI_ERROR和 API 特定失败值。 - 传给异步线程或窗口消息的原始指针必须有可证明的寿命;优先传稳定 ID 或拥有的数据。
- 不允许通过
transmute、伪造生命周期或全局可变状态绕过所有权问题。
- 进程数、CPU 数量和数组索引使用不会截断的平台合适类型,如
usize或u32。 - 内存、commit、工作集、网络字节和 KB 显示链路使用
u64。 - 跨乘法的百分比计算优先使用
u128,并明确舍入规则。 - 累积计数器计算 delta 时处理首次采样、回退、重置、溢出和零时间间隔。
- CPU delta 只有在基线和当前样本都可信且时间单调时才有效。
- 不用饱和运算掩盖本应暴露的数据错误。仅在 UI 格式化或明确边界语义中使用饱和/截断。
- processor group 相关代码必须 group-aware。当前 UI 无法完整表达跨 group 亲和性时,应明确限制并拒绝误导性操作。
先定位热路径和复杂度,再修改数据结构。没有测量或可证明复杂度收益时,不做广泛性能重构。
优先级如下:
- 移除 UI 线程上的阻塞 I/O 和同步采样。
- 消除每 tick 的 O(n x history) 搬移、重复系统查询和全量资源重建。
- 使用合适的数据结构降低渐进复杂度。
- 减少不可见行绘制、无变化布局和无效控件更新。
- 最后才考虑局部复制、分配和指令级微优化。
热路径约束:
- 时间历史使用 ring buffer,不用
copy_within每次平移整个历史。 - 大进程列表使用 owner-data/虚拟 ListView;只格式化活动列并只重绘可见、变更的行。
- 按身份查找使用
HashMap/HashSet,稳定显示顺序单独保存在Vec中。 - 排序可缓存大小写折叠键或稳定比较键,避免每次比较分配字符串。
- 网络页仅在适配器数量或布局条件改变时重排;普通 tick 只提交数据和图表更新。
- GDI/Direct2D brush、bitmap 和兼容 DC 应按生命周期复用,并在尺寸容量不足时事务式扩容。
- 缓存必须写明 key、所有者和失效条件。无法说明失效规则的缓存不应存在。
- 不为避免一次廉价调用引入跨线程共享、复杂锁或难以证明的一致性状态。
性能结论应提供场景、数据规模和前后结果,例如大量进程、64+ CPU、多网卡、连续切页或窗口缩放。
- 保持经典 Task Manager 的布局、文案和操作模型,除非任务明确要求新设计。
- 页面切换必须先成功准备目标页面与菜单,再隐藏旧页面;失败时旧页面继续可用。
- 列表刷新应保持有效选择、焦点和滚动位置,不把 PID/行号当作稳定选择身份。
- 失败不能让已有列表闪成空白。无数据与采样失败是两个不同状态。
- 隐藏页面可以预热数据,但不得抢焦点、显示错误对话框或执行用户操作。
- 控件启用状态必须由当前已验证选择决定。无法验证的行可以展示,但危险操作必须禁用。
- 网卡重命名等行内状态变化必须同步更新所有关联列和缓存文本。
- 新增可见文本必须进入
TextKey和全部 locale TOML;调试日志不需要本地化。 - 新菜单项必须有唯一资源 ID、命令路由、状态更新、帮助文本和适用页面。
- 分隔线只由显式 separator 模型产生,不根据相邻索引猜测。
Options以#[repr(C)]二进制结构写入注册表。未经迁移设计,不得重排、删除或改变现有字段类型。- 读取配置必须先验证大小、类型、枚举范围、列集合、窗口矩形和 flags,再供 UI 使用。
- 进程列集合必须包含唯一主列、无重复列、合法 sentinel 和合法宽度。
- 修改本地化时编辑
localization/*.toml和键源;不要编辑OUT_DIR中的生成文件。 - 仓库图像源文件只允许 PNG/BMP。应用、默认进程和十二级托盘图标必须按 README 列出的名称提供全部明确尺寸,不允许构建期缩放、猜测缺失尺寸或替代文件。
.ico只能由build_support/icon_pipeline.rs确定性生成到OUT_DIR;源码、assets/和发布目录不得保存显式.ico文件。最终 EXE 仍必须包含标准RT_GROUP_ICON/RT_ICON资源。- 修改图标、位图、manifest 或资源名时同步更新
build_support/resources.rs、src/ui/resource_ids.rs、cargo:rerun-if-changed和资源测试。 - 数值资源 ID 必须唯一且保持兼容;运行时位图加载使用
u16ID,不重新引入字符串资源名。 - 构建脚本失败应直接给出文件和原因,不生成部分或陈旧输出。
target/、artifacts/、dist/等生成目录不得提交。
测试范围按风险和影响面决定。纯算法必须优先提取为可测试函数。
至少覆盖以下类别:
- PID 复用、进程创建时间校验和不可验证身份。
- 首次/重置样本、计数器回退、溢出、KB 格式化和百分比舍入。
- ring buffer 顺序、容量边界和时间轴推进规则。
- Options 列规范化、坏配置、窗口矩形和 enum 映射。
- worker 失败时保留旧快照、合并刷新请求和关闭状态。
- 行级错误不清空进程、应用、网络或用户列表。
- 菜单 separator、菜单构建失败和页面切换事务。
- 网卡重命名、排序稳定性和可见行更新。
- 资源构造失败时的局部清理。
对资源与 UI 行为做 Windows 本机验证:
- 启动、切页、刷新、最小化、托盘恢复和退出循环。
- 应用程序列表非空,进程用户名与会话信息合理。
- 第一次切换每个标签页不出现同步加载停顿或叠页。
- worker、WTS 和 IP Helper 失败时旧数据仍在且错误可观察。
- 使用 GDI/User handle 计数确认图标、bitmap、DC、menu、accelerator 不随循环增长。
- 多显示器、DPI、64+ CPU、多 processor group 和多网卡场景在可用机器上验证。
不要用单个单元测试替代覆盖同一要求所需的运行时或资源验证。
在仓库根目录执行:
cargo fmt --all -- --check
cargo check --all-targets
cargo clippy --all-targets -- -D warnings
cargo test --all-targets
cargo build --release
git diff --check也可以用 scripts/release-clean.ps1 生成路径重映射后的 release 构建。-UseNightlyBuildStd 仅用于明确要求的 nightly 构建,不是日常验证默认项。
如果任何命令无法执行,变更说明必须写明原因、已完成的替代验证和剩余风险。不得把“能编译”表述为“行为已经验证”。
- 阅读相关模块、调用者、资源定义和现有测试,先确认所有权与线程边界。
- 写出要保持的不变量和能证明问题的证据。
- 选择与现有架构一致的最小完整改动,而不是最小代码 diff。
- 先补或调整纯逻辑测试,再实现高风险行为。
- 对候选资源和候选快照使用先构建、后提交的事务式更新。
- 运行针对性测试,再运行完整质量门槛。
- 审查最终 diff,确认没有无关重排、生成物、依赖漂移或隐藏 fallback。
- 记录运行时未验证项,不用推测填补证据空白。
- 工作树可能包含他人未提交修改。不要重置、覆盖或格式化无关文件。
- 禁止使用
git reset --hard、无确认的 checkout 覆盖或其他破坏性清理。 - 依赖升级、架构调整和行为修复应在可审查的范围内组织;不要把无关大重构混入错误修复。
- 提交应说明行为变化和验证证据,不以“cleanup”“misc fixes”掩盖实际范围。
- 只有任务明确要求时才 commit、push 或创建 PR。
- 提交
Cargo.lock,不提交本地 IDE 状态、临时日志和构建产物。
以下做法默认不接受:
- worker 失败后在 UI 线程再采一次。
- PID-only 的破坏性进程操作。
- 查询失败时用 0、空字符串或空列表冒充有效新数据。
- 为绕过 API 错误加入未经证明的备用 API 链。
- 每次刷新重建菜单、窗口、ImageList、brush 或完整图表布局。
- 在历史数组头部插入并整体搬移。
- 对大列表逐行插入、逐单元永久缓存所有格式化字符串。
- 未说明失效条件的全局缓存。
- 裸句柄跨多个所有者传递并手工猜测谁负责释放。
- 使用 sleep、固定重试次数或魔法延时解决竞态。
- 把所有页面塞进
app.rs,或反过来把每个小类型拆成独立文件。 - 用大量
allow、忽略返回值或宽泛unsafe消除编译器和 Clippy 信号。 - 为了通过当前测试而缩窄真实需求。
一项改动只有在以下条件同时成立时才算完成:
- 用户可见问题或工程目标已在真实代码路径中解决。
- 身份、线程、资源、错误和缓存不变量仍然成立。
- 没有新增静默 fallback、半提交状态或错误域混淆。
- 测试覆盖与风险相称,完整质量门槛通过。
- 需要运行时证明的行为已经验证,或明确记录未验证原因和风险。
- 文档、本地化、资源和配置兼容性已同步处理。
- 最终 diff 聚焦、可解释,不包含无关生成物或他人修改的回退。
当“更快”“更现代”或“更抽象”与这些完成条件冲突时,以可证明的正确性和清晰工程边界为准。