diff --git a/SHADE_Engine/SHADE_Engine.vcxproj b/SHADE_Engine/SHADE_Engine.vcxproj index b7f87645..fbb9599b 100644 --- a/SHADE_Engine/SHADE_Engine.vcxproj +++ b/SHADE_Engine/SHADE_Engine.vcxproj @@ -187,15 +187,24 @@ + + + + + + + + + @@ -262,9 +271,12 @@ Create + + + diff --git a/SHADE_Engine/SHADE_Engine.vcxproj.filters b/SHADE_Engine/SHADE_Engine.vcxproj.filters index fd513c56..77e4ac1f 100644 --- a/SHADE_Engine/SHADE_Engine.vcxproj.filters +++ b/SHADE_Engine/SHADE_Engine.vcxproj.filters @@ -109,6 +109,9 @@ {B3F7140E-1F0C-3DBF-E88D-E01E546139F0} + + {985A7358-04C5-27CF-4D03-D974B9AC0524} + {16CF2D0E-82E3-55BF-4B65-F91EB73852F0} @@ -327,6 +330,9 @@ Math + + Math + Math @@ -348,12 +354,21 @@ Resource + + Resource + Resource + + Resource + Resource + + Resource + Scene @@ -361,6 +376,15 @@ Scene + + Scripting + + + Scripting + + + Scripting + Tools @@ -373,6 +397,15 @@ Tools + + Tools + + + Tools + + + Tools + @@ -559,6 +592,12 @@ Scene + + Scripting + + + Scripting + Tools @@ -568,5 +607,8 @@ Tools + + Tools + \ No newline at end of file diff --git a/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.cpp b/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.cpp index 6005fb01..edf29ec7 100644 --- a/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.cpp +++ b/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.cpp @@ -28,7 +28,7 @@ namespace SHADE //SHEntityManager::RemoveEntity(this->entityID); } - EntityID SHEntity::GetEID() noexcept + EntityID SHEntity::GetEID() const noexcept { return this->entityID; } diff --git a/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.h b/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.h index d499042c..6f2ae36b 100644 --- a/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.h +++ b/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.h @@ -77,7 +77,7 @@ namespace SHADE * \return uint32_t * The entityID of this Entity object. ***************************************************************************/ - EntityID GetEID() noexcept; + EntityID GetEID() const noexcept; /*!************************************************************************* * \brief Set the Active object diff --git a/SHADE_Engine/src/Scripting/SHDotNetRuntime.cpp b/SHADE_Engine/src/Scripting/SHDotNetRuntime.cpp new file mode 100644 index 00000000..2d0cec1e --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHDotNetRuntime.cpp @@ -0,0 +1,198 @@ +/*************************************************************************************//*! +\file SHDotNetRuntime.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 2, 2021 +\brief Contains the definition of the SHDotNetRuntime class. + Implementation of code to set up code for SHDotNetRuntime is based on the + following repository: + https://github.com/mjrousos/SampleCoreCLRHost + +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 Header +#include +// Primary Header +#include "SHDotNetRuntime.h" +// Standard Library +#include +// External Dependencies +#include // PathRemoveFileSpecA +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors/Destructor */ + /*---------------------------------------------------------------------------------*/ + SHDotNetRuntime::SHDotNetRuntime(bool autoInit) + { + if (autoInit) + { + Init(); + } + } + + SHDotNetRuntime::~SHDotNetRuntime() + { + if (IsLoaded()) + { + try + { + Exit(); + } + catch (std::runtime_error& e) + { + SHLOG_ERROR(e.what()); + } + } + } + + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void SHDotNetRuntime::Init() + { + // State checking, in case there was an unload before, we must ensure that the state is valid + if (initialised) + throw std::runtime_error("[DotNetRuntime] Failed to initialise as it was already initialised or was deinitialised into an invalid state."); + + // Get the current executable directory + std::string runtimePath(MAX_PATH, '\0'); + GetModuleFileNameA(nullptr, runtimePath.data(), MAX_PATH); + PathRemoveFileSpecA(runtimePath.data()); + // Since PathRemoveFileSpecA() removes from data(), the size is not updated, so we must manually update it + runtimePath.resize(std::strlen(runtimePath.data())); + + // Do not need to load the library if it was previously loaded + if (coreClr == nullptr) + { + // Construct the CoreCLR path + std::string coreClrPath(runtimePath); // Works + coreClrPath += "\\coreclr.dll"; + + // Load the CoreCLR DLL + coreClr = LoadLibraryExA(coreClrPath.c_str(), nullptr, 0); + if (!coreClr) + { + std::ostringstream oss; + oss << "[DotNetRuntime] Error #" << GetLastError() << " Failed to load CoreCLR from \"" << coreClrPath << "\"\n"; + throw std::runtime_error(oss.str()); + } + + // Step 2: Get CoreCLR hosting functions + initializeCoreClr = getCoreClrFunctionPtr("coreclr_initialize"); + createManagedDelegate = getCoreClrFunctionPtr("coreclr_create_delegate"); + shutdownCoreClr = getCoreClrFunctionPtr("coreclr_shutdown"); + } + + // Step 3: Construct AppDomain properties used when starting the runtime + // Construct the trusted platform assemblies (TPA) list + // This is the list of assemblies that .NET Core can load as + // trusted system assemblies (similar to the .NET Framework GAC). + // For this host (as with most), assemblies next to CoreCLR will + // be included in the TPA list + std::string tpaList = buildTpaList(runtimePath); + + // Define CoreCLR properties + std::array propertyKeys = + { + "TRUSTED_PLATFORM_ASSEMBLIES", // Trusted assemblies (like the GAC) + "APP_PATHS", // Directories to probe for application assemblies + // "APP_NI_PATHS", // Directories to probe for application native images (not used in this sample) + // "NATIVE_DLL_SEARCH_DIRECTORIES", // Directories to probe for native dlls (not used in this sample) + }; + std::array propertyValues = + { + tpaList.c_str(), + runtimePath.c_str() + }; + + // Step 4: Start the CoreCLR runtime + int result = initializeCoreClr + ( + runtimePath.c_str(), // AppDomain base path + "SHADEHost", // AppDomain friendly name + propertyKeys.size(), // Property count + propertyKeys.data(), // Property names + propertyValues.data(), // Property values + &hostHandle, // Host handle + &domainId // AppDomain ID + ); + + // Check if intiialization of CoreCLR failed + throwIfFailed("[DotNetRuntime] Failed to initialize CoreCLR.", result); + + initialised = true; + SHLOG_INFO("[DotNetRuntime] Successfully loaded the .NET 5.0 Runtime."); + } + + void SHDotNetRuntime::Exit() + { + // State checking, in case there was an unload before, we must ensure that the state is valid + if (!initialised) + throw std::runtime_error("[DotNetRuntime] Failed to deinitialise as it was not initialised before."); + + // Shutdown CoreCLR + int result = shutdownCoreClr(hostHandle, domainId); + throwIfFailed("[DotNetRuntime] Failed to shut down CoreCLR.", result); + + // Unset pointers + hostHandle = nullptr; + domainId = 0; + initialised = false; + + SHLOG_INFO("[DotNetRuntime] Successfully shut down the .NET 5.0 Runtime."); + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + std::string SHDotNetRuntime::buildTpaList(const std::string& directory) + { + // Constants + static const std::string SEARCH_PATH = directory + "\\*.dll"; + static constexpr char PATH_DELIMITER = ';'; + + // Create a osstream object to compile the string + std::ostringstream tpaList; + + // Search the current directory for the TPAs (.DLLs) + WIN32_FIND_DATAA findData; + HANDLE fileHandle = FindFirstFileA(SEARCH_PATH.c_str(), &findData); + if (fileHandle != INVALID_HANDLE_VALUE) + { + do + { + // Append the assembly to the list + tpaList << directory << '\\' << findData.cFileName << PATH_DELIMITER; + + // Note that the CLR does not guarantee which assembly will be loaded if an assembly + // is in the TPA list multiple times (perhaps from different paths or perhaps with different NI/NI.dll + // extensions. Therefore, a real host should probably add items to the list in priority order and only + // add a file if it's not already present on the list. + // + // For this simple sample, though, and because we're only loading TPA assemblies from a single path, + // and have no native images, we can ignore that complication. + } + while (FindNextFileA(fileHandle, &findData)); + FindClose(fileHandle); + } + + return tpaList.str(); + } + + void SHDotNetRuntime::throwIfFailed(const std::string& errMsg, int resultCode) + { + if (resultCode < 0) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0') << std::setw(8) + << errMsg + << " Error 0x" << resultCode << "\n"; + throw std::runtime_error(oss.str()); + } + } +} diff --git a/SHADE_Engine/src/Scripting/SHDotNetRuntime.h b/SHADE_Engine/src/Scripting/SHDotNetRuntime.h new file mode 100644 index 00000000..22f8d9c7 --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHDotNetRuntime.h @@ -0,0 +1,149 @@ +/*************************************************************************************//*! +\file SHDotNetRuntime.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 2, 2021 +\brief Contains the interface of a wrapper class for interfacing with the + .NET 5 Runtime. + +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 + +// Standard Libraries +#include // std::setfill, std::setw +#include // std::runtime_error +#include // std::string +#include // std::ostringstream +// External Dependencies +#include // HMODULE +#include // coreclr_* + +namespace SHADE +{ + /********************************************************************************//*! + @brief Class that encapsulates the state of the .NET Core Runtime lifecycle. + *//*********************************************************************************/ + class SHDotNetRuntime + { + public: + /*----------------------------------------------------------------------------------*/ + /* Constructors/Destructor */ + /*----------------------------------------------------------------------------------*/ + /****************************************************************************//*! + @brief Default constructor that immediately initializes the CoreCLR. + + @param[in] autoInit + If true, loads the CoreCLR by calling Init(). + *//*****************************************************************************/ + SHDotNetRuntime(bool autoInit = true); + /****************************************************************************//*! + @brief Destructor that unloads the CoreCLR if it has not been unloaded + yet. + *//*****************************************************************************/ + ~SHDotNetRuntime(); + + // Disallow copy and moving + SHDotNetRuntime(const SHDotNetRuntime&) = delete; + SHDotNetRuntime(SHDotNetRuntime&&) = delete; + + /*----------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*----------------------------------------------------------------------------------*/ + /****************************************************************************//*! + @brief Loads the CoreCLR and grabs pointers to bootstrapping functions and + kickstarts the CoreCLR. + + @throws SystemExitException + Thrown if there is a failure in loading the CLR and related functions. + *//*****************************************************************************/ + void Init(); + /****************************************************************************//*! + @brief Unloads the CoreCLR. + + @throws SystemExitException + Thrown if there is a failure in unloading the CLR. + *//*****************************************************************************/ + void Exit(); + + /*----------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*----------------------------------------------------------------------------------*/ + /****************************************************************************//*! + @brief Checks if the DotNetRuntime has successfully been initialised. + + @return True if this DotNetRuntime has been initialised. + *//*****************************************************************************/ + inline bool IsLoaded() { return coreClr != nullptr; } + /****************************************************************************//*! + @brief Retrieves a function pointer from the a CLR assembly based on the + specified assembly, type and function names. + + @tparam FunctionType + Type of the function pointer that the specified function name will + provide. + + @params[in] assemblyName + Name of the CoreCLR assembly that contains the function. + @params[in] typeName + Name of the CoreCLR type in the assembly that contains the function. + Nested types are separated by a period(.). + @params[in] functionName + Name of the CoreCLR function to get a pointer to. + + @returns Pointer to the function in the assembly that was specified. + *//*****************************************************************************/ + template + FunctionType GetFunctionPtr(const std::string_view& assemblyName, + const std::string_view& typeName, + const std::string_view& functionName); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + bool initialised = false; + // References to CoreCLR key components + HMODULE coreClr = nullptr; + void* hostHandle = nullptr; + unsigned int domainId = 0; + // Function Pointers to CoreCLR functions + coreclr_initialize_ptr initializeCoreClr = nullptr; + coreclr_create_delegate_ptr createManagedDelegate = nullptr; + coreclr_shutdown_ptr shutdownCoreClr = nullptr; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + /****************************************************************************//*! + @brief Retrieves a function pointer from the CoreCLR based on the specified + function name. + + @tparam FunctionType + Type of the function pointer that the specified function name will + provide. + + @params[in] functionName + Name of the CoreCLR function to get a pointer to. + + @returns Pointer to the function in the CoreCLR that was specified. + *//*****************************************************************************/ + template + FunctionType getCoreClrFunctionPtr(const std::string& functionName); + /****************************************************************************//*! + @brief Compiles a semicolon separated string of trusted platform assemblies by + searching the specified directory. + + @params[in] directory + Path to the directory where the trusted platform assemblies reside. + + @returns Semicolon separated string of trusted platform assemblies. + *//*****************************************************************************/ + static std::string buildTpaList(const std::string& directory); + static void throwIfFailed(const std::string& errMsg, int resultCode); + }; +} // namespace PlushieEngine::Scripts + +#include "SHDotNetRuntime.hpp" diff --git a/SHADE_Engine/src/Scripting/SHDotNetRuntime.hpp b/SHADE_Engine/src/Scripting/SHDotNetRuntime.hpp new file mode 100644 index 00000000..3498bc63 --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHDotNetRuntime.hpp @@ -0,0 +1,61 @@ +/*************************************************************************************//*! +\file SHDotNetRuntime.hpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 2, 2021 +\brief Contains the implementation of the template functions of the + DotNetRuntime class. + +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 + +// Primary Include +#include "SHDotNetRuntime.h" + +namespace SHADE +{ + template + FunctionType SHDotNetRuntime::GetFunctionPtr(const std::string_view & assemblyName, + const std::string_view & typeName, + const std::string_view & functionName) + { + FunctionType managedDelegate = nullptr; + int result = createManagedDelegate + ( + hostHandle, + domainId, + assemblyName.data(), + typeName.data(), + functionName.data(), + reinterpret_cast(&managedDelegate) + ); + + // Check if it failed + if (result < 0) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0') << std::setw(8) + << "[DotNetRuntime] Failed to get pointer to function \"" + << typeName << "." << functionName << "\" in assembly (" << assemblyName << "). " + << "Error 0x" << result << "\n"; + throw std::runtime_error(oss.str()); + } + + return managedDelegate; + } + template + FunctionType SHDotNetRuntime::getCoreClrFunctionPtr(const std::string& functionName) + { + FunctionType fPtr = reinterpret_cast(GetProcAddress(coreClr, functionName.c_str())); + if (!fPtr) + { + std::ostringstream oss; + oss << "[DotNetRuntime] Unable to get pointer to function: \"" << functionName << "\""; + throw std::runtime_error(oss.str()); + } + return fPtr; + } +} diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp new file mode 100644 index 00000000..ac8ad84c --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp @@ -0,0 +1,509 @@ +/************************************************************************************//*! +\file SHScriptEngine.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2021 +\brief Contains the implementation for ScriptEngine class. + +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 +// Primary Header +#include "SHScriptEngine.h" +// Standard Library +#include // std::fstream +#include // std::filesystem::canonical, std::filesystem::remove +// Project Headers +#include "Tools/SHLogger.h" +#include "Tools/SHStringUtils.h" + +namespace SHADE +{ + /*--------------------------------------------------------------------------------*/ + /* Static Definitions */ + /*--------------------------------------------------------------------------------*/ + const std::string SHScriptEngine::DEFAULT_CSHARP_NAMESPACE = std::string(DEFAULT_CSHARP_LIB_NAME); + + /*---------------------------------------------------------------------------------*/ + /* Constructors/Destructors */ + /*---------------------------------------------------------------------------------*/ + SHScriptEngine::SHScriptEngine() + {} + + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void SHScriptEngine::Init() + { + // Do not allow initialization if already initialised + if (dotNet.IsLoaded()) + { + SHLOG_ERROR("[ScriptEngine] Attempted to initialise an already loaded DotNetRuntime."); + return; + } + + dotNet.Init(); + + // Load all the helpers + loadFunctions(); + + // Generate script assembly if it hasn't been before + if (!fileExists(std::string(MANAGED_SCRIPT_LIB_NAME) + ".dll")) + { + BuildScriptAssembly(); + } + + // Initialise the CSharp Engine + csEngineInit(); + + // Link events + // - Entity Creation + /*onEntityCreate = [this](const SHEntity& e) + { + csGOLibNotifyNewEntity(e.GetEID()); + }; + ECS::OnEntityCreated += onEntityCreate;*/ + // - Entity Destruction + /*onEntityDestroy = [this](const SHEntity& e) + { + csScriptsRemoveAll(e.GetEID()); + csGOLibNotifyDestroyEntity(e.GetEID()); + }; + ECS::OnEntityDestroy += onEntityDestroy;*/ + } + void SHScriptEngine::UnloadScriptAssembly() + { + csEngineUnloadScripts(); + } + void SHScriptEngine::LoadScriptAssembly() + { + csEngineLoadScripts(); + } + void SHScriptEngine::ReloadScriptAssembly() + { + csEngineReloadScripts(); + } + void SHScriptEngine::ExecuteFixedUpdates() + { + csScriptsExecuteFixedUpdate(); + } + + void SHScriptEngine::ExecuteOnTrigger() + { + csScriptsExecuteOnTrigger(); + } + + void SHScriptEngine::Exit() + { + // Do not allow deinitialization if not initialised + if (!dotNet.IsLoaded()) + { + SHLOG_ERROR("[ScriptEngine] Attempted to clean up an unloaded DotNetRuntime."); + return; + } + + // Unlink events + /*ECS::OnEntityCreated -= onEntityCreate; + ECS::OnEntityDestroy -= onEntityDestroy;*/ + + // Clean up the CSharp Engine + csEngineExit(); + + // Shut down the CLR + dotNet.Exit(); + } + + /*---------------------------------------------------------------------------------*/ + /* Script Manipulation Functions */ + /*---------------------------------------------------------------------------------*/ + bool SHScriptEngine::AddScript(const SHEntity& entity, const std::string_view& scriptName) const + { + return csScriptsAdd(entity.GetEID(), scriptName.data()); + } + void SHScriptEngine::RemoveAllScripts(const SHEntity& entity) const + { + csScriptsRemoveAll(entity.GetEID()); + } + void SHScriptEngine::RemoveAllScriptsImmediately(const SHEntity& entity, bool callOnDestroy) const + { + csScriptsRemoveAllImmediately(entity.GetEID(), callOnDestroy); + } + + /*---------------------------------------------------------------------------------*/ + /* Script Serialisation Functions */ + /*---------------------------------------------------------------------------------*/ + std::string SHScriptEngine::SerialiseScripts(const SHEntity& entity) const + { + // Create buffer needed to store serialised script data + constexpr int BUFFER_SIZE = 10240; + std::unique_ptr buffer { new char[BUFFER_SIZE] }; + std::memset(buffer.get(), 0, BUFFER_SIZE); + + // Attempt to serialise the script + std::string result; + if (csScriptsSerialise(entity.GetEID(), buffer.get(), BUFFER_SIZE)) + { + result = std::string(buffer.get()); + } + else + { + SHLOG_ERROR("[ScriptEngine] Failed to serialise scripts as string buffer is too small!"); + } + + // Return an empty string since we failed to serialise + return result; + } + + /*---------------------------------------------------------------------------------*/ + /* Script Serialisation Functions */ + /*---------------------------------------------------------------------------------*/ + void SHScriptEngine::DeserialiseScript(const SHEntity& entity, const std::string& yaml) const + { + csScriptDeserialise(entity.GetEID(), yaml.c_str()); + } + + /*---------------------------------------------------------------------------------*/ + /* Script Editor Functions */ + /*---------------------------------------------------------------------------------*/ + void SHScriptEngine::RenderScriptsInInspector(const SHEntity& entity) const + { + csEditorRenderScripts(entity.GetEID()); + } + + /*---------------------------------------------------------------------------------*/ + /* Static Utility Functions */ + /*---------------------------------------------------------------------------------*/ + bool SHScriptEngine::BuildScriptAssembly(bool debug) + { + constexpr std::string_view BUILD_LOG_PATH = "../Build.log"; + + // Prepare directory (delete useless files) + deleteFolder("net5.0"); + deleteFolder("ref"); + deleteFolder("../PlushieGameManaged"); + deleteFolder("../obj"); + + // Attempt to build the assembly + std::ostringstream oss; + oss << "[ScriptEngine] Building " << (debug ? " debug " : "") << "Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!"; + SHLOG_INFO(oss.str()); + oss.str(""); + const bool BUILD_SUCCESS = execProcess + ( + L"C:\\Windows\\system32\\cmd.exe", + L"/K \"dotnet build \"../PlushieGameManaged.csproj\" -c Debug -o \"./tmp/\" -fl -flp:LogFile=build.log;Verbosity=quiet & exit\"" + ) == 0; + if (BUILD_SUCCESS) + { + // Copy to built dll to the working directory and replace + std::filesystem::copy_file("./tmp/PlushieGameManaged.dll", "PlushieGameManaged.dll", std::filesystem::copy_options::overwrite_existing); + + oss << "[ScriptEngine] Successfully built Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!"; + SHLOG_INFO(oss.str()); + } + else + { + oss << "[ScriptEngine] Failed to build Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!"; + SHLOG_ERROR(oss.str()); + } + + // Clean up built files + deleteFolder("./tmp"); + + // Read the build log and output to the console + dumpBuildLog(BUILD_LOG_PATH); + // Delete the build log file since we no longer need it + deleteFile(BUILD_LOG_PATH); + + return BUILD_SUCCESS; + } + + void SHScriptEngine::GenerateScriptsCsProjFile(const std::filesystem::path& path) + { + // Sample + static std::string_view FILE_CONTENTS = +"\n\ + \n\ + net5.0\n\ + x64\n\ + Release;Debug\n\ + \n\ + \n\ + .\\bin_Release-x64\n\ + x64\n\ + \n\ + \n\ + .\\bin_Debug-x64\n\ + x64\n\ + DEBUG;TRACE\n\ + false\n\ + full\n\ + true\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + .\\bin\\PlushieAPI.dll\n\ + \n\ + \n\ +"; + + // Attempt to create the file + std::ofstream file(path); + if (!file.is_open()) + throw std::runtime_error("Unable to create CsProj file!"); + + // Fill the file + file << FILE_CONTENTS; + + // Close + file.close(); + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + void SHScriptEngine::loadFunctions() + { + std::ostringstream oss; + oss << "[ScriptEngine] Loading \"" << DEFAULT_CSHARP_LIB_NAME << "\" CLR library."; + SHLOG_INFO(oss.str()); + + // Load functions + csEngineInit = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "Init" + ); + csEngineLoadScripts = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "LoadScriptAssembly" + ); + csEngineUnloadScripts = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "UnloadScriptAssembly" + ); + csEngineReloadScripts = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "ReloadScriptAssembly" + ); + csEngineExit = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "Exit" + ); + csScriptsFrameSetUp = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "FrameSetUp" + ); + csScriptsExecuteOnTrigger = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "ExecuteOnTrigger" + ); + csScriptsExecuteFixedUpdate = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "ExecuteFixedUpdate" + ); + csScriptsExecuteUpdate = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "ExecuteUpdate" + ); + csScriptsExecuteLateUpdate = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "ExecuteLateUpdate" + ); + csScriptsFrameCleanUp = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "FrameCleanUp" + ); + csScriptsAdd = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "AddScriptViaName" + ); + csScriptsRemoveAll = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "RemoveAllScripts" + ); + csScriptsRemoveAllImmediately = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "RemoveAllScriptsImmediately" + ); + csScriptsSerialise = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "SerialiseScripts" + ); + csScriptsSerialiseJson = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "SerialiseScriptsJson" + ); + csScriptDeserialise = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "DeserialiseScript" + ); + csGOLibNotifyNewEntity = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".GameObjectLibrary", + "NotifyNewGameObject" + ); + csGOLibNotifyDestroyEntity = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".GameObjectLibrary", + "NotifyDestroyGameObject" + ); + csEditorRenderScripts = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".Editor", + "RenderScriptsInInspector" + ); + } + + void SHScriptEngine::dumpBuildLog(const std::string_view& buildLogPath) + { + std::ifstream buildLog(buildLogPath); + + // Fail to open + if (!buildLog.is_open()) + return; + + // Process line by line + std::string line; + while (std::getline(buildLog, line)) + { + if (line.find("error") != line.npos) + { + SHLOG_ERROR(line); + } + else + { + SHLOG_WARNING(line); + } + } + } + void SHScriptEngine::deleteFile(const std::string_view& filePath) + { + try + { + std::filesystem::remove(std::filesystem::canonical(filePath)); + } + catch (...) {} // Ignore deletion failures + } + + void SHScriptEngine::deleteFolder(const std::string_view& filePath) + { + try + { + std::filesystem::remove_all(std::filesystem::canonical(filePath)); + } + catch (...) {} // Ignore deletion failures + } + + bool SHScriptEngine::fileExists(const std::string_view& filePath) + { + std::error_code error; + if (std::filesystem::exists(filePath, error)) + { + return true; + } + return false; + } + + DWORD SHScriptEngine::execProcess(const std::wstring& path, const std::wstring& args) + { + STARTUPINFOW startInfo; + PROCESS_INFORMATION procInfo; + ZeroMemory(&startInfo, sizeof(startInfo)); + ZeroMemory(&procInfo, sizeof(procInfo)); + startInfo.cb = sizeof(startInfo); + + std::wstring argsWstr = args; + + // Start Process + const auto SUCCESS = CreateProcess + ( + path.data(), argsWstr.data(), + nullptr, nullptr, false, NULL, nullptr, nullptr, + &startInfo, &procInfo + ); + + // Error Check + if (!SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[ScriptEngine] Failed to launch process. Error code: " << std::hex << err + << " (" << SHStringUtils::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + // Wait for execution to end + DWORD status; + while (true) + { + const auto SUCCESS = GetExitCodeProcess(procInfo.hProcess, &status); + if (!SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[ScriptEngine] Failed to query process. Error code: " << std::hex << err + << " (" << SHStringUtils::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + // Break only if process ends + if (status != STILL_ACTIVE) + { + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + return status; + } + } + } + +} diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.h b/SHADE_Engine/src/Scripting/SHScriptEngine.h new file mode 100644 index 00000000..85e3ac3f --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.h @@ -0,0 +1,258 @@ +/************************************************************************************//*! +\file ScriptEngine.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2021 +\brief Contains the interface for ScriptEngine class. + +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 + +// STL Includes +#include + +// Project Headers +#include "SHDotNetRuntime.h" +#include "Engine/ECS_Base/SHECSMacros.h" +#include "Engine/ECS_Base/Entity/SHEntity.h" + +namespace SHADE +{ + /// + /// Manages initialisation of the DotNetRuntime and interfacing with CLR code written + /// and executed on .NET. + /// + class SHScriptEngine + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructors & Destructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Default Constructor + /// + SHScriptEngine(); + + /*-----------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Initialises the DotNetRuntime and retrieves function pointers to all + /// functions on the CLR used to interface with the engine. + /// + void Init(); + /// + /// Loads the managed script assembly. Ensure this is only called after + /// UnloadScriptAssembly() has been called. + /// + void UnloadScriptAssembly(); + /// + /// Unloads the managed script assembly. + /// Take note that this will clear all existing scripts, ensure that the scene + /// is saved before doing so. + /// + void LoadScriptAssembly(); + /// + /// Reloads the managed script assembly. + /// Take note that this will clear all existing scripts, ensure that the scene + /// is saved before doing so. + /// + void ReloadScriptAssembly(); + /// + /// Executes the FixedUpdate()s of the PlushieScripts that are attached to + /// Entities. + /// + void ExecuteFixedUpdates(); + /// + /// Executes the OnTrigger() family of functions of the PlushieScripts that are + /// attached to Entities. + /// + void ExecuteOnTrigger(); + /// + /// Shuts down the DotNetRuntime. + /// + void Exit(); + + /*-----------------------------------------------------------------------------*/ + /* Script Manipulation Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Script to a specified Entity. Note that while you can call this + /// multiple times on a specified Entity, it will work for all intents and + /// purposes but GetScript<T>() (C# only) currently only + /// gives you the first PlushieScript added of the specified type. + /// + /// The entity to add a script to. + /// Type name of the script to add. + /// + /// True if successfully added. False otherwise with the error logged to the + /// console. + /// + bool AddScript(const SHEntity& entity, const std::string_view& scriptName) const; + /// + /// Removes all Scripts attached to the specified Entity. Does not do anything + /// if the specified Entity is invalid or does not have any PlushieScripts + /// attached. + /// + /// The entity to remove the scripts from. + void RemoveAllScripts(const SHEntity& entity) const; + /// + /// Removes all Scripts attached to the specified Entity. Unlike + /// RemoveAllScripts(), this removes all the scripts immediately. + /// Does not do anything if the specified Entity is invalid or does not have any + /// PlushieScripts attached. + /// + /// The entity to remove the scripts from. + /// + /// Whether or not to call OnDestroy on the scripts. This is ignored if not in + /// play mode. + /// + void RemoveAllScriptsImmediately(const SHEntity& entity, bool callOnDestroy) const; + + /*-----------------------------------------------------------------------------*/ + /* Script Serialisation Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Generates a JSON string that represents the set of Scripts attached to the + /// specified Entity. + /// + /// The Entity to Serialise. + /// + /// String that represents the set of scripts attached to the specified Entity. + /// + std::string SerialiseScripts(const SHEntity& entity) const; + /// + /// Loads the specified JSON string and creates a Script for the specified Entity + /// based on the specified JSON string. + /// + /// The Entity to deserialise a Script on to. + /// + /// The JSON string that represents the Script to load into the Entity. + /// + void DeserialiseScript(const SHEntity& entity, const std::string& yaml) const; + + /*-----------------------------------------------------------------------------*/ + /* Script Editor Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Renders the set of attached PlushieScripts for the specified Entity into the + /// inspector. + ///
+ /// This function is meant for consumption from native code in the inspector + /// rendering code. + ///
+ /// The Entity to render the PlushieScripts of. + void RenderScriptsInInspector(const SHEntity& entity) const; + + /*-----------------------------------------------------------------------------*/ + /* Static Utility Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Utilises execution of a external batch file for invoking the dotnet build + /// tool to compile C# scripts in the Assets folder into the PlushieGameManaged + /// C# assembly DLL. + /// + /// + /// Whether or not a debug build will be built. Only debug built C# assemblies + /// can be debugged. + /// + /// Whether or not the build succeeded. + static bool BuildScriptAssembly(bool debug = false); + /// + /// Generates a .csproj file for editing and compiling the C# scripts. + /// + /// File path to the generated file. + static void GenerateScriptsCsProjFile(const std::filesystem::path& path); + + private: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + using CsFuncPtr = void(*)(void); + using CsScriptManipFuncPtr = bool(*)(EntityID, const char*); + using CsScriptBasicFuncPtr = void(*)(EntityID); + using CsScriptOptionalFuncPtr = void(*)(EntityID, bool); + using CsScriptSerialiseFuncPtr = bool(*)(EntityID, char*, int); + using CsScriptDeserialiseFuncPtr = bool(*)(EntityID, const char*); + using CsScriptSerialiseJsonFuncPtr = bool(*)(EntityID, void*); + using CsScriptEditorFuncPtr = void(*)(EntityID); + + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + static constexpr std::string_view DEFAULT_CSHARP_LIB_NAME = "SHADEAPI"; + static constexpr std::string_view MANAGED_SCRIPT_LIB_NAME = "SHADEManaged"; + static const std::string DEFAULT_CSHARP_NAMESPACE; + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + SHDotNetRuntime dotNet {false}; + // Function Pointers to CLR Code + // - Engine Init + CsFuncPtr csEngineInit = nullptr; + CsFuncPtr csEngineLoadScripts = nullptr; + CsFuncPtr csEngineUnloadScripts = nullptr; + CsFuncPtr csEngineReloadScripts = nullptr; + CsFuncPtr csEngineExit = nullptr; + // - Scripts Store + CsFuncPtr csScriptsFrameSetUp = nullptr; + CsFuncPtr csScriptsExecuteOnTrigger = nullptr; + CsFuncPtr csScriptsExecuteFixedUpdate = nullptr; + CsFuncPtr csScriptsExecuteUpdate = nullptr; + CsFuncPtr csScriptsExecuteLateUpdate = nullptr; + CsFuncPtr csScriptsFrameCleanUp = nullptr; + CsScriptManipFuncPtr csScriptsAdd = nullptr; + CsScriptBasicFuncPtr csScriptsRemoveAll = nullptr; + CsScriptOptionalFuncPtr csScriptsRemoveAllImmediately = nullptr; + CsScriptSerialiseFuncPtr csScriptsSerialise = nullptr; + CsScriptDeserialiseFuncPtr csScriptDeserialise = nullptr; + CsScriptSerialiseJsonFuncPtr csScriptsSerialiseJson = nullptr; + CsScriptSerialiseJsonFuncPtr csScriptDeserialiseJson = nullptr; + // - GameObject Library + CsScriptBasicFuncPtr csGOLibNotifyNewEntity = nullptr; + CsScriptBasicFuncPtr csGOLibNotifyDestroyEntity = nullptr; + // - Editor + CsScriptEditorFuncPtr csEditorRenderScripts = nullptr; + // Delegates + /*ECS::EntityEvent::Delegate onEntityCreate; + ECS::EntityEvent::Delegate onEntityDestroy;*/ + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Loads all the function pointers to CLR code that we need to execute. + /// + void loadFunctions(); + /// + /// Reads the file via the specified path that represents a build log of error + /// and warning messages. + /// + /// + /// File path to the build log of script builds done by BuildScriptAssembly() to + /// dump and process. + /// + static void dumpBuildLog(const std::string_view& buildLogPath); + /// + /// Deletes the file as specified by the file path. + /// + /// File path to the file to delete. + static void deleteFile(const std::string_view& filePath); + /// + /// Deletes the folder and all files in it as specified by the file path. + /// + /// File path to the file to delete. + static void deleteFolder(const std::string_view& filePath); + /// + /// Checks if a specified file exists. + /// + /// File path to the file to check. + /// True if the file exists + static bool fileExists(const std::string_view& filePath); + static DWORD execProcess(const std::wstring& path, const std::wstring& args); + }; +} // namespace PlushieEngine diff --git a/SHADE_Engine/src/Tools/SHStringUtils.cpp b/SHADE_Engine/src/Tools/SHStringUtils.cpp new file mode 100644 index 00000000..a8dc4a0c --- /dev/null +++ b/SHADE_Engine/src/Tools/SHStringUtils.cpp @@ -0,0 +1,52 @@ +/************************************************************************************//*! +\file StringUtilities.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 29, 2021 +\brief Contains the definition of functions for working with strings. + +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 Header +#include +// Primary Header +#include "SHStringUtils.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Utility Functions */ + /*---------------------------------------------------------------------------------*/ + std::vector SHStringUtils::Split(const std::string& str, const char& delim) + { + return Split(str, delim); + } + std::vector SHStringUtils::Split(const std::wstring& str, const wchar_t& delim) + { + return Split(str, delim); + } + std::string SHStringUtils::WstrToStr(const std::wstring& wstr) + { + static std::vector buffer; + const int STR_SIZE = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr) + 1 /* Null Terminator */; + buffer.resize(STR_SIZE); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), buffer.data(), MAX_PATH, nullptr, nullptr); + return std::string(buffer.data()); + } + std::wstring SHStringUtils::StrToWstr(const std::string& str) + { + static std::vector buffer; + const int WSTR_SIZE = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), nullptr, 0) + 1 /* Null Terminator */; + buffer.resize(WSTR_SIZE); + MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), buffer.data(), WSTR_SIZE); + return std::wstring(buffer.data()); + } + + std::string SHStringUtils::GetWin32ErrorMessage(unsigned long errorCode) + { + return std::system_category().message(errorCode); + } + +} // namespace PlushieEngine \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHStringUtils.h b/SHADE_Engine/src/Tools/SHStringUtils.h new file mode 100644 index 00000000..abfe9146 --- /dev/null +++ b/SHADE_Engine/src/Tools/SHStringUtils.h @@ -0,0 +1,81 @@ +/************************************************************************************//*! +\file StringUtilities.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 29, 2021 +\brief Contains the declaration of functions for working with files and folders. + +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 +// Standard Libraries +#include // std::basic_string +#include // std::vector + +namespace SHADE +{ + /// + /// Contains useful functions for operating on strings. + /// + class SHStringUtils + { + public: + /*-----------------------------------------------------------------------------*/ + /* Utility Functions */ + /*-----------------------------------------------------------------------------*/ + + /// + /// Splits a string separated by a specified delimiter into a vector of strings. + /// + /// Internal type of each element in the string. + /// Read only reference to the string to split. + /// Read only reference to the delimiter. + /// Vector of strings that have been split. + template + static std::vector> Split(const std::basic_string& str, const T& delim); + /// + /// Splits a string separated by a specified delimiter into a vector of strings. + /// Overload of Split() to allow for string literals to be accepted. + /// + /// Read only reference to the string to split. + /// Read only reference to the delimiter. + /// Vector of strings that have been split. + static std::vector Split(const std::string& str, const char& delim); + /// + /// Splits a string separated by a specified delimiter into a vector of strings. + /// Overload of Split() to allow for wide string literals to be accepted. + /// + /// Read only reference to the string to split. + /// Read only reference to the delimiter. + /// Vector of strings that have been split. + static std::vector Split(const std::wstring& str, const wchar_t& delim); + /// + /// Converts a wstring to a string. + /// + /// wstring to convert. + /// The converted wstring in string form. + static std::string WstrToStr(const std::wstring& wstr); + /// + /// Converts a string to a wstring. + /// + /// string to convert. + /// The converted string in wstring form. + static std::wstring StrToWstr(const std::string& str); + /// + /// Retrieves the error message associated with a Win32 error code. + /// + /// Win32 error code to decode. + /// String that represents the Win32 error. + static std::string GetWin32ErrorMessage(unsigned long errorCode); + + private: + /*-------------------------------------------------------------------------------*/ + /* Constructors/Destructors */ + /*-------------------------------------------------------------------------------*/ + SHStringUtils() = delete; + }; +} // namespace PlushieEngine + +#include "SHStringUtils.hpp" diff --git a/SHADE_Engine/src/Tools/SHStringUtils.hpp b/SHADE_Engine/src/Tools/SHStringUtils.hpp new file mode 100644 index 00000000..5b4caecb --- /dev/null +++ b/SHADE_Engine/src/Tools/SHStringUtils.hpp @@ -0,0 +1,46 @@ +/************************************************************************************//*! +\file StringUtilities.hpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 29, 2021 +\brief Contains the implementation of template functions for working with files + and folders. + +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 +// Primary Header +#include "SHStringUtils.h" + +namespace SHADE +{ + /*-------------------------------------------------------------------------------*/ + /* Template Function Definitions */ + /*-------------------------------------------------------------------------------*/ + template + inline std::vector> SHStringUtils::Split(const std::basic_string& str, const T& delim) + { + std::vector> results; + std::basic_string remaining = str; + + // Go through looking for delimiters + while (true) + { + const size_t DELIM_POS = remaining.find_first_of(delim); + results.emplace_back(remaining.substr(0, DELIM_POS)); + + // Check if we hit the end of the string + if (DELIM_POS == remaining.npos) + { + break; + } + + // Otherwise, cut the remainder + remaining = remaining.substr(DELIM_POS + 1); + } + + return results; + } +} // namespace PlushieEngine \ No newline at end of file