#define TEST_NO_MAIN #include "acutest.h" #include #include #include #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(parent, Vector3{1.0f, 0.0f, 0.0f}, Vector3{2.0f, 2.0f, 2.0f}, 0.0f); registry.emplace(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(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(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(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(®istry, &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(); for (auto entity : tagView) { const auto& tag = tagView.get(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(childEntity); TEST_CHECK(childHierarchy != nullptr); if (childHierarchy) { TEST_CHECK(childHierarchy->parent == parentEntity); } TEST_CHECK(registry.all_of(parentEntity)); TEST_CHECK(registry.all_of(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(®istry, &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(®istry, &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 / "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("registry.toml"); } entt::registry registry; { ModelManager modelManager; ShaderManager shaderManager; TextureManager textureManager; AssetManager assets; assets.SetManagers(&modelManager, &shaderManager, nullptr, &textureManager); assets.LoadRegistry("registry.toml"); SetSceneContext(®istry, &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(); entt::entity rootEntity = entt::null; for (auto entity : tagView) { const auto& tag = tagView.get(entity); if (tag.name == "Root") { rootEntity = entity; break; } } TEST_CHECK(rootEntity != entt::null); if (rootEntity != entt::null) { auto* renderer = registry.try_get(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(tmpDir); } void test_scene_loader_rejects_escape(void) { entt::registry registry; ModelManager modelManager; ShaderManager shaderManager; TextureManager textureManager; SetSceneContext(®istry, &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); }