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();
}
void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, Object^ object)
void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, System::Object^ object)
{
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)
{
// Create command and add it into the undo stack
UndoRedoStack::Command cmd;
cmd.Field = field;
cmd.Object = object;
cmd.NewData = newData;
cmd.OldData = oldData;
actionStack.Add(cmd);
actionStack.Add(gcnew FieldChangeCommand(object, field, newData, oldData));
// Inform the C++ Undo-Redo stack
SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast<SHBaseCommand>(std::make_shared<SHCLICommand>()));

View File

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

View File

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

View File

@ -19,6 +19,8 @@ of DigiPen Institute of Technology is prohibited.
// External Dependencies
#include "Editor/SHEditorUI.h"
// Project Headers
#include "Utility/Debug.hxx"
#include "Utility/Convert.hxx"
namespace SHADE
{
@ -32,7 +34,7 @@ namespace SHADE
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
if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1)
@ -52,8 +54,8 @@ namespace SHADE
if (!UndoActionPresent)
return;
Command cmd = commandStack[latestActionIndex];
cmd.Field->SetValue(cmd.Object, cmd.OldData);
ICommand^ cmd = commandStack[latestActionIndex];
cmd->Unexceute();
--latestActionIndex;
}
@ -62,8 +64,57 @@ namespace SHADE
if (!RedoActionPresent)
return;
Command cmd = commandStack[latestActionIndex];
cmd.Field->SetValue(cmd.Object, cmd.NewData);
ICommand^ cmd = commandStack[latestActionIndex];
cmd->Execute();
++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
{
/// <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>
/// Class that is able to store a stack of actions that can be done and redone.
/// </summary>
private ref class UndoRedoStack sealed
{
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 */
/*-----------------------------------------------------------------------------*/
@ -55,7 +80,7 @@ namespace SHADE
/// Adds a command onto the stack.
/// </summary>
/// <param name="command"></param>
void Add(Command command);
void Add(ICommand^ command);
/// <summary>
/// Undos the last added command if it exists.
/// </summary>
@ -70,6 +95,6 @@ namespace SHADE
/* Data Members */
/*-----------------------------------------------------------------------------*/
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^>();
};
}