跳到主要内容

文件监视 File Watcher

现代编辑器离不开“文件改了自动刷新”。HiEasyX 的文件监视基于 Windows ReadDirectoryChangesW,采用重叠 I/O 实现非阻塞监听。每帧轮询一次,有变化就回调,没有变化零开销。

核心设计

文件监视是订阅-通知模型:

  1. WatchDirectory 注册一个目录和一个回调。
  2. 每帧调用 PollFileWatcher() 检查是否有新事件。
  3. 有事件时,回调被触发,告诉你哪个文件、发生了什么。
为什么是轮询不是阻塞?

因为 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);

参数

参数类型说明
pathHXString要监视的目录路径。支持相对路径(相对于工作目录)或绝对路径。
callbackFileWatchCallback变化时触发的回调函数。
非递归监视

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 ...
别忘了 Poll

如果不调 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 对重命名会报告为两个独立动作:

  1. 旧文件名 → removed = true
  2. 新文件名 → 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”的基石。配上热重载,你的编辑器就拥有了工业软件的呼吸感 🔄