List Serialization and Editor for Scripts #193

Merged
Pycorax merged 21 commits from SP3-6-ArraySerialization into main 2022-11-13 11:58:06 +08:00
16 changed files with 1405 additions and 593 deletions

View File

@ -1,5 +1,6 @@
using SHADE; using SHADE;
using System; using System;
using System.Collections.Generic;
public class RaccoonShowcase : Script public class RaccoonShowcase : Script
{ {
@ -17,6 +18,11 @@ public class RaccoonShowcase : Script
private double rotation = 0.0; private double rotation = 0.0;
private Vector3 scale = Vector3.Zero; private Vector3 scale = Vector3.Zero;
private double originalScale = 1.0f; private double originalScale = 1.0f;
[Tooltip("Sample list of Vector3s.")]
public List<Vector3> vecList = new List<Vector3>(new Vector3[] { new Vector3(1, 2, 3), new Vector3(4, 5, 6) });
[Range(-5, 5)]
public List<int> intList = new List<int>(new int[] { 2, 8, 2, 6, 8, 0, 1 });
public List<Light.Type> enumList = new List<Light.Type>(new Light.Type[] { Light.Type.Point, Light.Type.Directional, Light.Type.Ambient });
public RaccoonShowcase(GameObject gameObj) : base(gameObj) {} public RaccoonShowcase(GameObject gameObj) : base(gameObj) {}
protected override void awake() protected override void awake()

View File

@ -53,9 +53,12 @@ namespace SHADE
/*-----------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------*/
/* ImGui Wrapper Functions - Organizers */ /* ImGui Wrapper Functions - Organizers */
/*-----------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------*/
bool SHEditorUI::CollapsingHeader(const std::string& title) bool SHEditorUI::CollapsingHeader(const std::string& title, bool* isHovered)
{ {
return ImGui::CollapsingHeader(title.c_str(), ImGuiTreeNodeFlags_DefaultOpen); const bool OPENED = ImGui::CollapsingHeader(title.c_str(), ImGuiTreeNodeFlags_DefaultOpen);
if (isHovered)
*isHovered = ImGui::IsItemHovered();
return OPENED;
} }
void SHEditorUI::SameLine() void SHEditorUI::SameLine()
@ -75,7 +78,7 @@ namespace SHADE
bool SHEditorUI::BeginMenu(const std::string& label) bool SHEditorUI::BeginMenu(const std::string& label)
{ {
return ImGui::BeginMenu(label.data()); return ImGui::BeginMenu(label.data());
} }
bool SHEditorUI::BeginMenu(const std::string& label, const char* icon) bool SHEditorUI::BeginMenu(const std::string& label, const char* icon)
@ -143,7 +146,7 @@ namespace SHADE
bool SHEditorUI::Selectable(const std::string& label) bool SHEditorUI::Selectable(const std::string& label)
{ {
return ImGui::Selectable(label.data()); return ImGui::Selectable(label.data());
} }
bool SHEditorUI::Selectable(const std::string& label, const char* icon) bool SHEditorUI::Selectable(const std::string& label, const char* icon)
@ -165,8 +168,10 @@ namespace SHADE
if (isHovered) if (isHovered)
*isHovered = ImGui::IsItemHovered(); *isHovered = ImGui::IsItemHovered();
ImGui::SameLine(); ImGui::SameLine();
return ImGui::InputInt("##", &value, return ImGui::DragInt("##", &value, 0.001f,
1, 10, std::numeric_limits<int>::min(),
std::numeric_limits<int>::max(),
"%d",
ImGuiInputTextFlags_EnterReturnsTrue); ImGuiInputTextFlags_EnterReturnsTrue);
} }
bool SHEditorUI::InputUnsignedInt(const std::string& label, unsigned int& value, bool* isHovered) bool SHEditorUI::InputUnsignedInt(const std::string& label, unsigned int& value, bool* isHovered)
@ -190,31 +195,22 @@ namespace SHADE
if (isHovered) if (isHovered)
*isHovered = ImGui::IsItemHovered(); *isHovered = ImGui::IsItemHovered();
ImGui::SameLine(); ImGui::SameLine();
return ImGui::InputFloat("##", &value, return ImGui::DragFloat("##", &value, 0.001f,
0.1f, 1.0f, "%.3f", std::numeric_limits<float>::lowest(),
std::numeric_limits<float>::max(),
"%.3f",
ImGuiInputTextFlags_EnterReturnsTrue); ImGuiInputTextFlags_EnterReturnsTrue);
} }
bool SHEditorUI::InputDouble(const std::string& label, double& value, bool* isHovered) bool SHEditorUI::InputDouble(const std::string& label, double& value, bool* isHovered)
{ {
ImGui::Text(label.c_str()); float val = value;
if (isHovered) const bool CHANGED = InputFloat(label, val, isHovered);
*isHovered = ImGui::IsItemHovered(); if (CHANGED)
ImGui::SameLine(); {
return ImGui::InputDouble("##", &value, value = static_cast<double>(val);
0.1, 1.0, "%.3f", }
ImGuiInputTextFlags_EnterReturnsTrue); return CHANGED;
} }
bool SHEditorUI::InputAngle(const std::string& label, double& value, bool* isHovered)
{
ImGui::Text(label.c_str());
if (isHovered)
*isHovered = ImGui::IsItemHovered();
ImGui::SameLine();
return ImGui::InputDouble("##", &value,
1.0, 45.0, "%.3f",
ImGuiInputTextFlags_EnterReturnsTrue);
}
bool SHEditorUI::InputSlider(const std::string& label, int min, int max, int& value, bool* isHovered /*= nullptr*/) bool SHEditorUI::InputSlider(const std::string& label, int min, int max, int& value, bool* isHovered /*= nullptr*/)
{ {
ImGui::Text(label.c_str()); ImGui::Text(label.c_str());
@ -266,10 +262,10 @@ namespace SHADE
static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y" }; static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y" };
return SHEditorWidgets::DragN<float, 2>(label, COMPONENT_LABELS, { &value.x, &value.y }, 0.1f, "%.3f", float{}, float{}, 0, isHovered); return SHEditorWidgets::DragN<float, 2>(label, COMPONENT_LABELS, { &value.x, &value.y }, 0.1f, "%.3f", float{}, float{}, 0, isHovered);
} }
bool SHEditorUI::InputVec3(const std::string& label, SHVec3& value, bool* isHovered, float speed) bool SHEditorUI::InputVec3(const std::string& label, SHVec3& value, bool* isHovered)
{ {
static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y", "Z"}; static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y", "Z"};
return SHEditorWidgets::DragN<float, 3>(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, speed, "%.3f", float{}, float{}, 0, isHovered); return SHEditorWidgets::DragN<float, 3>(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, 0.1f, "%.3f", float{}, float{}, 0, isHovered);
} }
bool SHEditorUI::InputTextField(const std::string& label, std::string& value, bool* isHovered) bool SHEditorUI::InputTextField(const std::string& label, std::string& value, bool* isHovered)

View File

@ -85,8 +85,9 @@ namespace SHADE
/// Wraps up ImGui::CollapsingHeader(). /// Wraps up ImGui::CollapsingHeader().
/// </summary> /// </summary>
/// <param name="title">Label for the header.</param> /// <param name="title">Label for the header.</param>
/// <param name="isHovered>If set, stores the hover state of this widget.</param>
/// <returns>True if the header is open, false otherwise.</returns> /// <returns>True if the header is open, false otherwise.</returns>
static bool CollapsingHeader(const std::string& title); static bool CollapsingHeader(const std::string& title, bool* isHovered = nullptr);
static void SameLine(); static void SameLine();
static void Separator(); static void Separator();
@ -219,17 +220,6 @@ namespace SHADE
/// <returns>True if the value was changed.</returns> /// <returns>True if the value was changed.</returns>
static bool InputDouble(const std::string& label, double& value, bool* isHovered = nullptr); static bool InputDouble(const std::string& label, double& value, bool* isHovered = nullptr);
/// <summary> /// <summary>
/// Creates a decimal field widget for double input with increments of higher
/// steps meant for angle variables.
/// <br/>
/// Wraps up ImGui::InputDouble().
/// </summary>
/// <param name="label">Label used to identify this widget.</param>
/// <param name="value">Reference to the variable to store the result.</param>
/// <param name="isHovered>If set, stores the hover state of this widget.</param>
/// <returns>True if the value was changed.</returns>
static bool InputAngle(const std::string& label, double& value, bool* isHovered = nullptr);
/// <summary>
/// Creates an int slider field widget for double input. /// Creates an int slider field widget for double input.
/// <br/> /// <br/>
/// Wraps up ImGui::SliderInt(). /// Wraps up ImGui::SliderInt().
@ -296,7 +286,7 @@ namespace SHADE
/// <param name="value">Reference to the variable to store the result.</param> /// <param name="value">Reference to the variable to store the result.</param>
/// <param name="isHovered>If set, stores the hover state of this widget.</param> /// <param name="isHovered>If set, stores the hover state of this widget.</param>
/// <returns>True if the value was changed.</returns> /// <returns>True if the value was changed.</returns>
static bool InputVec3(const std::string& label, SHVec3& value, bool* isHovered = nullptr, float speed = 0.1f); static bool InputVec3(const std::string& label, SHVec3& value, bool* isHovered = nullptr);
/// <summary> /// <summary>
/// Creates a text field widget for string input. /// Creates a text field widget for string input.
/// <br/> /// <br/>

View File

@ -18,12 +18,9 @@ of DigiPen Institute of Technology is prohibited.
#include "Editor/Editor.hxx" #include "Editor/Editor.hxx"
// STL Includes // STL Includes
#include <memory> #include <memory>
// External Dependencies
#include "Editor/SHEditorUI.h"
// Project Headers // Project Headers
#include "Components/Component.hxx" #include "Components/Component.hxx"
#include "Scripts/ScriptStore.hxx" #include "Scripts/ScriptStore.hxx"
#include "Utility/Convert.hxx"
#include "Utility/Debug.hxx" #include "Utility/Debug.hxx"
#include "Serialisation/ReflectionUtilities.hxx" #include "Serialisation/ReflectionUtilities.hxx"
#include "Editor/IconsMaterialDesign.h" #include "Editor/IconsMaterialDesign.h"
@ -31,98 +28,14 @@ of DigiPen Institute of Technology is prohibited.
#include "Editor/Command/SHCommand.hpp" #include "Editor/Command/SHCommand.hpp"
#include "TooltipAttribute.hxx" #include "TooltipAttribute.hxx"
#include "RangeAttribute.hxx" #include "RangeAttribute.hxx"
#include "Math/Vector2.hxx"
#include "Math/Vector3.hxx"
#include <string>
// Using Directives // Using Directives
using namespace System; using namespace System;
using namespace System::Collections::Generic; using namespace System::Collections::Generic;
/*-------------------------------------------------------------------------------------*/
/* Macro Functions */
/*-------------------------------------------------------------------------------------*/
/// <summary>
/// Macro expansion that is used in renderFieldInInspector() to check the type of a field
/// named "field" against the specified type and if it matches, retrieves the value of
/// that field from an object named "object" and pass it into the specified SHEditorUI::
/// function named "FUNC" by casting it into the NATIVE_TYPE specified.
/// <br/>
/// This only works for primitive types that have the same types for managed and native.
/// </summary>
/// <param name="MANAGED_TYPE">The managed type of the object to edit.</param>
/// <param name="NATIVE_TYPE">The native type of the object to edit.</param>
/// <param name="FUNC">The SHEditorUI:: function to use for editing.</param>
#define RENDER_FIELD(MANAGED_TYPE, NATIVE_TYPE, FUNC) \
(field->FieldType == MANAGED_TYPE::typeid) \
{ \
NATIVE_TYPE val = safe_cast<NATIVE_TYPE>(field->GetValue(object)); \
NATIVE_TYPE oldVal = val; \
if (SHEditorUI::FUNC(Convert::ToNative(field->Name), val, &isHovered))\
{ \
field->SetValue(object, val); \
registerUndoAction(object, field, val, oldVal); \
} \
} \
/// <summary>
/// Alternative to RENDER_FIELD that checks for RangeAttribute and switches to a slider
/// instead.
/// </summary>
/// <param name="MANAGED_TYPE">The managed type of the object to edit.</param>
/// <param name="NATIVE_TYPE">The native type of the object to edit.</param>
/// <param name="FUNC">The SHEditorUI:: function to use for editing.</param>
#define RENDER_FIELD_RANGE(MANAGED_TYPE, NATIVE_TYPE, FUNC) \
(field->FieldType == MANAGED_TYPE::typeid) \
{ \
NATIVE_TYPE val = safe_cast<NATIVE_TYPE>(field->GetValue(object)); \
NATIVE_TYPE oldVal = val; \
\
RangeAttribute^ rangeAttrib = hasAttribute<RangeAttribute^>(field);\
const std::string FIELD_NAME = Convert::ToNative(field->Name); \
bool changed = false; \
if (rangeAttrib) \
{ \
changed = SHEditorUI::InputSlider \
( \
FIELD_NAME, \
static_cast<NATIVE_TYPE>(rangeAttrib->Min), \
static_cast<NATIVE_TYPE>(rangeAttrib->Max), \
val, &isHovered \
); \
} \
else \
{ \
changed = SHEditorUI::FUNC(FIELD_NAME, val, &isHovered); \
} \
\
if (changed) \
{ \
field->SetValue(object, val); \
registerUndoAction(object, field, val, oldVal); \
} \
} \
/// <summary>
/// Macro expansion that is used in renderFieldInInspector() to check the type of a field
/// named "field" against the specified type and if it matches, retrieves the value of
/// that field from an object named "object" and pass it into the specified SHEditorUI::
/// function named "FUNC" by casting it into the NATIVE_TYPE specified.
/// <br/>
/// This only works for types that have an implementation of Convert::ToNative and
/// Convert::ToCLI.
/// </summary>
/// <param name="MANAGED_TYPE">The managed type of the object to edit.</param>
/// <param name="NATIVE_TYPE">The native type of the object to edit.</param>
/// <param name="FUNC">The SHEditorUI:: function to use for editing.</param>
#define RENDER_FIELD_CASTED(MANAGED_TYPE, NATIVE_TYPE, FUNC) \
(field->FieldType == MANAGED_TYPE::typeid) \
{ \
NATIVE_TYPE val = Convert::ToNative(safe_cast<MANAGED_TYPE>(field->GetValue(object))); \
NATIVE_TYPE oldVal = val; \
\
if (SHEditorUI::FUNC(Convert::ToNative(field->Name), val, &isHovered)) \
{ \
field->SetValue(object, Convert::ToCLI(val)); \
registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal)); \
} \
} \
/*-------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------*/
/* Function Definitions */ /* Function Definitions */
/*-------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------*/
@ -234,142 +147,137 @@ 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;
if RENDER_FIELD_RANGE (Int16, int, InputInt) const bool MODIFIED_PRIMITIVE =
else if RENDER_FIELD_RANGE (Int32, int, InputInt) renderSpecificField<int , Int16 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (Int64, int, InputInt) renderSpecificField<int , Int32 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (UInt16, unsigned int, InputUnsignedInt) renderSpecificField<int , Int64 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (UInt32, unsigned int, InputUnsignedInt) renderSpecificField<int , UInt16 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (UInt64, unsigned int, InputUnsignedInt) renderSpecificField<int , UInt32 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (Byte, int, InputInt) renderSpecificField<int , UInt64 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD (bool, bool, InputCheckbox) renderSpecificField<int , Byte >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (float, float, InputFloat) renderSpecificField<bool , bool >(field, object, SHEditorUI::InputCheckbox, &isHovered) ||
else if RENDER_FIELD_RANGE (double, double, InputDouble) renderSpecificField<float , float >(field, object, SHEditorUI::InputFloat , &isHovered) ||
else if (field->FieldType->IsSubclassOf(Enum::typeid)) renderSpecificField<double , double >(field, object, SHEditorUI::InputDouble , &isHovered) ||
{ renderSpecificField<SHVec2 , Vector2 >(field, object, SHEditorUI::InputVec2 , &isHovered) ||
// Get all the names of the enums renderSpecificField<SHVec3 , Vector3 >(field, object, SHEditorUI::InputVec3 , &isHovered) ||
const array<String^>^ ENUM_NAMES = field->FieldType->GetEnumNames(); renderSpecificField<uint32_t , GameObject >(field, object, nullptr , &isHovered) ||
std::vector<std::string> nativeEnumNames; renderSpecificField<std::string, System::String^>(field, object, nullptr , &isHovered) ||
for each (String^ str in ENUM_NAMES) renderSpecificField<int , System::Enum >(field, object, nullptr , &isHovered);
{
nativeEnumNames.emplace_back(Convert::ToNative(str));
}
int val = safe_cast<int>(field->GetValue(object)); if (!MODIFIED_PRIMITIVE)
int oldVal = val;
if (SHEditorUI::InputEnumCombo(Convert::ToNative(field->Name), val, nativeEnumNames, &isHovered))
{
field->SetValue(object, val);
registerUndoAction(object, field, val, oldVal);
}
}
else if RENDER_FIELD_CASTED(Vector2, SHVec2, InputVec2)
else if RENDER_FIELD_CASTED(Vector3, SHVec3, InputVec3)
else if (field->FieldType == String::typeid)
{ {
// Prevent issues where String^ is null due to being empty // Any List
String^ stringVal = safe_cast<String^>(field->GetValue(object)); if (ReflectionUtilities::FieldIsList(field))
if (stringVal == nullptr)
{ {
stringVal = ""; System::Type^ listType = field->FieldType->GenericTypeArguments[0];
} RangeAttribute^ rangeAttrib = hasAttribute<RangeAttribute^>(field);
System::Collections::IList^ iList = safe_cast<System::Collections::IList^>(field->GetValue(object));
// Actual Field if (SHEditorUI::CollapsingHeader(Convert::ToNative(field->Name), &isHovered))
std::string val = Convert::ToNative(stringVal);
std::string oldVal = val;
if (SHEditorUI::InputTextField(Convert::ToNative(field->Name), val, &isHovered))
{
field->SetValue(object, Convert::ToCLI(val));
registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal));
}
}
else if (field->FieldType == GameObject::typeid)
{
GameObject gameObj = safe_cast<GameObject>(field->GetValue(object));
uint32_t entityId = gameObj.GetEntity();
if (SHEditorUI::InputGameObjectField(Convert::ToNative(field->Name), entityId, &isHovered, !gameObj))
{
GameObject newVal = GameObject(entityId);
if (entityId != MAX_EID)
{ {
// Null GameObject set if (SHEditorUI::Button("Add Item"))
newVal = GameObject(entityId);
}
field->SetValue(object, newVal);
registerUndoAction(object, field, newVal, gameObj);
}
}
else
{
array<System::Type^>^ interfaces = field->FieldType->GetInterfaces();
if (interfaces->Length > 0 && interfaces[0] == ICallbackEvent::typeid)
{
array<System::Type^>^ typeArgs = field->FieldType->GenericTypeArguments;
System::String^ title = field->Name + " : CallbackEvent<";
for (int i = 0; i < typeArgs->Length; ++i)
{
title += typeArgs[i]->Name;
if (i < typeArgs->Length - 1)
title += ", ";
}
title += ">";
if (SHEditorUI::CollapsingHeader(Convert::ToNative(title)))
{
// Constants
const std::string LABEL = Convert::ToNative(field->Name);
SHEditorUI::PushID(LABEL);
ICallbackEvent^ callbackEvent = safe_cast<ICallbackEvent^>(field->GetValue(object));
if (callbackEvent == nullptr)
{ {
// Construct one since it was not constructed before System::Object^ obj = System::Activator::CreateInstance(listType);
callbackEvent = safe_cast<ICallbackEvent^>(System::Activator::CreateInstance(field->FieldType)); iList->Add(obj);
registerUndoListAddAction(listType, iList, iList->Count - 1, obj);
} }
for each (ICallbackAction ^ action in callbackEvent->Actions) for (int i = 0; i < iList->Count; ++i)
{ {
if (action->IsRuntimeAction) SHEditorUI::PushID(i);
continue; System::Object^ obj = iList[i];
System::Object^ oldObj = iList[i];
// Attempt to get the object if any if (renderFieldEditor(std::to_string(i), obj, rangeAttrib))
int entityId = static_cast<int>(-1);
if (action->TargetObject)
{ {
Script^ script = safe_cast<Script^>(action->TargetObject); iList[i] = obj;
if (script) registerUndoListChangeAction(listType, iList, i, obj, oldObj);
{
entityId = static_cast<int>(script->Owner.GetEntity());
}
} }
SHEditorUI::InputInt("", entityId);
SHEditorUI::SameLine();
System::String^ methodName = "";
if (action->TargetMethodName != nullptr)
{
methodName = action->TargetMethodName;
}
std::string methodNameNative = Convert::ToNative(methodName);
SHEditorUI::InputTextField("", methodNameNative);
SHEditorUI::SameLine(); SHEditorUI::SameLine();
if (SHEditorUI::Button("-")) if (SHEditorUI::Button("-"))
{ {
callbackEvent->DeregisterAction(action); System::Object^ obj = iList[i];
iList->RemoveAt(i);
registerUndoListRemoveAction(listType, iList, i, obj);
SHEditorUI::PopID();
break; break;
} }
SHEditorUI::PopID();
} }
if (SHEditorUI::Button("Add Action"))
{
callbackEvent->RegisterAction();
}
SHEditorUI::PopID();
} }
} }
else else
{ {
return; array<System::Type^>^ interfaces = field->FieldType->GetInterfaces();
if (interfaces->Length > 0 && interfaces[0] == ICallbackEvent::typeid)
{
array<System::Type^>^ typeArgs = field->FieldType->GenericTypeArguments;
System::String^ title = field->Name + " : CallbackEvent<";
for (int i = 0; i < typeArgs->Length; ++i)
{
title += typeArgs[i]->Name;
if (i < typeArgs->Length - 1)
title += ", ";
}
title += ">";
if (SHEditorUI::CollapsingHeader(Convert::ToNative(title)))
{
// Constants
const std::string LABEL = Convert::ToNative(field->Name);
SHEditorUI::PushID(LABEL);
ICallbackEvent^ callbackEvent = safe_cast<ICallbackEvent^>(field->GetValue(object));
if (callbackEvent == nullptr)
{
// Construct one since it was not constructed before
callbackEvent = safe_cast<ICallbackEvent^>(System::Activator::CreateInstance(field->FieldType));
}
for each (ICallbackAction ^ action in callbackEvent->Actions)
{
if (action->IsRuntimeAction)
continue;
// Attempt to get the object if any
int entityId = static_cast<int>(-1);
if (action->TargetObject)
{
Script^ script = safe_cast<Script^>(action->TargetObject);
if (script)
{
entityId = static_cast<int>(script->Owner.GetEntity());
}
}
SHEditorUI::InputInt("", entityId);
SHEditorUI::SameLine();
System::String^ methodName = "";
if (action->TargetMethodName != nullptr)
{
methodName = action->TargetMethodName;
}
std::string methodNameNative = Convert::ToNative(methodName);
SHEditorUI::InputTextField("", methodNameNative);
SHEditorUI::SameLine();
if (SHEditorUI::Button("-"))
{
callbackEvent->DeregisterAction(action);
break;
}
}
if (SHEditorUI::Button("Add Action"))
{
callbackEvent->RegisterAction();
}
SHEditorUI::PopID();
}
}
else
{
return;
}
} }
} }
@ -383,6 +291,51 @@ namespace SHADE
} }
} }
bool Editor::renderFieldEditor(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib)
{
bool modified;
const bool RENDERED =
renderFieldEditor<int , Int16 >(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) ||
renderFieldEditor<int , Int32 >(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) ||
renderFieldEditor<int , Int64 >(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) ||
renderFieldEditor<int , UInt16 >(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) ||
renderFieldEditor<int , UInt32 >(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) ||
renderFieldEditor<int , UInt64 >(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) ||
renderFieldEditor<int , Byte >(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) ||
renderFieldEditor<bool , bool >(fieldName, object, SHEditorUI::InputCheckbox, nullptr, rangeAttrib, modified) ||
renderFieldEditor<float , float >(fieldName, object, SHEditorUI::InputFloat , nullptr, rangeAttrib, modified) ||
renderFieldEditor<double , double >(fieldName, object, SHEditorUI::InputDouble , nullptr, rangeAttrib, modified) ||
renderFieldEditor<SHVec2 , Vector2 >(fieldName, object, SHEditorUI::InputVec2 , nullptr, rangeAttrib, modified) ||
renderFieldEditor<SHVec3 , Vector3 >(fieldName, object, SHEditorUI::InputVec3 , nullptr, rangeAttrib, modified) ||
renderFieldEditor<uint32_t , GameObject >(fieldName, object, nullptr , nullptr, rangeAttrib, modified) ||
renderFieldEditor<std::string, System::String^>(fieldName, object, nullptr , nullptr, rangeAttrib, modified) ||
renderFieldEditor<int , System::Enum >(fieldName, object, nullptr , nullptr, rangeAttrib, modified);
return modified;
}
bool Editor::renderEnumEditor(const std::string& fieldName, System::Object^% object, bool* isHovered)
{
// Get all the names of the enums
const array<String^>^ ENUM_NAMES = object->GetType()->GetEnumNames();
std::vector<std::string> nativeEnumNames;
for each (String ^ str in ENUM_NAMES)
{
nativeEnumNames.emplace_back(Convert::ToNative(str));
}
int val = safe_cast<int>(object);
int oldVal = val;
if (SHEditorUI::InputEnumCombo(fieldName, val, nativeEnumNames, isHovered))
{
object = val;
return true;
}
return false;
}
void Editor::renderScriptContextMenu(Entity entity, Script^ script) void Editor::renderScriptContextMenu(Entity entity, Script^ script)
{ {
// Right Click Menu // Right Click Menu
@ -400,12 +353,40 @@ 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; // Inform the C++ Undo-Redo stack
cmd.NewData = newData; SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast<SHBaseCommand>(std::make_shared<SHCLICommand>()));
cmd.OldData = oldData; }
actionStack.Add(cmd);
void Editor::registerUndoListChangeAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData)
{
if (list == nullptr)
return;
actionStack.Add(gcnew ListElementChangeCommand(list, index, newData, oldData));
// Inform the C++ Undo-Redo stack
SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast<SHBaseCommand>(std::make_shared<SHCLICommand>()));
}
void Editor::registerUndoListAddAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ data)
{
if (list == nullptr)
return;
actionStack.Add(gcnew ListElementAddCommand(list, index, data));
// Inform the C++ Undo-Redo stack
SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast<SHBaseCommand>(std::make_shared<SHCLICommand>()));
}
void Editor::registerUndoListRemoveAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ data)
{
if (list == nullptr)
return;
actionStack.Add(gcnew ListElementRemoveCommand(list, index, data));
// 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

@ -0,0 +1,203 @@
/************************************************************************************//*!
\file Editor.h++
\author Tng Kah Wei, kahwei.tng, 390009620
\par email: kahwei.tng\@digipen.edu
\date Nov 10, 2022
\brief Contains the definition of templated functions for the managed Editor
static class.
Note: This file is written in C++17/CLI.
Copyright (C) 2022 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the prior written consent
of DigiPen Institute of Technology is prohibited.
*//*************************************************************************************/
#pragma once
// Primary Include
#include "Editor.hxx"
// External Dependencies
#include "Editor/SHEditorUI.h"
// Project Includes
#include "Utility/Convert.hxx"
namespace SHADE
{
template<typename NativeType, typename ManagedType>
bool Editor::renderSpecificField(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered)
{
if constexpr (std::is_same_v<ManagedType, System::Enum>)
{
if (fieldInfo->FieldType->IsSubclassOf(Enum::typeid))
{
System::Object^ enumObj = fieldInfo->GetValue(object);
int oldVal = safe_cast<int>(enumObj);
int val = oldVal;
if (renderEnumEditor
(
Convert::ToNative(fieldInfo->Name),
enumObj,
isHovered
))
{
fieldInfo->SetValue(object, safe_cast<int>(enumObj));
registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal);
}
return true;
}
}
else
{
if (fieldInfo->FieldType == ManagedType::typeid)
{
RangeAttribute^ rangeAttrib;
if constexpr (std::is_arithmetic_v<NativeType> && !std::is_same_v<NativeType, bool>)
{
rangeAttrib = hasAttribute<RangeAttribute^>(fieldInfo);
}
ManagedType oldVal = safe_cast<ManagedType>(fieldInfo->GetValue(object));
ManagedType val = oldVal;
if (renderFieldEditorInternal<NativeType, ManagedType>
(
Convert::ToNative(fieldInfo->Name),
&val,
fieldEditor,
isHovered,
rangeAttrib
))
{
fieldInfo->SetValue(object, val);
registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal);
}
return true;
}
}
return false;
}
template<typename NativeType, typename ManagedType>
bool Editor::renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib, bool& modified)
{
modified = false;
if constexpr (std::is_same_v<ManagedType, System::Enum>)
{
if (object->GetType()->IsSubclassOf(Enum::typeid))
{
modified = renderEnumEditor(fieldName, object, isHovered);
return true;
}
}
else
{
if (object->GetType() == ManagedType::typeid)
{
ManagedType managedVal = safe_cast<ManagedType>(object);
cli::interior_ptr<ManagedType> managedValPtr = &managedVal;
if (renderFieldEditorInternal<NativeType, ManagedType>(fieldName, managedValPtr, fieldEditor, isHovered, rangeAttrib))
{
object = managedVal;
modified = true;
return true;
}
return false;
}
}
return false;
}
template<typename NativeType, typename ManagedType>
bool Editor::renderFieldEditorInternal(const std::string& fieldName, interior_ptr<ManagedType> managedValPtr, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib)
{
// Retrieve the native version of the object
NativeType val;
if constexpr (IsPrimitiveTypeMatches_V<NativeType>)
{
val = safe_cast<NativeType>(*managedValPtr);
}
else
{
val = Convert::ToNative(*managedValPtr);
}
// Throw into the SHEditorUI function
NativeType oldVal = val;
bool changed = false;
if (rangeAttrib)
{
// Do not allow bools for Sliders just in case
if constexpr (std::is_arithmetic_v<NativeType> && !std::is_same_v<NativeType, bool>)
{
changed = SHEditorUI::InputSlider
(
fieldName,
static_cast<NativeType>(rangeAttrib->Min),
static_cast<NativeType>(rangeAttrib->Max),
val, isHovered
);
}
}
else
{
changed = fieldEditor(fieldName, val, isHovered);
}
if (changed)
{
if constexpr (IsPrimitiveTypeMatches_V<NativeType>)
{
*managedValPtr = val;
}
else
{
*managedValPtr = Convert::ToCLI(val);
}
return true;
}
return false;
}
template<>
bool Editor::renderFieldEditorInternal<std::string, System::String^>(const std::string& fieldName, interior_ptr<System::String^> managedValPtr, EditorFieldFunc<std::string>, bool* isHovered, RangeAttribute^)
{
// Prevent issues where String^ is null due to being empty
if (*managedValPtr == nullptr)
*managedValPtr = "";
// Actual Field
std::string val = Convert::ToNative(*managedValPtr);
if (SHEditorUI::InputTextField(fieldName, val, isHovered))
{
*managedValPtr = Convert::ToCLI(val);
return true;
}
return false;
}
template<>
bool Editor::renderFieldEditorInternal<uint32_t, GameObject>(const std::string& fieldName, interior_ptr<GameObject> managedValPtr, EditorFieldFunc<uint32_t>, bool* isHovered, RangeAttribute^)
{
uint32_t entityId = managedValPtr->GetEntity();
if (SHEditorUI::InputGameObjectField(fieldName, entityId, isHovered, !(*managedValPtr)))
{
GameObject newVal = GameObject(entityId);
if (entityId != MAX_EID)
{
// Null GameObject set
newVal = GameObject(entityId);
}
*managedValPtr = newVal;
return true;
}
return false;
}
}

View File

@ -17,9 +17,14 @@ of DigiPen Institute of Technology is prohibited.
#include "Engine/Entity.hxx" #include "Engine/Entity.hxx"
#include "Scripts/Script.hxx" #include "Scripts/Script.hxx"
#include "UndoRedoStack.hxx" #include "UndoRedoStack.hxx"
#include "RangeAttribute.hxx"
namespace SHADE namespace SHADE
{ {
template<typename NativeType>
using EditorFieldFunc = bool(*)(const std::string& label, NativeType& val, bool* isHovered);
/// <summary> /// <summary>
/// Static class for Editor-related functions /// Static class for Editor-related functions
/// </summary> /// </summary>
@ -84,15 +89,114 @@ 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>
/// Renders a raw editor for a single value.
/// </summary>
/// <param name="fieldName">The name of the field to render.</param>
/// <param name="object">Tracking reference to the object to modify.</param>
/// <param name="rangeAttrib">
/// If specified, will be used to constrain values.
/// </param>
/// <returns>True if the value was modified.</returns>
static bool renderFieldEditor(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib);
/// <summary>
/// Renders a ImGui field editor based on the type of parameters specified if the
/// type matches.
/// </summary>
/// <typeparam name="NativeType">Native type of the field.</typeparam>
/// <typeparam name="ManagedType">Managed type of the field.</typeparam>
/// <param name="fieldName">Label to use for the field editor.</param>
/// <param name="managedVal">
/// Tracking reference for the managed variable to modify.
/// </param>
/// <param name="fieldEditor">ImGui field editor function to use.</param>
/// <param name="isHovered">
/// Pointer to a bool that stores if the field editor was hovered over.
/// </param>
/// <param name="rangeAttrib">
/// If provided and the type supports it, the field will be rendered with a
/// slider instead.
/// </param>
/// <param name="modified"></param>
/// <returns>True if the field was rendered..</returns>
template<typename NativeType, typename ManagedType>
static bool renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib, bool& modified);
/// <summary>
/// Renders a raw editor for a single enum value.
/// </summary>
/// <param name="fieldName">The name of the field to render.</param>
/// <param name="object">
/// Tracking reference to the object to modify. Must be an enum.
/// </param>
/// <param name="isHovered">
/// Pointer to a bool that stores if the field editor was hovered over.
/// </param>
/// <returns>True if the value was modified.</returns>
static bool renderEnumEditor(const std::string& fieldName, System::Object^% object, bool* isHovered);
/// <summary>
/// Checks if the specified field is of the specified native and managed type
/// equivalent and renders a ImGui field editor based on the specified field
/// editor function. Also handles fields that contain a RangeAttribute.
/// </summary>
/// <typeparam name="NativeType">Native type of the field.</typeparam>
/// <typeparam name="ManagedType">Managed type of the field.</typeparam>
/// <param name="fieldInfo">Describes the field to modify.</param>
/// <param name="object">Object to modify that has the specified field.</param>
/// <param name="fieldEditor">ImGui field editor function to use.</param>
/// <param name="isHovered">
/// Pointer to a bool that stores if the field editor was hovered over.
/// </param>
/// <returns>True if the field is modified.</returns>
template<typename NativeType, typename ManagedType>
static bool renderSpecificField(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered);
/// <summary>
/// Renders a ImGui field editor based on the type of parameters specified.
/// </summary>
/// <typeparam name="NativeType">Native type of the field.</typeparam>
/// <typeparam name="ManagedType">Managed type of the field.</typeparam>
/// <param name="fieldName">Label to use for the field editor.</param>
/// <param name="managedVal">
/// Tracking reference for the managed variable to modify.
/// </param>
/// <param name="fieldEditor">ImGui field editor function to use.</param>
/// <param name="isHovered">
/// Pointer to a bool that stores if the field editor was hovered over.
/// </param>
/// <param name="rangeAttrib">
/// If provided and the type supports it, the field will be rendered with a
/// slider instead.
/// </param>
/// <returns>True if the field is modified.</returns>
template<typename NativeType, typename ManagedType>
static bool renderFieldEditorInternal(const std::string& fieldName, interior_ptr<ManagedType> managedValPtr, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib);
/// <summary> /// <summary>
/// Renders a context menu when right clicked for the scripts /// Renders a context menu when right clicked for the scripts
/// </summary> /// </summary>
/// <param name="entity">The Entity to render the Scripts of.</param> /// <param name="entity">The Entity to render the Scripts of.</param>
/// <param name="script">The Script to render the inspector for.</param> /// <param name="script">The Script to render the inspector for.</param>
static void renderScriptContextMenu(Entity entity, Script^ script); static void renderScriptContextMenu(Entity entity, Script^ script);
/// <summary>
/// Adds changes to a variable as an undo-able/redo-able action on the Undo-Redo
/// stack.
/// </summary>
/// <param name="object">The object that changes are applied to.</param>
/// <param name="field">The field that was changed.</param>
/// <param name="newData">New data to set.</param>
/// <param name="oldData">Data that was overriden.</param>
static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData); static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData);
static void registerUndoListChangeAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData);
static void registerUndoListAddAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ data);
static void registerUndoListRemoveAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ data);
/// <summary>
/// Checks if a specific field has the specified attribute
/// </summary>
/// <typeparam name="Attribute">Type of Attribute to check for.</typeparam>
/// <param name="field">The field to check.</param>
/// <returns>The attribute to check for if it exists. Null otherwise.</returns>
generic<typename Attribute> where Attribute : System::Attribute generic<typename Attribute> where Attribute : System::Attribute
static Attribute hasAttribute(System::Reflection::FieldInfo^ field); static Attribute hasAttribute(System::Reflection::FieldInfo^ field);
}; };
} }
#include "Editor.h++"

View File

@ -19,9 +19,14 @@ 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
{ {
/*---------------------------------------------------------------------------------*/
/* UndoRedoStack - Properties */
/*---------------------------------------------------------------------------------*/
bool UndoRedoStack::UndoActionPresent::get() bool UndoRedoStack::UndoActionPresent::get()
{ {
return commandStack->Count > 0 && latestActionIndex >= 0; return commandStack->Count > 0 && latestActionIndex >= 0;
@ -32,7 +37,10 @@ namespace SHADE
return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1; return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1;
} }
void UndoRedoStack::Add(Command command) /*---------------------------------------------------------------------------------*/
/* UndoRedoStack - Usage Functions */
/*---------------------------------------------------------------------------------*/
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 +60,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 +70,192 @@ 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 - Constructor */
/*---------------------------------------------------------------------------------*/
FieldChangeCommand::FieldChangeCommand(System::Object^ obj, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData)
: objectToChange { obj }
, field { field }
, newData { newData }
, oldData { oldData }
{}
/*---------------------------------------------------------------------------------*/
/* FieldChangeCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
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;
}
/*---------------------------------------------------------------------------------*/
/* ListElementChangeCommand - Constructor */
/*---------------------------------------------------------------------------------*/
ListElementChangeCommand::ListElementChangeCommand(System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData)
: list { list }
, index { index }
, newData { newData }
, oldData { oldData }
{}
/*---------------------------------------------------------------------------------*/
/* ListElementChangeCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
bool ListElementChangeCommand::Execute()
{
if (list && index < list->Count)
{
list[index] = newData;
return true;
}
return false;
}
bool ListElementChangeCommand::Unexceute()
{
if (list && index < list->Count)
{
list[index] = oldData;
return true;
}
return false;
}
bool ListElementChangeCommand::Merge(ICommand^ command)
{
ListElementChangeCommand^ otherCommand = safe_cast<ListElementChangeCommand^>(command);
if (otherCommand == nullptr)
{
Debug::LogWarning("[Field Change Command] Attempted to merge two incompatible commands!");
return false;
}
if (command && list == otherCommand->list && index == otherCommand->index)
{
newData = otherCommand->newData;
return true;
}
}
/*---------------------------------------------------------------------------------*/
/* ListElementAddCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
ListElementAddCommand::ListElementAddCommand(System::Collections::IList^ list, int addIndex, System::Object^ data)
: list { list }
, addIndex { addIndex }
, data { data }
{}
/*---------------------------------------------------------------------------------*/
/* ListElementAddCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
bool ListElementAddCommand::Execute()
{
if (list)
{
list->Insert(addIndex, data);
return true;
}
return false;
}
bool ListElementAddCommand::Unexceute()
{
if (list && addIndex < list->Count)
{
list->RemoveAt(addIndex);
return true;
}
return false;
}
bool ListElementAddCommand::Merge(ICommand^)
{
// Not allowed
return false;
}
/*---------------------------------------------------------------------------------*/
/* ListElementRemoveCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
ListElementRemoveCommand::ListElementRemoveCommand(System::Collections::IList^ list, int removeIndex, System::Object^ data)
: list { list }
, removeIndex { removeIndex }
, data { data }
{}
/*---------------------------------------------------------------------------------*/
/* ListElementRemoveCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
bool ListElementRemoveCommand::Execute()
{
if (list && removeIndex < list->Count)
{
list->RemoveAt(removeIndex);
return true;
}
return false;
}
bool ListElementRemoveCommand::Unexceute()
{
if (list)
{
list->Insert(removeIndex, data);
return true;
}
return false;
}
bool ListElementRemoveCommand::Merge(ICommand^)
{
// Not allowed
return false;
}
} }

View File

@ -15,27 +15,99 @@ 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;
};
private ref class ListElementChangeCommand sealed : public ICommand
{
public:
ListElementChangeCommand(System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData);
bool Execute() override;
bool Unexceute() override;
bool Merge(ICommand^ command) override;
private:
System::Collections::IList^ list;
int index;
System::Object^ newData;
System::Object^ oldData;
};
private ref class ListElementAddCommand sealed : public ICommand
{
public:
ListElementAddCommand(System::Collections::IList^ list, int addIndex, System::Object^ data);
bool Execute() override;
bool Unexceute() override;
bool Merge(ICommand^ command) override;
private:
System::Collections::IList^ list;
int addIndex; // New index of the added element
System::Object^ data;
};
private ref class ListElementRemoveCommand sealed : public ICommand
{
public:
ListElementRemoveCommand(System::Collections::IList^ list, int removeIndex, System::Object^ data);
bool Execute() override;
bool Unexceute() override;
bool Merge(ICommand^ command) override;
private:
System::Collections::IList^ list;
int removeIndex; // Index of the element to remove at
System::Object^ data;
};
/// <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 +127,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 +142,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^>();
}; };
} }

View File

@ -26,7 +26,7 @@ of DigiPen Institute of Technology is prohibited.
#include "Utility/Convert.hxx" #include "Utility/Convert.hxx"
#include "Script.hxx" #include "Script.hxx"
#include "Engine/Entity.hxx" #include "Engine/Entity.hxx"
#include "Serialisation/ReflectionUtilities.hxx" #include "Serialisation/SerialisationUtilities.hxx"
#include "Engine/Application.hxx" #include "Engine/Application.hxx"
#include "Physics/SHPhysicsSystemInterface.h" #include "Physics/SHPhysicsSystemInterface.h"
#include "Physics/SHPhysicsUtils.h" #include "Physics/SHPhysicsUtils.h"
@ -613,7 +613,7 @@ namespace SHADE
System::Collections::Generic::List<Script^>^ scriptList = scripts[entity]; System::Collections::Generic::List<Script^>^ scriptList = scripts[entity];
for each (Script^ script in scriptList) for each (Script^ script in scriptList)
{ {
ReflectionUtilities::Serialise(script, *yamlNode); SerialisationUtilities::Serialise(script, *yamlNode);
} }
return true; return true;
@ -658,7 +658,7 @@ namespace SHADE
if (AddScriptViaNameWithRef(entity, typeName, script)) if (AddScriptViaNameWithRef(entity, typeName, script))
{ {
// Copy the data in // Copy the data in
ReflectionUtilities::Deserialise(script, node); SerialisationUtilities::Deserialise(script, node);
} }
else else
{ {

View File

@ -18,31 +18,6 @@ of DigiPen Institute of Technology is prohibited.
#include "Serialisation/ReflectionUtilities.hxx" #include "Serialisation/ReflectionUtilities.hxx"
// Project Includes // Project Includes
#include "SerializeFieldAttribute.hxx" #include "SerializeFieldAttribute.hxx"
#include "Utility/Convert.hxx"
#include "Math/Vector2.hxx"
#include "Math/Vector3.hxx"
#include "Utility/Debug.hxx"
#include "Engine/GameObject.hxx"
/*-------------------------------------------------------------------------------------*/
/* Macro Functions */
/*-------------------------------------------------------------------------------------*/
/// <summary>
/// Macro expansion that is used in RapidJsonValueToField() to retrieve the specified
/// member of a Vector type that is stored into a Vector named "vec".
/// </summary>
/// <param name="MEMBER">The name of the member to retrieve.</param>
#define PRIMITIVE_VECTOR_FIELD_ASSIGN(MEMBER) \
iter = jsonValue.FindMember(#MEMBER); \
if (iter != jsonValue.MemberEnd()) \
{ \
vec.MEMBER = iter->value.GetDouble(); \
} \
/*-------------------------------------------------------------------------------------*/
/* File-Level Constants */
/*-------------------------------------------------------------------------------------*/
static const std::string_view SCRIPT_TYPE_YAMLTAG = "Type";
/*-------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------*/
/* Function Definitions */ /* Function Definitions */
@ -64,202 +39,14 @@ namespace SHADE
return fieldInfo->IsPublic || fieldInfo->GetCustomAttributes(SerializeField::typeid, true)->Length > 0; return fieldInfo->IsPublic || fieldInfo->GetCustomAttributes(SerializeField::typeid, true)->Length > 0;
} }
/*---------------------------------------------------------------------------------*/ bool ReflectionUtilities::FieldIsList(System::Reflection::FieldInfo^ fieldInfo)
/* Serialisation Functions */
/*---------------------------------------------------------------------------------*/
void ReflectionUtilities::Serialise(System::Object^ object, YAML::Node& scriptListNode)
{ {
using namespace System::Reflection; return IsList(fieldInfo->FieldType);
// Create YAML object
YAML::Node scriptNode;
scriptNode.SetStyle(YAML::EmitterStyle::Block);
scriptNode[SCRIPT_TYPE_YAMLTAG.data()] = Convert::ToNative(object->GetType()->FullName);
// Get all fields
System::Collections::Generic::IEnumerable<FieldInfo^>^ fields = GetInstanceFields(object);
for each (FieldInfo^ field in fields)
{
// Ignore private and non-SerialiseField
if (!FieldIsSerialisable(field))
continue;
// Serialise
writeFieldIntoYaml(field, object, scriptNode);
}
scriptListNode.push_back(scriptNode);
}
void ReflectionUtilities::Deserialise(Object^ object, YAML::Node& yamlNode)
{
using namespace System::Reflection;
// Load the YAML
if (!yamlNode.IsMap())
{
// Invalid
Debug::LogError
(
System::String::Format("[ReflectionUtilities] Invalid YAML Node provided for deserialization of \"{0}\" script.",
object->GetType()->FullName)
);
return;
}
// Get all fields
System::Collections::Generic::IEnumerable<FieldInfo^>^ fields = GetInstanceFields(object);
for each (FieldInfo^ field in fields)
{
// Ignore private and non-SerialiseField
if (!FieldIsSerialisable(field))
continue;
// Deserialise
const std::string FIELD_NAME = Convert::ToNative(field->Name);
if (yamlNode[FIELD_NAME])
{
writeYamlIntoField(field, object, yamlNode[FIELD_NAME]);
}
}
}
/*---------------------------------------------------------------------------------*/
/* Serialization Helper Functions */
/*---------------------------------------------------------------------------------*/
void ReflectionUtilities::writeFieldIntoYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& yamlNode)
{
// Field YAML Node
YAML::Node fieldNode;
// Retrieve string for the YAML
const bool PRIMITIVE_SERIALIZED = fieldInsertYaml<System::Int16>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::Int32>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::Int64>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::UInt16>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::UInt32>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::UInt64>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::Byte>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<bool>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<float>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<double>(fieldInfo, object, fieldNode);
// Serialization of more complex types
if (!PRIMITIVE_SERIALIZED)
{
if (fieldInfo->FieldType->IsSubclassOf(System::Enum::typeid))
{
fieldNode = std::to_string(safe_cast<int>(fieldInfo->GetValue(object)));
}
else if (fieldInfo->FieldType == System::String::typeid)
{
System::String^ str = safe_cast<System::String^>(fieldInfo->GetValue(object));
fieldNode = Convert::ToNative(str);
}
else if (fieldInfo->FieldType == Vector2::typeid)
{
Vector2 vec = safe_cast<Vector2>(fieldInfo->GetValue(object));
fieldNode.SetStyle(YAML::EmitterStyle::Flow);
fieldNode.push_back(vec.x);
fieldNode.push_back(vec.y);
}
else if (fieldInfo->FieldType == Vector3::typeid)
{
Vector3 vec = safe_cast<Vector3>(fieldInfo->GetValue(object));
fieldNode.SetStyle(YAML::EmitterStyle::Flow);
fieldNode.push_back(vec.x);
fieldNode.push_back(vec.y);
fieldNode.push_back(vec.z);
}
else if (fieldInfo->FieldType == GameObject::typeid)
{
GameObject gameObj = safe_cast<GameObject>(fieldInfo->GetValue(object));
fieldNode = gameObj ? gameObj.GetEntity() : MAX_EID;
}
else // Not any of the supported types
{
Debug::LogWarning(Convert::ToNative(System::String::Format
(
"[ReflectionUtilities] Failed to parse \"{0}\" of \"{1}\" type for serialization.",
fieldInfo->Name, fieldInfo->FieldType)
));
return;
}
}
// Store the field into YAML
yamlNode[Convert::ToNative(fieldInfo->Name)] = fieldNode;
} }
void ReflectionUtilities::writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) bool ReflectionUtilities::IsList(System::Type^ type)
{ {
if (fieldAssignYaml<System::Int16> (fieldInfo, object, node) || return type->IsGenericType
fieldAssignYaml<System::Int32> (fieldInfo, object, node) || && type->GetGenericTypeDefinition() == System::Collections::Generic::List<int>::typeid->GetGenericTypeDefinition();
fieldAssignYaml<System::Int64> (fieldInfo, object, node) ||
fieldAssignYaml<System::UInt16>(fieldInfo, object, node) ||
fieldAssignYaml<System::UInt32>(fieldInfo, object, node) ||
fieldAssignYaml<System::UInt64>(fieldInfo, object, node) ||
fieldAssignYaml<System::Byte> (fieldInfo, object, node) ||
fieldAssignYaml<bool> (fieldInfo, object, node) ||
fieldAssignYaml<float> (fieldInfo, object, node) ||
fieldAssignYaml<double> (fieldInfo, object, node))
{
return;
}
else if (fieldInfo->FieldType->IsSubclassOf(System::Enum::typeid))
{
fieldInfo->SetValue(object, node.as<int>());
}
else if (fieldInfo->FieldType == System::String::typeid)
{
fieldInfo->SetValue(object, Convert::ToCLI(node.as<std::string>()));
}
else if (fieldInfo->FieldType == Vector2::typeid)
{
if (node.IsSequence() && node.size() == 2)
{
Vector2 vec;
vec.x = node[0].as<float>();
vec.y = node[1].as<float>();
fieldInfo->SetValue(object, vec);
}
else
{
Debug::LogWarning
(
System::String::Format("[ReflectionUtilities] Invalid YAML Node provided for deserialization of a Vector2 \"{0}\" field in \"{1}\" script.",
fieldInfo->Name, object->GetType()->FullName)
);
}
}
else if (fieldInfo->FieldType == Vector3::typeid)
{
if (node.IsSequence() && node.size() == 3)
{
Vector3 vec;
vec.x = node[0].as<float>();
vec.y = node[1].as<float>();
vec.z = node[2].as<float>();
fieldInfo->SetValue(object, vec);
}
else
{
Debug::LogWarning
(
System::String::Format("[ReflectionUtilities] Invalid YAML Node provided for deserialization of a Vector3 \"{0}\" field in \"{1}\" script.",
fieldInfo->Name, object->GetType()->FullName)
);
}
}
else if (fieldInfo->FieldType == GameObject::typeid)
{
const uint32_t EID = node.as<uint32_t>();
fieldInfo->SetValue(object, EID == MAX_EID ? GameObject() : GameObject(EID));
}
else // Not any of the supported types
{
Debug::LogWarning(Convert::ToNative(System::String::Format
(
"[ReflectionUtilities] Failed to parse \"{0}\" of \"{1}\" type for deserialisation.",
fieldInfo->Name, fieldInfo->FieldType)
));
}
} }
} }

View File

@ -1,55 +0,0 @@
/************************************************************************************//*!
\file ReflectionUtilities.h++
\author Tng Kah Wei, kahwei.tng, 390009620
\par email: kahwei.tng\@digipen.edu
\date Sep 16, 2022
\brief Contains the definition of the template functions of the managed
ReflectionUtilities static class.
Note: This file is written in C++17/CLI.
Copyright (C) 2022 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the prior written consent
of DigiPen Institute of Technology is prohibited.
*//*************************************************************************************/
#pragma once
// Primary Header
#include "ReflectionUtilities.hxx"
namespace SHADE
{
/*---------------------------------------------------------------------------------*/
/* Serialization Helper Functions */
/*---------------------------------------------------------------------------------*/
template<typename FieldType>
bool ReflectionUtilities::fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode)
{
if (fieldInfo->FieldType == FieldType::typeid)
{
const FieldType VALUE = safe_cast<FieldType>(fieldInfo->GetValue(object));
fieldNode = static_cast<FieldType>(VALUE);
return true;
}
return false;
}
template<typename FieldType>
bool ReflectionUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node)
{
return fieldAssignYaml<FieldType, ToNativeType_T<FieldType>>(fieldInfo, object, node);
}
template<typename FieldType, typename CastType>
bool ReflectionUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node)
{
if (fieldInfo->FieldType == FieldType::typeid)
{
fieldInfo->SetValue(object, node.as<CastType>());
return true;
}
return false;
}
}

View File

@ -13,9 +13,6 @@ of DigiPen Institute of Technology is prohibited.
*//*************************************************************************************/ *//*************************************************************************************/
#pragma once #pragma once
// External Dependencies
#include <yaml-cpp/yaml.h>
namespace SHADE namespace SHADE
{ {
/// <summary> /// <summary>
@ -42,40 +39,17 @@ namespace SHADE
/// True if the specified field is a candidate for serialisation. /// True if the specified field is a candidate for serialisation.
/// </returns> /// </returns>
static bool FieldIsSerialisable(System::Reflection::FieldInfo^ fieldInfo); static bool FieldIsSerialisable(System::Reflection::FieldInfo^ fieldInfo);
/*-----------------------------------------------------------------------------*/
/* Serialisation Functions */
/*-----------------------------------------------------------------------------*/
/// <summary> /// <summary>
/// Creates a JSON node that represents the specified object and its associated /// Checks if the specified field is a generic List.
/// serialisable fields. Public fields and fields marked with the SerialiseField
/// attribute will be serialised.
/// </summary> /// </summary>
/// <param name="object">The object to serialise.</param> /// <param name="fieldInfo">The field to check.</param>
static void Serialise(System::Object^ object, YAML::Node& yamlNode); /// <returns>True if fieldInfo is describing a generic List.</returns>
static bool FieldIsList(System::Reflection::FieldInfo^ fieldInfo);
/// <summary> /// <summary>
/// Deserialises a YAML node that contains a map of Scripts and copies the /// Checks if the specified type is a generic List type.
/// deserialised data into the specified object if there are matching fields.
/// </summary> /// </summary>
/// <param name="yamlNode"> /// <param name="type">The type to check.</param>
/// The JSON string that contains the data to copy into this Script object. /// <returns>True if type is a generic List.</returns>
/// </param> static bool IsList(System::Type^ type);
/// <param name="object">The object to copy deserialised data into.</param>
static void Deserialise(System::Object^ object, YAML::Node& yamlNode);
private:
/*-----------------------------------------------------------------------------*/
/* Serialization Helper Functions */
/*-----------------------------------------------------------------------------*/
static void writeFieldIntoYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& yamlNode);
template<typename FieldType>
static bool fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode);
static void writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node);
template<typename FieldType>
static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node);
template<typename FieldType, typename CastType>
static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node);
}; };
} }
#include "ReflectionUtilities.h++"

View File

@ -0,0 +1,263 @@
/************************************************************************************//*!
\file SerialisationUtilities.cxx
\author Tng Kah Wei, kahwei.tng, 390009620
\par email: kahwei.tng\@digipen.edu
\date Nov 6, 2021
\brief Contains the definition of the functions for the SerialisationUtilities
managed static class.
Note: This file is written in C++17/CLI.
Copyright (C) 2021 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the prior written consent
of DigiPen Institute of Technology is prohibited.
*//*************************************************************************************/
// Precompiled Headers
#include "SHpch.h"
// Primary Header
#include "Serialisation/SerialisationUtilities.hxx"
// Project Includes
#include "ReflectionUtilities.hxx"
/*-------------------------------------------------------------------------------------*/
/* File-Level Constants */
/*-------------------------------------------------------------------------------------*/
static const std::string_view SCRIPT_TYPE_YAMLTAG = "Type";
/*-------------------------------------------------------------------------------------*/
/* Function Definitions */
/*-------------------------------------------------------------------------------------*/
namespace SHADE
{
/*---------------------------------------------------------------------------------*/
/* Serialisation Functions */
/*---------------------------------------------------------------------------------*/
void SerialisationUtilities::Serialise(System::Object^ object, YAML::Node& scriptListNode)
{
using namespace System::Reflection;
// Create YAML object
YAML::Node scriptNode;
scriptNode.SetStyle(YAML::EmitterStyle::Block);
scriptNode[SCRIPT_TYPE_YAMLTAG.data()] = Convert::ToNative(object->GetType()->FullName);
// Get all fields
System::Collections::Generic::IEnumerable<FieldInfo^>^ fields = ReflectionUtilities::GetInstanceFields(object);
for each (FieldInfo^ field in fields)
{
// Ignore private and non-SerialiseField
if (!ReflectionUtilities::FieldIsSerialisable(field))
continue;
// Serialise
writeFieldIntoYaml(field, object, scriptNode);
}
scriptListNode.push_back(scriptNode);
}
void SerialisationUtilities::Deserialise(Object^ object, YAML::Node& yamlNode)
{
using namespace System::Reflection;
// Load the YAML
if (!yamlNode.IsMap())
{
// Invalid
Debug::LogError
(
System::String::Format("[SerialisationUtilities] Invalid YAML Node provided for deserialization of \"{0}\" script.",
object->GetType()->FullName)
);
return;
}
// Get all fields
System::Collections::Generic::IEnumerable<FieldInfo^>^ fields = ReflectionUtilities::GetInstanceFields(object);
for each (FieldInfo^ field in fields)
{
// Ignore private and non-SerialiseField
if (!ReflectionUtilities::FieldIsSerialisable(field))
continue;
// Deserialise
const std::string FIELD_NAME = Convert::ToNative(field->Name);
if (yamlNode[FIELD_NAME])
{
writeYamlIntoField(field, object, yamlNode[FIELD_NAME]);
}
}
}
/*---------------------------------------------------------------------------------*/
/* Serialization Helper Functions */
/*---------------------------------------------------------------------------------*/
void SerialisationUtilities::writeFieldIntoYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& yamlNode)
{
// Field YAML Node
YAML::Node fieldNode;
// Retrieve string for the YAML
const bool PRIMITIVE_SERIALIZED = fieldInsertYaml<System::Int16 >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::Int32 >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::Int64 >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::UInt16>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::UInt32>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::UInt64>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::Byte >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<bool >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<float >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<double >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::Enum >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<System::String>(fieldInfo, object, fieldNode) ||
fieldInsertYaml<Vector2 >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<Vector3 >(fieldInfo, object, fieldNode) ||
fieldInsertYaml<GameObject >(fieldInfo, object, fieldNode);
// Serialization of more complex types
if (!PRIMITIVE_SERIALIZED)
{
if (ReflectionUtilities::FieldIsList(fieldInfo))
{
System::Type^ listType = fieldInfo->FieldType->GenericTypeArguments[0];
System::Collections::IList^ iList = safe_cast<System::Collections::IList^>(fieldInfo->GetValue(object));
fieldNode.SetStyle(YAML::EmitterStyle::Block);
for (int i = 0; i < iList->Count; ++i)
{
YAML::Node elemNode;
if (varInsertYaml(iList[i], elemNode))
{
fieldNode.push_back(elemNode);
}
else
{
Debug::LogWarning(Convert::ToNative(System::String::Format
(
"[SerialisationUtilities] Failed to parse element # {2} of \"{0}\" of \"{1}\" type for serialization.",
fieldInfo->Name, fieldInfo->FieldType, i)
));
}
}
}
else // Not any of the supported types
{
Debug::LogWarning(Convert::ToNative(System::String::Format
(
"[SerialisationUtilities] Failed to parse \"{0}\" of \"{1}\" type for serialization.",
fieldInfo->Name, fieldInfo->FieldType)
));
return;
}
}
// Store the field into YAML
yamlNode[Convert::ToNative(fieldInfo->Name)] = fieldNode;
}
bool SerialisationUtilities::varInsertYaml(System::Object^ object, YAML::Node& fieldNode)
{
const bool INSERTED =
varInsertYamlInternal<System::Int16 >(object, fieldNode) ||
varInsertYamlInternal<System::Int32 >(object, fieldNode) ||
varInsertYamlInternal<System::Int64 >(object, fieldNode) ||
varInsertYamlInternal<System::UInt16>(object, fieldNode) ||
varInsertYamlInternal<System::UInt32>(object, fieldNode) ||
varInsertYamlInternal<System::UInt64>(object, fieldNode) ||
varInsertYamlInternal<System::Byte >(object, fieldNode) ||
varInsertYamlInternal<bool >(object, fieldNode) ||
varInsertYamlInternal<float >(object, fieldNode) ||
varInsertYamlInternal<double >(object, fieldNode) ||
varInsertYamlInternal<System::Enum >(object, fieldNode) ||
varInsertYamlInternal<System::String>(object, fieldNode) ||
varInsertYamlInternal<Vector2 >(object, fieldNode) ||
varInsertYamlInternal<Vector3 >(object, fieldNode) ||
varInsertYamlInternal<GameObject >(object, fieldNode);
return INSERTED;
}
/*---------------------------------------------------------------------------------*/
/* Deserialization Helper Functions */
/*---------------------------------------------------------------------------------*/
bool SerialisationUtilities::writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node)
{
const bool ASSIGNED =
fieldAssignYaml<System::Int16> (fieldInfo, object, node) ||
fieldAssignYaml<System::Int32> (fieldInfo, object, node) ||
fieldAssignYaml<System::Int64> (fieldInfo, object, node) ||
fieldAssignYaml<System::UInt16>(fieldInfo, object, node) ||
fieldAssignYaml<System::UInt32>(fieldInfo, object, node) ||
fieldAssignYaml<System::UInt64>(fieldInfo, object, node) ||
fieldAssignYaml<System::Byte> (fieldInfo, object, node) ||
fieldAssignYaml<bool> (fieldInfo, object, node) ||
fieldAssignYaml<float> (fieldInfo, object, node) ||
fieldAssignYaml<double> (fieldInfo, object, node) ||
fieldAssignYaml<System::Enum> (fieldInfo, object, node) ||
fieldAssignYaml<System::String>(fieldInfo, object, node) ||
fieldAssignYaml<Vector2> (fieldInfo, object, node) ||
fieldAssignYaml<Vector3> (fieldInfo, object, node) ||
fieldAssignYaml<GameObject> (fieldInfo, object, node);
if (!ASSIGNED)
{
if (ReflectionUtilities::FieldIsList(fieldInfo))
{
System::Type^ elemType = fieldInfo->FieldType->GenericTypeArguments[0];
System::Collections::IList^ iList = safe_cast<System::Collections::IList^>(fieldInfo->GetValue(object));
if (node.IsSequence())
{
// Get list size
const int LIST_SIZE = static_cast<int>(node.size());
if (LIST_SIZE > 0)
{
// Get list type
array<System::Type^>^ typeList = gcnew array<System::Type^>{ elemType };
System::Type^ listType = System::Collections::Generic::List<int>::typeid->GetGenericTypeDefinition()->MakeGenericType(typeList);
// Create a list of the specified type
array<int>^ params = gcnew array<int>{ node.size() };
System::Collections::IList^ list = safe_cast<System::Collections::IList^>
(
System::Activator::CreateInstance(listType, params)
);
// Populate the list
for (int i = 0; i < LIST_SIZE; ++i)
{
// Create the object
System::Object^ obj = System::Activator::CreateInstance(elemType);
// Set it's value
if (varAssignYaml(obj, node[i]))
{
list->Add(obj);
}
}
fieldInfo->SetValue(object, list);
}
}
return true;
}
}
return ASSIGNED;
}
bool SerialisationUtilities::varAssignYaml(System::Object^% object, YAML::Node& node)
{
const bool DESERIALISED =
varAssignYamlInternal<System::Int16> (object, node) ||
varAssignYamlInternal<System::Int32> (object, node) ||
varAssignYamlInternal<System::Int64> (object, node) ||
varAssignYamlInternal<System::UInt16>(object, node) ||
varAssignYamlInternal<System::UInt32>(object, node) ||
varAssignYamlInternal<System::UInt64>(object, node) ||
varAssignYamlInternal<System::Byte> (object, node) ||
varAssignYamlInternal<bool> (object, node) ||
varAssignYamlInternal<float> (object, node) ||
varAssignYamlInternal<double> (object, node) ||
varAssignYamlInternal<System::Enum> (object, node) ||
varAssignYamlInternal<System::String>(object, node) ||
varAssignYamlInternal<Vector2> (object, node) ||
varAssignYamlInternal<Vector3> (object, node) ||
varAssignYamlInternal<GameObject> (object, node);
return DESERIALISED;
}
}

View File

@ -0,0 +1,188 @@
/************************************************************************************//*!
\file SerialisationUtilities.h++
\author Tng Kah Wei, kahwei.tng, 390009620
\par email: kahwei.tng\@digipen.edu
\date Sep 16, 2022
\brief Contains the definition of the template functions of the managed
ReflectionUtilities static class.
Note: This file is written in C++17/CLI.
Copyright (C) 2022 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the prior written consent
of DigiPen Institute of Technology is prohibited.
*//*************************************************************************************/
#pragma once
// Primary Header
#include "SerialisationUtilities.hxx"
// Project Includes
#include "Utility/Convert.hxx"
#include "Utility/Debug.hxx"
namespace SHADE
{
/*---------------------------------------------------------------------------------*/
/* Serialization Helper Functions */
/*---------------------------------------------------------------------------------*/
template<typename FieldType>
bool SerialisationUtilities::fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode)
{
Debug::Log(FieldType::typeid->Name);
return varInsertYamlInternal<FieldType>(fieldInfo->GetValue(object), fieldNode);
}
template<typename FieldType>
bool SerialisationUtilities::varInsertYamlInternal(System::Object^ object, YAML::Node& fieldNode)
{
if constexpr (std::is_same_v<FieldType, System::Enum>)
{
if (object->GetType()->IsSubclassOf(System::Enum::typeid))
{
fieldNode = std::to_string(safe_cast<int>(object));
return true;
}
}
else if constexpr (std::is_same_v<FieldType, System::String>)
{
if (object->GetType() == System::String::typeid)
{
System::String^ str = safe_cast<System::String^>(object);
fieldNode = Convert::ToNative(str);
return true;
}
}
else if constexpr (std::is_same_v<FieldType, Vector2>)
{
if (object->GetType() == Vector2::typeid)
{
Vector2 vec = safe_cast<Vector2>(object);
fieldNode.SetStyle(YAML::EmitterStyle::Flow);
fieldNode.push_back(vec.x);
fieldNode.push_back(vec.y);
return true;
}
}
else if constexpr (std::is_same_v<FieldType, Vector3>)
{
if (object->GetType() == Vector3::typeid)
{
Vector3 vec = safe_cast<Vector3>(object);
fieldNode.SetStyle(YAML::EmitterStyle::Flow);
fieldNode.push_back(vec.x);
fieldNode.push_back(vec.y);
fieldNode.push_back(vec.z);
return true;
}
}
else if constexpr (std::is_same_v<FieldType, GameObject>)
{
if (object->GetType() == GameObject::typeid)
{
GameObject gameObj = safe_cast<GameObject>(object);
fieldNode = gameObj ? gameObj.GetEntity() : MAX_EID;
return true;
}
}
else
{
if (object->GetType() == FieldType::typeid)
{
FieldType value = safe_cast<FieldType>(object);
fieldNode = static_cast<FieldType>(value);
return true;
}
}
return false;
}
/*---------------------------------------------------------------------------------*/
/* Deserialization Helper Functions */
/*---------------------------------------------------------------------------------*/
template<typename FieldType>
bool SerialisationUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node)
{
System::Object^ valueObj = fieldInfo->GetValue(object);
if (varAssignYamlInternal<FieldType>(valueObj, node))
{
fieldInfo->SetValue(object, valueObj);
return true;
}
return false;
}
template<typename FieldType, typename CastType>
bool SerialisationUtilities::varAssignYamlInternal(System::Object^% object, YAML::Node& node)
{
if constexpr (std::is_same_v<FieldType, System::Enum>)
{
if (object->GetType()->IsSubclassOf(System::Enum::typeid))
{
object = node.as<int>();
return true;
}
}
else if constexpr (std::is_same_v<FieldType, System::Collections::IList>)
{
if (ReflectionUtilities::FieldIsList(fieldInfo))
{
System::Collections::IList^ iList = safe_cast<System::Collections::IList^>(object);
object = gcnew
if (node.IsSequence() )
}
}
else
{
if (object->GetType() == FieldType::typeid)
{
if constexpr (std::is_same_v<FieldType, System::String>)
{
object = Convert::ToCLI(node.as<std::string>());
}
else if constexpr (std::is_same_v<FieldType, Vector2>)
{
if (node.IsSequence() && node.size() == 2)
{
Vector2 vec;
vec.x = node[0].as<float>();
vec.y = node[1].as<float>();
object = vec;
}
else
{
return false;
}
}
else if constexpr (std::is_same_v<FieldType, Vector3>)
{
if (node.IsSequence() && node.size() == 3)
{
Vector3 vec;
vec.x = node[0].as<float>();
vec.y = node[1].as<float>();
vec.z = node[2].as<float>();
object = vec;
}
else
{
return false;
}
}
else if constexpr (std::is_same_v<FieldType, GameObject>)
{
const uint32_t EID = node.as<uint32_t>();
object = (EID == MAX_EID ? GameObject() : GameObject(EID));
}
else
{
object = node.as<CastType>();
}
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,75 @@
/************************************************************************************//*!
\file SerialisationUtilities.hxx
\author Tng Kah Wei, kahwei.tng, 390009620
\par email: kahwei.tng\@digipen.edu
\date Nov 6, 2021
\brief Contains the definition of the managed SerialisationUtilities static
class.
Note: This file is written in C++17/CLI.
Copyright (C) 2021 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the prior written consent
of DigiPen Institute of Technology is prohibited.
*//*************************************************************************************/
#pragma once
// External Dependencies
#include <yaml-cpp/yaml.h>
// Project Includes
#include "Math/Vector2.hxx"
#include "Math/Vector3.hxx"
#include "Engine/GameObject.hxx"
namespace SHADE
{
/// <summary>
/// Contains useful static functions for working with Serialisation of Managed data.
/// </summary>
private ref class SerialisationUtilities abstract sealed
{
public:
/*-----------------------------------------------------------------------------*/
/* Serialisation Functions */
/*-----------------------------------------------------------------------------*/
/// <summary>
/// Creates a JSON node that represents the specified object and its associated
/// serialisable fields. Public fields and fields marked with the SerialiseField
/// attribute will be serialised.
/// </summary>
/// <param name="object">The object to serialise.</param>
static void Serialise(System::Object^ object, YAML::Node& yamlNode);
/// <summary>
/// Deserialises a YAML node that contains a map of Scripts and copies the
/// deserialised data into the specified object if there are matching fields.
/// </summary>
/// <param name="yamlNode">
/// The JSON string that contains the data to copy into this Script object.
/// </param>
/// <param name="object">The object to copy deserialised data into.</param>
static void Deserialise(System::Object^ object, YAML::Node& yamlNode);
private:
/*-----------------------------------------------------------------------------*/
/* Serialization Helper Functions */
/*-----------------------------------------------------------------------------*/
static void writeFieldIntoYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& yamlNode);
template<typename FieldType>
static bool fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode);
static bool varInsertYaml(System::Object^ object, YAML::Node& fieldNode);
template<typename FieldType>
static bool varInsertYamlInternal(System::Object^ object, YAML::Node& fieldNode);
/*-----------------------------------------------------------------------------*/
/* Deserialization Helper Functions */
/*-----------------------------------------------------------------------------*/
static bool writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node);
template<typename FieldType>
static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node);
static bool varAssignYaml(System::Object^% object, YAML::Node& node);
template<typename FieldType, typename CastType = ToNativeType_T<FieldType>>
static bool varAssignYamlInternal(System::Object^% object, YAML::Node& node);
};
}
#include "SerialisationUtilities.h++"

View File

@ -152,6 +152,40 @@ namespace SHADE
}; };
/// <summary>
/// Checks if the specified type is matching between native C++ and the managed type.
/// </summary>
/// <typeparam name="T">Type to check.</typeparam>
template<typename T>
struct IsPrimitiveTypeMatches : public std::integral_constant
<
bool,
std::is_same_v<System::Int16 , typename std::remove_cv_t<T>> ||
std::is_same_v<System::Int32 , typename std::remove_cv_t<T>> ||
std::is_same_v<System::Int64 , typename std::remove_cv_t<T>> ||
std::is_same_v<System::UInt16, typename std::remove_cv_t<T>> ||
std::is_same_v<System::UInt32, typename std::remove_cv_t<T>> ||
std::is_same_v<System::UInt64, typename std::remove_cv_t<T>> ||
std::is_same_v<System::Byte , typename std::remove_cv_t<T>> ||
std::is_same_v<bool , typename std::remove_cv_t<T>> ||
std::is_same_v<double , typename std::remove_cv_t<T>> ||
std::is_same_v<float , typename std::remove_cv_t<T>> ||
std::is_same_v<int8_t , typename std::remove_cv_t<T>> ||
std::is_same_v<int16_t , typename std::remove_cv_t<T>> ||
std::is_same_v<int32_t , typename std::remove_cv_t<T>> ||
std::is_same_v<int64_t , typename std::remove_cv_t<T>> ||
std::is_same_v<uint16_t , typename std::remove_cv_t<T>> ||
std::is_same_v<uint32_t , typename std::remove_cv_t<T>> ||
std::is_same_v<uint64_t , typename std::remove_cv_t<T>>
>
{};
/// <summary>
/// Short hand for IsPrimitiveTypeMatches::value
/// </summary>
/// <typeparam name="T">Type to check.</typeparam>
template<typename T>
inline constexpr bool IsPrimitiveTypeMatches_V = IsPrimitiveTypeMatches<T>::value;
/// <summary> /// <summary>
/// Type Transformer for managed types to native types. /// Type Transformer for managed types to native types.
/// </summary> /// </summary>
@ -163,6 +197,7 @@ namespace SHADE
{ {
public: public:
using Value = void; using Value = void;
static bool IsDefined() { return is_same_v<ManagedType, Value>; }
}; };
template<> struct ToNativeType<System::Int16> { using Value = int16_t; }; template<> struct ToNativeType<System::Int16> { using Value = int16_t; };
template<> struct ToNativeType<System::Int32> { using Value = int32_t; }; template<> struct ToNativeType<System::Int32> { using Value = int32_t; };
@ -193,19 +228,20 @@ namespace SHADE
template<typename NativeType> template<typename NativeType>
struct ToManagedType struct ToManagedType
{ {
public: public:
using Value = void; using Value = void;
static bool IsDefined() { return is_same_v<NativeType, Value>; }
}; };
template<> struct ToManagedType<int8_t> { using Value = System::Byte; }; template<> struct ToManagedType<int8_t > { using Value = System::Byte; };
template<> struct ToManagedType<int16_t> { using Value = System::Int16; }; template<> struct ToManagedType<int16_t > { using Value = System::Int16; };
template<> struct ToManagedType<int32_t> { using Value = System::Int32; }; template<> struct ToManagedType<int32_t > { using Value = System::Int32; };
template<> struct ToManagedType<int64_t> { using Value = System::Int64; }; template<> struct ToManagedType<int64_t > { using Value = System::Int64; };
template<> struct ToManagedType<uint16_t> { using Value = System::UInt16; }; template<> struct ToManagedType<uint16_t> { using Value = System::UInt16; };
template<> struct ToManagedType<uint32_t> { using Value = System::UInt32; }; template<> struct ToManagedType<uint32_t> { using Value = System::UInt32; };
template<> struct ToManagedType<uint64_t> { using Value = System::UInt64; }; template<> struct ToManagedType<uint64_t> { using Value = System::UInt64; };
template<> struct ToManagedType<bool> { using Value = bool; }; template<> struct ToManagedType<bool > { using Value = bool; };
template<> struct ToManagedType<double> { using Value = double; }; template<> struct ToManagedType<double > { using Value = double; };
template<> struct ToManagedType<float> { using Value = float; }; template<> struct ToManagedType<float > { using Value = float; };
/// <summary> /// <summary>
/// Alias for ToManagedType::Value /// Alias for ToManagedType::Value