!I (1670414, 1670415, 1670416, 1670424, 1670431):
[CRYENGINE.git] / Code / Sandbox / EditorQt / EditorFileMonitor.cpp
blobe3dfede444017bceadbae3af42c00ac99138bb1c
1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
3 #include "StdAfx.h"
4 #include "EditorFileMonitor.h"
5 #include <CryCore/ToolsHelpers/ResourceCompilerHelper.h>
6 #include "CrySystem/IProjectManager.h"
7 #include "GameEngine.h"
8 #include "Include/IAnimationCompressionManager.h"
9 #include <CryString/StringUtils.h>
10 #include <CrySystem/IProjectManager.h>
11 #include "CryEdit.h"
12 #include "FilePathUtil.h"
14 //////////////////////////////////////////////////////////////////////////
15 CEditorFileMonitor::CEditorFileMonitor()
17 GetIEditorImpl()->RegisterNotifyListener(this);
20 //////////////////////////////////////////////////////////////////////////
21 CEditorFileMonitor::~CEditorFileMonitor()
23 CFileChangeMonitor::DeleteInstance();
26 //////////////////////////////////////////////////////////////////////////
27 void CEditorFileMonitor::OnEditorNotifyEvent(EEditorNotifyEvent ev)
29 if (ev == eNotify_OnInit)
31 // Setup file change monitoring
32 gEnv->pSystem->SetIFileChangeMonitor(this);
34 // We don't want the file monitor to be enabled while
35 // in console mode...
36 if (!GetIEditorImpl()->IsInConsolewMode())
37 MonitorDirectories();
39 CFileChangeMonitor::Instance()->Subscribe(this);
41 else if (ev == eNotify_OnQuit)
43 gEnv->pSystem->SetIFileChangeMonitor(NULL);
44 CFileChangeMonitor::Instance()->StopMonitor();
45 GetIEditorImpl()->UnregisterNotifyListener(this);
49 //////////////////////////////////////////////////////////////////////////
50 bool CEditorFileMonitor::RegisterListener(IFileChangeListener* pListener, const char* sMonitorItem)
52 return RegisterListener(pListener, sMonitorItem, "*");
55 //////////////////////////////////////////////////////////////////////////
56 static string CanonicalizePath(const char* szPath)
58 std::vector<char> canonicalizedPath(strlen(szPath) + 1, '\0');
59 if (PathCanonicalize(&canonicalizedPath[0], szPath))
60 return string(&canonicalizedPath[0]);
62 return string(szPath);
65 //////////////////////////////////////////////////////////////////////////
66 static string GetAbsolutePathOfProjectFolder(const char* szPath)
68 const char* szProjectRoot = GetISystem()->GetIProjectManager()->GetCurrentProjectDirectoryAbsolute();
69 CryPathString path = PathUtil::Make(szProjectRoot, szPath);
70 path = PathUtil::AddBackslash(path);
71 path = PathUtil::ToDosPath(path);
72 return CanonicalizePath(path.c_str());
75 //////////////////////////////////////////////////////////////////////////
76 // TODO: Change the initialization order to call MonitorDirectories() before any of CEditorFileMonitor::RegisterListener
77 void CEditorFileMonitor::MonitorDirectories()
80 // NOTE: Instead of monitoring each sub-directory we monitor the whole root
81 // folder. This is needed since if the sub-directory does not exist when
82 // we register it it will never get monitored properly.
83 CFileChangeMonitor::Instance()->MonitorItem(GetAbsolutePathOfProjectFolder(PathUtil::GetGameFolder()));
85 // Add mod paths too
86 for (int index = 0;; index++)
88 const char* sModPath = gEnv->pCryPak->GetMod(index);
89 if (!sModPath)
90 break;
91 CFileChangeMonitor::Instance()->MonitorItem(GetAbsolutePathOfProjectFolder(sModPath));
94 // Add editor directory for scripts
95 CFileChangeMonitor::Instance()->MonitorItem(GetAbsolutePathOfProjectFolder("Editor"));
97 // Add shader source folder
98 CFileChangeMonitor::Instance()->MonitorItem(GetAbsolutePathOfProjectFolder("Engine/Shaders"));
101 //////////////////////////////////////////////////////////////////////////
102 bool CEditorFileMonitor::RegisterListener(IFileChangeListener* pListener, const char* szFolderRelativeToGame, const char* sExtension)
104 m_vecFileChangeCallbacks.push_back(SFileChangeCallback(pListener, PathUtil::ToUnixPath(szFolderRelativeToGame), sExtension));
106 // NOTE: Here we do not register any CFileChangeMonitor::Instance()->MonitorItem(path).
107 // See CEditorFileMonitor::MonitorDirectories().
108 return true;
111 bool CEditorFileMonitor::UnregisterListener(IFileChangeListener* pListener)
113 bool bRet = false;
115 // Note that we remove the listener, but we don't currently remove the monitored item
116 // from the file monitor. This is fine, but inefficient
118 std::vector<SFileChangeCallback>::iterator iter = m_vecFileChangeCallbacks.begin();
119 while (iter != m_vecFileChangeCallbacks.end())
121 if (iter->pListener == pListener)
123 iter = m_vecFileChangeCallbacks.erase(iter);
124 bRet = true;
126 else
128 ++iter;
132 return bRet;
135 //////////////////////////////////////////////////////////////////////////
136 static bool IsFilenameEndsWithDotDaeDotZip(const char* fln)
138 size_t len = strlen(fln);
139 if (len < 8)
140 return false;
142 if (stricmp(fln + len - 8, ".dae.zip") == 0)
143 return true;
144 else
145 return false;
148 //////////////////////////////////////////////////////////////////////////
149 static bool RecompileColladaFile(const char* path)
151 string pathWithGameFolder = PathUtil::ToUnixPath(PathUtil::AddSlash(PathUtil::GetGameFolder())) + string(path);
152 if (CResourceCompilerHelper::CallResourceCompiler(
153 pathWithGameFolder.c_str(), "/refresh", NULL, false, CResourceCompilerHelper::eRcExePath_editor, true, true, L".")
154 != CResourceCompilerHelper::eRcCallResult_success)
155 return true;
156 else
157 return false;
160 //////////////////////////////////////////////////////////////////////////
161 static const char* AbsoluteToProjectPath(const char* szAbsolutePath)
163 if (!GetISystem()->GetIPak()->IsAbsPath(szAbsolutePath))
165 return szAbsolutePath;
168 const string projectPath = PathUtil::AddSlash(gEnv->pSystem->GetIProjectManager()->GetCurrentProjectDirectoryAbsolute());
169 const string fixedPath = PathUtil::ToUnixPath(szAbsolutePath);
171 if (cry_strnicmp(fixedPath.c_str(), projectPath.c_str(), projectPath.length()) == 0) // path starts with rootPathStr
173 return szAbsolutePath + projectPath.length();
175 return "";
178 //////////////////////////////////////////////////////////////////////////
179 const char* GetPathRelativeToModFolder(const char* szAbsolutePath)
181 if (szAbsolutePath[0] == '\0')
182 return szAbsolutePath;
184 if (_strnicmp("engine", szAbsolutePath, 6) == 0 && (szAbsolutePath[6] == '\\' || szAbsolutePath[6] == '/'))
185 return szAbsolutePath;
187 szAbsolutePath = AbsoluteToProjectPath(szAbsolutePath);
189 string gameFolder = PathUtil::GetGameFolder();
190 string modLocation;
191 const char* modFolder = gameFolder.c_str();
192 int modIndex = 0;
195 if (_strnicmp(modFolder, szAbsolutePath, strlen(modFolder)) == 0)
197 const char* result = szAbsolutePath + strlen(modFolder);
198 if (*result == '\\' || *result == '/')
199 ++result;
200 return result;
203 modFolder = gEnv->pCryPak->GetMod(modIndex);
204 ++modIndex;
206 while (modFolder != 0);
208 return "";
211 ///////////////////////////////////////////////////////////////////////////
213 // Called when file monitor message is received
214 void CEditorFileMonitor::OnFileMonitorChange(const SFileChangeInfo& rChange)
216 CCryEditApp* app = CCryEditApp::GetInstance();
217 if (app == NULL || app->IsExiting())
218 return;
220 // skip folders!
221 if (QDir(QString::fromLocal8Bit(rChange.filename)).exists())
223 return;
226 // Process updated file.
227 // Make file relative to MasterCD folder.
228 string filename = rChange.filename;
230 // Make sure there is no leading slash
231 if (!filename.IsEmpty() && filename[0] == '\\' || filename[0] == '/')
232 filename = filename.Mid(1);
234 if (!filename.IsEmpty())
236 // Make it relative to the game folder
237 const string filenameRelGame = GetPathRelativeToModFolder(filename.GetString());
239 const string ext = filename.Right(filename.GetLength() - filename.ReverseFind('.') - 1);
241 // Check for File Monitor callback
242 std::vector<SFileChangeCallback>::iterator iter;
243 for (iter = m_vecFileChangeCallbacks.begin(); iter != m_vecFileChangeCallbacks.end(); ++iter)
245 SFileChangeCallback& sCallback = *iter;
247 // We compare against length of callback string, so we get directory matches as well as full filenames
248 if (sCallback.pListener)
250 if (sCallback.extension == "*" || stricmp(ext, sCallback.extension) == 0)
252 if (!filenameRelGame.empty() && _strnicmp(filenameRelGame, sCallback.item, sCallback.item.GetLength()) == 0)
254 sCallback.pListener->OnFileChange(filenameRelGame, IFileChangeListener::EChangeType(rChange.changeType));
256 else if (_strnicmp(filename, sCallback.item, sCallback.item.GetLength()) == 0)
258 sCallback.pListener->OnFileChange(filename, IFileChangeListener::EChangeType(rChange.changeType));
264 //TODO: have all these file types encapsulated in some IFileChangeFileTypeHandler to deal with each of them in a more generic way
265 bool isCAF = stricmp(ext, "caf") == 0;
266 bool isLMG = (stricmp(ext, "lmg") == 0) || (stricmp(ext, "bspace") == 0) || (stricmp(ext, "comb") == 0);
268 bool isExportLog = stricmp(ext, "exportlog") == 0;
269 bool isRCDone = stricmp(ext, "rcdone") == 0;
270 bool isCOLLADA = (stricmp(ext, "dae") == 0 || IsFilenameEndsWithDotDaeDotZip(filename.GetString()));
272 if (!isCAF && !isLMG && !isCOLLADA && !isExportLog && !isRCDone)
274 // Updating an existing CGF file is often implemented as a DeleteFile + MoveFile pair.
275 // If the file listener triggers a refresh of a CStatObj after deletion and before moving,
276 // it will not find the CGF file, overwrite its path with a default file, and lose the
277 // reference to the original path. This causes objects in the level to disappear.
278 // Therefore, we ignore deletions of CGF objects.
279 if (rChange.changeType != IFileChangeListener::eChangeType_Deleted)
281 // Apart from reloading resources it also may trigger importing of tif files.
282 if (rChange.changeType != IFileChangeListener::eChangeType_RenamedOldName)
284 GetIEditorImpl()->GetGameEngine()->ReloadResourceFile(filenameRelGame);
287 if (stricmp(ext, "cgf") == 0)
289 CBaseObjectsArray objects;
290 GetIEditorImpl()->GetObjectManager()->GetObjects(objects);
291 for (CBaseObject* pObj : objects)
293 pObj->InvalidateGeometryFile(filenameRelGame);
298 else if (isCOLLADA)
300 // Make a corresponding .cgf path.
301 string cgfFileName;
302 int nameLength = filename.GetLength();
304 cgfFileName = filename;
305 if (stricmp(ext, "dae") == 0)
307 cgfFileName.SetAt(nameLength - 3, 'c');
308 cgfFileName.SetAt(nameLength - 2, 'g');
309 cgfFileName.SetAt(nameLength - 1, 'f');
311 else
313 cgfFileName.SetAt(nameLength - 7, 'c');
314 cgfFileName.SetAt(nameLength - 6, 'g');
315 cgfFileName.SetAt(nameLength - 5, 'f');
316 cgfFileName.SetAt(nameLength - 4, 0);
318 IStatObj* pStatObjectToReload = GetIEditorImpl()->Get3DEngine()->FindStatObjectByFilename(cgfFileName.GetBuffer());
320 // If the corresponding .cgf file exists, recompile the changed COLLADA file.
321 if (pStatObjectToReload)
323 CryLog("Recompile DAE file: %s", (LPCTSTR)filename);
324 RecompileColladaFile(filename.GetString());
327 else
329 ICharacterManager* pICharacterManager = GetISystem()->GetIAnimationSystem();
330 stack_string strPath = filenameRelGame;
331 PathUtil::UnifyFilePath(strPath);
333 if (isLMG)
335 CryLog("Reload blendspace file: %s", (LPCTSTR)strPath);
336 pICharacterManager->ReloadLMG(strPath.c_str());
340 // Set this flag to make sure that the viewport will update at least once,
341 // so that the changes will be shown, even if the app does not have focus.
342 CCryEditApp::GetInstance()->ForceNextIdleProcessing();