Просмотр исходного кода

Refactored how resources are unloaded to allow more fine grained control

BearishSun 10 лет назад
Родитель
Сommit
c62e06ed74
38 измененных файлов с 483 добавлено и 346 удалено
  1. 0 5
      BansheeCore/Include/BsGpuParams.h
  2. 0 6
      BansheeCore/Include/BsIResourceListener.h
  3. 0 5
      BansheeCore/Include/BsMaterial.h
  4. 34 1
      BansheeCore/Include/BsResourceHandle.h
  5. 0 12
      BansheeCore/Include/BsResourceListenerManager.h
  6. 86 38
      BansheeCore/Include/BsResources.h
  7. 0 7
      BansheeCore/Include/BsTexture.h
  8. 2 0
      BansheeCore/Include/BsTextureManager.h
  9. 1 1
      BansheeCore/Source/BsCoreApplication.cpp
  10. 0 5
      BansheeCore/Source/BsMaterial.cpp
  11. 20 0
      BansheeCore/Source/BsResourceHandle.cpp
  12. 1 40
      BansheeCore/Source/BsResourceListenerManager.cpp
  13. 165 118
      BansheeCore/Source/BsResources.cpp
  14. 0 18
      BansheeCore/Source/BsTexture.cpp
  15. 1 1
      BansheeEditor/Source/BsProjectLibrary.cpp
  16. 20 1
      BansheeEngine/Include/BsBuiltinResources.h
  17. 0 5
      BansheeEngine/Include/BsRenderable.h
  18. 11 0
      BansheeEngine/Source/BsBuiltinResources.cpp
  19. 2 11
      BansheeEngine/Source/BsCGUIWidget.cpp
  20. 0 6
      BansheeEngine/Source/BsRenderable.cpp
  21. 3 4
      BansheeEngine/Source/BsSpriteTexture.cpp
  22. 0 1
      BansheeMono/Include/BsMonoUtil.h
  23. 11 0
      MBansheeEngine/Resource.cs
  24. 60 8
      MBansheeEngine/Resources.cs
  25. 1 1
      SBansheeEditor/Include/BsEditorResourceLoader.h
  26. 2 2
      SBansheeEditor/Source/BsEditorResourceLoader.cpp
  27. 1 1
      SBansheeEditor/Source/BsScriptBuildManager.cpp
  28. 7 5
      SBansheeEngine/Include/BsGameResourceManager.h
  29. 1 0
      SBansheeEngine/Include/BsScriptResource.h
  30. 1 1
      SBansheeEngine/Include/BsScriptResourceManager.h
  31. 4 2
      SBansheeEngine/Include/BsScriptResources.h
  32. 4 4
      SBansheeEngine/Source/BsGameResourceManager.cpp
  33. 1 1
      SBansheeEngine/Source/BsManagedResourceManager.cpp
  34. 6 0
      SBansheeEngine/Source/BsScriptResource.cpp
  35. 2 3
      SBansheeEngine/Source/BsScriptResourceManager.cpp
  36. 16 4
      SBansheeEngine/Source/BsScriptResources.cpp
  37. 1 1
      SBansheeEngine/Source/BsScriptScene.cpp
  38. 19 28
      TODO.txt

+ 0 - 5
BansheeCore/Include/BsGpuParams.h

@@ -395,11 +395,6 @@ namespace BansheeEngine
 		 */
 		void notifyResourceLoaded(const HResource& resource) override { markCoreDirty(); }
 
-		/**
-		 * @copydoc IResourceListener::notifyResourceDestroyed
-		 */
-		void notifyResourceDestroyed(const HResource& resource) override { markCoreDirty(); }
-
 		/**
 		 * @copydoc IResourceListener::notifyResourceChanged
 		 */

+ 0 - 6
BansheeCore/Include/BsIResourceListener.h

@@ -39,11 +39,5 @@ namespace BansheeEngine
 		 *			is pointing to changes.
 		 */
 		virtual void notifyResourceChanged(const HResource& resource) { }
-
-		/**
-		 * @brief	Called just before the resource handle is destroyed and resource
-		 *			unloaded.
-		 */
-		virtual void notifyResourceDestroyed(const HResource& resource) { }
 	};
 }

+ 0 - 5
BansheeCore/Include/BsMaterial.h

@@ -722,11 +722,6 @@ namespace BansheeEngine
 		 */
 		void notifyResourceLoaded(const HResource& resource) override;
 
-		/**
-		 * @copydoc IResourceListener::notifyResourceDestroyed
-		 */
-		void notifyResourceDestroyed(const HResource& resource) override;
-
 		/**
 		 * @copydoc IResourceListener::notifyResourceChanged
 		 */

+ 34 - 1
BansheeCore/Include/BsResourceHandle.h

@@ -43,6 +43,13 @@ namespace BansheeEngine
 		 */
 		void blockUntilLoaded(bool waitForDependencies = true) const;
 
+		/**
+		 * @brief	Releases an internal reference to this resource held by the resources system, if there is one.
+		 * 			
+		 * @see		Resources::release(ResourceHandleBase&)
+		 */
+		void release();
+
 		/**
 		 * @brief	Returns the UUID of the resource the handle is referring to.
 		 */
@@ -56,6 +63,11 @@ namespace BansheeEngine
 	protected:
 		ResourceHandleBase();
 
+		/**
+		 * @brief	Destroys the resource the handle is pointing to.
+		 */
+		void destroy();
+
 		/**
 		 * @brief	Sets the created flag to true and assigns the resource pointer. Called
 		 * 			by the constructors, or if you constructed just using a UUID, then you need to
@@ -67,6 +79,18 @@ namespace BansheeEngine
 		 */
 		void setHandleData(const SPtr<Resource>& ptr, const String& uuid);
 
+		/**
+		 * @brief	Increments the reference count of the handle. Only to be used by ::Resources for keeping
+		 * 			internal references.
+		 */
+		void addInternalRef();
+
+		/**
+		 * @brief	Decrements the reference count of the handle. Only to be used by ::Resources for keeping
+		 * 			internal references.
+		 */
+		void removeInternalRef();
+
 		/** @note	All handles to the same source must share this same handle data. Otherwise things
 		 *			like counting number of references or replacing pointed to resource become impossible
 		 *			without additional logic. */
@@ -123,7 +147,16 @@ namespace BansheeEngine
 
 	protected:
 		void addRef() { if (mData) mData->mRefCount++; };
-		void releaseRef() { if (mData) mData->mRefCount--; };
+		void releaseRef() 
+		{ 
+			if (mData)
+			{
+				mData->mRefCount--;
+
+				if (mData->mRefCount == 0)
+					destroy();
+			}
+		};
 
 		/************************************************************************/
 		/* 								RTTI		                     		*/

+ 0 - 12
BansheeCore/Include/BsResourceListenerManager.h

@@ -50,11 +50,6 @@ namespace BansheeEngine
 		 */
 		void onResourceLoaded(const HResource& resource);
 
-		/**
-		 * @brief	Triggered by the resources system just before a resource is unloaded and destroyed.
-		 */
-		void onResourceDestroyed(const HResource& resource);
-
 		/**
 		 * @brief	Triggered by the resources system after a resource handle is modified (i.e. points to a new resource).
 		 */
@@ -65,11 +60,6 @@ namespace BansheeEngine
 		 */
 		void sendResourceLoaded(const HResource& resource);
 
-		/**
-		 * @brief	Sends resource destroyed event to all listeners referencing this resource.
-		 */
-		void sendResourceDestroyed(const HResource& resource);
-
 		/**
 		 * @brief	Sends resource modified event to all listeners referencing this resource.
 		 */
@@ -86,7 +76,6 @@ namespace BansheeEngine
 		void addDependencies(IResourceListener* listener);
 
 		HEvent mResourceLoadedConn;
-		HEvent mResourceDestroyedConn;
 		HEvent mResourceModifiedConn;
 		
 		Set<IResourceListener*> mDirtyListeners;
@@ -94,7 +83,6 @@ namespace BansheeEngine
 		Map<IResourceListener*, Vector<UINT64>> mListenerToResourceMap;
 
 		Map<String, HResource> mLoadedResources;
-		Map<String, HResource> mDestroyedResources;
 		Map<String, HResource> mModifiedResources;
 
 		Vector<HResource> mTempResourceBuffer;

+ 86 - 38
BansheeCore/Include/BsResources.h

@@ -16,15 +16,30 @@ namespace BansheeEngine
 	 */
 	class BS_CORE_EXPORT Resources : public Module<Resources>
 	{
+		struct LoadedResourceData
+		{
+			LoadedResourceData()
+				:numInternalRefs(0)
+			{ }
+
+			LoadedResourceData(const WeakResourceHandle<Resource>& resource)
+				:resource(resource), numInternalRefs(0)
+			{ }
+
+			WeakResourceHandle<Resource> resource;
+			UINT32 numInternalRefs;
+		};
+
 		struct ResourceLoadData
 		{
-			ResourceLoadData(const HResource& resource, UINT32 numDependencies)
-				:resource(resource), remainingDependencies(numDependencies)
+			ResourceLoadData(const WeakResourceHandle<Resource>& resource, UINT32 numDependencies)
+				:resData(resource), remainingDependencies(numDependencies)
 			{ }
 
-			HResource resource;
+			LoadedResourceData resData;
 			ResourcePtr loadedData;
 			UINT32 remainingDependencies;
+			Vector<HResource> dependencies;
 			bool notifyImmediately;
 		};
 
@@ -35,30 +50,47 @@ namespace BansheeEngine
 		/**
 		 * @brief	Loads the resource from a given path. Returns an empty handle if resource can't be loaded.
 		 *			Resource is loaded synchronously.
+		 *			
+		 *			All loaded resources are reference counted and will be automatically unloaded when all of their references go out
+		 *			of scope. 
+		 *			
+		 * @param	filePath				File path to the resource to load. This can be absolute or relative to the working folder.
+		 * @param	loadDependencies		If true all resources referenced by the root resource will be loaded as well.
+		 * @param	keepInternalReference	If true the resource system will keep an internal reference to the resource so it
+		 *									doesn't get destroyed with it goes out of scope. You can call ::release to release 
+		 *									the internal reference. Each call to load will create a new internal reference and 
+		 *									therefore must be followed by the same number of release calls. 
+		 *									
+		 *									If dependencies are being loaded, they will not have internal references created regardless
+		 *									of this parameter.
+		 *			
+		 * @see		release(ResourceHandleBase&), unloadAllUnused()
 		 */
-		HResource load(const Path& filePath, bool loadDependencies = true);
+		HResource load(const Path& filePath, bool loadDependencies = true, bool keepInternalReference = true);
 
 		/**
 		 * @copydoc	load(const Path&, bool)
 		 */
 		template <class T>
-		ResourceHandle<T> load(const Path& filePath, bool loadDependencies = true)
+		ResourceHandle<T> load(const Path& filePath, bool loadDependencies = true, bool keepInternalReference = true)
 		{
-			return static_resource_cast<T>(load(filePath, loadDependencies));
+			return static_resource_cast<T>(load(filePath, loadDependencies, keepInternalReference));
 		}
 
 		/**
 		 * @brief	Loads the resource for the provided weak resource handle, or returns a loaded resource if already loaded.
+		 * 			
+		 * @see		load(const Path&, bool)
 		 */
-		HResource load(const WeakResourceHandle<Resource>& handle, bool loadDependencies = true);
+		HResource load(const WeakResourceHandle<Resource>& handle, bool loadDependencies = true, bool keepInternalReference = true);
 
 		/**
 		 * @copydoc	load(const WeakResourceHandle<T>&, bool)
 		 */
 		template <class T>
-		ResourceHandle<T> load(const WeakResourceHandle<T>& handle, bool loadDependencies = true)
+		ResourceHandle<T> load(const WeakResourceHandle<T>& handle, bool loadDependencies = true, bool keepInternalReference = true)
 		{
-			return static_resource_cast<T>(load((const WeakResourceHandle<Resource>&)handle, loadDependencies));
+			return static_resource_cast<T>(load((const WeakResourceHandle<Resource>&)handle, loadDependencies, keepInternalReference));
 		}
 
 		/**
@@ -67,49 +99,57 @@ namespace BansheeEngine
 		 *
 		 * @param	filePath	Full pathname of the file.
 		 * 						
-		 * @note	You can use returned invalid handle in engine systems as the engine will check for handle 
+		 * @note	You can use returned invalid handle in many engine systems as the engine will check for handle 
 		 *			validity before using it.
+		 *			
+		 * @see		load(const Path&, bool)
 		 */
-		HResource loadAsync(const Path& filePath, bool loadDependencies = true);
+		HResource loadAsync(const Path& filePath, bool loadDependencies = true, bool keepInternalReference = true);
 
 		/**
 		 * @copydoc	loadAsync
 		 */
 		template <class T>
-		ResourceHandle<T> loadAsync(const Path& filePath, bool loadDependencies = true)
+		ResourceHandle<T> loadAsync(const Path& filePath, bool loadDependencies = true, bool keepInternalReference = true)
 		{
-			return static_resource_cast<T>(loadAsync(filePath));
+			return static_resource_cast<T>(loadAsync(filePath, loadDependencies, keepInternalReference));
 		}
 
 		/**
 		 * @brief	Loads the resource with the given UUID. Returns an empty handle if resource can't be loaded.
 		 *
-		 * @param	uuid	UUID of the resource to load.
-		 * @param	async	If true resource will be loaded asynchronously. Handle to non-loaded
-		 *					resource will be returned immediately while loading will continue in the background.
+		 * @param	uuid					UUID of the resource to load.
+		 * @param	async					If true resource will be loaded asynchronously. Handle to non-loaded
+		 *									resource will be returned immediately while loading will continue in the background.		
+		 * @param	loadDependencies		If true all resources referenced by the root resource will be loaded as well.
+		 * @param	keepInternalReference	If true the resource system will keep an internal reference to the resource so it
+		 *									doesn't get destroyed with it goes out of scope. You can call ::release to release 
+		 *									the internal reference. Each call to load will create a new internal reference and 
+		 *									therefore must be followed by the same number of release calls. 
+		 *									
+		 *									If dependencies are being loaded, they will not have internal references created regardless
+		 *									of this parameter.	
+		 *													
+		 * @see		load(const Path&, bool)
 		 */
-		HResource loadFromUUID(const String& uuid, bool async = false, bool loadDependencies = true);
+		HResource loadFromUUID(const String& uuid, bool async = false, bool loadDependencies = true, bool keepInternalReference = true);
 
 		/**
-		 * @brief	Unloads the resource that is referenced by the handle. Any dependencies held by the resource will also 
-		 * 			be unloaded, but only if the resource is holding the last reference to them. This method will unload a 
-		 * 			resource even if it is being referenced and the caller must ensure whatever is referencing it can
-		 * 			handle a null resource, or ensure there are no references.
+		 * @brief	Releases an internal reference to the resource held by the resources system. This allows the resource
+		 * 			to be unloaded when it goes out of scope, if the resource was loaded with "keepInternalReference" parameter.
 		 *
-		 * @param	resourceHandle	Handle of the resource to unload.
-		 * 							
-		 * @note	GPU resources held by the resource will be scheduled to be destroyed on the core thread.
-		 * 			Actual resource pointer wont be deleted until all user-held references to it are removed.
+		 *			Alternatively you can also skip manually calling release() and call ::unloadAllUnused which will unload 
+		 *			all resources that do not have any external references, but you lose the fine grained control of what 
+		 *			will be unloaded.
+		 *			
+		 * @param	resourceHandle	Handle of the resource to release.
 		 */
-		void unload(HResource resource);
+		void release(ResourceHandleBase& resource);
 
 		/**
-		 * @copydoc unload(HResource&)
-		 */
-		void unload(WeakResourceHandle<Resource> resource);
-
-		/**
-		 * @brief	Finds all resources that aren't being referenced anywhere and unloads them.
+		 * @brief	Finds all resources that aren't being referenced outside of the resources system and unloads them.
+		 * 			
+		 * @see		release(ResourceHandleBase&)
 		 */
 		void unloadAllUnused();
 
@@ -231,12 +271,11 @@ namespace BansheeEngine
 		Event<void(const HResource&)> onResourceLoaded;
 
 		/**
-		 * @brief	Called when the resource has been destroyed. Subscriber should not hold on to the provided resource
-		 * 			reference as it will be destroyed.
+		 * @brief	Called when the resource has been destroyed. Provides UUID of the destroyed resource.
 		 *
 		 * @note	It is undefined from which thread this will get called from.
 		 */
-		Event<void(const HResource&)> onResourceDestroyed;
+		Event<void(const String&)> onResourceDestroyed;
 
 		/**
 		 * @brief	Called when the internal resource the handle is pointing to has changed.
@@ -245,12 +284,16 @@ namespace BansheeEngine
 		 */
 		Event<void(const HResource&)> onResourceModified;
 	private:
+		friend class ResourceHandleBase;
+
 		/**
 		 * @brief	Starts resource loading or returns an already loaded resource. Both UUID and filePath must match the
 		 * 			same resource, although you may provide an empty path in which case the resource will be retrieved
 		 * 			from memory if its currently loaded.
+		 * 			
+		 * @param	incrementRef	Determines should the internal reference count be incremented.
 		 */
-		HResource loadInternal(const String& UUID, const Path& filePath, bool synchronous, bool loadDependencies);
+		HResource loadInternal(const String& UUID, const Path& filePath, bool synchronous, bool loadDependencies, bool incrementRef);
 
 		/**
 		 * @brief	Performs actually reading and deserializing of the resource file. 
@@ -268,6 +311,11 @@ namespace BansheeEngine
 		 */
 		void loadCallback(const Path& filePath, HResource& resource);
 
+		/**
+		 * @brief	Destroys a resource, freeing its memory.
+		 */
+		void destroy(ResourceHandleBase& resource);
+
 	private:
 		Vector<ResourceManifestPtr> mResourceManifests;
 		ResourceManifestPtr mDefaultResourceManifest;
@@ -276,9 +324,9 @@ namespace BansheeEngine
 		BS_MUTEX(mLoadedResourceMutex);
 
 		UnorderedMap<String, WeakResourceHandle<Resource>> mHandles;
-		UnorderedMap<String, HResource> mLoadedResources;
+		UnorderedMap<String, LoadedResourceData> mLoadedResources;
 		UnorderedMap<String, ResourceLoadData*> mInProgressResources; // Resources that are being asynchronously loaded
-		UnorderedMap<String, Vector<ResourceLoadData*>> mDependantLoads;
+		UnorderedMap<String, Vector<ResourceLoadData*>> mDependantLoads; // Allows dependency to be notified when a dependant is loaded
 	};
 
 	/**

+ 0 - 7
BansheeCore/Include/BsTexture.h

@@ -392,13 +392,6 @@ namespace BansheeEngine
 		 */
 		SPtr<TextureCore> getCore() const;
 
-		/**
-		 * @brief	Returns a dummy 2x2 texture. Don't modify the returned texture.
-		 * 			
-		 * @note	Thread safe.
-		 */
-		static const HTexture& dummy();
-
 		/************************************************************************/
 		/* 								STATICS		                     		*/
 		/************************************************************************/

+ 2 - 0
BansheeCore/Include/BsTextureManager.h

@@ -101,6 +101,8 @@ namespace BansheeEngine
 		 *			to be implemented by render systems with their own implementations.
 		 */
 		virtual MultiRenderTexturePtr createMultiRenderTextureImpl(const MULTI_RENDER_TEXTURE_DESC& desc) = 0;
+
+		mutable HTexture mDummyTexture;
     };
 
 /**

+ 1 - 1
BansheeCore/Source/BsCoreApplication.cpp

@@ -70,6 +70,7 @@ namespace BansheeEngine
 		
 		Input::shutDown();
 
+		StringTableManager::shutDown();
 		Resources::shutDown();
 		ResourceListenerManager::shutDown();
 		GameObjectManager::shutDown();
@@ -93,7 +94,6 @@ namespace BansheeEngine
 		DynLibManager::shutDown();
 		Time::shutDown();
 		DeferredCallManager::shutDown();
-		StringTableManager::shutDown();
 
 		CoreThread::shutDown();
 		RenderStats::shutDown();

+ 0 - 5
BansheeCore/Source/BsMaterial.cpp

@@ -1257,11 +1257,6 @@ namespace BansheeEngine
 		initializeIfLoaded();
 	}
 
-	void Material::notifyResourceDestroyed(const HResource& resource)
-	{
-		initializeIfLoaded();
-	}
-
 	template<class T>
 	void copyParam(SPtr<GpuParams>& from, SPtr<GpuParams>& to, const String& name, UINT32 arraySize)
 	{

+ 20 - 0
BansheeCore/Source/BsResourceHandle.cpp

@@ -64,6 +64,16 @@ namespace BansheeEngine
 		}
 	}
 
+	void ResourceHandleBase::release()
+	{
+		gResources().release(*this);
+	}
+
+	void ResourceHandleBase::destroy()
+	{
+		gResources().destroy(*this);
+	}
+
 	void ResourceHandleBase::setHandleData(const SPtr<Resource>& ptr, const String& uuid)
 	{
 		mData->mPtr = ptr;
@@ -84,6 +94,16 @@ namespace BansheeEngine
 		}
 	}
 
+	void ResourceHandleBase::addInternalRef()
+	{
+		mData->mRefCount++;
+	}
+
+	void ResourceHandleBase::removeInternalRef()
+	{
+		mData->mRefCount--;
+	}
+
 	void ResourceHandleBase::throwIfNotLoaded() const
 	{
 #if BS_DEBUG_MODE

+ 1 - 40
BansheeCore/Source/BsResourceListenerManager.cpp

@@ -9,7 +9,6 @@ namespace BansheeEngine
 	ResourceListenerManager::ResourceListenerManager()
 	{
 		mResourceLoadedConn = gResources().onResourceLoaded.connect(std::bind(&ResourceListenerManager::onResourceLoaded, this, _1));
-		mResourceDestroyedConn = gResources().onResourceDestroyed.connect(std::bind(&ResourceListenerManager::onResourceDestroyed, this, _1));
 		mResourceModifiedConn = gResources().onResourceModified.connect(std::bind(&ResourceListenerManager::onResourceModified, this, _1));
 	}
 
@@ -18,7 +17,7 @@ namespace BansheeEngine
 		assert(mResourceToListenerMap.size() == 0 && "Not all resource listeners had their resources unregistered properly.");
 
 		mResourceLoadedConn.disconnect();
-		mResourceDestroyedConn.disconnect();
+		mResourceModifiedConn.disconnect();
 	}
 
 	void ResourceListenerManager::registerListener(IResourceListener* listener)
@@ -64,14 +63,10 @@ namespace BansheeEngine
 			for (auto& entry : mLoadedResources)
 				sendResourceLoaded(entry.second);
 
-			for (auto& entry : mDestroyedResources)
-				sendResourceDestroyed(entry.second);
-
 			for (auto& entry : mModifiedResources)
 				sendResourceModified(entry.second);
 
 			mLoadedResources.clear();
-			mDestroyedResources.clear();
 			mModifiedResources.clear();
 		}
 	}
@@ -88,14 +83,6 @@ namespace BansheeEngine
 			mLoadedResources.erase(iterFindLoaded);
 		}
 
-		auto iterFindDestroyed = mDestroyedResources.find(resourceUUID);
-		if (iterFindDestroyed != mDestroyedResources.end())
-		{
-			sendResourceDestroyed(iterFindDestroyed->second);
-
-			mDestroyedResources.erase(iterFindDestroyed);
-		}
-
 		auto iterFindModified = mModifiedResources.find(resourceUUID);
 		if (iterFindModified != mModifiedResources.end())
 		{
@@ -112,13 +99,6 @@ namespace BansheeEngine
 		mLoadedResources[resource.getUUID()] = resource;
 	}
 
-	void ResourceListenerManager::onResourceDestroyed(const HResource& resource)
-	{
-		BS_LOCK_RECURSIVE_MUTEX(mMutex);
-
-		mDestroyedResources[resource.getUUID()] = resource;
-	}
-
 	void ResourceListenerManager::onResourceModified(const HResource& resource)
 	{
 		BS_LOCK_RECURSIVE_MUTEX(mMutex);
@@ -145,25 +125,6 @@ namespace BansheeEngine
 		}
 	}
 
-	void ResourceListenerManager::sendResourceDestroyed(const HResource& resource)
-	{
-		UINT64 handleId = (UINT64)resource.getHandleData().get();
-
-		auto iterFind = mResourceToListenerMap.find(handleId);
-		if (iterFind == mResourceToListenerMap.end())
-			return;
-
-		const Vector<IResourceListener*> relevantListeners = iterFind->second;
-		for (auto& listener : relevantListeners)
-		{
-#if BS_DEBUG_MODE
-			assert(mActiveListeners.find(listener) != mActiveListeners.end() && "Attempting to notify a destroyed IResourceListener");
-#endif
-
-			listener->notifyResourceDestroyed(resource);
-		}
-	}
-
 	void ResourceListenerManager::sendResourceModified(const HResource& resource)
 	{
 		UINT64 handleId = (UINT64)resource.getHandleData().get();

+ 165 - 118
BansheeCore/Source/BsResources.cpp

@@ -22,13 +22,13 @@ namespace BansheeEngine
 	Resources::~Resources()
 	{
 		// Unload and invalidate all resources
-		UnorderedMap<String, HResource> loadedResourcesCopy = mLoadedResources;
+		UnorderedMap<String, LoadedResourceData> loadedResourcesCopy = mLoadedResources;
 
 		for (auto& loadedResourcePair : loadedResourcesCopy)
-			unload(loadedResourcePair.second);
+			destroy(loadedResourcePair.second.resource);
 	}
 
-	HResource Resources::load(const Path& filePath, bool loadDependencies)
+	HResource Resources::load(const Path& filePath, bool loadDependencies, bool keepInternalReference)
 	{
 		String uuid;
 		bool foundUUID = getUUIDFromFilePath(filePath, uuid);
@@ -36,19 +36,19 @@ namespace BansheeEngine
 		if (!foundUUID)
 			uuid = UUIDGenerator::generateRandom();
 
-		return loadInternal(uuid, filePath, true, loadDependencies);
+		return loadInternal(uuid, filePath, true, loadDependencies, keepInternalReference);
 	}
 
-	HResource Resources::load(const WeakResourceHandle<Resource>& handle, bool loadDependencies)
+	HResource Resources::load(const WeakResourceHandle<Resource>& handle, bool loadDependencies, bool keepInternalReference)
 	{
 		if (handle.mData == nullptr)
 			return HResource();
 
 		String uuid = handle.getUUID();
-		return loadFromUUID(uuid, false, loadDependencies);
+		return loadFromUUID(uuid, false, loadDependencies, keepInternalReference);
 	}
 
-	HResource Resources::loadAsync(const Path& filePath, bool loadDependencies)
+	HResource Resources::loadAsync(const Path& filePath, bool loadDependencies, bool keepInternalReference)
 	{
 		String uuid;
 		bool foundUUID = getUUIDFromFilePath(filePath, uuid);
@@ -56,61 +56,74 @@ namespace BansheeEngine
 		if (!foundUUID)
 			uuid = UUIDGenerator::generateRandom();
 
-		return loadInternal(uuid, filePath, false, loadDependencies);
+		return loadInternal(uuid, filePath, false, loadDependencies, keepInternalReference);
 	}
 
-	HResource Resources::loadFromUUID(const String& uuid, bool async, bool loadDependencies)
+	HResource Resources::loadFromUUID(const String& uuid, bool async, bool loadDependencies, bool keepInternalReference)
 	{
 		Path filePath;
 
 		// Default manifest is at 0th index but all other take priority since Default manifest could
 		// contain obsolete data. 
-		for(auto iter = mResourceManifests.rbegin(); iter != mResourceManifests.rend(); ++iter) 
+		for (auto iter = mResourceManifests.rbegin(); iter != mResourceManifests.rend(); ++iter)
 		{
-			if((*iter)->uuidToFilePath(uuid, filePath))
+			if ((*iter)->uuidToFilePath(uuid, filePath))
 				break;
 		}
 
-		return loadInternal(uuid, filePath, !async, loadDependencies);
+		return loadInternal(uuid, filePath, !async, loadDependencies, keepInternalReference);
 	}
 
-	HResource Resources::loadInternal(const String& UUID, const Path& filePath, bool synchronous, bool loadDependencies)
+	HResource Resources::loadInternal(const String& UUID, const Path& filePath, bool synchronous, bool loadDependencies, bool keepInternalReference)
 	{
 		HResource outputResource;
 
-		// Check if resource is already full loaded
 		bool alreadyLoading = false;
+		bool loadInProgress = false;
 		{
-			BS_LOCK_MUTEX(mLoadedResourceMutex);
-			auto iterFind = mLoadedResources.find(UUID);
-			if(iterFind != mLoadedResources.end()) // Resource is already loaded
+			// Check if resource is already being loaded on a worker thread
+			BS_LOCK_MUTEX(mInProgressResourcesMutex);
+			auto iterFind2 = mInProgressResources.find(UUID);
+			if (iterFind2 != mInProgressResources.end())
 			{
-				outputResource = iterFind->second;
+				LoadedResourceData& resData = iterFind2->second->resData;
+				outputResource = resData.resource.lock();
+
+				if (keepInternalReference)
+				{
+					resData.numInternalRefs++;
+					outputResource.addInternalRef();
+				}
+
 				alreadyLoading = true;
+				loadInProgress = true;
 			}
-		}
 
-		// Check if resource is already being loaded on a worker thread
-		bool loadInProgress = false;
-		if (!alreadyLoading) // If not already detected as loaded
-		{
+			// Previously being loaded as async but now we want it synced, so we wait
+			if (loadInProgress && synchronous)
+				outputResource.blockUntilLoaded();
+
+			if (!alreadyLoading)
 			{
-				BS_LOCK_MUTEX(mInProgressResourcesMutex);
-				auto iterFind2 = mInProgressResources.find(UUID);
-				if (iterFind2 != mInProgressResources.end())
+				BS_LOCK_MUTEX(mLoadedResourceMutex);
+				auto iterFind = mLoadedResources.find(UUID);
+				if (iterFind != mLoadedResources.end()) // Resource is already loaded
 				{
-					outputResource = iterFind2->second->resource;
+					LoadedResourceData& resData = iterFind->second;
+					outputResource = resData.resource.lock();
+
+					if (keepInternalReference)
+					{
+						resData.numInternalRefs++;
+						outputResource.addInternalRef();
+					}
 
 					alreadyLoading = true;
-					loadInProgress = true;
 				}
 			}
-
-			// Previously being loaded as async but now we want it synced, so we wait
-			if (loadInProgress && synchronous)
-				outputResource.blockUntilLoaded();
 		}
 
+
 		// Not loaded and not in progress, start loading of new resource
 		// (or if already loaded or in progress, load any dependencies)
 		if (!alreadyLoading)
@@ -167,9 +180,16 @@ namespace BansheeEngine
 			{
 				BS_LOCK_MUTEX(mInProgressResourcesMutex);
 
-				ResourceLoadData* loadData = bs_new<ResourceLoadData>(outputResource, 0);
+				ResourceLoadData* loadData = bs_new<ResourceLoadData>(outputResource.getWeak(), 0);
 				mInProgressResources[UUID] = loadData;
-				loadData->resource = outputResource;
+				loadData->resData = outputResource.getWeak();
+
+				if (keepInternalReference)
+				{
+					loadData->resData.numInternalRefs++;
+					outputResource.addInternalRef();
+				}
+
 				loadData->remainingDependencies = 1;
 				loadData->notifyImmediately = synchronous; // Make resource listener trigger before exit if loading synchronously
 
@@ -189,9 +209,19 @@ namespace BansheeEngine
 
 			if (loadDependencies && savedResourceData != nullptr)
 			{
-				for (auto& dependency : savedResourceData->getDependencies())
+				const Vector<String>& dependencyUUIDs = savedResourceData->getDependencies();
+				UINT32 numDependencies = (UINT32)dependencyUUIDs.size();
+				Vector<HResource> dependencies(numDependencies);
+
+				for (UINT32 i = 0; i < numDependencies; i++)
+					dependencies[i] = loadFromUUID(dependencyUUIDs[i], !synchronous, true, false);
+
+				// Keep dependencies alive until the parent is done loading
 				{
-					loadFromUUID(dependency, !synchronous);
+					BS_LOCK_MUTEX(mInProgressResourcesMutex);
+
+					// At this point the resource is guaranteed to still be in-progress, so it's safe to update its dependency list
+					mInProgressResources[UUID]->dependencies = dependencies;
 				}
 			}
 		}
@@ -208,8 +238,8 @@ namespace BansheeEngine
 					auto iterFind = mInProgressResources.find(UUID);
 					if (iterFind == mInProgressResources.end()) // Fully loaded
 					{
-						loadData = bs_new<ResourceLoadData>(outputResource, 0);
-						loadData->resource = outputResource;
+						loadData = bs_new<ResourceLoadData>(outputResource.getWeak(), 0);
+						loadData->resData = outputResource.getWeak();
 						loadData->remainingDependencies = 0;
 						loadData->notifyImmediately = synchronous; // Make resource listener trigger before exit if loading synchronously
 
@@ -234,7 +264,7 @@ namespace BansheeEngine
 								auto iterFind3 = std::find_if(dependantData.begin(), dependantData.end(),
 									[&](ResourceLoadData* x)
 								{
-									return x->resource.getUUID() == outputResource.getUUID();
+									return x->resData.resource.getUUID() == outputResource.getUUID();
 								});
 
 								registerDependency = iterFind3 == dependantData.end();
@@ -244,13 +274,14 @@ namespace BansheeEngine
 							{
 								mDependantLoads[dependency].push_back(loadData);
 								loadData->remainingDependencies++;
+								loadData->dependencies.push_back(_getResourceHandle(dependency));
 							}
 						}
 					}
 				}
 
 				for (auto& dependency : dependencies)
-					loadFromUUID(dependency, !synchronous);
+					loadFromUUID(dependency, !synchronous, true, false);
 			}
 		}
 
@@ -308,64 +339,42 @@ namespace BansheeEngine
 		return resource;
 	}
 
-	void Resources::unload(HResource resource)
+	void Resources::release(ResourceHandleBase& resource)
 	{
-		if (resource == nullptr)
-			return;
+		const String& UUID = resource.getUUID();
 
-		if (!resource.isLoaded(false))
 		{
 			bool loadInProgress = false;
-			{
-				BS_LOCK_MUTEX(mInProgressResourcesMutex);
-				auto iterFind2 = mInProgressResources.find(resource.getUUID());
-				if (iterFind2 != mInProgressResources.end())
-					loadInProgress = true;
-			}
-
-			if (loadInProgress) // If it's still loading wait until that finishes
-				resource.blockUntilLoaded();
-			else
-				return; // Already unloaded
-		}
-
-		Vector<ResourceDependency> dependencies = Utility::findResourceDependencies(*resource.get());
 
-		// Notify external systems before we actually destroy it
-		onResourceDestroyed(resource);
-
-		resource->destroy();
-
-		const String& uuid = resource.getUUID();
-		{
-			BS_LOCK_MUTEX(mLoadedResourceMutex);
-			mLoadedResources.erase(uuid);
-		}
-
-		resource.setHandleData(nullptr, uuid);
+			BS_LOCK_MUTEX(mInProgressResourcesMutex);
+			auto iterFind2 = mInProgressResources.find(UUID);
+			if (iterFind2 != mInProgressResources.end())
+				loadInProgress = true;
 
-		for (auto& dependency : dependencies)
-		{
-			HResource dependantResource = dependency.resource;
+			// Technically we should be able to just cancel a load in progress instead of blocking until it finishes.
+			// However that would mean the last reference could get lost on whatever thread did the loading, which
+			// isn't something that's supported. If this ends up being a problem either make handle counting atomic
+			// or add a separate queue for objects destroyed from the load threads.
+			if (loadInProgress)
+				resource.blockUntilLoaded();
 
-			// Last reference was kept by the unloaded resource, so unload the dependency too
-			if ((UINT32)dependantResource.mData->mRefCount == (dependency.numReferences + 1))
 			{
-				// TODO - Use count is not thread safe. Meaning it might increase after above check, in
-				// which case we will be unloading a resource that is in use. I don't see a way around 
-				// it at the moment.
+				BS_LOCK_MUTEX(mLoadedResourceMutex);
+				auto iterFind = mLoadedResources.find(UUID);
+				if (iterFind != mLoadedResources.end()) // Resource is already loaded
+				{
+					LoadedResourceData& resData = iterFind->second;
 
-				unload(dependantResource);
+					assert(resData.numInternalRefs > 0);
+					resData.numInternalRefs--;
+					resource.removeInternalRef();
+
+					return;
+				}
 			}
 		}
 	}
 
-	void Resources::unload(WeakResourceHandle<Resource> resource)
-	{
-		HResource handle = resource.lock();
-		unload(handle);
-	}
-
 	void Resources::unloadAllUnused()
 	{
 		Vector<HResource> resourcesToUnload;
@@ -374,8 +383,10 @@ namespace BansheeEngine
 			BS_LOCK_MUTEX(mLoadedResourceMutex);
 			for(auto iter = mLoadedResources.begin(); iter != mLoadedResources.end(); ++iter)
 			{
-				if (iter->second.mData->mRefCount == 1) // We just have this one reference, meaning nothing is using this resource
-					resourcesToUnload.push_back(iter->second);
+				const LoadedResourceData& resData = iter->second;
+
+				if (resData.resource.mData->mRefCount == resData.numInternalRefs) // Only internal references exist, free it
+					resourcesToUnload.push_back(resData.resource.lock());
 			}
 		}
 
@@ -384,8 +395,58 @@ namespace BansheeEngine
 		// handles gracefully.
 		for(auto iter = resourcesToUnload.begin(); iter != resourcesToUnload.end(); ++iter)
 		{
-			unload(*iter);
+			release(*iter);
+		}
+	}
+
+	void Resources::destroy(ResourceHandleBase& resource)
+	{
+		if (resource.mData == nullptr)
+			return;
+
+		const String& uuid = resource.getUUID();
+		if (!resource.isLoaded(false))
+		{
+			bool loadInProgress = false;
+			{
+				BS_LOCK_MUTEX(mInProgressResourcesMutex);
+				auto iterFind2 = mInProgressResources.find(uuid);
+				if (iterFind2 != mInProgressResources.end())
+					loadInProgress = true;
+			}
+
+			if (loadInProgress) // If it's still loading wait until that finishes
+				resource.blockUntilLoaded();
+			else
+				return; // Already unloaded
+		}
+
+		// Notify external systems before we actually destroy it
+		onResourceDestroyed(uuid);
+		resource.mData->mPtr->destroy();
+
+		{
+			BS_LOCK_MUTEX(mLoadedResourceMutex);
+
+			auto iterFind = mLoadedResources.find(uuid);
+			if (iterFind != mLoadedResources.end())
+			{
+				LoadedResourceData& resData = iterFind->second;
+				while (resData.numInternalRefs > 0)
+				{
+					resData.numInternalRefs--;
+					resData.resource.removeInternalRef();
+				}
+
+				mLoadedResources.erase(iterFind);
+			}
+			else
+			{
+				assert(false); // This should never happen but in case it does fail silently in release mode
+			}
 		}
+
+		resource.setHandleData(nullptr, uuid);
 	}
 
 	void Resources::save(const HResource& resource, const Path& filePath, bool overwrite)
@@ -527,7 +588,8 @@ namespace BansheeEngine
 		{
 			BS_LOCK_MUTEX(mLoadedResourceMutex);
 
-			mLoadedResources[uuid] = newHandle;
+			LoadedResourceData& resData = mLoadedResources[uuid];
+			resData.resource = newHandle.getWeak();
 			mHandles[uuid] = newHandle.getWeak();
 		}
 	
@@ -536,35 +598,18 @@ namespace BansheeEngine
 
 	HResource Resources::_getResourceHandle(const String& uuid)
 	{
+		BS_LOCK_MUTEX(mLoadedResourceMutex);
+		auto iterFind3 = mHandles.find(uuid);
+		if (iterFind3 != mHandles.end()) // Not loaded, but handle does exist
 		{
-			BS_LOCK_MUTEX(mInProgressResourcesMutex);
-			auto iterFind2 = mInProgressResources.find(uuid);
-			if (iterFind2 != mInProgressResources.end())
-			{
-				return iterFind2->second->resource;
-			}
-
-			{
-				BS_LOCK_MUTEX(mLoadedResourceMutex);
-				auto iterFind = mLoadedResources.find(uuid);
-				if (iterFind != mLoadedResources.end()) // Resource is already loaded
-				{
-					return iterFind->second;
-				}
-
-				auto iterFind3 = mHandles.find(uuid);
-				if (iterFind3 != mHandles.end()) // Not loaded, but handle does exist
-				{
-					return iterFind3->second.lock();
-				}
+			return iterFind3->second.lock();
+		}
 
-				// Create new handle
-				HResource handle(uuid);
-				mHandles[uuid] = handle.getWeak();
+		// Create new handle
+		HResource handle(uuid);
+		mHandles[uuid] = handle.getWeak();
 
-				return handle;
-			}
-		}
+		return handle;
 	}
 
 	bool Resources::getFilePathFromUUID(const String& uuid, Path& filePath) const
@@ -628,8 +673,7 @@ namespace BansheeEngine
 				{
 					BS_LOCK_MUTEX(mLoadedResourceMutex);
 
-					mLoadedResources[uuid] = resource;
-					mHandles[uuid] = resource.getWeak();
+					mLoadedResources[uuid] = myLoadData->resData;
 					resource.setHandleData(myLoadData->loadedData, uuid);
 				}
 
@@ -639,7 +683,10 @@ namespace BansheeEngine
 		}
 
 		for (auto& dependantLoad : dependantLoads)
-			loadComplete(dependantLoad->resource);
+		{
+			HResource dependant = dependantLoad->resData.resource.lock();
+			loadComplete(dependant);
+		}
 
 		if (finishLoad && myLoadData != nullptr)
 		{

+ 0 - 18
BansheeCore/Source/BsTexture.cpp

@@ -535,22 +535,4 @@ namespace BansheeEngine
 		return TextureManager::instance().createTexture(texType, 
 			width, height, num_mips, format, usage, hwGammaCorrection, multisampleCount);
 	}
-
-	const HTexture& Texture::dummy()
-	{
-		static HTexture dummyTexture;
-		if (!dummyTexture)
-		{
-			PixelDataPtr pixelData = PixelData::create(2, 2, 1, PF_R8G8B8A8);
-
-			pixelData->setColorAt(Color::Red, 0, 0);
-			pixelData->setColorAt(Color::Red, 0, 1);
-			pixelData->setColorAt(Color::Red, 1, 0);
-			pixelData->setColorAt(Color::Red, 1, 1);
-
-			dummyTexture = create(pixelData);
-		}
-
-		return dummyTexture;
-	}
 }

+ 1 - 1
BansheeEditor/Source/BsProjectLibrary.cpp

@@ -462,7 +462,7 @@ namespace BansheeEngine
 				String uuid = importedResource.getUUID();
 
 				if (unloadWhenDone)
-					gResources().unload(importedResource);
+					gResources().release(importedResource);
 
 				mResourceManifest->registerResource(uuid, internalResourcesPath);
 			}

+ 20 - 1
BansheeEngine/Include/BsBuiltinResources.h

@@ -31,10 +31,25 @@ namespace BansheeEngine
 		 */
 		const HGUISkin& getGUISkin() const { return mSkin; }
 
+		/**
+		 * @brief	Returns an empty skin used to be used when no other is available.
+		 */
+		const HGUISkin& getEmptyGUISkin() const { return mEmptySkin; }
+
 		/**
 		 * @brief	Returns a small entirely white texture.
 		 */
-		const HSpriteTexture getWhiteSpriteTexture() const { return mWhiteSpriteTexture; }
+		const HSpriteTexture& getWhiteSpriteTexture() const { return mWhiteSpriteTexture; }
+
+		/**
+		 * @brief	Returns a 2x2 sprite texture that can be used when no other is available.
+		 */
+		const HSpriteTexture& getDummySpriteTexture() const { return mDummySpriteTexture; }
+
+		/**
+		 * @brief	Returns a dummy 2x2 texture that may be used when no other is available. Don't modify the returned texture.
+		 */
+		const HTexture& getDummyTexture() const { return mDummyTexture; }
 
 		/**
 		 * @brief	Returns image data for an arrow cursor, along with its hotspot.
@@ -168,6 +183,7 @@ namespace BansheeEngine
 		 */
 		HTexture getCursorTexture(const WString& name);
 
+		HGUISkin mEmptySkin;
 		HGUISkin mSkin;
 
 		PixelDataPtr mCursorArrow;
@@ -183,6 +199,9 @@ namespace BansheeEngine
 		PixelDataPtr mBansheeIcon;
 
 		HSpriteTexture mWhiteSpriteTexture;
+		HSpriteTexture mDummySpriteTexture;
+
+		HTexture mDummyTexture;
 
 		HShader mShaderSpriteText;
 		HShader mShaderSpriteImage;

+ 0 - 5
BansheeEngine/Include/BsRenderable.h

@@ -286,11 +286,6 @@ namespace BansheeEngine
 		 */
 		void notifyResourceLoaded(const HResource& resource) override;
 
-		/**
-		 * @copydoc IResourceListener::notifyResourceDestroyed
-		 */
-		void notifyResourceDestroyed(const HResource& resource) override;
-
 		/**
 		 * @copydoc IResourceListener::notifyResourceChanged
 		 */

+ 11 - 0
BansheeEngine/Source/BsBuiltinResources.cpp

@@ -242,9 +242,20 @@ namespace BansheeEngine
 		mShaderSpriteNonAlphaImage = getShader(ShaderSpriteImageNoAlphaFile);
 		mShaderDiffuse = getShader(ShaderDiffuseFile);
 
+		PixelDataPtr pixelData = PixelData::create(2, 2, 1, PF_R8G8B8A8);
+
+		pixelData->setColorAt(Color::Red, 0, 0);
+		pixelData->setColorAt(Color::Red, 0, 1);
+		pixelData->setColorAt(Color::Red, 1, 0);
+		pixelData->setColorAt(Color::Red, 1, 1);
+
+		mDummyTexture = Texture::create(pixelData);
+
 		mWhiteSpriteTexture = getSkinTexture(WhiteTex);
+		mDummySpriteTexture = SpriteTexture::create(mDummyTexture);
 
 		mSkin = gResources().load<GUISkin>(mBuiltinDataFolder + (GUISkinFile + L".asset"));
+		mEmptySkin = GUISkin::create();
 
 		/************************************************************************/
 		/* 								CURSOR		                     		*/

+ 2 - 11
BansheeEngine/Source/BsCGUIWidget.cpp

@@ -2,21 +2,14 @@
 #include "BsGUIManager.h"
 #include "BsGUISkin.h"
 #include "BsGUILabel.h"
-#include "BsGUIMouseEvent.h"
 #include "BsGUIPanel.h"
-#include "BsCoreApplication.h"
 #include "BsCoreThreadAccessor.h"
-#include "BsMaterial.h"
-#include "BsPass.h"
-#include "BsMesh.h"
 #include "BsVector2I.h"
 #include "BsCCamera.h"
 #include "BsViewport.h"
 #include "BsSceneObject.h"
-#include "BsRenderWindow.h"
 #include "BsCGUIWidgetRTTI.h"
-#include "BsProfilerCPU.h"
-#include "BsDebug.h"
+#include "BsBuiltinResources.h"
 
 namespace BansheeEngine
 {
@@ -268,12 +261,10 @@ namespace BansheeEngine
 
 	const GUISkin& CGUIWidget::getSkin() const
 	{
-		static const HGUISkin DEFAULT_SKIN = GUISkin::create();
-
 		if(mSkin.isLoaded())
 			return *mSkin;
 		else
-			return *DEFAULT_SKIN;
+			return *BuiltinResources::instance().getEmptyGUISkin();
 	}
 
 	bool CGUIWidget::isDirty(bool cleanIfDirty)

+ 0 - 6
BansheeEngine/Source/BsRenderable.cpp

@@ -348,12 +348,6 @@ namespace BansheeEngine
 		markCoreDirty();
 	}
 
-	void Renderable::notifyResourceDestroyed(const HResource& resource)
-	{
-		markDependenciesDirty();
-		markCoreDirty();
-	}
-
 	void Renderable::notifyResourceChanged(const HResource& resource)
 	{
 		markDependenciesDirty();

+ 3 - 4
BansheeEngine/Source/BsSpriteTexture.cpp

@@ -2,6 +2,7 @@
 #include "BsSpriteTextureRTTI.h"
 #include "BsTexture.h"
 #include "BsResources.h"
+#include "BsBuiltinResources.h"
 
 namespace BansheeEngine
 {
@@ -30,9 +31,7 @@ namespace BansheeEngine
 
 	const HSpriteTexture& SpriteTexture::dummy()
 	{
-		static HSpriteTexture dummyTex = create(Texture::dummy());
-
-		return dummyTex;
+		return BuiltinResources::instance().getDummySpriteTexture();
 	}
 
 	bool SpriteTexture::checkIsLoaded(const HSpriteTexture& tex)
@@ -87,7 +86,7 @@ namespace BansheeEngine
 	SpriteTexturePtr SpriteTexture::createEmpty()
 	{
 		SpriteTexturePtr texturePtr = bs_core_ptr<SpriteTexture>
-			(new (bs_alloc<SpriteTexture>()) SpriteTexture(Vector2(0.0f, 0.0f), Vector2(1.0f, 1.0f), Texture::dummy()));
+			(new (bs_alloc<SpriteTexture>()) SpriteTexture(Vector2(0.0f, 0.0f), Vector2(1.0f, 1.0f), HTexture()));
 
 		texturePtr->_setThisPtr(texturePtr);
 		texturePtr->initialize();

+ 0 - 1
BansheeMono/Include/BsMonoUtil.h

@@ -157,7 +157,6 @@ namespace BansheeEngine
 				String msg =  "Managed exception: " + toString(monoToWString(exceptionMsg)) + "\n" + toString(monoToWString(exceptionStackTrace));
 
 				LOGERR(msg);
-				BS_EXCEPT(InternalErrorException, msg);
 			}
 		}
 

+ 11 - 0
MBansheeEngine/Resource.cs

@@ -24,10 +24,21 @@ namespace BansheeEngine
             get { return Internal_GetUUID(mCachedPtr); }
         }
 
+        /// <summary>
+        /// Releases an internal reference to the resource held by the resources system. <see cref="Resources.Release"/>
+        /// </summary>
+        public void Release()
+        {
+            Internal_Release(mCachedPtr);
+        }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern string Internal_GetName(IntPtr nativeInstance);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern string Internal_GetUUID(IntPtr nativeInstance);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_Release(IntPtr nativeInstance);
     }
 }

+ 60 - 8
MBansheeEngine/Resources.cs

@@ -12,32 +12,78 @@ namespace BansheeEngine
         /// Loads a resource at the specified path. If running outside of the editor you must make sure to mark that 
         /// the resource gets included in the build. If running inside the editor this has similar functionality as
         /// if loading using the project library. If resource is already loaded an existing instance is returned.
+        /// 
+        /// All resources are automatically unloaded when they go out of scope unless <see cref="keepLoaded"/> parameter
+        /// is specified. 
         /// </summary>
         /// <typeparam name="T">Type of the resource.</typeparam>
         /// <param name="path">Path of the resource, relative to game directory. If running from editor this will be
         ///                    the same location as resource location in the project library.</param>
+        /// <param name="keepLoaded">If true the system will keep the resource loaded even when it goes out of scope.
+        ///                          You must call <see cref="Release(Resource)"/> in order to allow the resource to be
+        ///                          unloaded (it must be called once for each corresponding load). </param>
         /// <returns>Loaded resource, or null if resource cannot be found.</returns>
-        public static T Load<T>(string path) where T : Resource
+        public static T Load<T>(string path, bool keepLoaded = true) where T : Resource
         {
-            return (T)Internal_Load(path);
+            return (T)Internal_Load(path, keepLoaded);
         }
 
         /// <summary>
         /// Loads a resource referenced by the provided reference. If running outside of the editor you must make sure 
         /// to mark that the resource gets included in the build. If running inside the editor this has similar functionality 
         /// as if loading using the project library. If resource is already loaded an existing instance is returned.
+        ///
+        /// All resources are automatically unloaded when they go out of scope unless <see cref="keepLoaded"/> parameter
+        /// is specified.
         /// </summary>
         /// <typeparam name="T">Type of the resource.</typeparam>
         /// <param name="reference">Reference to the resource to load.</param>
+        /// <param name="keepLoaded">If true the system will keep the resource loaded even when it goes out of scope.
+        ///                          You must call <see cref="Release(ResourceRef)"/> in order to allow the resource to be
+        ///                          unloaded (it must be called once for each corresponding load). </param> 
         /// <returns>Loaded resource, or null if resource cannot be found.</returns>
-        public static T Load<T>(ResourceRef reference) where T : Resource
+        public static T Load<T>(ResourceRef reference, bool keepLoaded = true) where T : Resource
         {
-            return (T)Internal_LoadRef(reference);
+            return (T)Internal_LoadRef(reference, keepLoaded);
         }
 
         /// <summary>
-        /// Unloads all resources that are no longer referenced. Usually the system keeps resources in memory even if
-        /// they are no longer referenced to avoid constant loading/unloading if resource is often passed around.
+        /// Releases an internal reference to the resource held by the resources system. This allows the resource
+        ///	to be unloaded when it goes out of scope, if the resource was loaded with "keepLoaded" parameter.
+        ///
+        /// Alternatively you can also skip manually calling <see cref="Release(ResourceRef)"/> and call <see cref="UnloadUnused"/> 
+        /// which will unload all resources that do not have any external references, but you lose the fine grained control 
+        /// of what will be unloaded.
+        /// </summary>
+        /// <param name="resource">Resource to release</param>
+        public static void Release(ResourceRef resource)
+        {
+            if (resource == null)
+                return;
+
+            Internal_ReleaseRef(resource.GetCachedPtr());
+        }
+
+        /// <summary>
+        /// Releases an internal reference to the resource held by the resources system. This allows the resource
+        ///	to be unloaded when it goes out of scope, if the resource was loaded with "keepLoaded" parameter.
+        ///
+        /// Alternatively you can also skip manually calling <see cref="Release(Resource)"/> and call <see cref="UnloadUnused"/> 
+        /// which will unload all resources that do not have any external references, but you lose the fine grained control 
+        /// of what will be unloaded.
+        /// </summary>
+        /// <param name="resource">Resource to release</param>
+        public static void Release(Resource resource)
+        {
+            if(resource == null)
+                return;
+
+            Internal_Release(resource.GetCachedPtr());
+        }
+
+        /// <summary>
+        /// Unloads all resources that are no longer referenced. This only applies to resources loaded with "keepLoaded"
+        /// parameter, as all other resources will be loaded when they go out of scope.
         /// </summary>
         public static void UnloadUnused()
         {
@@ -45,10 +91,16 @@ namespace BansheeEngine
         }
 
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern Resource Internal_Load(string path);
+        private static extern Resource Internal_Load(string path, bool keepLoaded);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern Resource Internal_LoadRef(ResourceRef reference, bool keepLoaded);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_Release(IntPtr resource);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern Resource Internal_LoadRef(ResourceRef reference);
+        private static extern void Internal_ReleaseRef(IntPtr resource);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_UnloadUnused();

+ 1 - 1
SBansheeEditor/Include/BsEditorResourceLoader.h

@@ -14,6 +14,6 @@ namespace BansheeEngine
 		/**
 		 * @copydoc	IGameResourceLoader::load
 		 */
-		HResource load(const Path& path) const override;
+		HResource load(const Path& path, bool keepLoaded) const override;
 	};
 }

+ 2 - 2
SBansheeEditor/Source/BsEditorResourceLoader.cpp

@@ -6,7 +6,7 @@
 
 namespace BansheeEngine
 {
-	HResource EditorResourceLoader::load(const Path& path) const
+	HResource EditorResourceLoader::load(const Path& path, bool keepLoaded) const
 	{
 		ProjectLibrary::LibraryEntry* entry = gProjectLibrary().findEntry(path);
 
@@ -28,6 +28,6 @@ namespace BansheeEngine
 					isn't flagged to be included in the build. It may not be available outside of the editor.");
 		}
 
-		return gResources().loadFromUUID(resUUID);
+		return gResources().loadFromUUID(resUUID, false, true, keepLoaded);
 	}
 }

+ 1 - 1
SBansheeEditor/Source/BsScriptBuildManager.cpp

@@ -336,7 +336,7 @@ namespace BansheeEngine
 
 				// Need to unload this one as we modified it in memory, and we don't want to persist those changes past
 				// this point
-				gResources().unload(prefab);
+				gResources().release(prefab);
 
 				if (reload)
 					gProjectLibrary().load(sourcePath);

+ 7 - 5
SBansheeEngine/Include/BsGameResourceManager.h

@@ -17,7 +17,7 @@ namespace BansheeEngine
 		/**
 		 * @brief	Loads the resource at the specified path.
 		 */
-		virtual HResource load(const Path& path) const = 0;
+		virtual HResource load(const Path& path, bool keepLoaded) const = 0;
 	};
 
 	/**
@@ -29,7 +29,7 @@ namespace BansheeEngine
 		/**
 		 * @copydoc	IGameResourceLoader::load
 		 */
-		HResource load(const Path& path) const override;
+		HResource load(const Path& path, bool keepLoaded) const override;
 	};
 
 	/**
@@ -47,16 +47,18 @@ namespace BansheeEngine
 
 		/**
 		 * @brief	Loads the resource at the specified path.
+		 * 			
+		 * @see	Resources::load
 		 */
-		HResource load(const Path& path) const;	
+		HResource load(const Path& path, bool keepLoaded) const;	
 
 		/**
 		 * @copydoc	load
 		 */
 		template <class T>
-		ResourceHandle<T> load(const Path& filePath)
+		ResourceHandle<T> load(const Path& filePath, bool keepLoaded)
 		{
-			return static_resource_cast<T>(load(filePath));
+			return static_resource_cast<T>(load(filePath, keepLoaded));
 		}
 
 		/**

+ 1 - 0
SBansheeEngine/Include/BsScriptResource.h

@@ -164,5 +164,6 @@ namespace BansheeEngine
 		/************************************************************************/
 		static MonoString* internal_getName(ScriptResourceBase* nativeInstance);
 		static MonoString* internal_getUUID(ScriptResourceBase* nativeInstance);
+		static void internal_release(ScriptResourceBase* nativeInstance);
 	};
 }

+ 1 - 1
SBansheeEngine/Include/BsScriptResourceManager.h

@@ -92,7 +92,7 @@ namespace BansheeEngine
 		/**
 		 * @brief	Triggered when the native resource has been unloaded and therefore destroyed.
 		 */
-		void onResourceDestroyed(const HResource& resource);
+		void onResourceDestroyed(const String& UUID);
 
 		/**
 		 * @brief	Throws an exception if the provided UUID already exists in the interop object

+ 4 - 2
SBansheeEngine/Include/BsScriptResources.h

@@ -19,8 +19,10 @@ namespace BansheeEngine
 		/************************************************************************/
 		/* 								CLR HOOKS						   		*/
 		/************************************************************************/
-		static MonoObject* internal_Load(MonoString* path);
-		static MonoObject* internal_LoadRef(MonoObject* reference);
+		static MonoObject* internal_Load(MonoString* path, bool keepLoaded);
+		static MonoObject* internal_LoadRef(MonoObject* reference, bool keepLoaded);
+		static void internal_Release(ScriptResourceBase* resource);
+		static void internal_ReleaseRef(ScriptResourceRef* resource);
 		static void internal_UnloadUnused();
 	};
 }

+ 4 - 4
SBansheeEngine/Source/BsGameResourceManager.cpp

@@ -3,9 +3,9 @@
 
 namespace BansheeEngine
 {
-	HResource StandaloneResourceLoader::load(const Path& path) const
+	HResource StandaloneResourceLoader::load(const Path& path, bool keepLoaded) const
 	{
-		return gResources().load(path);
+		return gResources().load(path, true, keepLoaded);
 	}
 
 	GameResourceManager::GameResourceManager()
@@ -14,9 +14,9 @@ namespace BansheeEngine
 		
 	}
 
-	HResource GameResourceManager::load(const Path& path) const
+	HResource GameResourceManager::load(const Path& path, bool keepLoaded) const
 	{
-		return mLoader->load(path);
+		return mLoader->load(path, keepLoaded);
 	}
 
 	void GameResourceManager::setLoader(const SPtr<IGameResourceLoader>& loader)

+ 1 - 1
SBansheeEngine/Source/BsManagedResourceManager.cpp

@@ -19,7 +19,7 @@ namespace BansheeEngine
 		for (auto& resourcePair : resourceCopy)
 		{
 			WeakResourceHandle<ManagedResource> resource = resourcePair.second;
-			gResources().unload((WeakResourceHandle<Resource>&)resource);
+			gResources().release((WeakResourceHandle<Resource>&)resource);
 		}
 
 		mResources.clear();

+ 6 - 0
SBansheeEngine/Source/BsScriptResource.cpp

@@ -49,6 +49,7 @@ namespace BansheeEngine
 	{
 		metaData.scriptClass->addInternalCall("Internal_GetName", &ScriptResource::internal_getName);
 		metaData.scriptClass->addInternalCall("Internal_GetUUID", &ScriptResource::internal_getUUID);
+		metaData.scriptClass->addInternalCall("Internal_Release", &ScriptResource::internal_release);
 	}
 
 	MonoClass* ScriptResource::getClassFromTypeId(UINT32 typeId)
@@ -160,4 +161,9 @@ namespace BansheeEngine
 	{
 		return MonoUtil::stringToMono(nativeInstance->getGenericHandle().getUUID());
 	}
+
+	void ScriptResource::internal_release(ScriptResourceBase* nativeInstance)
+	{
+		nativeInstance->getGenericHandle().release();
+	}
 }

+ 2 - 3
SBansheeEngine/Source/BsScriptResourceManager.cpp

@@ -228,10 +228,9 @@ namespace BansheeEngine
 		auto findIter = mScriptResources.erase(uuid);
 	}
 
-	void ScriptResourceManager::onResourceDestroyed(const HResource& resource)
+	void ScriptResourceManager::onResourceDestroyed(const String& UUID)
 	{
-		const String& uuid = resource.getUUID();
-		auto findIter = mScriptResources.find(uuid);
+		auto findIter = mScriptResources.find(UUID);
 		if (findIter != mScriptResources.end())
 		{
 			findIter->second->notifyResourceDestroyed();

+ 16 - 4
SBansheeEngine/Source/BsScriptResources.cpp

@@ -20,13 +20,15 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_Load", &ScriptResources::internal_Load);
 		metaData.scriptClass->addInternalCall("Internal_LoadRef", &ScriptResources::internal_LoadRef);
 		metaData.scriptClass->addInternalCall("Internal_UnloadUnused", &ScriptResources::internal_UnloadUnused);
+		metaData.scriptClass->addInternalCall("Internal_Release", &ScriptResources::internal_Release);
+		metaData.scriptClass->addInternalCall("Internal_ReleaseRef", &ScriptResources::internal_ReleaseRef);
 	}
 
-	MonoObject* ScriptResources::internal_Load(MonoString* path)
+	MonoObject* ScriptResources::internal_Load(MonoString* path, bool keepLoaded)
 	{
 		Path nativePath = MonoUtil::monoToWString(path);
 
-		HResource resource = GameResourceManager::instance().load(nativePath);
+		HResource resource = GameResourceManager::instance().load(nativePath, keepLoaded);
 		if (resource == nullptr)
 			return nullptr;
 
@@ -36,13 +38,13 @@ namespace BansheeEngine
 		return scriptResource->getManagedInstance();
 	}
 
-	MonoObject* ScriptResources::internal_LoadRef(MonoObject* reference)
+	MonoObject* ScriptResources::internal_LoadRef(MonoObject* reference, bool keepLoaded)
 	{
 		ScriptResourceRef* scriptRef = ScriptResourceRef::toNative(reference);
 		if (scriptRef == nullptr)
 			return nullptr;
 
-		HResource resource = gResources().load(scriptRef->getHandle());
+		HResource resource = gResources().load(scriptRef->getHandle(), true, keepLoaded);
 		if (resource == nullptr)
 			return nullptr;
 
@@ -52,6 +54,16 @@ namespace BansheeEngine
 		return scriptResource->getManagedInstance();
 	}
 
+	void ScriptResources::internal_Release(ScriptResourceBase* resource)
+	{
+		resource->getGenericHandle().release();
+	}
+
+	void ScriptResources::internal_ReleaseRef(ScriptResourceRef* resourceRef)
+	{
+		resourceRef->getHandle().release();
+	}
+
 	void ScriptResources::internal_UnloadUnused()
 	{
 		gResources().unloadAllUnused();

+ 1 - 1
SBansheeEngine/Source/BsScriptScene.cpp

@@ -32,7 +32,7 @@ namespace BansheeEngine
 	{
 		Path nativePath = MonoUtil::monoToWString(path);
 
-		HPrefab prefab = GameResourceManager::instance().load<Prefab>(nativePath);
+		HPrefab prefab = GameResourceManager::instance().load<Prefab>(nativePath, true);
 		if (prefab.isLoaded(false))
 		{
 			HSceneObject root = prefab->instantiate();

+ 19 - 28
TODO.txt

@@ -9,56 +9,47 @@ Other polish:
   - Game Object (also add to context): Create(Empty, Empty Child, Camera, Renderable, Point/Spot/Directional Light), Apply prefab, Break prefab, Revert prefab
  - Inspector persistance (See below for details)
 
-Stage 2 polish:
- - Game window
- - Game play/pause/step (+ save/restore objects on play/pause switch)
- - Resource hotswap
- - When managed exception happens log an error and continue execution
-  - Doing a pass over all methods referencing Internal_ methods ensuring they do proper checking on C# side would be good
- - Game publishing (Build window, collect resources, output exe, default viewport) (described below)
-
 Optional:
- - Undocking of editor window doesn't work (they just get lost)
- - Crash when adding a new directional light and then shutting down (also crash when just deleting a directional light)
- - Start editor in fullscreen
- - If user clears the default shader he has no way of re-assigning it - add default shader to project folder? (needs to be packaged with project)
- - Toggle to enable/disable SceneObject in Inspector
- - Resource import options don't get saved
+ - Ortographic camera views (+ gizmo in scene view corner that shows camera orientation) (Use custom handles and implement this?)
+ - Add "focus on object" key (F) - animate it: rotate camera towards then speed towards while zooming in (+ menu entry)
+ - Add tooltips to toolbar items and other buttons with icons
  - Undo/Redo
   - CmdRecordSO records an SO and all its children but it should only record a single SO
   - CmdRecordSO should instead of recording the entire object record a diff
   - There should be a CmdRecordSO equivalent for resources (probably)
   - Add commands for breaking or reverting a scene object 
   - Test & finalize undo/redo system
- - Add "focus on object" key (F) - animate it: rotate camera towards then speed towards while zooming in (+ menu entry)
- - Ortographic camera views (+ gizmo in scene view corner that shows camera orientation) (Use custom handles and implement this?)
- - Cursors should be replaced with better ones, or at least hot-spots fixed
+ - Crash when adding a new directional light and then shutting down (also crash when just deleting a directional light)
+ - Test VS 2015 Community as code editor (possibly also move the code to VS 2015)
  - Drag and drop of a mesh into Hierarchy doesn't instantiate it
  - Drag and dropping a prefab onto the scene (or hierarchy) should work the same as with meshes
- - Add tooltips to toolbar items and other buttons with icons
+ - Undocking of editor window doesn't work (they just get lost)
+ - Start editor in fullscreen
+ - If user clears the default shader he has no way of re-assigning it - add default shader to project folder? (needs to be packaged with project)
+ - Toggle to enable/disable SceneObject in Inspector
+ - Resource import options don't get saved
+ - Cursors should be replaced with better ones, or at least hot-spots fixed
  - Either disable light tool icons before release or make them functional (With gizmos)
- - Test VS 2015 Community as code editor (possibly also move the code to VS 2015)
- - Clearing the console should also clear the status bar
 
  More optional:
  - Add a way to use GUI elements in game window (Default GUI available to all, plus GUIWidget component for custom ones. Make sure skin can be changed for both.)
- - When starting drag from hierarchy tree view it tends to select another object (can't repro)
- - Handle seems to lag behind the selected mesh
+ - If a field gets optimized out from a material's GPU program it won't get persisted by the inspector (or serialized)
+  - (i.e. the field exists in shader desc but not as a GPU variable)
+  - I should probably store a copy of all shader fields in Material itself (and serialize that)
+   - When I modify this make sure to copy parameter copying method that is called when shader changes (that persist parameters)
  - When starting play-in-editor, automatically make the Game Window active
+ - Need to list all script components in the Components menu
  - When resizing library window while docked, selection area appears
+ - Slow camera rotation at high fps (just limit FPS probably)
+ - When starting drag from hierarchy tree view it tends to select another object (can't repro)
+ - Handle seems to lag behind the selected mesh
  - Move all the code files into subfolders so their hierarchy is similar to VS filters
  - MenuBar - will likely need a way to mark elements as disabled when not appropriate (e.g. no "frame selected unless scene is focused")
    - Likely use a user-provided callback to trigger when populating the menus (I already added a callback to MenuItem, just need to implement it)
- - Need to list all script components in the Components menu
- - Slow camera rotation at high fps (just limit FPS probably)
  - Word wrap weirdness
    - When a GUI element containing a long piece of text is created, and it has flexible size, the initial optimal size
      calculation will try to fit all the text in one row, even if during the actual size calculation that turns out to be impossible.
      What happens then is that the text is rendered into multiple rows but its visible area only shows the first row.
- - If a field gets optimized out from a material's GPU program it won't get persisted by the inspector (or serialized)
-  - (i.e. the field exists in shader desc but not as a GPU variable)
-  - I should probably store a copy of all shader fields in Material itself (and serialize that)
-   - When I modify this make sure to copy parameter copying method that is called when shader changes (that persist parameters)
 
 Seriously optional:
  - Drag to select in scene view