Reworked Undo-Redo system to be more flexible and stable

This commit is contained in:
Kah Wei 2022-11-10 18:10:15 +08:00
parent bdc7297937
commit cf5cc41a3f
5 changed files with 104 additions and 35 deletions

View File

@ -149,7 +149,7 @@ namespace SHADE
} }
SHEditorUI::PopID(); SHEditorUI::PopID();
} }
void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, Object^ object) void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, System::Object^ object)
{ {
bool isHovered = false; bool isHovered = false;
@ -343,12 +343,7 @@ namespace SHADE
void Editor::registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData) void Editor::registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData)
{ {
// Create command and add it into the undo stack // Create command and add it into the undo stack
UndoRedoStack::Command cmd; actionStack.Add(gcnew FieldChangeCommand(object, field, newData, oldData));
cmd.Field = field;
cmd.Object = object;
cmd.NewData = newData;
cmd.OldData = oldData;
actionStack.Add(cmd);
// Inform the C++ Undo-Redo stack // Inform the C++ Undo-Redo stack
SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast<SHBaseCommand>(std::make_shared<SHCLICommand>())); SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast<SHBaseCommand>(std::make_shared<SHCLICommand>()));

View File

@ -30,7 +30,8 @@ namespace SHADE
rangeAttrib = hasAttribute<RangeAttribute^>(fieldInfo); rangeAttrib = hasAttribute<RangeAttribute^>(fieldInfo);
} }
ManagedType val = safe_cast<ManagedType>(fieldInfo->GetValue(object)); ManagedType oldVal = safe_cast<ManagedType>(fieldInfo->GetValue(object));
ManagedType val = oldVal;
if (renderFieldInInspector<NativeType, ManagedType> if (renderFieldInInspector<NativeType, ManagedType>
( (
Convert::ToNative(fieldInfo->Name), Convert::ToNative(fieldInfo->Name),
@ -41,7 +42,7 @@ namespace SHADE
)) ))
{ {
fieldInfo->SetValue(object, val); fieldInfo->SetValue(object, val);
// TODO: Register undo registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal);
} }
return true; return true;
@ -90,15 +91,12 @@ namespace SHADE
{ {
if constexpr (IsPrimitiveTypeMatches_V<NativeType>) if constexpr (IsPrimitiveTypeMatches_V<NativeType>)
{ {
//field->SetValue(object, val);
managedVal = val; managedVal = val;
//registerUndoAction(object, field, val, oldVal);
} }
else else
{ {
managedVal = Convert::ToCLI(val); managedVal = Convert::ToCLI(val);
//registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal));
} }
return true; return true;

View File

@ -89,7 +89,7 @@ namespace SHADE
/// <param name="object"> /// <param name="object">
/// The object that contains the data of the field to render. /// The object that contains the data of the field to render.
/// </param> /// </param>
static void renderFieldInInspector(System::Reflection::FieldInfo^ field, Object^ object); static void renderFieldInInspector(System::Reflection::FieldInfo^ field, System::Object^ object);
/// <summary> /// <summary>
/// Renders a context menu when right clicked for the scripts /// Renders a context menu when right clicked for the scripts
/// </summary> /// </summary>

View File

@ -19,6 +19,8 @@ of DigiPen Institute of Technology is prohibited.
// External Dependencies // External Dependencies
#include "Editor/SHEditorUI.h" #include "Editor/SHEditorUI.h"
// Project Headers // Project Headers
#include "Utility/Debug.hxx"
#include "Utility/Convert.hxx"
namespace SHADE namespace SHADE
{ {
@ -32,7 +34,7 @@ namespace SHADE
return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1; return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1;
} }
void UndoRedoStack::Add(Command command) void UndoRedoStack::Add(ICommand^ command)
{ {
// Erase any other actions ahead of the current action // Erase any other actions ahead of the current action
if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1) if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1)
@ -52,8 +54,8 @@ namespace SHADE
if (!UndoActionPresent) if (!UndoActionPresent)
return; return;
Command cmd = commandStack[latestActionIndex]; ICommand^ cmd = commandStack[latestActionIndex];
cmd.Field->SetValue(cmd.Object, cmd.OldData); cmd->Unexceute();
--latestActionIndex; --latestActionIndex;
} }
@ -62,8 +64,57 @@ namespace SHADE
if (!RedoActionPresent) if (!RedoActionPresent)
return; return;
Command cmd = commandStack[latestActionIndex]; ICommand^ cmd = commandStack[latestActionIndex];
cmd.Field->SetValue(cmd.Object, cmd.NewData); cmd->Execute();
++latestActionIndex; ++latestActionIndex;
} }
FieldChangeCommand::FieldChangeCommand(System::Object^ obj, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData)
: objectToChange { obj }
, field { field }
, newData { newData }
, oldData { oldData }
{}
bool FieldChangeCommand::Execute()
{
if (field && objectToChange)
{
field->SetValue(objectToChange, newData);
return true;
}
return false;
}
bool FieldChangeCommand::Unexceute()
{
if (field && objectToChange)
{
field->SetValue(objectToChange, oldData);
return true;
}
return false;
}
bool FieldChangeCommand::Merge(ICommand^ command)
{
FieldChangeCommand^ otherCommand = safe_cast<FieldChangeCommand^>(command);
if (otherCommand == nullptr)
{
Debug::LogWarning("[Field Change Command] Attempted to merge two incompatible commands!");
return false;
}
// Only merge if they are workng on the same object and field
if (field == otherCommand->field && objectToChange == otherCommand->objectToChange)
{
newData = otherCommand->newData;
return true;
}
return false;
}
} }

View File

@ -15,27 +15,52 @@ of DigiPen Institute of Technology is prohibited.
namespace SHADE namespace SHADE
{ {
/// <summary>
/// Interface for command that fits into the UndoRedoStack which can perform
/// undo-able and redo-able operations.
/// </summary>
private interface class ICommand
{
/// <summary>
/// Executes an action. This is called when a "Redo" is performed.
/// </summary>
/// <returns>Whether the action was successful or not.</returns>
bool Execute();
/// <summary>
/// Undoes an action. This is called when an "Undo" is performed.
/// </summary>
/// <returns>Whether the action was successful or not.</returns>
bool Unexceute();
/// <summary>
/// Merges this command with another command.
/// </summary>
/// <param name="command"></param>
/// <returns>Whether the merge was successful or not.</returns>
bool Merge(ICommand^ command);
};
private ref class FieldChangeCommand sealed : public ICommand
{
public:
FieldChangeCommand(System::Object^ obj, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData);
bool Execute() override;
bool Unexceute() override;
bool Merge(ICommand^ command) override;
private:
System::Object^ objectToChange;
System::Reflection::FieldInfo^ field;
System::Object^ newData;
System::Object^ oldData;
};
/// <summary> /// <summary>
/// Class that is able to store a stack of actions that can be done and redone. /// Class that is able to store a stack of actions that can be done and redone.
/// </summary> /// </summary>
private ref class UndoRedoStack sealed private ref class UndoRedoStack sealed
{ {
public: public:
/*-----------------------------------------------------------------------------*/
/* Type Definitions */
/*-----------------------------------------------------------------------------*/
/// <summary>
/// Command for the stack that represents a data modification.
/// </summary>
value struct Command
{
public:
System::Object^ Object;
System::Reflection::FieldInfo^ Field;
System::Object^ NewData;
System::Object^ OldData;
};
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
/* Properties */ /* Properties */
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
@ -55,7 +80,7 @@ namespace SHADE
/// Adds a command onto the stack. /// Adds a command onto the stack.
/// </summary> /// </summary>
/// <param name="command"></param> /// <param name="command"></param>
void Add(Command command); void Add(ICommand^ command);
/// <summary> /// <summary>
/// Undos the last added command if it exists. /// Undos the last added command if it exists.
/// </summary> /// </summary>
@ -70,6 +95,6 @@ namespace SHADE
/* Data Members */ /* Data Members */
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
int latestActionIndex = -1; int latestActionIndex = -1;
System::Collections::Generic::List<Command>^ commandStack = gcnew System::Collections::Generic::List<Command>(); System::Collections::Generic::List<ICommand^>^ commandStack = gcnew System::Collections::Generic::List<ICommand^>();
}; };
} }