From e8d2179d76150613a3f9daef12a65c2720c6cc30 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 10 Nov 2022 11:01:17 +0800 Subject: [PATCH 01/18] Added test support for List display in editor --- SHADE_Managed/src/Editor/Editor.cxx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index 54200c1e..d5ef3005 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -302,6 +302,29 @@ namespace SHADE registerUndoAction(object, field, newVal, gameObj); } } + // Any List + else if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) + { + System::Type^ listType = field->FieldType->GenericTypeArguments[0]; + System::Collections::IEnumerable^ listEnummerable = safe_cast(field->GetValue(object)); + + + SHEditorUI::Text(Convert::ToNative(field->Name)); + SHEditorUI::SameLine(); + SHEditorUI::Button("+"); + + SHEditorUI::Indent(); + int i = 0; + for each (System::Object^ obj in listEnummerable) + { + int val = safe_cast(obj); + SHEditorUI::InputInt(std::to_string(i), val, &isHovered); + SHEditorUI::SameLine(); + SHEditorUI::Button("-"); + ++i; + } + SHEditorUI::Unindent(); + } else { array^ interfaces = field->FieldType->GetInterfaces(); From bdc72979370823c5d3181bf60f824e495bac5306 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 10 Nov 2022 16:20:04 +0800 Subject: [PATCH 02/18] Converted macros for script field inspectors to use templates --- SHADE_Engine/src/Editor/SHEditorUI.cpp | 4 +- SHADE_Engine/src/Editor/SHEditorUI.h | 2 +- SHADE_Managed/src/Editor/Editor.cxx | 378 ++++++++++--------------- SHADE_Managed/src/Editor/Editor.h++ | 109 +++++++ SHADE_Managed/src/Editor/Editor.hxx | 56 ++++ SHADE_Managed/src/Utility/Convert.hxx | 52 +++- 6 files changed, 361 insertions(+), 240 deletions(-) create mode 100644 SHADE_Managed/src/Editor/Editor.h++ diff --git a/SHADE_Engine/src/Editor/SHEditorUI.cpp b/SHADE_Engine/src/Editor/SHEditorUI.cpp index 49cfbfd6..ba394f77 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.cpp +++ b/SHADE_Engine/src/Editor/SHEditorUI.cpp @@ -266,10 +266,10 @@ namespace SHADE static const std::vector COMPONENT_LABELS = { "X", "Y" }; return SHEditorWidgets::DragN(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 COMPONENT_LABELS = { "X", "Y", "Z"}; - return SHEditorWidgets::DragN(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, speed, "%.3f", float{}, float{}, 0, isHovered); + return SHEditorWidgets::DragN(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) diff --git a/SHADE_Engine/src/Editor/SHEditorUI.h b/SHADE_Engine/src/Editor/SHEditorUI.h index 4e8f4400..e0ea0521 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.h +++ b/SHADE_Engine/src/Editor/SHEditorUI.h @@ -296,7 +296,7 @@ namespace SHADE /// Reference to the variable to store the result. /// -/// 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. -///
-/// This only works for primitive types that have the same types for managed and native. -/// -/// The managed type of the object to edit. -/// The native type of the object to edit. -/// The SHEditorUI:: function to use for editing. -#define RENDER_FIELD(MANAGED_TYPE, NATIVE_TYPE, FUNC) \ -(field->FieldType == MANAGED_TYPE::typeid) \ -{ \ - NATIVE_TYPE val = safe_cast(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); \ - } \ -} \ -/// -/// Alternative to RENDER_FIELD that checks for RangeAttribute and switches to a slider -/// instead. -/// -/// The managed type of the object to edit. -/// The native type of the object to edit. -/// The SHEditorUI:: function to use for editing. -#define RENDER_FIELD_RANGE(MANAGED_TYPE, NATIVE_TYPE, FUNC) \ -(field->FieldType == MANAGED_TYPE::typeid) \ -{ \ - NATIVE_TYPE val = safe_cast(field->GetValue(object)); \ - NATIVE_TYPE oldVal = val; \ - \ - RangeAttribute^ rangeAttrib = hasAttribute(field);\ - const std::string FIELD_NAME = Convert::ToNative(field->Name); \ - bool changed = false; \ - if (rangeAttrib) \ - { \ - changed = SHEditorUI::InputSlider \ - ( \ - FIELD_NAME, \ - static_cast(rangeAttrib->Min), \ - static_cast(rangeAttrib->Max), \ - val, &isHovered \ - ); \ - } \ - else \ - { \ - changed = SHEditorUI::FUNC(FIELD_NAME, val, &isHovered); \ - } \ - \ - if (changed) \ - { \ - field->SetValue(object, val); \ - registerUndoAction(object, field, val, oldVal); \ - } \ -} \ -/// -/// 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. -///
-/// This only works for types that have an implementation of Convert::ToNative and -/// Convert::ToCLI. -///
-/// The managed type of the object to edit. -/// The native type of the object to edit. -/// The SHEditorUI:: function to use for editing. -#define RENDER_FIELD_CASTED(MANAGED_TYPE, NATIVE_TYPE, FUNC) \ -(field->FieldType == MANAGED_TYPE::typeid) \ -{ \ - NATIVE_TYPE val = Convert::ToNative(safe_cast(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 */ /*-------------------------------------------------------------------------------------*/ @@ -238,161 +153,166 @@ namespace SHADE { bool isHovered = false; - if RENDER_FIELD_RANGE (Int16, int, InputInt) - else if RENDER_FIELD_RANGE (Int32, int, InputInt) - else if RENDER_FIELD_RANGE (Int64, int, InputInt) - else if RENDER_FIELD_RANGE (UInt16, unsigned int, InputUnsignedInt) - else if RENDER_FIELD_RANGE (UInt32, unsigned int, InputUnsignedInt) - else if RENDER_FIELD_RANGE (UInt64, unsigned int, InputUnsignedInt) - else if RENDER_FIELD_RANGE (Byte, int, InputInt) - else if RENDER_FIELD (bool, bool, InputCheckbox) - else if RENDER_FIELD_RANGE (float, float, InputFloat) - else if RENDER_FIELD_RANGE (double, double, InputDouble) - else if (field->FieldType->IsSubclassOf(Enum::typeid)) - { - // Get all the names of the enums - const array^ ENUM_NAMES = field->FieldType->GetEnumNames(); - std::vector nativeEnumNames; - for each (String^ str in ENUM_NAMES) - { - nativeEnumNames.emplace_back(Convert::ToNative(str)); - } + const bool MODIFIED_PRIMITIVE = + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputCheckbox, &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputFloat , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputDouble , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputVec2 , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputVec3 , &isHovered); - int val = safe_cast(field->GetValue(object)); - 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) + if (!MODIFIED_PRIMITIVE) { - // Prevent issues where String^ is null due to being empty - String^ stringVal = safe_cast(field->GetValue(object)); - if (stringVal == nullptr) + if (field->FieldType->IsSubclassOf(Enum::typeid)) { - stringVal = ""; - } - - // Actual Field - 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(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) + // Get all the names of the enums + const array^ ENUM_NAMES = field->FieldType->GetEnumNames(); + std::vector nativeEnumNames; + for each (String ^ str in ENUM_NAMES) { - // Null GameObject set - newVal = GameObject(entityId); + nativeEnumNames.emplace_back(Convert::ToNative(str)); + } + + int val = safe_cast(field->GetValue(object)); + int oldVal = val; + if (SHEditorUI::InputEnumCombo(Convert::ToNative(field->Name), val, nativeEnumNames, &isHovered)) + { + field->SetValue(object, val); + registerUndoAction(object, field, val, oldVal); } - field->SetValue(object, newVal); - registerUndoAction(object, field, newVal, gameObj); } - } - // Any List - else if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) - { - System::Type^ listType = field->FieldType->GenericTypeArguments[0]; - System::Collections::IEnumerable^ listEnummerable = safe_cast(field->GetValue(object)); - - - SHEditorUI::Text(Convert::ToNative(field->Name)); - SHEditorUI::SameLine(); - SHEditorUI::Button("+"); - - SHEditorUI::Indent(); - int i = 0; - for each (System::Object^ obj in listEnummerable) + else if (field->FieldType == String::typeid) { - int val = safe_cast(obj); - SHEditorUI::InputInt(std::to_string(i), val, &isHovered); + // Prevent issues where String^ is null due to being empty + String^ stringVal = safe_cast(field->GetValue(object)); + if (stringVal == nullptr) + { + stringVal = ""; + } + + // Actual Field + 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(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 + newVal = GameObject(entityId); + } + field->SetValue(object, newVal); + registerUndoAction(object, field, newVal, gameObj); + } + } + // Any List + else if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) + { + System::Type^ listType = field->FieldType->GenericTypeArguments[0]; + System::Collections::IEnumerable^ listEnummerable = safe_cast(field->GetValue(object)); + + + SHEditorUI::Text(Convert::ToNative(field->Name)); SHEditorUI::SameLine(); - SHEditorUI::Button("-"); - ++i; - } - SHEditorUI::Unindent(); - } - else - { - array^ interfaces = field->FieldType->GetInterfaces(); - if (interfaces->Length > 0 && interfaces[0] == ICallbackEvent::typeid) - { - array^ typeArgs = field->FieldType->GenericTypeArguments; - System::String^ title = field->Name + " : CallbackEvent<"; - for (int i = 0; i < typeArgs->Length; ++i) + SHEditorUI::Button("+"); + + SHEditorUI::Indent(); + int i = 0; + for each (System::Object ^ obj in listEnummerable) { - 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(field->GetValue(object)); - if (callbackEvent == nullptr) - { - // Construct one since it was not constructed before - callbackEvent = safe_cast(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(-1); - if (action->TargetObject) - { - Script^ script = safe_cast(action->TargetObject); - if (script) - { - entityId = static_cast(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(); + int val = safe_cast(obj); + SHEditorUI::InputInt(std::to_string(i), val, &isHovered); + SHEditorUI::SameLine(); + SHEditorUI::Button("-"); + ++i; } + SHEditorUI::Unindent(); } else { - return; + array^ interfaces = field->FieldType->GetInterfaces(); + if (interfaces->Length > 0 && interfaces[0] == ICallbackEvent::typeid) + { + array^ 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(field->GetValue(object)); + if (callbackEvent == nullptr) + { + // Construct one since it was not constructed before + callbackEvent = safe_cast(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(-1); + if (action->TargetObject) + { + Script^ script = safe_cast(action->TargetObject); + if (script) + { + entityId = static_cast(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; + } } } diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ new file mode 100644 index 00000000..2cda78e7 --- /dev/null +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -0,0 +1,109 @@ +/************************************************************************************//*! +\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" + +namespace SHADE +{ + template + bool Editor::renderFieldInInspector(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered) + { + if (fieldInfo->FieldType == ManagedType::typeid) + { + RangeAttribute^ rangeAttrib; + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + rangeAttrib = hasAttribute(fieldInfo); + } + + ManagedType val = safe_cast(fieldInfo->GetValue(object)); + if (renderFieldInInspector + ( + Convert::ToNative(fieldInfo->Name), + val, + fieldEditor, + isHovered, + rangeAttrib + )) + { + fieldInfo->SetValue(object, val); + // TODO: Register undo + } + + return true; + } + + return false; + } + + template + bool Editor::renderFieldInInspector(const std::string& fieldName, ManagedType% managedVal, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) + { + // Retrieve the native version of the object + NativeType val; + if constexpr (IsPrimitiveTypeMatches_V) + { + val = safe_cast(managedVal); + } + else + { + val = Convert::ToNative(managedVal); + } + + // 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 && !std::is_same_v) + { + changed = SHEditorUI::InputSlider + ( + fieldName, + static_cast(rangeAttrib->Min), + static_cast(rangeAttrib->Max), + val, isHovered + ); + } + } + else + { + changed = fieldEditor(fieldName, val, isHovered); + } + + if (changed) + { + if constexpr (IsPrimitiveTypeMatches_V) + { + //field->SetValue(object, val); + managedVal = val; + //registerUndoAction(object, field, val, oldVal); + } + else + { + + managedVal = Convert::ToCLI(val); + //registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal)); + } + + return true; + } + + return false; + } +} diff --git a/SHADE_Managed/src/Editor/Editor.hxx b/SHADE_Managed/src/Editor/Editor.hxx index 109842b5..c7e86622 100644 --- a/SHADE_Managed/src/Editor/Editor.hxx +++ b/SHADE_Managed/src/Editor/Editor.hxx @@ -17,9 +17,14 @@ of DigiPen Institute of Technology is prohibited. #include "Engine/Entity.hxx" #include "Scripts/Script.hxx" #include "UndoRedoStack.hxx" +#include "RangeAttribute.hxx" namespace SHADE { + + template + using EditorFieldFunc = bool(*)(const std::string& label, NativeType& val, bool* isHovered); + /// /// Static class for Editor-related functions /// @@ -91,8 +96,59 @@ namespace SHADE /// The Entity to render the Scripts of. /// The Script to render the inspector for. static void renderScriptContextMenu(Entity entity, Script^ script); + /// + /// Adds changes to a variable as an undo-able/redo-able action on the Undo-Redo + /// stack. + /// + /// The object that changes are applied to. + /// The field that was changed. + /// New data to set. + /// Data that was overriden. static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData); + /// + /// Checks if a specific field has the specified attribute + /// + /// Type of Attribute to check for. + /// The field to check. + /// The attribute to check for if it exists. Null otherwise. generic where Attribute : System::Attribute static Attribute hasAttribute(System::Reflection::FieldInfo^ field); + /// + /// 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. + /// + /// Native type of the field. + /// Managed type of the field. + /// Describes the field to modify. + /// Object to modify that has the specified field. + /// ImGui field editor function to use. + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// True if the field is modified. + template + static bool renderFieldInInspector(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered); + /// + /// Renders a ImGui field editor based on the type of parameters specified. + /// + /// Native type of the field. + /// Managed type of the field. + /// Label to use for the field editor. + /// + /// Tracking reference for the managed variable to modify. + /// + /// ImGui field editor function to use. + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// + /// If provided and the type supports it, the field will be rendered with a + /// slider instead. + /// + /// True if the field is modified. + template + static bool renderFieldInInspector(const std::string& fieldName, ManagedType% managedVal, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); }; } +#include "Editor.h++" diff --git a/SHADE_Managed/src/Utility/Convert.hxx b/SHADE_Managed/src/Utility/Convert.hxx index 666b5062..4d0c5b59 100644 --- a/SHADE_Managed/src/Utility/Convert.hxx +++ b/SHADE_Managed/src/Utility/Convert.hxx @@ -152,6 +152,40 @@ namespace SHADE }; + /// + /// Checks if the specified type is matching between native C++ and the managed type. + /// + /// Type to check. + template + struct IsPrimitiveTypeMatches : public std::integral_constant + < + bool, + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> + > + {}; + /// + /// Short hand for IsPrimitiveTypeMatches::value + /// + /// Type to check. + template + inline constexpr bool IsPrimitiveTypeMatches_V = IsPrimitiveTypeMatches::value; + /// /// Type Transformer for managed types to native types. /// @@ -163,6 +197,7 @@ namespace SHADE { public: using Value = void; + static bool IsDefined() { return is_same_v; } }; template<> struct ToNativeType { using Value = int16_t; }; template<> struct ToNativeType { using Value = int32_t; }; @@ -193,19 +228,20 @@ namespace SHADE template struct ToManagedType { - public: + public: using Value = void; + static bool IsDefined() { return is_same_v; } }; - template<> struct ToManagedType { using Value = System::Byte; }; - template<> struct ToManagedType { using Value = System::Int16; }; - template<> struct ToManagedType { using Value = System::Int32; }; - template<> struct ToManagedType { using Value = System::Int64; }; + template<> struct ToManagedType { using Value = System::Byte; }; + template<> struct ToManagedType { using Value = System::Int16; }; + template<> struct ToManagedType { using Value = System::Int32; }; + template<> struct ToManagedType { using Value = System::Int64; }; template<> struct ToManagedType { using Value = System::UInt16; }; template<> struct ToManagedType { using Value = System::UInt32; }; template<> struct ToManagedType { using Value = System::UInt64; }; - template<> struct ToManagedType { using Value = bool; }; - template<> struct ToManagedType { using Value = double; }; - template<> struct ToManagedType { using Value = float; }; + template<> struct ToManagedType { using Value = bool; }; + template<> struct ToManagedType { using Value = double; }; + template<> struct ToManagedType { using Value = float; }; /// /// Alias for ToManagedType::Value From cf5cc41a3f6a0a641163eb8a80841c2373761c44 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 10 Nov 2022 18:10:15 +0800 Subject: [PATCH 03/18] Reworked Undo-Redo system to be more flexible and stable --- SHADE_Managed/src/Editor/Editor.cxx | 9 +--- SHADE_Managed/src/Editor/Editor.h++ | 8 ++- SHADE_Managed/src/Editor/Editor.hxx | 2 +- SHADE_Managed/src/Editor/UndoRedoStack.cxx | 61 ++++++++++++++++++++-- SHADE_Managed/src/Editor/UndoRedoStack.hxx | 59 +++++++++++++++------ 5 files changed, 104 insertions(+), 35 deletions(-) diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index bfd93401..d29f838d 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -149,7 +149,7 @@ namespace SHADE } SHEditorUI::PopID(); } - void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, Object^ object) + void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, System::Object^ object) { bool isHovered = false; @@ -343,12 +343,7 @@ namespace SHADE void Editor::registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData) { // Create command and add it into the undo stack - UndoRedoStack::Command cmd; - cmd.Field = field; - cmd.Object = object; - cmd.NewData = newData; - cmd.OldData = oldData; - actionStack.Add(cmd); + actionStack.Add(gcnew FieldChangeCommand(object, field, newData, oldData)); // Inform the C++ Undo-Redo stack SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ index 2cda78e7..b68b5da8 100644 --- a/SHADE_Managed/src/Editor/Editor.h++ +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -30,7 +30,8 @@ namespace SHADE rangeAttrib = hasAttribute(fieldInfo); } - ManagedType val = safe_cast(fieldInfo->GetValue(object)); + ManagedType oldVal = safe_cast(fieldInfo->GetValue(object)); + ManagedType val = oldVal; if (renderFieldInInspector ( Convert::ToNative(fieldInfo->Name), @@ -41,7 +42,7 @@ namespace SHADE )) { fieldInfo->SetValue(object, val); - // TODO: Register undo + registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal); } return true; @@ -90,15 +91,12 @@ namespace SHADE { if constexpr (IsPrimitiveTypeMatches_V) { - //field->SetValue(object, val); managedVal = val; - //registerUndoAction(object, field, val, oldVal); } else { managedVal = Convert::ToCLI(val); - //registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal)); } return true; diff --git a/SHADE_Managed/src/Editor/Editor.hxx b/SHADE_Managed/src/Editor/Editor.hxx index c7e86622..1f2c1be7 100644 --- a/SHADE_Managed/src/Editor/Editor.hxx +++ b/SHADE_Managed/src/Editor/Editor.hxx @@ -89,7 +89,7 @@ namespace SHADE /// /// The object that contains the data of the field to render. /// - static void renderFieldInInspector(System::Reflection::FieldInfo^ field, Object^ object); + static void renderFieldInInspector(System::Reflection::FieldInfo^ field, System::Object^ object); /// /// Renders a context menu when right clicked for the scripts /// diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.cxx b/SHADE_Managed/src/Editor/UndoRedoStack.cxx index 08e289cc..ae0a1dee 100644 --- a/SHADE_Managed/src/Editor/UndoRedoStack.cxx +++ b/SHADE_Managed/src/Editor/UndoRedoStack.cxx @@ -19,6 +19,8 @@ of DigiPen Institute of Technology is prohibited. // External Dependencies #include "Editor/SHEditorUI.h" // Project Headers +#include "Utility/Debug.hxx" +#include "Utility/Convert.hxx" namespace SHADE { @@ -32,7 +34,7 @@ namespace SHADE return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1; } - void UndoRedoStack::Add(Command command) + void UndoRedoStack::Add(ICommand^ command) { // Erase any other actions ahead of the current action if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1) @@ -52,8 +54,8 @@ namespace SHADE if (!UndoActionPresent) return; - Command cmd = commandStack[latestActionIndex]; - cmd.Field->SetValue(cmd.Object, cmd.OldData); + ICommand^ cmd = commandStack[latestActionIndex]; + cmd->Unexceute(); --latestActionIndex; } @@ -62,8 +64,57 @@ namespace SHADE if (!RedoActionPresent) return; - Command cmd = commandStack[latestActionIndex]; - cmd.Field->SetValue(cmd.Object, cmd.NewData); + ICommand^ cmd = commandStack[latestActionIndex]; + cmd->Execute(); ++latestActionIndex; } + + FieldChangeCommand::FieldChangeCommand(System::Object^ obj, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData) + : objectToChange { obj } + , field { field } + , newData { newData } + , oldData { oldData } + {} + + bool FieldChangeCommand::Execute() + { + if (field && objectToChange) + { + field->SetValue(objectToChange, newData); + return true; + } + + return false; + } + + bool FieldChangeCommand::Unexceute() + { + if (field && objectToChange) + { + field->SetValue(objectToChange, oldData); + return true; + } + + return false; + } + + bool FieldChangeCommand::Merge(ICommand^ command) + { + FieldChangeCommand^ otherCommand = safe_cast(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; + } + } diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.hxx b/SHADE_Managed/src/Editor/UndoRedoStack.hxx index 4c525228..69f462e3 100644 --- a/SHADE_Managed/src/Editor/UndoRedoStack.hxx +++ b/SHADE_Managed/src/Editor/UndoRedoStack.hxx @@ -15,27 +15,52 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { + /// + /// Interface for command that fits into the UndoRedoStack which can perform + /// undo-able and redo-able operations. + /// + private interface class ICommand + { + /// + /// Executes an action. This is called when a "Redo" is performed. + /// + /// Whether the action was successful or not. + bool Execute(); + /// + /// Undoes an action. This is called when an "Undo" is performed. + /// + /// Whether the action was successful or not. + bool Unexceute(); + /// + /// Merges this command with another command. + /// + /// + /// Whether the merge was successful or not. + 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; + }; + /// /// Class that is able to store a stack of actions that can be done and redone. /// private ref class UndoRedoStack sealed { public: - /*-----------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-----------------------------------------------------------------------------*/ - /// - /// Command for the stack that represents a data modification. - /// - value struct Command - { - public: - System::Object^ Object; - System::Reflection::FieldInfo^ Field; - System::Object^ NewData; - System::Object^ OldData; - }; - /*-----------------------------------------------------------------------------*/ /* Properties */ /*-----------------------------------------------------------------------------*/ @@ -55,7 +80,7 @@ namespace SHADE /// Adds a command onto the stack. /// /// - void Add(Command command); + void Add(ICommand^ command); /// /// Undos the last added command if it exists. /// @@ -70,6 +95,6 @@ namespace SHADE /* Data Members */ /*-----------------------------------------------------------------------------*/ int latestActionIndex = -1; - System::Collections::Generic::List^ commandStack = gcnew System::Collections::Generic::List(); + System::Collections::Generic::List^ commandStack = gcnew System::Collections::Generic::List(); }; } From 80db641b6f0353ae14d3c01567551b86cc0c95f5 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 11 Nov 2022 00:49:20 +0800 Subject: [PATCH 04/18] Added ListElementChangeCommand --- SHADE_Managed/src/Editor/UndoRedoStack.cxx | 49 ++++++++++++++++++++++ SHADE_Managed/src/Editor/UndoRedoStack.hxx | 19 ++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.cxx b/SHADE_Managed/src/Editor/UndoRedoStack.cxx index ae0a1dee..3feb43ed 100644 --- a/SHADE_Managed/src/Editor/UndoRedoStack.cxx +++ b/SHADE_Managed/src/Editor/UndoRedoStack.cxx @@ -117,4 +117,53 @@ namespace SHADE return false; } + generic + ListElementChangeCommand::ListElementChangeCommand(System::Collections::Generic::List^ list, int index, T newData, T oldData) + : list { list } + , index { index } + , newData { newData } + , oldData { oldData } + {} + + generic + bool ListElementChangeCommand::Execute() + { + if (list && index < System::Linq::Enumerable::Count(list)) + { + list[index] = newData; + return true; + } + + return false; + } + + generic + bool ListElementChangeCommand::Unexceute() + { + if (list && index < System::Linq::Enumerable::Count(list)) + { + list[index] = oldData; + return true; + } + + return false; + } + + generic + bool ListElementChangeCommand::Merge(ICommand^ command) + { + ListElementChangeCommand^ otherCommand = safe_cast^>(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; + } + } + } diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.hxx b/SHADE_Managed/src/Editor/UndoRedoStack.hxx index 69f462e3..dd78ecd9 100644 --- a/SHADE_Managed/src/Editor/UndoRedoStack.hxx +++ b/SHADE_Managed/src/Editor/UndoRedoStack.hxx @@ -38,7 +38,7 @@ namespace SHADE /// Whether the merge was successful or not. bool Merge(ICommand^ command); }; - + private ref class FieldChangeCommand sealed : public ICommand { public: @@ -55,6 +55,23 @@ namespace SHADE System::Object^ oldData; }; + generic + private ref class ListElementChangeCommand sealed : public ICommand + { + public: + ListElementChangeCommand(System::Collections::Generic::List^ list, int index, T newData, T oldData); + + bool Execute() override; + bool Unexceute() override; + bool Merge(ICommand^ command) override; + + private: + System::Collections::Generic::List^ list; + int index; + T newData; + T oldData; + }; + /// /// Class that is able to store a stack of actions that can be done and redone. /// From 85cc97ca27e373a2910b1b660a9631393f5b1fa7 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 11 Nov 2022 12:07:05 +0800 Subject: [PATCH 05/18] Added implementation for ListElementAddCommand and ListElementRemoveCommand --- SHADE_Managed/src/Editor/UndoRedoStack.cxx | 115 ++++++++++++++++++++- SHADE_Managed/src/Editor/UndoRedoStack.hxx | 32 ++++++ 2 files changed, 142 insertions(+), 5 deletions(-) diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.cxx b/SHADE_Managed/src/Editor/UndoRedoStack.cxx index 3feb43ed..10ef822c 100644 --- a/SHADE_Managed/src/Editor/UndoRedoStack.cxx +++ b/SHADE_Managed/src/Editor/UndoRedoStack.cxx @@ -24,6 +24,9 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { + /*---------------------------------------------------------------------------------*/ + /* UndoRedoStack - Properties */ + /*---------------------------------------------------------------------------------*/ bool UndoRedoStack::UndoActionPresent::get() { return commandStack->Count > 0 && latestActionIndex >= 0; @@ -33,7 +36,10 @@ namespace SHADE { return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1; } - + + /*---------------------------------------------------------------------------------*/ + /* UndoRedoStack - Usage Functions */ + /*---------------------------------------------------------------------------------*/ void UndoRedoStack::Add(ICommand^ command) { // Erase any other actions ahead of the current action @@ -68,14 +74,20 @@ namespace SHADE cmd->Execute(); ++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) @@ -116,15 +128,21 @@ namespace SHADE return false; } - + + /*---------------------------------------------------------------------------------*/ + /* ListElementChangeCommand - Constructor */ + /*---------------------------------------------------------------------------------*/ generic ListElementChangeCommand::ListElementChangeCommand(System::Collections::Generic::List^ list, int index, T newData, T oldData) : list { list } - , index { index } + , index{ index } , newData { newData } , oldData { oldData } {} + /*---------------------------------------------------------------------------------*/ + /* ListElementChangeCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ generic bool ListElementChangeCommand::Execute() { @@ -164,6 +182,93 @@ namespace SHADE newData = otherCommand->newData; return true; } + } + + /*---------------------------------------------------------------------------------*/ + /* ListElementAddCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + generic + ListElementAddCommand::ListElementAddCommand(System::Collections::Generic::List^ list, int addIndex, T data) + : list { list } + , addIndex { addIndex } + , data { data } + {} + + /*---------------------------------------------------------------------------------*/ + /* ListElementAddCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + generic + bool ListElementAddCommand::Execute() + { + if (list) + { + list->Insert(addIndex, data); + return true; + } + + return false; + } + + generic + bool ListElementAddCommand::Unexceute() + { + if (list && addIndex < System::Linq::Enumerable::Count(list)) + { + list->RemoveAt(addIndex); + return true; + } + + return false; + } + + generic + bool ListElementAddCommand::Merge(ICommand^) + { + // Not allowed + return false; } + /*---------------------------------------------------------------------------------*/ + /* ListElementRemoveCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + generic + ListElementRemoveCommand::ListElementRemoveCommand(System::Collections::Generic::List^ list, int removeIndex, T data) + : list { list } + , removeIndex { removeIndex } + , data { data } + {} + + /*---------------------------------------------------------------------------------*/ + /* ListElementRemoveCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + generic + bool ListElementRemoveCommand::Execute() + { + if (list && removeIndex < System::Linq::Enumerable::Count(list)) + { + list->RemoveAt(removeIndex); + return true; + } + + return false; + } + + generic + bool ListElementRemoveCommand::Unexceute() + { + if (list) + { + list->Insert(removeIndex, data); + return true; + } + + return false; + } + + generic + bool ListElementRemoveCommand::Merge(ICommand^) + { + // Not allowed + return false; + } } diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.hxx b/SHADE_Managed/src/Editor/UndoRedoStack.hxx index dd78ecd9..ed9a625a 100644 --- a/SHADE_Managed/src/Editor/UndoRedoStack.hxx +++ b/SHADE_Managed/src/Editor/UndoRedoStack.hxx @@ -71,6 +71,38 @@ namespace SHADE T newData; T oldData; }; + + generic + private ref class ListElementAddCommand sealed : public ICommand + { + public: + ListElementAddCommand(System::Collections::Generic::List^ list, int addIndex, T data); + + bool Execute() override; + bool Unexceute() override; + bool Merge(ICommand^ command) override; + + private: + System::Collections::Generic::List^ list; + int addIndex; // New index of the added element + T data; + }; + + generic + private ref class ListElementRemoveCommand sealed : public ICommand + { + public: + ListElementRemoveCommand(System::Collections::Generic::List^ list, int removeIndex, T data); + + bool Execute() override; + bool Unexceute() override; + bool Merge(ICommand^ command) override; + + private: + System::Collections::Generic::List^ list; + int removeIndex; // Index of the element to remove at + T data; + }; /// /// Class that is able to store a stack of actions that can be done and redone. From fdc8965b62a2abec77f3f1e4be95601a5d165e96 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 11 Nov 2022 12:07:26 +0800 Subject: [PATCH 06/18] Made String and GameObject editor template specializations instead --- SHADE_Managed/src/Editor/Editor.cxx | 63 +++++++---------------------- SHADE_Managed/src/Editor/Editor.h++ | 39 ++++++++++++++++++ 2 files changed, 53 insertions(+), 49 deletions(-) diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index d29f838d..c82cc0a1 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -18,12 +18,9 @@ of DigiPen Institute of Technology is prohibited. #include "Editor/Editor.hxx" // STL Includes #include -// External Dependencies -#include "Editor/SHEditorUI.h" // Project Headers #include "Components/Component.hxx" #include "Scripts/ScriptStore.hxx" -#include "Utility/Convert.hxx" #include "Utility/Debug.hxx" #include "Serialisation/ReflectionUtilities.hxx" #include "Editor/IconsMaterialDesign.h" @@ -154,18 +151,20 @@ namespace SHADE bool isHovered = false; const bool MODIFIED_PRIMITIVE = - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputCheckbox, &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputFloat , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputDouble , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputVec2 , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputVec3 , &isHovered); + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputCheckbox, &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputFloat , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputDouble , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputVec2 , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputVec3 , &isHovered) || + renderFieldInInspector(field, object, nullptr , &isHovered) || + renderFieldInInspector(field, object, nullptr , &isHovered); if (!MODIFIED_PRIMITIVE) { @@ -187,40 +186,6 @@ namespace SHADE registerUndoAction(object, field, val, oldVal); } } - else if (field->FieldType == String::typeid) - { - // Prevent issues where String^ is null due to being empty - String^ stringVal = safe_cast(field->GetValue(object)); - if (stringVal == nullptr) - { - stringVal = ""; - } - - // Actual Field - 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(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 - newVal = GameObject(entityId); - } - field->SetValue(object, newVal); - registerUndoAction(object, field, newVal, gameObj); - } - } // Any List else if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) { diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ index b68b5da8..46993f88 100644 --- a/SHADE_Managed/src/Editor/Editor.h++ +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -16,6 +16,10 @@ of DigiPen Institute of Technology is prohibited. // Primary Include #include "Editor.hxx" +// External Dependencies +#include "Editor/SHEditorUI.h" +// Project Includes +#include "Utility/Convert.hxx" namespace SHADE { @@ -102,6 +106,41 @@ namespace SHADE return true; } + return false; + } + template<> + bool Editor::renderFieldInInspector(const std::string& fieldName, System::String^% managedVal, EditorFieldFunc, bool* isHovered, RangeAttribute^) + { + // Prevent issues where String^ is null due to being empty + if (managedVal == nullptr) + managedVal = ""; + + // Actual Field + std::string val = Convert::ToNative(managedVal); + if (SHEditorUI::InputTextField(fieldName, val, isHovered)) + { + managedVal = Convert::ToCLI(val); + return true; + } + + return false; + } + template<> + bool Editor::renderFieldInInspector(const std::string& fieldName, GameObject% managedVal, EditorFieldFunc, bool* isHovered, RangeAttribute^) + { + uint32_t entityId = managedVal.GetEntity(); + if (SHEditorUI::InputGameObjectField(fieldName, entityId, isHovered, !managedVal)) + { + GameObject newVal = GameObject(entityId); + if (entityId != MAX_EID) + { + // Null GameObject set + newVal = GameObject(entityId); + } + managedVal = newVal; + return true; + } + return false; } } From 5d2aae35615dfd9d296e722c263da119599c3f84 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 11 Nov 2022 13:41:25 +0800 Subject: [PATCH 07/18] Made enum editor template specializations instead --- SHADE_Managed/src/Editor/Editor.cxx | 46 ++++++++++++--------- SHADE_Managed/src/Editor/Editor.h++ | 64 ++++++++++++++++++++--------- SHADE_Managed/src/Editor/Editor.hxx | 47 ++++++++++----------- 3 files changed, 94 insertions(+), 63 deletions(-) diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index c82cc0a1..e02a6acd 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -30,6 +30,7 @@ of DigiPen Institute of Technology is prohibited. #include "RangeAttribute.hxx" #include "Math/Vector2.hxx" #include "Math/Vector3.hxx" +#include // Using Directives using namespace System; @@ -146,6 +147,7 @@ namespace SHADE } SHEditorUI::PopID(); } + void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, System::Object^ object) { bool isHovered = false; @@ -164,30 +166,13 @@ namespace SHADE renderFieldInInspector(field, object, SHEditorUI::InputVec2 , &isHovered) || renderFieldInInspector(field, object, SHEditorUI::InputVec3 , &isHovered) || renderFieldInInspector(field, object, nullptr , &isHovered) || - renderFieldInInspector(field, object, nullptr , &isHovered); + renderFieldInInspector(field, object, nullptr , &isHovered) || + renderFieldInInspector (field, object, nullptr , &isHovered); if (!MODIFIED_PRIMITIVE) { - if (field->FieldType->IsSubclassOf(Enum::typeid)) - { - // Get all the names of the enums - const array^ ENUM_NAMES = field->FieldType->GetEnumNames(); - std::vector nativeEnumNames; - for each (String ^ str in ENUM_NAMES) - { - nativeEnumNames.emplace_back(Convert::ToNative(str)); - } - - int val = safe_cast(field->GetValue(object)); - int oldVal = val; - if (SHEditorUI::InputEnumCombo(Convert::ToNative(field->Name), val, nativeEnumNames, &isHovered)) - { - field->SetValue(object, val); - registerUndoAction(object, field, val, oldVal); - } - } // Any List - else if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) + if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) { System::Type^ listType = field->FieldType->GenericTypeArguments[0]; System::Collections::IEnumerable^ listEnummerable = safe_cast(field->GetValue(object)); @@ -291,6 +276,27 @@ namespace SHADE } } + bool Editor::renderEnumFieldInInspector(const std::string& fieldName, System::Object^% object, bool* isHovered) + { + // Get all the names of the enums + const array^ ENUM_NAMES = object->GetType()->GetEnumNames(); + std::vector nativeEnumNames; + for each (String ^ str in ENUM_NAMES) + { + nativeEnumNames.emplace_back(Convert::ToNative(str)); + } + + int val = safe_cast(object); + int oldVal = val; + if (SHEditorUI::InputEnumCombo(fieldName, val, nativeEnumNames, isHovered)) + { + object = val; + return true; + } + + return false; + } + void Editor::renderScriptContextMenu(Entity entity, Script^ script) { // Right Click Menu diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ index 46993f88..501a75ae 100644 --- a/SHADE_Managed/src/Editor/Editor.h++ +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -26,30 +26,54 @@ namespace SHADE template bool Editor::renderFieldInInspector(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered) { - if (fieldInfo->FieldType == ManagedType::typeid) + if constexpr (std::is_same_v) { - RangeAttribute^ rangeAttrib; - if constexpr (std::is_arithmetic_v && !std::is_same_v) + if (fieldInfo->FieldType->IsSubclassOf(Enum::typeid)) { - rangeAttrib = hasAttribute(fieldInfo); - } + System::Object^ enumObj = fieldInfo->GetValue(object); + int oldVal = safe_cast(enumObj); + int val = oldVal; + if (renderEnumFieldInInspector + ( + Convert::ToNative(fieldInfo->Name), + enumObj, + isHovered + )) + { + fieldInfo->SetValue(object, safe_cast(enumObj)); + registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal); + } - ManagedType oldVal = safe_cast(fieldInfo->GetValue(object)); - ManagedType val = oldVal; - if (renderFieldInInspector - ( - Convert::ToNative(fieldInfo->Name), - val, - fieldEditor, - isHovered, - rangeAttrib - )) - { - fieldInfo->SetValue(object, val); - registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal); + return true; + } + } + else + { + if (fieldInfo->FieldType == ManagedType::typeid) + { + RangeAttribute^ rangeAttrib; + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + rangeAttrib = hasAttribute(fieldInfo); + } + + ManagedType oldVal = safe_cast(fieldInfo->GetValue(object)); + ManagedType val = oldVal; + if (renderFieldInInspector + ( + Convert::ToNative(fieldInfo->Name), + val, + fieldEditor, + isHovered, + rangeAttrib + )) + { + fieldInfo->SetValue(object, val); + registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal); + } + + return true; } - - return true; } return false; diff --git a/SHADE_Managed/src/Editor/Editor.hxx b/SHADE_Managed/src/Editor/Editor.hxx index 1f2c1be7..c9c915f8 100644 --- a/SHADE_Managed/src/Editor/Editor.hxx +++ b/SHADE_Managed/src/Editor/Editor.hxx @@ -90,29 +90,7 @@ namespace SHADE /// The object that contains the data of the field to render. /// static void renderFieldInInspector(System::Reflection::FieldInfo^ field, System::Object^ object); - /// - /// Renders a context menu when right clicked for the scripts - /// - /// The Entity to render the Scripts of. - /// The Script to render the inspector for. - static void renderScriptContextMenu(Entity entity, Script^ script); - /// - /// Adds changes to a variable as an undo-able/redo-able action on the Undo-Redo - /// stack. - /// - /// The object that changes are applied to. - /// The field that was changed. - /// New data to set. - /// Data that was overriden. - static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData); - /// - /// Checks if a specific field has the specified attribute - /// - /// Type of Attribute to check for. - /// The field to check. - /// The attribute to check for if it exists. Null otherwise. - generic where Attribute : System::Attribute - static Attribute hasAttribute(System::Reflection::FieldInfo^ field); + static bool renderEnumFieldInInspector(const std::string& fieldName, System::Object^% object, bool* isHovered); /// /// 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 @@ -149,6 +127,29 @@ namespace SHADE /// True if the field is modified. template static bool renderFieldInInspector(const std::string& fieldName, ManagedType% managedVal, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); + /// + /// Renders a context menu when right clicked for the scripts + /// + /// The Entity to render the Scripts of. + /// The Script to render the inspector for. + static void renderScriptContextMenu(Entity entity, Script^ script); + /// + /// Adds changes to a variable as an undo-able/redo-able action on the Undo-Redo + /// stack. + /// + /// The object that changes are applied to. + /// The field that was changed. + /// New data to set. + /// Data that was overriden. + static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData); + /// + /// Checks if a specific field has the specified attribute + /// + /// Type of Attribute to check for. + /// The field to check. + /// The attribute to check for if it exists. Null otherwise. + generic where Attribute : System::Attribute + static Attribute hasAttribute(System::Reflection::FieldInfo^ field); }; } #include "Editor.h++" From d98d00b916908b95fc8ff938b4d9feb02c93f1ba Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 11 Nov 2022 15:20:14 +0800 Subject: [PATCH 08/18] Wonky solution for array editor that doesn't work for elements outside of the first --- SHADE_Managed/src/Editor/Editor.cxx | 39 ++++++++++++++---- SHADE_Managed/src/Editor/Editor.h++ | 64 ++++++++++++++++++++++------- SHADE_Managed/src/Editor/Editor.hxx | 5 ++- 3 files changed, 84 insertions(+), 24 deletions(-) diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index e02a6acd..ef5557c6 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -167,7 +167,7 @@ namespace SHADE renderFieldInInspector(field, object, SHEditorUI::InputVec3 , &isHovered) || renderFieldInInspector(field, object, nullptr , &isHovered) || renderFieldInInspector(field, object, nullptr , &isHovered) || - renderFieldInInspector (field, object, nullptr , &isHovered); + renderFieldInInspector(field, object, nullptr , &isHovered); if (!MODIFIED_PRIMITIVE) { @@ -175,22 +175,23 @@ namespace SHADE if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) { System::Type^ listType = field->FieldType->GenericTypeArguments[0]; - System::Collections::IEnumerable^ listEnummerable = safe_cast(field->GetValue(object)); - + RangeAttribute^ rangeAttrib = hasAttribute(field); + System::Collections::IList^ iList = safe_cast(field->GetValue(object)); SHEditorUI::Text(Convert::ToNative(field->Name)); SHEditorUI::SameLine(); SHEditorUI::Button("+"); SHEditorUI::Indent(); - int i = 0; - for each (System::Object ^ obj in listEnummerable) + for (int i = 0; i < iList->Count; ++i) { - int val = safe_cast(obj); - SHEditorUI::InputInt(std::to_string(i), val, &isHovered); + System::Object^ obj = iList[i]; + if (renderFieldInInspector(std::to_string(i), obj, rangeAttrib)) + { + iList[i] = obj; + } SHEditorUI::SameLine(); SHEditorUI::Button("-"); - ++i; } SHEditorUI::Unindent(); } @@ -276,6 +277,28 @@ namespace SHADE } } + bool Editor::renderFieldInInspector(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib) + { + const bool MODIFIED_PRIMITIVE = + renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputCheckbox, nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputFloat , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputDouble , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputVec2 , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, SHEditorUI::InputVec3 , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, nullptr , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, nullptr , nullptr, rangeAttrib) || + renderFieldInInspector(fieldName, object, nullptr , nullptr, rangeAttrib); + + return MODIFIED_PRIMITIVE; + } + bool Editor::renderEnumFieldInInspector(const std::string& fieldName, System::Object^% object, bool* isHovered) { // Get all the names of the enums diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ index 501a75ae..ead03f49 100644 --- a/SHADE_Managed/src/Editor/Editor.h++ +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -62,7 +62,7 @@ namespace SHADE if (renderFieldInInspector ( Convert::ToNative(fieldInfo->Name), - val, + &val, fieldEditor, isHovered, rangeAttrib @@ -78,19 +78,53 @@ namespace SHADE return false; } + + template + bool Editor::renderFieldInInspector(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) + { + if constexpr (std::is_same_v) + { + if (object->GetType()->IsSubclassOf(Enum::typeid)) + { + int managedVal = safe_cast(object); + if (renderFieldInInspector(fieldName, &managedVal, fieldEditor, isHovered, rangeAttrib)) + { + object = managedVal; + } + return true; + } + } + else + { + if (object->GetType() == ManagedType::typeid) + { + ManagedType managedVal = safe_cast(object); + cli::interior_ptr managedValPtr = &managedVal; + if (renderFieldInInspector(fieldName, managedValPtr, fieldEditor, isHovered, rangeAttrib)) + { + object = managedVal; + return true; + } + return false; + } + } + + return false; + } + template - bool Editor::renderFieldInInspector(const std::string& fieldName, ManagedType% managedVal, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) + bool Editor::renderFieldInInspector(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) { // Retrieve the native version of the object NativeType val; if constexpr (IsPrimitiveTypeMatches_V) { - val = safe_cast(managedVal); + val = safe_cast(*managedValPtr); } else { - val = Convert::ToNative(managedVal); + val = Convert::ToNative(*managedValPtr); } // Throw into the SHEditorUI function @@ -119,12 +153,12 @@ namespace SHADE { if constexpr (IsPrimitiveTypeMatches_V) { - managedVal = val; + *managedValPtr = val; } else { - managedVal = Convert::ToCLI(val); + *managedValPtr = Convert::ToCLI(val); } return true; @@ -133,27 +167,27 @@ namespace SHADE return false; } template<> - bool Editor::renderFieldInInspector(const std::string& fieldName, System::String^% managedVal, EditorFieldFunc, bool* isHovered, RangeAttribute^) + bool Editor::renderFieldInInspector(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc, bool* isHovered, RangeAttribute^) { // Prevent issues where String^ is null due to being empty - if (managedVal == nullptr) - managedVal = ""; + if (*managedValPtr == nullptr) + *managedValPtr = ""; // Actual Field - std::string val = Convert::ToNative(managedVal); + std::string val = Convert::ToNative(*managedValPtr); if (SHEditorUI::InputTextField(fieldName, val, isHovered)) { - managedVal = Convert::ToCLI(val); + *managedValPtr = Convert::ToCLI(val); return true; } return false; } template<> - bool Editor::renderFieldInInspector(const std::string& fieldName, GameObject% managedVal, EditorFieldFunc, bool* isHovered, RangeAttribute^) + bool Editor::renderFieldInInspector(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc, bool* isHovered, RangeAttribute^) { - uint32_t entityId = managedVal.GetEntity(); - if (SHEditorUI::InputGameObjectField(fieldName, entityId, isHovered, !managedVal)) + uint32_t entityId = managedValPtr->GetEntity(); + if (SHEditorUI::InputGameObjectField(fieldName, entityId, isHovered, !(*managedValPtr))) { GameObject newVal = GameObject(entityId); if (entityId != MAX_EID) @@ -161,7 +195,7 @@ namespace SHADE // Null GameObject set newVal = GameObject(entityId); } - managedVal = newVal; + *managedValPtr = newVal; return true; } diff --git a/SHADE_Managed/src/Editor/Editor.hxx b/SHADE_Managed/src/Editor/Editor.hxx index c9c915f8..f9bf751f 100644 --- a/SHADE_Managed/src/Editor/Editor.hxx +++ b/SHADE_Managed/src/Editor/Editor.hxx @@ -90,6 +90,7 @@ namespace SHADE /// The object that contains the data of the field to render. /// static void renderFieldInInspector(System::Reflection::FieldInfo^ field, System::Object^ object); + static bool renderFieldInInspector(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib); static bool renderEnumFieldInInspector(const std::string& fieldName, System::Object^% object, bool* isHovered); /// /// Checks if the specified field is of the specified native and managed type @@ -126,7 +127,9 @@ namespace SHADE /// /// True if the field is modified. template - static bool renderFieldInInspector(const std::string& fieldName, ManagedType% managedVal, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); + static bool renderFieldInInspector(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); + template + static bool renderFieldInInspector(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); /// /// Renders a context menu when right clicked for the scripts /// From dfc03839dbfa0910479aa77402c9c83f03b0d9d0 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 11 Nov 2022 22:18:32 +0800 Subject: [PATCH 09/18] Renamed functions for clarity --- SHADE_Managed/src/Editor/Editor.cxx | 68 ++++++++++++++--------------- SHADE_Managed/src/Editor/Editor.h++ | 18 ++++---- SHADE_Managed/src/Editor/Editor.hxx | 29 +++++++++--- 3 files changed, 67 insertions(+), 48 deletions(-) diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index ef5557c6..d26bd252 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -153,28 +153,28 @@ namespace SHADE bool isHovered = false; const bool MODIFIED_PRIMITIVE = - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputCheckbox, &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputFloat , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputDouble , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputVec2 , &isHovered) || - renderFieldInInspector(field, object, SHEditorUI::InputVec3 , &isHovered) || - renderFieldInInspector(field, object, nullptr , &isHovered) || - renderFieldInInspector(field, object, nullptr , &isHovered) || - renderFieldInInspector(field, object, nullptr , &isHovered); + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputCheckbox, &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputFloat , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputDouble , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputVec2 , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputVec3 , &isHovered) || + renderSpecificField(field, object, nullptr , &isHovered) || + renderSpecificField(field, object, nullptr , &isHovered) || + renderSpecificField(field, object, nullptr , &isHovered); if (!MODIFIED_PRIMITIVE) { // Any List if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) { - System::Type^ listType = field->FieldType->GenericTypeArguments[0]; + /* System::Type^ listType = field->FieldType->GenericTypeArguments[0]; RangeAttribute^ rangeAttrib = hasAttribute(field); System::Collections::IList^ iList = safe_cast(field->GetValue(object)); @@ -193,7 +193,7 @@ namespace SHADE SHEditorUI::SameLine(); SHEditorUI::Button("-"); } - SHEditorUI::Unindent(); + SHEditorUI::Unindent();*/ } else { @@ -277,29 +277,29 @@ namespace SHADE } } - bool Editor::renderFieldInInspector(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib) + bool Editor::renderFieldEditor(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib) { const bool MODIFIED_PRIMITIVE = - renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputCheckbox, nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputFloat , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputDouble , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputVec2 , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, SHEditorUI::InputVec3 , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, nullptr , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, nullptr , nullptr, rangeAttrib) || - renderFieldInInspector(fieldName, object, nullptr , nullptr, rangeAttrib); + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputCheckbox, nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputFloat , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputDouble , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputVec2 , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, SHEditorUI::InputVec3 , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib) || + renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib); return MODIFIED_PRIMITIVE; } - bool Editor::renderEnumFieldInInspector(const std::string& fieldName, System::Object^% object, bool* isHovered) + bool Editor::renderEnumEditor(const std::string& fieldName, System::Object^% object, bool* isHovered) { // Get all the names of the enums const array^ ENUM_NAMES = object->GetType()->GetEnumNames(); diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ index ead03f49..8fbc6348 100644 --- a/SHADE_Managed/src/Editor/Editor.h++ +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -24,7 +24,7 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { template - bool Editor::renderFieldInInspector(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered) + bool Editor::renderSpecificField(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered) { if constexpr (std::is_same_v) { @@ -33,7 +33,7 @@ namespace SHADE System::Object^ enumObj = fieldInfo->GetValue(object); int oldVal = safe_cast(enumObj); int val = oldVal; - if (renderEnumFieldInInspector + if (renderEnumEditor ( Convert::ToNative(fieldInfo->Name), enumObj, @@ -59,7 +59,7 @@ namespace SHADE ManagedType oldVal = safe_cast(fieldInfo->GetValue(object)); ManagedType val = oldVal; - if (renderFieldInInspector + if (renderFieldEditorInternal ( Convert::ToNative(fieldInfo->Name), &val, @@ -80,14 +80,14 @@ namespace SHADE } template - bool Editor::renderFieldInInspector(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) + bool Editor::renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) { if constexpr (std::is_same_v) { if (object->GetType()->IsSubclassOf(Enum::typeid)) { int managedVal = safe_cast(object); - if (renderFieldInInspector(fieldName, &managedVal, fieldEditor, isHovered, rangeAttrib)) + if (renderFieldEditorInternal(fieldName, &managedVal, fieldEditor, isHovered, rangeAttrib)) { object = managedVal; } @@ -100,7 +100,7 @@ namespace SHADE { ManagedType managedVal = safe_cast(object); cli::interior_ptr managedValPtr = &managedVal; - if (renderFieldInInspector(fieldName, managedValPtr, fieldEditor, isHovered, rangeAttrib)) + if (renderFieldEditorInternal(fieldName, managedValPtr, fieldEditor, isHovered, rangeAttrib)) { object = managedVal; return true; @@ -114,7 +114,7 @@ namespace SHADE template - bool Editor::renderFieldInInspector(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) + bool Editor::renderFieldEditorInternal(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) { // Retrieve the native version of the object NativeType val; @@ -167,7 +167,7 @@ namespace SHADE return false; } template<> - bool Editor::renderFieldInInspector(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc, bool* isHovered, RangeAttribute^) + bool Editor::renderFieldEditorInternal(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc, bool* isHovered, RangeAttribute^) { // Prevent issues where String^ is null due to being empty if (*managedValPtr == nullptr) @@ -184,7 +184,7 @@ namespace SHADE return false; } template<> - bool Editor::renderFieldInInspector(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc, bool* isHovered, RangeAttribute^) + bool Editor::renderFieldEditorInternal(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc, bool* isHovered, RangeAttribute^) { uint32_t entityId = managedValPtr->GetEntity(); if (SHEditorUI::InputGameObjectField(fieldName, entityId, isHovered, !(*managedValPtr))) diff --git a/SHADE_Managed/src/Editor/Editor.hxx b/SHADE_Managed/src/Editor/Editor.hxx index f9bf751f..54ab128d 100644 --- a/SHADE_Managed/src/Editor/Editor.hxx +++ b/SHADE_Managed/src/Editor/Editor.hxx @@ -90,8 +90,8 @@ namespace SHADE /// The object that contains the data of the field to render. /// static void renderFieldInInspector(System::Reflection::FieldInfo^ field, System::Object^ object); - static bool renderFieldInInspector(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib); - static bool renderEnumFieldInInspector(const std::string& fieldName, System::Object^% object, bool* isHovered); + static bool renderFieldEditor(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib); + static bool renderEnumEditor(const std::string& fieldName, System::Object^% object, bool* isHovered); /// /// 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 @@ -107,7 +107,7 @@ namespace SHADE /// /// True if the field is modified. template - static bool renderFieldInInspector(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered); + static bool renderSpecificField(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered); /// /// Renders a ImGui field editor based on the type of parameters specified. /// @@ -127,9 +127,28 @@ namespace SHADE /// /// True if the field is modified. template - static bool renderFieldInInspector(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); + static bool renderFieldEditorInternal(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); + /// + /// Renders a ImGui field editor based on the type of parameters specified. + /// + /// Native type of the field. + /// Managed type of the field. + /// Label to use for the field editor. + /// + /// Tracking reference for the managed variable to modify. + /// + /// ImGui field editor function to use. + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// + /// If provided and the type supports it, the field will be rendered with a + /// slider instead. + /// + /// True if the field is modified. template - static bool renderFieldInInspector(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); + static bool renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); + /// /// Renders a context menu when right clicked for the scripts /// From 2d2cc532a5446ddec3be23852fa0182be6f80f45 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 11 Nov 2022 22:59:45 +0800 Subject: [PATCH 10/18] Fixed editing intermdiate list values not working --- SHADE_Managed/src/Editor/Editor.cxx | 8 +++++--- SHADE_Managed/src/Editor/Editor.h++ | 6 +----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index d26bd252..7648e2aa 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -174,7 +174,7 @@ namespace SHADE // Any List if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) { - /* System::Type^ listType = field->FieldType->GenericTypeArguments[0]; + System::Type^ listType = field->FieldType->GenericTypeArguments[0]; RangeAttribute^ rangeAttrib = hasAttribute(field); System::Collections::IList^ iList = safe_cast(field->GetValue(object)); @@ -185,15 +185,17 @@ namespace SHADE SHEditorUI::Indent(); for (int i = 0; i < iList->Count; ++i) { + SHEditorUI::PushID(i); System::Object^ obj = iList[i]; - if (renderFieldInInspector(std::to_string(i), obj, rangeAttrib)) + if (renderFieldEditor(std::to_string(i), obj, rangeAttrib)) { iList[i] = obj; } SHEditorUI::SameLine(); SHEditorUI::Button("-"); + SHEditorUI::PopID(); } - SHEditorUI::Unindent();*/ + SHEditorUI::Unindent(); } else { diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ index 8fbc6348..009160ce 100644 --- a/SHADE_Managed/src/Editor/Editor.h++ +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -86,11 +86,7 @@ namespace SHADE { if (object->GetType()->IsSubclassOf(Enum::typeid)) { - int managedVal = safe_cast(object); - if (renderFieldEditorInternal(fieldName, &managedVal, fieldEditor, isHovered, rangeAttrib)) - { - object = managedVal; - } + renderEnumEditor(fieldName, object, isHovered); return true; } } From 543c199b03a7889e3ef85a94034dc6f1acecdf06 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Sat, 12 Nov 2022 02:33:00 +0800 Subject: [PATCH 11/18] Fixed lists undo not working --- SHADE_Managed/src/Editor/Editor.cxx | 66 ++++++++++++++++------ SHADE_Managed/src/Editor/Editor.h++ | 7 ++- SHADE_Managed/src/Editor/Editor.hxx | 65 ++++++++++++++------- SHADE_Managed/src/Editor/UndoRedoStack.cxx | 51 +++++++---------- SHADE_Managed/src/Editor/UndoRedoStack.hxx | 24 ++++---- 5 files changed, 129 insertions(+), 84 deletions(-) diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index 7648e2aa..d1672929 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -180,6 +180,7 @@ namespace SHADE SHEditorUI::Text(Convert::ToNative(field->Name)); SHEditorUI::SameLine(); + SHEditorUI::Button("+"); SHEditorUI::Indent(); @@ -187,9 +188,11 @@ namespace SHADE { SHEditorUI::PushID(i); System::Object^ obj = iList[i]; + System::Object^ oldObj = iList[i]; if (renderFieldEditor(std::to_string(i), obj, rangeAttrib)) { iList[i] = obj; + registerUndoListChangeAction(listType, iList, i, obj, oldObj); } SHEditorUI::SameLine(); SHEditorUI::Button("-"); @@ -281,24 +284,26 @@ namespace SHADE bool Editor::renderFieldEditor(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib) { - const bool MODIFIED_PRIMITIVE = - renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputCheckbox, nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputFloat , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputDouble , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputVec2 , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, SHEditorUI::InputVec3 , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib) || - renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib); + bool modified; - return MODIFIED_PRIMITIVE; + const bool RENDERED = + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputCheckbox, nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputFloat , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputDouble , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputVec2 , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputVec3 , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib, modified); + + return modified; } bool Editor::renderEnumEditor(const std::string& fieldName, System::Object^% object, bool* isHovered) @@ -345,6 +350,33 @@ namespace SHADE SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); } + 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(std::make_shared())); + } + + 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)); + } + + 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)); + } + generic Attribute Editor::hasAttribute(System::Reflection::FieldInfo^ field) { diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ index 009160ce..a186d7ea 100644 --- a/SHADE_Managed/src/Editor/Editor.h++ +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -80,13 +80,15 @@ namespace SHADE } template - bool Editor::renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) + bool Editor::renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib, bool& modified) { + modified = false; + if constexpr (std::is_same_v) { if (object->GetType()->IsSubclassOf(Enum::typeid)) { - renderEnumEditor(fieldName, object, isHovered); + modified = renderEnumEditor(fieldName, object, isHovered); return true; } } @@ -99,6 +101,7 @@ namespace SHADE if (renderFieldEditorInternal(fieldName, managedValPtr, fieldEditor, isHovered, rangeAttrib)) { object = managedVal; + modified = true; return true; } return false; diff --git a/SHADE_Managed/src/Editor/Editor.hxx b/SHADE_Managed/src/Editor/Editor.hxx index 54ab128d..64c445e5 100644 --- a/SHADE_Managed/src/Editor/Editor.hxx +++ b/SHADE_Managed/src/Editor/Editor.hxx @@ -90,7 +90,49 @@ namespace SHADE /// The object that contains the data of the field to render. /// static void renderFieldInInspector(System::Reflection::FieldInfo^ field, System::Object^ object); + /// + /// Renders a raw editor for a single value. + /// + /// The name of the field to render. + /// Tracking reference to the object to modify. + /// + /// If specified, will be used to constrain values. + /// + /// True if the value was modified. static bool renderFieldEditor(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib); + /// + /// Renders a ImGui field editor based on the type of parameters specified if the + /// type matches. + /// + /// Native type of the field. + /// Managed type of the field. + /// Label to use for the field editor. + /// + /// Tracking reference for the managed variable to modify. + /// + /// ImGui field editor function to use. + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// + /// If provided and the type supports it, the field will be rendered with a + /// slider instead. + /// + /// + /// True if the field was rendered.. + template + static bool renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib, bool& modified); + /// + /// Renders a raw editor for a single enum value. + /// + /// The name of the field to render. + /// + /// Tracking reference to the object to modify. Must be an enum. + /// + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// True if the value was modified. static bool renderEnumEditor(const std::string& fieldName, System::Object^% object, bool* isHovered); /// /// Checks if the specified field is of the specified native and managed type @@ -128,26 +170,6 @@ namespace SHADE /// True if the field is modified. template static bool renderFieldEditorInternal(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); - /// - /// Renders a ImGui field editor based on the type of parameters specified. - /// - /// Native type of the field. - /// Managed type of the field. - /// Label to use for the field editor. - /// - /// Tracking reference for the managed variable to modify. - /// - /// ImGui field editor function to use. - /// - /// Pointer to a bool that stores if the field editor was hovered over. - /// - /// - /// If provided and the type supports it, the field will be rendered with a - /// slider instead. - /// - /// True if the field is modified. - template - static bool renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); /// /// Renders a context menu when right clicked for the scripts @@ -164,6 +186,9 @@ namespace SHADE /// New data to set. /// Data that was overriden. 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); /// /// Checks if a specific field has the specified attribute /// diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.cxx b/SHADE_Managed/src/Editor/UndoRedoStack.cxx index 10ef822c..789d285d 100644 --- a/SHADE_Managed/src/Editor/UndoRedoStack.cxx +++ b/SHADE_Managed/src/Editor/UndoRedoStack.cxx @@ -132,10 +132,9 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* ListElementChangeCommand - Constructor */ /*---------------------------------------------------------------------------------*/ - generic - ListElementChangeCommand::ListElementChangeCommand(System::Collections::Generic::List^ list, int index, T newData, T oldData) + ListElementChangeCommand::ListElementChangeCommand(System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData) : list { list } - , index{ index } + , index { index } , newData { newData } , oldData { oldData } {} @@ -143,10 +142,9 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* ListElementChangeCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ - generic - bool ListElementChangeCommand::Execute() + bool ListElementChangeCommand::Execute() { - if (list && index < System::Linq::Enumerable::Count(list)) + if (list && index < list->Count) { list[index] = newData; return true; @@ -154,11 +152,10 @@ namespace SHADE return false; } - - generic - bool ListElementChangeCommand::Unexceute() + + bool ListElementChangeCommand::Unexceute() { - if (list && index < System::Linq::Enumerable::Count(list)) + if (list && index < list->Count) { list[index] = oldData; return true; @@ -166,11 +163,9 @@ namespace SHADE return false; } - - generic - bool ListElementChangeCommand::Merge(ICommand^ command) + bool ListElementChangeCommand::Merge(ICommand^ command) { - ListElementChangeCommand^ otherCommand = safe_cast^>(command); + ListElementChangeCommand^ otherCommand = safe_cast(command); if (otherCommand == nullptr) { Debug::LogWarning("[Field Change Command] Attempted to merge two incompatible commands!"); @@ -187,8 +182,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* ListElementAddCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ - generic - ListElementAddCommand::ListElementAddCommand(System::Collections::Generic::List^ list, int addIndex, T data) + ListElementAddCommand::ListElementAddCommand(System::Collections::IList^ list, int addIndex, System::Object^ data) : list { list } , addIndex { addIndex } , data { data } @@ -197,8 +191,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* ListElementAddCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ - generic - bool ListElementAddCommand::Execute() + bool ListElementAddCommand::Execute() { if (list) { @@ -209,10 +202,9 @@ namespace SHADE return false; } - generic - bool ListElementAddCommand::Unexceute() + bool ListElementAddCommand::Unexceute() { - if (list && addIndex < System::Linq::Enumerable::Count(list)) + if (list && addIndex < list->Count) { list->RemoveAt(addIndex); return true; @@ -221,8 +213,7 @@ namespace SHADE return false; } - generic - bool ListElementAddCommand::Merge(ICommand^) + bool ListElementAddCommand::Merge(ICommand^) { // Not allowed return false; @@ -231,8 +222,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* ListElementRemoveCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ - generic - ListElementRemoveCommand::ListElementRemoveCommand(System::Collections::Generic::List^ list, int removeIndex, T data) + ListElementRemoveCommand::ListElementRemoveCommand(System::Collections::IList^ list, int removeIndex, System::Object^ data) : list { list } , removeIndex { removeIndex } , data { data } @@ -241,10 +231,9 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* ListElementRemoveCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ - generic - bool ListElementRemoveCommand::Execute() + bool ListElementRemoveCommand::Execute() { - if (list && removeIndex < System::Linq::Enumerable::Count(list)) + if (list && removeIndex < list->Count) { list->RemoveAt(removeIndex); return true; @@ -253,8 +242,7 @@ namespace SHADE return false; } - generic - bool ListElementRemoveCommand::Unexceute() + bool ListElementRemoveCommand::Unexceute() { if (list) { @@ -265,8 +253,7 @@ namespace SHADE return false; } - generic - bool ListElementRemoveCommand::Merge(ICommand^) + bool ListElementRemoveCommand::Merge(ICommand^) { // Not allowed return false; diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.hxx b/SHADE_Managed/src/Editor/UndoRedoStack.hxx index ed9a625a..dea458bc 100644 --- a/SHADE_Managed/src/Editor/UndoRedoStack.hxx +++ b/SHADE_Managed/src/Editor/UndoRedoStack.hxx @@ -55,53 +55,51 @@ namespace SHADE System::Object^ oldData; }; - generic + private ref class ListElementChangeCommand sealed : public ICommand { public: - ListElementChangeCommand(System::Collections::Generic::List^ list, int index, T newData, T oldData); + 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::Generic::List^ list; + System::Collections::IList^ list; int index; - T newData; - T oldData; + System::Object^ newData; + System::Object^ oldData; }; - generic private ref class ListElementAddCommand sealed : public ICommand { public: - ListElementAddCommand(System::Collections::Generic::List^ list, int addIndex, T data); + ListElementAddCommand(System::Collections::IList^ list, int addIndex, System::Object^ data); bool Execute() override; bool Unexceute() override; bool Merge(ICommand^ command) override; private: - System::Collections::Generic::List^ list; + System::Collections::IList^ list; int addIndex; // New index of the added element - T data; + System::Object^ data; }; - generic private ref class ListElementRemoveCommand sealed : public ICommand { public: - ListElementRemoveCommand(System::Collections::Generic::List^ list, int removeIndex, T data); + ListElementRemoveCommand(System::Collections::IList^ list, int removeIndex, System::Object^ data); bool Execute() override; bool Unexceute() override; bool Merge(ICommand^ command) override; private: - System::Collections::Generic::List^ list; + System::Collections::IList^ list; int removeIndex; // Index of the element to remove at - T data; + System::Object^ data; }; /// From 4c01d68f958144b85598c7ec21baa96fdda4d99d Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Sat, 12 Nov 2022 02:33:12 +0800 Subject: [PATCH 12/18] Added list editor tests --- Assets/Scripts/RaccoonShowcase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Scripts/RaccoonShowcase.cs b/Assets/Scripts/RaccoonShowcase.cs index 836b93d0..75061b82 100644 --- a/Assets/Scripts/RaccoonShowcase.cs +++ b/Assets/Scripts/RaccoonShowcase.cs @@ -1,5 +1,6 @@ using SHADE; using System; +using System.Collections.Generic; public class RaccoonShowcase : Script { @@ -17,6 +18,9 @@ public class RaccoonShowcase : Script private double rotation = 0.0; private Vector3 scale = Vector3.Zero; private double originalScale = 1.0f; + public List vecList = new List(new Vector3[] { new Vector3(1, 2, 3), new Vector3(4, 5, 6) }); + public List intList = new List(new int[] { 2, 8, 2, 6, 8, 0, 1 }); + public List enumList = new List(new Light.Type[] { Light.Type.Point, Light.Type.Directional, Light.Type.Ambient }); public RaccoonShowcase(GameObject gameObj) : base(gameObj) {} protected override void awake() From d6764b45515465ab6a29fb6dbd0905a70a536091 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Sat, 12 Nov 2022 03:25:46 +0800 Subject: [PATCH 13/18] Added support for adding and removing elements from a list --- SHADE_Managed/src/Editor/Editor.cxx | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index d1672929..c481e0b8 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -181,7 +181,12 @@ namespace SHADE SHEditorUI::Text(Convert::ToNative(field->Name)); SHEditorUI::SameLine(); - SHEditorUI::Button("+"); + if (SHEditorUI::Button("+")) + { + System::Object^ obj = System::Activator::CreateInstance(listType); + iList->Add(obj); + registerUndoListAddAction(listType, iList, iList->Count - 1, obj); + } SHEditorUI::Indent(); for (int i = 0; i < iList->Count; ++i) @@ -195,7 +200,14 @@ namespace SHADE registerUndoListChangeAction(listType, iList, i, obj, oldObj); } SHEditorUI::SameLine(); - SHEditorUI::Button("-"); + if (SHEditorUI::Button("-")) + { + System::Object^ obj = iList[i]; + iList->RemoveAt(i); + registerUndoListRemoveAction(listType, iList, i, obj); + SHEditorUI::PopID(); + break; + } SHEditorUI::PopID(); } SHEditorUI::Unindent(); @@ -367,6 +379,9 @@ namespace SHADE return; actionStack.Add(gcnew ListElementAddCommand(list, index, data)); + + // Inform the C++ Undo-Redo stack + SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); } void Editor::registerUndoListRemoveAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ data) @@ -375,6 +390,9 @@ namespace SHADE return; actionStack.Add(gcnew ListElementRemoveCommand(list, index, data)); + + // Inform the C++ Undo-Redo stack + SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); } generic From 6dbda12f3009bed1c65d1c9284b60818a898332c Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Sat, 12 Nov 2022 03:53:30 +0800 Subject: [PATCH 14/18] Adjusted design of list on scripts and all numbers now use sliders --- SHADE_Engine/src/Editor/SHEditorUI.cpp | 41 ++++++++++------------ SHADE_Engine/src/Editor/SHEditorUI.h | 11 ------ SHADE_Managed/src/Editor/Editor.cxx | 47 ++++++++++++-------------- 3 files changed, 39 insertions(+), 60 deletions(-) diff --git a/SHADE_Engine/src/Editor/SHEditorUI.cpp b/SHADE_Engine/src/Editor/SHEditorUI.cpp index ba394f77..06c3f5c5 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.cpp +++ b/SHADE_Engine/src/Editor/SHEditorUI.cpp @@ -75,7 +75,7 @@ namespace SHADE 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) @@ -143,7 +143,7 @@ namespace SHADE 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) @@ -165,8 +165,10 @@ namespace SHADE if (isHovered) *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); - return ImGui::InputInt("##", &value, - 1, 10, + return ImGui::DragInt("##", &value, 0.001f, + std::numeric_limits::min(), + std::numeric_limits::max(), + "%d", ImGuiInputTextFlags_EnterReturnsTrue); } bool SHEditorUI::InputUnsignedInt(const std::string& label, unsigned int& value, bool* isHovered) @@ -190,31 +192,22 @@ namespace SHADE if (isHovered) *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); - return ImGui::InputFloat("##", &value, - 0.1f, 1.0f, "%.3f", + return ImGui::DragFloat("##", &value, 0.001f, + std::numeric_limits::lowest(), + std::numeric_limits::max(), + "%.3f", ImGuiInputTextFlags_EnterReturnsTrue); } bool SHEditorUI::InputDouble(const std::string& label, double& value, bool* isHovered) { - ImGui::Text(label.c_str()); - if (isHovered) - *isHovered = ImGui::IsItemHovered(); - ImGui::SameLine(); - return ImGui::InputDouble("##", &value, - 0.1, 1.0, "%.3f", - ImGuiInputTextFlags_EnterReturnsTrue); + float val = value; + const bool CHANGED = InputFloat(label, val, isHovered); + if (CHANGED) + { + value = static_cast(val); + } + 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*/) { ImGui::Text(label.c_str()); diff --git a/SHADE_Engine/src/Editor/SHEditorUI.h b/SHADE_Engine/src/Editor/SHEditorUI.h index e0ea0521..4f11a025 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.h +++ b/SHADE_Engine/src/Editor/SHEditorUI.h @@ -219,17 +219,6 @@ namespace SHADE /// True if the value was changed. static bool InputDouble(const std::string& label, double& value, bool* isHovered = nullptr); /// - /// Creates a decimal field widget for double input with increments of higher - /// steps meant for angle variables. - ///
- /// Wraps up ImGui::InputDouble(). - ///
- /// Label used to identify this widget. - /// Reference to the variable to store the result. - /// Name))) { - System::Object^ obj = System::Activator::CreateInstance(listType); - iList->Add(obj); - registerUndoListAddAction(listType, iList, iList->Count - 1, obj); - } - - SHEditorUI::Indent(); - for (int i = 0; i < iList->Count; ++i) - { - SHEditorUI::PushID(i); - System::Object^ obj = iList[i]; - System::Object^ oldObj = iList[i]; - if (renderFieldEditor(std::to_string(i), obj, rangeAttrib)) + if (SHEditorUI::Button("Add Item")) { - iList[i] = obj; - registerUndoListChangeAction(listType, iList, i, obj, oldObj); + System::Object^ obj = System::Activator::CreateInstance(listType); + iList->Add(obj); + registerUndoListAddAction(listType, iList, iList->Count - 1, obj); } - SHEditorUI::SameLine(); - if (SHEditorUI::Button("-")) + for (int i = 0; i < iList->Count; ++i) { + SHEditorUI::PushID(i); System::Object^ obj = iList[i]; - iList->RemoveAt(i); - registerUndoListRemoveAction(listType, iList, i, obj); + System::Object^ oldObj = iList[i]; + if (renderFieldEditor(std::to_string(i), obj, rangeAttrib)) + { + iList[i] = obj; + registerUndoListChangeAction(listType, iList, i, obj, oldObj); + } + SHEditorUI::SameLine(); + if (SHEditorUI::Button("-")) + { + System::Object^ obj = iList[i]; + iList->RemoveAt(i); + registerUndoListRemoveAction(listType, iList, i, obj); + SHEditorUI::PopID(); + break; + } SHEditorUI::PopID(); - break; } - SHEditorUI::PopID(); } - SHEditorUI::Unindent(); } else { From 24dcd77f3273e4a565e6555f2fe44af6cb9579c6 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Sat, 12 Nov 2022 12:17:11 +0800 Subject: [PATCH 15/18] Added tooltip and range attribute support for lists in scripts --- Assets/Scripts/RaccoonShowcase.cs | 2 ++ SHADE_Engine/src/Editor/SHEditorUI.cpp | 7 +++++-- SHADE_Engine/src/Editor/SHEditorUI.h | 3 ++- SHADE_Managed/src/Editor/Editor.cxx | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Assets/Scripts/RaccoonShowcase.cs b/Assets/Scripts/RaccoonShowcase.cs index 75061b82..dc9d914d 100644 --- a/Assets/Scripts/RaccoonShowcase.cs +++ b/Assets/Scripts/RaccoonShowcase.cs @@ -18,7 +18,9 @@ public class RaccoonShowcase : Script private double rotation = 0.0; private Vector3 scale = Vector3.Zero; private double originalScale = 1.0f; + [Tooltip("Sample list of Vector3s.")] public List vecList = new List(new Vector3[] { new Vector3(1, 2, 3), new Vector3(4, 5, 6) }); + [Range(-5, 5)] public List intList = new List(new int[] { 2, 8, 2, 6, 8, 0, 1 }); public List enumList = new List(new Light.Type[] { Light.Type.Point, Light.Type.Directional, Light.Type.Ambient }); public RaccoonShowcase(GameObject gameObj) : base(gameObj) {} diff --git a/SHADE_Engine/src/Editor/SHEditorUI.cpp b/SHADE_Engine/src/Editor/SHEditorUI.cpp index 06c3f5c5..40e08042 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.cpp +++ b/SHADE_Engine/src/Editor/SHEditorUI.cpp @@ -53,9 +53,12 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* 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() diff --git a/SHADE_Engine/src/Editor/SHEditorUI.h b/SHADE_Engine/src/Editor/SHEditorUI.h index 4f11a025..f450ac0d 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.h +++ b/SHADE_Engine/src/Editor/SHEditorUI.h @@ -85,8 +85,9 @@ namespace SHADE /// Wraps up ImGui::CollapsingHeader(). ///
/// Label for the header. + /// Date: Sat, 12 Nov 2022 23:14:25 +0800 Subject: [PATCH 16/18] Split serialization code into SerialisationUtilities, refactored serialisation code and implemented list serialisation --- SHADE_Managed/src/Editor/Editor.cxx | 2 +- SHADE_Managed/src/Scripts/ScriptStore.cxx | 6 +- .../src/Serialisation/ReflectionUtilities.cxx | 224 +--------------- .../src/Serialisation/ReflectionUtilities.h++ | 55 ---- .../src/Serialisation/ReflectionUtilities.hxx | 42 +-- .../Serialisation/SerialisationUtilities.cxx | 251 ++++++++++++++++++ .../Serialisation/SerialisationUtilities.h++ | 125 +++++++++ .../Serialisation/SerialisationUtilities.hxx | 74 ++++++ 8 files changed, 462 insertions(+), 317 deletions(-) delete mode 100644 SHADE_Managed/src/Serialisation/ReflectionUtilities.h++ create mode 100644 SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx create mode 100644 SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ create mode 100644 SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index ebc39c60..68dddf34 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -172,7 +172,7 @@ namespace SHADE if (!MODIFIED_PRIMITIVE) { // Any List - if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) + if (ReflectionUtilities::FieldIsList(field)) { System::Type^ listType = field->FieldType->GenericTypeArguments[0]; RangeAttribute^ rangeAttrib = hasAttribute(field); diff --git a/SHADE_Managed/src/Scripts/ScriptStore.cxx b/SHADE_Managed/src/Scripts/ScriptStore.cxx index a90b4f12..d11e70c3 100644 --- a/SHADE_Managed/src/Scripts/ScriptStore.cxx +++ b/SHADE_Managed/src/Scripts/ScriptStore.cxx @@ -26,7 +26,7 @@ of DigiPen Institute of Technology is prohibited. #include "Utility/Convert.hxx" #include "Script.hxx" #include "Engine/Entity.hxx" -#include "Serialisation/ReflectionUtilities.hxx" +#include "Serialisation/SerialisationUtilities.hxx" #include "Engine/Application.hxx" #include "Physics/SHPhysicsSystemInterface.h" #include "Physics/SHPhysicsUtils.h" @@ -613,7 +613,7 @@ namespace SHADE System::Collections::Generic::List^ scriptList = scripts[entity]; for each (Script^ script in scriptList) { - ReflectionUtilities::Serialise(script, *yamlNode); + SerialisationUtilities::Serialise(script, *yamlNode); } return true; @@ -658,7 +658,7 @@ namespace SHADE if (AddScriptViaNameWithRef(entity, typeName, script)) { // Copy the data in - ReflectionUtilities::Deserialise(script, node); + SerialisationUtilities::Deserialise(script, node); } else { diff --git a/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx b/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx index 651afb73..3bdbe90e 100644 --- a/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx +++ b/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx @@ -18,31 +18,6 @@ of DigiPen Institute of Technology is prohibited. #include "Serialisation/ReflectionUtilities.hxx" // Project Includes #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 */ -/*-------------------------------------------------------------------------------------*/ -/// -/// Macro expansion that is used in RapidJsonValueToField() to retrieve the specified -/// member of a Vector type that is stored into a Vector named "vec". -/// -/// The name of the member to retrieve. -#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 */ @@ -64,202 +39,9 @@ namespace SHADE return fieldInfo->IsPublic || fieldInfo->GetCustomAttributes(SerializeField::typeid, true)->Length > 0; } - /*---------------------------------------------------------------------------------*/ - /* Serialisation Functions */ - /*---------------------------------------------------------------------------------*/ - void ReflectionUtilities::Serialise(System::Object^ object, YAML::Node& scriptListNode) + bool ReflectionUtilities::FieldIsList(System::Reflection::FieldInfo^ fieldInfo) { - 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^ 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^ 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(fieldInfo, object, fieldNode) || - fieldInsertYaml(fieldInfo, object, fieldNode) || - fieldInsertYaml(fieldInfo, object, fieldNode) || - fieldInsertYaml(fieldInfo, object, fieldNode) || - fieldInsertYaml(fieldInfo, object, fieldNode) || - fieldInsertYaml(fieldInfo, object, fieldNode) || - fieldInsertYaml(fieldInfo, object, fieldNode) || - fieldInsertYaml(fieldInfo, object, fieldNode) || - fieldInsertYaml(fieldInfo, object, fieldNode) || - fieldInsertYaml(fieldInfo, object, fieldNode); - - // Serialization of more complex types - if (!PRIMITIVE_SERIALIZED) - { - if (fieldInfo->FieldType->IsSubclassOf(System::Enum::typeid)) - { - fieldNode = std::to_string(safe_cast(fieldInfo->GetValue(object))); - } - else if (fieldInfo->FieldType == System::String::typeid) - { - System::String^ str = safe_cast(fieldInfo->GetValue(object)); - fieldNode = Convert::ToNative(str); - } - else if (fieldInfo->FieldType == Vector2::typeid) - { - Vector2 vec = safe_cast(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(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(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) - { - if (fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml(fieldInfo, object, node) || - fieldAssignYaml(fieldInfo, object, node) || - fieldAssignYaml(fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node)) - { - return; - } - else if (fieldInfo->FieldType->IsSubclassOf(System::Enum::typeid)) - { - fieldInfo->SetValue(object, node.as()); - } - else if (fieldInfo->FieldType == System::String::typeid) - { - fieldInfo->SetValue(object, Convert::ToCLI(node.as())); - } - else if (fieldInfo->FieldType == Vector2::typeid) - { - if (node.IsSequence() && node.size() == 2) - { - Vector2 vec; - vec.x = node[0].as(); - vec.y = node[1].as(); - 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(); - vec.y = node[1].as(); - vec.z = node[2].as(); - 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(); - 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) - )); - } + return fieldInfo->FieldType->IsGenericType + && fieldInfo->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition(); } } diff --git a/SHADE_Managed/src/Serialisation/ReflectionUtilities.h++ b/SHADE_Managed/src/Serialisation/ReflectionUtilities.h++ deleted file mode 100644 index 7c39232a..00000000 --- a/SHADE_Managed/src/Serialisation/ReflectionUtilities.h++ +++ /dev/null @@ -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 - bool ReflectionUtilities::fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode) - { - if (fieldInfo->FieldType == FieldType::typeid) - { - const FieldType VALUE = safe_cast(fieldInfo->GetValue(object)); - fieldNode = static_cast(VALUE); - return true; - } - - return false; - } - - template - bool ReflectionUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) - { - return fieldAssignYaml>(fieldInfo, object, node); - } - - template - bool ReflectionUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) - { - if (fieldInfo->FieldType == FieldType::typeid) - { - fieldInfo->SetValue(object, node.as()); - return true; - } - - return false; - } -} diff --git a/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx b/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx index 403c913c..ffdc208f 100644 --- a/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx +++ b/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx @@ -13,9 +13,6 @@ of DigiPen Institute of Technology is prohibited. *//*************************************************************************************/ #pragma once -// External Dependencies -#include - namespace SHADE { /// @@ -42,40 +39,11 @@ namespace SHADE /// True if the specified field is a candidate for serialisation. /// static bool FieldIsSerialisable(System::Reflection::FieldInfo^ fieldInfo); - - /*-----------------------------------------------------------------------------*/ - /* Serialisation Functions */ - /*-----------------------------------------------------------------------------*/ /// - /// 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. + /// Checks if the specified field is a generic List. /// - /// The object to serialise. - static void Serialise(System::Object^ object, YAML::Node& yamlNode); - /// - /// Deserialises a YAML node that contains a map of Scripts and copies the - /// deserialised data into the specified object if there are matching fields. - /// - /// - /// The JSON string that contains the data to copy into this Script object. - /// - /// The object to copy deserialised data into. - 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 - 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 - static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); - template - static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); + /// The field to check. + /// True if fieldInfo is describing a generic List. + static bool FieldIsList(System::Reflection::FieldInfo^ fieldInfo); }; -} - -#include "ReflectionUtilities.h++" \ No newline at end of file +} \ No newline at end of file diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx b/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx new file mode 100644 index 00000000..e8a4e0e3 --- /dev/null +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx @@ -0,0 +1,251 @@ +/************************************************************************************//*! +\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^ 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^ 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(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(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(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(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode); + return INSERTED; + } + + void SerialisationUtilities::writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) + { + if (fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node)) + { + return; + } + else if (fieldInfo->FieldType->IsSubclassOf(System::Enum::typeid)) + { + fieldInfo->SetValue(object, node.as()); + } + else if (fieldInfo->FieldType == System::String::typeid) + { + fieldInfo->SetValue(object, Convert::ToCLI(node.as())); + } + else if (fieldInfo->FieldType == Vector2::typeid) + { + if (node.IsSequence() && node.size() == 2) + { + Vector2 vec; + vec.x = node[0].as(); + vec.y = node[1].as(); + fieldInfo->SetValue(object, vec); + } + else + { + Debug::LogWarning + ( + System::String::Format("[SerialisationUtilities] 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(); + vec.y = node[1].as(); + vec.z = node[2].as(); + fieldInfo->SetValue(object, vec); + } + else + { + Debug::LogWarning + ( + System::String::Format("[SerialisationUtilities] 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(); + fieldInfo->SetValue(object, EID == MAX_EID ? GameObject() : GameObject(EID)); + } + else // Not any of the supported types + { + Debug::LogWarning(Convert::ToNative(System::String::Format + ( + "[SerialisationUtilities] Failed to parse \"{0}\" of \"{1}\" type for deserialisation.", + fieldInfo->Name, fieldInfo->FieldType) + )); + } + } +} diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ new file mode 100644 index 00000000..93a14401 --- /dev/null +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ @@ -0,0 +1,125 @@ +/************************************************************************************//*! +\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 + bool SerialisationUtilities::fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode) + { + Debug::Log(FieldType::typeid->Name); + return varInsertYamlInternal(fieldInfo->GetValue(object), fieldNode); + } + template + bool SerialisationUtilities::varInsertYamlInternal(System::Object^ object, YAML::Node& fieldNode) + { + if constexpr (std::is_same_v) + { + Debug::Log("Enum Specialization"); + if (object->GetType()->IsSubclassOf(System::Enum::typeid)) + { + fieldNode = std::to_string(safe_cast(object)); + return true; + } + } + else if constexpr (std::is_same_v) + { + Debug::Log("String Specialization"); + if (object->GetType() == System::String::typeid) + { + System::String^ str = safe_cast(object); + fieldNode = Convert::ToNative(str); + return true; + } + } + else if constexpr (std::is_same_v) + { + Debug::Log("Vec2 Specialization"); + if (object->GetType() == Vector2::typeid) + { + Vector2 vec = safe_cast(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) + { + Debug::Log("Vec3 Specialization"); + if (object->GetType() == Vector3::typeid) + { + Vector3 vec = safe_cast(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) + { + Debug::Log("GameObject Specialization"); + if (object->GetType() == GameObject::typeid) + { + GameObject gameObj = safe_cast(object); + fieldNode = gameObj ? gameObj.GetEntity() : MAX_EID; + return true; + } + } + else + { + Debug::Log("No Specialization"); + if (object->GetType() == FieldType::typeid) + { + FieldType value = safe_cast(object); + fieldNode = static_cast(value); + return true; + } + } + + return false; + } + + /*---------------------------------------------------------------------------------*/ + /* Deserialization Helper Functions */ + /*---------------------------------------------------------------------------------*/ + template + bool SerialisationUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) + { + return fieldAssignYaml>(fieldInfo, object, node); + } + + template + bool SerialisationUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) + { + if (fieldInfo->FieldType == FieldType::typeid) + { + fieldInfo->SetValue(object, node.as()); + return true; + } + + return false; + } +} diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx b/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx new file mode 100644 index 00000000..93d88248 --- /dev/null +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx @@ -0,0 +1,74 @@ +/************************************************************************************//*! +\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 +// Project Includes +#include "Math/Vector2.hxx" +#include "Math/Vector3.hxx" +#include "Engine/GameObject.hxx" + +namespace SHADE +{ + /// + /// Contains useful static functions for working with Serialisation of Managed data. + /// + private ref class SerialisationUtilities abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Serialisation Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// 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. + /// + /// The object to serialise. + static void Serialise(System::Object^ object, YAML::Node& yamlNode); + /// + /// Deserialises a YAML node that contains a map of Scripts and copies the + /// deserialised data into the specified object if there are matching fields. + /// + /// + /// The JSON string that contains the data to copy into this Script object. + /// + /// The object to copy deserialised data into. + 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 + static bool fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode); + static bool varInsertYaml(System::Object^ object, YAML::Node& fieldNode); + template + static bool varInsertYamlInternal(System::Object^ object, YAML::Node& fieldNode); + + /*-----------------------------------------------------------------------------*/ + /* Deserialization Helper Functions */ + /*-----------------------------------------------------------------------------*/ + static void writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); + template + static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); + template + static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); + }; +} + +#include "SerialisationUtilities.h++" \ No newline at end of file From d98deda63d686e891a45f7575eace2c744067364 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Sun, 13 Nov 2022 02:42:47 +0800 Subject: [PATCH 17/18] Implemented deserialization of lists --- .../src/Serialisation/ReflectionUtilities.cxx | 9 +- .../src/Serialisation/ReflectionUtilities.hxx | 6 + .../Serialisation/SerialisationUtilities.cxx | 147 ++++++++++-------- .../Serialisation/SerialisationUtilities.h++ | 91 +++++++++-- .../Serialisation/SerialisationUtilities.hxx | 7 +- 5 files changed, 172 insertions(+), 88 deletions(-) diff --git a/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx b/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx index 3bdbe90e..f371686c 100644 --- a/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx +++ b/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx @@ -41,7 +41,12 @@ namespace SHADE bool ReflectionUtilities::FieldIsList(System::Reflection::FieldInfo^ fieldInfo) { - return fieldInfo->FieldType->IsGenericType - && fieldInfo->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition(); + return IsList(fieldInfo->FieldType); + } + + bool ReflectionUtilities::IsList(System::Type^ type) + { + return type->IsGenericType + && type->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition(); } } diff --git a/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx b/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx index ffdc208f..ae66cc34 100644 --- a/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx +++ b/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx @@ -45,5 +45,11 @@ namespace SHADE /// The field to check. /// True if fieldInfo is describing a generic List. static bool FieldIsList(System::Reflection::FieldInfo^ fieldInfo); + /// + /// Checks if the specified type is a generic List type. + /// + /// The type to check. + /// True if type is a generic List. + static bool IsList(System::Type^ type); }; } \ No newline at end of file diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx b/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx index e8a4e0e3..20880947 100644 --- a/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx @@ -173,79 +173,88 @@ namespace SHADE varInsertYamlInternal(object, fieldNode); return INSERTED; } - - void SerialisationUtilities::writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) + + /*---------------------------------------------------------------------------------*/ + /* Deserialization Helper Functions */ + /*---------------------------------------------------------------------------------*/ + bool SerialisationUtilities::writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) { - if (fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml(fieldInfo, object, node) || - fieldAssignYaml(fieldInfo, object, node) || - fieldAssignYaml(fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node) || - fieldAssignYaml (fieldInfo, object, node)) + const bool ASSIGNED = + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node); + if (!ASSIGNED) { - return; - } - else if (fieldInfo->FieldType->IsSubclassOf(System::Enum::typeid)) - { - fieldInfo->SetValue(object, node.as()); - } - else if (fieldInfo->FieldType == System::String::typeid) - { - fieldInfo->SetValue(object, Convert::ToCLI(node.as())); - } - else if (fieldInfo->FieldType == Vector2::typeid) - { - if (node.IsSequence() && node.size() == 2) + if (ReflectionUtilities::FieldIsList(fieldInfo)) { - Vector2 vec; - vec.x = node[0].as(); - vec.y = node[1].as(); - fieldInfo->SetValue(object, vec); - } - else - { - Debug::LogWarning - ( - System::String::Format("[SerialisationUtilities] Invalid YAML Node provided for deserialization of a Vector2 \"{0}\" field in \"{1}\" script.", - fieldInfo->Name, object->GetType()->FullName) - ); + System::Type^ elemType = fieldInfo->FieldType->GenericTypeArguments[0]; + System::Collections::IList^ iList = safe_cast(fieldInfo->GetValue(object)); + if (node.IsSequence()) + { + // Get list size + const int LIST_SIZE = static_cast(node.size()); + if (LIST_SIZE > 0) + { + // Get list type + array^ typeList = gcnew array{ elemType }; + System::Type^ listType = System::Collections::Generic::List::typeid->GetGenericTypeDefinition()->MakeGenericType(typeList); + // Create a list of the specified type + array^ params = gcnew array{ node.size() }; + object = System::Activator::CreateInstance(listType, params); + System::Collections::IList^ list = safe_cast(object); + + // 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); + } + } + } + } + + return true; } } - else if (fieldInfo->FieldType == Vector3::typeid) - { - if (node.IsSequence() && node.size() == 3) - { - Vector3 vec; - vec.x = node[0].as(); - vec.y = node[1].as(); - vec.z = node[2].as(); - fieldInfo->SetValue(object, vec); - } - else - { - Debug::LogWarning - ( - System::String::Format("[SerialisationUtilities] 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(); - fieldInfo->SetValue(object, EID == MAX_EID ? GameObject() : GameObject(EID)); - } - else // Not any of the supported types - { - Debug::LogWarning(Convert::ToNative(System::String::Format - ( - "[SerialisationUtilities] Failed to parse \"{0}\" of \"{1}\" type for deserialisation.", - fieldInfo->Name, fieldInfo->FieldType) - )); - } + + return ASSIGNED; + } + + bool SerialisationUtilities::varAssignYaml(System::Object^% object, YAML::Node& node) + { + const bool DESERIALISED = + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal(object, node) || + varAssignYamlInternal(object, node) || + varAssignYamlInternal(object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal(object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node); + return DESERIALISED; } } diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ index 93a14401..c1728fe6 100644 --- a/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ @@ -36,7 +36,6 @@ namespace SHADE { if constexpr (std::is_same_v) { - Debug::Log("Enum Specialization"); if (object->GetType()->IsSubclassOf(System::Enum::typeid)) { fieldNode = std::to_string(safe_cast(object)); @@ -45,7 +44,6 @@ namespace SHADE } else if constexpr (std::is_same_v) { - Debug::Log("String Specialization"); if (object->GetType() == System::String::typeid) { System::String^ str = safe_cast(object); @@ -55,7 +53,6 @@ namespace SHADE } else if constexpr (std::is_same_v) { - Debug::Log("Vec2 Specialization"); if (object->GetType() == Vector2::typeid) { Vector2 vec = safe_cast(object); @@ -67,7 +64,6 @@ namespace SHADE } else if constexpr (std::is_same_v) { - Debug::Log("Vec3 Specialization"); if (object->GetType() == Vector3::typeid) { Vector3 vec = safe_cast(object); @@ -80,7 +76,6 @@ namespace SHADE } else if constexpr (std::is_same_v) { - Debug::Log("GameObject Specialization"); if (object->GetType() == GameObject::typeid) { GameObject gameObj = safe_cast(object); @@ -90,7 +85,6 @@ namespace SHADE } else { - Debug::Log("No Specialization"); if (object->GetType() == FieldType::typeid) { FieldType value = safe_cast(object); @@ -108,18 +102,87 @@ namespace SHADE template bool SerialisationUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) { - return fieldAssignYaml>(fieldInfo, object, node); - } - - template - bool SerialisationUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) - { - if (fieldInfo->FieldType == FieldType::typeid) + System::Object^ valueObj = fieldInfo->GetValue(object); + if (varAssignYamlInternal(valueObj, node)) { - fieldInfo->SetValue(object, node.as()); + fieldInfo->SetValue(object, valueObj); return true; } return false; } + + template + bool SerialisationUtilities::varAssignYamlInternal(System::Object^% object, YAML::Node& node) + { + if constexpr (std::is_same_v) + { + if (object->GetType()->IsSubclassOf(System::Enum::typeid)) + { + object = node.as(); + return true; + } + } + else if constexpr (std::is_same_v) + { + if (ReflectionUtilities::FieldIsList(fieldInfo)) + { + System::Collections::IList^ iList = safe_cast(object); + object = gcnew + if (node.IsSequence() ) + + } + } + else + { + if (object->GetType() == FieldType::typeid) + { + if constexpr (std::is_same_v) + { + object = Convert::ToCLI(node.as()); + } + else if constexpr (std::is_same_v) + { + if (node.IsSequence() && node.size() == 2) + { + Vector2 vec; + vec.x = node[0].as(); + vec.y = node[1].as(); + object = vec; + } + else + { + return false; + } + } + else if constexpr (std::is_same_v) + { + if (node.IsSequence() && node.size() == 3) + { + Vector3 vec; + vec.x = node[0].as(); + vec.y = node[1].as(); + vec.z = node[2].as(); + object = vec; + } + else + { + return false; + } + } + else if constexpr (std::is_same_v) + { + const uint32_t EID = node.as(); + object = (EID == MAX_EID ? GameObject() : GameObject(EID)); + } + else + { + object = node.as(); + } + return true; + } + } + + return false; + } } diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx b/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx index 93d88248..5b6fc69e 100644 --- a/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx @@ -63,11 +63,12 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ /* Deserialization Helper Functions */ /*-----------------------------------------------------------------------------*/ - static void writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); + static bool writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); template static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); - template - static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); + static bool varAssignYaml(System::Object^% object, YAML::Node& node); + template> + static bool varAssignYamlInternal(System::Object^% object, YAML::Node& node); }; } From 26e0e72b259311ba6ee19aca004bc98fc63c1cb5 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Sun, 13 Nov 2022 04:57:10 +0800 Subject: [PATCH 18/18] Fixed lists not deserialising correctly --- SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx b/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx index 20880947..147591a5 100644 --- a/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx @@ -212,8 +212,10 @@ namespace SHADE System::Type^ listType = System::Collections::Generic::List::typeid->GetGenericTypeDefinition()->MakeGenericType(typeList); // Create a list of the specified type array^ params = gcnew array{ node.size() }; - object = System::Activator::CreateInstance(listType, params); - System::Collections::IList^ list = safe_cast(object); + System::Collections::IList^ list = safe_cast + ( + System::Activator::CreateInstance(listType, params) + ); // Populate the list for (int i = 0; i < LIST_SIZE; ++i) @@ -227,6 +229,7 @@ namespace SHADE list->Add(obj); } } + fieldInfo->SetValue(object, list); } }