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

Working on recording and restoring GameObjects (UndoRedo) (Untested)

Marko Pintera 11 лет назад
Родитель
Сommit
52af129bf2

+ 2 - 1
BansheeCore/Include/BsCorePrerequisites.h

@@ -199,7 +199,8 @@ namespace BansheeEngine
 	typedef std::shared_ptr<RendererFactory> RendererFactoryPtr;
 	typedef std::shared_ptr<RendererFactory> RendererFactoryPtr;
 	typedef std::shared_ptr<PassParameters> PassParametersPtr;
 	typedef std::shared_ptr<PassParameters> PassParametersPtr;
 	typedef std::shared_ptr<Component> ComponentPtr;
 	typedef std::shared_ptr<Component> ComponentPtr;
-	typedef std::shared_ptr<SceneObject> GameObjectPtr;
+	typedef std::shared_ptr<GameObject> GameObjectPtr;
+	typedef std::shared_ptr<SceneObject> SceneObjectPtr;
 	typedef std::shared_ptr<SamplerState> SamplerStatePtr;
 	typedef std::shared_ptr<SamplerState> SamplerStatePtr;
 	typedef std::shared_ptr<DepthStencilState> DepthStencilStatePtr;
 	typedef std::shared_ptr<DepthStencilState> DepthStencilStatePtr;
 	typedef std::shared_ptr<RasterizerState> RasterizerStatePtr;
 	typedef std::shared_ptr<RasterizerState> RasterizerStatePtr;

+ 23 - 2
BansheeCore/Include/BsGameObject.h

@@ -11,13 +11,15 @@ namespace BansheeEngine
 	struct GameObjectInstanceData
 	struct GameObjectInstanceData
 	{
 	{
 		GameObjectInstanceData()
 		GameObjectInstanceData()
-			:mInstanceId(0), object(nullptr)
+		:mInstanceId(0), object(nullptr)
 		{ }
 		{ }
 
 
 		std::shared_ptr<GameObject> object;
 		std::shared_ptr<GameObject> object;
 		UINT64 mInstanceId;
 		UINT64 mInstanceId;
 	};
 	};
 
 
+	typedef std::shared_ptr<GameObjectInstanceData> GameObjectInstanceDataPtr;
+
 	/**
 	/**
 	 * @brief	Type of object that can be referenced by a GameObject handle.
 	 * @brief	Type of object that can be referenced by a GameObject handle.
 	 *			Each object has an unique ID and is registered with the GameObjectManager.
 	 *			Each object has an unique ID and is registered with the GameObjectManager.
@@ -43,6 +45,25 @@ namespace BansheeEngine
 		 */
 		 */
 		void setName(const String& name) { mName = name; }
 		void setName(const String& name) { mName = name; }
 
 
+		/**
+		 * @brief	Replaces the instance data with another objects instance data.
+		 *			This object will basically become the original owner of the provided
+		 *			instance data as far as all game object handles referencing it are concerned.
+		 *
+		 * @note	Internal method. 
+		 *			No alive objects should ever be sharing the same instance data. This can be used
+		 *			for restoring dead handles.
+		 */
+		virtual void _setInstanceData(GameObjectInstanceDataPtr& other);
+
+		/**
+		 * @brief	Returns instance data that identifies this GameObject and is used for referencing
+		 *			by game object handles.
+		 *
+		 * @note	Internal method.
+		 */
+		virtual GameObjectInstanceDataPtr _getInstanceData() const { return mInstanceData; }
+
 	protected:
 	protected:
 		friend class GameObjectHandleBase;
 		friend class GameObjectHandleBase;
 		friend class GameObjectManager;
 		friend class GameObjectManager;
@@ -56,7 +77,7 @@ namespace BansheeEngine
 		String mName;
 		String mName;
 
 
 	private:
 	private:
-		std::shared_ptr<GameObjectInstanceData> mInstanceData;
+		GameObjectInstanceDataPtr mInstanceData;
 
 
 		/************************************************************************/
 		/************************************************************************/
 		/* 								RTTI		                     		*/
 		/* 								RTTI		                     		*/

+ 13 - 9
BansheeCore/Include/BsGameObjectHandle.h

@@ -104,6 +104,13 @@ namespace BansheeEngine
 		 */
 		 */
 		void _resolve(const GameObjectHandleBase& object);
 		void _resolve(const GameObjectHandleBase& object);
 
 
+		/**
+		 * @brief	Changes the GameObject instance the handle is pointing to.
+		 *
+		 * @note	Internal method.
+		 */
+		void _setHandleData(const GameObjectPtr& object);
+
 	protected:
 	protected:
 		friend class SceneObject;
 		friend class SceneObject;
 		friend class SceneObjectRTTI;
 		friend class SceneObjectRTTI;
@@ -119,18 +126,15 @@ namespace BansheeEngine
 		inline void throwIfDestroyed() const;
 		inline void throwIfDestroyed() const;
 		
 		
 		/**
 		/**
-		 * @brief	Invalidates the handle signifiying the referenced object was destroyed.
+		 * @brief	Invalidates the handle signifying the referenced object was destroyed.
 		 */
 		 */
 		void destroy()
 		void destroy()
 		{
 		{
-			// We need to clear mData->mPtr before we clear mData->mPtr->object,
-			// as this handle could be stored within the "object" and destroyed when
-			// we set it to null 
-			std::shared_ptr<GameObjectInstanceData> instanceData = mData->mPtr;
-			mData->mPtr = nullptr;
-
-			if(instanceData != nullptr)
-				instanceData->object = nullptr;
+			// It's important not to clear mData->mPtr as some code might rely
+			// on it. (e.g. for restoring lost handles)
+
+			if (mData->mPtr != nullptr)
+				mData->mPtr->object = nullptr;
 		}
 		}
 
 
 		std::shared_ptr<GameObjectHandleData> mData;
 		std::shared_ptr<GameObjectHandleData> mData;

+ 32 - 0
BansheeCore/Include/BsGameObjectManager.h

@@ -6,6 +6,23 @@
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
+	/**
+	 * @brief	Possible modes to use when deserializing games objects.
+	 */
+	enum GameObjectHandleDeserializationMode
+	{
+		/** All handles will point to old ID that were restored from the deserialized file. */
+		GODM_UseOriginalIds = 0x01, 
+		/** All handles will point to new IDs that were given to the deserialized GameObjects. */
+		GODM_UseNewIds = 0x02,
+		/** Handles pointing to GameObjects outside of the currently deserialized set
+		will attempt to be restored in case those objects are still active. */
+		GODM_RestoreExternal = 0x04,
+		/** Handles pointing to GameObjects outside of the currently deserialized set
+		will be broken. */
+		GODM_BreakExternal = 0x08
+	};
+
 	/**
 	/**
 	 * @brief	Tracks GameObject creation and destructions. Also resolves
 	 * @brief	Tracks GameObject creation and destructions. Also resolves
 	 *			GameObject references from GameObject handles.
 	 *			GameObject references from GameObject handles.
@@ -45,6 +62,13 @@ namespace BansheeEngine
 		 */
 		 */
 		bool objectExists(UINT64 id) const;
 		bool objectExists(UINT64 id) const;
 
 
+		/**
+		 * @brief	Changes the instance ID by which an object can be retrieved by. 
+		 *
+		 * @note	Caller is required to update the object itself with the new ID.
+		 */
+		void remapId(UINT64 oldId, UINT64 newId);
+
 		/************************************************************************/
 		/************************************************************************/
 		/* 							DESERIALIZATION                      		*/
 		/* 							DESERIALIZATION                      		*/
 		/************************************************************************/
 		/************************************************************************/
@@ -89,6 +113,13 @@ namespace BansheeEngine
 		 */
 		 */
 		void registerOnDeserializationEndCallback(std::function<void()> callback);
 		void registerOnDeserializationEndCallback(std::function<void()> callback);
 
 
+		/**
+		 * @brief	Changes the deserialization mode for any following GameObject handle.
+		 *
+		 * @param	gameObjectDeserializationMode		Mode that controls how are GameObjects handles resolved when being deserialized.
+		 */
+		void setDeserializationMode(UINT32 gameObjectDeserializationMode);
+
 	private:
 	private:
 		UINT64 mNextAvailableID; // 0 is not a valid ID
 		UINT64 mNextAvailableID; // 0 is not a valid ID
 		Map<UINT64, GameObjectHandleBase> mObjects;
 		Map<UINT64, GameObjectHandleBase> mObjects;
@@ -98,5 +129,6 @@ namespace BansheeEngine
 		Map<UINT64, UINT64> mIdMapping;
 		Map<UINT64, UINT64> mIdMapping;
 		Vector<GameObjectHandleBase> mUnresolvedHandles;
 		Vector<GameObjectHandleBase> mUnresolvedHandles;
 		Vector<std::function<void()>> mEndCallbacks;
 		Vector<std::function<void()>> mEndCallbacks;
+		UINT32 mGODeserializationMode;
 	};
 	};
 }
 }

+ 9 - 0
BansheeCore/Include/BsSceneObject.h

@@ -32,6 +32,15 @@ namespace BansheeEngine
 		 */
 		 */
 		void destroy();
 		void destroy();
 
 
+		/**
+		 * @copydoc	GameObject::_setInstanceData
+		 */
+		void _setInstanceData(GameObjectInstanceDataPtr& other);
+
+		/**
+		 * @brief	Returns a handle to this object.
+		 */
+		HSceneObject getHandle() const { return mThisHandle; }
 	private:
 	private:
 		SceneObject(const String& name);
 		SceneObject(const String& name);
 
 

+ 11 - 0
BansheeCore/Source/BsGameObject.cpp

@@ -16,6 +16,17 @@ namespace BansheeEngine
 		mInstanceData->object = object;
 		mInstanceData->object = object;
 		mInstanceData->mInstanceId = instanceId;
 		mInstanceData->mInstanceId = instanceId;
 	}
 	}
+
+	void GameObject::_setInstanceData(GameObjectInstanceDataPtr& other)
+	{
+		GameObjectPtr myPtr = mInstanceData->object;
+		UINT64 oldId = mInstanceData->mInstanceId;
+
+		mInstanceData = other;
+		mInstanceData->object = myPtr;
+
+		GameObjectManager::instance().remapId(oldId, mInstanceData->mInstanceId);
+	}
 	
 	
 	RTTITypeBase* GameObject::getRTTIStatic()
 	RTTITypeBase* GameObject::getRTTIStatic()
 	{
 	{

+ 7 - 0
BansheeCore/Source/BsGameObjectHandle.cpp

@@ -2,6 +2,7 @@
 #include "BsGameObject.h"
 #include "BsGameObject.h"
 #include "BsGameObjectHandle.h"
 #include "BsGameObjectHandle.h"
 #include "BsException.h"
 #include "BsException.h"
+#include "BsGameObject.h"
 #include "BsGameObjectHandleRTTI.h"
 #include "BsGameObjectHandleRTTI.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
@@ -31,6 +32,12 @@ namespace BansheeEngine
 		mData->mInstanceId = object.mData->mInstanceId;
 		mData->mInstanceId = object.mData->mInstanceId;
 	}
 	}
 
 
+	void GameObjectHandleBase::_setHandleData(const GameObjectPtr& object)
+	{
+		mData->mPtr = object->mInstanceData;
+		mData->mInstanceId = object->mInstanceData->mInstanceId;
+	}
+
 	void GameObjectHandleBase::throwIfDestroyed() const
 	void GameObjectHandleBase::throwIfDestroyed() const
 	{
 	{
 		if(isDestroyed()) 
 		if(isDestroyed()) 

+ 38 - 7
BansheeCore/Source/BsGameObjectManager.cpp

@@ -4,7 +4,7 @@
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
 	GameObjectManager::GameObjectManager()
 	GameObjectManager::GameObjectManager()
-		:mNextAvailableID(1), mIsDeserializationActive(false)
+		:mNextAvailableID(1), mIsDeserializationActive(false), mGODeserializationMode(GODM_UseNewIds | GODM_BreakExternal)
 	{
 	{
 
 
 	}
 	}
@@ -40,6 +40,15 @@ namespace BansheeEngine
 		return mObjects.find(id) != mObjects.end(); 
 		return mObjects.find(id) != mObjects.end(); 
 	}
 	}
 
 
+	void GameObjectManager::remapId(UINT64 oldId, UINT64 newId)
+	{
+		if (oldId == newId)
+			return;
+
+		mObjects[newId] = mObjects[oldId];
+		mObjects.erase(oldId);
+	}
+
 	GameObjectHandleBase GameObjectManager::registerObject(const std::shared_ptr<GameObject>& object)
 	GameObjectHandleBase GameObjectManager::registerObject(const std::shared_ptr<GameObject>& object)
 	{
 	{
 		object->initialize(object, mNextAvailableID);
 		object->initialize(object, mNextAvailableID);
@@ -70,17 +79,27 @@ namespace BansheeEngine
 		for(auto& unresolvedHandle : mUnresolvedHandles)
 		for(auto& unresolvedHandle : mUnresolvedHandles)
 		{
 		{
 			UINT64 instanceId = unresolvedHandle.getInstanceId();
 			UINT64 instanceId = unresolvedHandle.getInstanceId();
-
+			
+			bool isInternalReference = false;
+			
 			auto findIter = mIdMapping.find(instanceId);
 			auto findIter = mIdMapping.find(instanceId);
-			if(findIter != mIdMapping.end())
+			if (findIter != mIdMapping.end())
 			{
 			{
-				instanceId = findIter->second;
+				if ((mGODeserializationMode & GODM_UseNewIds) != 0)
+					instanceId = findIter->second;
+
+				isInternalReference = true;
 			}
 			}
 
 
-			auto findIterObj = mObjects.find(instanceId);
+			if (isInternalReference || (!isInternalReference && (mGODeserializationMode & GODM_RestoreExternal) != 0))
+			{
+				auto findIterObj = mObjects.find(instanceId);
 
 
-			if(findIterObj != mObjects.end())
-				unresolvedHandle._resolve(findIterObj->second);	
+				if (findIterObj != mObjects.end())
+					unresolvedHandle._resolve(findIterObj->second);
+				else
+					unresolvedHandle._resolve(nullptr);
+			}
 			else
 			else
 				unresolvedHandle._resolve(nullptr);
 				unresolvedHandle._resolve(nullptr);
 		}
 		}
@@ -132,4 +151,16 @@ namespace BansheeEngine
 
 
 		mEndCallbacks.push_back(callback);
 		mEndCallbacks.push_back(callback);
 	}
 	}
+
+	void GameObjectManager::setDeserializationMode(UINT32 gameObjectDeserializationMode)
+	{
+#if BS_DEBUG_MODE
+		if (mIsDeserializationActive)
+		{
+			BS_EXCEPT(InvalidStateException, "Deserialization modes can not be modified when deserialization is not active.");
+		}
+#endif
+
+		mGODeserializationMode = gameObjectDeserializationMode;
+	}
 }
 }

+ 9 - 0
BansheeCore/Source/BsSceneObject.cpp

@@ -80,6 +80,14 @@ namespace BansheeEngine
 		mThisHandle.destroy();
 		mThisHandle.destroy();
 	}
 	}
 
 
+	void SceneObject::_setInstanceData(GameObjectInstanceDataPtr& other)
+	{
+		GameObject::_setInstanceData(other);
+
+		// Instance data changed, so make sure to refresh the handles to reflect that
+		mThisHandle._setHandleData(mThisHandle.getInternalPtr());
+	}
+
 	/************************************************************************/
 	/************************************************************************/
 	/* 								Transform	                     		*/
 	/* 								Transform	                     		*/
 	/************************************************************************/
 	/************************************************************************/
@@ -395,6 +403,7 @@ namespace BansheeEngine
 		MemorySerializer serializer;
 		MemorySerializer serializer;
 		UINT8* buffer = serializer.encode(this, bufferSize, &bs_alloc);
 		UINT8* buffer = serializer.encode(this, bufferSize, &bs_alloc);
 
 
+		GameObjectManager::instance().setDeserializationMode(GODM_UseNewIds | GODM_RestoreExternal);
 		std::shared_ptr<SceneObject> cloneObj = std::static_pointer_cast<SceneObject>(serializer.decode(buffer, bufferSize));
 		std::shared_ptr<SceneObject> cloneObj = std::static_pointer_cast<SceneObject>(serializer.decode(buffer, bufferSize));
 		bs_free(buffer);
 		bs_free(buffer);
 
 

+ 2 - 0
BansheeEditor/BansheeEditor.vcxproj

@@ -264,6 +264,7 @@
   <ItemGroup>
   <ItemGroup>
     <ClInclude Include="Include\BsCmdEditPlainFieldGO.h" />
     <ClInclude Include="Include\BsCmdEditPlainFieldGO.h" />
     <ClInclude Include="Include\BsCmdInputFieldValueChange.h" />
     <ClInclude Include="Include\BsCmdInputFieldValueChange.h" />
+    <ClInclude Include="Include\BsCmdRecordSO.h" />
     <ClInclude Include="Include\BsCmdReparentSO.h" />
     <ClInclude Include="Include\BsCmdReparentSO.h" />
     <ClInclude Include="Include\BsDbgTestGameObjectRef.h" />
     <ClInclude Include="Include\BsDbgTestGameObjectRef.h" />
     <ClInclude Include="Include\BsDbgTestGameObjectRefRTTI.h" />
     <ClInclude Include="Include\BsDbgTestGameObjectRefRTTI.h" />
@@ -319,6 +320,7 @@
     <ClInclude Include="Include\BsUndoRedo.h" />
     <ClInclude Include="Include\BsUndoRedo.h" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
+    <ClCompile Include="Source\BsCmdRecordSO.cpp" />
     <ClCompile Include="Source\BsCmdReparentSO.cpp" />
     <ClCompile Include="Source\BsCmdReparentSO.cpp" />
     <ClCompile Include="Source\BsDbgTestGameObjectRef.cpp" />
     <ClCompile Include="Source\BsDbgTestGameObjectRef.cpp" />
     <ClCompile Include="Source\BsDockManager.cpp" />
     <ClCompile Include="Source\BsDockManager.cpp" />

+ 6 - 0
BansheeEditor/BansheeEditor.vcxproj.filters

@@ -195,6 +195,9 @@
     <ClInclude Include="Include\BsResourceImporter.h">
     <ClInclude Include="Include\BsResourceImporter.h">
       <Filter>Header Files\Editor</Filter>
       <Filter>Header Files\Editor</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\BsCmdRecordSO.h">
+      <Filter>Header Files\Editor\Commands</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsEditorWidgetContainer.cpp">
     <ClCompile Include="Source\BsEditorWidgetContainer.cpp">
@@ -338,5 +341,8 @@
     <ClCompile Include="Source\BsResourceImporter.cpp">
     <ClCompile Include="Source\BsResourceImporter.cpp">
       <Filter>Source Files\Editor</Filter>
       <Filter>Source Files\Editor</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\BsCmdRecordSO.cpp">
+      <Filter>Source Files\Editor\Command</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 42 - 0
BansheeEditor/Include/BsCmdRecordSO.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "BsEditorCommand.h"
+#include "BsUndoRedo.h"
+
+namespace BansheeEngine
+{
+	class CmdRecordSO : public EditorCommand
+	{
+		struct SceneObjProxy
+		{
+			GameObjectInstanceDataPtr instanceData;
+
+			Vector<GameObjectInstanceDataPtr> componentInstanceData;
+			Vector<SceneObjProxy> children;
+		};
+
+	public:
+		~CmdRecordSO();
+
+		static void execute(const HSceneObject& sceneObject);
+
+		void commit();
+		void revert();
+
+	private:
+		friend class UndoRedo;
+
+		CmdRecordSO(const HSceneObject& sceneObject);
+		void clear();
+		SceneObjProxy createProxy(const HSceneObject& sceneObject);
+		void restoreIds(const HSceneObject& restored);
+
+		HSceneObject mSceneObject;
+		SceneObjProxy mSceneObjectProxy;
+
+		UINT8* mSerializedObject;
+		UINT32 mSerializedObjectSize;
+		UINT64 mSerializedObjectParentId;
+	};
+}

+ 169 - 0
BansheeEditor/Source/BsCmdRecordSO.cpp

@@ -0,0 +1,169 @@
+#include "BsCmdRecordSO.h"
+#include "BsSceneObject.h"
+#include "BsComponent.h"
+#include "BsMemorySerializer.h"
+
+namespace BansheeEngine
+{
+	CmdRecordSO::CmdRecordSO(const HSceneObject& sceneObject)
+		:mSceneObject(sceneObject), mSerializedObject(nullptr), mSerializedObjectParentId(0), mSerializedObjectSize(0)
+	{
+
+	}
+
+	CmdRecordSO::~CmdRecordSO()
+	{
+		clear();
+	}
+
+	void CmdRecordSO::clear()
+	{
+		mSceneObject = nullptr;
+		mSerializedObjectSize = 0;
+		mSerializedObjectParentId = 0;
+
+		if (mSerializedObject != nullptr)
+		{
+			bs_free(mSerializedObject);
+			mSerializedObject = nullptr;
+		}
+	}
+
+	void CmdRecordSO::execute(const HSceneObject& sceneObject)
+	{
+		// Register command and commit it
+		CmdRecordSO* command = new (bs_alloc<CmdRecordSO>()) CmdRecordSO(sceneObject);
+		UndoRedo::instance().registerCommand(command);
+		command->commit();
+	}
+
+	void CmdRecordSO::commit()
+	{
+		clear();
+
+		if (mSceneObject == nullptr || mSceneObject.isDestroyed())
+			return;
+
+		MemorySerializer serializer;
+		mSerializedObject = serializer.encode(mSceneObject.get(), mSerializedObjectSize, &bs_alloc);
+
+		HSceneObject parent = mSceneObject->getParent();
+		if (parent != nullptr)
+			mSerializedObjectParentId = parent->getInstanceId();
+
+		mSceneObjectProxy = createProxy(mSceneObject);
+	}
+
+	void CmdRecordSO::revert()
+	{
+		if (mSceneObject == nullptr)
+			return;
+
+		HSceneObject parent;
+		if (mSerializedObjectParentId != 0)
+			parent = GameObjectManager::instance().getObject(mSerializedObjectParentId);
+
+		GameObjectManager::instance().setDeserializationMode(GODM_RestoreExternal | GODM_UseNewIds);
+
+		if (!mSceneObject.isDestroyed())
+			mSceneObject->destroy();
+
+		MemorySerializer serializer;
+		std::shared_ptr<SceneObject> restored = std::static_pointer_cast<SceneObject>(serializer.decode(mSerializedObject, mSerializedObjectSize));
+
+		restoreIds(restored->getHandle());
+	}
+
+	CmdRecordSO::SceneObjProxy CmdRecordSO::createProxy(const HSceneObject& sceneObject)
+	{
+		struct TempData
+		{
+			TempData(SceneObjProxy& proxy, const HSceneObject& so)
+				:proxy(proxy), obj(so)
+			{ }
+
+			SceneObjProxy& proxy;
+			HSceneObject obj;
+		};
+
+		SceneObjProxy rootProxy;
+
+		Stack<TempData> todo;
+		todo.push(TempData(rootProxy, sceneObject));
+
+		while (!todo.empty())
+		{
+			TempData data = todo.top();
+			todo.pop();
+
+			data.proxy.instanceData = data.obj->_getInstanceData();
+
+			const Vector<HComponent>& components = data.obj->getComponents();
+			for (auto& component : components)
+				data.proxy.componentInstanceData.push_back(component->_getInstanceData());
+
+			UINT32 numChildren = data.obj->getNumChildren();
+			data.proxy.children.resize(numChildren);
+			for (UINT32 i = 0; i < numChildren; i++)
+			{
+				todo.push(TempData(data.proxy.children[i], data.obj->getChild(i)));
+			}
+		}
+
+		return rootProxy;
+	}
+
+
+	void CmdRecordSO::restoreIds(const HSceneObject& restored)
+	{
+		// Note: This method relies that all restored GameObject handles pointing to the
+		// same object also have the same shared handle data (Since I only update instance
+		// data on a single handle I know exists, and expect all others will be updated
+		// by that as well).
+
+		struct TempData
+		{
+			TempData(SceneObjProxy& proxy, const HSceneObject& restoredObj)
+			:proxy(proxy), restoredObj(restoredObj)
+			{ }
+
+			SceneObjProxy& proxy;
+			HSceneObject restoredObj;
+		};
+
+		Stack<TempData> todo;
+		todo.push(TempData(mSceneObjectProxy, restored));
+
+		while (!todo.empty())
+		{
+			TempData data = todo.top();
+			todo.pop();
+
+			data.restoredObj->_setInstanceData(data.proxy.instanceData);
+
+			// Find components that are still active and swap the old ones with restored ones,
+			// keep any other as is.
+			const Vector<HComponent>& restoredComponents = data.restoredObj->getComponents();
+
+			UINT32 idx = 0;
+			for (auto& restoredComponent : restoredComponents)
+			{
+				restoredComponent->_setInstanceData(data.proxy.componentInstanceData[idx]);
+
+				GameObjectPtr restoredPtr = std::static_pointer_cast<GameObject>(restoredComponent.getInternalPtr());
+				HComponent restoredComponentCopy = restoredComponent; // To remove const
+				restoredComponentCopy._setHandleData(restoredPtr);
+
+				idx++;
+			}
+			
+			// Find children that are still active and swap the old ones with restored ones,
+			// keep any other as is
+			UINT32 restoredNumChildren = data.restoredObj->getNumChildren();
+			for (UINT32 i = 0; i < restoredNumChildren; i++)
+			{
+				todo.push(TempData(data.proxy.children[i], data.restoredObj->getChild(i)));
+			}
+		}
+	}
+}

+ 18 - 0
Inspector.txt

@@ -1,10 +1,28 @@
 Update C# GUIElementStyle
 Update C# GUIElementStyle
 Update GUIFoldout with sub styles
 Update GUIFoldout with sub styles
 
 
+Test if drag and dropping scene objects works with object and resource fields. Especially custom resources and components.
+
 Test custom resources:
 Test custom resources:
  - Can I load them? (Will likely need ProjectLIbrary::load)
  - Can I load them? (Will likely need ProjectLIbrary::load)
  - Can I reference them in Component and will the reference be held after after cloning?
  - Can I reference them in Component and will the reference be held after after cloning?
 
 
+Test CmdRecordSO.
+ - Figure out how to properly restore all handles. Requirements:
+   1. Any active game object needs to replaced with the restored version
+   2. Replaced game objects must be destroyed
+   3. All handles to the object must be kept intact
+   4. All handles (internal and external) on the restored object must be kept intact
+ - Create hierarchy with three layers. One SO needs to have a couple of components. One components should reference child SO,
+   other component should reference outside SO. Outside component should reference one SO in hierarchy, and one component in hierarchy.
+   Then I perform Record.
+   Modify some plain values
+   Perform undo
+   Check that number of game objects remained the same and that they all have original IDs
+   Check that all above mentioned references are still active
+
+   Repeat the similar test but this time instead of modfying plain values, modify number of components and the hierarchy.
+
 TODO:
 TODO:
  - Hook up int field set/get callbacks
  - Hook up int field set/get callbacks
     - Ensure int field isn't updated from app when in focus
     - Ensure int field isn't updated from app when in focus

+ 1 - 0
Notes.txt

@@ -64,6 +64,7 @@ Reminders:
     which would require more thought.
     which would require more thought.
   - GUIElementBase::_getElementAreas is currently only implemented for layouts. It will work of child elements of layouts and layouts themselves but will not 
   - GUIElementBase::_getElementAreas is currently only implemented for layouts. It will work of child elements of layouts and layouts themselves but will not 
     work for child elements of custom element types. This method is used for calculating size and position of an element in its parent.
     work for child elements of custom element types. This method is used for calculating size and position of an element in its parent.
+  - I shouldn't use WeakRef with GameObjects. They need be deserialized in the order of their hierarchy and weak ref can break that.
 
 
 Potential optimizations:
 Potential optimizations:
  - bulkPixelConversion is EXTREMELY poorly unoptimized. Each pixel it calls a separate method that does redudant operations every pixel.
  - bulkPixelConversion is EXTREMELY poorly unoptimized. Each pixel it calls a separate method that does redudant operations every pixel.