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

Added a base undo/redo command class to scripting, so that custom undo/redo operations can be recorded

BearishSun 9 лет назад
Родитель
Сommit
f243fadd5e

+ 1 - 0
Source/MBansheeEditor/MBansheeEditor.csproj

@@ -51,6 +51,7 @@
     <Compile Include="Utility\SerializedSceneObject.cs" />
     <Compile Include="Utility\SerializedDiff.cs" />
     <Compile Include="Utility\SerializedObject.cs" />
+    <Compile Include="Utility\UndoableCommand.cs" />
     <Compile Include="Windows\AboutBox.cs" />
     <Compile Include="Windows\AnimationWindow.cs" />
     <Compile Include="Windows\Animation\AnimationGizmo.cs" />

+ 32 - 0
Source/MBansheeEditor/Utility/UndoableCommand.cs

@@ -0,0 +1,32 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+using System;
+using System.Runtime.CompilerServices;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    /// <summary>
+    /// Base class for implementation of commands that can be recorded in the UndoRedo system.
+    /// </summary>
+    public abstract class UndoableCommand : ScriptObject
+    {
+        public UndoableCommand()
+        {
+            Internal_CreateInstance(this);
+        }
+
+        /// <summary>
+        /// Applies the command, committing the change.
+        /// </summary>
+        protected abstract void Commit();
+
+        /// <summary>
+        /// Reverts the command, reverting the change previously done with <see cref="Commit"/>.
+        /// </summary>
+        protected abstract void Revert();
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_CreateInstance(UndoableCommand instance);
+    }
+}

+ 2 - 0
Source/SBansheeEditor/CMakeSources.cmake

@@ -11,6 +11,7 @@ set(BS_SBANSHEEEDITOR_INC_NOFILTER
 	"Include/BsEditorScriptLibrary.h"
 	"Include/BsToolbarItemManager.h"
 	"Include/BsScriptGizmoManager.h"
+	"Include/BsManagedEditorCommand.h"
 )
 
 set(BS_SBANSHEEEDITOR_SRC_WRAPPERS_GUI
@@ -85,6 +86,7 @@ set(BS_SBANSHEEEDITOR_SRC_NOFILTER
 	"Source/BsEditorResourceLoader.cpp"
 	"Source/BsEditorScriptLibrary.cpp"
 	"Source/BsToolbarItemManager.cpp"
+	"Source/BsManagedEditorCommand.cpp"
 )
 
 set(BS_SBANSHEEEDITOR_INC_WRAPPERS

+ 87 - 0
Source/SBansheeEditor/Include/BsManagedEditorCommand.h

@@ -0,0 +1,87 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "BsScriptObject.h"
+#include "BsEditorCommand.h"
+
+namespace BansheeEngine
+{
+	class CmdManaged;
+
+	/** @addtogroup ScriptInteropEditor
+	 *  @{
+	 */
+
+	/**	Interop class between C++ & CLR for CmdManaged. */
+	class BS_SCR_BED_EXPORT ScriptCmdManaged : public ScriptObject <ScriptCmdManaged>
+	{
+	public:
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "UndoableCommand")
+
+		~ScriptCmdManaged();
+
+	private:
+		friend class CmdManaged;
+
+		ScriptCmdManaged(MonoObject* instance);
+
+		/** Triggers the Commit() method on the managed object instance. */
+		void triggerCommit();
+
+		/** Triggers the Revert() method on the managed object instance. */
+		void triggerRevert();
+
+		/** Notifies the script instance that the underlying managed command was destroyed. */
+		void notifyCommandDestroyed();
+
+		CmdManaged* mManagedCommand;
+
+		/************************************************************************/
+		/* 								CLR HOOKS						   		*/
+		/************************************************************************/
+		static void internal_CreateInstance(MonoObject* instance);
+
+		static MonoMethod* sCommitMethod;
+		static MonoMethod* sRevertMethod;
+	};
+
+	/** @} */
+
+	/** @addtogroup SBansheeEditor
+	 *  @{
+	 */
+
+	/** 
+	 * A command used for undo/redo purposes. This particular implementation provides a generic overridable base to be
+	 * implemented by script classes. 
+	 */
+	class BS_SCR_BED_EXPORT CmdManaged : public EditorCommand
+	{
+	public:
+		~CmdManaged();
+
+		/** @copydoc EditorCommand::commit */
+		void commit() override;
+
+		/** @copydoc EditorCommand::revert */
+		void revert() override;
+
+	private:
+		friend class UndoRedo;
+		friend class ScriptCmdManaged;
+
+		CmdManaged(ScriptCmdManaged* scriptObj);
+
+		/** 
+		 * Notifies the command the managed script object instance it is referencing has been destroyed. Normally when this
+		 * happens the command should already be outside of the undo/redo stack, but we clear the instance just in case.
+		 */
+		void notifyScriptInstanceDestroyed();
+
+		ScriptCmdManaged* mScriptObj;
+	};
+
+	/** @} */
+}

+ 110 - 0
Source/SBansheeEditor/Source/BsManagedEditorCommand.cpp

@@ -0,0 +1,110 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsManagedEditorCommand.h"
+#include "BsScriptMeta.h"
+#include "BsMonoField.h"
+#include "BsMonoClass.h"
+#include "BsMonoMethod.h"
+#include "BsMonoManager.h"
+
+namespace BansheeEngine
+{
+	MonoMethod* ScriptCmdManaged::sCommitMethod = nullptr;
+	MonoMethod* ScriptCmdManaged::sRevertMethod = nullptr;
+
+	ScriptCmdManaged::ScriptCmdManaged(MonoObject* managedInstance)
+		:ScriptObject(managedInstance), mManagedCommand(nullptr)
+	{
+		mManagedCommand = new (bs_alloc<CmdManaged>()) CmdManaged(this);
+	}
+
+	ScriptCmdManaged::~ScriptCmdManaged()
+	{
+		if(mManagedCommand != nullptr)
+		{
+			mManagedCommand->notifyScriptInstanceDestroyed();
+			mManagedCommand = nullptr;
+		}
+	}
+
+	void ScriptCmdManaged::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptCmdManaged::internal_CreateInstance);
+
+		sCommitMethod = metaData.scriptClass->getMethod("Commit");
+		sRevertMethod = metaData.scriptClass->getMethod("Revert");
+	}
+
+	void ScriptCmdManaged::internal_CreateInstance(MonoObject* managedInstance)
+	{
+		new (bs_alloc<ScriptCmdManaged>()) ScriptCmdManaged(managedInstance);
+	}
+
+	void ScriptCmdManaged::triggerCommit()
+	{
+		if (sCommitMethod == nullptr)
+			return;
+
+		sCommitMethod->invokeVirtual(mManagedInstance, nullptr);
+	}
+
+	void ScriptCmdManaged::triggerRevert()
+	{
+		if (sRevertMethod == nullptr)
+			return;
+
+		sRevertMethod->invokeVirtual(mManagedInstance, nullptr);
+	}
+
+	void ScriptCmdManaged::notifyCommandDestroyed()
+	{
+		mManagedCommand = nullptr;
+	}
+
+	CmdManaged::CmdManaged(ScriptCmdManaged* scriptObj)
+		: EditorCommand(L""), mScriptObj(scriptObj)
+	{
+
+	}
+
+	CmdManaged::~CmdManaged()
+	{
+		if (mScriptObj != nullptr)
+			mScriptObj->notifyCommandDestroyed();
+	}
+
+	void CmdManaged::commit()
+	{
+		if(mScriptObj == nullptr)
+		{
+			LOGWRN("Trying to execute a managed undo/redo command whose managed object has been destroyed, ignoring.");
+			// Note: This can most likely happen if managed undo commands are queued on a global undo/redo stack. When
+			// assembly refresh happens those commands will be destroyed but not removed from the stack. To fix the issue
+			// implement assembly refresh handling for such commands.
+
+			return;
+		}
+
+		mScriptObj->triggerCommit();
+	}
+
+	void CmdManaged::revert()
+	{
+		if (mScriptObj == nullptr)
+		{
+			LOGWRN("Trying to execute a managed undo/redo command whose managed object has been destroyed, ignoring.");
+			// Note: This can most likely happen if managed undo commands are queued on a global undo/redo stack. When
+			// assembly refresh happens those commands will be destroyed but not removed from the stack. To fix the issue
+			// implement assembly refresh handling for such commands.
+
+			return;
+		}
+
+		mScriptObj->triggerRevert();
+	}
+
+	void CmdManaged::notifyScriptInstanceDestroyed()
+	{
+		mScriptObj = nullptr;
+	}
+}