BsResources.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. #include "BsResources.h"
  2. #include "BsResource.h"
  3. #include "BsResourceManifest.h"
  4. #include "BsException.h"
  5. #include "BsFileSerializer.h"
  6. #include "BsFileSystem.h"
  7. #include "BsTaskScheduler.h"
  8. #include "BsUUID.h"
  9. #include "BsDebug.h"
  10. #include "BsUtility.h"
  11. #include "BsSavedResourceData.h"
  12. #include "BsResourceListenerManager.h"
  13. namespace BansheeEngine
  14. {
  15. Resources::Resources()
  16. {
  17. mDefaultResourceManifest = ResourceManifest::create("Default");
  18. mResourceManifests.push_back(mDefaultResourceManifest);
  19. }
  20. Resources::~Resources()
  21. {
  22. // Unload and invalidate all resources
  23. UnorderedMap<String, HResource> loadedResourcesCopy = mLoadedResources;
  24. for (auto& loadedResourcePair : loadedResourcesCopy)
  25. {
  26. unload(loadedResourcePair.second);
  27. // Invalidate the handle
  28. loadedResourcePair.second._setHandleData(nullptr, "");
  29. }
  30. }
  31. HResource Resources::load(const Path& filePath, bool loadDependencies)
  32. {
  33. String uuid;
  34. bool foundUUID = getUUIDFromFilePath(filePath, uuid);
  35. if (!foundUUID)
  36. uuid = UUIDGenerator::generateRandom();
  37. return loadInternal(uuid, filePath, true, loadDependencies);
  38. }
  39. HResource Resources::loadAsync(const Path& filePath, bool loadDependencies)
  40. {
  41. String uuid;
  42. bool foundUUID = getUUIDFromFilePath(filePath, uuid);
  43. if (!foundUUID)
  44. uuid = UUIDGenerator::generateRandom();
  45. return loadInternal(uuid, filePath, false, loadDependencies);
  46. }
  47. HResource Resources::loadFromUUID(const String& uuid, bool async, bool loadDependencies)
  48. {
  49. Path filePath;
  50. // Default manifest is at 0th index but all other take priority since Default manifest could
  51. // contain obsolete data.
  52. for(auto iter = mResourceManifests.rbegin(); iter != mResourceManifests.rend(); ++iter)
  53. {
  54. if((*iter)->uuidToFilePath(uuid, filePath))
  55. break;
  56. }
  57. return loadInternal(uuid, filePath, !async, loadDependencies);
  58. }
  59. HResource Resources::loadInternal(const String& UUID, const Path& filePath, bool synchronous, bool loadDependencies)
  60. {
  61. HResource outputResource;
  62. // Check if resource is already full loaded
  63. bool alreadyLoading = false;
  64. {
  65. BS_LOCK_MUTEX(mLoadedResourceMutex);
  66. auto iterFind = mLoadedResources.find(UUID);
  67. if(iterFind != mLoadedResources.end()) // Resource is already loaded
  68. {
  69. outputResource = iterFind->second;
  70. alreadyLoading = true;
  71. }
  72. }
  73. // Check if resource is already being loaded on a worker thread
  74. bool loadInProgress = false;
  75. if (!alreadyLoading) // If not already detected as loaded
  76. {
  77. {
  78. BS_LOCK_MUTEX(mInProgressResourcesMutex);
  79. auto iterFind2 = mInProgressResources.find(UUID);
  80. if (iterFind2 != mInProgressResources.end())
  81. {
  82. outputResource = iterFind2->second->resource;
  83. alreadyLoading = true;
  84. loadInProgress = true;
  85. }
  86. }
  87. // Previously being loaded as async but now we want it synced, so we wait
  88. if (loadInProgress && synchronous)
  89. outputResource.blockUntilLoaded();
  90. }
  91. // Not loaded and not in progress, start loading of new resource
  92. // (or if already loaded or in progress, load any dependencies)
  93. if (!alreadyLoading)
  94. outputResource = HResource(UUID);
  95. // We have nowhere to load from, warn and complete load if a file path was provided,
  96. // otherwise pass through as we might just want to load from memory.
  97. if (filePath.isEmpty())
  98. {
  99. if (!alreadyLoading)
  100. {
  101. gDebug().logWarning("Cannot load resource. Resource with UUID '" + UUID + "' doesn't exist.");
  102. // Complete the load as that the depedency counter is properly reduced, in case this
  103. // is a dependency of some other resource.
  104. loadComplete(outputResource);
  105. return outputResource;
  106. }
  107. }
  108. else if (!FileSystem::isFile(filePath))
  109. {
  110. LOGWRN("Cannot load resource. Specified file: " + filePath.toString() + " doesn't exist.");
  111. // Complete the load as that the depedency counter is properly reduced, in case this
  112. // is a dependency of some other resource.
  113. loadComplete(outputResource);
  114. assert(!loadInProgress); // Resource already being loaded but we can't find its path now?
  115. return outputResource;
  116. }
  117. // Load dependency data if a file path is provided
  118. SPtr<SavedResourceData> savedResourceData;
  119. if (!filePath.isEmpty())
  120. {
  121. FileDecoder fs(filePath);
  122. savedResourceData = std::static_pointer_cast<SavedResourceData>(fs.decode());
  123. }
  124. // If already loading keep the old load operation active, otherwise create a new one
  125. if (!alreadyLoading)
  126. {
  127. {
  128. BS_LOCK_MUTEX(mInProgressResourcesMutex);
  129. ResourceLoadData* loadData = bs_new<ResourceLoadData>(outputResource, 0);
  130. mInProgressResources[UUID] = loadData;
  131. loadData->resource = outputResource;
  132. loadData->remainingDependencies = 1;
  133. loadData->notifyImmediately = synchronous; // Make resource listener trigger before exit if loading synchronously
  134. // Register dependencies and count them so we know when the resource is fully loaded
  135. if (loadDependencies && savedResourceData != nullptr)
  136. {
  137. for (auto& dependency : savedResourceData->getDependencies())
  138. {
  139. if (dependency != UUID)
  140. {
  141. mDependantLoads[dependency].push_back(loadData);
  142. loadData->remainingDependencies++;
  143. }
  144. }
  145. }
  146. }
  147. if (loadDependencies && savedResourceData != nullptr)
  148. {
  149. for (auto& dependency : savedResourceData->getDependencies())
  150. {
  151. loadFromUUID(dependency, !synchronous);
  152. }
  153. }
  154. }
  155. else if (loadDependencies && savedResourceData != nullptr) // Queue dependencies in case they aren't already loaded
  156. {
  157. const Vector<String>& dependencies = savedResourceData->getDependencies();
  158. if (!dependencies.empty())
  159. {
  160. {
  161. BS_LOCK_MUTEX(mInProgressResourcesMutex);
  162. ResourceLoadData* loadData = nullptr;
  163. auto iterFind = mInProgressResources.find(UUID);
  164. if (iterFind == mInProgressResources.end()) // Fully loaded
  165. {
  166. loadData = bs_new<ResourceLoadData>(outputResource, 0);
  167. loadData->resource = outputResource;
  168. loadData->remainingDependencies = 0;
  169. loadData->notifyImmediately = synchronous; // Make resource listener trigger before exit if loading synchronously
  170. mInProgressResources[UUID] = loadData;
  171. }
  172. else
  173. {
  174. loadData = iterFind->second;
  175. }
  176. // Register dependencies and count them so we know when the resource is fully loaded
  177. for (auto& dependency : dependencies)
  178. {
  179. if (dependency != UUID)
  180. {
  181. bool registerDependency = true;
  182. auto iterFind2 = mDependantLoads.find(dependency);
  183. if (iterFind2 != mDependantLoads.end())
  184. {
  185. Vector<ResourceLoadData*>& dependantData = iterFind2->second;
  186. auto iterFind3 = std::find_if(dependantData.begin(), dependantData.end(),
  187. [&](ResourceLoadData* x)
  188. {
  189. return x->resource.getUUID() == outputResource.getUUID();
  190. });
  191. registerDependency = iterFind3 == dependantData.end();
  192. }
  193. if (registerDependency)
  194. {
  195. mDependantLoads[dependency].push_back(loadData);
  196. loadData->remainingDependencies++;
  197. }
  198. }
  199. }
  200. }
  201. for (auto& dependency : dependencies)
  202. loadFromUUID(dependency, !synchronous);
  203. }
  204. }
  205. // Actually start the file read operation if not already loaded or in progress
  206. if (!alreadyLoading && !filePath.isEmpty())
  207. {
  208. // Synchronous or the resource doesn't support async, read the file immediately
  209. if (synchronous || !savedResourceData->allowAsyncLoading())
  210. {
  211. loadCallback(filePath, outputResource);
  212. }
  213. else // Asynchronous, read the file on a worker thread
  214. {
  215. String fileName = filePath.getFilename();
  216. String taskName = "Resource load: " + fileName;
  217. TaskPtr task = Task::create(taskName, std::bind(&Resources::loadCallback, this, filePath, outputResource));
  218. TaskScheduler::instance().addTask(task);
  219. }
  220. }
  221. else // File already loaded or in progress
  222. {
  223. // Complete the load unless its in progress in which case we wait for its worker thread to complete it.
  224. // In case file is already loaded this will only decrement dependency count in case this resource is a dependency.
  225. if (!loadInProgress)
  226. loadComplete(outputResource);
  227. else
  228. {
  229. // In case loading finished in the meantime we cannot be sure at what point ::loadComplete was triggered,
  230. // so trigger it manually so that the dependency count is properly decremented in case this resource
  231. // is a dependency.
  232. BS_LOCK_MUTEX(mLoadedResourceMutex);
  233. auto iterFind = mLoadedResources.find(UUID);
  234. if (iterFind != mLoadedResources.end())
  235. loadComplete(outputResource);
  236. }
  237. }
  238. return outputResource;
  239. }
  240. ResourcePtr Resources::loadFromDiskAndDeserialize(const Path& filePath)
  241. {
  242. FileDecoder fs(filePath);
  243. fs.skip(); // Skipped over saved resource data
  244. std::shared_ptr<IReflectable> loadedData = fs.decode();
  245. if(loadedData == nullptr)
  246. BS_EXCEPT(InternalErrorException, "Unable to load resource.");
  247. if(!loadedData->isDerivedFrom(Resource::getRTTIStatic()))
  248. BS_EXCEPT(InternalErrorException, "Loaded class doesn't derive from Resource.");
  249. ResourcePtr resource = std::static_pointer_cast<Resource>(loadedData);
  250. return resource;
  251. }
  252. void Resources::unload(HResource resource)
  253. {
  254. if (resource == nullptr)
  255. return;
  256. if (!resource.isLoaded()) // If it's still loading wait until that finishes
  257. {
  258. LOGWRN("Performance warning: Unloading a resource that is still in process of loading "
  259. "causes a stall until resource finishes loading.");
  260. resource.blockUntilLoaded();
  261. }
  262. Vector<ResourceDependency> dependencies = Utility::findResourceDependencies(*resource.get());
  263. // Call this before we actually destroy it
  264. onResourceDestroyed(resource);
  265. resource->destroy();
  266. {
  267. BS_LOCK_MUTEX(mLoadedResourceMutex);
  268. mLoadedResources.erase(resource.getUUID());
  269. }
  270. resource._setHandleData(nullptr, "");
  271. for (auto& dependency : dependencies)
  272. {
  273. HResource dependantResource = dependency.resource;
  274. // Last reference was kept by the unloaded resource, so unload the dependency too
  275. if ((UINT32)dependantResource.mData.use_count() == (dependency.numReferences + 1))
  276. {
  277. // TODO - Use count is not thread safe. Meaning it might increase after above check, in
  278. // which case we will be unloading a resource that is in use. I don't see a way around
  279. // it at the moment.
  280. unload(dependantResource);
  281. }
  282. }
  283. }
  284. void Resources::unloadAllUnused()
  285. {
  286. Vector<HResource> resourcesToUnload;
  287. {
  288. BS_LOCK_MUTEX(mLoadedResourceMutex);
  289. for(auto iter = mLoadedResources.begin(); iter != mLoadedResources.end(); ++iter)
  290. {
  291. if (iter->second.mData.unique()) // We just have this one reference, meaning nothing is using this resource
  292. resourcesToUnload.push_back(iter->second);
  293. }
  294. }
  295. // Note: When unloading multiple resources it's possible that unloading one will also unload
  296. // another resource in "resourcesToUnload". This is fine because "unload" deals with invalid
  297. // handles gracefully.
  298. for(auto iter = resourcesToUnload.begin(); iter != resourcesToUnload.end(); ++iter)
  299. {
  300. unload(*iter);
  301. }
  302. }
  303. void Resources::save(HResource resource, const Path& filePath, bool overwrite)
  304. {
  305. if(!resource.isLoaded())
  306. resource.blockUntilLoaded();
  307. bool fileExists = FileSystem::isFile(filePath);
  308. if(fileExists)
  309. {
  310. if(overwrite)
  311. FileSystem::remove(filePath);
  312. else
  313. BS_EXCEPT(InvalidParametersException, "Another file exists at the specified location.");
  314. }
  315. mDefaultResourceManifest->registerResource(resource.getUUID(), filePath);
  316. Vector<ResourceDependency> dependencyList = Utility::findResourceDependencies(*resource.get());
  317. Vector<String> dependencyUUIDs(dependencyList.size());
  318. for (UINT32 i = 0; i < (UINT32)dependencyList.size(); i++)
  319. dependencyUUIDs[i] = dependencyList[i].resource.getUUID();
  320. SPtr<SavedResourceData> resourceData = bs_shared_ptr_new<SavedResourceData>(dependencyUUIDs, resource->allowAsyncLoading());
  321. FileEncoder fs(filePath);
  322. fs.encode(resourceData.get());
  323. fs.encode(resource.get());
  324. }
  325. void Resources::save(HResource resource)
  326. {
  327. if (resource == nullptr)
  328. return;
  329. Path path;
  330. if (getFilePathFromUUID(resource.getUUID(), path))
  331. save(resource, path, true);
  332. }
  333. void Resources::registerResourceManifest(const ResourceManifestPtr& manifest)
  334. {
  335. if(manifest->getName() == "Default")
  336. return;
  337. auto findIter = std::find(mResourceManifests.begin(), mResourceManifests.end(), manifest);
  338. if(findIter == mResourceManifests.end())
  339. mResourceManifests.push_back(manifest);
  340. else
  341. *findIter = manifest;
  342. }
  343. void Resources::unregisterResourceManifest(const ResourceManifestPtr& manifest)
  344. {
  345. if (manifest->getName() == "Default")
  346. return;
  347. auto findIter = std::find(mResourceManifests.begin(), mResourceManifests.end(), manifest);
  348. if (findIter != mResourceManifests.end())
  349. mResourceManifests.erase(findIter);
  350. }
  351. ResourceManifestPtr Resources::getResourceManifest(const String& name) const
  352. {
  353. for(auto iter = mResourceManifests.rbegin(); iter != mResourceManifests.rend(); ++iter)
  354. {
  355. if(name == (*iter)->getName())
  356. return (*iter);
  357. }
  358. return nullptr;
  359. }
  360. bool Resources::isLoaded(const String& uuid, bool checkInProgress)
  361. {
  362. if (checkInProgress)
  363. {
  364. BS_LOCK_MUTEX(mInProgressResourcesMutex);
  365. auto iterFind2 = mInProgressResources.find(uuid);
  366. if (iterFind2 != mInProgressResources.end())
  367. {
  368. return true;
  369. }
  370. {
  371. BS_LOCK_MUTEX(mLoadedResourceMutex);
  372. auto iterFind = mLoadedResources.find(uuid);
  373. if (iterFind != mLoadedResources.end())
  374. {
  375. return true;
  376. }
  377. }
  378. }
  379. return false;
  380. }
  381. HResource Resources::_createResourceHandle(const ResourcePtr& obj)
  382. {
  383. String uuid = UUIDGenerator::generateRandom();
  384. HResource newHandle(obj, uuid);
  385. {
  386. BS_LOCK_MUTEX(mLoadedResourceMutex);
  387. mLoadedResources[uuid] = newHandle;
  388. }
  389. return newHandle;
  390. }
  391. HResource Resources::_getResourceHandle(const String& uuid)
  392. {
  393. {
  394. BS_LOCK_MUTEX(mInProgressResourcesMutex);
  395. auto iterFind2 = mInProgressResources.find(uuid);
  396. if (iterFind2 != mInProgressResources.end())
  397. {
  398. return iterFind2->second->resource;
  399. }
  400. {
  401. BS_LOCK_MUTEX(mLoadedResourceMutex);
  402. auto iterFind = mLoadedResources.find(uuid);
  403. if (iterFind != mLoadedResources.end()) // Resource is already loaded
  404. {
  405. return iterFind->second;
  406. }
  407. }
  408. }
  409. return HResource();
  410. }
  411. bool Resources::getFilePathFromUUID(const String& uuid, Path& filePath) const
  412. {
  413. for(auto iter = mResourceManifests.rbegin(); iter != mResourceManifests.rend(); ++iter)
  414. {
  415. if((*iter)->uuidToFilePath(uuid, filePath))
  416. return true;
  417. }
  418. return false;
  419. }
  420. bool Resources::getUUIDFromFilePath(const Path& path, String& uuid) const
  421. {
  422. Path manifestPath = path;
  423. if (!manifestPath.isAbsolute())
  424. manifestPath.makeAbsolute(FileSystem::getWorkingDirectoryPath());
  425. for(auto iter = mResourceManifests.rbegin(); iter != mResourceManifests.rend(); ++iter)
  426. {
  427. if ((*iter)->filePathToUUID(manifestPath, uuid))
  428. return true;
  429. }
  430. return false;
  431. }
  432. void Resources::loadComplete(HResource& resource)
  433. {
  434. String uuid = resource.getUUID();
  435. ResourceLoadData* myLoadData = nullptr;
  436. bool finishLoad = true;
  437. Vector<ResourceLoadData*> dependantLoads;
  438. {
  439. BS_LOCK_MUTEX(mInProgressResourcesMutex);
  440. auto iterFind = mInProgressResources.find(uuid);
  441. if (iterFind != mInProgressResources.end())
  442. {
  443. myLoadData = iterFind->second;
  444. finishLoad = myLoadData->remainingDependencies == 0;
  445. if (finishLoad)
  446. mInProgressResources.erase(iterFind);
  447. }
  448. auto iterFind2 = mDependantLoads.find(uuid);
  449. if (iterFind2 != mDependantLoads.end())
  450. dependantLoads = iterFind2->second;
  451. if (finishLoad)
  452. {
  453. mDependantLoads.erase(uuid);
  454. // If loadedData is null then we're probably completing load on an already loaded resource, triggered
  455. // by its dependencies.
  456. if (myLoadData != nullptr && myLoadData->loadedData != nullptr)
  457. {
  458. BS_LOCK_MUTEX(mLoadedResourceMutex);
  459. mLoadedResources[uuid] = resource;
  460. resource._setHandleData(myLoadData->loadedData, uuid);
  461. }
  462. for (auto& dependantLoad : dependantLoads)
  463. dependantLoad->remainingDependencies--;
  464. }
  465. }
  466. for (auto& dependantLoad : dependantLoads)
  467. loadComplete(dependantLoad->resource);
  468. if (finishLoad && myLoadData != nullptr)
  469. {
  470. onResourceLoaded(resource);
  471. // This should only ever be true on the main thread
  472. if (myLoadData->notifyImmediately)
  473. ResourceListenerManager::instance().notifyListeners(uuid);
  474. bs_delete(myLoadData);
  475. }
  476. }
  477. void Resources::loadCallback(const Path& filePath, HResource& resource)
  478. {
  479. ResourcePtr rawResource = loadFromDiskAndDeserialize(filePath);
  480. {
  481. BS_LOCK_MUTEX(mInProgressResourcesMutex);
  482. // Check if all my dependencies are loaded
  483. ResourceLoadData* myLoadData = mInProgressResources[resource.getUUID()];
  484. myLoadData->loadedData = rawResource;
  485. myLoadData->remainingDependencies--;
  486. }
  487. loadComplete(resource);
  488. }
  489. BS_CORE_EXPORT Resources& gResources()
  490. {
  491. return Resources::instance();
  492. }
  493. }