feat: add ObjectRef for scene loading to not rely on runtime id's
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
add_executable(destrum_tests
|
||||
destrum_tests.cpp
|
||||
${CMAKE_SOURCE_DIR}/lightkeeper/src/components/Activatable.cpp
|
||||
${CMAKE_SOURCE_DIR}/lightkeeper/src/components/Interactable.cpp
|
||||
${CMAKE_SOURCE_DIR}/lightkeeper/src/components/InteractableButton.cpp
|
||||
)
|
||||
|
||||
set_target_properties(destrum_tests PROPERTIES
|
||||
@@ -12,6 +15,7 @@ target_compile_definitions(destrum_tests PRIVATE
|
||||
SDL_MAIN_HANDLED
|
||||
DESTRUM_ENGINE_ASSETS_SRC_DIR="${CMAKE_SOURCE_DIR}/destrum/assets_src"
|
||||
)
|
||||
target_include_directories(destrum_tests PRIVATE "${CMAKE_SOURCE_DIR}/lightkeeper/include")
|
||||
target_link_libraries(destrum_tests PRIVATE destrum::destrum)
|
||||
|
||||
destrum_enable_warnings(destrum_tests)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <glm/gtc/packing.hpp>
|
||||
@@ -44,6 +45,9 @@
|
||||
#include <destrum/Graphics/imageLoader.h>
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
|
||||
#include <components/Activatable.h>
|
||||
#include <components/InteractableButton.h>
|
||||
|
||||
namespace {
|
||||
class TestComponent final : public Component {
|
||||
public:
|
||||
@@ -53,6 +57,18 @@ namespace {
|
||||
std::string GetTypeName() const override { return "TestComponent"; }
|
||||
};
|
||||
|
||||
class TestActivatable final : public Activatable {
|
||||
public:
|
||||
explicit TestActivatable(GameObject& owner) : Activatable(owner, "TestActivatable") {}
|
||||
|
||||
std::string GetTypeName() const override { return "TestActivatable"; }
|
||||
|
||||
int activations{0};
|
||||
|
||||
protected:
|
||||
void OnActivate(GameObject&) override { ++activations; }
|
||||
};
|
||||
|
||||
class RemovingListener final : public EventListener {
|
||||
public:
|
||||
explicit RemovingListener(std::unique_ptr<RemovingListener>* victim)
|
||||
@@ -1473,6 +1489,132 @@ namespace {
|
||||
!PrimitiveMeshes::MakeFromKey("Box").has_value(),
|
||||
"invalid primitive keys must not build a mesh");
|
||||
}
|
||||
|
||||
void testSceneFileIdsAreLocal()
|
||||
{
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_local_ids_scene.json";
|
||||
std::ofstream{path}
|
||||
<< R"({"version":2,"currentCamera":2,"objects":[)"
|
||||
R"({"id":1,"name":"root"},)"
|
||||
R"({"id":2,"name":"child","parent":1,"components":[{"type":"CameraComponent"}]}]})";
|
||||
|
||||
Scene& first = SceneManager::GetInstance().CreateScene("local ids a");
|
||||
Scene& second = SceneManager::GetInstance().CreateScene("local ids b");
|
||||
check(SceneSerializer::Load(first, path), "the first copy of the scene must load");
|
||||
check(SceneSerializer::Load(second, path), "the second copy of the scene must load");
|
||||
std::filesystem::remove(path);
|
||||
first.CommitPendingAdditions();
|
||||
second.CommitPendingAdditions();
|
||||
|
||||
std::unordered_set<ObjectId> ids;
|
||||
std::size_t count = 0;
|
||||
for (const Scene* scene : {&first, &second}) {
|
||||
for (const auto& object : scene->GetObjects()) {
|
||||
ids.insert(object->GetId());
|
||||
++count;
|
||||
}
|
||||
}
|
||||
check(count == 4 && ids.size() == 4, "loading one file into two scenes must not duplicate ids");
|
||||
|
||||
for (const Scene* scene : {&first, &second}) {
|
||||
const CameraComponent* camera = scene->GetCurrentCamera();
|
||||
check(camera != nullptr && camera->GetGameObject()->GetName() == "child" &&
|
||||
camera->GetGameObject()->GetScene() == scene,
|
||||
"the current camera must be remapped to the object in its own scene");
|
||||
const Transform* parent = camera->GetGameObject()->GetTransform().GetParent();
|
||||
check(parent != nullptr && parent->GetOwner()->GetName() == "root" &&
|
||||
parent->GetOwner()->GetScene() == scene,
|
||||
"parents must be remapped within each loaded scene");
|
||||
}
|
||||
|
||||
const GameObject* fresh = first.CreateGameObject("fresh");
|
||||
check(!ids.contains(fresh->GetId()), "objects created after a load must get unused ids");
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
GameObject* findByName(const Scene& scene, const std::string& name)
|
||||
{
|
||||
for (const auto& object : scene.GetObjects()) {
|
||||
if (object->GetName() == name) {
|
||||
return object.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void testButtonTargetsRoundTrip()
|
||||
{
|
||||
ComponentFactory::Register<InteractableButton>("InteractableButton");
|
||||
ComponentFactory::Register<TestActivatable>("TestActivatable");
|
||||
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("button targets");
|
||||
GameObject* door = scene.CreateGameObject("door");
|
||||
door->AddComponent<TestActivatable>();
|
||||
GameObject* lamp = scene.CreateGameObject("lamp");
|
||||
lamp->AddComponent<TestActivatable>();
|
||||
GameObject* buttonObject = scene.CreateGameObject("button");
|
||||
auto* button = buttonObject->AddComponent<InteractableButton>();
|
||||
button->SetCycleColors(false);
|
||||
button->AddTarget(*door);
|
||||
button->AddTarget(*lamp);
|
||||
button->AddTarget(*door);
|
||||
scene.CommitPendingAdditions();
|
||||
check(button->GetTargets().size() == 2, "a button must not target the same object twice");
|
||||
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_button_targets_scene.json";
|
||||
check(SceneSerializer::Save(scene, path), "the button scene must save");
|
||||
check(SceneSerializer::Load(scene, path), "the button scene must load");
|
||||
|
||||
Scene& copy = SceneManager::GetInstance().CreateScene("button targets copy");
|
||||
check(SceneSerializer::Load(copy, path), "the button scene must load into a second scene");
|
||||
std::filesystem::remove(path);
|
||||
|
||||
for (Scene* loaded : {&scene, ©}) {
|
||||
loaded->CommitPendingAdditions();
|
||||
GameObject* loadedButton = findByName(*loaded, "button");
|
||||
auto* loadedComponent = loadedButton != nullptr ? loadedButton->GetComponent<InteractableButton>() : nullptr;
|
||||
check(loadedComponent != nullptr, "the button must survive a round-trip");
|
||||
|
||||
const auto& targets = loadedComponent->GetTargets();
|
||||
check(targets.size() == 2, "button targets must survive a round-trip");
|
||||
GameObject* loadedDoor = targets[0].Get(*loaded);
|
||||
GameObject* loadedLamp = targets[1].Get(*loaded);
|
||||
check(loadedDoor == findByName(*loaded, "door") && loadedDoor != nullptr &&
|
||||
loadedLamp == findByName(*loaded, "lamp") && loadedLamp != nullptr,
|
||||
"button targets must point at the loaded objects in their own scene");
|
||||
|
||||
loadedComponent->Interact(*loadedButton);
|
||||
check(loadedDoor->GetComponent<TestActivatable>()->activations == 1 &&
|
||||
loadedLamp->GetComponent<TestActivatable>()->activations == 1,
|
||||
"pressing a loaded button must activate its targets");
|
||||
}
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testButtonDropsMissingTargets()
|
||||
{
|
||||
ComponentFactory::Register<InteractableButton>("InteractableButton");
|
||||
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("missing targets");
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_missing_targets_scene.json";
|
||||
std::ofstream{path}
|
||||
<< R"({"version":2,"objects":[{"id":5,"name":"button","components":)"
|
||||
R"([{"type":"InteractableButton","data":{"cycleColors":false,"targets":[5,99]}}]}]})";
|
||||
|
||||
check(SceneSerializer::Load(scene, path), "a button with a dangling target must still load");
|
||||
std::filesystem::remove(path);
|
||||
scene.CommitPendingAdditions();
|
||||
|
||||
const GameObject* object = scene.GetObjects().front().get();
|
||||
const auto* button = object->GetComponent<InteractableButton>();
|
||||
check(button != nullptr && button->GetTargets().size() == 1 &&
|
||||
button->GetTargets().front().GetId() == object->GetId(),
|
||||
"dangling targets must be dropped and valid ones remapped");
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -1518,6 +1660,9 @@ int main()
|
||||
testSceneLoadDropsMissingCamera();
|
||||
testGlmJson();
|
||||
testPrimitiveMeshKeys();
|
||||
testSceneFileIdsAreLocal();
|
||||
testButtonTargetsRoundTrip();
|
||||
testButtonDropsMissingTargets();
|
||||
std::cout << "destrum tests passed\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user