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