Преглед изворни кода

Nested prefab instances now have a link ID assigned by their parent
Clear obsolete prefab diffs when saving scene or breaking prefab
updateFromPrefab will now set parents only after everything is instantiated so that prefabs lower in the hierarchy wont get destroyed by their parents

BearishSun пре 10 година
родитељ
комит
24df064285

+ 3 - 3
BansheeCore/Include/BsGameObjectRTTI.h

@@ -40,18 +40,18 @@ namespace BansheeEngine
 			addPlainField("mLinkId", 2, &GameObjectRTTI::getLinkId, &GameObjectRTTI::setLinkId);
 		}
 
-		virtual const String& getRTTIName()
+		virtual const String& getRTTIName() override
 		{
 			static String name = "GameObject";
 			return name;
 		}
 
-		virtual UINT32 getRTTIId()
+		virtual UINT32 getRTTIId() override
 		{
 			return TID_GameObject;
 		}
 
-		virtual std::shared_ptr<IReflectable> newRTTIObject()
+		virtual std::shared_ptr<IReflectable> newRTTIObject() override
 		{
 			BS_EXCEPT(InternalErrorException, "Cannot instantiate an abstract class.");
 		}

+ 5 - 2
BansheeCore/Include/BsPrefabUtility.h

@@ -97,12 +97,15 @@ namespace BansheeEngine
 		 * @brief	Restores instance data in the provided hierarchy, using link ids to determine
 		 *			what data maps to which objects. 
 		 *
-		 * @param[in]	so					Object to traverse and restore the instance data.
+		 * @param[in]	so		Object to traverse and restore the instance data.
+		 * @param[in]	proxy	Hierarchy containing instance data for all objects and components, returned by
+		 *						"recordInstanceData" method.				
 		 * @param[in]	linkedInstanceData	A map of link IDs to instance data, returned by "recordInstanceData" method.
 		 *
 		 * @note	Does not recurse into child prefab instances.
 		 */
-		static void restoreLinkedInstanceData(const HSceneObject& so, UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData);
+		static void restoreLinkedInstanceData(const HSceneObject& so, SceneObjectProxy& proxy, 
+			UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData);
 
 		/**
 		 * @brief	Restores instance data in the provided hierarchy, but only for objects without a link id.

+ 76 - 38
BansheeCore/Source/BsPrefabDiff.cpp

@@ -29,9 +29,12 @@ namespace BansheeEngine
 
 	SPtr<PrefabDiff> PrefabDiff::create(const HSceneObject& prefab, const HSceneObject& instance)
 	{
-		if (prefab->mPrefabLinkUUID != instance->mPrefabLinkUUID || prefab->getLinkId() != instance->getLinkId())
+		if (prefab->mPrefabLinkUUID != instance->mPrefabLinkUUID)
 			return nullptr;
 
+		// Note: If this method is called multiple times in a row then renaming all objects every time is redundant, it
+		// would be more efficient to do it once outside of this method. I'm keeping it this way for simplicity for now.
+
 		Vector<RenamedGameObject> renamedObjects;
 		renameInstanceIds(prefab, instance, renamedObjects);
 
@@ -55,9 +58,6 @@ namespace BansheeEngine
 
 	void PrefabDiff::applyDiff(const SPtr<PrefabObjectDiff>& diff, const HSceneObject& object)
 	{
-		if (diff->id != object->getLinkId())
-			return;
-
 		object->setName(diff->name);
 
 		// Note: It is important to remove objects and components first, before adding them.
@@ -159,7 +159,9 @@ namespace BansheeEngine
 
 				if (prefabChild->getLinkId() == instanceChild->getLinkId())
 				{
-					childDiff = generateDiff(prefabChild, instanceChild);
+					if (instanceChild->mPrefabLinkUUID.empty())
+						childDiff = generateDiff(prefabChild, instanceChild);
+
 					foundMatching = true;
 					break;
 				}
@@ -319,73 +321,109 @@ namespace BansheeEngine
 
 	void PrefabDiff::renameInstanceIds(const HSceneObject& prefab, const HSceneObject& instance, Vector<RenamedGameObject>& output)
 	{
-		UnorderedMap<UINT32, UINT64> linkToInstanceId;
+		UnorderedMap<String, UnorderedMap<UINT32, UINT64>> linkToInstanceId;
+
+		struct StackEntry
+		{
+			HSceneObject so;
+			String uuid;
+		};
+
+		Stack<StackEntry> todo;
+		todo.push({ prefab, "root" });
 
-		Stack<HSceneObject> todo;
-		todo.push(prefab);
 		while (!todo.empty())
 		{
-			HSceneObject current = todo.top();
+			StackEntry current = todo.top();
 			todo.pop();
 
-			linkToInstanceId[current->getLinkId()] = current->getInstanceId();
+			UnorderedMap<UINT32, UINT64>& parentIdMap = linkToInstanceId[current.uuid];
+			parentIdMap[current.so->getLinkId()] = current.so->getInstanceId();
+
+			// SceneObject's link ID belongs to the parent prefab, but components belong to current one
+			UnorderedMap<UINT32, UINT64>* idMap = &parentIdMap;
+			if (!current.so->mPrefabLinkUUID.empty())
+				idMap = &linkToInstanceId[current.so->mPrefabLinkUUID];
 
-			const Vector<HComponent>& components = current->getComponents();
+			const Vector<HComponent>& components = current.so->getComponents();
 			for (auto& component : components)
-				linkToInstanceId[component->getLinkId()] = component->getInstanceId();
+				(*idMap)[component->getLinkId()] = component->getInstanceId();
 
-			UINT32 numChildren = current->getNumChildren();
+			UINT32 numChildren = current.so->getNumChildren();
 			for (UINT32 i = 0; i < numChildren; i++)
 			{
-				HSceneObject child = current->getChild(i);
+				HSceneObject child = current.so->getChild(i);
 
-				if (child->mPrefabLinkUUID.empty())
-					todo.push(child);
+				if (current.so->mPrefabLinkUUID.empty())
+					todo.push({ child, current.uuid });
+				else
+					todo.push({ child, current.so->mPrefabLinkUUID });
 			}
 		}
 
-		todo.push(instance);
+		todo.push({ instance, "root" });
 		while (!todo.empty())
 		{
-			HSceneObject current = todo.top();
+			StackEntry current = todo.top();
 			todo.pop();
 
-			if (current->getLinkId() != -1)
+			auto iterFind = linkToInstanceId.find(current.uuid);
+			UnorderedMap<UINT32, UINT64>* idMap = nullptr;
+			if (iterFind != linkToInstanceId.end())
 			{
-				auto iterFind = linkToInstanceId.find(current->getLinkId());
-				if (iterFind != linkToInstanceId.end())
+				UnorderedMap<UINT32, UINT64>& parentIdMap = iterFind->second;
+
+				if (current.so->getLinkId() != -1)
 				{
-					output.push_back(RenamedGameObject());
-					RenamedGameObject& renamedGO = output.back();
-					renamedGO.instanceData = current->mInstanceData;
-					renamedGO.originalId = current->getInstanceId();
+					auto iterFind2 = parentIdMap.find(current.so->getLinkId());
+					if (iterFind2 != parentIdMap.end())
+					{
+						output.push_back(RenamedGameObject());
+						RenamedGameObject& renamedGO = output.back();
+						renamedGO.instanceData = current.so->mInstanceData;
+						renamedGO.originalId = current.so->getInstanceId();
 
-					current->mInstanceData->mInstanceId = iterFind->second;
+						current.so->mInstanceData->mInstanceId = iterFind2->second;
+					}
 				}
+
+				if (current.so->mPrefabLinkUUID.empty())
+					idMap = &parentIdMap;
 			}
 
-			const Vector<HComponent>& components = current->getComponents();
-			for (auto& component : components)
+			if (idMap == nullptr && !current.so->mPrefabLinkUUID.empty())
 			{
-				auto iterFind = linkToInstanceId.find(component->getLinkId());
-				if (iterFind != linkToInstanceId.end())
+				auto iterFind3 = linkToInstanceId.find(current.so->mPrefabLinkUUID);
+				idMap = &iterFind3->second;
+			}
+
+			if (idMap != nullptr)
+			{
+				const Vector<HComponent>& components = current.so->getComponents();
+				for (auto& component : components)
 				{
-					output.push_back(RenamedGameObject());
-					RenamedGameObject& renamedGO = output.back();
-					renamedGO.instanceData = component->mInstanceData;
-					renamedGO.originalId = component->getInstanceId();
+					auto iterFind2 = idMap->find(component->getLinkId());
+					if (iterFind2 != idMap->end())
+					{
+						output.push_back(RenamedGameObject());
+						RenamedGameObject& renamedGO = output.back();
+						renamedGO.instanceData = component->mInstanceData;
+						renamedGO.originalId = component->getInstanceId();
 
-					component->mInstanceData->mInstanceId = iterFind->second;
+						component->mInstanceData->mInstanceId = iterFind2->second;
+					}
 				}
 			}
 
-			UINT32 numChildren = current->getNumChildren();
+			UINT32 numChildren = current.so->getNumChildren();
 			for (UINT32 i = 0; i < numChildren; i++)
 			{
-				HSceneObject child = current->getChild(i);
+				HSceneObject child = current.so->getChild(i);
 
 				if (child->mPrefabLinkUUID.empty())
-					todo.push(child);
+					todo.push({ child, current.uuid });
+				else
+					todo.push({ child, child->mPrefabLinkUUID });
 			}
 		}
 	}

+ 58 - 49
BansheeCore/Source/BsPrefabUtility.cpp

@@ -28,7 +28,7 @@ namespace BansheeEngine
 		HSceneObject newInstance = prefabLink->instantiate();
 		newInstance->mParent = parent;
 
-		restoreLinkedInstanceData(newInstance, linkedInstanceData);
+		restoreLinkedInstanceData(newInstance, soProxy, linkedInstanceData);
 	}
 
 	void PrefabUtility::updateFromPrefab(const HSceneObject& so)
@@ -67,6 +67,8 @@ namespace BansheeEngine
 			}
 		}
 
+		Vector<std::pair<HSceneObject, HSceneObject>> newPrefabInstances;
+
 		// Need to do this bottom up to ensure I don't destroy the parents before children
 		for (auto iter = prefabInstanceRoots.rbegin(); iter != prefabInstanceRoots.rend(); ++iter)
 		{
@@ -83,20 +85,25 @@ namespace BansheeEngine
 				PrefabDiffPtr prefabDiff = current->mPrefabDiff;
 				HSceneObject parent = current->getParent();
 
-				// This will destroy the object but keep it in the parent's child list
-				current->destroyInternal(current, true);
-
+				current->destroy(true);
 				HSceneObject newInstance = prefabLink->instantiate();
-				newInstance->mParent = parent;
 
 				if (prefabDiff != nullptr)
 					prefabDiff->apply(newInstance);
 
-				restoreLinkedInstanceData(newInstance, linkedInstanceData);
+				restoreLinkedInstanceData(newInstance, soProxy, linkedInstanceData);
 				restoreUnlinkedInstanceData(newInstance, soProxy);
+
+				newPrefabInstances.push_back({ newInstance, parent });
 			}
 		}
 
+		// Once everything is instantiated, restore old parents
+		for (auto& newInstanceData : newPrefabInstances)
+		{
+			newInstanceData.first->mParent = newInstanceData.second;
+		}
+
 		gResources().unloadAllUnused();
 	}
 
@@ -113,17 +120,12 @@ namespace BansheeEngine
 			HSceneObject currentSO = todo.top();
 			todo.pop();
 
-			if (currentSO->mLinkId == -1)
-				objectsToId.push_back(currentSO);
-			else
-				existingIds.insert(currentSO->mLinkId);
-
 			for (auto& component : currentSO->mComponents)
 			{
-				if (component->mLinkId == -1)
+				if (component->getLinkId() == -1)
 					objectsToId.push_back(component);
 				else
-					existingIds.insert(component->mLinkId);
+					existingIds.insert(component->getLinkId());
 			}
 
 			UINT32 numChildren = (UINT32)currentSO->getNumChildren();
@@ -131,8 +133,16 @@ namespace BansheeEngine
 			{
 				HSceneObject child = currentSO->getChild(i);
 
-				if (!child->hasFlag(SOF_DontSave) && child->mPrefabLinkUUID.empty())
-					todo.push(currentSO->getChild(i));
+				if (!child->hasFlag(SOF_DontSave))
+				{
+					if (child->getLinkId() == -1)
+						objectsToId.push_back(child);
+					else
+						existingIds.insert(child->getLinkId());
+
+					if(child->mPrefabLinkUUID.empty())
+						todo.push(currentSO->getChild(i));
+				}
 			}
 		}
 
@@ -169,7 +179,6 @@ namespace BansheeEngine
 			HSceneObject currentSO = todo.top();
 			todo.pop();
 
-			currentSO->mLinkId = -1;
 			for (auto& component : currentSO->mComponents)
 				component->mLinkId = -1;
 
@@ -179,6 +188,7 @@ namespace BansheeEngine
 				for (UINT32 i = 0; i < numChildren; i++)
 				{
 					HSceneObject child = currentSO->getChild(i);
+					child->mLinkId = -1;
 
 					if (child->mPrefabLinkUUID.empty())
 						todo.push(child);
@@ -242,16 +252,14 @@ namespace BansheeEngine
 		Stack<StackData> todo;
 		todo.push({so, &output});
 
+		output.instanceData = so->_getInstanceData();
+		output.linkId = -1;
+
 		while (!todo.empty())
 		{
 			StackData curData = todo.top();
 			todo.pop();
 
-			curData.proxy->instanceData = curData.so->_getInstanceData();
-			curData.proxy->linkId = curData.so->getLinkId();
-
-			linkedInstanceData[curData.proxy->linkId] = curData.proxy->instanceData;
-
 			const Vector<HComponent>& components = curData.so->getComponents();
 			for (auto& component : components)
 			{
@@ -265,48 +273,41 @@ namespace BansheeEngine
 			}
 
 			UINT32 numChildren = curData.so->getNumChildren();
-			UINT32 numProxyChildren = 0;
+			curData.proxy->children.resize(numChildren);
+
 			for (UINT32 i = 0; i < numChildren; i++)
 			{
 				HSceneObject child = curData.so->getChild(i);
 
-				if (child->mPrefabLinkUUID.empty())
-					numProxyChildren++;
-			}
+				SceneObjectProxy& childProxy = curData.proxy->children[i];
 
-			curData.proxy->children.resize(numProxyChildren);
+				childProxy.instanceData = child->_getInstanceData();
+				childProxy.linkId = child->getLinkId();
 
-			UINT32 proxyIdx = 0;
-			for (UINT32 i = 0; i < numChildren; i++)
-			{
-				HSceneObject child = curData.so->getChild(i);
+				linkedInstanceData[childProxy.linkId] = childProxy.instanceData;
 
 				if (child->mPrefabLinkUUID.empty())
 				{
-					todo.push({ child, &curData.proxy->children[proxyIdx] });
-					proxyIdx++;
+					todo.push({ child, &curData.proxy->children[i] });
 				}
 			}
 		}
 	}
 
-	void PrefabUtility::restoreLinkedInstanceData(const HSceneObject& so, UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData)
+	void PrefabUtility::restoreLinkedInstanceData(const HSceneObject& so, SceneObjectProxy& proxy, 
+		UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData)
 	{
 		Stack<HSceneObject> todo;
 		todo.push(so);
 
+		// Root is not in the instance data map because its link ID belongs to the parent prefab, if any
+		so->_setInstanceData(proxy.instanceData);
+
 		while (!todo.empty())
 		{
 			HSceneObject current = todo.top();
 			todo.pop();
 
-			if (current->getLinkId() != -1)
-			{
-				auto iterFind = linkedInstanceData.find(current->getLinkId());
-				if (iterFind != linkedInstanceData.end())
-					current->_setInstanceData(iterFind->second);
-			}
-
 			const Vector<HComponent>& components = current->getComponents();
 			for (auto& component : components)
 			{
@@ -323,6 +324,13 @@ namespace BansheeEngine
 			{
 				HSceneObject child = current->getChild(i);
 
+				if (child->getLinkId() != -1)
+				{
+					auto iterFind = linkedInstanceData.find(child->getLinkId());
+					if (iterFind != linkedInstanceData.end())
+						child->_setInstanceData(iterFind->second);
+				}
+
 				if (child->mPrefabLinkUUID.empty())
 					todo.push(child);
 			}
@@ -340,8 +348,6 @@ namespace BansheeEngine
 		Stack<StackEntry> todo;
 		todo.push(StackEntry());
 
-		assert(so->getLinkId() == proxy.linkId);
-		
 		StackEntry& topEntry = todo.top();
 		topEntry.so = so;
 		topEntry.proxy = &proxy;
@@ -383,9 +389,6 @@ namespace BansheeEngine
 			{
 				HSceneObject child = current.so->getChild(i);
 
-				if (!child->mPrefabLinkUUID.empty())
-					continue;
-
 				if (child->getLinkId() == -1)
 				{
 					bool foundInstanceData = false;
@@ -397,11 +400,14 @@ namespace BansheeEngine
 						assert(current.proxy->children[childProxyIdx].linkId == -1);
 						child->_setInstanceData(current.proxy->children[childProxyIdx].instanceData);
 
-						todo.push(StackEntry());
+						if (child->mPrefabLinkUUID.empty())
+						{
+							todo.push(StackEntry());
 
-						StackEntry& newEntry = todo.top();
-						newEntry.so = child;
-						newEntry.proxy = &current.proxy->children[childProxyIdx];
+							StackEntry& newEntry = todo.top();
+							newEntry.so = child;
+							newEntry.proxy = &current.proxy->children[childProxyIdx];
+						}
 
 						foundInstanceData = true;
 						break;
@@ -411,6 +417,9 @@ namespace BansheeEngine
 				}
 				else
 				{
+					if (!child->mPrefabLinkUUID.empty())
+						continue;
+
 					for (UINT32 j = 0; j < numChildProxies; j++)
 					{
 						if (child->getLinkId() == current.proxy->children[j].linkId)

+ 29 - 12
BansheeCore/Source/BsSceneObject.cpp

@@ -127,23 +127,40 @@ namespace BansheeEngine
 
 	void SceneObject::breakPrefabLink()
 	{
-		SceneObject* curObj = this;
+		SceneObject* rootObj = this;
 
-		while (curObj == nullptr)
+		while (rootObj == nullptr)
 		{
-			if (!curObj->mPrefabLinkUUID.empty())
+			if (!rootObj->mPrefabLinkUUID.empty())
+				return;
+
+			if (rootObj->mParent != nullptr)
+				rootObj = rootObj->mParent.get();
+			else
+				rootObj = nullptr;
+		}
+
+		if (rootObj != nullptr)
+		{
+			rootObj->mPrefabLinkUUID = "";
+			PrefabUtility::clearPrefabIds(rootObj->getHandle());
+
+			Stack<SceneObject*> todo;
+			todo.push(rootObj);
+
+			while (!todo.empty())
 			{
-				curObj->mPrefabLinkUUID = "";
+				SceneObject* curObj = todo.top();
+				todo.pop();
+
 				curObj->mPrefabDiff = nullptr;
-				PrefabUtility::clearPrefabIds(curObj->getHandle());
 
-				return;
+				for (auto& child : curObj->mChildren)
+				{
+					if (child->mPrefabLinkUUID.empty())
+						todo.push(child.get());
+				}
 			}
-
-			if (curObj->mParent != nullptr)
-				curObj = curObj->mParent.get();
-			else
-				curObj = nullptr;
 		}
 	}
 
@@ -499,7 +516,7 @@ namespace BansheeEngine
 #if BS_EDITOR_BUILD
 			String newPrefab = getPrefabLink();
 			if (originalPrefab != newPrefab)
-				PrefabUtility::clearPrefabIds(mThisHandle, false);
+				PrefabUtility::clearPrefabIds(mThisHandle);
 #endif
 
 			setWorldPosition(worldPos);