Skip to content

Commit

Permalink
feat(tesseratos): add Project plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
RiscadoA committed Sep 29, 2024
1 parent 53d39cc commit e396faa
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Initial application debugging through Tesseratos (#1303, **@RiscadoA**).
- Print stacktrace with *cpptrace* on calls to CUBOS_FAIL (#1172, **@RiscadoA**).
- Simple cross-platform multi-process utilities (**@RiscadoA**).
- Project opening and closing on Tesseratos (#1218, **@RiscadoA**).

### Changed

Expand Down
2 changes: 2 additions & 0 deletions tools/tesseratos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ set(TESSERATOS_SOURCE
"src/tesseratos/main.cpp"
"src/tesseratos/debugger/plugin.cpp"
"src/tesseratos/debugger/debugger.cpp"
"src/tesseratos/project/plugin.cpp"
"src/tesseratos/project/manager.cpp"
"src/tesseratos/asset_explorer/plugin.cpp"
"src/tesseratos/asset_explorer/popup.cpp"
"src/tesseratos/scene_editor/plugin.cpp"
Expand Down
3 changes: 3 additions & 0 deletions tools/tesseratos/src/tesseratos/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "asset_explorer/plugin.hpp"
#include "debugger/plugin.hpp"
#include "project/plugin.hpp"
#include "scene_editor/plugin.hpp"
#include "voxel_palette_editor/plugin.hpp"

Expand All @@ -34,6 +35,8 @@ int main(int argc, char** argv)
cubos.plugin(debuggerPlugin);
cubos.plugin(assetExplorerPlugin);

cubos.plugin(projectPlugin);

cubos.plugin(sceneEditorPlugin);
cubos.plugin(voxelPaletteEditorPlugin);

Expand Down
145 changes: 145 additions & 0 deletions tools/tesseratos/src/tesseratos/project/manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include "manager.hpp"
#include <thread>

#include <cubos/core/data/fs/file_system.hpp>
#include <cubos/core/data/fs/standard_archive.hpp>
#include <cubos/core/reflection/external/cstring.hpp>
#include <cubos/core/reflection/external/primitives.hpp>
#include <cubos/core/reflection/external/string.hpp>
#include <cubos/core/reflection/traits/constructible.hpp>
#include <cubos/core/reflection/type.hpp>

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<State>().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<StandardArchive>(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.
}
115 changes: 115 additions & 0 deletions tools/tesseratos/src/tesseratos/project/manager.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/// @file
/// @brief System argument @ref tesseratos::ProjectManager.
/// @ingroup tesseratos-debugger-plugin

#pragma once

#include <string>

#include <cubos/core/ecs/system/fetcher.hpp>
#include <cubos/core/thread/process.hpp>

#include <cubos/engine/assets/plugin.hpp>

#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<tesseratos::ProjectManager>
{
public:
static inline constexpr bool ConsumesOptions = false;

SystemFetcher<tesseratos::ProjectManager::State&> state;
SystemFetcher<cubos::engine::Assets&> assets;
SystemFetcher<tesseratos::Debugger&> 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
85 changes: 85 additions & 0 deletions tools/tesseratos/src/tesseratos/project/plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include "plugin.hpp"
#include <sstream>

#include <imgui.h>
#include <imgui_stdlib.h>

#include <cubos/core/data/fs/file_system.hpp>
#include <cubos/core/data/fs/standard_archive.hpp>
#include <cubos/core/reflection/external/string.hpp>

#include <cubos/engine/imgui/plugin.hpp>
#include <cubos/engine/settings/plugin.hpp>
#include <cubos/engine/tools/toolbox/plugin.hpp>

#include "../debugger/plugin.hpp"

using namespace cubos::core::data;
using namespace cubos::engine;

void tesseratos::projectPlugin(Cubos& cubos)
{
cubos.depends(toolboxPlugin);
cubos.depends(imguiPlugin);
cubos.depends(settingsPlugin);
cubos.depends(assetsPlugin);
cubos.depends(debuggerPlugin);

cubos.resource<ProjectManager::State>();

cubos.system("show Project")
.tagged(imguiTag)
.call([](ProjectManager project, Toolbox& toolbox, Settings& settings) {
if (!toolbox.isOpen("Project"))
{
return;
}

if (!ImGui::Begin("Project"))
{
ImGui::End();
return;
}

if (!project.open())
{
// If the project is not open, only show the open dialog.
auto projectOSPath = settings.getString("project.path", "");
if (ImGui::InputText("Project Path", &projectOSPath))
{
settings.setString("project.path", projectOSPath);
}

auto binaryOSPath = settings.getString("project.binary.path", "");
if (ImGui::InputText("Binary Path", &binaryOSPath))
{
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"))
{
project.close();
}

ImGui::End();
});
}
Loading

0 comments on commit e396faa

Please sign in to comment.