Przeglądaj źródła

Editor: Add particles live update

Panagiotis Christopoulos Charitos 1 miesiąc temu
rodzic
commit
fef4763cf9

+ 36 - 15
AnKi/Resource/ResourceManager.cpp

@@ -115,16 +115,17 @@ Error ResourceManager::loadResource(CString filename, ResourcePtr<T>& out, Bool
 
 	ANKI_ASSERT(entry);
 
-	// Try loading the resource
+	// Try to load the resource
 	Error err = Error::kNone;
+	T* rsrc = nullptr;
 	{
 		LockGuard lock(entry->m_mtx);
 
-		if(entry->m_resource == nullptr)
+		if(entry->m_resources.getSize() == 0 || entry->m_resources.getBack()->isObsolete())
 		{
-			// Resource hasn't been loaded, load it
+			// Resource hasn't been loaded or it needs update, load it
 
-			T* rsrc = newInstance<T>(ResourceMemoryPool::getSingleton(), filename, m_uuid.fetchAdd(1));
+			rsrc = newInstance<T>(ResourceMemoryPool::getSingleton(), filename, m_uuid.fetchAdd(1));
 
 			// Increment the refcount in that case where async jobs increment it and decrement it in the scope of a load()
 			rsrc->retain();
@@ -138,10 +139,11 @@ Error ResourceManager::loadResource(CString filename, ResourcePtr<T>& out, Bool
 			{
 				ANKI_RESOURCE_LOGE("Failed to load resource: %s", filename.cstr());
 				deleteInstance(ResourceMemoryPool::getSingleton(), rsrc);
+				rsrc = nullptr;
 			}
 			else
 			{
-				entry->m_resource = rsrc;
+				entry->m_resources.emplaceBack(rsrc);
 			}
 
 			if(m_trackFileUpdateTimes)
@@ -149,11 +151,16 @@ Error ResourceManager::loadResource(CString filename, ResourcePtr<T>& out, Bool
 				entry->m_fileUpdateTime = ResourceFilesystem::getSingleton().getFileUpdateTime(filename);
 			}
 		}
-	}
+		else
+		{
+			rsrc = entry->m_resources.getBack();
+		}
 
-	if(!err)
-	{
-		out.reset(entry->m_resource);
+		if(!err)
+		{
+			ANKI_ASSERT(rsrc);
+			out.reset(rsrc);
+		}
 	}
 
 	return err;
@@ -177,9 +184,18 @@ void ResourceManager::freeResource(T* ptr)
 
 	{
 		LockGuard lock(entry->m_mtx);
-		ANKI_ASSERT(entry->m_resource);
-		deleteInstance(ResourceMemoryPool::getSingleton(), entry->m_resource);
-		entry->m_resource = nullptr;
+
+		auto it = entry->m_resources.getBegin();
+		for(; it != entry->m_resources.getEnd(); ++it)
+		{
+			if(*it == ptr)
+			{
+				break;
+			}
+		}
+		ANKI_ASSERT(it != entry->m_resources.getEnd());
+		deleteInstance(ResourceMemoryPool::getSingleton(), *it);
+		entry->m_resources.erase(it);
 	}
 }
 
@@ -200,16 +216,21 @@ void ResourceManager::refreshFileUpdateTimesInternal()
 	{
 		LockGuard lock(entry.m_mtx);
 
-		if(!entry.m_resource)
+		if(entry.m_resources.getSize() == 0)
 		{
 			continue;
 		}
 
-		const U64 newTime = ResourceFilesystem::getSingleton().getFileUpdateTime(entry.m_resource->getFilename());
+		const U64 newTime = ResourceFilesystem::getSingleton().getFileUpdateTime(entry.m_resources[0]->getFilename());
 		if(newTime != entry.m_fileUpdateTime)
 		{
-			ANKI_RESOURCE_LOGV("File updated: %s", entry.m_resource->getFilename().cstr());
+			ANKI_RESOURCE_LOGV("File updated, loaded resource now obsolete: %s", entry.m_resources[0]->getFilename().cstr());
 			entry.m_fileUpdateTime = newTime;
+
+			for(T* rsrc : entry.m_resources)
+			{
+				rsrc->m_isObsolete.store(1);
+			}
 		}
 	}
 }

+ 3 - 3
AnKi/Resource/ResourceManager.h

@@ -64,9 +64,9 @@ private:
 		class Entry
 		{
 		public:
-			Type* m_resource = nullptr;
-			U64 m_fileUpdateTime = 0;
+			DynamicArray<Type*> m_resources; // Hosts multiple versions of a resource. The last element is the newest
 			SpinLock m_mtx;
+			U64 m_fileUpdateTime = 0;
 		};
 
 		ResourceHashMap<CString, U32> m_map;
@@ -77,7 +77,7 @@ private:
 		{
 			for([[maybe_unused]] const Entry& e : m_entries)
 			{
-				ANKI_ASSERT(e.m_resource == nullptr && "Forgot to release some resource");
+				ANKI_ASSERT(e.m_resources.getSize() > 0 && "Forgot to release some resource");
 			}
 		}
 	};

+ 7 - 0
AnKi/Resource/ResourceObject.h

@@ -58,6 +58,12 @@ public:
 		return m_refcount.load();
 	}
 
+	// If true the resource has changed in the filesystem and this one is an obsolete version
+	Bool isObsolete() const
+	{
+		return m_isObsolete.load() != 0;
+	}
+
 protected:
 	Error openFile(const ResourceFilename& filename, ResourceFilePtr& file);
 
@@ -67,6 +73,7 @@ protected:
 
 private:
 	mutable Atomic<I32> m_refcount = {0};
+	mutable Atomic<U32> m_isObsolete = {0}; // If the file of the resource changed in the filesystem then this flag is 1
 	U32 m_uuid = 0;
 	ResourceString m_fname; // Unique resource name
 };

+ 1 - 0
AnKi/Scene/Common.h

@@ -27,6 +27,7 @@ class PhysicsWorld;
 #define ANKI_SCENE_LOGE(...) ANKI_LOG("SCEN", kError, __VA_ARGS__)
 #define ANKI_SCENE_LOGW(...) ANKI_LOG("SCEN", kWarning, __VA_ARGS__)
 #define ANKI_SCENE_LOGF(...) ANKI_LOG("SCEN", kFatal, __VA_ARGS__)
+#define ANKI_SCENE_LOGV(...) ANKI_LOG("SCEN", kVerbose, __VA_ARGS__)
 
 class SceneMemoryPool : public HeapMemoryPool, public MakeSingleton<SceneMemoryPool>
 {

+ 22 - 0
AnKi/Scene/Components/ParticleEmitter2Component.cpp

@@ -234,6 +234,28 @@ void ParticleEmitter2Component::update(SceneComponentUpdateInfo& info, Bool& upd
 
 	ParticleEmitterQuadGeometry::getSingleton().tryUploadToGpuScene();
 
+	if(info.m_checkForResourceUpdates) [[unlikely]]
+	{
+		if(m_particleEmitterResource->isObsolete()) [[unlikely]]
+		{
+			ANKI_SCENE_LOGV("Particle emitter resource is obsolete. Will reload it");
+			BaseString<MemoryPoolPtrWrapper<StackMemoryPool>> fname(info.m_framePool);
+			fname = m_particleEmitterResource->getFilename();
+			ParticleEmitterResource2Ptr oldVersion = m_particleEmitterResource;
+			m_particleEmitterResource.reset(nullptr);
+			if(ResourceManager::getSingleton().loadResource(fname, m_particleEmitterResource))
+			{
+				ANKI_SCENE_LOGE("Can't update the particle resource. Ignoring the load");
+				m_particleEmitterResource = oldVersion;
+			}
+			else
+			{
+				m_anyDirty = true;
+				ANKI_ASSERT(!m_particleEmitterResource->isObsolete());
+			}
+		}
+	}
+
 	if(!m_anyDirty) [[likely]]
 	{
 		return;

+ 3 - 1
AnKi/Scene/Components/SceneComponent.h

@@ -71,13 +71,15 @@ public:
 	const Second m_currentTime;
 	const Second m_dt;
 	const Bool m_forceUpdateSceneBounds;
+	const Bool m_checkForResourceUpdates;
 	StackMemoryPool* m_framePool = nullptr;
 
-	SceneComponentUpdateInfo(Second prevTime, Second crntTime, Bool forceUpdateSceneBounds)
+	SceneComponentUpdateInfo(Second prevTime, Second crntTime, Bool forceUpdateSceneBounds, Bool checkForResourceUpdates)
 		: m_previousTime(prevTime)
 		, m_currentTime(crntTime)
 		, m_dt(crntTime - prevTime)
 		, m_forceUpdateSceneBounds(forceUpdateSceneBounds)
+		, m_checkForResourceUpdates(checkForResourceUpdates)
 	{
 	}
 

+ 1 - 1
AnKi/Scene/SceneGraph.cpp

@@ -390,7 +390,7 @@ void SceneGraph::updateNode(U32 tid, SceneNode& node, UpdateSceneNodesCtx& ctx)
 	UpdateSceneNodesCtx::PerThread& thread = ctx.m_perThread[tid];
 
 	// Components update
-	SceneComponentUpdateInfo componentUpdateInfo(ctx.m_prevUpdateTime, ctx.m_crntTime, ctx.m_forceUpdateSceneBounds);
+	SceneComponentUpdateInfo componentUpdateInfo(ctx.m_prevUpdateTime, ctx.m_crntTime, ctx.m_forceUpdateSceneBounds, m_checkForResourceUpdates);
 	componentUpdateInfo.m_framePool = &m_framePool;
 	U32 sceneComponentUpdatedCount = 0;
 	node.iterateComponents([&](SceneComponent& comp) {

+ 9 - 0
AnKi/Scene/SceneGraph.h

@@ -163,6 +163,13 @@ public:
 		return {m_sceneMin, m_sceneMax};
 	}
 
+	// If enable is true the components will be checking for updates of resources. Useful for the editor resource updates. It has a perf hit so it
+	// should be enabled only by the editor
+	void setCheckForResourceUpdates(Bool enable)
+	{
+		m_checkForResourceUpdates = enable;
+	}
+
 private:
 	class UpdateSceneNodesCtx;
 
@@ -197,6 +204,8 @@ private:
 	SceneDynamicArray<std::pair<SceneNode*, SceneString>> m_nodesRenamed;
 	SpinLock m_nodesForRegistrationMtx;
 
+	Bool m_checkForResourceUpdates = false;
+
 	Atomic<U32> m_nodesUuid = {1};
 
 	SceneComponentArrays m_componentArrays;

+ 1 - 0
Tools/Editor/EditorMain.cpp

@@ -78,6 +78,7 @@ public:
 
 	Error userPostInit() override
 	{
+		SceneGraph::getSingleton().setCheckForResourceUpdates(true);
 		m_editorUiNode = SceneGraph::getSingleton().newSceneNode<EditorUiNode>("MainUi");
 
 		if(m_sceneLuaFname)