Files
simian/tests/scenegraph_tests.cpp
T
nick 5e00d8c2ec
CI / build-and-test (push) Successful in 2m18s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 11s
feat: added kv store, correct file permissions
2026-03-13 12:31:33 +13:00

371 lines
11 KiB
C++

#define TEST_NO_MAIN
#include "acutest.h"
#include <filesystem>
#include <fstream>
#include <cstdlib>
#include "../include/ECSComponents.h"
#include "../include/SceneGraph.h"
#include "../include/SceneLoader.h"
#include "../include/ModelManager.h"
#include "../include/ShaderManager.h"
#include "../include/TextureManager.h"
#include "../include/AssetManager.h"
#include "raylib.h"
#include "entt.hpp"
#include "physfs_test_utils.h"
extern "C" {
void acutest_skip_(const char* file, int line, const char* fmt, ...);
}
static bool HasDisplay() {
#if defined(_WIN32)
return true;
#elif defined(__APPLE__)
return true;
#else
const char* display = std::getenv("DISPLAY");
const char* wayland = std::getenv("WAYLAND_DISPLAY");
return (display && *display) || (wayland && *wayland);
#endif
}
static bool InitRaylibHidden() {
const char* forceHeadless = std::getenv("SIMIAN_HEADLESS");
if (forceHeadless && forceHeadless[0] == '1') {
return false;
}
if (!HasDisplay()) {
return false;
}
if (!IsWindowReady()) {
SetConfigFlags(FLAG_WINDOW_HIDDEN);
InitWindow(1, 1, "simian_test_scene");
SetTargetFPS(60);
}
return IsWindowReady();
}
static void ShutdownRaylibHidden() {
if (IsWindowReady()) {
CloseWindow();
}
}
void test_scenegraph_world_transform(void) {
entt::registry registry;
entt::entity parent = registry.create();
entt::entity child = registry.create();
registry.emplace<ECSTransform>(parent, Vector3{1.0f, 0.0f, 0.0f}, Vector3{2.0f, 2.0f, 2.0f}, 0.0f);
registry.emplace<ECSTransform>(child, Vector3{1.0f, 0.0f, 0.0f}, Vector3{1.0f, 1.0f, 1.0f}, 0.0f);
AttachChild(registry, child, parent);
UpdateWorldTransforms(registry);
auto* childWorld = registry.try_get<WorldTransform>(child);
TEST_CHECK(childWorld != nullptr);
if (childWorld) {
TEST_CHECK(childWorld->position.x == 3.0f);
TEST_CHECK(childWorld->position.y == 0.0f);
TEST_CHECK(childWorld->position.z == 0.0f);
}
}
void test_scenegraph_detach(void) {
entt::registry registry;
entt::entity parent = registry.create();
entt::entity child = registry.create();
AttachChild(registry, child, parent);
DetachFromParent(registry, child);
auto* childHierarchy = registry.try_get<Hierarchy>(child);
TEST_CHECK(childHierarchy != nullptr);
if (childHierarchy) {
TEST_CHECK(childHierarchy->parent == entt::null);
TEST_CHECK(childHierarchy->prev == entt::null);
TEST_CHECK(childHierarchy->next == entt::null);
}
auto* parentHierarchy = registry.try_get<Hierarchy>(parent);
TEST_CHECK(parentHierarchy != nullptr);
if (parentHierarchy) {
TEST_CHECK(parentHierarchy->first == entt::null);
}
}
void test_scene_loader_basic(void) {
if (!InitRaylibHidden()) {
TEST_SKIP("No display available for raylib");
return;
}
entt::registry registry;
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene";
std::filesystem::create_directories(tmpDir);
std::filesystem::path scenePath = tmpDir / "scene.toml";
{
std::ofstream scene(scenePath);
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
scene << "position = [0.0, 0.0, 0.0]\n";
scene << "\n";
scene << "[[entity]]\n";
scene << "id = \"parent\"\n";
scene << "parent = \"root\"\n";
scene << "tag = \"Parent\"\n";
scene << "position = [1.0, 0.0, 0.0]\n";
scene << "model = \"cube\"\n";
scene << "model_size = [1.0, 1.0, 1.0]\n";
scene << "color = 0x00FF00FF\n";
scene << "\n";
scene << "[[entity]]\n";
scene << "id = \"child\"\n";
scene << "parent = \"parent\"\n";
scene << "tag = \"Child\"\n";
scene << "position = [0.0, 1.0, 0.0]\n";
scene << "model = \"sphere\"\n";
scene << "radius = 0.5\n";
scene << "color = 0xFF0000FF\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
{
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
entt::entity root = LoadSceneFromFile("scene.toml", true);
TEST_CHECK(root != entt::null);
}
entt::entity parentEntity = entt::null;
entt::entity childEntity = entt::null;
auto tagView = registry.view<Tag>();
for (auto entity : tagView) {
const auto& tag = tagView.get<Tag>(entity);
if (tag.name == "Parent") {
parentEntity = entity;
} else if (tag.name == "Child") {
childEntity = entity;
}
}
TEST_CHECK(parentEntity != entt::null);
TEST_CHECK(childEntity != entt::null);
if (parentEntity != entt::null && childEntity != entt::null) {
auto* childHierarchy = registry.try_get<Hierarchy>(childEntity);
TEST_CHECK(childHierarchy != nullptr);
if (childHierarchy) {
TEST_CHECK(childHierarchy->parent == parentEntity);
}
TEST_CHECK(registry.all_of<ModelRenderer>(parentEntity));
TEST_CHECK(registry.all_of<ModelRenderer>(childEntity));
}
ShutdownRaylibHidden();
std::filesystem::remove(scenePath);
std::filesystem::remove(tmpDir);
}
void test_scene_loader_scene_script_path(void) {
entt::registry registry;
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_script";
std::filesystem::create_directories(tmpDir);
std::filesystem::path scenePath = tmpDir / "scene.toml";
{
std::ofstream scene(scenePath);
scene << "scene_script = \"scripts/demo_scene.as\"\n";
scene << "\n";
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
entt::entity root = LoadSceneFromFile("scene.toml", true);
TEST_CHECK(root != entt::null);
std::string sceneScript;
bool changed = ConsumeSceneScriptPath(sceneScript);
TEST_CHECK(changed == true);
TEST_CHECK(sceneScript == "scripts/demo_scene.as");
std::filesystem::remove(scenePath);
std::filesystem::remove(tmpDir);
}
void test_scene_loader_clear_scene_script(void) {
entt::registry registry;
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
SetSceneScriptPath("scripts/old_scene.as");
std::string previous;
ConsumeSceneScriptPath(previous);
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_clear";
std::filesystem::create_directories(tmpDir);
std::filesystem::path scenePath = tmpDir / "scene.toml";
{
std::ofstream scene(scenePath);
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
LoadSceneFromFile("scene.toml", true);
std::string sceneScript;
bool changed = ConsumeSceneScriptPath(sceneScript);
TEST_CHECK(changed == true);
TEST_CHECK(sceneScript.empty());
ShutdownPhysFSTest();
std::filesystem::remove(scenePath);
std::filesystem::remove(tmpDir);
}
void test_scene_loader_asset_keys(void) {
if (!InitRaylibHidden()) {
TEST_SKIP("No display available for raylib");
return;
}
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_assets";
std::filesystem::create_directories(tmpDir);
std::filesystem::path registryPath = tmpDir / "assets" / "registry.toml";
std::filesystem::path scenePath = tmpDir / "scene_asset.toml";
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
{
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
AssetManager assets;
assets.SetManagers(&modelManager, &shaderManager, nullptr, &textureManager);
assets.LoadCube("demo_cube", 1.0f, 1.0f, 1.0f);
assets.SaveRegistry("assets/registry.toml");
}
entt::registry registry;
{
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
AssetManager assets;
assets.SetManagers(&modelManager, &shaderManager, nullptr, &textureManager);
assets.LoadRegistry("assets/registry.toml");
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
SetSceneAssetManager(&assets);
{
std::ofstream scene(scenePath);
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
scene << "position = [0.0, 0.0, 0.0]\n";
scene << "model_asset = \"demo_cube\"\n";
scene << "color = 0xFFFFFFFF\n";
scene << "\n";
}
entt::entity root = LoadSceneFromFile("scene_asset.toml", true);
TEST_CHECK(root != entt::null);
auto tagView = registry.view<Tag>();
entt::entity rootEntity = entt::null;
for (auto entity : tagView) {
const auto& tag = tagView.get<Tag>(entity);
if (tag.name == "Root") {
rootEntity = entity;
break;
}
}
TEST_CHECK(rootEntity != entt::null);
if (rootEntity != entt::null) {
auto* renderer = registry.try_get<ModelRenderer>(rootEntity);
TEST_CHECK(renderer != nullptr);
if (renderer) {
unsigned int expectedId = assets.GetModelId("demo_cube");
TEST_CHECK(expectedId != 0);
TEST_CHECK(renderer->modelId == expectedId);
}
}
}
ShutdownRaylibHidden();
ShutdownPhysFSTest();
std::filesystem::remove(scenePath);
std::filesystem::remove(registryPath);
std::filesystem::remove(registryPath.parent_path());
std::filesystem::remove(tmpDir);
}
void test_scene_loader_rejects_escape(void) {
entt::registry registry;
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_sandbox";
std::filesystem::create_directories(tmpDir);
std::filesystem::path outsidePath = std::filesystem::current_path() / "outside_scene.toml";
{
std::ofstream scene(outsidePath);
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
entt::entity root = LoadSceneFromFile("../outside_scene.toml", true);
TEST_CHECK(root == entt::null);
ShutdownPhysFSTest();
std::filesystem::remove(outsidePath);
std::filesystem::remove(tmpDir);
}