fix(infra): gradle_run detecta android-sdk — issue 0076 #2
Binary file not shown.
@@ -257,3 +257,78 @@ const DagNodeDef* dag_find(const std::string& name) {
|
||||
}
|
||||
|
||||
} // namespace fn::gfx
|
||||
|
||||
#ifdef DAG_CATALOG_TEST
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <set>
|
||||
|
||||
int main() {
|
||||
using namespace fn::gfx;
|
||||
const auto& cat = dag_catalog();
|
||||
|
||||
// 1. Catalog not empty
|
||||
assert(!cat.empty());
|
||||
|
||||
// 2. All node names are unique
|
||||
std::set<std::string> names;
|
||||
for (const auto& n : cat) {
|
||||
assert(names.insert(n.name).second && "duplicate node name");
|
||||
}
|
||||
|
||||
// 3. Invariants by kind
|
||||
int gen_count = 0, op_count = 0, blend_count = 0, output_count = 0;
|
||||
for (const auto& n : cat) {
|
||||
switch (n.kind) {
|
||||
case DagKind::Gen:
|
||||
assert(n.num_inputs == 0 && "Gen must have 0 inputs");
|
||||
++gen_count; break;
|
||||
case DagKind::Op:
|
||||
assert(n.num_inputs >= 1 && "Op must have >= 1 input");
|
||||
++op_count; break;
|
||||
case DagKind::Blend:
|
||||
assert(n.num_inputs >= 2 && "Blend must have >= 2 inputs");
|
||||
++blend_count; break;
|
||||
case DagKind::Output:
|
||||
assert(n.num_inputs == 1 && "Output must have exactly 1 input");
|
||||
++output_count; break;
|
||||
}
|
||||
assert(n.num_inputs >= 0 && n.num_inputs <= 4);
|
||||
assert(n.body_glsl && "body_glsl must be set");
|
||||
}
|
||||
|
||||
// 4. Exactly one Output in the catalog
|
||||
assert(output_count == 1 && "catalog must declare exactly one Output node");
|
||||
|
||||
// 5. At least one Gen and one Op
|
||||
assert(gen_count >= 1);
|
||||
assert(op_count >= 1);
|
||||
assert(blend_count >= 1);
|
||||
|
||||
// 6. dag_find works and returns nullptr for unknown names
|
||||
assert(dag_find("output") != nullptr);
|
||||
assert(dag_find("plasma") != nullptr);
|
||||
assert(dag_find("__nonexistent__") == nullptr);
|
||||
|
||||
// 7. Every non-Output body emits non-empty GLSL
|
||||
for (const auto& n : cat) {
|
||||
if (n.kind == DagKind::Output) continue;
|
||||
auto body = n.body_glsl(0);
|
||||
assert(!body.empty() && "non-Output nodes must emit body");
|
||||
// sanity: should contain a return statement
|
||||
assert(body.find("return") != std::string::npos);
|
||||
}
|
||||
|
||||
// 8. Control param indices stay within 0..3
|
||||
for (const auto& n : cat) {
|
||||
for (const auto& c : n.controls) {
|
||||
for (int idx : c.param_idx) {
|
||||
assert(idx >= -1 && idx < 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::printf("dag_catalog: 8/8 asserts passed (%zu nodes)\n", cat.size());
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -106,3 +106,83 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
|
||||
}
|
||||
|
||||
} // namespace fn::gfx
|
||||
|
||||
#ifdef DAG_COMPILE_TEST
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
static bool contains(const std::string& hay, const std::string& needle) {
|
||||
return hay.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
int main() {
|
||||
using namespace fn::gfx;
|
||||
|
||||
// 1. Empty pipeline → seed color
|
||||
{
|
||||
std::vector<DagStep> p;
|
||||
auto s = compile_dag_to_glsl(p);
|
||||
assert(contains(s, "fragColor = vec4(0.04"));
|
||||
}
|
||||
|
||||
// 2. Single Gen → fragColor = out_0
|
||||
{
|
||||
std::vector<DagStep> p;
|
||||
DagStep g; g.id = "a"; g.name = "plasma";
|
||||
p.push_back(g);
|
||||
auto s = compile_dag_to_glsl(p);
|
||||
assert(contains(s, "vec4 node_0"));
|
||||
assert(contains(s, "vec4 out_0 = node_0("));
|
||||
assert(contains(s, "fragColor = out_0"));
|
||||
}
|
||||
|
||||
// 3. Gen + Op → Op uses out_0 as input a
|
||||
{
|
||||
std::vector<DagStep> p;
|
||||
DagStep g; g.id = "a"; g.name = "plasma"; p.push_back(g);
|
||||
DagStep o; o.id = "b"; o.name = "invert"; o.source_ids[0] = "a"; p.push_back(o);
|
||||
auto s = compile_dag_to_glsl(p);
|
||||
assert(contains(s, "out_1 = node_1(out_0, uv)"));
|
||||
assert(contains(s, "fragColor = out_1"));
|
||||
}
|
||||
|
||||
// 4. Blend with multi-source → both inputs resolved
|
||||
{
|
||||
std::vector<DagStep> p;
|
||||
DagStep a; a.id = "a"; a.name = "plasma"; p.push_back(a);
|
||||
DagStep b; b.id = "b"; b.name = "solid"; p.push_back(b);
|
||||
DagStep m; m.id = "m"; m.name = "blend_mix";
|
||||
m.source_ids[0] = "a"; m.source_ids[1] = "b";
|
||||
p.push_back(m);
|
||||
auto s = compile_dag_to_glsl(p);
|
||||
assert(contains(s, "out_2 = node_2(out_0, out_1, uv)"));
|
||||
}
|
||||
|
||||
// 5. Output node drives fragColor from its source, not from last index
|
||||
{
|
||||
std::vector<DagStep> p;
|
||||
DagStep g1; g1.id = "g1"; g1.name = "plasma"; p.push_back(g1);
|
||||
DagStep g2; g2.id = "g2"; g2.name = "solid"; p.push_back(g2);
|
||||
DagStep o; o.id = "o"; o.name = "output";
|
||||
o.source_ids[0] = "g1"; // connect Output to the plasma (first gen), not last
|
||||
p.push_back(o);
|
||||
auto s = compile_dag_to_glsl(p);
|
||||
// Output must NOT emit a node_2 function
|
||||
assert(!contains(s, "vec4 node_2("));
|
||||
// fragColor must come from out_0 (plasma), not out_1 (solid)
|
||||
assert(contains(s, "fragColor = out_0"));
|
||||
}
|
||||
|
||||
// 6. Output with no connection → seed fallback
|
||||
{
|
||||
std::vector<DagStep> p;
|
||||
DagStep g; g.id = "g"; g.name = "plasma"; p.push_back(g);
|
||||
DagStep o; o.id = "o"; o.name = "output"; p.push_back(o); // no source
|
||||
auto s = compile_dag_to_glsl(p);
|
||||
assert(contains(s, "fragColor = vec4(0.04"));
|
||||
}
|
||||
|
||||
std::printf("dag_compile: 6/6 asserts passed\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -138,25 +138,26 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
||||
s_ctx = ed::CreateEditor(&cfg);
|
||||
}
|
||||
|
||||
// Palette drop zone: accept dropped node types (from the Functions panel)
|
||||
// and queue an add at the mouse canvas position (resolved after ed::Begin).
|
||||
// Palette drop: detect without a capturing target (an InvisibleButton would
|
||||
// steal clicks from the node editor). Observe the active drag-drop payload
|
||||
// and, if the mouse is over this window and the user releases LMB, queue an
|
||||
// add at that canvas position.
|
||||
static std::string s_pending_add_name;
|
||||
static ImVec2 s_pending_add_pos(0, 0);
|
||||
static bool s_pending_add = false;
|
||||
|
||||
ImVec2 origin = ImGui::GetCursorScreenPos();
|
||||
ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||
ImGui::InvisibleButton("##dag_drop_zone", avail,
|
||||
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("DAG_NODE_TYPE")) {
|
||||
s_pending_add_name.assign(static_cast<const char*>(p->Data), static_cast<size_t>(p->DataSize));
|
||||
s_pending_add_pos = ImGui::GetMousePos();
|
||||
s_pending_add = true;
|
||||
const bool window_hovered = ImGui::IsWindowHovered(
|
||||
ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||
if (window_hovered) {
|
||||
if (const ImGuiPayload* p = ImGui::GetDragDropPayload()) {
|
||||
if (p->IsDataType("DAG_NODE_TYPE") && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||
s_pending_add_name.assign(static_cast<const char*>(p->Data),
|
||||
static_cast<size_t>(p->DataSize));
|
||||
s_pending_add_pos = ImGui::GetMousePos();
|
||||
s_pending_add = true;
|
||||
}
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
ImGui::SetCursorScreenPos(origin);
|
||||
|
||||
ed::SetCurrentEditor(s_ctx);
|
||||
ed::Begin("dag_editor", ImVec2(0.0f, 0.0f));
|
||||
|
||||
Reference in New Issue
Block a user