Ver Fonte

WIP: UndoRedo refactor
- RecordSO & DeleteSO commands refactored to use SerializedSceneObject internally
- RecordSO command moved to C#

BearishSun há 6 anos atrás
pai
commit
0c3f07d747

+ 2 - 2
Source/EditorCore/CMakeSources.cmake

@@ -119,10 +119,10 @@ set(BS_BANSHEEEDITOR_INC_GUI
 set(BS_BANSHEEEDITOR_INC_UNDOREDO
 	"UndoRedo/BsEditorCommand.h"
 	"UndoRedo/BsCmdReparentSO.h"
-	"UndoRedo/BsCmdRecordSO.h"
 	"UndoRedo/BsCmdDeleteSO.h"
 	"UndoRedo/BsCmdCreateSO.h"
 	"UndoRedo/BsCmdCloneSO.h"
+	"UndoRedo/BsCmdRenameSO.h"
 	"UndoRedo/BsCmdInstantiateSO.h"
 	"UndoRedo/BsCmdBreakPrefab.h"
 	"UndoRedo/BsUndoRedo.h"
@@ -211,10 +211,10 @@ set(BS_BANSHEEEDITOR_SRC_SETTINGS
 set(BS_BANSHEEEDITOR_SRC_UNDOREDO
 	"UndoRedo/BsEditorCommand.cpp"
 	"UndoRedo/BsCmdReparentSO.cpp"
-	"UndoRedo/BsCmdRecordSO.cpp"
 	"UndoRedo/BsCmdDeleteSO.cpp"
 	"UndoRedo/BsCmdCreateSO.cpp"
 	"UndoRedo/BsCmdCloneSO.cpp"
+	"UndoRedo/BsCmdRenameSO.cpp"
 	"UndoRedo/BsCmdInstantiateSO.cpp"
 	"UndoRedo/BsCmdBreakPrefab.cpp"
 	"UndoRedo/BsUndoRedo.cpp"

+ 2 - 3
Source/EditorCore/GUI/BsGUISceneTreeView.cpp

@@ -4,8 +4,8 @@
 #include "Scene/BsSceneObject.h"
 #include "Scene/BsSceneManager.h"
 #include "GUI/BsGUISkin.h"
-#include "UndoRedo/BsCmdRecordSO.h"
 #include "UndoRedo/BsCmdReparentSO.h"
+#include "UndoRedo/BsCmdRenameSO.h"
 #include "UndoRedo/BsCmdDeleteSO.h"
 #include "UndoRedo/BsCmdCloneSO.h"
 #include "UndoRedo/BsCmdCreateSO.h"
@@ -273,8 +273,7 @@ namespace bs
 		SceneTreeElement* sceneTreeElement = static_cast<SceneTreeElement*>(element);
 
 		HSceneObject so = sceneTreeElement->mSceneObject;
-		CmdRecordSO::execute(so, false, "Renamed \"" + so->getName() + "\"");
-		so->setName(name);
+		CmdRenameSO::execute(so, name);
 
 		onModified();
 	}

+ 0 - 1
Source/EditorCore/Scene/BsSerializedSceneObject.cpp

@@ -67,7 +67,6 @@ namespace bs
 		if (mSerializedObjectParentId != 0)
 			parent = static_object_cast<SceneObject>(GameObjectManager::instance().getObject(mSerializedObjectParentId));
 
-
 		HSceneObject* children = nullptr;
 		UINT32 numChildren = 0;
 		if (!mSceneObject.isDestroyed())

+ 4 - 4
Source/EditorCore/Testing/BsEditorTestSuite.cpp

@@ -2,7 +2,6 @@
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "Testing/BsEditorTestSuite.h"
 #include "Scene/BsSceneObject.h"
-#include "UndoRedo/BsCmdRecordSO.h"
 #include "UndoRedo/BsCmdDeleteSO.h"
 #include "UndoRedo/BsUndoRedo.h"
 #include "Reflection/BsRTTIType.h"
@@ -15,6 +14,7 @@
 #include "Scene/BsPrefabDiff.h"
 #include "FileSystem/BsFileSystem.h"
 #include "Scene/BsSceneManager.h"
+#include "Scene/BsSerializedSceneObject.h"
 
 namespace bs
 {
@@ -439,10 +439,10 @@ namespace bs
 		cmpExternal->ref1 = so1_1;
 		cmpExternal->ref2 = static_object_cast<Component>(cmpA1_1);
 
-		CmdRecordSO::execute(so0_0);
+		auto serializedSO = bs_shared_ptr_new<SerializedSceneObject>(so0_0);
 		cmpB1_1->val1 = "ModifiedValue";
 		so0_0->setName("modified");
-		UndoRedo::instance().undo();
+		serializedSO->restore();
 
 		BS_TEST_ASSERT(!so0_0.isDestroyed());
 		BS_TEST_ASSERT(!so1_0.isDestroyed());
@@ -806,4 +806,4 @@ namespace bs
 		alloc.free(a13);
 		alloc.clear();
 	}
-}
+}

+ 4 - 62
Source/EditorCore/UndoRedo/BsCmdDeleteSO.cpp

@@ -1,10 +1,10 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "UndoRedo/BsCmdDeleteSO.h"
+#include "UndoRedo/BsUndoRedo.h"
 #include "Scene/BsSceneObject.h"
-#include "Scene/BsComponent.h"
+#include "Scene/BsSerializedSceneObject.h"
 #include "Serialization/BsMemorySerializer.h"
-#include "Utility/BsUtility.h"
 
 namespace bs
 {
@@ -12,24 +12,6 @@ namespace bs
 		: EditorCommand(description), mSceneObject(sceneObject)
 	{ }
 
-	CmdDeleteSO::~CmdDeleteSO()
-	{
-		mSceneObject = nullptr;
-		clear();
-	}
-
-	void CmdDeleteSO::clear()
-	{
-		mSerializedObjectSize = 0;
-		mSerializedObjectParentId = 0;
-
-		if (mSerializedObject != nullptr)
-		{
-			bs_free(mSerializedObject);
-			mSerializedObject = nullptr;
-		}
-	}
-
 	void CmdDeleteSO::execute(const HSceneObject& sceneObject, const String& description)
 	{
 		// Register command and commit it
@@ -42,55 +24,15 @@ namespace bs
 
 	void CmdDeleteSO::commit()
 	{
-		clear();
-
 		if (mSceneObject == nullptr || mSceneObject.isDestroyed())
 			return;
 
-		recordSO(mSceneObject);
+		mSerialized = bs_shared_ptr_new<SerializedSceneObject>(mSceneObject, true);
 		mSceneObject->destroy();
 	}
 
 	void CmdDeleteSO::revert()
 	{
-		HSceneObject parent;
-		if (mSerializedObjectParentId != 0)
-			parent = static_object_cast<SceneObject>(GameObjectManager::instance().getObject(mSerializedObjectParentId));
-
-		CoreSerializationContext serzContext;
-		serzContext.goState = bs_shared_ptr_new<GameObjectDeserializationState>(GODM_RestoreExternal | GODM_UseNewIds);
-
-		// Object might still only be queued for destruction, but we need to fully destroy it since we're about to replace
-		// the potentially only reference to the old object
-		if (!mSceneObject.isDestroyed())
-			mSceneObject->destroy(true);
-
-		MemorySerializer serializer;
-		SPtr<SceneObject> restored = std::static_pointer_cast<SceneObject>(
-			serializer.decode(mSerializedObject, mSerializedObjectSize, &serzContext));
-
-		EditorUtility::restoreIds(restored->getHandle(), mSceneObjectProxy);
-		restored->setParent(parent);
-
-		restored->_instantiate();
-		mSceneObject = restored->getHandle();
-	}
-
-	void CmdDeleteSO::recordSO(const HSceneObject& sceneObject)
-	{
-		bool isInstantiated = !sceneObject->hasFlag(SOF_DontInstantiate);
-		sceneObject->_setFlags(SOF_DontInstantiate);
-
-		MemorySerializer serializer;
-		mSerializedObject = serializer.encode(sceneObject.get(), mSerializedObjectSize);
-
-		if (isInstantiated)
-			sceneObject->_unsetFlags(SOF_DontInstantiate);
-
-		HSceneObject parent = sceneObject->getParent();
-		if (parent != nullptr)
-			mSerializedObjectParentId = parent->getInstanceId();
-
-		mSceneObjectProxy = EditorUtility::createProxy(sceneObject);
+		mSerialized->restore();
 	}
 }

+ 4 - 18
Source/EditorCore/UndoRedo/BsCmdDeleteSO.h

@@ -4,11 +4,12 @@
 
 #include "BsEditorPrerequisites.h"
 #include "UndoRedo/BsEditorCommand.h"
-#include "UndoRedo/BsUndoRedo.h"
 #include "Utility/BsEditorUtility.h"
 
 namespace bs
 {
+	class SerializedSceneObject;
+
 	/** @addtogroup UndoRedo
 	 *  @{
 	 */
@@ -17,8 +18,6 @@ namespace bs
 	class BS_ED_EXPORT CmdDeleteSO final : public EditorCommand
 	{
 	public:
-		~CmdDeleteSO();
-
 		/**
 		 * Creates and executes the command on the provided scene object. Automatically registers the command with 
 		 * undo/redo system.
@@ -39,22 +38,9 @@ namespace bs
 
 		CmdDeleteSO(const String& description, const HSceneObject& sceneObject);
 
-		/**
-		 * Saves the state of the specified object, all of its children and components. Make sure to call clear() when you
-		 * no longer need the data, or wish to call this method again.
-		 */
-		void recordSO(const HSceneObject& sceneObject);
-
-		/**	Clears all the stored data and frees memory. */
-		void clear();
-
 		HSceneObject mSceneObject;
-		EditorUtility::SceneObjProxy mSceneObjectProxy;
-
-		UINT8* mSerializedObject = nullptr;
-		UINT32 mSerializedObjectSize = 0;
-		UINT64 mSerializedObjectParentId = 0;
+		SPtr<SerializedSceneObject> mSerialized;
 	};
 
 	/** @} */
-}
+}

+ 0 - 135
Source/EditorCore/UndoRedo/BsCmdRecordSO.cpp

@@ -1,135 +0,0 @@
-//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
-//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
-#include "UndoRedo/BsCmdRecordSO.h"
-#include "Scene/BsSceneObject.h"
-#include "Scene/BsComponent.h"
-#include "Serialization/BsMemorySerializer.h"
-#include "Utility/BsUtility.h"
-
-namespace bs
-{
-	CmdRecordSO::CmdRecordSO(const String& description, const HSceneObject& sceneObject, bool recordHierarchy)
-		: EditorCommand(description), mSceneObject(sceneObject), mRecordHierarchy(recordHierarchy)
-		, mSerializedObject(nullptr), mSerializedObjectSize(0)
-	{
-
-	}
-
-	CmdRecordSO::~CmdRecordSO()
-	{
-		mSceneObject = nullptr;
-		clear();
-	}
-
-	void CmdRecordSO::clear()
-	{
-		mSerializedObjectSize = 0;
-
-		if (mSerializedObject != nullptr)
-		{
-			bs_free(mSerializedObject);
-			mSerializedObject = nullptr;
-		}
-	}
-
-	void CmdRecordSO::execute(const HSceneObject& sceneObject, bool recordHierarchy, const String& description)
-	{
-		// Register command and commit it
-		CmdRecordSO* command = new (bs_alloc<CmdRecordSO>()) CmdRecordSO(description, sceneObject, recordHierarchy);
-		SPtr<CmdRecordSO> commandPtr = bs_shared_ptr(command);
-
-		UndoRedo::instance().registerCommand(commandPtr);
-		commandPtr->commit();
-	}
-
-	void CmdRecordSO::commit()
-	{
-		clear();
-
-		if (mSceneObject == nullptr || mSceneObject.isDestroyed())
-			return;
-
-		recordSO(mSceneObject);
-	}
-
-	void CmdRecordSO::revert()
-	{
-		if (mSceneObject == nullptr || mSceneObject.isDestroyed())
-			return;
-
-		HSceneObject parent = mSceneObject->getParent();
-
-		UINT32 numChildren = mSceneObject->getNumChildren();
-		HSceneObject* children = nullptr;
-		if (!mRecordHierarchy)
-		{
-			children = bs_stack_new<HSceneObject>(numChildren);
-			for (UINT32 i = 0; i < numChildren; i++)
-			{
-				HSceneObject child = mSceneObject->getChild(i);
-				children[i] = child;
-
-				child->setParent(HSceneObject());
-			}
-		}
-
-		mSceneObject->destroy(true);
-
-		CoreSerializationContext serzContext;
-		serzContext.goState = bs_shared_ptr_new<GameObjectDeserializationState>(GODM_RestoreExternal | GODM_UseNewIds);
-
-		MemorySerializer serializer;
-		SPtr<SceneObject> restored = std::static_pointer_cast<SceneObject>(
-			serializer.decode(mSerializedObject, mSerializedObjectSize, &serzContext));
-
-		EditorUtility::restoreIds(restored->getHandle(), mSceneObjectProxy);
-		restored->setParent(parent);
-
-		if (!mRecordHierarchy)
-		{
-			for (UINT32 i = 0; i < numChildren; i++)
-				children[i]->setParent(restored->getHandle());
-
-			bs_stack_delete(children, numChildren);
-		}
-
-		restored->_instantiate();
-	}
-
-	void CmdRecordSO::recordSO(const HSceneObject& sceneObject)
-	{
-		UINT32 numChildren = mSceneObject->getNumChildren();
-		HSceneObject* children = nullptr;
-
-		if (!mRecordHierarchy)
-		{
-			children = bs_stack_new<HSceneObject>(numChildren);
-			for (UINT32 i = 0; i < numChildren; i++)
-			{
-				HSceneObject child = mSceneObject->getChild(i);
-				children[i] = child;
-
-				child->setParent(HSceneObject());
-			}
-		}
-
-		bool isInstantiated = !mSceneObject->hasFlag(SOF_DontInstantiate);
-		mSceneObject->_setFlags(SOF_DontInstantiate);
-
-		MemorySerializer serializer;
-		mSerializedObject = serializer.encode(mSceneObject.get(), mSerializedObjectSize);
-
-		if (isInstantiated)
-			mSceneObject->_unsetFlags(SOF_DontInstantiate);
-
-		mSceneObjectProxy = EditorUtility::createProxy(mSceneObject);
-
-		if (!mRecordHierarchy)
-		{
-			for (UINT32 i = 0; i < numChildren; i++)
-				children[i]->setParent(sceneObject->getHandle());
-
-			bs_stack_delete(children, numChildren);
-		}
-	}
-}

+ 0 - 65
Source/EditorCore/UndoRedo/BsCmdRecordSO.h

@@ -1,65 +0,0 @@
-//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
-//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
-#pragma once
-
-#include "BsEditorPrerequisites.h"
-#include "UndoRedo/BsEditorCommand.h"
-#include "UndoRedo/BsUndoRedo.h"
-#include "Utility/BsEditorUtility.h"
-
-namespace bs
-{
-	/** @addtogroup UndoRedo
-	 *  @{
-	 */
-
-	/**
-	 * A command used for undo/redo purposes. It records a state of the entire scene object at a specific point and allows
-	 * you to restore it to its original values as needed.
-	 */
-	class BS_ED_EXPORT CmdRecordSO final : public EditorCommand
-	{
-	public:
-		~CmdRecordSO();
-
-		/**
-		 * Creates and executes the command on the provided scene object. Automatically registers the command with undo/redo
-		 * system.
-		 *
-		 * @param[in]	sceneObject		Scene object to record.
-		 * @param[in]	recordHierarchy	If true, all children of the provided scene object will be recorded as well.
-		 * @param[in]	description		Optional description of what exactly the command does.
-		 */
-		static void execute(const HSceneObject& sceneObject, bool recordHierarchy = false, 
-			const String& description = StringUtil::BLANK);
-
-		/** @copydoc EditorCommand::commit */
-		void commit() override;
-
-		/** @copydoc EditorCommand::revert */
-		void revert() override;
-
-	private:
-		friend class UndoRedo;
-
-		CmdRecordSO(const String& description, const HSceneObject& sceneObject, bool recordHierarchy);
-
-		/**
-		 * Saves the state of the specified object, all of its children and components. Make sure to call clear() when you
-		 * no longer need the data, or wish to call this method again.
-		 */
-		void recordSO(const HSceneObject& sceneObject);
-
-		/**	Clears all the stored data and frees memory. */
-		void clear();
-
-		HSceneObject mSceneObject;
-		EditorUtility::SceneObjProxy mSceneObjectProxy;
-		bool mRecordHierarchy;
-
-		UINT8* mSerializedObject;
-		UINT32 mSerializedObjectSize;
-	};
-
-	/** @} */
-}

+ 41 - 0
Source/EditorCore/UndoRedo/BsCmdRenameSO.cpp

@@ -0,0 +1,41 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2019 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "UndoRedo/BsCmdRenameSO.h"
+#include "Scene/BsSceneObject.h"
+
+namespace bs
+{
+	CmdRenameSO::CmdRenameSO(const String& description, const HSceneObject& sceneObject, const String& newName)
+		:EditorCommand(description), mSceneObject(sceneObject), mNewName(newName)
+	{
+		if(!sceneObject.isDestroyed())
+			mOldName = sceneObject->getName();
+	}
+
+	void CmdRenameSO::execute(const HSceneObject& sceneObject, const String& newName)
+	{
+		String oldName;
+		if(!sceneObject.isDestroyed())
+			oldName = sceneObject->getName();
+
+		// Register command and commit it
+		CmdRenameSO* command = new (bs_alloc<CmdRenameSO>()) 
+			CmdRenameSO(StringUtil::format("Rename scene object '{0}' to '{1}'", oldName, newName), sceneObject, newName);
+		SPtr<CmdRenameSO> commandPtr = bs_shared_ptr(command);
+
+		UndoRedo::instance().registerCommand(commandPtr);
+		commandPtr->commit();
+	}
+
+	void CmdRenameSO::commit()
+	{
+		if (!mSceneObject.isDestroyed())
+			mSceneObject->setName(mNewName);
+	}
+
+	void CmdRenameSO::revert()
+	{
+		if (!mSceneObject.isDestroyed())
+			mSceneObject->setName(mOldName);
+	}
+}

+ 48 - 0
Source/EditorCore/UndoRedo/BsCmdRenameSO.h

@@ -0,0 +1,48 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2019 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "UndoRedo/BsEditorCommand.h"
+#include "UndoRedo/BsUndoRedo.h"
+
+namespace bs
+{
+	/** @addtogroup UndoRedo
+	 *  @{
+	 */
+
+	/**
+	 * A command used for undo/redo purposes. It records a scene object name change operations. It allows you to apply
+	 * the name change or revert the object to its original name as needed.
+	 */
+	class BS_ED_EXPORT CmdRenameSO final : public EditorCommand
+	{
+	public:
+		/**
+		 * Creates and executes the command on the provided scene object(s). Automatically registers the command with
+		 * undo/redo system.
+		 *
+		 * @param[in]	sceneObject		Object to rename.
+		 * @param[in]	newName			New name for the provided object.
+		 */
+		static void execute(const HSceneObject& sceneObjects, const String& newName);
+
+		/** @copydoc EditorCommand::commit */
+		void commit() override;
+
+		/** @copydoc EditorCommand::revert */
+		void revert() override;
+
+	private:
+		friend class UndoRedo;
+
+		CmdRenameSO(const String& description, const HSceneObject& sceneObject, const String& newName);
+
+		HSceneObject mSceneObject;
+		String mOldName;
+		String mNewName;
+	};
+
+	/** @} */
+}

+ 136 - 0
Source/EditorManaged/Utility/GameObjectUndo.cs

@@ -108,8 +108,51 @@ namespace bs.Editor
             }
         }
 
+        /// <summary>
+        /// Contains information about a scene object that needs its diff recorded. Unlike
+        /// <see cref="SceneObjectHeaderToRecord"/> this will record the entire scene object,
+        /// including its components and optionally the child hierarchy.
+        /// </summary>
+        private struct SceneObjectToRecord
+        {
+            private SceneObject obj;
+            private string description;
+            private SerializedSceneObject orgState;
+
+            /// <summary>
+            /// Creates a new object instance, recording the current state of the scene object.
+            /// </summary>
+            /// <param name="obj">Scene object to record the state of.</param>
+            /// <param name="hierarchy">If true, the child scene objects will be recorded as well.</param>
+            /// <param name="description">
+            /// Optional description that describes the change that is happening.
+            /// </param>
+            internal SceneObjectToRecord(SceneObject obj, bool hierarchy, string description)
+            {
+                this.obj = obj;
+                this.description = description;
+
+                orgState = new SerializedSceneObject(obj, hierarchy);
+            }
+
+            /// <summary>
+            /// Generates the diff from the previously recorded state and the current state. If there is a difference
+            /// an undo command is recorded.
+            /// </summary>
+            internal void RecordCommand()
+            {
+                if (obj.IsDestroyed)
+                    return;
+
+                var newState = new SerializedSceneObject(obj);
+                UndoRedo.Global.RegisterCommand(new RecordSceneObjectUndo(obj, orgState, newState, description));
+            }
+        }
+
+
         private static List<ComponentToRecord> components = new List<ComponentToRecord>();
         private static List<SceneObjectHeaderToRecord> sceneObjectHeaders = new List<SceneObjectHeaderToRecord>();
+        private static List<SceneObjectToRecord> sceneObjects = new List<SceneObjectToRecord>();
 
         /// <summary>
         /// Records the current state of the provided component, and generates a diff with the next state at the end of the
@@ -145,6 +188,25 @@ namespace bs.Editor
             sceneObjectHeaders.Add(so);
         }
 
+        /// <summary>
+        /// Records the current state of the provided scene object header, and generates a diff with the next state at the
+        /// end of the frame. If change is detected an undo operation will be recorded. Generally you want to call this
+        /// just before you are about to make a change to the scene object.
+        /// 
+        /// Note this records the complete state of a scene object, including its header, its components and optionally its
+        /// child hierarchy.
+        /// </summary>
+        /// <param name="obj">Scene object to record.</param>
+        /// <param name="hierarchy">
+        /// If true the child objects will be recorded as well, otherwise just the provided object.
+        /// </param>
+        /// <param name="description">Optional description specifying the type of changes about to be made.</param>
+        public static void RecordSceneObject(SceneObject obj, bool hierarchy, string description)
+        {
+            SceneObjectToRecord so = new SceneObjectToRecord(obj, hierarchy, description);
+            sceneObjects.Add(so);
+        }
+
         /// <summary>
         /// Generates diffs for any objects that were previously recorded using any of the Record* methods. The diff is
         /// generated by comparing the state at the time Record* was called, compared to the current object state.
@@ -157,8 +219,12 @@ namespace bs.Editor
             foreach (var entry in sceneObjectHeaders)
                 entry.RecordCommand();
 
+            foreach (var entry in sceneObjects)
+                entry.RecordCommand();
+
             components.Clear();
             sceneObjectHeaders.Clear();
+            sceneObjects.Clear();
         }
     }
 
@@ -369,6 +435,76 @@ namespace bs.Editor
         }
     }
 
+    /// <summary>
+    /// Stores the field changes in a <see cref="SceneObject"/> as a difference between two states. Allows those changes to
+    /// be reverted and re-applied. Unlike <see cref="bs.Editor.RecordSceneObjectHeaderUndo"/> this command records the
+    /// full scene object state, including changes to its components and optionally any child objects.
+    /// </summary>
+    [SerializeObject]
+    internal class RecordSceneObjectUndo : UndoableCommand
+    {
+        private SceneObject obj;
+        private SerializedSceneObject oldState;
+        private SerializedSceneObject newState;
+
+        private RecordSceneObjectUndo() { }
+
+        /// <summary>
+        /// Creates the new scene object undo command.
+        /// </summary>
+        /// <param name="obj">Scene object on which to apply the performed changes.</param>
+        /// <param name="oldState">Recorded original state of the scene object(s).</param>
+        /// <param name="newState">Recorded new state of the scene object(s).</param>
+        /// <param name="description">
+        /// Optional description of the changes made to the scene object between the two states.
+        /// </param>
+        public RecordSceneObjectUndo(SceneObject obj, SerializedSceneObject oldState, SerializedSceneObject newState,
+            string description)
+        {
+            this.obj = obj;
+            this.oldState = oldState;
+            this.newState = newState;
+        }
+
+        /// <inheritdoc/>
+        protected override void Commit()
+        {
+            newState?.Restore();
+
+            FocusOnObject();
+            RefreshInspector();
+        }
+
+        /// <inheritdoc/>
+        protected override void Revert()
+        {
+            oldState?.Restore();
+
+            FocusOnObject();
+            RefreshInspector();
+        }
+
+        /// <summary>
+        /// Selects the scene object if not already selected.
+        /// </summary>
+        private void FocusOnObject()
+        {
+            if (obj != null)
+            {
+                if (Selection.SceneObject != obj)
+                    Selection.SceneObject = obj;
+            }
+        }
+
+        /// <summary>
+        /// Updates the values of the fields displayed in the inspector window.
+        /// </summary>
+        private void RefreshInspector()
+        {
+            InspectorWindow inspectorWindow = EditorWindow.GetWindow<InspectorWindow>();
+            inspectorWindow?.RefreshSceneObjectFields(true);
+        }
+    }
 
     /// <summary>
     /// Stores the field changes in a <see cref="Component"/> as a difference between two states. Allows those changes to

+ 0 - 16
Source/EditorManaged/Utility/UndoRedo.cs

@@ -117,19 +117,6 @@ namespace bs.Editor
             Internal_Clear(mCachedPtr);
         }
 
-        /// <summary>
-        /// Records a state of the entire scene object at a specific point and allows you to restore it to its original 
-        /// values as needed. Undo operation recorded in global undo/redo stack.
-        /// </summary>
-        /// <param name="so">Scene object to record.</param>
-        /// <param name="recordHierarchy">If true all children of this object will also be recorded.</param>
-        /// <param name="description">Optional description of what exactly the command does.</param>
-        public static void RecordSO(SceneObject so, bool recordHierarchy = false, string description = "")
-        {
-            if (so != null)
-                Internal_RecordSO(so.GetCachedPtr(), recordHierarchy, description);
-        }
-
         /// <summary>
         /// Creates new scene object(s) by cloning existing objects. Undo operation recorded in global undo/redo stack.
         /// </summary>
@@ -299,9 +286,6 @@ namespace bs.Editor
         [MethodImpl(MethodImplOptions.InternalCall)]
         internal static extern int Internal_GetTopCommandId(IntPtr thisPtr);
 
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        internal static extern void Internal_RecordSO(IntPtr soPtr, bool recordHierarchy, string description);
-
         [MethodImpl(MethodImplOptions.InternalCall)]
         internal static extern SceneObject Internal_CloneSO(IntPtr soPtr, string description);
 

+ 85 - 30
Source/EditorManaged/Window/MenuItems.cs

@@ -24,10 +24,11 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added a Camera component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a Camera component");
             Camera cam = so.AddComponent<Camera>();
             cam.Main = true;
 
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -41,8 +42,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added a Renderable component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a Renderable component");
             so.AddComponent<Renderable>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -56,8 +59,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added a Particle System component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a Particle System component");
             so.AddComponent<ParticleSystem>();
+            
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -71,8 +76,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added a Decal component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a Decal component");
             so.AddComponent<Decal>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -86,9 +93,11 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added a Light component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a Light component");
             Light light = so.AddComponent<Light>();
             light.Type = LightType.Radial;
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -102,9 +111,11 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added a Light component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a Light component");
             Light light = so.AddComponent<Light>();
             light.Type = LightType.Spot;
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -118,9 +129,11 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added a Light component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a Light component");
             Light light = so.AddComponent<Light>();
             light.Type = LightType.Directional;
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -134,8 +147,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added an Skybox component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added an Skybox component");
             so.AddComponent<Skybox>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -149,8 +164,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added an ReflectionProbe component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added an ReflectionProbe component");
             so.AddComponent<ReflectionProbe>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -164,8 +181,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added a Light Probe Volume component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a Light Probe Volume component");
             so.AddComponent<LightProbeVolume>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -179,8 +198,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added a GUIWidget component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a GUIWidget component");
             so.AddComponent<GUIWidget>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -199,9 +220,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a BoxCollider component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a BoxCollider component");
             so.AddComponent<BoxCollider>();
 
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -220,8 +242,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a SphereCollider component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a SphereCollider component");
             so.AddComponent<SphereCollider>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -240,8 +264,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a CapsuleCollider component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a CapsuleCollider component");
             so.AddComponent<CapsuleCollider>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -260,8 +286,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a MeshCollider component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a MeshCollider component");
             so.AddComponent<MeshCollider>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -280,8 +308,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a PlaneCollider component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a PlaneCollider component");
             so.AddComponent<PlaneCollider>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -300,8 +330,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a Rigidbody component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a Rigidbody component");
             so.AddComponent<Rigidbody>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -320,8 +352,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a CharacterController component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a CharacterController component");
             so.AddComponent<CharacterController>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -340,8 +374,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a FixedJoint component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a FixedJoint component");
             so.AddComponent<FixedJoint>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -360,8 +396,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a DistanceJoint component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a DistanceJoint component");
             so.AddComponent<DistanceJoint>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -380,8 +418,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a HingeJoint component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a HingeJoint component");
             so.AddComponent<HingeJoint>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -400,8 +440,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a SphericalJoint component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a SphericalJoint component");
             so.AddComponent<SphericalJoint>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -420,8 +462,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a SliderJoint component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a SliderJoint component");
             so.AddComponent<SliderJoint>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -440,8 +484,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a D6Joint component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a D6Joint component");
             so.AddComponent<D6Joint>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -460,8 +506,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a AudioListener component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a AudioListener component");
             so.AddComponent<AudioListener>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -480,8 +528,10 @@ namespace bs.Editor
                 FocusOnHierarchyOrScene();
             }
 
-            UndoRedo.RecordSO(so, false, "Added a AudioSource component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added a AudioSource component");
             so.AddComponent<AudioSource>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -495,8 +545,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added an Animation component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added an Animation component");
             so.AddComponent<Animation>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -511,8 +563,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, false, "Added an Bone component");
+            GameObjectUndo.RecordSceneObject(so, false, "Added an Bone component");
             so.AddComponent<Bone>();
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 
@@ -754,9 +808,10 @@ namespace bs.Editor
             if (so == null)
                 return;
 
-            UndoRedo.RecordSO(so, true, "Reverting \"" + so.Name + "\" to prefab.");
-
+            GameObjectUndo.RecordSceneObject(so, true, "Reverting \"" + so.Name + "\" to prefab.");
             PrefabUtility.RevertPrefab(so);
+
+            GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
         }
 

+ 3 - 2
Source/EditorManaged/Windows/Inspector/InspectorWindow.cs

@@ -372,9 +372,10 @@ namespace bs.Editor
                     };
                     btnRevertPrefab.OnClick += () =>
                     {
-                        UndoRedo.RecordSO(activeSO, true, "Reverting \"" + activeSO.Name + "\" to prefab.");
-
+                        GameObjectUndo.RecordSceneObject(activeSO, true, "Reverting \"" + activeSO.Name + "\" to prefab.");
                         PrefabUtility.RevertPrefab(activeSO);
+
+                        GameObjectUndo.ResolveDiffs();
                         EditorApplication.SetSceneDirty();
                     };
                     btnBreakPrefab.OnClick += () =>

+ 0 - 8
Source/EditorScript/Wrappers/BsScriptUndoRedo.cpp

@@ -8,7 +8,6 @@
 #include "BsMonoUtil.h"
 #include "BsScriptGameObjectManager.h"
 #include "UndoRedo/BsUndoRedo.h"
-#include "UndoRedo/BsCmdRecordSO.h"
 #include "UndoRedo/BsCmdCloneSO.h"
 #include "UndoRedo/BsCmdCreateSO.h"
 #include "UndoRedo/BsCmdDeleteSO.h"
@@ -40,7 +39,6 @@ namespace bs
 		metaData.scriptClass->addInternalCall("Internal_Clear", (void*)&ScriptUndoRedo::internal_Clear);
 		metaData.scriptClass->addInternalCall("Internal_GetTopCommandId", (void*)&ScriptUndoRedo::internal_GetTopCommandId);
 		metaData.scriptClass->addInternalCall("Internal_PopCommand", (void*)&ScriptUndoRedo::internal_PopCommand);
-		metaData.scriptClass->addInternalCall("Internal_RecordSO", (void*)&ScriptUndoRedo::internal_RecordSO);
 		metaData.scriptClass->addInternalCall("Internal_CloneSO", (void*)&ScriptUndoRedo::internal_CloneSO);
 		metaData.scriptClass->addInternalCall("Internal_CloneSOMulti", (void*)&ScriptUndoRedo::internal_CloneSOMulti);
 		metaData.scriptClass->addInternalCall("Internal_Instantiate", (void*)&ScriptUndoRedo::internal_Instantiate);
@@ -149,12 +147,6 @@ namespace bs
 		undoRedo->popCommand(id);
 	}
 
-	void ScriptUndoRedo::internal_RecordSO(ScriptSceneObject* soPtr, bool recordHierarchy, MonoString* description)
-	{
-		String nativeDescription = MonoUtil::monoToString(description);
-		CmdRecordSO::execute(soPtr->getHandle(), recordHierarchy, nativeDescription);
-	}
-
 	MonoObject* ScriptUndoRedo::internal_CloneSO(ScriptSceneObject* soPtr, MonoString* description)
 	{
 		String nativeDescription = MonoUtil::monoToString(description);

+ 0 - 1
Source/EditorScript/Wrappers/BsScriptUndoRedo.h

@@ -49,7 +49,6 @@ namespace bs
 		static void internal_Clear(ScriptUndoRedo* thisPtr);
 		static UINT32 internal_GetTopCommandId(ScriptUndoRedo* thisPtr);
 		static void internal_PopCommand(ScriptUndoRedo* thisPtr, UINT32 id);
-		static void internal_RecordSO(ScriptSceneObject* soPtr, bool recordHierarchy, MonoString* description);
 		static MonoObject* internal_CloneSO(ScriptSceneObject* soPtr, MonoString* description);
 		static MonoArray* internal_CloneSOMulti(MonoArray* soPtrs, MonoString* description);
 		static MonoObject* internal_Instantiate(ScriptPrefab* prefabPtr, MonoString* description);