1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Core/Context.h"
26 #include "../Core/CoreEvents.h"
27 #include "../Core/Profiler.h"
28 #include "../Core/WorkQueue.h"
29 #include "../IO/FileSystem.h"
30 #include "../IO/FileWatcher.h"
31 #include "../IO/Log.h"
32 #include "../IO/PackageFile.h"
33 #include "../Resource/BackgroundLoader.h"
34 #include "../Resource/Image.h"
35 #include "../Resource/JSONFile.h"
36 #include "../Resource/PListFile.h"
37 #include "../Resource/ResourceCache.h"
38 #include "../Resource/ResourceEvents.h"
39 #include "../Resource/XMLFile.h"
40 
41 #include "../DebugNew.h"
42 
43 #include <cstdio>
44 
45 namespace Urho3D
46 {
47 
48 static const char* checkDirs[] =
49 {
50     "Fonts",
51     "Materials",
52     "Models",
53     "Music",
54     "Objects",
55     "Particle",
56     "PostProcess",
57     "RenderPaths",
58     "Scenes",
59     "Scripts",
60     "Sounds",
61     "Shaders",
62     "Techniques",
63     "Textures",
64     "UI",
65     0
66 };
67 
68 static const SharedPtr<Resource> noResource;
69 
ResourceCache(Context * context)70 ResourceCache::ResourceCache(Context* context) :
71     Object(context),
72     autoReloadResources_(false),
73     returnFailedResources_(false),
74     searchPackagesFirst_(true),
75     isRouting_(false),
76     finishBackgroundResourcesMs_(5)
77 {
78     // Register Resource library object factories
79     RegisterResourceLibrary(context_);
80 
81 #ifdef URHO3D_THREADING
82     // Create resource background loader. Its thread will start on the first background request
83     backgroundLoader_ = new BackgroundLoader(this);
84 #endif
85 
86     // Subscribe BeginFrame for handling directory watchers and background loaded resource finalization
87     SubscribeToEvent(E_BEGINFRAME, URHO3D_HANDLER(ResourceCache, HandleBeginFrame));
88 }
89 
~ResourceCache()90 ResourceCache::~ResourceCache()
91 {
92 #ifdef URHO3D_THREADING
93     // Shut down the background loader first
94     backgroundLoader_.Reset();
95 #endif
96 }
97 
AddResourceDir(const String & pathName,unsigned priority)98 bool ResourceCache::AddResourceDir(const String& pathName, unsigned priority)
99 {
100     MutexLock lock(resourceMutex_);
101 
102     FileSystem* fileSystem = GetSubsystem<FileSystem>();
103     if (!fileSystem || !fileSystem->DirExists(pathName))
104     {
105         URHO3D_LOGERROR("Could not open directory " + pathName);
106         return false;
107     }
108 
109     // Convert path to absolute
110     String fixedPath = SanitateResourceDirName(pathName);
111 
112     // Check that the same path does not already exist
113     for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
114     {
115         if (!resourceDirs_[i].Compare(fixedPath, false))
116             return true;
117     }
118 
119     if (priority < resourceDirs_.Size())
120         resourceDirs_.Insert(priority, fixedPath);
121     else
122         resourceDirs_.Push(fixedPath);
123 
124     // If resource auto-reloading active, create a file watcher for the directory
125     if (autoReloadResources_)
126     {
127         SharedPtr<FileWatcher> watcher(new FileWatcher(context_));
128         watcher->StartWatching(fixedPath, true);
129         fileWatchers_.Push(watcher);
130     }
131 
132     URHO3D_LOGINFO("Added resource path " + fixedPath);
133     return true;
134 }
135 
AddPackageFile(PackageFile * package,unsigned priority)136 bool ResourceCache::AddPackageFile(PackageFile* package, unsigned priority)
137 {
138     MutexLock lock(resourceMutex_);
139 
140     // Do not add packages that failed to load
141     if (!package || !package->GetNumFiles())
142     {
143         URHO3D_LOGERRORF("Could not add package file %s due to load failure", package->GetName().CString());
144         return false;
145     }
146 
147     if (priority < packages_.Size())
148         packages_.Insert(priority, SharedPtr<PackageFile>(package));
149     else
150         packages_.Push(SharedPtr<PackageFile>(package));
151 
152     URHO3D_LOGINFO("Added resource package " + package->GetName());
153     return true;
154 }
155 
AddPackageFile(const String & fileName,unsigned priority)156 bool ResourceCache::AddPackageFile(const String& fileName, unsigned priority)
157 {
158     SharedPtr<PackageFile> package(new PackageFile(context_));
159     return package->Open(fileName) && AddPackageFile(package);
160 }
161 
AddManualResource(Resource * resource)162 bool ResourceCache::AddManualResource(Resource* resource)
163 {
164     if (!resource)
165     {
166         URHO3D_LOGERROR("Null manual resource");
167         return false;
168     }
169 
170     const String& name = resource->GetName();
171     if (name.Empty())
172     {
173         URHO3D_LOGERROR("Manual resource with empty name, can not add");
174         return false;
175     }
176 
177     resource->ResetUseTimer();
178     resourceGroups_[resource->GetType()].resources_[resource->GetNameHash()] = resource;
179     UpdateResourceGroup(resource->GetType());
180     return true;
181 }
182 
RemoveResourceDir(const String & pathName)183 void ResourceCache::RemoveResourceDir(const String& pathName)
184 {
185     MutexLock lock(resourceMutex_);
186 
187     String fixedPath = SanitateResourceDirName(pathName);
188 
189     for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
190     {
191         if (!resourceDirs_[i].Compare(fixedPath, false))
192         {
193             resourceDirs_.Erase(i);
194             // Remove the filewatcher with the matching path
195             for (unsigned j = 0; j < fileWatchers_.Size(); ++j)
196             {
197                 if (!fileWatchers_[j]->GetPath().Compare(fixedPath, false))
198                 {
199                     fileWatchers_.Erase(j);
200                     break;
201                 }
202             }
203             URHO3D_LOGINFO("Removed resource path " + fixedPath);
204             return;
205         }
206     }
207 }
208 
RemovePackageFile(PackageFile * package,bool releaseResources,bool forceRelease)209 void ResourceCache::RemovePackageFile(PackageFile* package, bool releaseResources, bool forceRelease)
210 {
211     MutexLock lock(resourceMutex_);
212 
213     for (Vector<SharedPtr<PackageFile> >::Iterator i = packages_.Begin(); i != packages_.End(); ++i)
214     {
215         if (*i == package)
216         {
217             if (releaseResources)
218                 ReleasePackageResources(*i, forceRelease);
219             URHO3D_LOGINFO("Removed resource package " + (*i)->GetName());
220             packages_.Erase(i);
221             return;
222         }
223     }
224 }
225 
RemovePackageFile(const String & fileName,bool releaseResources,bool forceRelease)226 void ResourceCache::RemovePackageFile(const String& fileName, bool releaseResources, bool forceRelease)
227 {
228     MutexLock lock(resourceMutex_);
229 
230     // Compare the name and extension only, not the path
231     String fileNameNoPath = GetFileNameAndExtension(fileName);
232 
233     for (Vector<SharedPtr<PackageFile> >::Iterator i = packages_.Begin(); i != packages_.End(); ++i)
234     {
235         if (!GetFileNameAndExtension((*i)->GetName()).Compare(fileNameNoPath, false))
236         {
237             if (releaseResources)
238                 ReleasePackageResources(*i, forceRelease);
239             URHO3D_LOGINFO("Removed resource package " + (*i)->GetName());
240             packages_.Erase(i);
241             return;
242         }
243     }
244 }
245 
ReleaseResource(StringHash type,const String & name,bool force)246 void ResourceCache::ReleaseResource(StringHash type, const String& name, bool force)
247 {
248     StringHash nameHash(name);
249     const SharedPtr<Resource>& existingRes = FindResource(type, nameHash);
250     if (!existingRes)
251         return;
252 
253     // If other references exist, do not release, unless forced
254     if ((existingRes.Refs() == 1 && existingRes.WeakRefs() == 0) || force)
255     {
256         resourceGroups_[type].resources_.Erase(nameHash);
257         UpdateResourceGroup(type);
258     }
259 }
260 
ReleaseResources(StringHash type,bool force)261 void ResourceCache::ReleaseResources(StringHash type, bool force)
262 {
263     bool released = false;
264 
265     HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
266     if (i != resourceGroups_.End())
267     {
268         for (HashMap<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Begin();
269              j != i->second_.resources_.End();)
270         {
271             HashMap<StringHash, SharedPtr<Resource> >::Iterator current = j++;
272             // If other references exist, do not release, unless forced
273             if ((current->second_.Refs() == 1 && current->second_.WeakRefs() == 0) || force)
274             {
275                 i->second_.resources_.Erase(current);
276                 released = true;
277             }
278         }
279     }
280 
281     if (released)
282         UpdateResourceGroup(type);
283 }
284 
ReleaseResources(StringHash type,const String & partialName,bool force)285 void ResourceCache::ReleaseResources(StringHash type, const String& partialName, bool force)
286 {
287     bool released = false;
288 
289     HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
290     if (i != resourceGroups_.End())
291     {
292         for (HashMap<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Begin();
293              j != i->second_.resources_.End();)
294         {
295             HashMap<StringHash, SharedPtr<Resource> >::Iterator current = j++;
296             if (current->second_->GetName().Contains(partialName))
297             {
298                 // If other references exist, do not release, unless forced
299                 if ((current->second_.Refs() == 1 && current->second_.WeakRefs() == 0) || force)
300                 {
301                     i->second_.resources_.Erase(current);
302                     released = true;
303                 }
304             }
305         }
306     }
307 
308     if (released)
309         UpdateResourceGroup(type);
310 }
311 
ReleaseResources(const String & partialName,bool force)312 void ResourceCache::ReleaseResources(const String& partialName, bool force)
313 {
314     // Some resources refer to others, like materials to textures. Release twice to ensure these get released.
315     // This is not necessary if forcing release
316     unsigned repeat = force ? 1 : 2;
317 
318     while (repeat--)
319     {
320         for (HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Begin(); i != resourceGroups_.End(); ++i)
321         {
322             bool released = false;
323 
324             for (HashMap<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Begin();
325                  j != i->second_.resources_.End();)
326             {
327                 HashMap<StringHash, SharedPtr<Resource> >::Iterator current = j++;
328                 if (current->second_->GetName().Contains(partialName))
329                 {
330                     // If other references exist, do not release, unless forced
331                     if ((current->second_.Refs() == 1 && current->second_.WeakRefs() == 0) || force)
332                     {
333                         i->second_.resources_.Erase(current);
334                         released = true;
335                     }
336                 }
337             }
338             if (released)
339                 UpdateResourceGroup(i->first_);
340         }
341     }
342 }
343 
ReleaseAllResources(bool force)344 void ResourceCache::ReleaseAllResources(bool force)
345 {
346     unsigned repeat = force ? 1 : 2;
347 
348     while (repeat--)
349     {
350         for (HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Begin();
351              i != resourceGroups_.End(); ++i)
352         {
353             bool released = false;
354 
355             for (HashMap<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Begin();
356                  j != i->second_.resources_.End();)
357             {
358                 HashMap<StringHash, SharedPtr<Resource> >::Iterator current = j++;
359                 // If other references exist, do not release, unless forced
360                 if ((current->second_.Refs() == 1 && current->second_.WeakRefs() == 0) || force)
361                 {
362                     i->second_.resources_.Erase(current);
363                     released = true;
364                 }
365             }
366             if (released)
367                 UpdateResourceGroup(i->first_);
368         }
369     }
370 }
371 
ReloadResource(Resource * resource)372 bool ResourceCache::ReloadResource(Resource* resource)
373 {
374     if (!resource)
375         return false;
376 
377     resource->SendEvent(E_RELOADSTARTED);
378 
379     bool success = false;
380     SharedPtr<File> file = GetFile(resource->GetName());
381     if (file)
382         success = resource->Load(*(file.Get()));
383 
384     if (success)
385     {
386         resource->ResetUseTimer();
387         UpdateResourceGroup(resource->GetType());
388         resource->SendEvent(E_RELOADFINISHED);
389         return true;
390     }
391 
392     // If reloading failed, do not remove the resource from cache, to allow for a new live edit to
393     // attempt loading again
394     resource->SendEvent(E_RELOADFAILED);
395     return false;
396 }
397 
ReloadResourceWithDependencies(const String & fileName)398 void ResourceCache::ReloadResourceWithDependencies(const String& fileName)
399 {
400     StringHash fileNameHash(fileName);
401     // If the filename is a resource we keep track of, reload it
402     const SharedPtr<Resource>& resource = FindResource(fileNameHash);
403     if (resource)
404     {
405         URHO3D_LOGDEBUG("Reloading changed resource " + fileName);
406         ReloadResource(resource);
407     }
408     // Always perform dependency resource check for resource loaded from XML file as it could be used in inheritance
409     if (!resource || GetExtension(resource->GetName()) == ".xml")
410     {
411         // Check if this is a dependency resource, reload dependents
412         HashMap<StringHash, HashSet<StringHash> >::ConstIterator j = dependentResources_.Find(fileNameHash);
413         if (j != dependentResources_.End())
414         {
415             // Reloading a resource may modify the dependency tracking structure. Therefore collect the
416             // resources we need to reload first
417             Vector<SharedPtr<Resource> > dependents;
418             dependents.Reserve(j->second_.Size());
419 
420             for (HashSet<StringHash>::ConstIterator k = j->second_.Begin(); k != j->second_.End(); ++k)
421             {
422                 const SharedPtr<Resource>& dependent = FindResource(*k);
423                 if (dependent)
424                     dependents.Push(dependent);
425             }
426 
427             for (unsigned k = 0; k < dependents.Size(); ++k)
428             {
429                 URHO3D_LOGDEBUG("Reloading resource " + dependents[k]->GetName() + " depending on " + fileName);
430                 ReloadResource(dependents[k]);
431             }
432         }
433     }
434 }
435 
SetMemoryBudget(StringHash type,unsigned long long budget)436 void ResourceCache::SetMemoryBudget(StringHash type, unsigned long long budget)
437 {
438     resourceGroups_[type].memoryBudget_ = budget;
439 }
440 
SetAutoReloadResources(bool enable)441 void ResourceCache::SetAutoReloadResources(bool enable)
442 {
443     if (enable != autoReloadResources_)
444     {
445         if (enable)
446         {
447             for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
448             {
449                 SharedPtr<FileWatcher> watcher(new FileWatcher(context_));
450                 watcher->StartWatching(resourceDirs_[i], true);
451                 fileWatchers_.Push(watcher);
452             }
453         }
454         else
455             fileWatchers_.Clear();
456 
457         autoReloadResources_ = enable;
458     }
459 }
460 
AddResourceRouter(ResourceRouter * router,bool addAsFirst)461 void ResourceCache::AddResourceRouter(ResourceRouter* router, bool addAsFirst)
462 {
463     // Check for duplicate
464     for (unsigned i = 0; i < resourceRouters_.Size(); ++i)
465     {
466         if (resourceRouters_[i] == router)
467             return;
468     }
469 
470     if (addAsFirst)
471         resourceRouters_.Insert(0, SharedPtr<ResourceRouter>(router));
472     else
473         resourceRouters_.Push(SharedPtr<ResourceRouter>(router));
474 }
475 
RemoveResourceRouter(ResourceRouter * router)476 void ResourceCache::RemoveResourceRouter(ResourceRouter* router)
477 {
478     for (unsigned i = 0; i < resourceRouters_.Size(); ++i)
479     {
480         if (resourceRouters_[i] == router)
481         {
482             resourceRouters_.Erase(i);
483             return;
484         }
485     }
486 }
487 
GetFile(const String & nameIn,bool sendEventOnFailure)488 SharedPtr<File> ResourceCache::GetFile(const String& nameIn, bool sendEventOnFailure)
489 {
490     MutexLock lock(resourceMutex_);
491 
492     String name = SanitateResourceName(nameIn);
493     if (!isRouting_)
494     {
495         isRouting_ = true;
496         for (unsigned i = 0; i < resourceRouters_.Size(); ++i)
497             resourceRouters_[i]->Route(name, RESOURCE_GETFILE);
498         isRouting_ = false;
499     }
500 
501     if (name.Length())
502     {
503         File* file = 0;
504 
505         if (searchPackagesFirst_)
506         {
507             file = SearchPackages(name);
508             if (!file)
509                 file = SearchResourceDirs(name);
510         }
511         else
512         {
513             file = SearchResourceDirs(name);
514             if (!file)
515                 file = SearchPackages(name);
516         }
517 
518         if (file)
519             return SharedPtr<File>(file);
520     }
521 
522     if (sendEventOnFailure)
523     {
524         if (resourceRouters_.Size() && name.Empty() && !nameIn.Empty())
525             URHO3D_LOGERROR("Resource request " + nameIn + " was blocked");
526         else
527             URHO3D_LOGERROR("Could not find resource " + name);
528 
529         if (Thread::IsMainThread())
530         {
531             using namespace ResourceNotFound;
532 
533             VariantMap& eventData = GetEventDataMap();
534             eventData[P_RESOURCENAME] = name.Length() ? name : nameIn;
535             SendEvent(E_RESOURCENOTFOUND, eventData);
536         }
537     }
538 
539     return SharedPtr<File>();
540 }
541 
GetExistingResource(StringHash type,const String & nameIn)542 Resource* ResourceCache::GetExistingResource(StringHash type, const String& nameIn)
543 {
544     String name = SanitateResourceName(nameIn);
545 
546     if (!Thread::IsMainThread())
547     {
548         URHO3D_LOGERROR("Attempted to get resource " + name + " from outside the main thread");
549         return 0;
550     }
551 
552     // If empty name, return null pointer immediately
553     if (name.Empty())
554         return 0;
555 
556     StringHash nameHash(name);
557 
558     const SharedPtr<Resource>& existing = FindResource(type, nameHash);
559     return existing;
560 }
561 
GetResource(StringHash type,const String & nameIn,bool sendEventOnFailure)562 Resource* ResourceCache::GetResource(StringHash type, const String& nameIn, bool sendEventOnFailure)
563 {
564     String name = SanitateResourceName(nameIn);
565 
566     if (!Thread::IsMainThread())
567     {
568         URHO3D_LOGERROR("Attempted to get resource " + name + " from outside the main thread");
569         return 0;
570     }
571 
572     // If empty name, return null pointer immediately
573     if (name.Empty())
574         return 0;
575 
576     StringHash nameHash(name);
577 
578 #ifdef URHO3D_THREADING
579     // Check if the resource is being background loaded but is now needed immediately
580     backgroundLoader_->WaitForResource(type, nameHash);
581 #endif
582 
583     const SharedPtr<Resource>& existing = FindResource(type, nameHash);
584     if (existing)
585         return existing;
586 
587     SharedPtr<Resource> resource;
588     // Make sure the pointer is non-null and is a Resource subclass
589     resource = DynamicCast<Resource>(context_->CreateObject(type));
590     if (!resource)
591     {
592         URHO3D_LOGERROR("Could not load unknown resource type " + String(type));
593 
594         if (sendEventOnFailure)
595         {
596             using namespace UnknownResourceType;
597 
598             VariantMap& eventData = GetEventDataMap();
599             eventData[P_RESOURCETYPE] = type;
600             SendEvent(E_UNKNOWNRESOURCETYPE, eventData);
601         }
602 
603         return 0;
604     }
605 
606     // Attempt to load the resource
607     SharedPtr<File> file = GetFile(name, sendEventOnFailure);
608     if (!file)
609         return 0;   // Error is already logged
610 
611     URHO3D_LOGDEBUG("Loading resource " + name);
612     resource->SetName(name);
613 
614     if (!resource->Load(*(file.Get())))
615     {
616         // Error should already been logged by corresponding resource descendant class
617         if (sendEventOnFailure)
618         {
619             using namespace LoadFailed;
620 
621             VariantMap& eventData = GetEventDataMap();
622             eventData[P_RESOURCENAME] = name;
623             SendEvent(E_LOADFAILED, eventData);
624         }
625 
626         if (!returnFailedResources_)
627             return 0;
628     }
629 
630     // Store to cache
631     resource->ResetUseTimer();
632     resourceGroups_[type].resources_[nameHash] = resource;
633     UpdateResourceGroup(type);
634 
635     return resource;
636 }
637 
BackgroundLoadResource(StringHash type,const String & nameIn,bool sendEventOnFailure,Resource * caller)638 bool ResourceCache::BackgroundLoadResource(StringHash type, const String& nameIn, bool sendEventOnFailure, Resource* caller)
639 {
640 #ifdef URHO3D_THREADING
641     // If empty name, fail immediately
642     String name = SanitateResourceName(nameIn);
643     if (name.Empty())
644         return false;
645 
646     // First check if already exists as a loaded resource
647     StringHash nameHash(name);
648     if (FindResource(type, nameHash) != noResource)
649         return false;
650 
651     return backgroundLoader_->QueueResource(type, name, sendEventOnFailure, caller);
652 #else
653     // When threading not supported, fall back to synchronous loading
654     return GetResource(type, nameIn, sendEventOnFailure);
655 #endif
656 }
657 
GetTempResource(StringHash type,const String & nameIn,bool sendEventOnFailure)658 SharedPtr<Resource> ResourceCache::GetTempResource(StringHash type, const String& nameIn, bool sendEventOnFailure)
659 {
660     String name = SanitateResourceName(nameIn);
661 
662     // If empty name, return null pointer immediately
663     if (name.Empty())
664         return SharedPtr<Resource>();
665 
666     SharedPtr<Resource> resource;
667     // Make sure the pointer is non-null and is a Resource subclass
668     resource = DynamicCast<Resource>(context_->CreateObject(type));
669     if (!resource)
670     {
671         URHO3D_LOGERROR("Could not load unknown resource type " + String(type));
672 
673         if (sendEventOnFailure)
674         {
675             using namespace UnknownResourceType;
676 
677             VariantMap& eventData = GetEventDataMap();
678             eventData[P_RESOURCETYPE] = type;
679             SendEvent(E_UNKNOWNRESOURCETYPE, eventData);
680         }
681 
682         return SharedPtr<Resource>();
683     }
684 
685     // Attempt to load the resource
686     SharedPtr<File> file = GetFile(name, sendEventOnFailure);
687     if (!file)
688         return SharedPtr<Resource>();  // Error is already logged
689 
690     URHO3D_LOGDEBUG("Loading temporary resource " + name);
691     resource->SetName(file->GetName());
692 
693     if (!resource->Load(*(file.Get())))
694     {
695         // Error should already been logged by corresponding resource descendant class
696         if (sendEventOnFailure)
697         {
698             using namespace LoadFailed;
699 
700             VariantMap& eventData = GetEventDataMap();
701             eventData[P_RESOURCENAME] = name;
702             SendEvent(E_LOADFAILED, eventData);
703         }
704 
705         return SharedPtr<Resource>();
706     }
707 
708     return resource;
709 }
710 
GetNumBackgroundLoadResources() const711 unsigned ResourceCache::GetNumBackgroundLoadResources() const
712 {
713 #ifdef URHO3D_THREADING
714     return backgroundLoader_->GetNumQueuedResources();
715 #else
716     return 0;
717 #endif
718 }
719 
GetResources(PODVector<Resource * > & result,StringHash type) const720 void ResourceCache::GetResources(PODVector<Resource*>& result, StringHash type) const
721 {
722     result.Clear();
723     HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Find(type);
724     if (i != resourceGroups_.End())
725     {
726         for (HashMap<StringHash, SharedPtr<Resource> >::ConstIterator j = i->second_.resources_.Begin();
727              j != i->second_.resources_.End(); ++j)
728             result.Push(j->second_);
729     }
730 }
731 
Exists(const String & nameIn) const732 bool ResourceCache::Exists(const String& nameIn) const
733 {
734     MutexLock lock(resourceMutex_);
735 
736     String name = SanitateResourceName(nameIn);
737     if (!isRouting_)
738     {
739         isRouting_ = true;
740         for (unsigned i = 0; i < resourceRouters_.Size(); ++i)
741             resourceRouters_[i]->Route(name, RESOURCE_CHECKEXISTS);
742         isRouting_ = false;
743     }
744 
745     if (name.Empty())
746         return false;
747 
748     for (unsigned i = 0; i < packages_.Size(); ++i)
749     {
750         if (packages_[i]->Exists(name))
751             return true;
752     }
753 
754     FileSystem* fileSystem = GetSubsystem<FileSystem>();
755     for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
756     {
757         if (fileSystem->FileExists(resourceDirs_[i] + name))
758             return true;
759     }
760 
761     // Fallback using absolute path
762     return fileSystem->FileExists(name);
763 }
764 
GetMemoryBudget(StringHash type) const765 unsigned long long ResourceCache::GetMemoryBudget(StringHash type) const
766 {
767     HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Find(type);
768     return i != resourceGroups_.End() ? i->second_.memoryBudget_ : 0;
769 }
770 
GetMemoryUse(StringHash type) const771 unsigned long long ResourceCache::GetMemoryUse(StringHash type) const
772 {
773     HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Find(type);
774     return i != resourceGroups_.End() ? i->second_.memoryUse_ : 0;
775 }
776 
GetTotalMemoryUse() const777 unsigned long long ResourceCache::GetTotalMemoryUse() const
778 {
779     unsigned long long total = 0;
780     for (HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups_.Begin(); i != resourceGroups_.End(); ++i)
781         total += i->second_.memoryUse_;
782     return total;
783 }
784 
GetResourceFileName(const String & name) const785 String ResourceCache::GetResourceFileName(const String& name) const
786 {
787     FileSystem* fileSystem = GetSubsystem<FileSystem>();
788     for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
789     {
790         if (fileSystem->FileExists(resourceDirs_[i] + name))
791             return resourceDirs_[i] + name;
792     }
793 
794     if (IsAbsolutePath(name) && fileSystem->FileExists(name))
795         return name;
796     else
797         return String();
798 }
799 
GetResourceRouter(unsigned index) const800 ResourceRouter* ResourceCache::GetResourceRouter(unsigned index) const
801 {
802     return index < resourceRouters_.Size() ? resourceRouters_[index] : (ResourceRouter*)0;
803 }
804 
GetPreferredResourceDir(const String & path) const805 String ResourceCache::GetPreferredResourceDir(const String& path) const
806 {
807     String fixedPath = AddTrailingSlash(path);
808 
809     bool pathHasKnownDirs = false;
810     bool parentHasKnownDirs = false;
811 
812     FileSystem* fileSystem = GetSubsystem<FileSystem>();
813 
814     for (unsigned i = 0; checkDirs[i] != 0; ++i)
815     {
816         if (fileSystem->DirExists(fixedPath + checkDirs[i]))
817         {
818             pathHasKnownDirs = true;
819             break;
820         }
821     }
822     if (!pathHasKnownDirs)
823     {
824         String parentPath = GetParentPath(fixedPath);
825         for (unsigned i = 0; checkDirs[i] != 0; ++i)
826         {
827             if (fileSystem->DirExists(parentPath + checkDirs[i]))
828             {
829                 parentHasKnownDirs = true;
830                 break;
831             }
832         }
833         // If path does not have known subdirectories, but the parent path has, use the parent instead
834         if (parentHasKnownDirs)
835             fixedPath = parentPath;
836     }
837 
838     return fixedPath;
839 }
840 
SanitateResourceName(const String & nameIn) const841 String ResourceCache::SanitateResourceName(const String& nameIn) const
842 {
843     // Sanitate unsupported constructs from the resource name
844     String name = GetInternalPath(nameIn);
845     name.Replace("../", "");
846     name.Replace("./", "");
847 
848     // If the path refers to one of the resource directories, normalize the resource name
849     FileSystem* fileSystem = GetSubsystem<FileSystem>();
850     if (resourceDirs_.Size())
851     {
852         String namePath = GetPath(name);
853         String exePath = fileSystem->GetProgramDir().Replaced("/./", "/");
854         for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
855         {
856             String relativeResourcePath = resourceDirs_[i];
857             if (relativeResourcePath.StartsWith(exePath))
858                 relativeResourcePath = relativeResourcePath.Substring(exePath.Length());
859 
860             if (namePath.StartsWith(resourceDirs_[i], false))
861                 namePath = namePath.Substring(resourceDirs_[i].Length());
862             else if (namePath.StartsWith(relativeResourcePath, false))
863                 namePath = namePath.Substring(relativeResourcePath.Length());
864         }
865 
866         name = namePath + GetFileNameAndExtension(name);
867     }
868 
869     return name.Trimmed();
870 }
871 
SanitateResourceDirName(const String & nameIn) const872 String ResourceCache::SanitateResourceDirName(const String& nameIn) const
873 {
874     String fixedPath = AddTrailingSlash(nameIn);
875     if (!IsAbsolutePath(fixedPath))
876         fixedPath = GetSubsystem<FileSystem>()->GetCurrentDir() + fixedPath;
877 
878     // Sanitate away /./ construct
879     fixedPath.Replace("/./", "/");
880 
881     return fixedPath.Trimmed();
882 }
883 
StoreResourceDependency(Resource * resource,const String & dependency)884 void ResourceCache::StoreResourceDependency(Resource* resource, const String& dependency)
885 {
886     if (!resource)
887         return;
888 
889     MutexLock lock(resourceMutex_);
890 
891     StringHash nameHash(resource->GetName());
892     HashSet<StringHash>& dependents = dependentResources_[dependency];
893     dependents.Insert(nameHash);
894 }
895 
ResetDependencies(Resource * resource)896 void ResourceCache::ResetDependencies(Resource* resource)
897 {
898     if (!resource)
899         return;
900 
901     MutexLock lock(resourceMutex_);
902 
903     StringHash nameHash(resource->GetName());
904 
905     for (HashMap<StringHash, HashSet<StringHash> >::Iterator i = dependentResources_.Begin(); i != dependentResources_.End();)
906     {
907         HashSet<StringHash>& dependents = i->second_;
908         dependents.Erase(nameHash);
909         if (dependents.Empty())
910             i = dependentResources_.Erase(i);
911         else
912             ++i;
913     }
914 }
915 
PrintMemoryUsage() const916 String ResourceCache::PrintMemoryUsage() const
917 {
918     String output = "Resource Type                 Cnt       Avg       Max    Budget     Total\n\n";
919     char outputLine[256];
920 
921     unsigned totalResourceCt = 0;
922     unsigned long long totalLargest = 0;
923     unsigned long long totalAverage = 0;
924     unsigned long long totalUse = GetTotalMemoryUse();
925 
926     for (HashMap<StringHash, ResourceGroup>::ConstIterator cit = resourceGroups_.Begin(); cit != resourceGroups_.End(); ++cit)
927     {
928         const unsigned resourceCt = cit->second_.resources_.Size();
929         unsigned long long average = 0;
930         if (resourceCt > 0)
931             average = cit->second_.memoryUse_ / resourceCt;
932         else
933             average = 0;
934         unsigned long long largest = 0;
935         for (HashMap<StringHash, SharedPtr<Resource> >::ConstIterator resIt = cit->second_.resources_.Begin(); resIt != cit->second_.resources_.End(); ++resIt)
936         {
937             if (resIt->second_->GetMemoryUse() > largest)
938                 largest = resIt->second_->GetMemoryUse();
939             if (largest > totalLargest)
940                 totalLargest = largest;
941         }
942 
943         totalResourceCt += resourceCt;
944 
945         const String countString(cit->second_.resources_.Size());
946         const String memUseString = GetFileSizeString(average);
947         const String memMaxString = GetFileSizeString(largest);
948         const String memBudgetString = GetFileSizeString(cit->second_.memoryBudget_);
949         const String memTotalString = GetFileSizeString(cit->second_.memoryUse_);
950         const String resTypeName = context_->GetTypeName(cit->first_);
951 
952         memset(outputLine, ' ', 256);
953         outputLine[255] = 0;
954         sprintf(outputLine, "%-28s %4s %9s %9s %9s %9s\n", resTypeName.CString(), countString.CString(), memUseString.CString(), memMaxString.CString(), memBudgetString.CString(), memTotalString.CString());
955 
956         output += ((const char*)outputLine);
957     }
958 
959     if (totalResourceCt > 0)
960         totalAverage = totalUse / totalResourceCt;
961 
962     const String countString(totalResourceCt);
963     const String memUseString = GetFileSizeString(totalAverage);
964     const String memMaxString = GetFileSizeString(totalLargest);
965     const String memTotalString = GetFileSizeString(totalUse);
966 
967     memset(outputLine, ' ', 256);
968     outputLine[255] = 0;
969     sprintf(outputLine, "%-28s %4s %9s %9s %9s %9s\n", "All", countString.CString(), memUseString.CString(), memMaxString.CString(), "-", memTotalString.CString());
970     output += ((const char*)outputLine);
971 
972     return output;
973 }
974 
FindResource(StringHash type,StringHash nameHash)975 const SharedPtr<Resource>& ResourceCache::FindResource(StringHash type, StringHash nameHash)
976 {
977     MutexLock lock(resourceMutex_);
978 
979     HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
980     if (i == resourceGroups_.End())
981         return noResource;
982     HashMap<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Find(nameHash);
983     if (j == i->second_.resources_.End())
984         return noResource;
985 
986     return j->second_;
987 }
988 
FindResource(StringHash nameHash)989 const SharedPtr<Resource>& ResourceCache::FindResource(StringHash nameHash)
990 {
991     MutexLock lock(resourceMutex_);
992 
993     for (HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Begin(); i != resourceGroups_.End(); ++i)
994     {
995         HashMap<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Find(nameHash);
996         if (j != i->second_.resources_.End())
997             return j->second_;
998     }
999 
1000     return noResource;
1001 }
1002 
ReleasePackageResources(PackageFile * package,bool force)1003 void ResourceCache::ReleasePackageResources(PackageFile* package, bool force)
1004 {
1005     HashSet<StringHash> affectedGroups;
1006 
1007     const HashMap<String, PackageEntry>& entries = package->GetEntries();
1008     for (HashMap<String, PackageEntry>::ConstIterator i = entries.Begin(); i != entries.End(); ++i)
1009     {
1010         StringHash nameHash(i->first_);
1011 
1012         // We do not know the actual resource type, so search all type containers
1013         for (HashMap<StringHash, ResourceGroup>::Iterator j = resourceGroups_.Begin(); j != resourceGroups_.End(); ++j)
1014         {
1015             HashMap<StringHash, SharedPtr<Resource> >::Iterator k = j->second_.resources_.Find(nameHash);
1016             if (k != j->second_.resources_.End())
1017             {
1018                 // If other references exist, do not release, unless forced
1019                 if ((k->second_.Refs() == 1 && k->second_.WeakRefs() == 0) || force)
1020                 {
1021                     j->second_.resources_.Erase(k);
1022                     affectedGroups.Insert(j->first_);
1023                 }
1024                 break;
1025             }
1026         }
1027     }
1028 
1029     for (HashSet<StringHash>::Iterator i = affectedGroups.Begin(); i != affectedGroups.End(); ++i)
1030         UpdateResourceGroup(*i);
1031 }
1032 
UpdateResourceGroup(StringHash type)1033 void ResourceCache::UpdateResourceGroup(StringHash type)
1034 {
1035     HashMap<StringHash, ResourceGroup>::Iterator i = resourceGroups_.Find(type);
1036     if (i == resourceGroups_.End())
1037         return;
1038 
1039     for (;;)
1040     {
1041         unsigned totalSize = 0;
1042         unsigned oldestTimer = 0;
1043         HashMap<StringHash, SharedPtr<Resource> >::Iterator oldestResource = i->second_.resources_.End();
1044 
1045         for (HashMap<StringHash, SharedPtr<Resource> >::Iterator j = i->second_.resources_.Begin();
1046              j != i->second_.resources_.End(); ++j)
1047         {
1048             totalSize += j->second_->GetMemoryUse();
1049             unsigned useTimer = j->second_->GetUseTimer();
1050             if (useTimer > oldestTimer)
1051             {
1052                 oldestTimer = useTimer;
1053                 oldestResource = j;
1054             }
1055         }
1056 
1057         i->second_.memoryUse_ = totalSize;
1058 
1059         // If memory budget defined and is exceeded, remove the oldest resource and loop again
1060         // (resources in use always return a zero timer and can not be removed)
1061         if (i->second_.memoryBudget_ && i->second_.memoryUse_ > i->second_.memoryBudget_ &&
1062             oldestResource != i->second_.resources_.End())
1063         {
1064             URHO3D_LOGDEBUG("Resource group " + oldestResource->second_->GetTypeName() + " over memory budget, releasing resource " +
1065                      oldestResource->second_->GetName());
1066             i->second_.resources_.Erase(oldestResource);
1067         }
1068         else
1069             break;
1070     }
1071 }
1072 
HandleBeginFrame(StringHash eventType,VariantMap & eventData)1073 void ResourceCache::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
1074 {
1075     for (unsigned i = 0; i < fileWatchers_.Size(); ++i)
1076     {
1077         String fileName;
1078         while (fileWatchers_[i]->GetNextChange(fileName))
1079         {
1080             ReloadResourceWithDependencies(fileName);
1081 
1082             // Finally send a general file changed event even if the file was not a tracked resource
1083             using namespace FileChanged;
1084 
1085             VariantMap& eventData = GetEventDataMap();
1086             eventData[P_FILENAME] = fileWatchers_[i]->GetPath() + fileName;
1087             eventData[P_RESOURCENAME] = fileName;
1088             SendEvent(E_FILECHANGED, eventData);
1089         }
1090     }
1091 
1092     // Check for background loaded resources that can be finished
1093 #ifdef URHO3D_THREADING
1094     {
1095         URHO3D_PROFILE(FinishBackgroundResources);
1096         backgroundLoader_->FinishResources(finishBackgroundResourcesMs_);
1097     }
1098 #endif
1099 }
1100 
SearchResourceDirs(const String & nameIn)1101 File* ResourceCache::SearchResourceDirs(const String& nameIn)
1102 {
1103     FileSystem* fileSystem = GetSubsystem<FileSystem>();
1104     for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
1105     {
1106         if (fileSystem->FileExists(resourceDirs_[i] + nameIn))
1107         {
1108             // Construct the file first with full path, then rename it to not contain the resource path,
1109             // so that the file's name can be used in further GetFile() calls (for example over the network)
1110             File* file(new File(context_, resourceDirs_[i] + nameIn));
1111             file->SetName(nameIn);
1112             return file;
1113         }
1114     }
1115 
1116     // Fallback using absolute path
1117     if (fileSystem->FileExists(nameIn))
1118         return new File(context_, nameIn);
1119 
1120     return 0;
1121 }
1122 
SearchPackages(const String & nameIn)1123 File* ResourceCache::SearchPackages(const String& nameIn)
1124 {
1125     for (unsigned i = 0; i < packages_.Size(); ++i)
1126     {
1127         if (packages_[i]->Exists(nameIn))
1128             return new File(context_, packages_[i], nameIn);
1129     }
1130 
1131     return 0;
1132 }
1133 
RegisterResourceLibrary(Context * context)1134 void RegisterResourceLibrary(Context* context)
1135 {
1136     Image::RegisterObject(context);
1137     JSONFile::RegisterObject(context);
1138     PListFile::RegisterObject(context);
1139     XMLFile::RegisterObject(context);
1140 }
1141 
1142 }
1143