diff --git a/Dependencies.bat b/Dependencies.bat index ba411815..460a1d87 100644 --- a/Dependencies.bat +++ b/Dependencies.bat @@ -20,10 +20,11 @@ echo "M - SDL" echo "N - dotnet" echo "O - tinyddsloader" echo "P - fmod" +echo "Q - vswhere" echo --------------------------------------------------- echo. -choice /C ABCDEFGHIJKLMNOP /T 10 /D A +choice /C ABCDEFGHIJKLMNOPQ /T 10 /D A set _e=%ERRORLEVEL% if %_e%==1 goto VMA @@ -42,6 +43,7 @@ if %_e%==13 goto SDL if %_e%==14 goto dotnet if %_e%==15 goto tinyddsloader if %_e%==16 goto fmod +if %_e%==17 goto vswhere :VMA echo -----------------------VMA---------------------------- @@ -155,6 +157,13 @@ if %_e%==15 (goto :done) else (goto :fmod) echo --------------------fmod------------------------- rmdir "Dependencies/fmod" /S /Q git clone https://github.com/SHADE-DP/FMOD.git "Dependencies/fmod" +if %_e%==16 (goto :done) else (goto :vswhere) + +:vswhere +echo -----------------------vswhere---------------------------- +rmdir "Dependencies/vswhere" /S /Q +mkdir "Dependencies/vswhere" +powershell -Command "& {wget https://github.com/microsoft/vswhere/releases/download/3.1.1/vswhere.exe -OutFile "Dependencies/vswhere/vswhere.exe"}" :done echo DONE! diff --git a/Dependencies.lua b/Dependencies.lua index fe75c3f4..2694fe35 100644 --- a/Dependencies.lua +++ b/Dependencies.lua @@ -16,4 +16,5 @@ IncludeDir["SDL"] = "%{wks.location}\\Dependencies\\SDL" IncludeDir["VULKAN"] = "$(VULKAN_SDK)" IncludeDir["dotnet"] = "%{wks.location}\\Dependencies\\dotnet" IncludeDir["tinyddsloader"] = "%{wks.location}\\Dependencies\\tinyddsloader" -IncludeDir["fmod"] = "%{wks.location}\\Dependencies\\fmod" \ No newline at end of file +IncludeDir["fmod"] = "%{wks.location}\\Dependencies\\fmod" +IncludeDir["vswhere"] = "%{wks.location}\\Dependencies\\vswhere" \ No newline at end of file diff --git a/SHADE_Engine/premake5.lua b/SHADE_Engine/premake5.lua index 7fb7291d..17ca5be8 100644 --- a/SHADE_Engine/premake5.lua +++ b/SHADE_Engine/premake5.lua @@ -124,7 +124,8 @@ project "SHADE_Engine" "xcopy /r /y /q \"%{IncludeDir.ModelCompiler}\\bin\\Debug\\ModelCompiler.exe\" \"$(OutDir)\"", "xcopy /r /y /q \"%{IncludeDir.FontCompiler}\\bin\\Debug\\FontCompiler.exe\" \"$(OutDir)\"", "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmodL.dll\" \"$(OutDir)\"", - "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmodstudioL.dll\" \"$(OutDir)\"" + "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmodstudioL.dll\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.vswhere}\\vswhere.exe\" \"$(OutDir)\"" } filter "configurations:Release" @@ -134,7 +135,8 @@ project "SHADE_Engine" "xcopy /r /y /q \"%{IncludeDir.ModelCompiler}\\bin\\Release\\ModelCompiler.exe\" \"$(OutDir)\"", "xcopy /r /y /q \"%{IncludeDir.FontCompiler}\\bin\\Release\\FontCompiler.exe\" \"$(OutDir)\"", "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmod.dll\" \"$(OutDir)\"", - "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmodstudio.dll\" \"$(OutDir)\"" + "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmodstudio.dll\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.vswhere}\\vswhere.exe\" \"$(OutDir)\"" } filter "configurations:Publish" diff --git a/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.cpp b/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.cpp index 66b3c962..bd39b38e 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.cpp @@ -14,6 +14,8 @@ #include "Editor/DragDrop/SHDragDrop.hpp" #include "Editor/EditorWindow/MaterialInspector/SHMaterialInspector.h" #include "Editor/EditorWindow/SHEditorWindowManager.h" +#include "Scripting/SHVSUtilities.h" +#include "Scripting/SHScriptEngine.h" namespace SHADE { @@ -249,6 +251,12 @@ namespace SHADE matInspector->OpenMaterial(asset->id); } break; + case AssetType::SCRIPT: + if(auto scriptEngine = SHSystemManager::GetSystem()) + { + scriptEngine->OpenFile(asset->path); + } + break; case AssetType::MAX_COUNT: break; default:; } diff --git a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp index a1335e19..22d78173 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp @@ -120,6 +120,11 @@ namespace SHADE auto* scriptEngine = static_cast(SHSystemManager::GetSystem()); scriptEngine->GenerateScriptsCsProjFile(); } + if (ImGui::Selectable("Open Visual Studio Project")) + { + auto* scriptEngine = static_cast(SHSystemManager::GetSystem()); + scriptEngine->OpenSolution(); + } ImGui::BeginDisabled(SHSystemManager::GetSystem()->editorState != SHEditor::State::STOP); if (ImGui::Selectable("Build Scripts - Debug")) { diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp index 3746d1d0..96f1539d 100644 --- a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp @@ -28,8 +28,9 @@ of DigiPen Institute of Technology is prohibited. #include "Physics/System/SHPhysicsSystem.h" #include "Physics/SHPhysicsEvents.h" #include "Scene/SHSceneEvents.h" - #include "Assets/SHAssetMacros.h" +#include "Tools/Utilities/SHExecUtilities.h" +#include "SHVSUtilities.h" namespace SHADE { @@ -189,12 +190,9 @@ namespace SHADE 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 \"" + generateBuildCommand(debug) + L" & exit\"" - ) == 0; - if (BUILD_SUCCESS) + const auto BUILD_SUCCESS = SHExecUtilties::ExecBlockingProcess(L"C:\\Program Files\\dotnet\\dotnet.exe", generateBuildCommand(debug), false, false); + SHLOG_WARNING("Build Output: {}", SHStringUtilities::WstrToStr(BUILD_SUCCESS.StdOutput)); + if (BUILD_SUCCESS.Code == 0) { // Copy to built dll to the working directory and replace if (!copyFile("./tmp/SHADE_Scripting.dll", "SHADE_Scripting.dll", std::filesystem::copy_options::overwrite_existing)) @@ -238,7 +236,7 @@ namespace SHADE LoadScriptAssembly(); } - return BUILD_SUCCESS; + return BUILD_SUCCESS.Code == 0; } void SHScriptEngine::GenerateScriptsCsProjFile(const std::filesystem::path& path) const @@ -306,6 +304,16 @@ namespace SHADE file.close(); } + void SHScriptEngine::OpenSolution() + { + SHVSUtilties::OpenProject(CSPROJ_PATH); + } + + void SHScriptEngine::OpenFile(const std::filesystem::path& path) + { + SHVSUtilties::OpenFile(CSPROJ_PATH, path); + } + /*-----------------------------------------------------------------------------------*/ /* Event Handler Functions */ /*-----------------------------------------------------------------------------------*/ @@ -610,64 +618,12 @@ namespace SHADE } } - 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 - << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; - throw std::runtime_error(oss.str()); - } - - // Wait for execution to end - DWORD status; - while (true) - { - const auto EXEC_SUCCESS = GetExitCodeProcess(procInfo.hProcess, &status); - if (!EXEC_SUCCESS) - { - auto err = GetLastError(); - std::ostringstream oss; - oss << "[ScriptEngine] Failed to query process. Error code: " << std::hex << err - << " (" << SHStringUtilities::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; - } - } - } - std::wstring SHScriptEngine::generateBuildCommand(bool debug) { std::wostringstream oss; - oss << "dotnet build \"" << SHStringUtilities::StrToWstr(CSPROJ_PATH) << "\" -c "; + oss << " build \"" << SHStringUtilities::StrToWstr(std::filesystem::absolute(CSPROJ_PATH).string()) << "\" -c "; oss << debug ? "Debug" : "Release"; - oss << " -o \"./tmp/\" -fl -flp:LogFile=build.log;Verbosity=quiet -r \"win-x64\""; + oss << " -o \"./tmp/\" -r \"win-x64\""; return oss.str(); } } diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.h b/SHADE_Engine/src/Scripting/SHScriptEngine.h index 1a38a678..9710f5c5 100644 --- a/SHADE_Engine/src/Scripting/SHScriptEngine.h +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.h @@ -1,5 +1,5 @@ /************************************************************************************//*! -\file ScriptEngine.h +\file SHScriptEngine.h \author Tng Kah Wei, kahwei.tng, 390009620 \par email: kahwei.tng\@digipen.edu \date Sep 17, 2021 @@ -217,6 +217,15 @@ namespace SHADE /// /// File path to the generated file. void GenerateScriptsCsProjFile(const std::filesystem::path& path = CSPROJ_PATH) const; + /// + /// Opens the script solution in Visual Studio. + /// + void OpenSolution(); + /// + /// Opens the file in Visual Studio. + /// + /// + void OpenFile(const std::filesystem::path& path); private: /*-----------------------------------------------------------------------------*/ @@ -320,7 +329,6 @@ namespace SHADE /// True if the file exists static bool fileExists(const std::filesystem::path& filePath); static bool copyFile(const std::filesystem::path& from, const std::filesystem::path& to, const std::filesystem::copy_options options) noexcept; - static DWORD execProcess(const std::wstring& path, const std::wstring& args); static std::wstring generateBuildCommand(bool debug); }; } diff --git a/SHADE_Engine/src/Scripting/SHVSUtilities.cpp b/SHADE_Engine/src/Scripting/SHVSUtilities.cpp new file mode 100644 index 00000000..17435d6f --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHVSUtilities.cpp @@ -0,0 +1,127 @@ +/************************************************************************************//*! +\file SHVSUtilities.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Dec 21, 2022 +\brief Contains the implementation for SHVSUtilities static class. + +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. +*//*************************************************************************************/ +// Precompiled Headers +#include +// Primary Header +#include "SHVSUtilities.h" +// Project Headers +#include "Tools/Utilities/SHExecUtilities.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*-----------------------------------------------------------------------------------*/ + std::filesystem::path SHVSUtilties::devEnvPath; + HANDLE SHVSUtilties::devEnvInst = nullptr; + + /*-----------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHVSUtilties::OpenProject(const std::filesystem::path& projPath) + try + { + // Check if there is an instance + bool devEnvActive = false; + if (devEnvInst) + { + + DWORD status; + const auto GET_CODE_STATUS = GetExitCodeProcess(devEnvInst, &status); + devEnvActive = GET_CODE_STATUS && status == STILL_ACTIVE; + } + + // Reuse the existing instance if there is one + if (devEnvActive) + { + // No need to reopen one + return; + } + else + { + if (devEnvPath.empty()) + { + devEnvPath = SHVSUtilties::getDevEnvPath(); + } + + auto absProjPath = std::filesystem::canonical(projPath); + auto pi = SHExecUtilties::ExecProcess(devEnvPath.generic_wstring(), L" " + absProjPath.generic_wstring()); + // Cache the process handle + devEnvInst = pi.hProcess; + } + } + catch (std::exception& e) + { + SHLOG_ERROR("{}", e.what()); + SHLOG_ERROR("[SHVSUtilities] Failed to launch Visual Studio."); + } + + void SHVSUtilties::OpenFile(const std::filesystem::path& projPath, const std::filesystem::path& path) + try + { + // Check if there is an instance + bool devEnvActive = false; + if (devEnvInst) + { + + DWORD status; + const auto GET_CODE_STATUS = GetExitCodeProcess(devEnvInst, &status); + devEnvActive = GET_CODE_STATUS && status == STILL_ACTIVE; + } + + auto absPath = std::filesystem::canonical(path); + + // Reuse the existing instance if there is one + if (devEnvActive) + { + // Edit the file only + SHExecUtilties::ExecProcess(devEnvPath.generic_wstring(), L" /Edit " + absPath.generic_wstring()); + } + else + { + if (devEnvPath.empty()) + { + devEnvPath = SHVSUtilties::getDevEnvPath(); + } + + auto absProjPath = std::filesystem::canonical(projPath); + auto pi = SHExecUtilties::ExecProcess(devEnvPath.generic_wstring(), absProjPath.generic_wstring() + L" " + absPath.generic_wstring()); + // Cache the process handle + devEnvInst = pi.hProcess; + } + } + catch (std::exception& e) + { + SHLOG_ERROR("{}", e.what()); + SHLOG_ERROR("[SHVSUtilities] Failed to launch Visual Studio."); + } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------------*/ + std::filesystem::path SHVSUtilties::getDevEnvPath() + { +#ifdef _PUBLISH + return {}; // Don't do anything if it's a published build +#else + static constexpr int EXCESS_CHARS_COUNT = 2; + + const auto RESULT = SHExecUtilties::ExecBlockingPowerShellCommand(L"./vswhere -version \"[15.0,19.0]\" -requires Microsoft.NetCore.Component.DevelopmentTools -find Common7\\\\IDE\\\\devenv.exe | Select-Object -first 1", true, true); + if (RESULT.StdOutput.size() < EXCESS_CHARS_COUNT) + { + SHLOG_ERROR("[SHVSUtilities] Unable to get path to Visual Studio installation. SHVSUtilities will not work."); + return {}; + } + return RESULT.StdOutput.substr(0, RESULT.StdOutput.size() - EXCESS_CHARS_COUNT); +#endif + } +} diff --git a/SHADE_Engine/src/Scripting/SHVSUtilities.h b/SHADE_Engine/src/Scripting/SHVSUtilities.h new file mode 100644 index 00000000..8e702dfd --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHVSUtilities.h @@ -0,0 +1,61 @@ +/************************************************************************************//*! +\file SHVSUtilties.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Dec 21, 2022 +\brief Contains the interface for SHVSUtilties class. + +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. +*//*************************************************************************************/ + +// STL Includes +#include +// External Dependencies +#include + +namespace SHADE +{ + /// + /// Static class containing functions for working with Visual Studio installation and + /// running instances. + /// + class SH_API SHVSUtilties final + { + public: + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Opens the project at the specified path with a new or existing instance of Visual + /// Studio if it exists. + /// + /// Path to the project file to open. + static void OpenProject(const std::filesystem::path& projPath); + /// + /// Opens the file at the specified path with a new or existing instance of Visual + /// Studio if it exists. + /// + /// Path to the project file to open. + /// Path to the file to open. + static void OpenFile(const std::filesystem::path& projPath, const std::filesystem::path& path); + + private: + /*---------------------------------------------------------------------------------*/ + /* Constructors/Destructors */ + /*---------------------------------------------------------------------------------*/ + SHVSUtilties() = delete; + + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + static std::filesystem::path devEnvPath; + static HANDLE devEnvInst; + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + static std::filesystem::path getDevEnvPath(); + }; +} diff --git a/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp new file mode 100644 index 00000000..4a5a963c --- /dev/null +++ b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp @@ -0,0 +1,215 @@ +/************************************************************************************//*! +\file SHExecUtilities.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Dec 21, 2022 +\brief Contains the implementation for SHExecUtilities static class. + +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. +*//*************************************************************************************/ +// Precompiled Headers +#include +// Primary Header +#include "SHExecUtilities.h" +// Project Includes +#include "SHStringUtilities.h" +#include "SHFileUtilties.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Process Execution Functions */ + /*-----------------------------------------------------------------------------------*/ + PROCESS_INFORMATION SHExecUtilties::ExecProcess(const std::wstring& path, const std::wstring& args) + { + return execProcess(path, args, nullptr, nullptr); + } + + ExecResult SHExecUtilties::ExecBlockingProcess(const std::wstring& path, const std::wstring& args, bool output, bool errorOutput) + { + // Create pipes for stdout and stderr if specified + HANDLE stdoutReadPipe = nullptr; + HANDLE stdoutWritePipe = nullptr; + HANDLE stderrReadPipe = nullptr; + HANDLE stderrWritePipe = nullptr; + if (output) + { + std::tie(stdoutReadPipe, stdoutWritePipe) = createIoPipes(); + } + if (errorOutput) + { + std::tie(stderrReadPipe, stderrWritePipe) = createIoPipes(); + } + + PROCESS_INFORMATION procInfo = execProcess(path, args, stdoutWritePipe, stderrWritePipe); + + // Wait for execution to end + ExecResult result; + while (true) + { + const auto EXEC_SUCCESS = GetExitCodeProcess(procInfo.hProcess, &result.Code); + if (!EXEC_SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[SHExecUtilties] Failed to query process. Error code: " << std::hex << err + << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + // Break only if process ends + if (result.Code != STILL_ACTIVE) + { + // Get stdout/stderror output + if (stdoutReadPipe) + { + readPipeData(stdoutReadPipe, stdoutWritePipe, result.StdOutput); + } + if (stderrReadPipe) + { + readPipeData(stderrReadPipe, stderrWritePipe, result.StdErrOutput); + } + + // Close the process and threads for that process + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + + // Return results + return result; + } + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Command Execution Functions */ + /*-----------------------------------------------------------------------------------*/ + PROCESS_INFORMATION SHExecUtilties::ExecCommand(const std::wstring& command) + { + return ExecProcess + ( + L"C:\\Windows\\system32\\cmd.exe", + L"/K \"" + command + L" & exit\"" + ); + } + + ExecResult SHExecUtilties::ExecBlockingCommand(const std::wstring& command, bool output, bool errorOutput) + { + return ExecBlockingProcess + ( + L"C:\\Windows\\system32\\cmd.exe", + L"/K \"" + command + L" & exit\"", + output, + errorOutput + ); + } + + PROCESS_INFORMATION SHExecUtilties::ExecPowerShellCommand(const std::wstring& command) + { + return ExecProcess + ( + L"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + L"-Command \"& {" + command + L"} \"" + ); + } + + ExecResult SHExecUtilties::ExecBlockingPowerShellCommand(const std::wstring& command, bool output, bool errorOutput) + { + return ExecBlockingProcess + ( + L"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + L"-Command \"& { cd \"" + SHFileUtilities::GetExecDir().generic_wstring() + L"\";" + command + L"} \"", + output, + errorOutput + ); + } + + PROCESS_INFORMATION SHExecUtilties::execProcess(const std::wstring& path, const std::wstring& args, HANDLE outputWritePipe, HANDLE errorOutputWritePipe) + { + STARTUPINFOW startInfo; + PROCESS_INFORMATION procInfo; + ZeroMemory(&startInfo, sizeof(startInfo)); + ZeroMemory(&procInfo, sizeof(procInfo)); + startInfo.cb = sizeof(startInfo); + if (outputWritePipe) + { + startInfo.hStdOutput = outputWritePipe; + startInfo.dwFlags |= STARTF_USESTDHANDLES; + startInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + } + if (errorOutputWritePipe) + { + startInfo.hStdError = errorOutputWritePipe; + startInfo.dwFlags |= STARTF_USESTDHANDLES; + startInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + } + + std::wstring argsWstr = args; + + // Start Process + const auto SUCCESS = CreateProcess + ( + path.data(), argsWstr.data(), + nullptr, nullptr, true, NULL, nullptr, nullptr, + &startInfo, &procInfo + ); + + // Error Check + if (!SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[SHExecUtilties] Failed to launch process. Error code: " << std::hex << err + << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + return procInfo; + } + + std::pair SHExecUtilties::createIoPipes() + { + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = true; + saAttr.lpSecurityDescriptor = nullptr; + + // First: Read | Second: Write + std::pair output = { nullptr, nullptr }; + if (CreatePipe(&output.first, &output.second, &saAttr, 0)) + { + if (!SetHandleInformation(output.first, HANDLE_FLAG_INHERIT, 0)) + { + SHLOG_ERROR("[SHExecUtilities] Failed to initialize pipe for process IO."); + CloseHandle(output.first); + CloseHandle(output.second); + output = { nullptr, nullptr }; + } + } + else + { + SHLOG_ERROR("[SHExecUtilities] Failed to create pipe for process IO."); + } + + return output; + } + + void SHExecUtilties::readPipeData(HANDLE readPipe, HANDLE writePipe, std::wstring& output) + { + CloseHandle(writePipe); + + LARGE_INTEGER stdoutSize = {}; + while (true) + { + std::array buffer{}; + DWORD bytesRead = 0; + const auto RESULT = ReadFile(readPipe, buffer.data(), buffer.size(), &bytesRead, nullptr); + if (!RESULT || bytesRead <= 0) + break; + output.insert(output.end(), buffer.data(), buffer.data() + bytesRead); + } + + CloseHandle(readPipe); + } +} diff --git a/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h new file mode 100644 index 00000000..718fc45b --- /dev/null +++ b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h @@ -0,0 +1,142 @@ +/************************************************************************************//*! +\file SHExecUtilties.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Dec 21, 2022 +\brief Contains the interface for SHExecUtilities class. + +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. +*//*************************************************************************************/ + +// STL Includes +#include +// External Dependencies +#include + +namespace SHADE +{ + /// + /// Stores the result of an execution of a process. + /// + struct SH_API ExecResult final + { + /// + /// Exit code of the process. + /// + DWORD Code; + /// + /// Stored text output from the process. + /// + std::wstring StdOutput; + /// + /// Stored error text output from the process. + /// + std::wstring StdErrOutput; + }; + + /// + /// Static class containing functions for executing external processes or commands. + /// + class SH_API SHExecUtilties final + { + public: + /*---------------------------------------------------------------------------------*/ + /* Process Execution Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Executes a process at the specified path with the specified arguments. This call + /// does not wait for the process to finish executing. + /// + /// Path to the processs to start. + /// Arguments to pass to the process. + /// Information about the started process. + /// + /// Thrown if failed to start the process. + /// + static PROCESS_INFORMATION ExecProcess(const std::wstring& path, const std::wstring& args); + /// + /// Executes a process at the specified path with the specified arguments and waits + /// for that process to finish executing. + /// + /// Path to the processs to start. + /// Arguments to pass to the process. + /// If true, stdout will be routed to return. + /// If true, outstderr will be routed to return. + /// Information about the process's execution. + /// + /// Thrown if failed to start the process. + /// + static ExecResult ExecBlockingProcess(const std::wstring& path, const std::wstring& args, bool output = false, bool errorOutput = false); + + /*---------------------------------------------------------------------------------*/ + /* Command Execution Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Executes a specified command in cmd. + /// This call does not wait for the command to finish executing. + /// + /// Command to execute. + /// + /// Information about the started cmd process that executes the command. + /// + /// + /// Thrown if failed to start the process. + /// + static PROCESS_INFORMATION ExecCommand(const std::wstring& command); + /// + /// Executes a specified command in cmd and waits for that process to finish + /// executing. + /// + /// Command to execute. + /// If true, stdout will be routed to return. + /// If true, outstderr will be routed to return. + /// Information about the process's execution. + /// + /// Thrown if failed to start the process. + /// + static ExecResult ExecBlockingCommand(const std::wstring& command, bool output = false, bool errorOutput = false); + + /*---------------------------------------------------------------------------------*/ + /* PowerShell Execution Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Executes a specified command in PowerShell. + /// This call does not wait for the command to finish executing. + /// + /// Command to execute. + /// + /// Information about the started cmd process that executes the command. + /// + /// + /// Thrown if failed to start the process. + /// + static PROCESS_INFORMATION ExecPowerShellCommand(const std::wstring& command); + /// + /// Executes a specified command in PowerShell and waits for that process to finish + /// executing. + /// + /// Command to execute. + /// If true, stdout will be routed to return. + /// If true, outstderr will be routed to return. + /// Information about the process's execution. + /// + /// Thrown if failed to start the process. + /// + static ExecResult ExecBlockingPowerShellCommand(const std::wstring& command, bool output, bool errorOutput); + + private: + /*---------------------------------------------------------------------------------*/ + /* Constructors/Destructors */ + /*---------------------------------------------------------------------------------*/ + SHExecUtilties() = delete; + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + static PROCESS_INFORMATION execProcess(const std::wstring& path, const std::wstring& args, HANDLE outputWritePipe, HANDLE errorOutputWritePipe); + static std::pair createIoPipes(); + static void readPipeData(HANDLE readPipe, HANDLE writePipe, std::wstring& output); + }; +} diff --git a/SHADE_Engine/src/Tools/Utilities/SHFileUtilties.cpp b/SHADE_Engine/src/Tools/Utilities/SHFileUtilties.cpp index 0e75b16a..63e7d26e 100644 --- a/SHADE_Engine/src/Tools/Utilities/SHFileUtilties.cpp +++ b/SHADE_Engine/src/Tools/Utilities/SHFileUtilties.cpp @@ -22,9 +22,21 @@ namespace SHADE { void SHFileUtilities::SetWorkDirToExecDir() { - TCHAR currentExecFilePath[MAX_PATH] = { '\0' }; - GetModuleFileName(nullptr, currentExecFilePath, MAX_PATH); - PathRemoveFileSpec(currentExecFilePath); - std::filesystem::current_path(currentExecFilePath); + std::filesystem::current_path(GetExecDir()); + } + + std::filesystem::path SHFileUtilities::GetExecDir() + { + TCHAR currentExecFilePath[MAX_PATH] = { '\0' }; + GetModuleFileName(nullptr, currentExecFilePath, MAX_PATH); + PathRemoveFileSpec(currentExecFilePath); + return std::filesystem::path(currentExecFilePath); + } + + std::filesystem::path SHFileUtilities::GetExecPath() + { + TCHAR currentExecFilePath[MAX_PATH] = { '\0' }; + GetModuleFileName(nullptr, currentExecFilePath, MAX_PATH); + return std::filesystem::path(currentExecFilePath); } } diff --git a/SHADE_Engine/src/Tools/Utilities/SHFileUtilties.h b/SHADE_Engine/src/Tools/Utilities/SHFileUtilties.h index b9ba164b..fdbee34e 100644 --- a/SHADE_Engine/src/Tools/Utilities/SHFileUtilties.h +++ b/SHADE_Engine/src/Tools/Utilities/SHFileUtilties.h @@ -15,24 +15,29 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { - /*!************************************************************************************ - - \class SHFileUtilities - - \brief - Static class that contains functions for working with files and directories. - - **************************************************************************************/ + /// + /// Static class that contains functions for working with files and directories. + /// class SH_API SHFileUtilities { public: - /*!********************************************************************************** - - \brief - Sets the application's current working directory to the application executable's - directory. - - ************************************************************************************/ + /*---------------------------------------------------------------------------------*/ + /* Executable Directory Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Sets the application's current working directory to the application executable's + /// directory. + /// static void SetWorkDirToExecDir(); + /// + /// Retrieves the file path to the executable's directory. + /// + /// File path to the executable's directory. + static std::filesystem::path GetExecDir(); + /// + /// Retrieves the file path to the executable. + /// + /// File path to the executable. + static std::filesystem::path GetExecPath(); }; } diff --git a/SHADE_Engine/src/Tools/Utilities/SHStringUtilities.cpp b/SHADE_Engine/src/Tools/Utilities/SHStringUtilities.cpp index b1e4aa92..b9698071 100644 --- a/SHADE_Engine/src/Tools/Utilities/SHStringUtilities.cpp +++ b/SHADE_Engine/src/Tools/Utilities/SHStringUtilities.cpp @@ -26,27 +26,29 @@ namespace SHADE std::vector SHStringUtilities::Split(const std::wstring& str, const wchar_t& delim) { return Split(str, delim); - } - std::string SHStringUtilities::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 SHStringUtilities::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 SHStringUtilities::WstrToStr(const std::wstring& wstr) + { + static std::vector buffer; + buffer.clear(); + 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, '\0'); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), buffer.data(), MAX_PATH, nullptr, nullptr); + return std::string(buffer.data()); + } + std::wstring SHStringUtilities::StrToWstr(const std::string& str) + { + static std::vector buffer; + buffer.clear(); + const int WSTR_SIZE = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), nullptr, 0) + 1 /* Null Terminator */; + buffer.resize(WSTR_SIZE, '\0'); + MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), buffer.data(), WSTR_SIZE); + return std::wstring(buffer.data()); + } - std::string SHStringUtilities::GetWin32ErrorMessage(unsigned long errorCode) - { - return std::system_category().message(errorCode); - } + std::string SHStringUtilities::GetWin32ErrorMessage(unsigned long errorCode) + { + return std::system_category().message(errorCode); + } } \ No newline at end of file diff --git a/SHADE_Managed/src/Engine/Application.cxx b/SHADE_Managed/src/Engine/Application.cxx index c19bafa6..06ad632f 100644 --- a/SHADE_Managed/src/Engine/Application.cxx +++ b/SHADE_Managed/src/Engine/Application.cxx @@ -44,6 +44,10 @@ namespace SHADE return false; } + bool Application::IsEditor::get() + { + return SHSystemManager::GetSystem() != nullptr; + } int Application::WindowWidth::get() { return SHGraphicsSystemInterface::GetWindowWidth(); @@ -66,6 +70,9 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ void Application::Quit() { - SHGraphicsSystemInterface::CloseWindow(); + if (!IsEditor) + { + SHGraphicsSystemInterface::CloseWindow(); + } } } diff --git a/SHADE_Managed/src/Engine/Application.hxx b/SHADE_Managed/src/Engine/Application.hxx index 8629cf75..4467ec3b 100644 --- a/SHADE_Managed/src/Engine/Application.hxx +++ b/SHADE_Managed/src/Engine/Application.hxx @@ -43,6 +43,13 @@ namespace SHADE bool get(); } /// + /// True if the engine is running in the editor. + /// + static property bool IsEditor + { + bool get(); + } + /// /// Retrieves the designated width of the current window. /// static property int WindowWidth @@ -71,6 +78,7 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ /// /// Marks the application to stop at the end of the current frame. + /// If running in the editor, this function does nothing. /// static void Quit(); };