Leviathan  0.8.0.0
Leviathan game engine
ResourceRefreshHandler.cpp
Go to the documentation of this file.
1 // ------------------------------------ //
3 
5 #include "../TimeIncludes.h"
6 #include "IDFactory.h"
7 #ifdef __linux__
8 #include <sys/types.h>
9 #include <sys/inotify.h>
10 #endif
11 using namespace Leviathan;
12 using namespace std;
13 // ------------------------------------ //
14 #ifdef __linux__
15 
16 #define IN_EVENT_SIZE (sizeof(inotify_event))
17 #define IN_READ_BUFFER_SIZE (124*(IN_EVENT_SIZE + 16))
18 
19 
20 #endif
21 
23 
24 }
25 
27  LEVIATHAN_ASSERT(Staticaccess != this,
28  "ResourceRefreshHandler should have been released before destructor");
29 }
30 
32  return Staticaccess;
33 }
34 
36 // ------------------------------------ //
38  // Set the next update time //
40 
41  Staticaccess = this;
42  return true;
43 }
44 
46  GUARD_LOCK();
47 
48  Staticaccess = NULL;
49 
50  // Release all listeners //
51  auto end = ActiveFileListeners.end();
52  for(auto iter = ActiveFileListeners.begin(); iter != end; ++iter){
53 
54  (*iter)->StopThread();
55  }
56 
57 
58  ActiveFileListeners.clear();
59 }
60 // ------------------------------------ //
62  const std::vector<const std::string*> &filestowatch,
63  std::function<void (const std::string &, ResourceFolderListener&)> notifyfunction,
64  int &createdid)
65 {
66 
67  unique_ptr<ResourceFolderListener> tmpcreated(new ResourceFolderListener(filestowatch,
68  notifyfunction));
69 
70  createdid = tmpcreated->GetID();
71 
72  if(!tmpcreated->StartListening())
73  return false;
74 
75  GUARD_LOCK();
76 
77  // Add it //
78  ActiveFileListeners.push_back(move(tmpcreated));
79 
80  return true;
81 }
82 
84  int idoflistener)
85 {
86 
87  GUARD_LOCK();
88 
89  // Find the specific listener //
90  auto end = ActiveFileListeners.end();
91  for(auto iter = ActiveFileListeners.begin(); iter != end; ++iter){
92 
93  if((*iter)->GetID() == idoflistener){
94 
95  (*iter)->StopThread();
96  ActiveFileListeners.erase(iter);
97  return;
98  }
99  }
100 }
101 
103  GUARD_LOCK();
104 
105  if(Time::GetThreadSafeSteadyTimePoint() > NextUpdateTime){
106  // Update all the file listeners //
107  for(size_t i = 0; i < ActiveFileListeners.size(); i++){
108 
109  ActiveFileListeners[i]->CheckUpdatesEnded();
110  }
111 
112  // Set new update time //
114  }
115 }
116 // ------------------------------------ //
118  const std::vector<int> &ids)
119 {
120 
121  GUARD_LOCK();
122 
123  // Find any listeners matching any of the ids //
124  auto end = ActiveFileListeners.end();
125  for(auto iter = ActiveFileListeners.begin(); iter != end; ++iter){
126 
127  const int& curid = (*iter)->GetID();
128 
129  // Check against all the ids //
130  auto end2 = ids.end();
131  for(auto iter2 = ids.begin(); iter2 != end2; ++iter2){
132  if(curid == *iter2){
133 
134  (*iter)->MarkAllAsNotUpdated();
135  break;
136  }
137  }
138  }
139 }
140 // ------------------ ResourceFolderListener ------------------ //
142  const std::vector<const std::string*> &filestowatch,
143  std::function<void (const std::string &, ResourceFolderListener&)> notifyfunction) :
144  ListenedFiles(filestowatch.size()),
145  UpdatedFiles(filestowatch.size(), false),
146  ID(IDFactory::GetID()), CallbackFunction(notifyfunction)
147 {
148 #ifdef _WIN32
149  // Avoid having to re-allocate the vector later //
150  SignalingHandles.reserve(1+1);
151 
152 #else
153 
154  InotifyID = inotify_init();
155 
156  if(InotifyID < -1){
157 
158  Logger::Get()->Error("ResourceRefreshHandler: ResourceFolderListener: "
159  "failed to create inotify instance");
160  return;
161  }
162 
163 #endif //_WIN32
164 
165  // Copy the target files //
166  for(size_t i = 0; i < ListenedFiles.size(); i++){
167 
168  // Get the folder on the first loop //
169  if(i == 0){
170 
171 
172  TargetFolder = StringOperations::GetPath<std::string>(*filestowatch[i]);
173  }
174 
175  ListenedFiles[i] = make_unique<std::string>(
176  StringOperations::RemovePath<std::string>(*filestowatch[i]));
177  }
178 
179 }
180 
182  LEVIATHAN_ASSERT(ShouldQuit,
183  "ResourceFolderListener should have been stopped before destructor");
184 }
185 // ------------------------------------ //
187  return ID;
188 }
189 // ------------------------------------ //
191 
192 #ifdef _WIN32
193  // First create the stop signaler //
194  HANDLE ourstopper = CreateEvent(NULL, FALSE, FALSE, NULL);
195 
196  if(!ourstopper){
197 
198  Logger::Get()->Error("ResourceFolderListener: StartListening: failed to create stop "
199  "notify handle, CreateEvent failed");
200  return false;
201  }
202 
203 
204  SignalingHandles.push_back(ourstopper);
205 
206  // Now the folder listener //
207  TargetFolderHandle = CreateFileA(TargetFolder.c_str(), FILE_READ_DATA | FILE_TRAVERSE |
208  FILE_READ_EA, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
209  NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
210 
211  if(TargetFolderHandle == INVALID_HANDLE_VALUE || !TargetFolderHandle){
212 
213  Logger::Get()->Error("ResourceFolderListener: StartListening: failed to open folder for "
214  "reading, error: "+Convert::ToHexadecimalString(GetLastError()));
215  return false;
216  }
217 
218 
219  // Create the OVERLAPPED struct next //
220  OverlappedInfo = new OVERLAPPED;
221 
222  ZeroMemory(OverlappedInfo, sizeof(OVERLAPPED));
223 
224  // Create an event for notification //
225  HANDLE readcompleteevent = CreateEvent(NULL, FALSE, FALSE, NULL);
226 
227  if(!readcompleteevent){
228 
229  Logger::Get()->Error("ResourceFolderListener: StartListening: "
230  "failed to create read notify handle, CreateEvent failed, error: " +
231  Convert::ToHexadecimalString(GetLastError()));
232  return false;
233  }
234 
235  // Add it to the overlapped //
236  OverlappedInfo->hEvent = readcompleteevent;
237 
238 
239 
240  OurReadBuffer = new FILE_NOTIFY_INFORMATION[100];
241 
242 
243  // Create the update notification read thing //
244  BOOL createresult = ReadDirectoryChangesW(TargetFolderHandle, OurReadBuffer,
245  sizeof(FILE_NOTIFY_INFORMATION)*100,
246  // Only the top level directory is watched
247  FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, OverlappedInfo, NULL);
248 
249  if(!createresult){
250 
251  CloseHandle(TargetFolderHandle);
252  Logger::Get()->Error("ResourceFolderListener: StartListening: failed to start reading "
253  "directory changes, error: "+Convert::ToHexadecimalString(GetLastError()));
254  return false;
255  }
256 
257  SignalingHandles.push_back(readcompleteevent);
258 
259 #else
260 
261  // Create watches for all of them //
262 
263  InotifyWatches = inotify_add_watch(InotifyID, TargetFolder.c_str(), IN_MODIFY);
264 
265 
266  if(InotifyWatches < 0){
267 
268 
269  Logger::Get()->Error("ResourceRefreshHandler: ResourceFolderListener: failed to add "
270  "watch for folder: "+TargetFolder);
271  return false;
272  }
273 
274 
275  // Allocate the read buffer //
276  ReadBuffer = new char[IN_READ_BUFFER_SIZE];
277 
278 #endif //_WIN32
279 
280 
281  ShouldQuit = false;
282 
283  // Finally start the thread //
284  ListenerThread = std::thread(std::bind(&ResourceFolderListener::_RunListeningThread,
285  this));
286 
287  return true;
288 }
289 
291 #ifdef _WIN32
292  // Don't do anything if already done //
293  if(ShouldQuit && SignalingHandles.empty())
294  return;
295 
296  ShouldQuit = true;
297 
298  // Signal the close handle //
299  SetEvent(SignalingHandles[0]);
300 
301  // Join the thread to wait for it to quit //
302  ListenerThread.join();
303 
304  // Close the handles //
305  CloseHandle(SignalingHandles[0]);
306  CloseHandle(SignalingHandles[1]);
307  CloseHandle(TargetFolderHandle);
308 
309  SignalingHandles.clear();
310 
311  if(OurReadBuffer){
312  delete[] OurReadBuffer;
313  OurReadBuffer = NULL;
314  }
315 
316  SAFE_DELETE(OverlappedInfo);
317 #else
318 
319  if(ShouldQuit && InotifyID == -1)
320  return;
321 
322  ShouldQuit = true;
323 
324  inotify_rm_watch(InotifyID, InotifyWatches);
325  close(InotifyID);
326 
327  ListenerThread.join();
328 
329  if(ReadBuffer){
330  delete[] ReadBuffer;
331  ReadBuffer = NULL;
332  }
333 
334  InotifyID = -1;
335  InotifyWatches = -1;
336 
337 #endif //_WIN32
338 }
339 // ------------------------------------ //
341  // Run until quit is requested //
342  while(!ShouldQuit){
343 
344 #ifdef _WIN32
345 
346  // Wait for the handles //
347  DWORD waitstatus = WaitForMultipleObjects(static_cast<DWORD>(SignalingHandles.size()), &SignalingHandles[0],
348  FALSE, INFINITE);
349 
350  // Check what happened //
351  switch(waitstatus){
352  case WAIT_OBJECT_0:
353 
354  // Quit has been called //
355  return;
356 
357  case WAIT_OBJECT_0 + 1:
358  {
359  // A modification has been detected //
360 
361  // Check what is detected //
362  FILE_NOTIFY_INFORMATION* dataptr = OurReadBuffer;
363 
364  DWORD numread;
365 
366  GetOverlappedResult(TargetFolderHandle, OverlappedInfo, &numread, FALSE);
367 
368  if(numread < 1){
369  // Nothing read/failed //
370  Logger::Get()->Error("ResourceFolderListener: _RunListeningThread: result "
371  "has 0 bytes: ");
372  continue;
373  }
374 
375  bool working = true;
376 
377  while(working){
378  // Check what the notification is //
379  if(dataptr->Action == FILE_ACTION_REMOVED ||
380  dataptr->Action == FILE_ACTION_RENAMED_OLD_NAME){
381 
382  goto movetonextdatalabel;
383  }
384 
385  {
386  // Get the filename //
387  std::wstring entrydata;
388 
389  size_t filenameinwchars = dataptr->FileNameLength/sizeof(wchar_t);
390 
391  entrydata.resize(filenameinwchars);
392 
393  // Copy the data //
394  memcpy_s(&entrydata[0], entrydata.size()*sizeof(wchar_t),
395  dataptr->FileName, dataptr->FileNameLength);
396 
397  std::string utf8file = Convert::Utf16ToUtf8(entrydata);
398 
399  // Skip if nothing //
400  if(utf8file.empty()){
401 
402  goto movetonextdatalabel;
403  }
404 
405 
406  // Check which file matches and set it as updated //
407  for(size_t i = 0; i < ListenedFiles.size(); i++){
408 
409  if(*ListenedFiles[i] == utf8file){
410 
411  // Updated //
412  UpdatedFiles[i] = true;
413  break;
414  }
415  }
416  }
417 movetonextdatalabel:
418 
419  if(!dataptr->NextEntryOffset){
420 
421  // No more entries
422  working = false;
423  break;
424  } else {
425  // Move to next entry //
426 
427  char* tmpptr = reinterpret_cast<char*>(dataptr);
428  tmpptr += dataptr->NextEntryOffset;
429 
430  dataptr = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(tmpptr);
431  }
432  }
433 
434  // Start listening again //
435  BOOL createresult = ReadDirectoryChangesW(TargetFolderHandle, OurReadBuffer,
436  sizeof(FILE_NOTIFY_INFORMATION)*100,
437  // Only the top level directory is watched
438  FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, OverlappedInfo, NULL);
439 
440  if(!createresult){
441 
442  Logger::Get()->Error("ResourceFolderListener: _RunListeningThread: "
443  "re-queuing file change read failed: "+
444  Convert::ToHexadecimalString(GetLastError()));
445  return;
446  }
447 
448  }
449  break;
450 
451  case WAIT_TIMEOUT:
452  // NO such thing, continue!
453  continue;
454 
455  default:
456  Logger::Get()->Error("ResourceFolderListener: _RunListeningThread: invalid wait "
457  "result: "+Convert::ToString(waitstatus));
458  break;
459  }
460 
461 #else
462 
463  // Read the changes until the end of time //
464  int readcount = read(InotifyID, ReadBuffer, IN_READ_BUFFER_SIZE);
465 
466  if(readcount < 0){
467 
468  // Failed reading, quit //
469  Logger::Get()->Warning("ResourceFolderListener: read failed, quitting thread");
470  return;
471  }
472 
473  // Handle all the data //
474  for(int i = 0; i < readcount; ){
475 
476  // Break if invalid buffer //
477  if(!ReadBuffer)
478  return;
479 
480  inotify_event* event = reinterpret_cast<inotify_event*>(&ReadBuffer[i]);
481 
482  if(event->len){
483 
484  if(event->mask & IN_MODIFY){
485  if(!(event->mask & IN_ISDIR)){
486 
487  // Some file was modified, check was it one of ours //
488  const string modifiedfile(event->name, event->len);
489 
490  if(!modifiedfile.empty()){
491 
492  // Check which file matches and set it as updated //
493  for(size_t i = 0; i < ListenedFiles.size(); i++){
494 
495  if(*ListenedFiles[i] == modifiedfile){
496 
497  // Updated //
498  UpdatedFiles[i] = true;
499  break;
500  }
501  }
502  }
503  }
504  }
505  }
506 
507  i += IN_EVENT_SIZE+event->len;
508  }
509 
510 
511 
512 #endif //_Win32
513  }
514 }
515 // ------------------------------------ //
517  // Check are some updated files readable //
518  for(size_t i = 0; i < UpdatedFiles.size(); i++){
519 
520  if(UpdatedFiles[i]){
521 
522  // Check is it readable //
523  const std::string checkread = TargetFolder+*ListenedFiles[i];
524 
525  ifstream reader(checkread);
526 
527  if(reader.is_open()){
528 
529  reader.close();
530 
531  // Notify that the file is now available //
532  CallbackFunction(*ListenedFiles[i], *this);
533  UpdatedFiles[i] = false;
534  }
535  }
536  }
537 }
538 // ------------------------------------ //
540  auto end = UpdatedFiles.end();
541  for(auto iter = UpdatedFiles.begin(); iter != end; ++iter){
542 
543  (*iter) = false;
544  }
545 }
546 // ------------------------------------ //
548  // Try to find a set bool //
549  auto end = UpdatedFiles.end();
550  for(auto iter = UpdatedFiles.begin(); iter != end; ++iter){
551 
552  if((*iter)){
553 
554  return true;
555  }
556  }
557 
558  // No file is marked as updated //
559  return false;
560 }
bool StartListening()
Starts a listening thread.
std::chrono::duration< int64_t, std::milli > MillisecondDuration
Definition: TimeIncludes.h:13
static DLLEXPORT WantedClockType::time_point GetThreadSafeSteadyTimePoint()
void CheckUpdatesEnded()
Checks if files marked as updated (only the first is actually checked) are available for reading...
DLLEXPORT void CheckFileStatus()
Called by Engine to check are updated files available.
DLLEXPORT int GetID() const
Gets the ID of this object.
DLLEXPORT void StopListeningForFileChanges(int idoflistener)
Stops a listener with a specific id.
int InotifyID
The ID of our inotify instance thing.
static DLLEXPORT std::string Utf16ToUtf8(const std::wstring &utf16str)
Encodes an UTF8 string from a wide string (wstring/utf16)
Definition: Convert.cpp:108
STL namespace.
DLLEXPORT bool ListenForFileChanges(const std::vector< const std::string *> &filestowatch, std::function< void(const std::string &, ResourceFolderListener &)> notifyfunction, int &createdid)
Starts listening for changes made to filestowatch.
ResourceFolderListener(const std::vector< const std::string *> &filestowatch, std::function< void(const std::string &, ResourceFolderListener &)> notifyfunction)
Creates a new listener.
void StopThread()
Sets the internal thread to die and signals it.
DLLEXPORT void Warning(const std::string &data) override
Definition: Logger.cpp:190
Allows various resource loaders to get notified when the file on disk changes.
DLLEXPORT void MarkAllAsNotUpdated()
Marks all files as not updated.
#define LEVIATHAN_ASSERT(x, msg)
Definition: Define.h:98
static std::string ToString(const T &val)
Definition: Convert.h:72
static std::string ToHexadecimalString(const T &val)
Definition: Convert.h:93
std::string TargetFolder
The folder in which to listen for stuff.
static DLLEXPORT Logger * Get()
Definition: Logger.cpp:106
A file listener instance which listens for file changes in a folder.
DLLEXPORT void MarkListenersAsNotUpdated(const std::vector< int > &ids)
Marks all files in all listeners matching any of the IDs as not updated.
#define DLLEXPORT
Definition: Include.h:84
std::vector< std::unique_ptr< std::string > > ListenedFiles
The files which are listened for.
The access mask controls which registered functions and classes a script sees.
Definition: GameModule.h:12
DLLEXPORT bool IsAFileStillUpdated() const
Checks whether a file is still marked as updated.
#define SAFE_DELETE(x)
Definition: Define.h:147
DLLEXPORT void Error(const std::string &data) override
Definition: Logger.cpp:177
static DLLEXPORT ResourceRefreshHandler * Get()
#define GUARD_LOCK()
Definition: ThreadSafe.h:111
static ResourceRefreshHandler * Staticaccess