diff --git a/core/src/error/instance.h b/core/src/error/instance.h index 88d48d9..1e48f09 100644 --- a/core/src/error/instance.h +++ b/core/src/error/instance.h @@ -7,6 +7,11 @@ class NoSuchInstance : public Error { inline NoSuchInstance(std::string className) : Error("NoSuchInstance", "Cannot create instance of unknown type " + className) {} }; +class NotCreatableInstance : public Error { + public: + inline NotCreatableInstance(std::string className) : Error("NotCreatableInstance", "Instance class " + className + " is not creatable") {} +}; + class NoSuchService : public Error { public: inline NoSuchService(std::string className) : Error("NoSuchService", "Unknown service type " + className) {} diff --git a/core/src/objects/base/instance.cpp b/core/src/objects/base/instance.cpp index 593cf34..ba830f6 100644 --- a/core/src/objects/base/instance.cpp +++ b/core/src/objects/base/instance.cpp @@ -524,4 +524,17 @@ std::string Instance::GetFullName() { } return currentName; +} + +result, NoSuchInstance, NotCreatableInstance> Instance::New(std::string className) { + const InstanceType* type = INSTANCE_MAP[className]; + + if (type == nullptr) { + return NoSuchInstance(className); + } + + if (type->flags & (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE) || type->constructor == nullptr) + return NotCreatableInstance(className); + + return type->constructor(); } \ No newline at end of file diff --git a/core/src/objects/base/instance.h b/core/src/objects/base/instance.h index 3f57242..b29bd62 100644 --- a/core/src/objects/base/instance.h +++ b/core/src/objects/base/instance.h @@ -114,6 +114,9 @@ public: inline void AddChild(std::shared_ptr object) { object->SetParent(this->shared_from_this()); } nullable std::shared_ptr FindFirstChild(std::string); std::string GetFullName(); + + // Dynamically create an instance + static result, NoSuchInstance, NotCreatableInstance> New(std::string className); // Properties result GetProperty(std::string name); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ac7fcd8..3847337 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(obtest src/lua/luasched.cpp src/lua/luasignal.cpp src/lua/luageneric.cpp + src/objectmodel/basic.cpp ) target_link_libraries(obtest PRIVATE openblocks Catch2::Catch2WithMain) target_include_directories(obtest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) diff --git a/tests/src/common.cpp b/tests/src/common.cpp index a9069e5..cd9605e 100644 --- a/tests/src/common.cpp +++ b/tests/src/common.cpp @@ -18,9 +18,6 @@ public: void testRunStarting(Catch::TestRunInfo const&) override { // TODO: Make physicsInit optional in headless environments physicsInit(); - - gTestModel = DataModel::New(); - gTestModel->Init(true); Logger::initTest(&testLogOutput); } @@ -33,21 +30,12 @@ public: void testCasePartialStarting(const Catch::TestCaseInfo &testInfo, uint64_t partNumber) override { // Clear the log output prior to each test testLogOutput.str(""); + + gTestModel = DataModel::New(); + gTestModel->Init(true); } - void testCasePartialEnded(const Catch::TestCaseStats &testCaseStats, uint64_t partNumber) override { - auto ctx = gTestModel->GetService(); - ctx->DebugClearSleepingThreads(); - - // Clean up remaining scripts from ServerScriptService - for (auto& obj : gTestModel->GetService()->GetChildren()) { - obj->Destroy(); - } - - // Also clear workspace - for (auto& obj : gTestModel->GetService()->GetChildren()) { - obj->Destroy(); - } + void testCasePartialEnded(const Catch::TestCaseStats &testCaseStats, uint64_t partNumber) override { } }; diff --git a/tests/src/objectmodel/basic.cpp b/tests/src/objectmodel/basic.cpp new file mode 100644 index 0000000..79dd993 --- /dev/null +++ b/tests/src/objectmodel/basic.cpp @@ -0,0 +1,112 @@ +// Basic operations such as instantiation, re-parenting, and destruction + +#include "common.h" +#include "error/instance.h" +#include "objects/model.h" +#include "objects/part/part.h" +#include + +static auto& m = gDataModel; + +TEST_CASE("Construction") { + auto folder = Model::New(); + m->AddChild(folder); + + SECTION("Constructing container") { + bool found = false; + for (auto& obj : m->GetChildren()) { + if (obj == folder) { + found = true; + } + } + + REQUIRE(found); + REQUIRE(folder->GetParent() != nullptr); + REQUIRE(folder->GetParent() == m); + } + + SECTION("Constructing Part") { + auto part = Part::New(); + folder->AddChild(part); + + REQUIRE(folder->GetChildren().size() == 1); + REQUIRE(folder->GetChildren()[0] == part); + REQUIRE(part->GetParent() != nullptr); + REQUIRE(part->GetParent() == folder); + } + + SECTION("Dynamic construction of part") { + auto result = Instance::New("Part"); + REQUIRE(result.isSuccess()); + } + + SECTION("Invalid construction of service") { + auto result = Instance::New("Workspace"); + REQUIRE(result.isError()); + REQUIRE(std::holds_alternative(result.error().value())); + } + + SECTION("Invalid construction of nonexistent type") { + auto result = Instance::New("__INVALID"); + REQUIRE(result.isError()); + REQUIRE(std::holds_alternative(result.error().value())); + } +} + +TEST_CASE("Parenting") { + auto folder = Model::New(); + m->AddChild(folder); + + SECTION("Reparent part to another folder") { + auto folder2 = Model::New(); + m->AddChild(folder2); + + auto part = Part::New(); + folder->AddChild(part); + + // Verify initial folder + REQUIRE(folder->GetChildren().size() == 1); + REQUIRE(folder->GetChildren()[0] == part); + REQUIRE(part->GetParent() != nullptr); + REQUIRE(part->GetParent() == folder); + + folder2->AddChild(part); // AddChild just internally calls SetParent, so it should automatically take care of cleanup + + // Verify new folder + REQUIRE(folder->GetChildren().size() == 0); + REQUIRE(folder2->GetChildren().size() == 1); + REQUIRE(folder2->GetChildren()[0] == part); + REQUIRE(part->GetParent() != nullptr); + REQUIRE(part->GetParent() == folder2); + } + + SECTION("Unparenting") { + auto part = Part::New(); + folder->AddChild(part); + + part->SetParent(nullptr); + REQUIRE(folder->GetChildren().size() == 0); + REQUIRE(part->GetParent() == nullptr); + } + + SECTION("Nested reparent") { + auto folder2 = Model::New(); + m->AddChild(folder2); + + auto part = Part::New(); + folder->AddChild(part); + auto part2 = Part::New(); + part->AddChild(part2); + + REQUIRE(part->GetChildren().size() == 1); + REQUIRE(part->GetChildren()[0] == part2); + REQUIRE(part2->GetParent() == part); + + folder2->AddChild(part); // AddChild just internally calls SetParent, so it should automatically take care of cleanup + + // Make sure nothing changed + REQUIRE(part->GetChildren().size() == 1); + REQUIRE(part->GetChildren()[0] == part2); + REQUIRE(part2->GetParent() == part); + } +} \ No newline at end of file