文件监视 File Watcher
现代编辑器离不开“文件改了自动刷新”。HiEasyX 的文件监视基于 Windows
ReadDirectoryChangesW,采用重叠 I/O 实现非阻塞监听。每帧轮询一次,有变化就回调,没有变化零开销。
核心设计
文件监视是订阅-通知模型:
WatchDirectory注册一个目录和一个回调。- 每帧调用
PollFileWatcher()检查是否有新事件。 - 有事件时,回调被触发,告诉你哪个文件、发生了什么。
因为 IMGUI 是单线程帧循环,不能阻塞在 ReadDirectoryChangesW 上等待。重叠 I/O + 每帧 Poll 是 Windows GUI 程序的标准做法。
回调类型
using FileWatchCallback = std::function<void(
const HXString &file, // 发生变化的文件名(不含路径)
bool added, // 是否新增
bool removed, // 是否删除
bool modified // 是否修改
)>;
一个文件操作可能同时触发多个标志。例如重命名通常表现为:先 removed(旧名),再 added(新名)。覆盖写入通常表现为 modified。
API 详解
WatchDirectory
void WatchDirectory(const HXString &path, FileWatchCallback callback);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
path | HXString | 要监视的目录路径。支持相对路径(相对于工作目录)或绝对路径。 |
callback | FileWatchCallback | 变化时触发的回调函数。 |
WatchDirectory 只监视指定目录的直接子项,不递归子目录。如果需要监视多层目录,请为每一层分别调用 WatchDirectory。
UnwatchDirectory
void UnwatchDirectory(const HXString &path);
停止监视指定路径。如果该路径没有被监视过,调用安全无效果。
PollFileWatcher
void PollFileWatcher();
必须在每帧 HXBegin() 之后、任何 UI 逻辑之前调用,用于把操作系统通知转成交回调:
HX::HXBegin();
HX::PollFileWatcher(); // <-- 在这里!
while (peekmessage(&msg)) {
HX::PushMessage(HX::GetHXMessage(&msg));
}
// ... 后续 UI ...
如果不调 PollFileWatcher,回调永远不会触发。这不是自动的——你必须显式轮询。
完整示例:自动重载纹理
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
void SetupFileWatcher() {
HX::WatchDirectory(HXStr("./assets/"), [](
const HXString &file,
bool added,
bool removed,
bool modified
) {
if (modified && file.find(HXStr(".png")) != HXString::npos) {
// 纹理被修改了,标记重载
G_TextureNeedsReload = true;
G_LastChangedTexture = file;
}
if (added) {
// 新资源进来,刷新资源列表
G_AssetListDirty = true;
}
if (removed) {
// 资源被删,清理引用
G_AssetListDirty = true;
}
});
}
void DemoFileWatcher() {
static bool watcherSetup = false;
if (!watcherSetup) {
SetupFileWatcher();
watcherSetup = true;
}
// 每帧开头轮询
HX::PollFileWatcher();
// 如果有纹理需要重载
if (G_TextureNeedsReload) {
ReloadTexture(G_LastChangedTexture);
G_TextureNeedsReload = false;
}
// UI
HX::WindowProfile wp;
wp.Title = HXStr("Asset Browser");
HX::Window(HXStr("assets"), wp);
HX::Text(HXStr("Watching: ./assets/"));
if (G_AssetListDirty) {
HX::Text(HXStr("[Asset list changed, refreshing...]"));
RefreshAssetList();
G_AssetListDirty = false;
}
HX::End();
}
重命名的处理
Windows 的 ReadDirectoryChangesW 对重命名会报告为两个独立动作:
- 旧文件名 →
removed = true - 新文件名 →
added = true
如果你的业务需要“追踪同一个文件的重命名”,请在应用层维护一个文件内容哈希或 inode 映射,把 removed + added 关联成一次 renamed 事件。HiEasyX 的底层只负责忠实上报。
性能与限制
- 每个
WatchDirectory创建一个目录句柄和一个重叠 I/O 缓冲区。 PollFileWatcher()无事件时只是检查 OVERLAPPED 状态,开销极低。- 事件堆积时会在一帧内连续触发多次回调,但通常不超过几次。
传入的路径如果是相对路径,会基于当前工作目录解析。如果你的程序会被从快捷方式、不同目录启动,建议使用绝对路径,或者在启动时 SetCurrentDirectory 到项目根目录。
总结
| 需求 | 做法 |
|---|---|
| 监视目录 | WatchDirectory(path, callback) |
| 取消监视 | UnwatchDirectory(path) |
| 每帧轮询 | PollFileWatcher() 放 HXBegin() 之后 |
| 自动重载资源 | 在回调里标记 dirty flag,下帧处理 |
| 递归监视 | 为每层子目录分别调用 WatchDirectory |
| 重命名追踪 | 应用层把 removed + added 关联处理 |
文件监视是“Live Reload”的基石。配上热重载,你的编辑器就拥有了工业软件的呼吸感 🔄