Keep CMake module names and trim switches, but stop flattening editing/editor and runtime bridges as top-level src/modules directories. Parent scans skip nested facets so runtime-3d can drop authoring by directory leaf. Co-authored-by: Cursor <cursoragent@cursor.com>
121 lines
5.3 KiB
C++
121 lines
5.3 KiB
C++
#include "particles/editing/ParticleGraph.h"
|
|
|
|
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace eve::particles_editing;
|
|
using namespace eve::editing;
|
|
|
|
namespace {
|
|
|
|
GraphDocumentData particleGraph(ParticleGraphDomain& domain, int bufferSize = 1000) {
|
|
auto emission = domain.makeNode(GraphNodeId("emission"), "emission");
|
|
auto motion = domain.makeNode(GraphNodeId("motion"), "motion");
|
|
auto collision = domain.makeNode(GraphNodeId("collision"), "collision");
|
|
auto renderer = domain.makeNode(GraphNodeId("renderer"), "renderer");
|
|
auto output = domain.makeNode(GraphNodeId("output"), "output");
|
|
REQUIRE(emission.ok());
|
|
REQUIRE(motion.ok());
|
|
REQUIRE(collision.ok());
|
|
REQUIRE(renderer.ok());
|
|
REQUIRE(output.ok());
|
|
auto* emissionProperties = emission.value().properties.getIf<EditorValue::Object>();
|
|
auto* outputProperties = output.value().properties.getIf<EditorValue::Object>();
|
|
REQUIRE(emissionProperties);
|
|
REQUIRE(outputProperties);
|
|
(*emissionProperties)["rate"] = 100.0;
|
|
(*emissionProperties)["lifetime"] = EditorValue::Array{1.0, 2.0};
|
|
(*outputProperties)["bufferSize"] = bufferSize;
|
|
|
|
GraphDocument graph;
|
|
REQUIRE(graph.createNode(emission.value()).ok());
|
|
REQUIRE(graph.createNode(motion.value()).ok());
|
|
REQUIRE(graph.createNode(collision.value()).ok());
|
|
REQUIRE(graph.createNode(renderer.value()).ok());
|
|
REQUIRE(graph.createNode(output.value()).ok());
|
|
const std::vector<std::pair<const char*, const char*>> links = {
|
|
{"emission.out", "motion.in"}, {"motion.out", "collision.in"},
|
|
{"collision.out", "renderer.in"}, {"renderer.out", "output.in"}};
|
|
int index = 0;
|
|
for (const auto& [fromId, toId] : links) {
|
|
const GraphPinRecord* from = graph.findPin(GraphPinId(fromId));
|
|
const GraphPinRecord* to = graph.findPin(GraphPinId(toId));
|
|
REQUIRE(from);
|
|
REQUIRE(to);
|
|
REQUIRE(graph.connect({StableId("edge-" + std::to_string(index++)), from->id, to->id},
|
|
domain.canConnect(*from, *to)).ok());
|
|
}
|
|
return graph.snapshot(domain.domain());
|
|
}
|
|
|
|
GraphNodeRecord* findNode(GraphDocumentData& graph, const char* type) {
|
|
const auto found = std::find_if(graph.nodes.begin(), graph.nodes.end(), [&](const GraphNodeRecord& node) {
|
|
return node.type == type;
|
|
});
|
|
return found == graph.nodes.end() ? nullptr : &*found;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("editor.particles.graph_compiles_real_emitter_modules") {
|
|
ParticleGraphDomain domain;
|
|
const GraphDocumentData graph = particleGraph(domain);
|
|
const ParticleGraphCompileResult compiled = domain.compile(graph);
|
|
CHECK_EQ(static_cast<int>(compiled.status), static_cast<int>(EditorStatus::Applied));
|
|
CHECK_EQ(compiled.documentRevision, graph.revision);
|
|
const auto* config = compiled.configuration.getIf<EditorValue::Object>();
|
|
REQUIRE(config);
|
|
CHECK(config->contains("emission"));
|
|
CHECK(config->contains("motion"));
|
|
CHECK(config->contains("collision"));
|
|
CHECK(config->contains("renderer"));
|
|
CHECK(config->contains("output"));
|
|
}
|
|
|
|
TEST_CASE("editor.particles.preview_is_revision_tagged_and_budget_bounded") {
|
|
ParticleGraphDomain domain;
|
|
const GraphDocumentData graph = particleGraph(domain, 150);
|
|
const auto preview = domain.preview(graph, 10.0, 1.0 / 60.0, 500, 5);
|
|
CHECK_EQ(static_cast<int>(preview.status), static_cast<int>(EditorStatus::Applied));
|
|
CHECK_EQ(preview.documentRevision, graph.revision);
|
|
CHECK_EQ(preview.spawnedParticles, 500);
|
|
CHECK_EQ(preview.droppedParticles, 500);
|
|
CHECK_EQ(preview.peakLiveParticles, 150);
|
|
CHECK(!preview.diagnostics.empty());
|
|
|
|
const auto invalid = domain.preview(graph, -1.0);
|
|
CHECK_EQ(static_cast<int>(invalid.status), static_cast<int>(EditorStatus::Rejected));
|
|
CHECK_EQ(static_cast<int>(domain.preview(graph, 601.0).status),
|
|
static_cast<int>(EditorStatus::Rejected));
|
|
CHECK_EQ(static_cast<int>(domain.preview(graph, 1.0, 1.0e-9).status),
|
|
static_cast<int>(EditorStatus::Rejected));
|
|
}
|
|
|
|
TEST_CASE("editor.particles.graph_rejects_cycles_missing_modules_and_bad_properties") {
|
|
ParticleGraphDomain domain;
|
|
GraphDocumentData graph = particleGraph(domain);
|
|
graph.schemaVersion = 99;
|
|
CHECK_EQ(static_cast<int>(domain.compile(graph).status), static_cast<int>(EditorStatus::Unsupported));
|
|
|
|
graph = particleGraph(domain);
|
|
graph.nodes.erase(std::remove_if(graph.nodes.begin(), graph.nodes.end(), [](const GraphNodeRecord& node) {
|
|
return node.type == "renderer";
|
|
}),
|
|
graph.nodes.end());
|
|
CHECK_EQ(static_cast<int>(domain.compile(graph).status), static_cast<int>(EditorStatus::Failed));
|
|
|
|
graph = particleGraph(domain);
|
|
GraphNodeRecord* emission = findNode(graph, "emission");
|
|
REQUIRE(emission);
|
|
auto* properties = emission->properties.getIf<EditorValue::Object>();
|
|
REQUIRE(properties);
|
|
(*properties)["rate"] = -1.0;
|
|
CHECK_EQ(static_cast<int>(domain.compile(graph).status), static_cast<int>(EditorStatus::Failed));
|
|
|
|
graph = particleGraph(domain);
|
|
graph.edges.push_back({StableId("cycle"), GraphPinId("renderer.out"), GraphPinId("motion.in")});
|
|
CHECK_EQ(static_cast<int>(domain.compile(graph).status), static_cast<int>(EditorStatus::Failed));
|
|
}
|