Skip to content
This repository has been archived by the owner on Jan 25, 2023. It is now read-only.

FileWatcher enhanced. (Currently only available for Windows) #2586

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion Source/Urho3D/AngelScript/ResourceAPI.cpp
Expand Up @@ -177,8 +177,9 @@ static void RegisterResourceCache(asIScriptEngine* engine)
engine->RegisterObjectMethod("ResourceCache", "Array<PackageFile@>@ get_packageFiles() const", asFUNCTION(ResourceCacheGetPackageFiles), asCALL_CDECL_OBJLAST);
engine->RegisterObjectMethod("ResourceCache", "void set_searchPackagesFirst(bool)", asMETHOD(ResourceCache, SetSearchPackagesFirst), asCALL_THISCALL);
engine->RegisterObjectMethod("ResourceCache", "bool get_seachPackagesFirst() const", asMETHOD(ResourceCache, GetSearchPackagesFirst), asCALL_THISCALL);
engine->RegisterObjectMethod("ResourceCache", "void set_autoReloadResources(bool)", asMETHOD(ResourceCache, SetAutoReloadResources), asCALL_THISCALL);
engine->RegisterObjectMethod("ResourceCache", "void SetAutoReloadResources(bool, bool)", asMETHOD(ResourceCache, SetAutoReloadResources), asCALL_THISCALL);
engine->RegisterObjectMethod("ResourceCache", "bool get_autoReloadResources() const", asMETHOD(ResourceCache, GetAutoReloadResources), asCALL_THISCALL);
engine->RegisterObjectMethod("ResourceCache", "bool get_fullResourceWatch() const", asMETHOD(ResourceCache, GetFullResourceWatch), asCALL_THISCALL);
engine->RegisterObjectMethod("ResourceCache", "void set_returnFailedResources(bool)", asMETHOD(ResourceCache, SetReturnFailedResources), asCALL_THISCALL);
engine->RegisterObjectMethod("ResourceCache", "bool get_returnFailedResources() const", asMETHOD(ResourceCache, GetReturnFailedResources), asCALL_THISCALL);
engine->RegisterObjectMethod("ResourceCache", "void set_finishBackgroundResourcesMs(int)", asMETHOD(ResourceCache, SetFinishBackgroundResourcesMs), asCALL_THISCALL);
Expand Down
113 changes: 86 additions & 27 deletions Source/Urho3D/IO/FileWatcher.cpp
Expand Up @@ -53,7 +53,8 @@ FileWatcher::FileWatcher(Context* context) :
Object(context),
fileSystem_(GetSubsystem<FileSystem>()),
delay_(1.0f),
watchSubDirs_(false)
watchSubDirs_(false),
fullWatch_(false)
{
#ifdef URHO3D_FILEWATCHER
#ifdef __linux__
Expand All @@ -74,7 +75,7 @@ FileWatcher::~FileWatcher()
#endif
}

bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs, bool fullWatch)
{
if (!fileSystem_)
{
Expand Down Expand Up @@ -102,6 +103,7 @@ bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
{
path_ = AddTrailingSlash(pathName);
watchSubDirs_ = watchSubDirs;
fullWatch_ = fullWatch;
Run();

URHO3D_LOGDEBUG("Started watching path " + pathName);
Expand Down Expand Up @@ -241,14 +243,19 @@ void FileWatcher::ThreadFunction()
unsigned char buffer[BUFFERSIZE];
DWORD bytesFilled = 0;

DWORD flags;
if (fullWatch_)
flags = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_DIR_NAME;
else
flags = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE;

while (shouldRun_)
{
if (ReadDirectoryChangesW((HANDLE)dirHandle_,
buffer,
BUFFERSIZE,
watchSubDirs_,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_LAST_WRITE,
flags,
&bytesFilled,
nullptr,
nullptr))
Expand All @@ -259,18 +266,35 @@ void FileWatcher::ThreadFunction()
{
FILE_NOTIFY_INFORMATION* record = (FILE_NOTIFY_INFORMATION*)&buffer[offset];

if (record->Action == FILE_ACTION_MODIFIED || record->Action == FILE_ACTION_RENAMED_NEW_NAME)
{
String fileName;
const wchar_t* src = record->FileName;
const wchar_t* end = src + record->FileNameLength / 2;
while (src < end)
fileName.AppendUTF8(String::DecodeUTF16(src));
FileChangeType changeType;

String fileName;
const wchar_t* src = record->FileName;
const wchar_t* end = src + record->FileNameLength / 2;
while (src < end)
fileName.AppendUTF8(String::DecodeUTF16(src));

fileName = GetInternalPath(fileName);
AddChange(fileName);
fileName = GetInternalPath(fileName);

switch (record->Action)
{
case FILE_ACTION_MODIFIED:
changeType = FILECHANGE_MODIFIED;
break;
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
changeType = FILECHANGE_ADDED;
break;
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
changeType = FILECHANGE_REMOVED;
break;
default:
changeType = FILECHANGE_UNKNOWN;
}

AddChange({fileName, changeType});

if (!record->NextEntryOffset)
break;
else
Expand Down Expand Up @@ -299,7 +323,7 @@ void FileWatcher::ThreadFunction()
{
String fileName;
fileName = dirHandle_[event->wd] + event->name;
AddChange(fileName);
AddChange({fileName, FILECHANGE_UNKNOWN});
}
}

Expand All @@ -316,43 +340,78 @@ void FileWatcher::ThreadFunction()
{
Vector<String> fileNames = changes.Split(1);
for (unsigned i = 0; i < fileNames.Size(); ++i)
AddChange(fileNames[i]);
AddChange({fileNames[i], FILECHANGE_UNKNOWN});
}
}
#endif
#endif
}

void FileWatcher::AddChange(const String& fileName)
void FileWatcher::AddChange(const FileChange& change)
{
MutexLock lock(changesMutex_);

// Reset the timer associated with the filename. Will be notified once timer exceeds the delay
changes_[fileName].Reset();
if (fullWatch_)
{
allChanges_.Resize(allChanges_.Size() + 1);

TimedFileChange& timedChange = allChanges_.Back();
timedChange.change_ = change;

// Reset the timer associated with the filename. Will be notified once timer exceeds the delay
timedChange.timer_.Reset();
}
else
{
HashMap<String, TimedFileChange>::Iterator it = fileChanges_.Find(change.fileName_);
if (it == fileChanges_.End())
{
fileChanges_[change.fileName_].change_ = change;
}
else
{
// Reset the timer associated with the filename. Will be notified once timer exceeds the delay
it->second_.timer_.Reset();
}
}
}

bool FileWatcher::GetNextChange(String& dest)
bool FileWatcher::GetNextChange(FileChange& dest)
{
MutexLock lock(changesMutex_);

auto delayMsec = (unsigned)(delay_ * 1000.0f);

if (changes_.Empty())
return false;
if (fullWatch_)
{
if (allChanges_.Empty())
return false;

TimedFileChange& timedChange = allChanges_.Front();
if (timedChange.timer_.GetMSec(false) >= delayMsec)
{
dest = timedChange.change_;
allChanges_.PopFront();
return true;
}
}
else
{
for (HashMap<String, Timer>::Iterator i = changes_.Begin(); i != changes_.End(); ++i)
if (fileChanges_.Empty())
return false;

for (HashMap<String, TimedFileChange>::Iterator i = fileChanges_.Begin(); i != fileChanges_.End(); ++i)
{
if (i->second_.GetMSec(false) >= delayMsec)
if (i->second_.timer_.GetMSec(false) >= delayMsec)
{
dest = i->first_;
changes_.Erase(i);
dest = i->second_.change_;
fileChanges_.Erase(i);
return true;
}
}

return false;
}

return false;
}

}
41 changes: 36 additions & 5 deletions Source/Urho3D/IO/FileWatcher.h
Expand Up @@ -33,6 +33,24 @@ namespace Urho3D

class FileSystem;

/// File change type.
enum FileChangeType
SuperWangKai marked this conversation as resolved.
Show resolved Hide resolved
{
FILECHANGE_UNKNOWN = 0,
FILECHANGE_ADDED,
FILECHANGE_MODIFIED,
FILECHANGE_REMOVED
};

/// File change information.
struct FileChange
{
/// Changed file name.
String fileName_;
/// Changed file type.
FileChangeType type_;
};

/// Watches a directory and its subdirectories for files being modified.
class URHO3D_API FileWatcher : public Object, public Thread
{
Expand All @@ -48,15 +66,15 @@ class URHO3D_API FileWatcher : public Object, public Thread
void ThreadFunction() override;

/// Start watching a directory. Return true if successful.
bool StartWatching(const String& pathName, bool watchSubDirs);
bool StartWatching(const String& pathName, bool watchSubDirs, bool fullWatch);
/// Stop watching the directory.
void StopWatching();
/// Set the delay in seconds before file changes are notified. This (hopefully) avoids notifying when a file save is still in progress. Default 1 second.
void SetDelay(float interval);
/// Add a file change into the changes queue.
void AddChange(const String& fileName);
void AddChange(const FileChange& change);
/// Return a file change (true if was found, false if not.)
bool GetNextChange(String& dest);
bool GetNextChange(FileChange& dest);

/// Return the path being watched, or empty if not watching.
const String& GetPath() const { return path_; }
Expand All @@ -65,18 +83,31 @@ class URHO3D_API FileWatcher : public Object, public Thread
float GetDelay() const { return delay_; }

private:
/// File change information with timer.
struct TimedFileChange
{
/// File change information.
FileChange change_;
/// Timer used to delay reporting change.
Timer timer_;
};

/// Filesystem.
SharedPtr<FileSystem> fileSystem_;
/// The path being watched.
String path_;
/// Pending changes. These will be returned and removed from the list when their timer has exceeded the delay.
HashMap<String, Timer> changes_;
/// Pending changes. These will be returned and removed from the list when their timer has exceeded the delay. Used in full watch mode.
List<TimedFileChange> allChanges_;
/// Pending changes. These will be returned and removed from the list when their timer has exceeded the delay. Used in reloading mode.
HashMap<String, TimedFileChange> fileChanges_;
/// Mutex for the change buffer.
Mutex changesMutex_;
/// Delay in seconds for notifying changes.
float delay_;
/// Watch subdirectories flag.
bool watchSubDirs_;
/// Full watching mode.
bool fullWatch_;

#ifdef _WIN32

Expand Down
4 changes: 2 additions & 2 deletions Source/Urho3D/LuaScript/pkgs/Resource/ResourceCache.pkg
Expand Up @@ -9,7 +9,7 @@ class ResourceCache
void SetMemoryBudget(StringHash type, unsigned long long budget);
void SetMemoryBudget(const String type, unsigned long long budget);

void SetAutoReloadResources(bool enable);
void SetAutoReloadResources(bool enable, bool fullResourceWatch);
void SetReturnFailedResources(bool enable);
void SetSearchPackagesFirst(bool value);
void SetFinishBackgroundResourcesMs(int ms);
Expand All @@ -29,6 +29,7 @@ class ResourceCache
String GetResourceFileName(const String name) const;

bool GetAutoReloadResources() const;
bool GetFullResourceWatch() const;
bool GetReturnFailedResources() const;
bool GetSearchPackagesFirst() const;
int GetFinishBackgroundResourcesMs() const;
Expand All @@ -38,7 +39,6 @@ class ResourceCache
String SanitateResourceDirName(const String name) const;

tolua_readonly tolua_property__get_set unsigned long long totalMemoryUse;
tolua_property__get_set bool autoReloadResources;
tolua_property__get_set bool returnFailedResources;
tolua_property__get_set bool searchPackagesFirst;
tolua_readonly tolua_property__get_set unsigned numBackgroundLoadResources;
Expand Down
25 changes: 14 additions & 11 deletions Source/Urho3D/Resource/ResourceCache.cpp
Expand Up @@ -70,6 +70,7 @@ static const SharedPtr<Resource> noResource;
ResourceCache::ResourceCache(Context* context) :
Object(context),
autoReloadResources_(false),
fullResourceWatch_(false),
returnFailedResources_(false),
searchPackagesFirst_(true),
isRouting_(false),
Expand Down Expand Up @@ -125,7 +126,7 @@ bool ResourceCache::AddResourceDir(const String& pathName, unsigned priority)
if (autoReloadResources_)
{
SharedPtr<FileWatcher> watcher(new FileWatcher(context_));
watcher->StartWatching(fixedPath, true);
watcher->StartWatching(fixedPath, true, fullResourceWatch_);
fileWatchers_.Push(watcher);
}

Expand Down Expand Up @@ -438,23 +439,24 @@ void ResourceCache::SetMemoryBudget(StringHash type, unsigned long long budget)
resourceGroups_[type].memoryBudget_ = budget;
}

void ResourceCache::SetAutoReloadResources(bool enable)
void ResourceCache::SetAutoReloadResources(bool enable, bool fullResourceWatch)
{
if (enable != autoReloadResources_)
if (enable != autoReloadResources_ || fullResourceWatch != fullResourceWatch_)
{
fileWatchers_.Clear();

if (enable)
{
for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
{
SharedPtr<FileWatcher> watcher(new FileWatcher(context_));
watcher->StartWatching(resourceDirs_[i], true);
watcher->StartWatching(resourceDirs_[i], true, fullResourceWatch_);
fileWatchers_.Push(watcher);
}
}
else
fileWatchers_.Clear();

autoReloadResources_ = enable;
fullResourceWatch_ = fullResourceWatch;
}
}

Expand Down Expand Up @@ -1074,17 +1076,18 @@ void ResourceCache::HandleBeginFrame(StringHash eventType, VariantMap& eventData
{
for (unsigned i = 0; i < fileWatchers_.Size(); ++i)
{
String fileName;
while (fileWatchers_[i]->GetNextChange(fileName))
FileChange change;
while (fileWatchers_[i]->GetNextChange(change))
{
ReloadResourceWithDependencies(fileName);
ReloadResourceWithDependencies(change.fileName_);

// Finally send a general file changed event even if the file was not a tracked resource
using namespace FileChanged;

VariantMap& eventData = GetEventDataMap();
eventData[P_FILENAME] = fileWatchers_[i]->GetPath() + fileName;
eventData[P_RESOURCENAME] = fileName;
eventData[P_FILENAME] = fileWatchers_[i]->GetPath() + change.fileName_;
eventData[P_CHANGETYPE] = (int)change.type_;
eventData[P_RESOURCENAME] = change.fileName_;
SendEvent(E_FILECHANGED, eventData);
}
}
Expand Down