Browse Source

GameObjectHandles stored in PrefabDiff will now be updated when game object deserialization happens
Added a method for updating an existing Prefab with a new hierarchy

Marko Pintera 10 years ago
parent
commit
e7c65605da

+ 15 - 1
BansheeCore/Include/BsGameObjectManager.h

@@ -20,7 +20,9 @@ namespace BansheeEngine
 		GODM_RestoreExternal = 0x04,
 		/** Handles pointing to GameObjects outside of the currently deserialized set
 		will be broken. */
-		GODM_BreakExternal = 0x08
+		GODM_BreakExternal = 0x08,
+		/** Handles pointing to GameObjects that cannot be found will not be set to null. */
+		GODM_KeepMissing = 0x10
 	};
 
 	/**
@@ -130,6 +132,18 @@ namespace BansheeEngine
 		 */
 		void setDeserializationMode(UINT32 gameObjectDeserializationMode);
 
+		/**
+		 * @brief	Attempts to update the ID of the provided handle by mapping its old ID to
+		 *			the newly deserialized object and its new ID. Game object deserialization
+		 *			must be active.
+		 */
+		void resolveDeserializedHandle(GameObjectHandleBase& handle, UINT32 flags);
+
+		/**
+		 * @brief	Gets the currently active flags that control how are game object handles deserialized.
+		 */
+		UINT32 getDeserializationFlags() const { return mGODeserializationMode; }
+
 	private:
 		UINT64 mNextAvailableID; // 0 is not a valid ID
 		Map<UINT64, GameObjectHandleBase> mObjects;

+ 6 - 0
BansheeCore/Include/BsPrefab.h

@@ -30,6 +30,12 @@ namespace BansheeEngine
 		 */
 		HSceneObject instantiate();
 
+		/**
+		 * @brief	Replaces the contents of this prefab with new contents
+		 *			from the provided object.
+		 */
+		void update(const HSceneObject& sceneObject);
+
 		/**
 		 * @brief	Returns a reference to the internal prefab hierarchy.
 		 */

+ 116 - 0
BansheeCore/Include/BsPrefabDiffRTTI.h

@@ -3,6 +3,9 @@
 #include "BsCorePrerequisites.h"
 #include "BsRTTIType.h"
 #include "BsPrefabDiff.h"
+#include "BsSerializedObject.h"
+#include "BsGameObjectManager.h"
+#include "BsBinarySerializer.h"
 
 namespace BansheeEngine
 {
@@ -90,6 +93,119 @@ namespace BansheeEngine
 			BS_ADD_REFLPTR_FIELD(mRoot, 0);
 		}
 
+		virtual void onDeserializationStarted(IReflectable* obj) override
+		{
+			PrefabDiff* prefabDiff = static_cast<PrefabDiff*>(obj);
+
+			if (GameObjectManager::instance().isGameObjectDeserializationActive())
+				GameObjectManager::instance().registerOnDeserializationEndCallback(std::bind(&PrefabDiffRTTI::delayedOnDeserializationEnded, prefabDiff));
+		}
+
+		/**
+		 * @brief	Decodes GameObjectHandles from their binary format, because during deserialization GameObjectManager
+		 *			will update all object IDs and we want to keep the handles up to date.So we deserialize them
+		 *			and allow them to be updated before storing them back into binary format.
+		 */
+		static void delayedOnDeserializationEnded(PrefabDiff* prefabDiff)
+		{
+			Stack<SPtr<PrefabObjectDiff>> todo;
+			todo.push(prefabDiff->mRoot);
+
+			UnorderedSet<SPtr<SerializedObject>> handleObjects;
+
+			while (!todo.empty())
+			{
+				SPtr<PrefabObjectDiff> current = todo.top();
+				todo.pop();
+
+				for (auto& component : current->addedComponents)
+					findGameObjectHandles(component, handleObjects);
+
+				for (auto& child : current->addedChildren)
+					findGameObjectHandles(child, handleObjects);
+
+				for (auto& component : current->componentDiffs)
+					findGameObjectHandles(component->data, handleObjects);
+
+				for (auto& child : current->childDiffs)
+					todo.push(child);
+			}
+
+			BinarySerializer bs;
+			for (auto& serializedHandle : handleObjects)
+			{
+				SPtr<GameObjectHandleBase> handle = std::static_pointer_cast<GameObjectHandleBase>(bs._decodeIntermediate(serializedHandle));
+				if (handle != nullptr)
+				{
+					UINT32 flags = GameObjectManager::instance().getDeserializationFlags();
+					GameObjectManager::instance().resolveDeserializedHandle(*handle, flags | GODM_KeepMissing);
+					*serializedHandle = *bs._encodeIntermediate(handle.get());
+				}
+			}
+		}
+
+		/**
+		 * @brief	Scans the entire hierarchy and find all serialized GameObjectHandle objects.
+		 */
+		static void findGameObjectHandles(const SPtr<SerializedObject>& serializedObject, UnorderedSet<SPtr<SerializedObject>>& handleObjects)
+		{
+			for (auto& subObject : serializedObject->subObjects)
+			{
+				RTTITypeBase* rtti = IReflectable::_getRTTIfromTypeId(subObject.typeId);
+				if (rtti == nullptr)
+					continue;
+
+				if (rtti->getRTTIId() == TID_GameObjectHandleBase)
+				{
+					handleObjects.insert(serializedObject);
+					return;
+				}
+
+				for (auto& child : subObject.entries)
+				{
+					RTTIField* curGenericField = rtti->getField(child.second.fieldId);
+					if (curGenericField == nullptr)
+						continue;
+
+					SPtr<SerializedInstance> entryData = child.second.serialized;
+					if (curGenericField->isArray())
+					{
+						SPtr<SerializedArray> arrayData = std::static_pointer_cast<SerializedArray>(entryData);
+
+						switch (curGenericField->mType)
+						{
+						case SerializableFT_ReflectablePtr:
+						case SerializableFT_Reflectable:
+						{
+							for (auto& arrayElem : arrayData->entries)
+							{
+								SPtr<SerializedObject> arrayElemData = std::static_pointer_cast<SerializedObject>(arrayElem.second.serialized);
+
+								if (arrayElemData != nullptr)
+									findGameObjectHandles(arrayElemData, handleObjects);
+							}
+						}
+							break;
+						}
+					}
+					else
+					{
+						switch (curGenericField->mType)
+						{
+						case SerializableFT_ReflectablePtr:
+						case SerializableFT_Reflectable:
+						{
+							SPtr<SerializedObject> fieldObjectData = std::static_pointer_cast<SerializedObject>(entryData);
+							if (fieldObjectData != nullptr)
+								findGameObjectHandles(fieldObjectData, handleObjects);
+						}
+							break;
+						}
+					}
+				}
+			}
+		}
+
 		virtual const String& getRTTIName() override
 		{
 			static String name = "PrefabDiff";

+ 1 - 1
BansheeCore/Include/BsSceneObjectRTTI.h

@@ -50,7 +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);
+			addReflectablePtrField("mPrefabDiff", 4, &SceneObjectRTTI::getPrefabDiff, &SceneObjectRTTI::setPrefabDiff);
 		}
 
 		virtual void onDeserializationStarted(IReflectable* obj) override

+ 37 - 26
BansheeCore/Source/BsGameObjectManager.cpp

@@ -99,32 +99,7 @@ namespace BansheeEngine
 		assert(mIsDeserializationActive);
 
 		for(auto& unresolvedHandle : mUnresolvedHandles)
-		{
-			UINT64 instanceId = unresolvedHandle.getInstanceId();
-			
-			bool isInternalReference = false;
-			
-			auto findIter = mIdMapping.find(instanceId);
-			if (findIter != mIdMapping.end())
-			{
-				if ((mGODeserializationMode & GODM_UseNewIds) != 0)
-					instanceId = findIter->second;
-
-				isInternalReference = true;
-			}
-
-			if (isInternalReference || (!isInternalReference && (mGODeserializationMode & GODM_RestoreExternal) != 0))
-			{
-				auto findIterObj = mObjects.find(instanceId);
-
-				if (findIterObj != mObjects.end())
-					unresolvedHandle._resolve(findIterObj->second);
-				else
-					unresolvedHandle._resolve(nullptr);
-			}
-			else
-				unresolvedHandle._resolve(nullptr);
-		}
+			resolveDeserializedHandle(unresolvedHandle, mGODeserializationMode);
 
 		for(auto iter = mEndCallbacks.rbegin(); iter != mEndCallbacks.rend(); ++iter)
 		{
@@ -138,6 +113,42 @@ namespace BansheeEngine
 		mEndCallbacks.clear();
 	}
 
+	void GameObjectManager::resolveDeserializedHandle(GameObjectHandleBase& handle, UINT32 flags)
+	{
+		assert(mIsDeserializationActive);
+
+		UINT64 instanceId = handle.getInstanceId();
+
+		bool isInternalReference = false;
+
+		auto findIter = mIdMapping.find(instanceId);
+		if (findIter != mIdMapping.end())
+		{
+			if ((flags & GODM_UseNewIds) != 0)
+				instanceId = findIter->second;
+
+			isInternalReference = true;
+		}
+
+		if (isInternalReference || (!isInternalReference && (flags & GODM_RestoreExternal) != 0))
+		{
+			auto findIterObj = mObjects.find(instanceId);
+
+			if (findIterObj != mObjects.end())
+				handle._resolve(findIterObj->second);
+			else
+			{
+				if ((flags & GODM_KeepMissing) == 0)
+					handle._resolve(nullptr);
+			}
+		}
+		else
+		{
+			if ((flags & GODM_KeepMissing) == 0)
+				handle._resolve(nullptr);
+		}
+	}
+
 	void GameObjectManager::registerDeserializedId(UINT64 serializedId, UINT64 actualId)
 	{
 #if BS_DEBUG_MODE

+ 5 - 0
BansheeCore/Source/BsPrefab.cpp

@@ -61,6 +61,11 @@ namespace BansheeEngine
 		}
 	}
 
+	void Prefab::update(const HSceneObject& sceneObject)
+	{
+		initialize(sceneObject);
+	}
+
 	HSceneObject Prefab::instantiate()
 	{
 		if (mRoot == nullptr)

+ 0 - 6
TODO.txt

@@ -27,17 +27,11 @@ return them in checkForModifications?
 ---------------------------------------------------------------------
 Prefab diff
 
-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.
-
 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
 
 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.