Browse Source

WIP: UndoRedo refactor
- Added a new command for recording a newly created scene object, along with its initial state
- CreateSO and DeleteSO now focus on the scene object they are operating on
- All menu-item added scene objects are now recorded as a single undo/redo command, and properly record added components as well as changes to their individual fields immediately following the creation

BearishSun 6 years ago
parent
commit
f0de730b0e

+ 24 - 0
Source/EditorCore/UndoRedo/BsCmdCreateSO.cpp

@@ -2,6 +2,7 @@
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "UndoRedo/BsCmdCreateSO.h"
 #include "Scene/BsSceneObject.h"
+#include "Scene/BsSelection.h"
 
 namespace bs
 {
@@ -9,6 +10,11 @@ namespace bs
 		:EditorCommand(description), mName(name), mFlags(flags)
 	{ }
 
+	CmdCreateSO::CmdCreateSO(const String& description, const String& name, const Vector<UINT32>& componentTypeIds, 
+		UINT32 flags)
+		:EditorCommand(description), mName(name), mFlags(flags), mComponentTypeIds(componentTypeIds)
+	{ }
+
 	HSceneObject CmdCreateSO::execute(const String& name, UINT32 flags, const String& description)
 	{
 		// Register command and commit it
@@ -21,9 +27,27 @@ namespace bs
 		return commandPtr->mSceneObject;
 	}
 
+	HSceneObject CmdCreateSO::execute(const String& name, UINT32 flags, const Vector<UINT32>& componentTypeIds,
+		const String& description)
+	{
+		// Register command and commit it
+		CmdCreateSO* command = new (bs_alloc<CmdCreateSO>()) CmdCreateSO(description, name, componentTypeIds, flags);
+		SPtr<CmdCreateSO> commandPtr = bs_shared_ptr(command);
+
+		UndoRedo::instance().registerCommand(commandPtr);
+		commandPtr->commit();
+
+		return commandPtr->mSceneObject;
+	}
+
 	void CmdCreateSO::commit()
 	{
 		mSceneObject = SceneObject::create(mName, mFlags);
+
+		for(auto entry : mComponentTypeIds)
+			mSceneObject->addComponent(entry);
+
+		Selection::instance().setSceneObjects({ mSceneObject });
 	}
 
 	void CmdCreateSO::revert()

+ 15 - 0
Source/EditorCore/UndoRedo/BsCmdCreateSO.h

@@ -26,6 +26,19 @@ namespace bs
 		 */
 		static HSceneObject execute(const String& name, UINT32 flags, const String& description = StringUtil::BLANK);
 
+		/**
+		 * Creates a new scene object with an initial set of components. Automatically registers the command with undo/redo 
+		 * system.
+		 *
+		 * @param[in]	name				Name of the scene object.
+		 * @param[in]	flags				Optional creation flags for the scene object.
+		 * @param[in]	componentTypeIds	A list of type ID's of the components to add to the scene object.
+		 * @param[in]	description			Optional description of what exactly the command does.
+		 * @return							Newly created scene object.
+		 */
+		static HSceneObject execute(const String& name, UINT32 flags, const Vector<UINT32>& componentTypeIds, 
+			const String& description = StringUtil::BLANK);
+
 		/** @copydoc EditorCommand::commit */
 		void commit() override;
 
@@ -36,11 +49,13 @@ namespace bs
 		friend class UndoRedo;
 
 		CmdCreateSO(const String& description, const String& name, UINT32 flags);
+		CmdCreateSO(const String& description, const String& name, const Vector<UINT32>& componentTypeIds, UINT32 flags);
 
 		String mName;
 		UINT32 mFlags;
 
 		HSceneObject mSceneObject;
+		Vector<UINT32> mComponentTypeIds;
 	};
 
 	/** @} */

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

@@ -5,6 +5,7 @@
 #include "Scene/BsSceneObject.h"
 #include "Scene/BsSerializedSceneObject.h"
 #include "Serialization/BsMemorySerializer.h"
+#include "Scene/BsSelection.h"
 
 namespace bs
 {
@@ -34,5 +35,8 @@ namespace bs
 	void CmdDeleteSO::revert()
 	{
 		mSerialized->restore();
+
+		if(!mSceneObject.isDestroyed(true))
+			Selection::instance().setSceneObjects({ mSceneObject });
 	}
 }

+ 4 - 1
Source/EditorManaged/GUI/GUISceneTreeView.cs

@@ -138,11 +138,14 @@ namespace bs.Editor
                         if (mesh == null)
                             continue;
 
-                        SceneObject so = UndoRedo.CreateSO(meshName, "Created a new Renderable \"" + meshName + "\"");
+                        SceneObject so = new SceneObject(meshName);
+
+                        GameObjectUndo.RecordNewSceneObject(so);
                         so.Parent = parent;
 
                         Renderable renderable = so.AddComponent<Renderable>();
                         renderable.Mesh = mesh;
+                        GameObjectUndo.ResolveDiffs();
 
                         addedObjects.Add(so);
                     }

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

@@ -181,9 +181,40 @@ namespace bs.Editor
             }
         }
 
+
+        /// <summary>
+        /// Contains information about a newly created scene object and its state immediately following the creation.
+        /// </summary>
+        private struct NewSceneObjectToRecord
+        {
+            private SceneObject obj;
+
+            /// <summary>
+            /// Creates a new object instance, recording that a new scene object has been created.
+            /// </summary>
+            /// <param name="obj">Newly created scene object.</param>
+            internal NewSceneObjectToRecord(SceneObject obj)
+            {
+                this.obj = obj;
+            }
+
+            /// <summary>
+            /// Records the current scene object state.
+            /// </summary>
+            internal void RecordCommand()
+            {
+                if (obj.IsDestroyed)
+                    return;
+
+                var state = new SerializedSceneObject(obj);
+                UndoRedo.Global.RegisterCommand(new NewSceneObjectUndo(obj, state));
+            }
+        }
+
         private static List<ComponentToRecord> components = new List<ComponentToRecord>();
         private static List<SceneObjectHeaderToRecord> sceneObjectHeaders = new List<SceneObjectHeaderToRecord>();
         private static List<SceneObjectToRecord> sceneObjects = new List<SceneObjectToRecord>();
+        private static List<NewSceneObjectToRecord> newSceneObjects = new List<NewSceneObjectToRecord>();
 
         /// <summary>
         /// Records the current state of the provided component, and generates a diff with the next state at the end of the
@@ -253,6 +284,24 @@ namespace bs.Editor
             sceneObjects.Add(so);
         }
 
+        /// <summary>
+        /// Creates a brand new scene object and begins <see cref="RecordSceneObject(SceneObject,bool,string)"/> operation
+        /// on the newly created scene object. Both the creation and the initially recorded set of data will be recorded
+        /// as a single undo/redo command. Generally you want to call this when creating a new scene object and immediately
+        /// make some initial changes to it (such as adding component or modifying their properties).
+        /// </summary>
+        /// <param name="obj">Newly created scene object on which to initiate the record operation on.</param>
+        public static void RecordNewSceneObject(SceneObject obj)
+        {
+            if (obj == null || obj.IsDestroyed)
+                return;
+
+            NewSceneObjectToRecord record = new NewSceneObjectToRecord(obj);
+            newSceneObjects.Add(record);
+
+            Selection.SceneObject = obj;
+        }
+
         /// <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.
@@ -268,9 +317,13 @@ namespace bs.Editor
             foreach (var entry in sceneObjects)
                 entry.RecordCommand();
 
+            foreach (var entry in newSceneObjects)
+                entry.RecordCommand();
+
             components.Clear();
             sceneObjectHeaders.Clear();
             sceneObjects.Clear();
+            newSceneObjects.Clear();
         }
     }
 
@@ -515,6 +568,69 @@ namespace bs.Editor
         }
     }
 
+    /// <summary>
+    /// Records the creation of a new <see cref="SceneObject"/> and its recorded state immediately following its creation.
+    /// Undo will destroy the created object, while redo will restore the object with the recorded state.
+    /// </summary>
+    [SerializeObject]
+    internal class NewSceneObjectUndo : UndoableCommand
+    {
+        private SceneObject obj;
+        private SerializedSceneObject state;
+
+        private NewSceneObjectUndo() { }
+
+        /// <summary>
+        /// Creates the new scene object undo command.
+        /// </summary>
+        /// <param name="obj">Newly created scene object on which to apply the command.</param>
+        /// <param name="state">Recorded state of the scene object.</param>
+        public NewSceneObjectUndo(SceneObject obj, SerializedSceneObject state)
+        {
+            this.obj = obj;
+            this.state = state;
+        }
+
+        /// <inheritdoc/>
+        protected override void Commit()
+        {
+            state?.Restore();
+
+            if(obj != null && !obj.IsDestroyed)
+                Selection.SceneObject = obj;
+
+            FocusOnObject();
+            RefreshInspector();
+        }
+
+        /// <inheritdoc/>
+        protected override void Revert()
+        {
+            obj.Destroy(true);
+        }
+
+        /// <summary>
+        /// Selects the scene object if not already selected.
+        /// </summary>
+        private void FocusOnObject()
+        {
+            if (obj != null && !obj.IsDestroyed)
+            {
+                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="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

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

@@ -179,6 +179,18 @@ namespace bs.Editor
             return Internal_CreateSO(name, description);
         }
 
+        /// <summary>
+        /// Creates a new scene object with a set of initial components. Undo operation recorded in global undo/redo stack.
+        /// </summary>
+        /// <param name="name">Name of the scene object.</param>
+        /// <param name="description">Optional description of what exactly the command does.</param>
+        /// <param name="componentTypes">Optional set of components that will be added to the scene object.</param>
+        /// <returns>Newly created scene object.</returns>
+        public static SceneObject CreateSO(string name, string description = "", params Type[] componentTypes)
+        {
+            return Internal_CreateSO2(name, componentTypes, description);
+        }
+
         /// <summary>
         /// Deletes a scene object. Undo operation recorded in global undo/redo stack.
         /// </summary>
@@ -298,6 +310,9 @@ namespace bs.Editor
         [MethodImpl(MethodImplOptions.InternalCall)]
         internal static extern SceneObject Internal_CreateSO(string name, string description);
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        internal static extern SceneObject Internal_CreateSO2(string name, Type[] componentTypes, string description);
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         internal static extern void Internal_DeleteSO(IntPtr soPtr, string description);
 

+ 189 - 103
Source/EditorManaged/Window/MenuItems.cs

@@ -214,14 +214,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             {
-                so = UndoRedo.CreateSO("BoxCollider", "New scene object");
+                so = new SceneObject("BoxCollider");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<BoxCollider>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a BoxCollider component");
-            so.AddComponent<BoxCollider>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a BoxCollider component");
+                so.AddComponent<BoxCollider>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -236,14 +240,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("SphereCollider", "New scene object");
+                so = new SceneObject("SphereCollider");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<SphereCollider>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a SphereCollider component");
-            so.AddComponent<SphereCollider>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a SphereCollider component");
+                so.AddComponent<SphereCollider>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -258,14 +266,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("CapsuleCollider", "New scene object");
+                so = new SceneObject("CapsuleCollider");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<CapsuleCollider>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a CapsuleCollider component");
-            so.AddComponent<CapsuleCollider>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a CapsuleCollider component");
+                so.AddComponent<CapsuleCollider>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -280,14 +292,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("MeshCollider", "New scene object");
+                so = new SceneObject("MeshCollider");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<MeshCollider>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a MeshCollider component");
-            so.AddComponent<MeshCollider>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a MeshCollider component");
+                so.AddComponent<MeshCollider>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -302,14 +318,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("PlaneCollider", "New scene object");
+                so = new SceneObject("PlaneCollider");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<PlaneCollider>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a PlaneCollider component");
-            so.AddComponent<PlaneCollider>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a PlaneCollider component");
+                so.AddComponent<PlaneCollider>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -324,14 +344,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             {
-                so = UndoRedo.CreateSO("Rigidbody", "New scene object");
+                so = new SceneObject("Rigidbody");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<Rigidbody>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a Rigidbody component");
-            so.AddComponent<Rigidbody>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a Rigidbody component");
+                so.AddComponent<Rigidbody>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -346,14 +370,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("CharacterController", "New scene object");
+                so = new SceneObject("CharacterController");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<CharacterController>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a CharacterController component");
-            so.AddComponent<CharacterController>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a CharacterController component");
+                so.AddComponent<CharacterController>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -368,14 +396,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("FixedJoint", "New scene object");
+                so = new SceneObject("FixedJoint");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<FixedJoint>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a FixedJoint component");
-            so.AddComponent<FixedJoint>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a FixedJoint component");
+                so.AddComponent<FixedJoint>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -390,14 +422,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("DistanceJoint", "New scene object");
+                so = new SceneObject("DistanceJoint");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<DistanceJoint>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a DistanceJoint component");
-            so.AddComponent<DistanceJoint>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a DistanceJoint component");
+                so.AddComponent<DistanceJoint>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -412,14 +448,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("HingeJoint", "New scene object");
+                so = new SceneObject("HingeJoint");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<HingeJoint>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a HingeJoint component");
-            so.AddComponent<HingeJoint>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a HingeJoint component");
+                so.AddComponent<HingeJoint>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -434,14 +474,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("SphericalJoint", "New scene object");
+                so = new SceneObject("SphericalJoint");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<SphericalJoint>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a SphericalJoint component");
-            so.AddComponent<SphericalJoint>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a SphericalJoint component");
+                so.AddComponent<SphericalJoint>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -456,14 +500,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("SliderJoint", "New scene object");
+                so = new SceneObject("SliderJoint");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<SliderJoint>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a SliderJoint component");
-            so.AddComponent<SliderJoint>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a SliderJoint component");
+                so.AddComponent<SliderJoint>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -478,14 +526,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             { 
-                so = UndoRedo.CreateSO("D6Joint", "New scene object");
+                so = new SceneObject("D6Joint");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<D6Joint>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a D6Joint component");
-            so.AddComponent<D6Joint>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a D6Joint component");
+                so.AddComponent<D6Joint>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -500,14 +552,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             {
-                so = UndoRedo.CreateSO("AudioListener", "New scene object");
+                so = new SceneObject("AudioListener");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<AudioListener>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a AudioListener component");
-            so.AddComponent<AudioListener>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a AudioListener component");
+                so.AddComponent<AudioListener>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -522,14 +578,18 @@ namespace bs.Editor
             SceneObject so = Selection.SceneObject;
             if (so == null)
             {
-                so = UndoRedo.CreateSO("AudioSource", "New scene object");
+                so = new SceneObject("AudioSource");
+
+                GameObjectUndo.RecordNewSceneObject(so);
+                so.AddComponent<AudioSource>();
 
-                Selection.SceneObject = so;
                 FocusOnHierarchyOrScene();
             }
-
-            GameObjectUndo.RecordSceneObject(so, false, "Added a AudioSource component");
-            so.AddComponent<AudioSource>();
+            else
+            {
+                GameObjectUndo.RecordSceneObject(so, false, "Added a AudioSource component");
+                so.AddComponent<AudioSource>();
+            }
 
             GameObjectUndo.ResolveDiffs();
             EditorApplication.SetSceneDirty();
@@ -578,9 +638,8 @@ namespace bs.Editor
         [ToolbarItem("SceneObject", ToolbarIcon.NewSceneObject, "Creates a new empty scene object", 1601, true)]
         private static void AddEmptySO()
         {
-            SceneObject so = UndoRedo.CreateSO("SceneObject", "New scene object");
+            UndoRedo.CreateSO("SceneObject", "New scene object");
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -592,11 +651,14 @@ namespace bs.Editor
         [ToolbarItem("Camera", ToolbarIcon.NewCamera, "New camera", 1600, false)]
         private static void AddCameraSO()
         {
-            SceneObject so = UndoRedo.CreateSO("Camera", "Created a Camera");
+            SceneObject so = new SceneObject("Camera");
+            GameObjectUndo.RecordNewSceneObject(so);
+
             Camera cam = so.AddComponent<Camera>();
             cam.Main = true;
 
-            Selection.SceneObject = so;
+            GameObjectUndo.ResolveDiffs();
+
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -608,10 +670,12 @@ namespace bs.Editor
         [ToolbarItem("Renderable", ToolbarIcon.NewRenderable, "New renderable", 1599)]
         private static void AddRenderableSO()
         {
-            SceneObject so = UndoRedo.CreateSO("Renderable", "Created a Renderable");
+            SceneObject so = new SceneObject("Renderable");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             so.AddComponent<Renderable>();
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -622,10 +686,12 @@ namespace bs.Editor
         [MenuItem("Scene Objects/Particle system", 8948)]
         private static void AddParticleSystemSO()
         {
-            SceneObject so = UndoRedo.CreateSO("Particle system", "Created a ParticleSystem");
+            SceneObject so = new SceneObject("Particle system");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             so.AddComponent<ParticleSystem>();
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -636,10 +702,12 @@ namespace bs.Editor
         [MenuItem("Scene Objects/Decal", 8947)]
         private static void AddDecalSO()
         {
-            SceneObject so = UndoRedo.CreateSO("Decal", "Created a Decal");
+            SceneObject so = new SceneObject("Decal");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             so.AddComponent<Decal>();
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -651,11 +719,13 @@ namespace bs.Editor
         [ToolbarItem("Point light", ToolbarIcon.NewPointLight, "New radial light", 1598)]
         private static void AddPointLightSO()
         {
-            SceneObject so = UndoRedo.CreateSO("Radial light", "Created a Light");
+            SceneObject so = new SceneObject("Radial light");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             Light light = so.AddComponent<Light>();
             light.Type = LightType.Radial;
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -667,11 +737,13 @@ namespace bs.Editor
         [ToolbarItem("Spot light", ToolbarIcon.NewSpotLight, "New spot light", 1597)]
         private static void AddSpotLightSO()
         {
-            SceneObject so = UndoRedo.CreateSO("Spot light", "Created a Light");
+            SceneObject so = new SceneObject("Spot light");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             Light light = so.AddComponent<Light>();
             light.Type = LightType.Spot;
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -683,11 +755,13 @@ namespace bs.Editor
         [ToolbarItem("Directional light", ToolbarIcon.NewDirLight, "New directional light", 1596)]
         private static void AddDirectionalLightSO()
         {
-            SceneObject so = UndoRedo.CreateSO("Directional light", "Created a Light");
+            SceneObject so = new SceneObject("Directional light");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             Light light = so.AddComponent<Light>();
             light.Type = LightType.Directional;
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -698,10 +772,12 @@ namespace bs.Editor
         [MenuItem("Scene Objects/GUI widget", 8850, true)]
         private static void AddGUIWidgetSO()
         {
-            SceneObject so = UndoRedo.CreateSO("GUIWidget", "Created a GUIWidget");
+            SceneObject so = new SceneObject("GUI widget");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             so.AddComponent<GUIWidget>();
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -713,11 +789,13 @@ namespace bs.Editor
         [ToolbarItem("Cube", ToolbarIcon.NewCube, "Creates a scene object with a box primitive", 1700, true)]
         private static void Add3DBox()
         {
-            SceneObject so = UndoRedo.CreateSO("Box", "Created a box");
+            SceneObject so = new SceneObject("Box");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             Renderable renderable = so.AddComponent<Renderable>();
             renderable.Mesh = Builtin.Box;
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -729,11 +807,13 @@ namespace bs.Editor
         [ToolbarItem("Sphere", ToolbarIcon.NewSphere, "Creates a scene object with a sphere primitive", 1699)]
         private static void Add3DSphere()
         {
-            SceneObject so = UndoRedo.CreateSO("Sphere", "Created a sphere");
+            SceneObject so = new SceneObject("Sphere");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             Renderable renderable = so.AddComponent<Renderable>();
             renderable.Mesh = Builtin.Sphere;
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -745,11 +825,13 @@ namespace bs.Editor
         [ToolbarItem("Cone", ToolbarIcon.NewCone, "Creates a scene object with a cone primitive", 1698)]
         private static void Add3DCone()
         {
-            SceneObject so = UndoRedo.CreateSO("Cone", "Created a cone");
+            SceneObject so = new SceneObject("Cone");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             Renderable renderable = so.AddComponent<Renderable>();
             renderable.Mesh = Builtin.Cone;
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -761,11 +843,13 @@ namespace bs.Editor
         [ToolbarItem("Quad", ToolbarIcon.NewQuad, "Creates a scene object with a quad primitive", 1697)]
         private static void Add3DQuad()
         {
-            SceneObject so = UndoRedo.CreateSO("Quad", "Created a quad");
+            SceneObject so = new SceneObject("Quad");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             Renderable renderable = so.AddComponent<Renderable>();
             renderable.Mesh = Builtin.Quad;
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }
@@ -776,11 +860,13 @@ namespace bs.Editor
         [MenuItem("Scene Objects/3D primitives/Disc", 8096)]
         private static void Add3DDisc()
         {
-            SceneObject so = UndoRedo.CreateSO("Disc", "Created a disc");
+            SceneObject so = new SceneObject("Disc");
+
+            GameObjectUndo.RecordNewSceneObject(so);
             Renderable renderable = so.AddComponent<Renderable>();
             renderable.Mesh = Builtin.Disc;
+            GameObjectUndo.ResolveDiffs();
 
-            Selection.SceneObject = so;
             FocusOnHierarchyOrScene();
             EditorApplication.SetSceneDirty();
         }

+ 6 - 2
Source/EditorManaged/Windows/Scene/SceneWindow.cs

@@ -600,12 +600,16 @@ namespace bs.Editor
                                 {
                                     if (!string.IsNullOrEmpty(draggedPaths[i]))
                                     {
-                                        string meshName = Path.GetFileNameWithoutExtension(draggedPaths[i]);
-                                        draggedSO = UndoRedo.CreateSO(meshName, "Created a new Renderable \"" + meshName + "\"");
                                         Mesh mesh = ProjectLibrary.Load<Mesh>(draggedPaths[i]);
 
+                                        string meshName = Path.GetFileNameWithoutExtension(draggedPaths[i]);
+                                        draggedSO = new SceneObject(meshName);
+
+                                        GameObjectUndo.RecordNewSceneObject(draggedSO);
                                         Renderable renderable = draggedSO.AddComponent<Renderable>();
                                         renderable.Mesh = mesh;
+                                        GameObjectUndo.ResolveDiffs();
+
                                         if (mesh != null)
                                             draggedSOOffset = mesh.Bounds.Box.Center;
                                         else

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

@@ -43,6 +43,7 @@ namespace bs
 		metaData.scriptClass->addInternalCall("Internal_CloneSOMulti", (void*)&ScriptUndoRedo::internal_CloneSOMulti);
 		metaData.scriptClass->addInternalCall("Internal_Instantiate", (void*)&ScriptUndoRedo::internal_Instantiate);
 		metaData.scriptClass->addInternalCall("Internal_CreateSO", (void*)&ScriptUndoRedo::internal_CreateSO);
+		metaData.scriptClass->addInternalCall("Internal_CreateSO2", (void*)&ScriptUndoRedo::internal_CreateSO2);
 		metaData.scriptClass->addInternalCall("Internal_DeleteSO", (void*)&ScriptUndoRedo::internal_DeleteSO);
 		metaData.scriptClass->addInternalCall("Internal_ReparentSO", (void*)&ScriptUndoRedo::internal_ReparentSO);
 		metaData.scriptClass->addInternalCall("Internal_ReparentSOMulti", (void*)&ScriptUndoRedo::internal_ReparentSOMulti);
@@ -197,6 +198,46 @@ namespace bs
 		return ScriptGameObjectManager::instance().createScriptSceneObject(newObj)->getManagedInstance();
 	}
 
+	MonoObject* ScriptUndoRedo::internal_CreateSO2(MonoString* name, MonoArray* types, MonoString* description)
+	{
+		String nativeName = MonoUtil::monoToString(name);
+		String nativeDescription = MonoUtil::monoToString(description);
+
+		ScriptAssemblyManager& sam = ScriptAssemblyManager::instance();
+		MonoClass* managedComponent = sam.getBuiltinClasses().managedComponentClass;
+
+		ScriptArray scriptArray(types);
+		Vector<UINT32> typeIds;
+		for (UINT32 i = 0; i < scriptArray.size(); i++)
+		{
+			MonoReflectionType* paramType = scriptArray.get<MonoReflectionType*>(i);
+			if(!paramType)
+				continue;
+
+			::MonoClass* requestedClass = MonoUtil::getClass(paramType);
+
+			bool isManagedComponent = MonoUtil::isSubClassOf(requestedClass, managedComponent->_getInternalClass());
+			if (isManagedComponent)
+			{
+				LOGWRN("Only built-in components can be added though this method.");
+				continue;
+			}
+
+			BuiltinComponentInfo* info = sam.getBuiltinComponentInfo(paramType);
+			if (info == nullptr)
+			{
+				LOGWRN("Provided type is not a valid component")
+				continue;
+			}
+
+			typeIds.push_back(info->typeId);
+		}
+
+		HSceneObject newObj = CmdCreateSO::execute(nativeName, 0, typeIds, nativeDescription);
+
+		return ScriptGameObjectManager::instance().createScriptSceneObject(newObj)->getManagedInstance();
+	}
+
 	void ScriptUndoRedo::internal_DeleteSO(ScriptSceneObject* soPtr, MonoString* description)
 	{
 		String nativeDescription = MonoUtil::monoToString(description);

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

@@ -53,6 +53,7 @@ namespace bs
 		static MonoArray* internal_CloneSOMulti(MonoArray* soPtrs, MonoString* description);
 		static MonoObject* internal_Instantiate(ScriptPrefab* prefabPtr, MonoString* description);
 		static MonoObject* internal_CreateSO(MonoString* name, MonoString* description);
+		static MonoObject* internal_CreateSO2(MonoString* name, MonoArray* types, MonoString* description);
 		static void internal_DeleteSO(ScriptSceneObject* soPtr, MonoString* description);
 		static void internal_ReparentSO(ScriptSceneObject* soPtr, ScriptSceneObject* parentSOPtr, MonoString* description);
 		static void internal_ReparentSOMulti(MonoArray* soPtrs, ScriptSceneObject* parentSOPtr, MonoString* description);