1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Resources/BsResources.h"
4#include "Resources/BsResource.h"
5#include "Resources/BsResourceManifest.h"
6#include "Error/BsException.h"
7#include "Serialization/BsFileSerializer.h"
8#include "FileSystem/BsFileSystem.h"
9#include "Threading/BsTaskScheduler.h"
10#include "Utility/BsUUID.h"
11#include "Debug/BsDebug.h"
12#include "Utility/BsUtility.h"
13#include "Resources/BsSavedResourceData.h"
14#include "Managers/BsResourceListenerManager.h"
15#include "Serialization/BsMemorySerializer.h"
16#include "Utility/BsCompression.h"
17#include "FileSystem/BsDataStream.h"
18#include "Serialization/BsBinarySerializer.h"
19#include "Reflection/BsRTTIType.h"
20#include "BsCoreApplication.h"
21
22namespace bs
23{
24 Resources::Resources()
25 {
26 {
27 Lock lock(mDefaultManifestMutex);
28 mDefaultResourceManifest = ResourceManifest::create("Default");
29 mResourceManifests.push_back(mDefaultResourceManifest);
30 }
31 }
32
33 Resources::~Resources()
34 {
35 unloadAll();
36 }
37
38 HResource Resources::load(const Path& filePath, ResourceLoadFlags loadFlags)
39 {
40 if (!FileSystem::isFile(filePath))
41 {
42 LOGWRN("Cannot load resource. Specified file: " + filePath.toString() + " doesn't exist.");
43
44 return HResource();
45 }
46
47 UUID uuid;
48 bool foundUUID = getUUIDFromFilePath(filePath, uuid);
49
50 if (!foundUUID)
51 uuid = UUIDGenerator::generateRandom();
52
53 return loadInternal(uuid, filePath, true, loadFlags).resource;
54 }
55
56 HResource Resources::load(const WeakResourceHandle<Resource>& handle, ResourceLoadFlags loadFlags)
57 {
58 if (handle.mData == nullptr)
59 return HResource();
60
61 UUID uuid = handle.getUUID();
62 return loadFromUUID(uuid, false, loadFlags);
63 }
64
65 HResource Resources::loadAsync(const Path& filePath, ResourceLoadFlags loadFlags)
66 {
67 if (!FileSystem::isFile(filePath))
68 {
69 LOGWRN("Cannot load resource. Specified file: " + filePath.toString() + " doesn't exist.");
70
71 return HResource();
72 }
73
74 UUID uuid;
75 bool foundUUID = getUUIDFromFilePath(filePath, uuid);
76
77 if (!foundUUID)
78 uuid = UUIDGenerator::generateRandom();
79
80 return loadInternal(uuid, filePath, false, loadFlags).resource;
81 }
82
83 HResource Resources::loadFromUUID(const UUID& uuid, bool async, ResourceLoadFlags loadFlags)
84 {
85 Path filePath;
86 getFilePathFromUUID(uuid, filePath);
87
88 return loadInternal(uuid, filePath, !async, loadFlags).resource;
89 }
90
91 Resources::LoadInfo Resources::loadInternal(const UUID& uuid, const Path& filePath, bool synchronous,
92 ResourceLoadFlags loadFlags)
93 {
94 LoadInfo output;
95
96 // Retrieve/create resource handle, and register with the system
97 bool loadInProgress = false;
98 bool loadFailed = false;
99 bool initiateLoad = false;
100 Vector<UUID> dependenciesToLoad;
101 {
102 bool alreadyLoading = false;
103
104 // Check if the resource is being loaded on a worker thread
105 Lock inProgressLock(mInProgressResourcesMutex);
106 Lock loadedLock(mLoadedResourceMutex);
107
108 auto iterFind2 = mInProgressResources.find(uuid);
109 if (iterFind2 != mInProgressResources.end())
110 {
111 LoadedResourceData& resData = iterFind2->second->resData;
112 output.resource = resData.resource.lock();
113 output.state = LoadInfo::AlreadyInProgress;
114 output.size = resData.size;
115
116 // Increase ref. count
117 if (loadFlags.isSet(ResourceLoadFlag::KeepInternalRef))
118 {
119 resData.numInternalRefs++;
120 output.resource.addInternalRef();
121 }
122
123 loadInProgress = true;
124 alreadyLoading = true;
125 }
126
127 // Check if the resource is already loaded
128 auto iterFind = mLoadedResources.find(uuid);
129 if (iterFind != mLoadedResources.end())
130 {
131 LoadedResourceData& resData = iterFind->second;
132 output.resource = resData.resource.lock();
133 output.state = LoadInfo::AlreadyLoaded;
134 output.size = resData.size;
135
136 // Increase ref. count
137 if (loadFlags.isSet(ResourceLoadFlag::KeepInternalRef))
138 {
139 resData.numInternalRefs++;
140 output.resource.addInternalRef();
141 }
142
143 alreadyLoading = true;
144 }
145
146 // Not loaded and not in progress, register a new handle or find a pre-registered one
147 if(!alreadyLoading)
148 {
149 output.state = LoadInfo::Loading;
150 output.size = 0;
151
152 auto iterFind = mHandles.find(uuid);
153 if (iterFind != mHandles.end())
154 output.resource = iterFind->second.lock();
155 else
156 {
157 output.resource = HResource(uuid);
158 mHandles[uuid] = output.resource.getWeak();
159 }
160 }
161
162 // If we have nowhere to load from, warn and complete load if a file path was provided, otherwise pass through
163 // as we might just want to complete a previously queued load
164 if (filePath.isEmpty())
165 {
166 if (!alreadyLoading)
167 {
168 LOGWRN_VERBOSE("Cannot load resource. Resource with UUID '" + uuid.toString() + "' doesn't exist.");
169 loadFailed = true;
170 }
171 }
172 else if (!FileSystem::isFile(filePath))
173 {
174 LOGWRN_VERBOSE("Cannot load resource. Specified file: " + filePath.toString() + " doesn't exist.");
175 loadFailed = true;
176 }
177
178 bool loadDependencies = loadFlags.isSet(ResourceLoadFlag::LoadDependencies);
179 if(!loadFailed)
180 {
181 // Load dependency data if a file path is provided
182 SPtr<SavedResourceData> savedResourceData;
183 if (!filePath.isEmpty())
184 {
185 // Note: Ideally this data gets cached eventually (e.g. as part of the manifest). When loading objects
186 // with a lot of dependencies (e.g. scenes) this will get called for every dependency, synchronously,
187 // which might take a while. It would be nice to just read it from a single location. Another option is
188 // to make this whole block asynchronous so every dependency does it on its own thread.
189 FileDecoder fs(filePath);
190 savedResourceData = std::static_pointer_cast<SavedResourceData>(fs.decode());
191 output.size = fs.getSize();
192 }
193
194 // Register an in-progress load unless there is an existing load operation, or the resource is already
195 // loaded
196 if(!alreadyLoading)
197 {
198 ResourceLoadData* loadData = bs_new<ResourceLoadData>(output.resource.getWeak(), 0, output.size);
199 mInProgressResources[uuid] = loadData;
200
201 if (loadFlags.isSet(ResourceLoadFlag::KeepInternalRef))
202 {
203 loadData->resData.numInternalRefs++;
204 output.resource.addInternalRef();
205 }
206
207 loadData->remainingDependencies = 1; // Self
208 loadData->progress.store(0.0f, std::memory_order_relaxed);
209
210 // Make resource listener trigger before exit if loading synchronously on the main thread
211 loadData->notifyImmediately = synchronous && BS_THREAD_CURRENT_ID == gCoreApplication().getSimThreadId();
212
213 // Register dependencies and count them so we know when the resource is fully loaded
214 if (loadDependencies && savedResourceData != nullptr)
215 {
216 for (auto& dependency : savedResourceData->getDependencies())
217 {
218 if (dependency != uuid)
219 {
220 mDependantLoads[dependency].push_back(loadData);
221 loadData->remainingDependencies++;
222 dependenciesToLoad.push_back(dependency);
223 }
224 }
225 }
226 }
227 // The resource is already being loaded, or is loaded, but we might still need to load some dependencies
228 else if(loadDependencies && savedResourceData != nullptr)
229 {
230 const Vector<UUID>& dependencies = savedResourceData->getDependencies();
231 if (!dependencies.empty())
232 {
233 ResourceLoadData* loadData = nullptr;
234
235 // If load not in progress, register the resource for load
236 if (!loadInProgress)
237 {
238 loadData = bs_new<ResourceLoadData>(output.resource.getWeak(), 0, output.size);
239 loadData->remainingDependencies = 0;
240 loadData->progress.store(0.0f, std::memory_order_relaxed);
241
242 // Make resource listener trigger before exit if loading synchronously
243 loadData->notifyImmediately = synchronous && BS_THREAD_CURRENT_ID == gCoreApplication().getSimThreadId();
244 }
245 else
246 loadData = mInProgressResources[uuid];
247
248 // Find dependencies that aren't already loaded or queued for loading
249 for (auto& dependency : dependencies)
250 {
251 if (dependency != uuid)
252 {
253 bool registerDependency = false;
254
255 auto iterFind3 = mLoadedResources.find(dependency);
256 if(iterFind3 == mLoadedResources.end())
257 {
258 registerDependency = true;
259
260 auto iterFind2 = mDependantLoads.find(dependency);
261 if (iterFind2 != mDependantLoads.end())
262 {
263 Vector<ResourceLoadData*>& dependantData = iterFind2->second;
264 auto iterFind3 = std::find_if(dependantData.begin(), dependantData.end(),
265 [&](ResourceLoadData* x)
266 {
267 return x->resData.resource.getUUID() == output.resource.getUUID();
268 });
269
270 registerDependency = iterFind3 == dependantData.end();
271 }
272 }
273
274 if (registerDependency)
275 {
276 mDependantLoads[dependency].push_back(loadData);
277 loadData->remainingDependencies++;
278 dependenciesToLoad.push_back(dependency);
279 }
280 }
281 }
282
283 if(!loadInProgress)
284 {
285 if(!dependenciesToLoad.empty())
286 mInProgressResources[uuid] = loadData;
287 else
288 bs_delete(loadData);
289 }
290 }
291 }
292
293 initiateLoad = !alreadyLoading && !filePath.isEmpty();
294
295 if(savedResourceData != nullptr)
296 synchronous = synchronous || !savedResourceData->allowAsyncLoading();
297 }
298 }
299
300 // Something went wrong, clean up and exit
301 if(loadFailed)
302 {
303 output.state = LoadInfo::Failed;
304 output.size = 0;
305
306 // Clean up in-progress state
307 loadComplete(output.resource, true);
308 return output;
309 }
310
311 // Load dependencies (before the main resource)
312 const auto numDependencies = (UINT32)dependenciesToLoad.size();
313 if(numDependencies > 0)
314 {
315 ResourceLoadFlags depLoadFlags = ResourceLoadFlag::LoadDependencies;
316 if (loadFlags.isSet(ResourceLoadFlag::KeepSourceData))
317 depLoadFlags |= ResourceLoadFlag::KeepSourceData;
318
319 Vector<HResource> dependencies(numDependencies);
320 UINT32 dependencySize = 0;
321 for (UINT32 i = 0; i < numDependencies; i++)
322 {
323 const UUID& depUUID = dependenciesToLoad[i];
324
325 Path depFilePath;
326 getFilePathFromUUID(depUUID, depFilePath);
327
328 LoadInfo loadInfo = loadInternal(depUUID, depFilePath, synchronous, depLoadFlags);
329 dependencies[i] = loadInfo.resource;
330
331 // Calculate the size of dependencies that still need to be loaded, for progress reporting
332 if(loadInfo.state == LoadInfo::Loading || loadInfo.state == LoadInfo::AlreadyInProgress)
333 {
334 // Note: Technically, since we're queuing the dependency load with no locking, the load could complete
335 // before the size of the dependency has been registered, which means getLoadProgress() method would
336 // incorrectly report the progress to be higher than it should be. If that becomes an issue then this
337 // operation is better to be moved to the child loadInternal() call.
338 dependencySize += loadInfo.size;
339 }
340 }
341
342 // Keep dependencies alive until the parent is done loading, and record total size of dependencies to load
343 {
344 Lock inProgressLock(mInProgressResourcesMutex);
345
346 // If we're doing a dependency-only load (main resource itself was previously loaded), then the in-progress
347 // operation could have already finished when the last dependency was loaded (this will always be true for
348 // synchronous loads), and no need to register dependencies.
349 const auto iterFind = mInProgressResources.find(uuid);
350 if(iterFind != mInProgressResources.end())
351 {
352 iterFind->second->dependencySize = dependencySize;
353 iterFind->second->dependencies = dependencies;
354 }
355 }
356 }
357
358 // Previously being loaded as async but now we want it synced, so we wait
359 if (loadInProgress && synchronous)
360 output.resource.blockUntilLoaded(false);
361
362 // Actually start the file read operation if not already loaded or in progress
363 if (initiateLoad)
364 {
365 // Synchronous or the resource doesn't support async, read the file immediately
366 if (synchronous)
367 {
368 loadCallback(filePath, output.resource, loadFlags.isSet(ResourceLoadFlag::KeepSourceData));
369 }
370 else // Asynchronous, read the file on a worker thread
371 {
372 String fileName = filePath.getFilename();
373 String taskName = "Resource load: " + fileName;
374
375 bool keepSourceData = loadFlags.isSet(ResourceLoadFlag::KeepSourceData);
376 SPtr<Task> task = Task::create(taskName,
377 std::bind(&Resources::loadCallback, this, filePath, output.resource, keepSourceData));
378 TaskScheduler::instance().addTask(task);
379 }
380 }
381 else
382 {
383 if(!loadInProgress)
384 {
385 // Already loaded, decrement dependency count
386 loadComplete(output.resource, false);
387 }
388 }
389
390 output.state = LoadInfo::Loading;
391 return output;
392 }
393
394 SPtr<Resource> Resources::loadFromDiskAndDeserialize(const Path& filePath, bool loadWithSaveData,
395 std::atomic<float>& progress)
396 {
397 Lock fileLock = FileScheduler::getLock(filePath);
398
399 SPtr<DataStream> stream = FileSystem::openFile(filePath, true);
400 if (stream == nullptr)
401 return nullptr;
402
403 if (stream->size() > std::numeric_limits<UINT32>::max())
404 {
405 BS_EXCEPT(InternalErrorException,
406 "File size is larger that UINT32 can hold. Ask a programmer to use a bigger data type.");
407 }
408
409 CoreSerializationContext serzContext;
410 serzContext.flags = loadWithSaveData ? SF_KeepResourceSourceData : 0;
411
412 // Read meta-data
413 SPtr<SavedResourceData> metaData;
414 {
415 if (!stream->eof())
416 {
417 UINT32 objectSize = 0;
418 stream->read(&objectSize, sizeof(objectSize));
419
420 BinarySerializer bs;
421 metaData = std::static_pointer_cast<SavedResourceData>(bs.decode(stream, objectSize, &serzContext));
422 }
423 }
424
425 // Read resource data
426 SPtr<IReflectable> loadedData;
427 {
428 if(metaData && !stream->eof())
429 {
430 UINT32 objectSize = 0;
431 stream->read(&objectSize, sizeof(objectSize));
432
433 if (metaData->getCompressionMethod() != 0)
434 {
435 stream = Compression::decompress(stream, [&progress](float val)
436 {
437 progress.exchange(val * 0.9f, std::memory_order_relaxed);
438 });
439
440 BinarySerializer bs;
441 loadedData = bs.decode(stream, objectSize, &serzContext, [&progress](float val)
442 {
443 progress.exchange(0.9f + val * 0.1f, std::memory_order_relaxed);
444 });
445 }
446 else
447 {
448 BinarySerializer bs;
449 loadedData = bs.decode(stream, objectSize, &serzContext, [&progress](float val)
450 {
451 progress.exchange(val, std::memory_order_relaxed);
452 });
453 }
454 }
455 }
456
457 if (loadedData == nullptr)
458 {
459 LOGERR("Unable to load resource at path \"" + filePath.toString() + "\"");
460 }
461 else
462 {
463 if (!loadedData->isDerivedFrom(Resource::getRTTIStatic()))
464 BS_EXCEPT(InternalErrorException, "Loaded class doesn't derive from Resource.");
465 }
466
467 SPtr<Resource> resource = std::static_pointer_cast<Resource>(loadedData);
468 return resource;
469 }
470
471 void Resources::release(ResourceHandleBase& resource)
472 {
473 const UUID& uuid = resource.getUUID();
474
475 {
476 bool loadInProgress = false;
477
478 {
479 Lock inProgressLock(mInProgressResourcesMutex);
480 auto iterFind2 = mInProgressResources.find(uuid);
481 if (iterFind2 != mInProgressResources.end())
482 loadInProgress = true;
483 }
484
485 // Technically we should be able to just cancel a load in progress instead of blocking until it finishes.
486 // However that would mean the last reference could get lost on whatever thread did the loading, which
487 // isn't something that's supported. If this ends up being a problem either make handle counting atomic
488 // or add a separate queue for objects destroyed from the load threads.
489 if (loadInProgress)
490 resource.blockUntilLoaded();
491
492 bool lostLastRef = false;
493 {
494 Lock loadedLock(mLoadedResourceMutex);
495 auto iterFind = mLoadedResources.find(uuid);
496 if (iterFind != mLoadedResources.end())
497 {
498 LoadedResourceData& resData = iterFind->second;
499
500 assert(resData.numInternalRefs > 0);
501 resData.numInternalRefs--;
502 resource.removeInternalRef();
503
504 std::uint32_t refCount = resource.getHandleData()->mRefCount.load(std::memory_order_relaxed);
505 lostLastRef = refCount == 0;
506 }
507 }
508
509 if(lostLastRef)
510 destroy(resource);
511 }
512 }
513
514 void Resources::unloadAllUnused()
515 {
516 Vector<HResource> resourcesToUnload;
517
518 {
519 Lock lock(mLoadedResourceMutex);
520 for(auto iter = mLoadedResources.begin(); iter != mLoadedResources.end(); ++iter)
521 {
522 const LoadedResourceData& resData = iter->second;
523
524 std::uint32_t refCount = resData.resource.mData->mRefCount.load(std::memory_order_relaxed);
525 assert(refCount > 0); // No references but kept in mLoadedResources list?
526
527 if (refCount == resData.numInternalRefs) // Only internal references exist, free it
528 resourcesToUnload.push_back(resData.resource.lock());
529 }
530 }
531
532 // Note: When unloading multiple resources it's possible that unloading one will also unload
533 // another resource in "resourcesToUnload". This is fine because "unload" deals with invalid
534 // handles gracefully.
535 for(auto iter = resourcesToUnload.begin(); iter != resourcesToUnload.end(); ++iter)
536 {
537 release(*iter);
538 }
539 }
540
541 void Resources::unloadAll()
542 {
543 // Unload and invalidate all resources
544 UnorderedMap<UUID, LoadedResourceData> loadedResourcesCopy;
545
546 {
547 Lock lock(mLoadedResourceMutex);
548 loadedResourcesCopy = mLoadedResources;
549 }
550
551 for (auto& loadedResourcePair : loadedResourcesCopy)
552 destroy(loadedResourcePair.second.resource);
553 }
554
555 void Resources::destroy(ResourceHandleBase& resource)
556 {
557 if (resource.mData == nullptr)
558 return;
559
560 RecursiveLock lock(mDestroyMutex);
561
562 // If load in progress, first wait until it completes
563 const UUID& uuid = resource.getUUID();
564 if (!resource.isLoaded(false))
565 {
566 bool loadInProgress = false;
567 {
568 Lock lock(mInProgressResourcesMutex);
569 auto iterFind2 = mInProgressResources.find(uuid);
570 if (iterFind2 != mInProgressResources.end())
571 loadInProgress = true;
572 }
573
574 if (loadInProgress) // If it's still loading wait until that finishes
575 resource.blockUntilLoaded();
576 else
577 return; // Already unloaded
578 }
579
580 // At this point resource is guaranteed to be loaded and this state cannot change by some other thread because of
581 // the mDestroyMutex lock
582
583 // Notify external systems before we actually destroy it
584 onResourceDestroyed(uuid);
585 resource.mData->mPtr->destroy();
586
587 {
588 Lock lock(mLoadedResourceMutex);
589 auto iterFind = mLoadedResources.find(uuid);
590 if (iterFind != mLoadedResources.end())
591 {
592 LoadedResourceData& resData = iterFind->second;
593 while (resData.numInternalRefs > 0)
594 {
595 resData.numInternalRefs--;
596 resData.resource.removeInternalRef();
597 }
598
599 mLoadedResources.erase(iterFind);
600 }
601 else
602 {
603 assert(false); // This should never happen but in case it does fail silently in release mode
604 }
605 }
606
607 resource.clearHandleData();
608 }
609
610 void Resources::save(const HResource& resource, const Path& filePath, bool overwrite, bool compress)
611 {
612 if (resource == nullptr)
613 return;
614
615 if (!resource.isLoaded(false))
616 {
617 bool loadInProgress = false;
618 {
619 Lock lock(mInProgressResourcesMutex);
620 auto iterFind2 = mInProgressResources.find(resource.getUUID());
621 if (iterFind2 != mInProgressResources.end())
622 loadInProgress = true;
623 }
624
625 if (loadInProgress) // If it's still loading wait until that finishes
626 resource.blockUntilLoaded();
627 else
628 return; // Nothing to save
629 }
630
631 const bool fileExists = FileSystem::isFile(filePath);
632 if(fileExists && !overwrite)
633 {
634 LOGERR("Another file exists at the specified location. Not saving.");
635 return;
636 }
637
638 {
639 Lock lock(mDefaultManifestMutex);
640 mDefaultResourceManifest->registerResource(resource.getUUID(), filePath);
641 }
642
643 _save(resource.getInternalPtr(), filePath, compress);
644 }
645
646 void Resources::save(const HResource& resource, bool compress)
647 {
648 if (resource == nullptr)
649 return;
650
651 Path path;
652 if (getFilePathFromUUID(resource.getUUID(), path))
653 save(resource, path, true, compress);
654 }
655
656 void Resources::_save(const SPtr<Resource>& resource, const Path& filePath, bool compress)
657 {
658 if (!resource->mKeepSourceData)
659 {
660 LOGWRN("Saving a resource that was created/loaded without ResourceLoadFlag::KeepSourceData. Some data might "
661 "not be available for saving. File path: " + filePath.toString());
662 }
663
664 Vector<ResourceDependency> dependencyList = Utility::findResourceDependencies(*resource);
665 Vector<UUID> dependencyUUIDs(dependencyList.size());
666 for (UINT32 i = 0; i < (UINT32)dependencyList.size(); i++)
667 dependencyUUIDs[i] = dependencyList[i].resource.getUUID();
668
669 UINT32 compressionMethod = (compress && resource->isCompressible()) ? 1 : 0;
670 SPtr<SavedResourceData> resourceData = bs_shared_ptr_new<SavedResourceData>(dependencyUUIDs,
671 resource->allowAsyncLoading(), compressionMethod);
672
673 Path parentDir = filePath.getDirectory();
674 if (!FileSystem::exists(parentDir))
675 FileSystem::createDir(parentDir);
676
677 Path savePath;
678 const bool fileExists = FileSystem::isFile(filePath);
679 if(fileExists)
680 {
681 // If a file exists, save to a temporary location, then copy over only after a save was successful. This guards
682 // against data loss in case the save process fails.
683
684 // TODO: Temp directory should always be on this drive, as files moved from one drive to another will in fact
685 // be copied
686 savePath = FileSystem::getTempDirectoryPath();
687 savePath.setFilename(UUIDGenerator::generateRandom().toString());
688
689 UINT32 safetyCounter = 0;
690 while(FileSystem::exists(savePath))
691 {
692 if(safetyCounter > 10)
693 {
694 LOGERR("Internal error. Unable to save resource due to not being able to find a unique filename.");
695 return;
696 }
697
698 savePath.setFilename(UUIDGenerator::generateRandom().toString());
699 safetyCounter++;
700 }
701
702 }
703 else
704 savePath = filePath;
705
706 Lock fileLock = FileScheduler::getLock(filePath);
707
708 std::ofstream stream;
709 stream.open(savePath.toPlatformString().c_str(), std::ios::out | std::ios::binary);
710 if (stream.fail())
711 LOGWRN("Failed to save file: \"" + filePath.toString() + "\". Error: " + strerror(errno) + ".");
712
713 // Write meta-data
714 {
715 MemorySerializer ms;
716 UINT32 numBytes = 0;
717 UINT8* bytes = ms.encode(resourceData.get(), numBytes);
718
719 stream.write((char*)&numBytes, sizeof(numBytes));
720 stream.write((char*)bytes, numBytes);
721
722 bs_free(bytes);
723 }
724
725 // Write object data
726 {
727 MemorySerializer ms;
728 UINT32 numBytes = 0;
729 UINT8* bytes = ms.encode(resource.get(), numBytes);
730
731 SPtr<MemoryDataStream> objStream = bs_shared_ptr_new<MemoryDataStream>(bytes, numBytes);
732 if (compressionMethod != 0)
733 {
734 SPtr<DataStream> srcStream = std::static_pointer_cast<DataStream>(objStream);
735 objStream = Compression::compress(srcStream);
736 }
737
738 stream.write((char*)&numBytes, sizeof(numBytes));
739 stream.write((char*)objStream->getPtr(), objStream->size());
740 }
741
742 stream.close();
743 stream.clear();
744
745 if (fileExists)
746 {
747 FileSystem::remove(filePath);
748 FileSystem::move(savePath, filePath);
749 }
750 }
751
752 void Resources::update(HResource& handle, const SPtr<Resource>& resource)
753 {
754 const UUID& uuid = handle.getUUID();
755 handle.setHandleData(resource, uuid);
756 handle.notifyLoadComplete();
757
758 if(resource)
759 {
760 Lock lock(mLoadedResourceMutex);
761 auto iterFind = mLoadedResources.find(uuid);
762 if (iterFind == mLoadedResources.end())
763 {
764 LoadedResourceData& resData = mLoadedResources[uuid];
765 resData.resource = handle.getWeak();
766 }
767 }
768
769 onResourceModified(handle);
770
771 // This method is not thread safe due to this call (callable from main thread only)
772 ResourceListenerManager::instance().notifyListeners(uuid);
773 }
774
775 Vector<UUID> Resources::getDependencies(const Path& filePath)
776 {
777 SPtr<SavedResourceData> savedResourceData;
778 if (!filePath.isEmpty())
779 {
780 FileDecoder fs(filePath);
781 savedResourceData = std::static_pointer_cast<SavedResourceData>(fs.decode());
782 }
783
784 return savedResourceData->getDependencies();
785 }
786
787 void Resources::registerResourceManifest(const SPtr<ResourceManifest>& manifest)
788 {
789 auto findIter = std::find(mResourceManifests.begin(), mResourceManifests.end(), manifest);
790 if(findIter == mResourceManifests.end())
791 mResourceManifests.push_back(manifest);
792 else
793 *findIter = manifest;
794 }
795
796 void Resources::unregisterResourceManifest(const SPtr<ResourceManifest>& manifest)
797 {
798 if (manifest->getName() == "Default")
799 return;
800
801 auto findIter = std::find(mResourceManifests.begin(), mResourceManifests.end(), manifest);
802 if (findIter != mResourceManifests.end())
803 mResourceManifests.erase(findIter);
804 }
805
806 SPtr<ResourceManifest> Resources::getResourceManifest(const String& name) const
807 {
808 for(auto iter = mResourceManifests.rbegin(); iter != mResourceManifests.rend(); ++iter)
809 {
810 if(name == (*iter)->getName())
811 return (*iter);
812 }
813
814 return nullptr;
815 }
816
817 bool Resources::isLoaded(const UUID& uuid, bool checkInProgress)
818 {
819 if (checkInProgress)
820 {
821 Lock inProgressLock(mInProgressResourcesMutex);
822 auto iterFind2 = mInProgressResources.find(uuid);
823 if (iterFind2 != mInProgressResources.end())
824 {
825 return true;
826 }
827 }
828
829 {
830 Lock loadedLock(mLoadedResourceMutex);
831 auto iterFind = mLoadedResources.find(uuid);
832 if (iterFind != mLoadedResources.end())
833 {
834 return true;
835 }
836 }
837
838 return false;
839 }
840
841 float Resources::getLoadProgress(const HResource& resource, bool includeDependencies)
842 {
843 const UUID& uuid = resource.getUUID();
844 if(uuid.empty())
845 return 0.0f;
846
847 Lock inProgressLock(mInProgressResourcesMutex);
848 Lock loadedLock(mLoadedResourceMutex);
849
850 // Fully loaded
851 auto iterFind = mLoadedResources.find(uuid);
852 if (iterFind != mLoadedResources.end())
853 return 1.0f;
854
855 // Not loaded nor being loaded
856 auto iterFind2 = mInProgressResources.find(uuid);
857 if (iterFind2 == mInProgressResources.end())
858 return 0.0f;
859
860 ResourceLoadData* loadData = iterFind2->second;
861
862 // Don't care about dependencies, just report own progress directly
863 if(!includeDependencies)
864 return loadData->progress.load(std::memory_order_relaxed);
865
866 // Dependencies that are already fully loaded will just have their loaded sizes in 'dependencyLoadedAmount', while
867 // for those still in progress we need to check their load data
868 float totalBytesLoaded = (float)loadData->dependencyLoadedAmount;
869 for(auto& entry : loadData->dependencies)
870 {
871 auto iterFind3 = mInProgressResources.find(entry.getUUID());
872 if (iterFind3 == mInProgressResources.end())
873 continue;
874
875 ResourceLoadData* dependencyLoadData = iterFind3->second;
876 totalBytesLoaded += dependencyLoadData->resData.size * dependencyLoadData->progress.load(std::memory_order_relaxed);
877 }
878
879 totalBytesLoaded += loadData->resData.size * loadData->progress.load(std::memory_order_relaxed);
880
881 float totalBytesToLoad = (float)(loadData->dependencySize + loadData->resData.size);
882 assert(totalBytesLoaded <= totalBytesToLoad);
883
884 return std::min(1.0f, totalBytesLoaded / totalBytesToLoad);
885 }
886
887 HResource Resources::_createResourceHandle(const SPtr<Resource>& obj)
888 {
889 UUID uuid = UUIDGenerator::generateRandom();
890 return _createResourceHandle(obj, uuid);
891 }
892
893 HResource Resources::_createResourceHandle(const SPtr<Resource>& obj, const UUID& UUID)
894 {
895 HResource newHandle(obj, UUID);
896
897 {
898 Lock lock(mLoadedResourceMutex);
899
900 if(obj)
901 {
902 LoadedResourceData& resData = mLoadedResources[UUID];
903 resData.resource = newHandle.getWeak();
904 }
905
906 mHandles[UUID] = newHandle.getWeak();
907 }
908
909 return newHandle;
910 }
911
912 HResource Resources::_getResourceHandle(const UUID& uuid)
913 {
914 Lock lock(mLoadedResourceMutex);
915 auto iterFind3 = mHandles.find(uuid);
916 if (iterFind3 != mHandles.end()) // Not loaded, but handle does exist
917 {
918 return iterFind3->second.lock();
919 }
920
921 // Create new handle
922 HResource handle(uuid);
923 mHandles[uuid] = handle.getWeak();
924
925 return handle;
926 }
927
928 bool Resources::getFilePathFromUUID(const UUID& uuid, Path& filePath) const
929 {
930 // Default manifest is at 0th index but all other take priority since Default manifest could
931 // contain obsolete data.
932 for(auto iter = mResourceManifests.rbegin(); iter != mResourceManifests.rend(); ++iter)
933 {
934 if((*iter)->uuidToFilePath(uuid, filePath))
935 return true;
936 }
937
938 return false;
939 }
940
941 bool Resources::getUUIDFromFilePath(const Path& path, UUID& uuid) const
942 {
943 Path manifestPath = path;
944 if (!manifestPath.isAbsolute())
945 manifestPath.makeAbsolute(FileSystem::getWorkingDirectoryPath());
946
947 for(auto iter = mResourceManifests.rbegin(); iter != mResourceManifests.rend(); ++iter)
948 {
949 if ((*iter)->filePathToUUID(manifestPath, uuid))
950 return true;
951 }
952
953 return false;
954 }
955
956 void Resources::loadComplete(HResource& resource, bool notifyProgress)
957 {
958 UUID uuid = resource.getUUID();
959
960 ResourceLoadData* myLoadData = nullptr;
961 bool finishLoad = true;
962 Vector<ResourceLoadData*> dependantLoads;
963 {
964 Lock inProgresslock(mInProgressResourcesMutex);
965
966 auto iterFind = mInProgressResources.find(uuid);
967 if (iterFind != mInProgressResources.end())
968 {
969 myLoadData = iterFind->second;
970 finishLoad = myLoadData->remainingDependencies == 0;
971
972 if (finishLoad)
973 mInProgressResources.erase(iterFind);
974 }
975
976 auto iterFind2 = mDependantLoads.find(uuid);
977
978 if (iterFind2 != mDependantLoads.end())
979 dependantLoads = iterFind2->second;
980
981 if (finishLoad)
982 {
983 mDependantLoads.erase(uuid);
984
985 // If loadedData is null then we're probably completing load on an already loaded resource, triggered
986 // by its dependencies.
987 if (myLoadData != nullptr && myLoadData->loadedData != nullptr)
988 {
989 Lock loadedLock(mLoadedResourceMutex);
990
991 mLoadedResources[uuid] = myLoadData->resData;
992 resource.setHandleData(myLoadData->loadedData, uuid);
993 }
994
995 resource.notifyLoadComplete();
996
997 for (auto& dependantLoad : dependantLoads)
998 dependantLoad->remainingDependencies--;
999 }
1000 }
1001
1002 for (auto& dependantLoad : dependantLoads)
1003 {
1004 if(notifyProgress && myLoadData)
1005 dependantLoad->dependencyLoadedAmount += myLoadData->resData.size;
1006
1007 HResource dependant = dependantLoad->resData.resource.lock();
1008 loadComplete(dependant, false);
1009 }
1010
1011 if (finishLoad && myLoadData != nullptr)
1012 {
1013 onResourceLoaded(resource);
1014
1015 // This should only ever be true on the main thread
1016 if (myLoadData->notifyImmediately)
1017 ResourceListenerManager::instance().notifyListeners(uuid);
1018
1019 bs_delete(myLoadData);
1020 }
1021 }
1022
1023 void Resources::loadCallback(const Path& filePath, HResource& resource, bool loadWithSaveData)
1024 {
1025 ResourceLoadData* myLoadData;
1026 {
1027 Lock lock(mInProgressResourcesMutex);
1028 myLoadData = mInProgressResources[resource.getUUID()];
1029 }
1030
1031 SPtr<Resource> rawResource = loadFromDiskAndDeserialize(filePath, loadWithSaveData, myLoadData->progress);
1032
1033 {
1034 Lock lock(mInProgressResourcesMutex);
1035
1036 myLoadData->loadedData = rawResource;
1037 myLoadData->remainingDependencies--;
1038 myLoadData->progress.exchange(1.0f, std::memory_order_relaxed);
1039 }
1040
1041 loadComplete(resource, true);
1042 }
1043
1044 BS_CORE_EXPORT Resources& gResources()
1045 {
1046 return Resources::instance();
1047 }
1048}
1049