/************************************************************************************//*! \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 // External Dependencies #include "ECS_Base/Managers/SHEntityManager.h" // Project Headers #include "Utility/Debug.hxx" #include "Utility/Convert.hxx" #include "Script.hxx" #include "Engine/Entity.hxx" namespace SHADE { /*---------------------------------------------------------------------------------*/ /* Scripts Manipulation Functions */ /*---------------------------------------------------------------------------------*/ generic 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 ^ 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(); scripts.Add(entity, entityScriptList); } else { entityScriptList = scripts[entity]; } // Create the script and add it in array^ params = gcnew array{GameObject(entity)}; Script^ script = safe_cast(System::Activator::CreateInstance(T::typeid, params)); entityScriptList->Add(script); awakeList.Add(script); startList.Add(script); script->OnAttached(); return safe_cast(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() 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^ params = gcnew array{entity}; createdScript = safe_cast(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 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(script); return actualScript; } catch (System::InvalidCastException^) { continue; } } return T(); } generic 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 Transform component and get the children list throw gcnew System::NotImplementedException; //Pls::Transform* tf = Pls::ECS::GetComponent(Convert::ToNative(entity)); //if (tf == nullptr) // return T(); //// Search direct children first //for (const auto& child : tf->GetChildren()) //{ // T script = GetScript(Convert::ToCLI(child)); // if (script != nullptr) // return script; //} //// Search their children //for (const auto& child : tf->GetChildren()) //{ // T script = GetScriptInChildren(Convert::ToCLI(child)); // if (script != nullptr) // return script; //} // None here return T(); } generic System::Collections::Generic::IEnumerable^ 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^ foundScripts = gcnew System::Collections::Generic::List(); // 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(script); foundScripts->Add(actualScript); } catch (System::InvalidCastException^) { continue; } } return foundScripts; } System::Collections::Generic::IEnumerable^ 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 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(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 if (!EntityUtils::IsValid(entity)) { Debug::LogError("[ScriptStore] Attempted to remove Scripts from an invalid Entity!"); return; } // Check if entity exists in the script storage if (!scripts.ContainsKey(entity)) return; // Search for and clear System::Collections::Generic::List^ 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 if (!EntityUtils::IsValid(entity)) { Debug::LogError("[ScriptStore] Attempted to remove Scripts from an invalid Entity!"); return; } // Check if entity exists in the script storage if (!scripts.ContainsKey(entity)) return; // Clear all System::Collections::Generic::List^ 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(); /*if (Application::IsPlaying) { 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 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^ ScriptStore::GetAvailableScriptList() { return scriptTypeList; } /*---------------------------------------------------------------------------------*/ /* Script Execution Functions */ /*---------------------------------------------------------------------------------*/ void ScriptStore::ExecuteFixedUpdate() { SAFE_NATIVE_CALL_BEGIN for each (System::Collections::Generic::KeyValuePair 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 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 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::Text::StringBuilder^ buffer, int bufferSize) { SAFE_NATIVE_CALL_BEGIN // Create a buffer that we can work with temporarily System::Text::StringBuilder^ jsonString = gcnew System::Text::StringBuilder(); // Check if entity exists, otherwise nothing if (!EntityUtils::IsValid(entity)) return true; // Check if entity exists in the script storage if (!scripts.ContainsKey(entity)) return true; // Serialise each script System::Collections::Generic::List^ scriptList = scripts[entity]; for (int i = 0; i < scriptList->Count; ++i) { throw gcnew System::NotImplementedException; //jsonString->Append(ReflectionUtilities::Serialise(scriptList[i])); // Only add separator if is not last script if (i != scriptList->Count - 1) { jsonString->Append(",\r\n"); } } // Check if the size is too big if (jsonString->Length > bufferSize) return false; // Otherwise we copy it over buffer->Clear(); buffer->Append(jsonString->ToString()); return true; SAFE_NATIVE_CALL_END_N("SHADE.ScriptStore") return false; } bool ScriptStore::DeserialiseScript(Entity entity, System::String^ yaml) { SAFE_NATIVE_CALL_BEGIN // Check if entity exists, otherwise nothing if (!EntityUtils::IsValid(entity)) return false; // Get the name of the script const int FIRST_QUOTE = yaml->IndexOf('\"'); const int FIRST_COLON = yaml->IndexOf(':'); if (FIRST_QUOTE < 0 || FIRST_COLON < 0) // No script name, it's invalid return false; const int SCRIPT_NAME_START = FIRST_QUOTE + 1; const int SCRIPT_NAME_END = FIRST_COLON - 1; System::String^ typeName = yaml->Substring(SCRIPT_NAME_START, SCRIPT_NAME_END - SCRIPT_NAME_START); // Create the script Script^ script; if (AddScriptViaNameWithRef(entity, typeName, script)) { // Copy the data in throw gcnew System::NotImplementedException; //ReflectionUtilities::Deserialise(json, script); 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^ 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^ assemblies = AppDomain::CurrentDomain->GetAssemblies(); Func^>^ collectionSelector = gcnew Func^>(selectorFunc); Func^ resultSelector = gcnew Func(resultSelectorFunc); IEnumerable^ selectManyResult = Enumerable::SelectMany(assemblies, collectionSelector, resultSelector); /* Where: Are concrete Scripts */ Func^ predicate = gcnew Func(predicateFunc); IEnumerable^ whereResult = Enumerable::Where(selectManyResult, predicate); /* Select: Select them all */ Func^ selector = gcnew Func(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()\". 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) throw gcnew System::InvalidOperationException("Attempted to get native Component to an invalid Entity."); // Check active state return nativeEntity->GetActive(); } }