diff --git a/tools/tesseratos/CMakeLists.txt b/tools/tesseratos/CMakeLists.txt index 96dcc28c3..cf2fc5677 100644 --- a/tools/tesseratos/CMakeLists.txt +++ b/tools/tesseratos/CMakeLists.txt @@ -6,7 +6,7 @@ set(TESSERATOS_SOURCE "src/tesseratos/debugger/plugin.cpp" "src/tesseratos/debugger/debugger.cpp" "src/tesseratos/project/plugin.cpp" - "src/tesseratos/project/project.cpp" + "src/tesseratos/project/manager.cpp" "src/tesseratos/asset_explorer/plugin.cpp" "src/tesseratos/asset_explorer/popup.cpp" "src/tesseratos/scene_editor/plugin.cpp" diff --git a/tools/tesseratos/src/tesseratos/main.cpp b/tools/tesseratos/src/tesseratos/main.cpp index 47b16da7c..95a2c9fbd 100644 --- a/tools/tesseratos/src/tesseratos/main.cpp +++ b/tools/tesseratos/src/tesseratos/main.cpp @@ -34,6 +34,7 @@ int main(int argc, char** argv) cubos.plugin(debuggerPlugin); cubos.plugin(assetExplorerPlugin); + cubos.plugin(projectPlugin); cubos.plugin(sceneEditorPlugin); diff --git a/tools/tesseratos/src/tesseratos/project/manager.cpp b/tools/tesseratos/src/tesseratos/project/manager.cpp new file mode 100644 index 000000000..f1c28590d --- /dev/null +++ b/tools/tesseratos/src/tesseratos/project/manager.cpp @@ -0,0 +1,145 @@ +#include "manager.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include + +using cubos::core::data::FileSystem; +using cubos::core::data::StandardArchive; +using cubos::core::net::Address; + +CUBOS_REFLECT_IMPL(tesseratos::ProjectManager::State) +{ + using namespace cubos::core::reflection; + return Type::create("tesseratos::ProjectManager::State") + .with(ConstructibleTrait::typed().withMoveConstructor().build()); +} + +tesseratos::ProjectManager::ProjectManager(State& state, cubos::engine::Assets& assets, Debugger& debugger) + : mState(state) + , mAssets(assets) + , mDebugger(debugger) +{ +} + +bool tesseratos::ProjectManager::open(std::string projectOSPath, std::string binaryOSPath) +{ + if (projectOSPath.empty()) + { + CUBOS_ERROR("Project path is empty"); + return false; + } + + this->close(); + + // Prepare the project directory for mounting. + auto archive = std::make_unique(projectOSPath, /*isDirectory=*/true, /*readOnly=*/false); + if (!archive->initialized()) + { + CUBOS_ERROR("Could not open project at {}", projectOSPath); + return false; + } + + if (!FileSystem::mount("/project", std::move(archive))) + { + CUBOS_ERROR("Could not mount project archive to /project"); + return false; + } + + // Load asset metadata from the project assets directory. + mAssets.loadMeta("/project/assets"); + + // We managed to open the project's directory, store the paths. + mState.mProjectOSPath = std::move(projectOSPath); + mState.mBinaryOSPath = std::move(binaryOSPath); + + // Try to launch the project. + // If we can't, we can still keep the project open, but we won't be able to use its types. + this->launch(); + + return true; +} + +bool tesseratos::ProjectManager::open() const +{ + return !mState.mProjectOSPath.empty(); +} + +void tesseratos::ProjectManager::close() +{ + if (mState.mProjectOSPath.empty()) + { + return; + } + + this->terminate(); + + // Unload asset metadata from the project. + mAssets.unloadMeta("/project/assets"); + + // Unmount its directory from the virtual file system. + if (!FileSystem::unmount("/project")) + { + CUBOS_ERROR("Failed to unmount previously open project directory"); + } + + mState.mProjectOSPath.clear(); +} + +bool tesseratos::ProjectManager::launch() +{ + // Stop the binary if it is already running. + this->terminate(); + + if (!mState.mProcess.start(mState.mBinaryOSPath, {"--debug", std::to_string(mState.mPort)})) + { + CUBOS_ERROR("Could not start project's process at {}", mState.mBinaryOSPath); + return false; + } + + // Try to connect to the child process's debugger. + for (int i = 1, sleep = 100; i <= 3; ++i, sleep *= 2) + { + if (i > 0) + { + CUBOS_WARN("CCould not connect to project's process debugger in try {}, retrying in {} ms", i - 1, sleep); + std::this_thread::sleep_for(std::chrono::milliseconds(sleep)); + } + + if (mDebugger.connect(Address::LocalHost, mState.mPort)) + { + CUBOS_INFO("Successfully connected to project's process debugger in try {}", i); + break; + } + } + + // Check if we successfully connected to the child process's debugger. + // If we didn't, kill the child process. + if (!mDebugger.isConnected()) + { + CUBOS_ERROR("Could not connect to the project's process debugger, terminating the process"); + mState.mProcess.kill(); + return false; + } + + return true; +} + +void tesseratos::ProjectManager::terminate() +{ + if (mDebugger.isConnected()) + { + mDebugger.close(); // Issue a close command to the process' debugger. + } + else + { + mState.mProcess.kill(); + } + + mState.mProcess.wait(); // Wait for the process to finish. +} diff --git a/tools/tesseratos/src/tesseratos/project/manager.hpp b/tools/tesseratos/src/tesseratos/project/manager.hpp new file mode 100644 index 000000000..580752503 --- /dev/null +++ b/tools/tesseratos/src/tesseratos/project/manager.hpp @@ -0,0 +1,115 @@ +/// @file +/// @brief System argument @ref tesseratos::ProjectManager. +/// @ingroup tesseratos-debugger-plugin + +#pragma once + +#include + +#include +#include + +#include + +#include "../debugger/debugger.hpp" + +namespace tesseratos +{ + /// @brief System argument which can be used to manage the currently loaded project. + class ProjectManager + { + public: + CUBOS_REFLECT; + + /// @brief Resource which holds the state of the project manager. + class State; + + /// @brief Constructs. + /// @param state State of the project manager. + /// @param assets Asset manager. + /// @param debugger Debugger. + ProjectManager(State& state, cubos::engine::Assets& assets, Debugger& debugger); + + /// @brief Opens a project directory. + /// + /// The given project directory is mounted in the /project directory in the virtual file system. + /// Unmounts any previously opened project directory. + /// + /// @param projectOSPath Project's directory path in the operating system. + /// @param binaryOSPath Project's binary directory path in the operating system. + /// @return Whether the project could be opened successfully. + bool open(std::string projectOSPath, std::string binaryOSPath); + + /// @brief Checks whether a project is currently open. + /// @return Whether a project is currently open. + bool open() const; + + /// @brief Closes the currently open project. + void close(); + + /// @brief Launches the project's binary and attaches the debugger. + /// + /// If the binary is already running, it is stopped first. + /// + /// @return Whether the project could be launched successfully. + bool launch(); + + /// @brief Stops the project's binary. + void terminate(); + + private: + State& mState; + cubos::engine::Assets& mAssets; + Debugger& mDebugger; + }; + + class ProjectManager::State + { + public: + CUBOS_REFLECT; + + /// @brief Default constructs. + State() = default; + + private: + friend ProjectManager; + + std::string mProjectOSPath{}; + std::string mBinaryOSPath{}; + cubos::core::thread::Process mProcess{}; + uint16_t mPort{9335}; + }; +} // namespace tesseratos + +namespace cubos::core::ecs +{ + template <> + class SystemFetcher + { + public: + static inline constexpr bool ConsumesOptions = false; + + SystemFetcher state; + SystemFetcher assets; + SystemFetcher debugger; + + SystemFetcher(World& world, const SystemOptions& options) + : state{world, options} + , assets{world, options} + , debugger{world, options} + { + } + + void analyze(SystemAccess& access) const + { + state.analyze(access); + assets.analyze(access); + debugger.analyze(access); + } + + tesseratos::ProjectManager fetch(const SystemContext& ctx) + { + return {state.fetch(ctx), assets.fetch(ctx), debugger.fetch(ctx)}; + } + }; +} // namespace cubos::core::ecs diff --git a/tools/tesseratos/src/tesseratos/project/plugin.cpp b/tools/tesseratos/src/tesseratos/project/plugin.cpp index 8239386d5..bf44e0bcc 100644 --- a/tools/tesseratos/src/tesseratos/project/plugin.cpp +++ b/tools/tesseratos/src/tesseratos/project/plugin.cpp @@ -8,11 +8,12 @@ #include #include -#include #include #include #include +#include "../debugger/plugin.hpp" + using namespace cubos::core::data; using namespace cubos::engine; @@ -22,12 +23,13 @@ void tesseratos::projectPlugin(Cubos& cubos) cubos.depends(imguiPlugin); cubos.depends(settingsPlugin); cubos.depends(assetsPlugin); + cubos.depends(debuggerPlugin); - cubos.resource(); + cubos.resource(); cubos.system("show Project") .tagged(imguiTag) - .call([](Toolbox& toolbox, Project& project, Settings& settings, Assets& assets) { + .call([](ProjectManager project, Toolbox& toolbox, Settings& settings) { if (!toolbox.isOpen("Project")) { return; @@ -48,25 +50,33 @@ void tesseratos::projectPlugin(Cubos& cubos) settings.setString("project.path", projectOSPath); } - if (ImGui::Button("Open")) + auto binaryOSPath = settings.getString("project.binary.path", ""); + if (ImGui::InputText("Binary Path", &binaryOSPath)) { - if (project.open(projectOSPath)) - { - assets.loadMeta("/project/assets"); - } - else - { - CUBOS_ERROR("Failed to open project"); - } + settings.setString("project.binary.path", binaryOSPath); + } + + if (ImGui::Button("Open") && !project.open(projectOSPath, binaryOSPath)) + { + CUBOS_ERROR("Failed to open project"); } ImGui::End(); return; } + if (ImGui::Button("Launch")) + { + project.launch(); + } + + if (ImGui::Button("Terminate")) + { + project.terminate(); + } + if (ImGui::Button("Close")) { - assets.unloadMeta("/project/assets"); project.close(); } diff --git a/tools/tesseratos/src/tesseratos/project/plugin.hpp b/tools/tesseratos/src/tesseratos/project/plugin.hpp index 96ded310c..4ded20a54 100644 --- a/tools/tesseratos/src/tesseratos/project/plugin.hpp +++ b/tools/tesseratos/src/tesseratos/project/plugin.hpp @@ -9,7 +9,7 @@ #include -#include "project.hpp" +#include "manager.hpp" namespace tesseratos { diff --git a/tools/tesseratos/src/tesseratos/project/project.cpp b/tools/tesseratos/src/tesseratos/project/project.cpp deleted file mode 100644 index 943b26a3c..000000000 --- a/tools/tesseratos/src/tesseratos/project/project.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "project.hpp" - -#include -#include -#include -#include -#include - -using cubos::core::data::FileSystem; -using cubos::core::data::StandardArchive; - -CUBOS_REFLECT_IMPL(tesseratos::Project) -{ - using namespace cubos::core::reflection; - return Type::create("tesseratos::Project").with(ConstructibleTrait::typed().withMoveConstructor().build()); -} - -bool tesseratos::Project::open(std::string projectOSPath) -{ - if (projectOSPath.empty()) - { - CUBOS_ERROR("Project path is empty"); - return false; - } - - this->close(); - - // Prepare the project directory for mounting. - auto archive = std::make_unique(projectOSPath, /*isDirectory=*/true, /*readOnly=*/false); - if (!archive->initialized()) - { - CUBOS_ERROR("Could not open project at {}", projectOSPath); - return false; - } - - if (!FileSystem::mount("/project", std::move(archive))) - { - CUBOS_ERROR("Could not mount project archive to /project"); - return false; - } - - mProjectOSPath = std::move(projectOSPath); - return true; -} - -bool tesseratos::Project::open() const -{ - return !mProjectOSPath.empty(); -} - -void tesseratos::Project::close() -{ - if (mProjectOSPath.empty()) - { - return; - } - - // Then unmount its directory from the virtual file system. - if (!FileSystem::unmount("/project")) - { - CUBOS_ERROR("Failed to unmount previously open project directory"); - } - - mProjectOSPath.clear(); -} diff --git a/tools/tesseratos/src/tesseratos/project/project.hpp b/tools/tesseratos/src/tesseratos/project/project.hpp deleted file mode 100644 index ffd9e06fe..000000000 --- a/tools/tesseratos/src/tesseratos/project/project.hpp +++ /dev/null @@ -1,38 +0,0 @@ -/// @file -/// @brief Resource @ref tesseratos::Project. -/// @ingroup tesseratos-debugger-plugin - -#pragma once - -#include - -#include - -namespace tesseratos -{ - /// @brief Resource which can be used to manage the currently loaded project. - class Project - { - public: - CUBOS_REFLECT; - - /// @brief Opens a project directory. - /// - /// The given project directory is mounted in the /project directory in the virtual file system. - /// Unmounts any previously opened project directory. - /// - /// @param projectOSPath Project's directory path in the operating system. - /// @return Whether the project could be opened successfully. - bool open(std::string projectOSPath); - - /// @brief Checks whether a project is currently open. - /// @return Whether a project is currently open. - bool open() const; - - /// @brief Closes the currently open project. - void close(); - - private: - std::string mProjectOSPath{}; - }; -} // namespace tesseratos