List Serialization and Editor for Scripts #193
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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/>
|
||||||
|
|
|
@ -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>()));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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++"
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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^>();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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++"
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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++"
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue