Merge branch 'main' into SP3-6-c-scripting

This commit is contained in:
Kah Wei 2022-11-13 12:44:50 +08:00
commit 4b6ede88e2
31 changed files with 1878 additions and 750 deletions

View File

@ -1,5 +1,6 @@
using SHADE;
using System;
using System.Collections.Generic;
public class RaccoonShowcase : Script
{
@ -17,6 +18,11 @@ 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<Vector3> vecList = new List<Vector3>(new Vector3[] { new Vector3(1, 2, 3), new Vector3(4, 5, 6) });
[Range(-5, 5)]
public List<int> intList = new List<int>(new int[] { 2, 8, 2, 6, 8, 0, 1 });
public List<Light.Type> enumList = new List<Light.Type>(new Light.Type[] { Light.Type.Point, Light.Type.Directional, Light.Type.Ambient });
public RaccoonShowcase(GameObject gameObj) : base(gameObj) {}
protected override void awake()

View File

@ -7,13 +7,15 @@
#include "ECS_Base/Managers/SHComponentManager.h"
#include "Math/Transform/SHTransformComponent.h"
#include <math.h>
#include "Scene/SHSceneManager.h"
namespace SHADE
{
void SHCameraSystem::UpdateEditorCamera(double dt) noexcept
{
auto& camera = editorCamera;
SHVec3 view, right, UP;
GetCameraAxis(camera, view, right, UP);
@ -62,62 +64,50 @@ namespace SHADE
UpdateCameraComponent(editorCamera);
if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::LEFT_ALT))
{
UpdateEditorArm(dt, true, SHVec3{ 0.0f });
}
else
UpdateEditorArm(dt, false, SHVec3{ 0.0f });
}
void SHCameraSystem::EditorCameraUpdate::Execute(double dt) noexcept
{
SHCameraSystem* system = static_cast<SHCameraSystem*>(GetSystem());
auto& camera = system->editorCamera;
SHVec3 view, right, UP;
system->GetCameraAxis(camera, view, right, UP);
if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::A))
void SHCameraSystem::UpdateEditorArm(double dt,bool active ,SHVec3 const& targetPos) noexcept
{
if (active == false)
{
//std::cout << "Camera movement: "<<right.x<<", " << right.y << std::endl;
camera.position -= right * dt * camera.movementSpeed;
camera.dirtyView = true;
}
if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::D))
{
camera.position += right * dt * camera.movementSpeed;
camera.dirtyView = true;
}
if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::W))
{
camera.position += view * dt * camera.movementSpeed;
camera.dirtyView = true;
}
if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::S))
{
camera.position -= view * dt * camera.movementSpeed;
camera.dirtyView = true;
}
if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::Q))
{
camera.position += UP * dt * camera.movementSpeed;
camera.dirtyView = true;
}
if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::E))
{
camera.position -= UP * dt * camera.movementSpeed;
camera.dirtyView = true;
}
if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::RMB))
{
double mouseX, mouseY;
SHInputManager::GetMouseVelocity(&mouseX,&mouseY);
//std::cout << camera.yaw << std::endl;
camera.pitch -= mouseY * dt * camera.turnSpeed.x;
camera.yaw -= mouseX * dt * camera.turnSpeed.y;
camera.dirtyView = true;
editorCameraArm.offset = SHVec3{0.0f};
return;
}
//std::cout << "Camera position: " << camera.position.x << " " << camera.position.y << std::endl;
system->UpdateCameraComponent(system->editorCamera);
editorCamera.SetPosition(targetPos);
double mouseX, mouseY;
SHInputManager::GetMouseVelocity(&mouseX, &mouseY);
editorCameraArm.pitch -= mouseY * dt * editorCamera.turnSpeed.x;
editorCameraArm.yaw -= mouseX * dt * editorCamera.turnSpeed.y;
constexpr float pitchClamp = 85.0f;
if (editorCameraArm.pitch > pitchClamp)
editorCameraArm.pitch = pitchClamp;
if (editorCameraArm.pitch < -pitchClamp)
editorCameraArm.pitch = -pitchClamp;
editorCameraArm.armLength -= SHInputManager::GetMouseWheelVerticalDelta() * dt;
if (editorCameraArm.armLength < 1.0f)
editorCameraArm.armLength = 1.0f;
UpdatePivotArmComponent(editorCameraArm);
editorCamera.offset = editorCameraArm.GetOffset();
CameraLookAt(editorCamera, targetPos);
}
system->DecomposeViewMatrix(camera.viewMatrix, camera.pitch, camera.yaw, camera.roll, camera.position);
}
void SHCameraSystem::Init(void)
{
@ -164,6 +154,9 @@ namespace SHADE
void SHCameraSystem::UpdateCameraComponent(SHCameraComponent& camera) noexcept
{
if (camera.isActive == false)
return;
if (SHComponentManager::HasComponent<SHTransformComponent>(camera.GetEID()) == true && &camera != &editorCamera)
{
auto transform = SHComponentManager::GetComponent<SHTransformComponent>(camera.GetEID());
@ -183,11 +176,17 @@ namespace SHADE
if (SHComponentManager::HasComponent<SHCameraArmComponent>(camera.GetEID()))
{
auto arm = SHComponentManager::GetComponent<SHCameraArmComponent>(camera.GetEID());
camera.offset = arm->GetOffset();
if(arm->lookAtCameraOrigin)
CameraLookAt(camera, camera.position);
if (arm->isActive == true)
{
camera.offset = arm->GetOffset();
if (arm->lookAtCameraOrigin)
CameraLookAt(camera, camera.position);
}
}
SHVec3 view, right, UP;
@ -287,12 +286,14 @@ namespace SHADE
for (auto& pivot : pivotDense)
{
system->UpdatePivotArmComponent(pivot);
if(SHSceneManager::CheckNodeAndComponentsActive<SHCameraArmComponent>(pivot.GetEID()))
system->UpdatePivotArmComponent(pivot);
}
for (auto& cam : dense)
{
system->UpdateCameraComponent(cam);
if (SHSceneManager::CheckNodeAndComponentsActive<SHCameraComponent>(cam.GetEID()))
system->UpdateCameraComponent(cam);
}
for (auto& handle : system->directorHandleList)
{
@ -399,7 +400,7 @@ namespace SHADE
SHVec3 up = { 0.0f,1.0f,0.0f };
////SHVec3::RotateZ(target, SHMath::DegreesToRadians(camera.roll));
//SHVec3::RotateZ(target, SHMath::DegreesToRadians(camera.roll));
//target = SHVec3::Normalise(target);

View File

@ -5,6 +5,7 @@
#include "ECS_Base/System/SHSystemRoutine.h"
#include "Resource/SHResourceLibrary.h"
#include "SHCameraDirector.h"
#include "SHCameraArmComponent.h"
#include "SH_API.h"
namespace SHADE
@ -18,6 +19,7 @@ namespace SHADE
//A camera component that represents editor camera.
//This is not tied to any entity. Hence this EID should not be used.
SHCameraComponent editorCamera;
SHCameraArmComponent editorCameraArm;
SHResourceLibrary<SHCameraDirector> directorLibrary;
std::vector<DirectorHandle> directorHandleList;
@ -34,14 +36,7 @@ namespace SHADE
void Init (void);
void Exit (void);
class SH_API EditorCameraUpdate final : public SHSystemRoutine
{
public:
EditorCameraUpdate() : SHSystemRoutine("Editor Camera Update", true) { };
virtual void Execute(double dt) noexcept override final;
};
friend class EditorCameraUpdate;
class SH_API CameraSystemUpdate final: public SHSystemRoutine
@ -63,6 +58,7 @@ namespace SHADE
void DecomposeViewMatrix(SHMatrix const& matrix, float& pitch, float& yaw, float& roll, SHVec3& pos) noexcept;
void SetCameraViewMatrix(SHCameraComponent& camera, SHMatrix const& viewMatrix) noexcept;
void CameraLookAt(SHCameraComponent& camera, SHVec3 target) noexcept;
void UpdateEditorArm(double dt,bool active ,SHVec3 const& targetPos) noexcept;
};

View File

@ -10,63 +10,102 @@
namespace SHADE
{
SHCommandManager::CommandStack SHCommandManager::undoStack{};
SHCommandManager::CommandStack SHCommandManager::redoStack{};
SHCommandManager::CommandStack SHCommandManager::undoStack(defaultStackSize);
SHCommandManager::CommandStack SHCommandManager::redoStack(defaultStackSize);
SHCommandManager::CommandStack SHCommandManager::secondaryUndoStack(defaultStackSize);
SHCommandManager::CommandStack SHCommandManager::secondaryRedoStack(defaultStackSize);
SHCommandManager::CommandStackPtr SHCommandManager::pCurrUndoStack(&undoStack);
SHCommandManager::CommandStackPtr SHCommandManager::pCurrRedoStack(&redoStack);
void SHCommandManager::PerformCommand(BaseCommandPtr commandPtr, bool const& overrideValue)
{
redoStack = CommandStack();
*pCurrRedoStack = CommandStack(defaultStackSize);
commandPtr->Execute();
if (overrideValue && !undoStack.empty())
if (overrideValue && !pCurrUndoStack->Empty())
{
undoStack.top()->Merge(commandPtr);
pCurrUndoStack->Top()->Merge(commandPtr);
}
else
{
undoStack.push(commandPtr);
pCurrUndoStack->Push(commandPtr);
}
}
void SHCommandManager::RegisterCommand(BaseCommandPtr commandPtr)
{
undoStack.push(commandPtr);
pCurrUndoStack->Push(commandPtr);
}
void SHCommandManager::UndoCommand()
{
if (undoStack.empty())
if (pCurrUndoStack->Empty())
return;
undoStack.top()->Undo();
redoStack.push(undoStack.top());
undoStack.pop();
pCurrUndoStack->Top()->Undo();
pCurrRedoStack->Push(pCurrUndoStack->Top());
pCurrUndoStack->Pop();
}
void SHCommandManager::RedoCommand()
{
if (redoStack.empty())
if (pCurrRedoStack->Empty())
return;
redoStack.top()->Execute();
undoStack.push(redoStack.top());
redoStack.pop();
pCurrRedoStack->Top()->Execute();
pCurrUndoStack->Push(pCurrRedoStack->Top());
pCurrRedoStack->Pop();
}
std::size_t SHCommandManager::GetUndoStackSize()
{
return undoStack.size();
return pCurrUndoStack->Size();
}
std::size_t SHCommandManager::GetRedoStackSize()
{
return redoStack.size();
return pCurrRedoStack->Size();
}
void SHCommandManager::PopLatestCommandFromRedoStack()
{
redoStack.pop();
pCurrRedoStack->Pop();
}
void SHCommandManager::PopLatestCommandFromUndoStack()
{
undoStack.pop();
pCurrUndoStack->Pop();
}
void SHCommandManager::SwapStacks()
{
if (pCurrUndoStack == &undoStack)
{
pCurrUndoStack = &secondaryUndoStack;
}
else
{
secondaryUndoStack.Clear();
pCurrUndoStack = &undoStack;
}
if (pCurrRedoStack == &redoStack)
{
pCurrRedoStack = &secondaryRedoStack;
}
else
{
secondaryRedoStack.Clear();
pCurrRedoStack = &redoStack;
}
}
void SHCommandManager::ClearAll()
{
undoStack.Clear();
redoStack.Clear();
secondaryUndoStack.Clear();
secondaryRedoStack.Clear();
}
}//namespace SHADE

View File

@ -10,6 +10,7 @@
//#==============================================================#
#include "SHCommand.hpp"
#include "SH_API.h"
#include "Tools/SHDeque.h"
namespace SHADE
{
@ -22,7 +23,8 @@ namespace SHADE
using BaseCommandPtr = std::shared_ptr<SHBaseCommand>;
template<typename T>
using SHCommandPtr = std::shared_ptr<SHCommand<T>>;
using CommandStack = std::stack<BaseCommandPtr>;
using CommandStack = SHDeque<BaseCommandPtr>;
using CommandStackPtr = CommandStack*;
static void PerformCommand(BaseCommandPtr commandPtr, bool const& overrideValue = false);
static void RegisterCommand(BaseCommandPtr commandPtr);
@ -34,8 +36,17 @@ namespace SHADE
static void PopLatestCommandFromRedoStack();
static void PopLatestCommandFromUndoStack();
static void SwapStacks();
static void ClearAll();
static constexpr CommandStack::SizeType defaultStackSize = 100;
private:
static CommandStackPtr pCurrUndoStack;
static CommandStackPtr pCurrRedoStack;
static CommandStack undoStack;
static CommandStack secondaryUndoStack;
static CommandStack redoStack;
static CommandStack secondaryRedoStack;
};
}//namespace SHADE

View File

@ -17,7 +17,7 @@
namespace SHADE
{
SHAssetBrowser::SHAssetBrowser()
:SHEditorWindow("\xee\x8b\x87 Asset Browser", ImGuiWindowFlags_MenuBar), rootFolder(SHAssetManager::GetRootFolder()), prevFolder(rootFolder), currentFolder(rootFolder), assetBeingCreated(std::nullopt)
:SHEditorWindow("\xee\x8b\x87 Asset Browser", ImGuiWindowFlags_MenuBar), rootFolder(SHAssetManager::GetRootFolder()), prevFolder(rootFolder), currentFolder(rootFolder)
{
}
@ -34,23 +34,48 @@ namespace SHADE
RecursivelyDrawTree(rootFolder);
DrawMenuBar();
DrawCurrentFolder();
DrawAssetBeingCreated();
}
ImGui::End();
if(refreshQueued)
Refresh();
}
void SHAssetBrowser::QueueRefresh() noexcept
{
refreshQueued = true;
}
void SHAssetBrowser::Refresh() noexcept
{
SHAssetManager::RefreshDirectory();
rootFolder = SHAssetManager::GetRootFolder();
refreshQueued = false;
}
void SHAssetBrowser::DrawMenuBar()
{
if (ImGui::BeginMenuBar())
{
if(ImGui::SmallButton(ICON_MD_SYNC))
{
QueueRefresh();
}
if(ImGui::SmallButton(ICON_FA_CIRCLE_PLUS))
{
isAssetBeingCreated = true;
}
ImGui::EndMenuBar();
}
}
//if !compiled, set genMeta to true
ImRect SHAssetBrowser::RecursivelyDrawTree(FolderPointer folder)
{
auto const& subFolders = folder->subFolders;
auto const& files = folder->files;
auto files = folder->files;
const bool isSelected = std::ranges::find(selectedFolders, folder) != selectedFolders.end();
ImGuiTreeNodeFlags flags = (subFolders.empty() && files.empty()) ? ImGuiTreeNodeFlags_Leaf : ImGuiTreeNodeFlags_OpenOnArrow;
if (isSelected)
@ -62,21 +87,10 @@ namespace SHADE
ImGuiID folderID = ImGui::GetItemID();
const ImRect nodeRect = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax());
if (ImGui::BeginPopupContextItem())
{
if (ImGui::BeginMenu("Create Asset"))
{
//TODO: Change to rttr type enum align
if (ImGui::Selectable("Material"))
{
assetBeingCreated = { folder, AssetType::MATERIAL, "New Material" };
ImGui::TreeNodeSetOpen(folderID, true);
isOpen = true;
}
ImGui::EndMenu();
}
ImGui::EndPopup();
}
//if (ImGui::BeginPopupContextItem())
//{
// ImGui::EndPopup();
//}
if (ImGui::IsItemClicked())
{
@ -100,19 +114,15 @@ namespace SHADE
drawList->AddLine(ImVec2(vertLineStart.x, midPoint), ImVec2(vertLineStart.x + horizontalLineSize, midPoint), treeLineColor, 1);
vertLineEnd.y = midPoint;
}
for (auto const& file : files)
for (auto& file : files)
{
if(file.assetMeta == nullptr)
continue;
const float horizontalLineSize = 25.0f;
const ImRect childRect = DrawFile(file.assetMeta);
const ImRect childRect = DrawFile(file);
const float midPoint = (childRect.Min.y + childRect.Max.y) * 0.5f;
drawList->AddLine(ImVec2(vertLineStart.x, midPoint), ImVec2(vertLineStart.x + horizontalLineSize, midPoint), treeLineColor, 1);
vertLineEnd.y = midPoint;
}
drawList->AddLine(vertLineStart, vertLineEnd, treeLineColor, 1);
if(assetBeingCreated.has_value() && std::get<0>(assetBeingCreated.value()) == folder)
DrawAssetBeingCreated();
ImGui::TreePop();
}
@ -148,7 +158,33 @@ namespace SHADE
//}
}
ImRect SHAssetBrowser::DrawFile(SHAsset const* const asset) noexcept
ImRect SHAssetBrowser::DrawFile(SHFile& file) noexcept
{
if(file.compilable)
{
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf;
static constexpr std::string_view icon = ICON_MD_FILE_PRESENT;
ImGui::PushID(file.name.data());
bool const isOpen = ImGui::TreeNodeEx(file.name.data(), flags, "%s %s%s", icon.data(), file.name.data(), file.ext.data());
const ImRect nodeRect = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax());
if(ImGui::BeginPopupContextItem())
{
if(ImGui::Selectable("Compile"))
{
SHAssetManager::CompileAsset(file.path, !file.compiled);
QueueRefresh();
}
ImGui::EndPopup();
}
ImGui::TreePop();
ImGui::PopID();
return nodeRect;
}
if(file.assetMeta)
DrawAsset(file.assetMeta, file.ext);
}
ImRect SHAssetBrowser::DrawAsset(SHAsset const* const asset, FileExt const& ext /*= ""*/) noexcept
{
if (asset == nullptr)
return ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax());
@ -173,7 +209,7 @@ namespace SHADE
default:;
}
bool const isOpen = ImGui::TreeNodeEx(asset, flags, "%s %s", icon.data(), asset->name.data());
bool const isOpen = ImGui::TreeNodeEx(asset, flags, "%s %s%s", icon.data(), asset->name.data(), ext.data());
const ImRect nodeRect = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax());
if (SHDragDrop::BeginSource())
{
@ -212,7 +248,6 @@ namespace SHADE
case AssetType::MAX_COUNT: break;
default:;
}
}
//TODO: Combine Draw asset and Draw Folder recursive drawing
@ -227,7 +262,7 @@ namespace SHADE
for(auto const& subAsset : asset->subAssets)
{
const float horizontalLineSize = 25.0f;
const ImRect childRect = DrawFile(subAsset);
const ImRect childRect = DrawAsset(subAsset);
const float midPoint = (childRect.Min.y + childRect.Max.y) * 0.5f;
drawList->AddLine(ImVec2(vertLineStart.x, midPoint), ImVec2(vertLineStart.x + horizontalLineSize, midPoint), treeLineColor, 1);
vertLineEnd.y = midPoint;
@ -240,19 +275,52 @@ namespace SHADE
void SHAssetBrowser::DrawAssetBeingCreated() noexcept
{
if (!assetBeingCreated.has_value())
return;
auto& path = std::get<0>(assetBeingCreated.value());
auto& type = std::get<1>(assetBeingCreated.value());
auto& assetName = std::get<2>(assetBeingCreated.value());
if (ImGui::InputText("##newAssetName", &assetName, ImGuiInputTextFlags_EnterReturnsTrue))
if(isAssetBeingCreated)
ImGui::OpenPopup(newAssetPopup.data());
if(ImGui::BeginPopupModal(newAssetPopup.data(), &isAssetBeingCreated))
{
AssetID assetId = SHAssetManager::CreateNewAsset(type, assetName);
if (auto matInspector = SHEditorWindowManager::GetEditorWindow<SHMaterialInspector>())
ImGui::RadioButton("Material", true);
ImGui::SameLine();
if (ImGui::InputText("##newAssetName", &nameOfAssetBeingCreated, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_AutoSelectAll))
{
matInspector->OpenMaterial(assetId, true);
AssetID assetId = SHAssetManager::CreateNewAsset(AssetType::MATERIAL, nameOfAssetBeingCreated);
if (auto matInspector = SHEditorWindowManager::GetEditorWindow<SHMaterialInspector>())
{
matInspector->OpenMaterial(assetId, true);
}
nameOfAssetBeingCreated.clear();
QueueRefresh();
isAssetBeingCreated = false;
ImGui::CloseCurrentPopup();
}
assetBeingCreated.reset();
ImGui::EndPopup();
}
//if (ImGui::BeginMenu("Create Asset"))
//{
// //TODO: Change to rttr type enum align
// if (ImGui::Selectable("Material"))
// {
// assetBeingCreated = { folder, AssetType::MATERIAL, "NewMaterial" };
// ImGui::TreeNodeSetOpen(folderID, true);
// isOpen = true;
// }
// ImGui::EndMenu();
//}
//if (!assetBeingCreated.has_value())
// return;
//auto& path = std::get<0>(assetBeingCreated.value());
//auto& type = std::get<1>(assetBeingCreated.value());
//auto& assetName = std::get<2>(assetBeingCreated.value());
//if (ImGui::InputText("##newAssetName", &assetName, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_AutoSelectAll))
//{
// AssetID assetId = SHAssetManager::CreateNewAsset(type, assetName);
// if (auto matInspector = SHEditorWindowManager::GetEditorWindow<SHMaterialInspector>())
// {
// matInspector->OpenMaterial(assetId, true);
// }
// assetBeingCreated.reset();
// QueueRefresh();
//}
}
}

View File

@ -10,24 +10,29 @@ namespace SHADE
class SHAssetBrowser final : public SHEditorWindow
{
public:
using AssetEntry = std::tuple<FolderPointer, AssetType, std::string>;
SHAssetBrowser();
void Init();
void Update();
void Refresh();
void QueueRefresh() noexcept;
private:
void DrawMenuBar();
ImRect RecursivelyDrawTree(FolderPointer folder);
void DrawCurrentFolder();
ImRect DrawFile(SHAsset const* const asset) noexcept;
ImRect DrawFile(SHFile& file) noexcept;
ImRect DrawAsset(SHAsset const* const asset, FileExt const& ext = "") noexcept;
void DrawAssetBeingCreated() noexcept;
void Refresh() noexcept;
FolderPointer rootFolder, prevFolder, currentFolder;
std::optional<AssetEntry> assetBeingCreated;
std::vector<FolderPointer> selectedFolders;
std::vector<AssetID> selectedAssets;
static constexpr float tileWidth = 50.0f;
bool refreshQueued = false;
bool isAssetBeingCreated = false;
static constexpr std::string_view newAssetPopup = "Create New Asset";
std::string nameOfAssetBeingCreated;
};
}

View File

@ -93,13 +93,14 @@ namespace SHADE
{
EntityID const& eid = editor->selectedEntities[0];
SHEntity* entity = SHEntityManager::GetEntityByID(eid);
if(!entity)
SHSceneNode* entityNode = SHSceneManager::GetCurrentSceneGraph().GetNode(eid);
if(!entity || !entityNode)
{
ImGui::End();
return;
}
ImGui::TextColored(ImGuiColors::green, "EID: %zu", eid);
SHEditorWidgets::CheckBox("##IsActive", [entity]()->bool {return entity->GetActive(); }, [entity](bool const& active) {entity->SetActive(active); });
SHEditorWidgets::CheckBox("##IsActive", [entityNode]()->bool {return entityNode->IsActive(); }, [entityNode](bool const& active) {entityNode->SetActive(active); });
ImGui::SameLine();
ImGui::InputText("##EntityName", &entity->name);

View File

@ -223,39 +223,20 @@ namespace SHADE
{
if(editor->SaveScene())
{
const SHEditorStateChangeEvent STATE_CHANGE_EVENT
{
.previousState = editor->editorState
};
editor->editorState = SHEditor::State::PLAY;
SHEventManager::BroadcastEvent<SHEditorStateChangeEvent>(STATE_CHANGE_EVENT, SH_EDITOR_ON_PLAY_EVENT);
editor->Play();
}
}
ImGui::EndDisabled();
ImGui::BeginDisabled(editor->editorState == SHEditor::State::PAUSE);
if(ImGui::SmallButton(ICON_MD_PAUSE))
{
const SHEditorStateChangeEvent STATE_CHANGE_EVENT
{
.previousState = editor->editorState
};
editor->editorState = SHEditor::State::PAUSE;
SHEventManager::BroadcastEvent<SHEditorStateChangeEvent>(STATE_CHANGE_EVENT, SH_EDITOR_ON_PAUSE_EVENT);
editor->Pause();
}
ImGui::EndDisabled();
ImGui::BeginDisabled(editor->editorState == SHEditor::State::STOP);
if(ImGui::SmallButton(ICON_MD_STOP))
{
const SHEditorStateChangeEvent STATE_CHANGE_EVENT
{
.previousState = editor->editorState
};
editor->editorState = SHEditor::State::STOP;
SHEventManager::BroadcastEvent<SHEditorStateChangeEvent>(STATE_CHANGE_EVENT, SH_EDITOR_ON_STOP_EVENT);
editor->LoadScene(SHSceneManager::GetCurrentSceneAssetID());
editor->Stop();
}
ImGui::EndDisabled();
ImGui::EndMenuBar();

View File

@ -40,6 +40,7 @@ namespace SHADE
shouldUpdateCamera = false;
}
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
SHEditor* editor = SHSystemManager::GetSystem<SHEditor>();
if (Begin())
{
@ -51,7 +52,6 @@ namespace SHADE
beginCursorPos = ImGui::GetCursorScreenPos();
viewportMousePos = { mousePos.x - beginCursorPos.x, mousePos.y - beginCursorPos.y };
gfxSystem->GetMousePickSystem()->SetViewportMousePos(viewportMousePos);
ImGui::Image((ImTextureID)descriptorSet, { beginContentRegionAvailable.x, beginContentRegionAvailable.y });
if (ImGui::IsWindowHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Right))
@ -64,24 +64,25 @@ namespace SHADE
shouldUpdateCamera = true;
}
if (ImGui::IsWindowFocused() && !ImGui::IsMouseDown(ImGuiMouseButton_Right))
if (editor->editorState != SHEditor::State::PLAY && ImGui::IsWindowFocused() && !ImGui::IsMouseDown(ImGuiMouseButton_Right))
{
if (ImGui::IsKeyReleased(ImGuiKey_Q))
if (ImGui::IsKeyReleased(ImGuiKey_W))
{
transformGizmo.operation = SHTransformGizmo::Operation::TRANSLATE;
}
if (ImGui::IsKeyReleased(ImGuiKey_W))
if (ImGui::IsKeyReleased(ImGuiKey_E))
{
transformGizmo.operation = SHTransformGizmo::Operation::ROTATE;
}
if (ImGui::IsKeyReleased(ImGuiKey_E))
if (ImGui::IsKeyReleased(ImGuiKey_R))
{
transformGizmo.operation = SHTransformGizmo::Operation::SCALE;
}
}
}
ImGuizmo::SetRect(beginCursorPos.x, beginCursorPos.y, beginContentRegionAvailable.x, beginContentRegionAvailable.y);
transformGizmo.Draw();
if(editor->editorState != SHEditor::State::PLAY)
transformGizmo.Draw();
ImGui::End();
ImGui::PopStyleVar();
}

View File

@ -168,6 +168,18 @@ namespace SHADE
{
SHCommandManager::UndoCommand();
}
if(ImGui::IsKeyReleased(ImGuiKey_F5))
{
Play();
}
else if (ImGui::IsKeyReleased(ImGuiKey_F6))
{
Pause();
}
else if (ImGui::IsKeyReleased(ImGuiKey_F7))
{
Stop();
}
Render();
}
@ -597,6 +609,48 @@ namespace SHADE
}
}
void SHEditor::Play()
{
if(editorState == State::PLAY)
return;
if (SaveScene())
{
const SHEditorStateChangeEvent STATE_CHANGE_EVENT
{
.previousState = editorState
};
editorState = State::PLAY;
SHCommandManager::SwapStacks();
SHEventManager::BroadcastEvent<SHEditorStateChangeEvent>(STATE_CHANGE_EVENT, SH_EDITOR_ON_PLAY_EVENT);
}
}
void SHEditor::Pause()
{
if (editorState == State::PAUSE)
return;
const SHEditorStateChangeEvent STATE_CHANGE_EVENT
{
.previousState = editorState
};
editorState = State::PAUSE;
SHEventManager::BroadcastEvent<SHEditorStateChangeEvent>(STATE_CHANGE_EVENT, SH_EDITOR_ON_PAUSE_EVENT);
}
void SHEditor::Stop()
{
if (editorState == State::STOP)
return;
const SHEditorStateChangeEvent STATE_CHANGE_EVENT
{
.previousState = editorState
};
editorState = SHEditor::State::STOP;
SHCommandManager::SwapStacks();
SHEventManager::BroadcastEvent<SHEditorStateChangeEvent>(STATE_CHANGE_EVENT, SH_EDITOR_ON_STOP_EVENT);
LoadScene(SHSceneManager::GetCurrentSceneAssetID());
}
void SHEditor::NewFrame()
{
SDL_Event event;

View File

@ -184,6 +184,10 @@ namespace SHADE
void LoadScene(AssetID const& assetID) noexcept;
void Play();
void Pause();
void Stop();
// List of selected entities
std::vector<EntityID> selectedEntities;

View File

@ -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()
@ -75,7 +78,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 +146,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 +168,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<int>::min(),
std::numeric_limits<int>::max(),
"%d",
ImGuiInputTextFlags_EnterReturnsTrue);
}
bool SHEditorUI::InputUnsignedInt(const std::string& label, unsigned int& value, bool* isHovered)
@ -190,31 +195,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<float>::lowest(),
std::numeric_limits<float>::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<double>(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());
@ -266,10 +262,10 @@ namespace SHADE
static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y" };
return SHEditorWidgets::DragN<float, 2>(label, COMPONENT_LABELS, { &value.x, &value.y }, 0.1f, "%.3f", float{}, float{}, 0, isHovered);
}
bool SHEditorUI::InputVec3(const std::string& label, SHVec3& value, bool* isHovered, float speed)
bool SHEditorUI::InputVec3(const std::string& label, SHVec3& value, bool* isHovered)
{
static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y", "Z"};
return SHEditorWidgets::DragN<float, 3>(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, speed, "%.3f", float{}, float{}, 0, isHovered);
return SHEditorWidgets::DragN<float, 3>(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, 0.1f, "%.3f", float{}, float{}, 0, isHovered);
}
bool SHEditorUI::InputTextField(const std::string& label, std::string& value, bool* isHovered)

View File

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

View File

@ -21,6 +21,7 @@
#include "SH_API.h"
#include "ECS_Base/General/SHFamily.h"
#include "Assets/SHAssetMacros.h"
#include "ECS_Base/Managers/SHComponentManager.h"
namespace SHADE
{
@ -116,6 +117,56 @@ namespace SHADE
sceneChanged = true;
}
/********************************************************************
* \brief
* Check if the Entity's scene node is active and all the
* components specified are active.
* This does not check if the entity HasComponent. Please use
* CheckNodeAndHasComponentActive for that.
* \param eid
* EntityID of the entity to check for.
* \return
* true if scene node is active and all the components specified
* are also active.
********************************************************************/
template<typename ...ComponentTypes>
static std::enable_if_t<(... && std::is_base_of_v<SHComponent, ComponentTypes>), bool> CheckNodeAndComponentsActive(EntityID eid)
{
return CheckNodeActive(eid) && (... && SHComponentManager::GetComponent_s<ComponentTypes>(eid)->isActive);
}
/********************************************************************
* \brief
* Check if the Entity's scene node is active and all the
* components specified are active.
* This also checks to verify that the entity has such components.
* \param eid
* EntityID of the entity to check for.
* \return
* true if scene node is active and all the components specified
* are also active.
********************************************************************/
template<typename ...ComponentTypes>
static std::enable_if_t<(... && std::is_base_of_v<SHComponent, ComponentTypes>), bool> CheckNodeAndHasComponentsActive(EntityID eid)
{
return CheckNodeActive(eid)
&& (... && SHComponentManager::HasComponent<ComponentTypes>(eid))
&& (... && SHComponentManager::GetComponent_s<ComponentTypes>(eid)->isActive);
}
/********************************************************************
* \brief
* Check if Scene node is active.
* \param eid
* EntityID of the entity to check for.
* \return
* true if scene node is active
********************************************************************/
static bool CheckNodeActive(EntityID eid)
{
return GetCurrentSceneGraph().IsActiveInHierarchy(eid);
}
/*!*************************************************************************
* \brief

View File

@ -136,7 +136,7 @@ namespace SHADE
for (auto* child : children)
{
SetActive(newActiveState);
child->SetActive(newActiveState);
}
}

View File

@ -0,0 +1,69 @@
#pragma once
#pragma once
#include "SH_API.h"
#include <deque>
namespace SHADE
{
template<typename T>
class SH_API SHDeque
{
public:
using ValueType = T;
using Pointer = T*;
using ValueRef = T&;
using ValueConstRef = T const&;
using SizeType = uint32_t;
using ContainerType = std::deque<ValueType>;
using ContainerTypeConstRef = std::deque<ValueType>;
SHDeque(SizeType n) : max_size(n) {}
ContainerTypeConstRef const& GetDeque() const
{
return deque;
}
void Push(ValueConstRef obj)
{
if (deque.size() < max_size)
deque.push_front(std::move(obj));
else
{
deque.pop_back();
deque.push_front(std::move(obj));
}
}
bool Empty()
{
return deque.empty();
}
void Pop()
{
deque.pop_front();
}
ValueConstRef Top()
{
return deque.front();
}
SizeType Size() const noexcept
{
return deque.size();
}
void Clear()
{
deque.clear();
}
private:
int max_size;
ContainerType deque{};
};
}

View File

@ -0,0 +1,35 @@
#pragma once
#include "SH_API.h"
#include "ECS_Base/Components/SHComponent.h"
namespace SHADE
{
class SH_API SHCanvasComponent final: public SHComponent
{
using CanvasSizeType = uint32_t;
public:
SHCanvasComponent();
~SHCanvasComponent() = default;
void SetCanvasSize(CanvasSizeType width, CanvasSizeType height) noexcept;
void SetCanvasWidth(CanvasSizeType width) noexcept;
void SetCanvasHeight(CanvasSizeType height) noexcept;
CanvasSizeType const GetCanvasWidth() const noexcept;
CanvasSizeType const GetCanvasHeight() const noexcept;
private:
CanvasSizeType width;
CanvasSizeType height;
};
}

View File

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

View File

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

View File

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

View File

@ -19,9 +19,14 @@ of DigiPen Institute of Technology is prohibited.
// External Dependencies
#include "Editor/SHEditorUI.h"
// Project Headers
#include "Utility/Debug.hxx"
#include "Utility/Convert.hxx"
namespace SHADE
{
/*---------------------------------------------------------------------------------*/
/* UndoRedoStack - Properties */
/*---------------------------------------------------------------------------------*/
bool UndoRedoStack::UndoActionPresent::get()
{
return commandStack->Count > 0 && latestActionIndex >= 0;
@ -32,7 +37,10 @@ namespace SHADE
return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1;
}
void UndoRedoStack::Add(Command command)
/*---------------------------------------------------------------------------------*/
/* UndoRedoStack - Usage Functions */
/*---------------------------------------------------------------------------------*/
void UndoRedoStack::Add(ICommand^ command)
{
// Erase any other actions ahead of the current action
if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1)
@ -52,8 +60,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 +70,192 @@ namespace SHADE
if (!RedoActionPresent)
return;
Command cmd = commandStack[latestActionIndex];
cmd.Field->SetValue(cmd.Object, cmd.NewData);
ICommand^ cmd = commandStack[latestActionIndex];
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)
{
field->SetValue(objectToChange, newData);
return true;
}
return false;
}
bool FieldChangeCommand::Unexceute()
{
if (field && objectToChange)
{
field->SetValue(objectToChange, oldData);
return true;
}
return false;
}
bool FieldChangeCommand::Merge(ICommand^ command)
{
FieldChangeCommand^ otherCommand = safe_cast<FieldChangeCommand^>(command);
if (otherCommand == nullptr)
{
Debug::LogWarning("[Field Change Command] Attempted to merge two incompatible commands!");
return false;
}
// Only merge if they are workng on the same object and field
if (field == otherCommand->field && objectToChange == otherCommand->objectToChange)
{
newData = otherCommand->newData;
return true;
}
return false;
}
/*---------------------------------------------------------------------------------*/
/* ListElementChangeCommand - Constructor */
/*---------------------------------------------------------------------------------*/
ListElementChangeCommand::ListElementChangeCommand(System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData)
: list { list }
, index { index }
, newData { newData }
, oldData { oldData }
{}
/*---------------------------------------------------------------------------------*/
/* ListElementChangeCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
bool ListElementChangeCommand::Execute()
{
if (list && index < list->Count)
{
list[index] = newData;
return true;
}
return false;
}
bool ListElementChangeCommand::Unexceute()
{
if (list && index < list->Count)
{
list[index] = oldData;
return true;
}
return false;
}
bool ListElementChangeCommand::Merge(ICommand^ command)
{
ListElementChangeCommand^ otherCommand = safe_cast<ListElementChangeCommand^>(command);
if (otherCommand == nullptr)
{
Debug::LogWarning("[Field Change Command] Attempted to merge two incompatible commands!");
return false;
}
if (command && list == otherCommand->list && index == otherCommand->index)
{
newData = otherCommand->newData;
return true;
}
}
/*---------------------------------------------------------------------------------*/
/* ListElementAddCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
ListElementAddCommand::ListElementAddCommand(System::Collections::IList^ list, int addIndex, System::Object^ data)
: list { list }
, addIndex { addIndex }
, data { data }
{}
/*---------------------------------------------------------------------------------*/
/* ListElementAddCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
bool ListElementAddCommand::Execute()
{
if (list)
{
list->Insert(addIndex, data);
return true;
}
return false;
}
bool ListElementAddCommand::Unexceute()
{
if (list && addIndex < list->Count)
{
list->RemoveAt(addIndex);
return true;
}
return false;
}
bool ListElementAddCommand::Merge(ICommand^)
{
// Not allowed
return false;
}
/*---------------------------------------------------------------------------------*/
/* ListElementRemoveCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
ListElementRemoveCommand::ListElementRemoveCommand(System::Collections::IList^ list, int removeIndex, System::Object^ data)
: list { list }
, removeIndex { removeIndex }
, data { data }
{}
/*---------------------------------------------------------------------------------*/
/* ListElementRemoveCommand - ICommand Functions */
/*---------------------------------------------------------------------------------*/
bool ListElementRemoveCommand::Execute()
{
if (list && removeIndex < list->Count)
{
list->RemoveAt(removeIndex);
return true;
}
return false;
}
bool ListElementRemoveCommand::Unexceute()
{
if (list)
{
list->Insert(removeIndex, data);
return true;
}
return false;
}
bool ListElementRemoveCommand::Merge(ICommand^)
{
// Not allowed
return false;
}
}

View File

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

View File

@ -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<Script^>^ 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
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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