Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow opening projects through Tesseratos #1329

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Generic Camera component to hold projection matrix (#1331, **@mkuritsu**).
- 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

- Moved Glad and stb-image libs to another repositories, cubos-glad and cubos-stb, respectively (#1323, **@kuukitenshi**).
- Moved most tools from Tesseratos to the engine (#1322, **@RiscadoA**).

### Fixed

Expand Down
1 change: 1 addition & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ set(CUBOS_CORE_SOURCE
"src/metrics.cpp"

"src/thread/pool.cpp"
"src/thread/process.cpp"
"src/thread/task.cpp"

"src/memory/stream.cpp"
Expand Down
4 changes: 4 additions & 0 deletions core/include/cubos/core/data/fs/standard_archive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ namespace cubos::core::data
/// @param readOnly True if the archive is read-only, false otherwise.
StandardArchive(const std::filesystem::path& osPath, bool isDirectory, bool readOnly);

/// @brief Checks if the archive was successfully initialized.
/// @return True if the archive was successfully initialized, false otherwise.
bool initialized() const;

std::size_t create(std::size_t parent, std::string_view name, bool directory = false) override;
bool destroy(std::size_t id) override;
std::string name(std::size_t id) const override;
Expand Down
58 changes: 58 additions & 0 deletions core/include/cubos/core/thread/process.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/// @file
/// @brief Class @ref cubos::core::thread::Process.
/// @ingroup core-thread

#pragma once

#include <string>
#include <vector>

#include <cubos/core/reflection/reflect.hpp>

namespace cubos::core::thread
{
/// @brief Provides a cross-platform way to spawn child processes.
/// @ingroup core-thread
class Process final
{
public:
CUBOS_REFLECT;

~Process();

/// @brief Default constructor.
Process() = default;

Check warning on line 24 in core/include/cubos/core/thread/process.hpp

View check run for this annotation

Codecov / codecov/patch

core/include/cubos/core/thread/process.hpp#L24

Added line #L24 was not covered by tests

/// @brief Move constructor.
Process(Process&& other) noexcept;

/// @brief Move assignment operator.
Process& operator=(Process&& other) noexcept;

/// @brief Starts a new process.
/// @param command Command to execute.
/// @param args Arguments to pass to the command.
/// @param cwd Working directory for the new process.
/// @return Whether the process was started successfully.
bool start(const std::string& command, const std::vector<std::string>& args = {}, const std::string& cwd = "");

/// @brief Kills the process.
void kill();

/// @brief Waits for the process to finish.
/// @return Whether the process exited normally.
bool wait();

/// @brief Waits for the process to finish.
/// @param status Exit code of the process, if it exited normally.
/// @return Whether the process exited normally.
bool wait(int& status);

/// @brief Checks whether the process has been started.
/// @return Whether the process has been started.
bool started() const;

private:
void* mHandle{nullptr};
};
} // namespace cubos::core::thread
9 changes: 9 additions & 0 deletions core/include/cubos/core/thread/task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ namespace cubos::core::thread
mData = new Data();
}

/// @brief Constructs a finished task.
/// @param value Task result.
/// @return Task.
Task(T value)
: Task{}
{
this->finish(std::move(value));
}

/// @brief Copy constructs.
/// @param other Task.
Task(const Task& other)
Expand Down
5 changes: 5 additions & 0 deletions core/src/data/fs/standard_archive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ void StandardArchive::generate(std::size_t parent)
}
}

bool StandardArchive::initialized() const
{
return !mFiles.empty();
}

std::size_t StandardArchive::create(std::size_t parent, std::string_view name, bool directory)
{
INIT_OR_RETURN(0);
Expand Down
197 changes: 197 additions & 0 deletions core/src/thread/process.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#include <cubos/core/log.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>
#include <cubos/core/thread/process.hpp>

using cubos::core::thread::Process;

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <csignal>
#include <cstring>

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif

CUBOS_REFLECT_IMPL(Process)

Check warning on line 23 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L23

Added line #L23 was not covered by tests
{
return reflection::Type::create("cubos::core::thread::Process")
.with(reflection::ConstructibleTrait::typed<Process>().withDefaultConstructor().withMoveConstructor().build());

Check warning on line 26 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L25-L26

Added lines #L25 - L26 were not covered by tests
}

Process::~Process()

Check warning on line 29 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L29

Added line #L29 was not covered by tests
{
this->kill();
this->wait();

Check warning on line 32 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L31-L32

Added lines #L31 - L32 were not covered by tests
}

Process::Process(Process&& other) noexcept
: mHandle{other.mHandle}

Check warning on line 36 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L35-L36

Added lines #L35 - L36 were not covered by tests
{
other.mHandle = nullptr;

Check warning on line 38 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L38

Added line #L38 was not covered by tests
}

Process& Process::operator=(Process&& other) noexcept

Check warning on line 41 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L41

Added line #L41 was not covered by tests
{
if (this == &other)

Check warning on line 43 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L43

Added line #L43 was not covered by tests
{
return *this;

Check warning on line 45 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L45

Added line #L45 was not covered by tests
}

this->wait();
mHandle = other.mHandle;
other.mHandle = nullptr;
return *this;

Check warning on line 51 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L48-L51

Added lines #L48 - L51 were not covered by tests
}

bool Process::start(const std::string& command, const std::vector<std::string>& args, const std::string& cwd)

Check warning on line 54 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L54

Added line #L54 was not covered by tests
{
this->wait();

Check warning on line 56 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L56

Added line #L56 was not covered by tests

#ifdef _WIN32
// Launch the binary with appropriate arguments.
std::string finalCommand = command;
for (const auto& arg : args)
{
finalCommand += " " + arg;
}
STARTUPINFOA si = {sizeof(si)};
PROCESS_INFORMATION pi;
if (!CreateProcessA(nullptr, const_cast<char*>(finalCommand.c_str()), nullptr, nullptr, FALSE, 0, nullptr,
cwd.empty() ? nullptr : cwd.c_str(), &si, &pi))
{
CUBOS_ERROR("Failed to start process {} with error {}", command, GetLastError());
return false;
}

mHandle = new PROCESS_INFORMATION{pi};
CUBOS_INFO("Started process {} with PID {}", command, pi.dwProcessId);
#else
auto pid = fork();
if (pid == -1)

Check warning on line 78 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L77-L78

Added lines #L77 - L78 were not covered by tests
{
CUBOS_ERROR("Failed to fork process");
return false;

Check warning on line 81 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L80-L81

Added lines #L80 - L81 were not covered by tests
}

if (pid == 0) // Are we the child process?

Check warning on line 84 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L84

Added line #L84 was not covered by tests
{
if (!cwd.empty())

Check warning on line 86 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L86

Added line #L86 was not covered by tests
{
// Change the working directory.
if (chdir(cwd.c_str()) == -1)

Check warning on line 89 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L89

Added line #L89 was not covered by tests
{
CUBOS_CRITICAL("Failed to change working directory to {}", cwd);
exit(1);

Check warning on line 92 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L91-L92

Added lines #L91 - L92 were not covered by tests
}
}

// Launch the binary with appropriate arguments.
std::vector<std::string> argsCopy = args;
argsCopy.insert(argsCopy.begin(), command);
std::vector<char*> argv{};
argv.reserve(argsCopy.size() + 1);
for (const auto& arg : argsCopy)

Check warning on line 101 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L97-L101

Added lines #L97 - L101 were not covered by tests
RiscadoA marked this conversation as resolved.
Show resolved Hide resolved
{
argv.push_back(const_cast<char*>(arg.c_str()));

Check warning on line 103 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L103

Added line #L103 was not covered by tests
}
argv.push_back(nullptr);
execvp(command.c_str(), argv.data());

Check warning on line 106 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L105-L106

Added lines #L105 - L106 were not covered by tests

// If we reach this point, execv failed. Get the error message and log it.
CUBOS_CRITICAL("Failed to start process {} with error {}", command, strerror(errno));
exit(1);

Check warning on line 110 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L109-L110

Added lines #L109 - L110 were not covered by tests
}

// We are the parent process.
mHandle = new pid_t{pid};
CUBOS_INFO("Started process {} with PID {}", command, pid);

Check warning on line 115 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L114-L115

Added lines #L114 - L115 were not covered by tests
#endif

return true;

Check warning on line 118 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L118

Added line #L118 was not covered by tests
}

void Process::kill()

Check warning on line 121 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L121

Added line #L121 was not covered by tests
{
if (mHandle == nullptr)

Check warning on line 123 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L123

Added line #L123 was not covered by tests
{
return;

Check warning on line 125 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L125

Added line #L125 was not covered by tests
}

#ifdef _WIN32
auto* pi = static_cast<PROCESS_INFORMATION*>(mHandle);
::TerminateProcess(pi->hProcess, 1);
CUBOS_DEBUG("Process {} killed", pi->hProcess);
#else
auto* pid = static_cast<pid_t*>(mHandle);
::kill(*pid, SIGKILL);
CUBOS_DEBUG("Process {} killed", *pid);

Check warning on line 135 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L133-L135

Added lines #L133 - L135 were not covered by tests
#endif
}

bool Process::wait()

Check warning on line 139 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L139

Added line #L139 was not covered by tests
{
int status;
return this->wait(status);

Check warning on line 142 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L142

Added line #L142 was not covered by tests
}

bool Process::wait(int& status)

Check warning on line 145 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L145

Added line #L145 was not covered by tests
{
if (mHandle == nullptr)

Check warning on line 147 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L147

Added line #L147 was not covered by tests
{
return false;

Check warning on line 149 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L149

Added line #L149 was not covered by tests
}

#ifdef _WIN32
auto* pi = static_cast<PROCESS_INFORMATION*>(mHandle);
mHandle = nullptr;

::WaitForSingleObject(pi->hProcess, INFINITE);

DWORD exitCode;
GetExitCodeProcess(pi->hProcess, &exitCode);
status = static_cast<int>(exitCode);
CUBOS_DEBUG("Process {} exited with status {}", pi->hProcess, status);

::CloseHandle(pi->hProcess);
::CloseHandle(pi->hThread);
delete pi;

return true;
#else
auto pid = *static_cast<pid_t*>(mHandle);
delete static_cast<pid_t*>(mHandle);
mHandle = nullptr;

Check warning on line 171 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L169-L171

Added lines #L169 - L171 were not covered by tests

int waitStatus;
::waitpid(pid, &waitStatus, 0);

Check warning on line 174 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L174

Added line #L174 was not covered by tests

if (WIFEXITED(waitStatus))

Check warning on line 176 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L176

Added line #L176 was not covered by tests
{
status = WEXITSTATUS(waitStatus);
CUBOS_DEBUG("Process {} exited with status {}", pid, status);
return true;

Check warning on line 180 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L178-L180

Added lines #L178 - L180 were not covered by tests
}

if (WIFSIGNALED(waitStatus))

Check warning on line 183 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L183

Added line #L183 was not covered by tests
{
CUBOS_WARN("Process {} terminated by signal {}", pid, WTERMSIG(waitStatus));
return false;

Check warning on line 186 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L185-L186

Added lines #L185 - L186 were not covered by tests
}

CUBOS_WARN("Process {} terminated abnormally", pid);
return false;

Check warning on line 190 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L189-L190

Added lines #L189 - L190 were not covered by tests
#endif
}

bool Process::started() const

Check warning on line 194 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L194

Added line #L194 was not covered by tests
{
return mHandle != nullptr;

Check warning on line 196 in core/src/thread/process.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/thread/process.cpp#L196

Added line #L196 was not covered by tests
}
4 changes: 4 additions & 0 deletions core/tests/data/fs/standard_archive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ using cubos::core::memory::Stream;
static void assertInitializationFailed(StandardArchive& archive)
{
REQUIRE_FALSE(archive.directory(1)); // Independently of it was created as a directory or not.
REQUIRE_FALSE(archive.initialized());
REQUIRE(archive.parent(1) == 0);
REQUIRE(archive.sibling(1) == 0);
REQUIRE(archive.child(1) == 0);
Expand Down Expand Up @@ -62,6 +63,7 @@ TEST_CASE("data::StandardArchive") // NOLINT(readability-function-size)

// Check if the structure is correct.
CHECK(archive.readOnly() == readOnly);
CHECK(archive.initialized());
CHECK(archive.parent(1) == 0);
CHECK(archive.sibling(1) == 0);
CHECK(archive.child(1) == 0);
Expand Down Expand Up @@ -133,6 +135,7 @@ TEST_CASE("data::StandardArchive") // NOLINT(readability-function-size)
{
archive = std::make_unique<StandardArchive>(path, true, false);
CHECK_FALSE(archive->readOnly());
CHECK(archive->initialized());

// Check initial structure.
CHECK(archive->parent(1) == 0);
Expand Down Expand Up @@ -220,6 +223,7 @@ TEST_CASE("data::StandardArchive") // NOLINT(readability-function-size)
// Create a read-only archive on the generated directory.
archive = std::make_unique<StandardArchive>(path, true, true);
CHECK(archive->readOnly());
CHECK(archive->initialized());

// Check root.
CHECK(archive->directory(1));
Expand Down
7 changes: 7 additions & 0 deletions core/tests/thread/task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ using cubos::core::thread::Task;

TEST_CASE("thread::Task")
{
SUBCASE("finished task")
{
Task<int> task{42};
REQUIRE(task.isDone());
REQUIRE(task.result() == 42);
}

SUBCASE("task which produces an int")
{
Task<int> task{};
Expand Down
16 changes: 16 additions & 0 deletions engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,22 @@ set(CUBOS_ENGINE_SOURCE
"src/render/shadow_atlas/shadow_atlas.cpp"
"src/render/shadow_atlas_rasterizer/plugin.cpp"
"src/render/shadow_atlas_rasterizer/shadow_atlas_rasterizer.cpp"

"src/tools/settings_inspector/plugin.cpp"
"src/tools/selection/plugin.cpp"
"src/tools/selection/selection.cpp"
"src/tools/world_inspector/plugin.cpp"
"src/tools/entity_inspector/plugin.cpp"
"src/tools/debug_camera/plugin.cpp"
"src/tools/toolbox/plugin.cpp"
"src/tools/toolbox/toolbox.cpp"
"src/tools/transform_gizmo/plugin.cpp"
"src/tools/metrics_panel/plugin.cpp"
"src/tools/collider_gizmos/plugin.cpp"
"src/tools/play_pause/plugin.cpp"
"src/tools/ecs_statistics/plugin.cpp"
"src/tools/console/plugin.cpp"
"src/tools/plugin.cpp"
)

# Create cubos engine
Expand Down
Loading
Loading