Browse Source

Added prefab ID renaming in prefab diff so that game object handles are compared properly (untested)
Added utilities for reverting and updating to prefab
Added code for restoring object IDs after prefab revert/update (untested)

Marko Pintera 10 years ago
parent
commit
612e9680b7

+ 2 - 0
BansheeCore/BansheeCore.vcxproj

@@ -287,6 +287,7 @@
     <ClInclude Include="Include\BsPrefabDiff.h" />
     <ClInclude Include="Include\BsPrefabDiffRTTI.h" />
     <ClInclude Include="Include\BsPrefabRTTI.h" />
+    <ClInclude Include="Include\BsPrefabUtility.h" />
     <ClInclude Include="Include\BsShaderIncludeRTTI.h" />
     <ClInclude Include="Include\BsIResourceListener.h" />
     <ClInclude Include="Include\BsMaterialParam.h" />
@@ -442,6 +443,7 @@
     <ClCompile Include="Source\BsMeshUtility.cpp" />
     <ClCompile Include="Source\BsPrefab.cpp" />
     <ClCompile Include="Source\BsPrefabDiff.cpp" />
+    <ClCompile Include="Source\BsPrefabUtility.cpp" />
     <ClCompile Include="Source\BsProfilerCPU.cpp" />
     <ClCompile Include="Source\BsDeferredCallManager.cpp" />
     <ClCompile Include="Source\BsDrawOps.cpp" />

+ 6 - 0
BansheeCore/BansheeCore.vcxproj.filters

@@ -545,6 +545,9 @@
     <ClInclude Include="Include\BsMeshUtility.h">
       <Filter>Header Files\Utility</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsPrefabUtility.h">
+      <Filter>Header Files\Scene</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsCoreApplication.cpp">
@@ -862,5 +865,8 @@
     <ClCompile Include="Source\BsMeshUtility.cpp">
       <Filter>Source Files\Utility</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsPrefabUtility.cpp">
+      <Filter>Source Files\Scene</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 2 - 0
BansheeCore/Include/BsCorePrerequisites.h

@@ -167,6 +167,7 @@ namespace BansheeEngine
 	class TextureProperties;
 	class IShaderIncludeHandler;
 	class Prefab;
+	class PrefabDiff;
 	// Asset import
 	class SpecificImporter;
 	class Importer;
@@ -264,6 +265,7 @@ namespace BansheeEngine
 	typedef std::shared_ptr<ResourceMetaData> ResourceMetaDataPtr;
 	typedef std::shared_ptr<IShaderIncludeHandler> ShaderIncludeHandlerPtr;
 	typedef std::shared_ptr<Prefab> PrefabPtr;
+	typedef std::shared_ptr<PrefabDiff> PrefabDiffPtr;
 }
 
 /************************************************************************/

+ 2 - 0
BansheeCore/Include/BsGameObject.h

@@ -87,6 +87,8 @@ namespace BansheeEngine
 	protected:
 		friend class GameObjectHandleBase;
 		friend class GameObjectManager;
+		friend class PrefabDiff;
+		friend class PrefabUtility;
 
 		/**
 		 * @brief	Initializes the GameObject after construction.

+ 8 - 1
BansheeCore/Include/BsGameObjectHandle.h

@@ -4,6 +4,9 @@ namespace BansheeEngine
 {
 	class GameObjectManager;
 
+	template <typename T>
+	class GameObjectHandle;
+
 	/**
 	 * @brief	Internal data shared between GameObject handles.
 	 */
@@ -121,6 +124,9 @@ namespace BansheeEngine
 		friend class SceneObjectRTTI;
 		friend class GameObjectManager;
 
+		template<class _Ty1, class _Ty2>
+		friend bool operator==(const GameObjectHandle<_Ty1>& _Left, const GameObjectHandle<_Ty2>& _Right);
+
 		GameObjectHandleBase(const std::shared_ptr<GameObject> ptr);
 		GameObjectHandleBase(const std::shared_ptr<GameObjectHandleData>& data);
 		GameObjectHandleBase(std::nullptr_t ptr);
@@ -294,7 +300,8 @@ namespace BansheeEngine
 	template<class _Ty1, class _Ty2>
 	bool operator==(const GameObjectHandle<_Ty1>& _Left, const GameObjectHandle<_Ty2>& _Right)
 	{	
-		return (_Left == nullptr && _Right == nullptr) || (_Left != nullptr && _Right != nullptr && _Left.get() == _Right.get());
+		return (_Left.mData == nullptr && _Right.mData == nullptr) || 
+			(_Left.mData != nullptr && _Right.mData != nullptr && _Left.mData->mInstanceId == _Right.mData->mInstanceId);
 	}
 
 	/**

+ 0 - 6
BansheeCore/Include/BsPrefab.h

@@ -41,12 +41,6 @@ namespace BansheeEngine
 		 */
 		void initialize(const HSceneObject& sceneObject);
 
-		/**
-		 * @brief	Generates prefab "link" ID that can be used for tracking which game object
-		 *			in a prefab instance corresponds to an object in the prefab.
-		 */
-		void generatePrefabIds(const HSceneObject& sceneObject);
-
 		/**
 		 * @brief	Creates an empty and uninitialized prefab.
 		 */

+ 29 - 0
BansheeCore/Include/BsPrefabDiff.h

@@ -75,6 +75,16 @@ namespace BansheeEngine
 		void apply(const HSceneObject& object);
 
 	private:
+		/**
+		 * @brief	A reference to a renamed game object instance data, and its original ID
+		 *			so it may be restored later.
+		 */
+		struct RenamedGameObject
+		{
+			GameObjectInstanceDataPtr instanceData;
+			UINT64 originalId;
+		};
+
 		/**
 		 * @brief	Recurses over every scene object in the prefab a generates differences between itself
 		 *			and the instanced version.
@@ -90,6 +100,25 @@ namespace BansheeEngine
 		 */
 		static void applyDiff(const SPtr<PrefabObjectDiff>& diff, const HSceneObject& object);
 
+		/**
+		 * @brief	Renames all game objects in the provided instance so that IDs of the objects will match
+		 *			the IDs of their counterparts in the prefab. 
+		 *
+		 * @note	This is a temporary action and should be undone by calling "restoreInstanceIds" and providing 
+		 *			it with the output of this method. 
+		 * @par		By doing this before calling "diff" we ensure that any game object handles pointing to objects 
+		 *			within the prefab instance hierarchy aren't recorded by the diff system, since we want those to 
+		 *			remain as they are after applying the diff.
+		 */
+		static void renameInstanceIds(const HSceneObject& prefab, const HSceneObject& instance, Vector<RenamedGameObject>& output);
+
+		/**
+		 * @brief	Restores any instance IDs that were modified by the "renameInstanceIds" method.
+		 *
+		 * @see		renameInstanceIds;
+		 */
+		static void restoreInstanceIds(const Vector<RenamedGameObject>& renamedObjects);
+
 		SPtr<PrefabObjectDiff> mRoot;
 
 		/************************************************************************/

+ 107 - 0
BansheeCore/Include/BsPrefabUtility.h

@@ -0,0 +1,107 @@
+#pragma once
+
+#include "BsCorePrerequisites.h"
+#include "BsGameObject.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Handles various prefab specific operations.
+	 */
+	class BS_CORE_EXPORT PrefabUtility
+	{
+	private:
+		/**
+		 * @brief	Contains saved Component instance data.
+		 */
+		struct ComponentProxy
+		{
+			GameObjectInstanceDataPtr instanceData;
+			INT32 linkId;
+		};
+
+		/**
+		 * @brief	Contains saved SceneObject instance data, as well as
+		 *			saved instance data for all its children and components.
+		 */
+		struct SceneObjectProxy
+		{
+			GameObjectInstanceDataPtr instanceData;
+			INT32 linkId;
+
+			Vector<ComponentProxy> components;
+			Vector<SceneObjectProxy> children;
+		};
+
+	public:
+		/**
+		 * @brief	Updates all of the objects belonging to the same prefab instance
+		 *			as the provided object (if any). The update will remove any instance
+		 *			specific changes to the hierarchy and restore it to the exact copy of 
+		 *			linked prefab.
+		 *
+		 * @param[in]	so	Object to revert.
+		 */
+		static void revertToPrefab(const HSceneObject& so);
+
+		/**
+		 * @brief	Updates all of the objects belonging to the same prefab instance
+		 *			as the provided object (if any). The update will apply any changes
+		 *			from the linked prefab to the hierarchy (if any).
+		 *
+		 * @param[in]	so	Object to update.
+		 */
+		static void updateFromPrefab(const HSceneObject& so);
+
+		/**
+		 * @brief	Generates prefab "link" ID that can be used for tracking which game object
+		 *			in a prefab instance corresponds to an object in the prefab.
+		 *
+		 * @note	If any children of the provided object belong to another prefab they will 
+		 *			not have IDs generated.
+		 */
+		static void generatePrefabIds(const HSceneObject& sceneObject);
+
+		/**
+		 * @brief	Clears all prefab "link" IDs in the provided object and its children.
+		 *
+		 * @note	If any of its children belong to another prefab they will not be cleared.
+		 */
+		static void clearPrefabIds(const HSceneObject& sceneObject);
+
+	private:
+		/**
+	     * @brief	Traverses the object hierarchy, finds all child objects and components
+		 *			and records their instance data, as well as their original place in the hierarchy.
+		 *			Instance data essentially holds the object's "identity" and by restoring it we
+		 *			ensure any handles pointing to the object earlier will still point to the new version.
+		 *
+		 * @param[in]	so					Object to traverse and record.
+		 * @param[out]	output				Contains the output hierarchy of instance data.
+		 * @param[out]	linkedInstanceData	A map of link IDs to instance data. Objects without
+		 *									link IDs will not be included here.
+		 */
+		static void recordInstanceData(const HSceneObject& so, SceneObjectProxy& output, 
+			UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData);
+
+		/**
+		 * @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]	linkedInstanceData	A map of link IDs to instance data, returned by "recordInstanceData" method.
+		 */
+		static void restoreLinkedInstanceData(const HSceneObject& so, UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData);
+
+		/**
+		 * @brief	Restores instance data in the provided hierarchy, but only for objects without a link id.
+		 *			Since the objects do not have a link ID we rely on their sequential order to find out
+		 *			which instance data belong to which object.
+		 *
+		 * @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.
+		 */
+		static void restoreUnlinkedInstanceData(const HSceneObject& so, SceneObjectProxy& proxy);
+	};
+}

+ 14 - 1
BansheeCore/Include/BsSceneObject.h

@@ -39,6 +39,7 @@ namespace BansheeEngine
 		friend class CoreSceneManager;
 		friend class Prefab;
 		friend class PrefabDiff;
+		friend class PrefabUtility;
 	public:
 		~SceneObject();
 
@@ -60,7 +61,7 @@ namespace BansheeEngine
 		/**
 		 * @copydoc	GameObject::_setInstanceData
 		 */
-		void _setInstanceData(GameObjectInstanceDataPtr& other);
+		void _setInstanceData(GameObjectInstanceDataPtr& other) override;
 
 		/**
 		 * @brief	Returns a handle to this object.
@@ -74,6 +75,11 @@ namespace BansheeEngine
 		 */
 		HPrefab getPrefabLink() const;
 
+		/**
+		 * @brief	Breaks the link between this prefab instance and its prefab.
+		 */
+		void breakPrefabLink();
+
 		/**
 		 * @brief	Checks if the scene object has a specific bit flag set.
 		 */
@@ -126,6 +132,7 @@ namespace BansheeEngine
 	private:
 		HSceneObject mThisHandle;
 		HPrefab mPrefabLink;
+		PrefabDiffPtr mPrefabDiff;
 		UINT32 mFlags;
 
 		/************************************************************************/
@@ -416,6 +423,12 @@ namespace BansheeEngine
 		 */
 		HSceneObject clone();
 
+		/**
+		 * @brief	Updates the internal prefab diff data by recording the difference
+		 *			between the current values in this prefab instance and the prefab.
+		 */
+		void recordPrefabDiff();
+
 	private:
 		HSceneObject mParent;
 		Vector<HSceneObject> mChildren;

+ 4 - 0
BansheeCore/Include/BsSceneObjectRTTI.h

@@ -36,6 +36,9 @@ namespace BansheeEngine
 		HPrefab& getPrefabLink(SceneObject* obj) { return obj->mPrefabLink; }
 		void setPrefabLink(SceneObject* obj, HPrefab& value) { obj->mPrefabLink = value; }
 
+		PrefabDiffPtr getPrefabDiff(SceneObject* obj) { return obj->mPrefabDiff; }
+		void setPrefabDiff(SceneObject* obj, PrefabDiffPtr value) { obj->mPrefabDiff = value; }
+
 		UINT32& getFlags(SceneObject* obj) { return obj->mFlags; }
 		void setFlags(SceneObject* obj, UINT32& value) { obj->mFlags = value; }
 	public:
@@ -47,6 +50,7 @@ namespace BansheeEngine
 				&SceneObjectRTTI::getNumComponents, &SceneObjectRTTI::setComponent, &SceneObjectRTTI::setNumComponents);
 			addReflectableField("mPrefabLink", 2, &SceneObjectRTTI::getPrefabLink, &SceneObjectRTTI::setPrefabLink);
 			addPlainField("mFlags", 3, &SceneObjectRTTI::getFlags, &SceneObjectRTTI::setFlags);
+			addReflectablePtrField("mPrefabDiff", 3, &SceneObjectRTTI::getPrefabDiff, &SceneObjectRTTI::setPrefabDiff);
 		}
 
 		virtual void onDeserializationStarted(IReflectable* obj) override

+ 2 - 52
BansheeCore/Source/BsPrefab.cpp

@@ -2,6 +2,7 @@
 #include "BsPrefabRTTI.h"
 #include "BsResources.h"
 #include "BsSceneObject.h"
+#include "BsPrefabUtility.h"
 
 namespace BansheeEngine
 {
@@ -32,60 +33,9 @@ namespace BansheeEngine
 		return newPrefab;
 	}
 
-	void Prefab::generatePrefabIds(const HSceneObject& sceneObject)
-	{
-		Vector<HGameObject> objectsToId;
-		Set<INT32> existingIds;
-
-		Stack<HSceneObject> todo;
-		todo.push(sceneObject);
-
-		while (!todo.empty())
-		{
-			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)
-					objectsToId.push_back(component);
-				else
-					existingIds.insert(component->mLinkId);
-			}
-
-			UINT32 numChildren = (UINT32)currentSO->getNumChildren();
-			for (UINT32 i = 0; i < numChildren; i++)
-				todo.push(currentSO->getChild(i));
-		}
-
-		auto setIter = existingIds.begin();
-		INT32 nextId = 0;
-		for (auto& object : objectsToId)
-		{
-			INT32 freeId = -1;
-			for (; setIter != existingIds.end(); ++setIter)
-			{
-				if (nextId < (*setIter))
-					freeId = nextId++;
-				else
-					nextId++;
-			}
-
-			if (freeId == -1)
-				freeId = nextId++;
-
-			object->mLinkId = freeId;
-		}		
-	}
-
 	void Prefab::initialize(const HSceneObject& sceneObject)
 	{
-		generatePrefabIds(sceneObject);
+		PrefabUtility::generatePrefabIds(sceneObject);
 
 		sceneObject->setFlags(SOF_DontInstantiate);
 		mRoot = sceneObject->clone();

+ 87 - 0
BansheeCore/Source/BsPrefabDiff.cpp

@@ -32,9 +32,14 @@ namespace BansheeEngine
 		if (prefab->getPrefabLink() != instance->getPrefabLink() || prefab->getLinkId() != instance->getLinkId())
 			return nullptr;
 
+		Vector<RenamedGameObject> renamedObjects;
+		renameInstanceIds(prefab, instance, renamedObjects);
+
 		SPtr<PrefabDiff> output = bs_shared_ptr<PrefabDiff>();
 		output->mRoot = generateDiff(prefab, instance);
 
+		restoreInstanceIds(renamedObjects);
+
 		return output;
 	}
 
@@ -55,6 +60,9 @@ namespace BansheeEngine
 
 		object->setName(diff->name);
 
+		// Note: It is important to remove objects and components first, before adding them.
+		//		 Some systems rely on the fact that applyDiff added components/objects are 
+		//       always at the end.
 		const Vector<HComponent>& components = object->getComponents();
 		for (auto& removedId : diff->removedComponents)
 		{
@@ -306,6 +314,85 @@ namespace BansheeEngine
 		return output;
 	}
 
+	void PrefabDiff::renameInstanceIds(const HSceneObject& prefab, const HSceneObject& instance, Vector<RenamedGameObject>& output)
+	{
+		UnorderedMap<UINT32, UINT64> linkToInstanceId;
+
+		Stack<HSceneObject> todo;
+		todo.push(prefab);
+		while (!todo.empty())
+		{
+			HSceneObject current = todo.top();
+			todo.pop();
+
+			linkToInstanceId[current->getLinkId()] = current->getInstanceId();
+
+			const Vector<HComponent>& components = current->getComponents();
+			for (auto& component : components)
+				linkToInstanceId[component->getLinkId()] = component->getInstanceId();
+
+			UINT32 numChildren = current->getNumChildren();
+			for (UINT32 i = 0; i < numChildren; i++)
+			{
+				HSceneObject child = current->getChild(i);
+
+				if (child->mPrefabLink == nullptr)
+					todo.push(child);
+			}
+		}
+
+		todo.push(instance);
+		while (!todo.empty())
+		{
+			HSceneObject current = todo.top();
+			todo.pop();
+
+			if (current->getLinkId() != -1)
+			{
+				auto iterFind = linkToInstanceId.find(current->getLinkId());
+				if (iterFind != linkToInstanceId.end())
+				{
+					output.push_back(RenamedGameObject());
+					RenamedGameObject& renamedGO = output.back();
+					renamedGO.instanceData = current->mInstanceData;
+					renamedGO.originalId = current->getInstanceId();
+
+					current->mInstanceData->mInstanceId = iterFind->second;
+				}
+			}
+
+			const Vector<HComponent>& components = current->getComponents();
+			for (auto& component : components)
+			{
+				auto iterFind = linkToInstanceId.find(component->getLinkId());
+				if (iterFind != linkToInstanceId.end())
+				{
+					output.push_back(RenamedGameObject());
+					RenamedGameObject& renamedGO = output.back();
+					renamedGO.instanceData = component->mInstanceData;
+					renamedGO.originalId = component->getInstanceId();
+
+					component->mInstanceData->mInstanceId = iterFind->second;
+				}
+			}
+
+			UINT32 numChildren = current->getNumChildren();
+			for (UINT32 i = 0; i < numChildren; i++)
+			{
+				HSceneObject child = current->getChild(i);
+
+				if (child->mPrefabLink == nullptr)
+					todo.push(child);
+			}
+		}
+	}
+
+	void PrefabDiff::restoreInstanceIds(const Vector<RenamedGameObject>& renamedObjects)
+	{
+		for (auto& renamedGO : renamedObjects)
+			renamedGO.instanceData->mInstanceId = renamedGO.originalId;
+	}
+
 	RTTITypeBase* PrefabDiff::getRTTIStatic()
 	{
 		return PrefabDiffRTTI::instance();

+ 325 - 0
BansheeCore/Source/BsPrefabUtility.cpp

@@ -0,0 +1,325 @@
+#include "BsPrefabUtility.h"
+#include "BsPrefabDiff.h"
+#include "BsPrefab.h"
+#include "BsSceneObject.h"
+
+namespace BansheeEngine
+{
+	void PrefabUtility::revertToPrefab(const HSceneObject& so)
+	{
+		HPrefab prefabLink = so->getPrefabLink();
+		if (prefabLink == nullptr)
+			return;
+
+		// Save IDs, destroy original, create new, restore IDs
+		SceneObjectProxy soProxy;
+		UnorderedMap<UINT32, GameObjectInstanceDataPtr> linkedInstanceData;
+		recordInstanceData(so, soProxy, linkedInstanceData);
+
+		so->destroy();
+
+		HSceneObject newInstance = prefabLink->instantiate();
+		restoreLinkedInstanceData(newInstance, linkedInstanceData);
+	}
+
+	void PrefabUtility::updateFromPrefab(const HSceneObject& so)
+	{
+		HPrefab prefabLink = so->getPrefabLink();
+		if (prefabLink == nullptr)
+			return;
+
+		// Save IDs, destroy original, create new, apply diff, restore IDs
+		SceneObjectProxy soProxy;
+		UnorderedMap<UINT32, GameObjectInstanceDataPtr> linkedInstanceData;
+		recordInstanceData(so, soProxy, linkedInstanceData);
+
+		PrefabDiffPtr prefabDiff = so->mPrefabDiff;
+		so->destroy();
+
+		HSceneObject newInstance = prefabLink->instantiate();
+		if (prefabDiff != nullptr)
+			prefabDiff->apply(newInstance);
+
+		restoreLinkedInstanceData(newInstance, linkedInstanceData);
+		restoreUnlinkedInstanceData(newInstance, soProxy);
+	}
+
+	void PrefabUtility::generatePrefabIds(const HSceneObject& sceneObject)
+	{
+		Vector<HGameObject> objectsToId;
+		Set<INT32> existingIds;
+
+		Stack<HSceneObject> todo;
+		todo.push(sceneObject);
+
+		while (!todo.empty())
+		{
+			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)
+					objectsToId.push_back(component);
+				else
+					existingIds.insert(component->mLinkId);
+			}
+
+			UINT32 numChildren = (UINT32)currentSO->getNumChildren();
+			for (UINT32 i = 0; i < numChildren; i++)
+			{
+				HSceneObject child = currentSO->getChild(i);
+
+				if (child->mPrefabLink == nullptr)
+					todo.push(currentSO->getChild(i));
+			}
+		}
+
+		auto setIter = existingIds.begin();
+		INT32 nextId = 0;
+		for (auto& object : objectsToId)
+		{
+			INT32 freeId = -1;
+			for (; setIter != existingIds.end(); ++setIter)
+			{
+				if (nextId < (*setIter))
+					freeId = nextId++;
+				else
+					nextId++;
+			}
+
+			if (freeId == -1)
+				freeId = nextId++;
+
+			object->mLinkId = freeId;
+		}
+	}
+
+	void PrefabUtility::clearPrefabIds(const HSceneObject& sceneObject)
+	{
+		Stack<HSceneObject> todo;
+		todo.push(sceneObject);
+
+		while (!todo.empty())
+		{
+			HSceneObject currentSO = todo.top();
+			todo.pop();
+
+			currentSO->mLinkId = -1;
+			for (auto& component : currentSO->mComponents)
+				component->mLinkId = -1;
+
+			UINT32 numChildren = (UINT32)currentSO->getNumChildren();
+			for (UINT32 i = 0; i < numChildren; i++)
+			{
+				HSceneObject child = currentSO->getChild(i);
+
+				if (child->mPrefabLink == nullptr)
+					todo.push(child);
+			}
+		}
+	}
+
+	void PrefabUtility::recordInstanceData(const HSceneObject& so, SceneObjectProxy& output,
+		UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData)
+	{
+		struct StackEntry
+		{
+			HSceneObject so;
+			bool isPartOfPrefab;
+		};
+
+		Stack<StackEntry> todo;
+		todo.push(StackEntry());
+
+		StackEntry& topEntry = todo.top();
+		topEntry.so = so;
+		topEntry.isPartOfPrefab = true;
+
+		while (!todo.empty())
+		{
+			StackEntry current = todo.top();
+			todo.pop();
+
+			output.instanceData = current.so->_getInstanceData();
+
+			if (current.isPartOfPrefab)
+			{
+				output.linkId = current.so->getLinkId();
+				linkedInstanceData[output.linkId] = output.instanceData;
+			}
+			else
+				output.linkId = -1;
+
+			const Vector<HComponent>& components = current.so->getComponents();
+			for (auto& component : components)
+			{
+				output.components.push_back(ComponentProxy());
+
+				ComponentProxy& componentProxy = output.components.back();
+				componentProxy.instanceData = component->_getInstanceData();
+
+				if (current.isPartOfPrefab)
+				{
+					componentProxy.linkId = component->getLinkId();
+					linkedInstanceData[componentProxy.linkId] = componentProxy.instanceData;
+				}
+				else
+					componentProxy.linkId = -1;
+			}
+
+			UINT32 numChildren = current.so->getNumChildren();
+			for (UINT32 i = 0; i < numChildren; i++)
+			{
+				HSceneObject child = current.so->getChild(i);
+
+				todo.push(StackEntry());
+				StackEntry& newEntry = todo.top();
+
+				newEntry.so = child;
+				newEntry.isPartOfPrefab = current.isPartOfPrefab && (child->mPrefabLink == nullptr);
+			}
+		}
+	}
+
+	void PrefabUtility::restoreLinkedInstanceData(const HSceneObject& so, UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData)
+	{
+		Stack<HSceneObject> todo;
+		todo.push(so);
+
+		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)
+			{
+				if (component->getLinkId() != -1)
+				{
+					auto iterFind = linkedInstanceData.find(component->getLinkId());
+					if (iterFind != linkedInstanceData.end())
+						component->_setInstanceData(iterFind->second);
+				}
+			}
+
+			UINT32 numChildren = current->getNumChildren();
+			for (UINT32 i = 0; i < numChildren; i++)
+			{
+				HSceneObject child = current->getChild(i);
+
+				if (child->mPrefabLink == nullptr)
+					todo.push(child);
+			}
+		}
+	}
+
+	void PrefabUtility::restoreUnlinkedInstanceData(const HSceneObject& so, SceneObjectProxy& proxy)
+	{
+		struct StackEntry
+		{
+			HSceneObject so;
+			SceneObjectProxy* proxy;
+		};
+
+		Stack<StackEntry> todo;
+		todo.push(StackEntry());
+
+		assert(so->getLinkId() == proxy.linkId);
+		
+		StackEntry& topEntry = todo.top();
+		topEntry.so = so;
+		topEntry.proxy = &proxy;
+
+		while (!todo.empty())
+		{
+			StackEntry current = todo.top();
+			todo.pop();
+
+			if (current.proxy->linkId == -1)
+				current.so->_setInstanceData(current.proxy->instanceData);
+
+			const Vector<HComponent>& components = current.so->getComponents();
+			UINT32 componentProxyIdx = 0;
+			UINT32 numComponentProxies = (UINT32)current.proxy->components.size();
+			for (auto& component : components)
+			{
+				if (component->getLinkId() == -1)
+				{
+					bool foundInstanceData = false;
+					for (; componentProxyIdx < numComponentProxies; componentProxyIdx++)
+					{
+						if (current.proxy->components[componentProxyIdx].linkId != -1)
+							continue;
+
+						assert(current.proxy->components[componentProxyIdx].linkId == -1);
+						component->_setInstanceData(current.proxy->components[componentProxyIdx].instanceData);
+						foundInstanceData = true;
+						break;
+					}
+
+					assert(foundInstanceData);
+				}
+			}
+
+			UINT32 numChildren = current.so->getNumChildren();
+			UINT32 childProxyIdx = 0;
+			UINT32 numChildProxies = (UINT32)current.proxy->children.size();
+			for (UINT32 i = 0; i < numChildren; i++)
+			{
+				HSceneObject child = current.so->getChild(i);
+
+				if (child->getLinkId() == -1)
+				{
+					bool foundInstanceData = false;
+					for (; childProxyIdx < numChildProxies; childProxyIdx++)
+					{
+						if (current.proxy->children[childProxyIdx].linkId != -1)
+							continue;
+
+						assert(current.proxy->children[childProxyIdx].linkId == -1);
+						child->_setInstanceData(current.proxy->children[childProxyIdx].instanceData);
+
+						todo.push(StackEntry());
+
+						StackEntry& newEntry = todo.top();
+						newEntry.so = child;
+						newEntry.proxy = &current.proxy->children[childProxyIdx];
+
+						foundInstanceData = true;
+						break;
+					}
+
+					assert(foundInstanceData);
+				}
+				else
+				{
+					for (UINT32 j = 0; j < numChildProxies; j++)
+					{
+						if (child->getLinkId() == current.proxy->children[j].linkId)
+						{
+							todo.push(StackEntry());
+
+							StackEntry& newEntry = todo.top();
+							newEntry.so = child;
+							newEntry.proxy = &current.proxy->children[j];
+							break;
+						}
+					}
+				}
+			}
+		}
+	}
+}

+ 46 - 0
BansheeCore/Source/BsSceneObject.cpp

@@ -6,6 +6,9 @@
 #include "BsSceneObjectRTTI.h"
 #include "BsMemorySerializer.h"
 #include "BsGameObjectManager.h"
+#include "BsPrefab.h"
+#include "BsPrefabUtility.h"
+#include "BsPrefabDiff.h"
 
 namespace BansheeEngine
 {
@@ -121,6 +124,28 @@ namespace BansheeEngine
 		return HPrefab();
 	}
 
+	void SceneObject::breakPrefabLink()
+	{
+		SceneObject* curObj = this;
+
+		while (curObj == nullptr)
+		{
+			if (curObj->mPrefabLink != nullptr)
+			{
+				curObj->mPrefabLink = nullptr;
+				curObj->mPrefabDiff = nullptr;
+				PrefabUtility::clearPrefabIds(curObj->getHandle());
+
+				return;
+			}
+
+			if (curObj->mParent != nullptr)
+				curObj = curObj->mParent.get();
+			else
+				curObj = nullptr;
+		}
+	}
+
 	bool SceneObject::hasFlag(UINT32 flag) const
 	{
 		return (mFlags & flag) != 0;
@@ -528,6 +553,27 @@ namespace BansheeEngine
 		return cloneObj->mThisHandle;
 	}
 
+	void SceneObject::recordPrefabDiff()
+	{
+		SceneObject* curObj = this;
+
+		while (curObj == nullptr)
+		{
+			if (curObj->mPrefabLink != nullptr)
+			{
+				curObj->mPrefabDiff = nullptr;
+				curObj->mPrefabDiff = PrefabDiff::create(curObj->mPrefabLink->getRoot(), curObj->getHandle());
+				
+				return;
+			}
+
+			if (curObj->mParent != nullptr)
+				curObj = curObj->mParent.get();
+			else
+				curObj = nullptr;
+		}
+	}
+
 	HComponent SceneObject::getComponent(UINT32 typeId) const
 	{
 		for(auto iter = mComponents.begin(); iter != mComponents.end(); ++iter)

+ 4 - 4
BansheeUtility/Include/BsBinaryCloner.h

@@ -14,10 +14,10 @@ namespace BansheeEngine
 		/**
 		 * @brief	Returns a copy of the provided object with identical data.
 		 *
-		 * @param	object		Object to clone.
-		 * @param	shallow		If false then all referenced objects will be cloned
-		 *						as well, otherwise the references to the original
-		 *						objects will be kept.
+		 * @param[in]	object		Object to clone.
+		 * @param[in]	shallow		If false then all referenced objects will be cloned
+		 *							as well, otherwise the references to the original
+		 *							objects will be kept.
 		 */
 		static SPtr<IReflectable> clone(IReflectable* object, bool shallow = false);
 

+ 0 - 2
BansheeUtility/Source/BsBinaryCloner.cpp

@@ -36,8 +36,6 @@ namespace BansheeEngine
 		if (object == nullptr)
 			return;
 
-		Stack<IReflectable*> todo;
-
 		RTTITypeBase* rtti = object->getRTTI();
 		Stack<RTTITypeBase*> rttiTypes;
 		while (rtti != nullptr)

+ 14 - 29
TODO.txt

@@ -27,40 +27,25 @@ return them in checkForModifications?
 ---------------------------------------------------------------------
 Prefab diff
 
-TODO:
- - Test & debug prefab diff (execute unit test)
-
-Get prefab saving/loading working and only add diffs later
+TODO - Any GameObjectHandles stored in PrefabDiff will not be valid after deserialization. Normally GameObjectManager updates the handles
+ but in PrefabDiff they're stored as raw memory and cannot be updated.
+  - Have PrefabDiff store a separate list of just handles and their original IDs? Then after applyDiff I update the handles manually.
 
-Need to figure out how to restore IDs after prefab revert/update
-
-High level goal list:
- - Add prefab ID system
-   - Update IDs when linking/breaking a prefab
- - Fix native and binary diff so that GameObjectHandle can be compared properly
- - Hook up updates:
-    - Save a diff whenever a HSceneObject with a HPrefab link is saved
-    - Update active scene when prefab is modified (destroy them, create original prefab then apply saved diff)
+TODO:
+ - Need to figure out how to restore IDs after prefab revert/update
+ - Hook up prefabs & prefab diffs:
+   - Save a diff whenever a HSceneObject with a HPrefab link is saved (call recordPrefabDiff)
+   - Update active scene when prefab is modified (destroy them, create original prefab then apply saved diff)
+ - C# prefab
+ - Need a way to update an existing prefab from an instance
 
-When dealing with prefab saving make sure to consider scene object flags like "dont save"
+TODO - Whenever I load a SceneObject the entire prefab will be loaded as well. It might be better to store the
+ prefab link as a UUID and then load it on demand.
 
+Later: Test game object handle compare, and ID restore systems (likely later once I have more editor functionality working)
 Later: Native+Managed diff is not tested (only the link needs to be tested, I have a separate managed and native tests)
 Later: Modify BinarySerializer so that _encodeIntermediate outputs intermediate format directly without a full on encode
-
-See "Prefabs" gdoc for later goals
-See "Brainstorm" gdoc for ideas about how to solve the ID issue (and see below)
-
-When deserializing diffs I need to turn on GameObjectManager
- - Since diffs should be part of HSceneObject it should already be active but it is something to pay attention to
-
-GameObjectHandle compare in managed serializable diff won't work for prefabs (will have the same problem for native diff)
- - Handle comparison compares instanceIDs which will be different between prefab and its instance
- - Best solution is probably to add a flag that tells the diff system how to deal with game object handles
-   (i.e. compare using instance IDs or use the prefab link IDs)
- - But since I know I will be using serialized data (and not actual objects) to compare with
-   I could do a pre-processing step and replace all the handle IDs before compare
-
-Consider making ManagedSerializable* array/list/dictionary method/field references static (right now each instance has its own instance
+Later: Consider making ManagedSerializable* array/list/dictionary method/field references static (right now each instance has its own instance
  but they're identical)
 
 ----------------------------------------------------------------------