Polyphase Game Engine
Loading...
Searching...
No Matches
FileWatcher.h
Go to the documentation of this file.
1#pragma once
2
3#include "EngineTypes.h"
4
5#if !defined(POLYPHASE_PLATFORM_ADDON)
6
7#include <string>
8#include <vector>
9#include <functional>
10#include <thread>
11#include <atomic>
12#include <unordered_map>
13#include <mutex>
14
15enum class FileAction
16{
17 Added,
19 Removed,
21};
22
24{
25 std::string filePath;
27 std::string oldPath; // For rename events
28};
29
30using FileChangeCallback = std::function<void(const FileChangeEvent&)>;
31
32// ---------------------------------------------------------------------------
33// Portable polling file watcher.
34//
35// A worker thread walks the watched directories every kPollIntervalMs and
36// diffs each file's mtime against a snapshot; differences become events that
37// the main thread drains in Update(). This deliberately uses only stat() and
38// the SYS_* directory API, so it behaves identically on every editor-capable
39// platform (Windows, Linux, and anything added later) with no per-platform
40// code. The previous implementation was Windows-only — ReadDirectoryChangesW
41// over an IOCP — which meant script hot-reload silently did nothing on the
42// Linux editor build, and which stored the in-flight OVERLAPPED/buffer inside
43// a std::vector whose reallocation handed the kernel dangling pointers.
44//
45// The cost is that a change is picked up within a poll interval rather than
46// instantly. For a Scripts/ tree of a few hundred .lua files that walk is
47// negligible, and sub-second latency is imperceptible in an edit/save loop.
48// ---------------------------------------------------------------------------
50{
51public:
54
55 // Initialize the file watcher
56 bool Initialize();
57
58 // Shutdown the file watcher
59 void Shutdown();
60
61 // Add a directory to watch
62 bool WatchDirectory(const std::string& directory, bool recursive = true);
63
64 // Remove a directory from watching
65 void UnwatchDirectory(const std::string& directory);
66
67 // Drop every watch at once. Called on project close so the next project's
68 // watches don't stack on top of the previous one's.
69 void UnwatchAll();
70
71 // Set callback for file change events
73
74 // Update function to process events (called from main thread)
75 void Update();
76
77 // Enable/disable the file watcher
78 void SetEnabled(bool enabled);
79 bool IsEnabled() const { return mEnabled; }
80
81private:
82 struct WatchDir
83 {
84 std::string path; // always ends in '/'
85 bool recursive = true;
86 };
87
88 struct FileSnapshot
89 {
90 int64_t lastSeenTime = 0; // mtime observed by the most recent scan
91 int64_t lastEmittedTime = 0; // mtime of the last change we dispatched
92 bool seenThisScan = false; // scan mark used to detect deletions
93 };
94
95 void WatcherThread();
96 void ProcessEvents();
97
98 // Worker-thread only. Walks a watched root, marking and diffing snapshots.
99 void ScanDirRecursive(const std::string& dir, bool recursive, std::vector<FileChangeEvent>& outEvents, bool rebaseline, uint32_t depth = 0);
100 void QueueEvents(const std::vector<FileChangeEvent>& events);
101
102 std::thread mWatcherThread;
103 std::atomic<bool> mRunning;
104 std::atomic<bool> mEnabled;
105
106 // Set when the watch set changes or the watcher is re-enabled: the next
107 // scan records mtimes without emitting, so re-enabling hot-reload doesn't
108 // dispatch a reload for every file edited while it was switched off.
109 std::atomic<bool> mNeedsRebaseline;
110
111 FileChangeCallback mCallback;
112
113 std::vector<WatchDir> mWatchDirs;
114 std::mutex mWatchMutex;
115
116 std::vector<FileChangeEvent> mPendingEvents;
117 std::mutex mEventsMutex;
118
119 // Worker-thread private: absolute path -> observed mtimes.
120 std::unordered_map<std::string, FileSnapshot> mSnapshots;
121};
122
123// Global file watcher instance
125void CreateFileWatcher();
126void DestroyFileWatcher();
127
128#else // POLYPHASE_PLATFORM_ADDON
129
130// ---------------------------------------------------------------------------
131// Addon-platform stub. The full FileWatcher uses std::mutex / std::thread /
132// std::atomic — none of which are available in embedded libstdc++ builds
133// like MARSDEV's m68k-elf. Hot-reload makes no sense on a ROM cart anyway,
134// so we provide a header-only no-op stub. Engine.cpp's `if (GetFileWatcher())`
135// checks already gate every real call site, and they all become inert when
136// GetFileWatcher() returns null.
137// ---------------------------------------------------------------------------
138
139#include <string>
140
141enum class FileAction { Added, Modified, Removed, Renamed };
142
143struct FileChangeEvent
144{
145 std::string filePath;
147 std::string oldPath;
148};
149
150// Trivial callback type — no <functional>/std::function so we don't drag
151// any extra STL into the addon build. Engine code passes a free function
152// pointer at the only call site (OnScriptFileChanged in Engine.cpp).
153using FileChangeCallback = void(*)(const FileChangeEvent&);
154
155class FileWatcher
156{
157public:
158 bool Initialize() { return false; }
159 void Shutdown() {}
160 bool WatchDirectory(const std::string&, bool = true) { return false; }
161 void UnwatchDirectory(const std::string&) {}
162 void UnwatchAll() {}
164 void Update() {}
165 void SetEnabled(bool) {}
166 bool IsEnabled() const { return false; }
167};
168
169// Declared here, defined in the matching #else block in FileWatcher.cpp so
170// header inline + .cpp body don't clash at link time.
172void CreateFileWatcher();
173void DestroyFileWatcher();
174
175#endif // POLYPHASE_PLATFORM_ADDON
void CreateFileWatcher()
Definition FileWatcher.cpp:53
std::function< void(const FileChangeEvent &)> FileChangeCallback
Definition FileWatcher.h:30
FileWatcher * GetFileWatcher()
Definition FileWatcher.cpp:48
void DestroyFileWatcher()
Definition FileWatcher.cpp:61
FileAction
Definition FileWatcher.h:16
Definition FileWatcher.h:50
FileWatcher()
Definition FileWatcher.cpp:70
~FileWatcher()
Definition FileWatcher.cpp:77
void SetFileChangeCallback(FileChangeCallback callback)
Definition FileWatcher.cpp:218
void UnwatchDirectory(const std::string &directory)
Definition FileWatcher.cpp:179
void UnwatchAll()
Definition FileWatcher.cpp:201
void SetEnabled(bool enabled)
Definition FileWatcher.cpp:231
void Shutdown()
Definition FileWatcher.cpp:96
bool Initialize()
Definition FileWatcher.cpp:82
void Update()
Definition FileWatcher.cpp:223
bool IsEnabled() const
Definition FileWatcher.h:79
bool WatchDirectory(const std::string &directory, bool recursive=true)
Definition FileWatcher.cpp:140
Definition FileWatcher.h:24
std::string filePath
Definition FileWatcher.h:25
std::string oldPath
Definition FileWatcher.h:27
FileAction action
Definition FileWatcher.h:26