SHADE_Y3/SHADE_Managed/src/Scripts/ScriptStore.cxx

677 lines
22 KiB
C++

/************************************************************************************//*!
\file ScriptStore.cxx
\author Tng Kah Wei, kahwei.tng, 390009620
\par email: kahwei.tng\@digipen.edu
\date Oct 28, 2021
\brief Contains the definition of the functions for the ScriptStore 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 "ScriptStore.hxx"
// Standard Libraries
#include <sstream>
// External Dependencies
#include "ECS_Base/Managers/SHEntityManager.h"
#include "Tools/SHLog.h"
// Project Headers
#include "Utility/Debug.hxx"
#include "Utility/Convert.hxx"
#include "Script.hxx"
#include "Engine/Entity.hxx"
#include "Serialisation/ReflectionUtilities.hxx"
namespace SHADE
{
/*---------------------------------------------------------------------------------*/
/* Scripts Manipulation Functions */
/*---------------------------------------------------------------------------------*/
generic<typename T>
T ScriptStore::AddScript(Entity entity)
{
// Check if entity exists
if (!EntityUtils::IsValid(entity))
throw gcnew System::ArgumentException("Invalid Entity provided to add a Script to.");
System::Collections::Generic::List<Script^> ^ entityScriptList;
// Check if storage for scripts of this entity exists
if (!scripts.ContainsKey(entity))
{
// Create a new list for this set of scripts
entityScriptList = gcnew System::Collections::Generic::List<Script^>();
scripts.Add(entity, entityScriptList);
}
else
{
entityScriptList = scripts[entity];
}
// Create the script and add it in
array<Object^>^ params = gcnew array<Object^>{GameObject(entity)};
Script^ script = safe_cast<Script^>(System::Activator::CreateInstance(T::typeid, params));
entityScriptList->Add(script);
awakeList.Add(script);
startList.Add(script);
script->OnAttached();
return safe_cast<T>(script);
}
bool ScriptStore::AddScriptViaName(Entity entity, System::String^ scriptName)
{
SAFE_NATIVE_CALL_BEGIN
Script^ script;
return AddScriptViaNameWithRef(entity, scriptName, script);
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
return false;
}
bool ScriptStore::AddScriptViaNameWithRef(Entity entity, System::String^ scriptName, Script^% createdScript)
{
// Check if we are set up to get scripts
if (addScriptMethod == nullptr)
{
Debug::LogError("[ScriptStore] Native AddScript<T>() was not loaded. Unable to add scripts.");
return false;
}
// Get the script if it exists
System::Type^ scriptType = getScriptType(scriptName);
if (scriptType == nullptr)
{
std::ostringstream oss;
oss << "[ScriptStore] No Script named "
<< Convert::ToNative(scriptName)
<< " found!";
Debug::LogError(oss.str());
return false;
}
// Otherwise, add the script
System::Reflection::MethodInfo^ method = addScriptMethod->MakeGenericMethod(scriptType);
try
{
array<Object^>^ params = gcnew array<Object^>{entity};
createdScript = safe_cast<Script^>(method->Invoke(nullptr, params));
}
catch (System::Exception^ e)
{
std::ostringstream oss;
oss << "[ScriptStore] Failed to add Script named \"" << Convert::ToNative(scriptName)
<< "\" to Entity #" << entity << "! (" << Convert::ToNative(e->GetType()->Name) << ")";
Debug::LogError(oss.str());
return false;
}
return true;
}
generic<typename T>
T ScriptStore::GetScript(Entity entity)
{
// Check if entity exists
if (!EntityUtils::IsValid(entity))
throw gcnew System::ArgumentException("Invalid Entity provided to get a Script from.");
// Check if entity exists in the script storage
if (!scripts.ContainsKey(entity))
{
return T();
}
// Search for and obtain
for each (Script^ script in scripts[entity])
{
try
{
T actualScript = safe_cast<T>(script);
return actualScript;
}
catch (System::InvalidCastException^)
{
continue;
}
}
return T();
}
generic <typename T>
T ScriptStore::GetScriptInChildren(Entity entity)
{
// Check if entity exists and is a valid GameObject
if (!EntityUtils::IsValid(entity))
throw gcnew System::ArgumentException("Invalid Entity provided to get a Script from.");
// Check if entity exists in the script storage
if (!scripts.ContainsKey(entity))
{
return T();
}
// Get Entity's SceneGraphNode
SHSceneNode* sceneGraphNode = SHSceneManager::GetCurrentSceneGraph().GetNode(entity);
if (sceneGraphNode == nullptr)
{
std::ostringstream oss;
oss << "[ECS_CLI] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!";
SHLog::Warning(oss.str());
return T();
}
// Search direct children first
for (const auto& child : sceneGraphNode->GetChildren())
{
T script = GetScript<T>(child->GetEntityID());
if (script != nullptr)
return script;
}
// Search their children
for (const auto& child : sceneGraphNode->GetChildren())
{
T script = GetScript<T>(child->GetEntityID());
if (script != nullptr)
return script;
}
// None here
return T();
}
generic <typename T>
System::Collections::Generic::IEnumerable<T>^ ScriptStore::GetScripts(Entity entity)
{
// Check if entity exists and is a valid GameObject
if (!EntityUtils::IsValid(entity))
throw gcnew System::ArgumentException("Invalid Entity provided to get a Script from.");
// Create a list to store entries
System::Collections::Generic::List<T>^ foundScripts = gcnew System::Collections::Generic::List<T>();
// Check if entity exists in the script storage
if (!scripts.ContainsKey(entity))
{
return foundScripts;
}
// Search for and obtain
for each (Script^ script in scripts[entity])
{
try
{
T actualScript = safe_cast<T>(script);
foundScripts->Add(actualScript);
}
catch (System::InvalidCastException^)
{
continue;
}
}
return foundScripts;
}
System::Collections::Generic::IEnumerable<Script^>^ ScriptStore::GetAllScripts(Entity entity)
{
// Check if entity exists
if (!EntityUtils::IsValid(entity))
return nullptr;
// Check if entity exists in the script storage
if (scripts.ContainsKey(entity))
{
return scripts[entity];
}
return nullptr;
}
generic<typename T>
void ScriptStore::RemoveScript(Entity entity)
{
// Check if entity exists
if (!EntityUtils::IsValid(entity))
throw gcnew System::ArgumentException("Invalid Entity provided to remove a Script from.");
// Check if entity exists in the script storage
if (!scripts.ContainsKey(entity))
{
Debug::LogError("[ScriptStore] Attempted to remove a Script that does not belong to the specified Entity!");
return;
}
// Search for and obtain
for each (Script^ script in scripts[entity])
{
try
{
safe_cast<T>(script);
removeScript(script);
}
catch (System::InvalidCastException^)
{
continue;
}
}
}
bool ScriptStore::RemoveScript(Entity entity, Script^ script)
{
// Check if entity exists
if (!EntityUtils::IsValid(entity))
{
Debug::LogError("[ScriptStore] Attempted to remove a Script from an invalid Entity!");
return false;
}
// Check if entity exists in the script storage
if (!scripts.ContainsKey(entity))
{
Debug::LogError("[ScriptStore] Attempted to remove a Script that does not belong to the specified Entity!");
return false;
}
// Check if the script exists to begin with
if (!scripts[entity]->Contains(script))
{
Debug::LogError("[ScriptStore] Attempted to remove a Script that does not belong to the specified Entity!");
return false;
}
// Script found, queue it for deletion
removeScript(script);
return true;
}
void ScriptStore::RemoveAllScripts(Entity entity)
{
SAFE_NATIVE_CALL_BEGIN
// Check if entity exists in the script storage
if (!scripts.ContainsKey(entity))
return;
// Search for and clear
System::Collections::Generic::List<Script^>^ scriptList = scripts[entity];
for each (Script^ script in scriptList)
{
removeScript(script);
}
scriptList->Clear();
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
}
void ScriptStore::RemoveAllScriptsImmediately(Entity entity, bool callOnDestroy)
{
SAFE_NATIVE_CALL_BEGIN
// Check if entity exists in the script storage
if (!scripts.ContainsKey(entity))
return;
// Clear all
System::Collections::Generic::List<Script^>^ scriptList = scripts[entity];
for each (Script ^ script in scriptList)
{
// Call OnDestroy only if indicated and also in play mode
if (callOnDestroy)
{
script->OnDestroy();
}
script->OnDetached();
// Remove scripts from awakening if they were not woken up to begin with
awakeList.Remove(script);
startList.Remove(script);
}
scriptList->Clear();
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
}
/*---------------------------------------------------------------------------------*/
/* Lifecycle Functions */
/*---------------------------------------------------------------------------------*/
void ScriptStore::Init()
{
// Create an enumerable list of script types
refreshScriptTypeList();
// Get stored methods for interop variants of functions
getGenericMethods();
}
void ScriptStore::FrameSetUp()
{
SAFE_NATIVE_CALL_BEGIN
// Clear the awake queue
for each (Script^ script in awakeList)
{
script->Awake();
}
awakeList.Clear();
// Clear the start queue
for each (Script^ script in startList)
{
if (script->Owner.IsActiveInHierarchy)
{
script->Start();
}
else
{
inactiveStartList.Add(script);
}
}
startList.Clear();
startList.AddRange(%inactiveStartList);
inactiveStartList.Clear();
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
}
void ScriptStore::FrameCleanUp()
{
SAFE_NATIVE_CALL_BEGIN
// Clear the queue
while (disposalQueue.Count > 0)
{
Script^ script = disposalQueue.Dequeue();
script->OnDestroy();
auto entity = script->Owner.GetEntity();
auto scriptList = scripts[script->Owner.GetEntity()];
scriptList->Remove(script);
if (scriptList->Count <= 0)
{
scripts.Remove(entity);
}
}
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
}
void ScriptStore::Exit()
{
SAFE_NATIVE_CALL_BEGIN
// Run the deinit all scripts if needed
//if (Application::IsPlaying)
{
Debug::Log("Running OnDestroy() for scripts.");
for each (System::Collections::Generic::KeyValuePair<Entity, ScriptList^> entity in scripts)
{
for each (Script^ script in entity.Value)
{
script->OnDestroy();
}
}
}
// Clear Script Storage
scripts.Clear();
awakeList.Clear();
startList.Clear();
disposalQueue.Clear();
scriptTypeList = nullptr;
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
}
/*---------------------------------------------------------------------------------*/
/* Script Information Functions */
/*---------------------------------------------------------------------------------*/
System::Collections::Generic::IEnumerable<System::Type^>^ ScriptStore::GetAvailableScriptList()
{
return scriptTypeList;
}
/*---------------------------------------------------------------------------------*/
/* Script Execution Functions */
/*---------------------------------------------------------------------------------*/
void ScriptStore::ExecuteFixedUpdate()
{
SAFE_NATIVE_CALL_BEGIN
for each (System::Collections::Generic::KeyValuePair<Entity, ScriptList^> entity in scripts)
{
// Check active state
if (!isEntityActive(entity.Key))
continue;
// Update each script
for each (Script^ script in entity.Value)
{
script->FixedUpdate();
}
}
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
}
void ScriptStore::ExecuteUpdate()
{
SAFE_NATIVE_CALL_BEGIN
for each (System::Collections::Generic::KeyValuePair<Entity, ScriptList^> entity in scripts)
{
// Check active state
if (!isEntityActive(entity.Key))
continue;
// Update each script
for each (Script^ script in entity.Value)
{
script->Update();
}
}
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
}
void ScriptStore::ExecuteLateUpdate()
{
SAFE_NATIVE_CALL_BEGIN
for each (System::Collections::Generic::KeyValuePair<Entity, ScriptList^> entity in scripts)
{
// Check active state
if (!isEntityActive(entity.Key))
continue;
// Update each script
for each (Script^ script in entity.Value)
{
script->LateUpdate();
}
}
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
}
bool ScriptStore::SerialiseScripts(Entity entity, System::IntPtr yamlNodePtr)
{
SAFE_NATIVE_CALL_BEGIN
// Convert to pointer
YAML::Node* yamlNode = reinterpret_cast<YAML::Node*>(yamlNodePtr.ToPointer());
// Check if yamlNode is valid
if (yamlNode == nullptr)
{
Debug::LogWarning("[ScriptStore] Attempted to serialise scripts with an invalid YAML Node! Skipping.");
return false;
}
// Check if entity exists, otherwise nothing
if (!EntityUtils::IsValid(entity))
{
Debug::LogWarning("[ScriptStore] Attempted to serialise scripts for an invalid Entity! Skipping.");
return false;
}
// Check if entity exists in the script storage
if (!scripts.ContainsKey(entity))
return true;
// Serialise each script
yamlNode->SetStyle(YAML::EmitterStyle::Block);
System::Collections::Generic::List<Script^>^ scriptList = scripts[entity];
for each (Script^ script in scriptList)
{
ReflectionUtilities::Serialise(script, *yamlNode);
}
return true;
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
return false;
}
bool ScriptStore::DeserialiseScripts(Entity entity, System::IntPtr yamlNodePtr)
{
SAFE_NATIVE_CALL_BEGIN
// Convert to pointer
YAML::Node* yamlNode = reinterpret_cast<YAML::Node*>(yamlNodePtr.ToPointer());
// Check if yamlNode is valid
if (yamlNode == nullptr)
{
Debug::LogWarning("[ScriptStore] Attempted to deserialise scripts with an invalid YAML Node! Skipping.");
return false;
}
// Check if entity exists, otherwise nothing
if (!EntityUtils::IsValid(entity))
{
Debug::LogWarning("[ScriptStore] Attempted to deserialise scripts for an invalid Entity! Skipping.");
return false;
}
// Go through all elements in the node
for (YAML::Node& node : *yamlNode)
{
// Get the name of the script
if (!node["Type"])
{
Debug::LogWarning("[ScriptStore] Script with no type detected, skipping.");
continue;
}
System::String^ typeName = Convert::ToCLI(node["Type"].as<std::string>());
// Create
Script^ script;
if (AddScriptViaNameWithRef(entity, typeName, script))
{
// Copy the data in
ReflectionUtilities::Deserialise(script, node);
}
else
{
Debug::LogWarning("[ScriptStore] Script with unloaded type detected, skipping.");
}
}
return true;
SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore")
return false;
}
/*---------------------------------------------------------------------------------*/
/* Helper Functions */
/*---------------------------------------------------------------------------------*/
void ScriptStore::removeScript(Script^ script)
{
// Prepare for disposal
disposalQueue.Enqueue(script);
// Also remove it fromm awake and start queues if they were created but not initialised
awakeList.Remove(script);
startList.Remove(script);
script->OnDetached();
}
namespace
{
/* Select Many */
ref struct Pair
{
System::Reflection::Assembly^ assembly;
System::Type^ type;
};
System::Collections::Generic::IEnumerable<System::Type^>^ selectorFunc(System::Reflection::Assembly^ assembly)
{
return assembly->GetExportedTypes();
}
Pair^ resultSelectorFunc(System::Reflection::Assembly^ assembly, System::Type^ type)
{
Pair^ p = gcnew Pair();
p->assembly = assembly;
p->type = type;
return p;
}
/* Where */
bool predicateFunc(Pair^ pair)
{
return pair->type->IsSubclassOf(Script::typeid) && !pair->type->IsAbstract;
}
/* Select */
System::Type^ selectorFunc(Pair^ pair)
{
return pair->type;
}
}
void ScriptStore::refreshScriptTypeList()
{
using namespace System;
using namespace System::Reflection;
using namespace System::Linq;
using namespace System::Collections::Generic;
/* Select Many: Types in Loaded Assemblies */
IEnumerable<Assembly^>^ assemblies = AppDomain::CurrentDomain->GetAssemblies();
Func<Assembly^, IEnumerable<Type^>^>^ collectionSelector = gcnew Func<Assembly^, IEnumerable<Type^>^>(selectorFunc);
Func<Assembly^, Type^, Pair^>^ resultSelector = gcnew Func<Assembly^, Type^, Pair^>(resultSelectorFunc);
IEnumerable<Pair^>^ selectManyResult = Enumerable::SelectMany(assemblies, collectionSelector, resultSelector);
/* Where: Are concrete Scripts */
Func<Pair^, bool>^ predicate = gcnew Func<Pair^, bool>(predicateFunc);
IEnumerable<Pair^>^ whereResult = Enumerable::Where(selectManyResult, predicate);
/* Select: Select them all */
Func<Pair^, Type^>^ selector = gcnew Func<Pair^, Type^>(selectorFunc);
scriptTypeList = Enumerable::Select(whereResult, selector);
// Log
std::ostringstream oss;
oss << "[ScriptStore] Successfully retrieved references to " << Enumerable::Count(scriptTypeList)
<< " Script(s) from currently loaded assemblies.";
Debug::Log(oss.str());
}
void ScriptStore::getGenericMethods()
{
addScriptMethod = ScriptStore::typeid->GetMethod("AddScript");
if (addScriptMethod == nullptr)
{
Debug::LogError("[ScriptStore] Failed to get MethodInfo of \"AddScript<T>()\". Adding of scripts from native code will fail.");
}
}
System::Type^ ScriptStore::getScriptType(System::String^ scriptName)
{
// Remove any whitespaces just in case
scriptName = scriptName->Trim();
// Look for the correct script
for each (System::Type^ type in scriptTypeList)
{
if (type->FullName == scriptName || type->Name == scriptName)
{
return type;
}
}
return nullptr;
}
bool ScriptStore::isEntityActive(Entity entity)
{
// Get native Entity
SHEntity* nativeEntity = SHEntityManager::GetEntityByID(entity);
// Entity Validity Check
if (nativeEntity == nullptr)
return false;
// Check active state
return nativeEntity->GetActive();
}
}