fix(fn-run): propagar stdout/stderr de bash functions library-style #1

Open
dataforge wants to merge 537 commits from auto/0077-fn-run-bash-mudo into master
11 changed files with 253 additions and 106 deletions
Showing only changes of commit e828af3ac1 - Show all commits
Binary file not shown.
+1
View File
@@ -12,6 +12,7 @@ add_imgui_app(shaders_lab
${CMAKE_SOURCE_DIR}/functions/gfx/dag_uniforms.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/dag_panel.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/dag_node_editor.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/dag_palette.cpp
${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp
)
target_include_directories(shaders_lab PRIVATE
+32 -7
View File
@@ -10,6 +10,7 @@
#include "gfx/dag_uniforms.h"
#include "gfx/dag_panel.h"
#include "gfx/dag_node_editor.h"
#include "gfx/dag_palette.h"
#include "core/fps_overlay.h"
#include "seed_shaders.h"
@@ -73,14 +74,32 @@ static void load_preset(const char* src) {
}
static void ensure_dag_default() {
// Seed with a Plasma connected to an Output node.
if (g_pipeline.empty()) {
const fn::gfx::DagNodeDef* def = fn::gfx::dag_find("plasma");
if (def) {
fn::gfx::DagStep step;
step.id = "n_default";
step.name = def->name;
step.params = def->param_defaults;
g_pipeline.push_back(step);
const fn::gfx::DagNodeDef* plasma = fn::gfx::dag_find("plasma");
if (plasma) {
fn::gfx::DagStep s;
s.id = "n_plasma";
s.name = plasma->name;
s.params = plasma->param_defaults;
g_pipeline.push_back(s);
}
}
// Ensure there is always an Output node at the end.
bool has_output = false;
for (const auto& s : g_pipeline) {
const fn::gfx::DagNodeDef* d = fn::gfx::dag_find(s.name);
if (d && d->kind == fn::gfx::DagKind::Output) { has_output = true; break; }
}
if (!has_output) {
const fn::gfx::DagNodeDef* out = fn::gfx::dag_find("output");
if (out) {
fn::gfx::DagStep s;
s.id = "n_output";
s.name = out->name;
s.editor_pos_x = 500.0f;
if (!g_pipeline.empty()) s.source_ids[0] = g_pipeline.front().id;
g_pipeline.push_back(s);
}
}
}
@@ -181,6 +200,12 @@ static void render() {
}
ImGui::End();
// --- Functions palette (drag into DAG Pipeline) ---
if (ImGui::Begin("Functions")) {
fn::gfx::dag_palette();
}
ImGui::End();
// --- Generated GLSL window (DAG compiled output, read-only) ---
if (ImGui::Begin("Generated GLSL")) {
if (g_dag_glsl.empty()) {
+15
View File
@@ -225,6 +225,21 @@ static const std::vector<DagNodeDef>& build_catalog() {
v.push_back(std::move(n));
}
// ── Output (sink — drives fragColor) ─────────────────────────
{
DagNodeDef n;
n.name = "output";
n.label = "Output";
n.desc = "canvas DAG output";
n.kind = DagKind::Output;
n.num_inputs = 1;
n.param_names = {"", "", "", ""};
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
n.controls = {};
n.body_glsl = [](int) -> std::string { return ""; };
v.push_back(std::move(n));
}
return v;
}();
return catalog;
+32 -15
View File
@@ -22,11 +22,12 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
return out.str();
}
// Emit per-node functions with signatures based on num_inputs
// Emit per-node functions (skip Output: it's a sink, no body)
for (int i = 0; i < n; ++i) {
const DagStep& step = pipeline[static_cast<size_t>(i)];
const DagNodeDef* def = dag_find(step.name);
if (!def) continue;
if (def->kind == DagKind::Output) continue;
int ni = def->num_inputs;
out << "vec4 node_" << i << "(";
@@ -43,34 +44,30 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
out << "void main() {\n";
out << " vec2 uv = gl_FragCoord.xy / u_resolution;\n";
int last_valid_out = -1;
int output_idx = -1;
for (int i = 0; i < n; ++i) {
const DagStep& step = pipeline[static_cast<size_t>(i)];
const DagNodeDef* def = dag_find(step.name);
if (!def) {
if (i == 0) {
out << " vec4 out_" << i << " = vec4(0.0, 0.0, 0.0, 1.0);\n";
} else {
out << " vec4 out_" << i << " = out_" << (i - 1) << ";\n";
}
continue;
}
if (!def) continue;
if (def->kind == DagKind::Output) { output_idx = i; continue; }
int ni = def->num_inputs;
// Resolve each input slot
// For slot k: look for source_ids[k] in pipeline[0..i-1]; fallback = prev output
auto resolve = [&](int k) -> std::string {
const std::string& sid = step.source_ids[static_cast<size_t>(k)];
if (!sid.empty()) {
for (int j = 0; j < i; ++j) {
if (pipeline[static_cast<size_t>(j)].id == sid) {
const DagNodeDef* jdef = dag_find(pipeline[static_cast<size_t>(j)].name);
if (jdef && jdef->kind == DagKind::Output) continue; // Output has no out_j
return "out_" + std::to_string(j);
}
}
}
// fallback
if (i == 0) return "vec4(0.0, 0.0, 0.0, 1.0)";
return "out_" + std::to_string(i - 1);
if (last_valid_out < 0) return "vec4(0.0, 0.0, 0.0, 1.0)";
return "out_" + std::to_string(last_valid_out);
};
out << " vec4 out_" << i << " = node_" << i << "(";
@@ -80,9 +77,29 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
}
if (ni > 0) out << ", ";
out << "uv);\n";
last_valid_out = i;
}
// Resolve fragColor: if there's an Output node with a connection, use that; else fallback.
auto seed = [&]() { out << " fragColor = vec4(0.04, 0.04, 0.06, 1.0);\n"; };
if (output_idx >= 0) {
const std::string& sid = pipeline[static_cast<size_t>(output_idx)].source_ids[0];
int src = -1;
if (!sid.empty()) {
for (int j = 0; j < output_idx; ++j) {
if (pipeline[static_cast<size_t>(j)].id == sid) { src = j; break; }
}
}
if (src >= 0) out << " fragColor = out_" << src << ";\n";
else seed();
} else if (last_valid_out >= 0) {
out << " fragColor = out_" << last_valid_out << ";\n";
} else {
seed();
}
out << " fragColor = out_" << (n - 1) << ";\n";
out << "}\n";
return out.str();
+56 -77
View File
@@ -61,9 +61,10 @@ static int find_by_id(const std::vector<DagStep>& p, const std::string& id) {
static ImVec4 kind_color(DagKind kind) {
switch (kind) {
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
case DagKind::Output: return ImVec4(0.85f, 0.25f, 0.25f, 1.0f);
}
return ImVec4(1, 1, 1, 1);
}
@@ -120,70 +121,6 @@ static bool topo_sort(std::vector<DagStep>& pipeline) {
return true;
}
// Add node popup toolbar — rendered OUTSIDE ed::Begin
static bool draw_add_toolbar(std::vector<DagStep>& pipeline) {
bool changed = false;
int sz = static_cast<int>(pipeline.size());
if (ImGui::Button("+ Add Node")) {
ImGui::OpenPopup("ne_add_popup");
}
ImGui::SameLine();
ImGui::Text("%d/%d", sz, MAX_NODES);
ImGui::SameLine();
if (ImGui::Button("Clear") && !pipeline.empty()) {
ImGui::OpenPopup("ne_clear_confirm");
}
ImGui::SameLine();
if (ImGui::Button("Fit")) {
if (s_ctx) {
ed::SetCurrentEditor(s_ctx);
ed::NavigateToContent(0.0f);
ed::SetCurrentEditor(nullptr);
}
}
if (ImGui::BeginPopupModal("ne_clear_confirm", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Vaciar el pipeline?");
if (ImGui::Button("Si")) {
pipeline.clear();
changed = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); }
ImGui::EndPopup();
}
if (ImGui::BeginPopup("ne_add_popup")) {
const char* kind_names[] = { "Gen", "Op", "Blend" };
DagKind kinds[] = { DagKind::Gen, DagKind::Op, DagKind::Blend };
for (int k = 0; k < 3; ++k) {
if (ImGui::BeginMenu(kind_names[k])) {
for (const auto& def : dag_catalog()) {
if (def.kind != kinds[k]) continue;
if (ImGui::MenuItem(def.label.c_str())) {
if (sz < MAX_NODES) {
DagStep step;
step.id = "n" + std::to_string(s_next_uid);
step.name = def.name;
step.params = def.param_defaults;
step.editor_uid = s_next_uid++;
// stagger position so nodes don't stack
step.editor_pos_x = 50.0f + static_cast<float>(sz) * 220.0f;
step.editor_pos_y = 100.0f;
pipeline.push_back(step);
changed = true;
}
}
}
ImGui::EndMenu();
}
}
ImGui::EndPopup();
}
return changed;
}
bool dag_node_editor(std::vector<DagStep>& pipeline) {
bool changed = false;
@@ -201,12 +138,47 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
s_ctx = ed::CreateEditor(&cfg);
}
changed |= draw_add_toolbar(pipeline);
ImGui::Separator();
// Palette drop zone: accept dropped node types (from the Functions panel)
// and queue an add at the mouse canvas position (resolved after ed::Begin).
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;
}
ImGui::EndDragDropTarget();
}
ImGui::SetCursorScreenPos(origin);
ed::SetCurrentEditor(s_ctx);
ed::Begin("dag_editor", ImVec2(0.0f, 0.0f));
if (s_pending_add) {
const DagNodeDef* def = dag_find(s_pending_add_name);
if (def && static_cast<int>(pipeline.size()) < MAX_NODES) {
uint32_t uid = s_next_uid++;
DagStep step;
step.id = "n" + std::to_string(uid);
step.name = def->name;
step.params = def->param_defaults;
step.editor_uid = uid;
ImVec2 canvas_pos = ed::ScreenToCanvas(s_pending_add_pos);
step.editor_pos_x = canvas_pos.x;
step.editor_pos_y = canvas_pos.y;
pipeline.push_back(step);
changed = true;
}
s_pending_add = false;
}
// ── Draw nodes ───────────────────────────────────────────────────────────
for (int i = 0; i < static_cast<int>(pipeline.size()); ++i) {
DagStep& step = pipeline[static_cast<size_t>(i)];
@@ -279,11 +251,13 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
}
ImGui::PopID();
// Output pin
ImGui::Dummy(ImVec2(0, 2));
ed::BeginPin(ed::PinId(output_pin_id(step.editor_uid)), ed::PinKind::Output);
ImGui::Text("out ->");
ed::EndPin();
// Output pin (skip for the terminal Output node — it has no output)
if (def->kind != DagKind::Output) {
ImGui::Dummy(ImVec2(0, 2));
ed::BeginPin(ed::PinId(output_pin_id(step.editor_uid)), ed::PinKind::Output);
ImGui::Text("out ->");
ed::EndPin();
}
ed::EndNode();
}
@@ -369,12 +343,17 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
ed::NodeId del_nid;
while (ed::QueryDeletedNode(&del_nid)) {
uint32_t uid = static_cast<uint32_t>(del_nid.Get());
int idx = find_by_uid(pipeline, uid);
// Refuse to delete the Output node (it's the sink)
const DagNodeDef* ddef = (idx >= 0) ? dag_find(pipeline[static_cast<size_t>(idx)].name) : nullptr;
if (ddef && ddef->kind == DagKind::Output) {
ed::RejectDeletedItem();
continue;
}
if (ed::AcceptDeletedItem()) {
uint32_t uid = static_cast<uint32_t>(del_nid.Get());
int idx = find_by_uid(pipeline, uid);
if (idx >= 0) {
const std::string& del_step_id = pipeline[static_cast<size_t>(idx)].id;
// Clear any source_ids pointing to the deleted node
for (auto& step : pipeline) {
for (auto& sid : step.source_ids) {
if (sid == del_step_id) sid.clear();
+54
View File
@@ -0,0 +1,54 @@
#include "gfx/dag_palette.h"
#include "gfx/dag_catalog.h"
#include "imgui.h"
namespace fn::gfx {
static ImVec4 kind_tint(DagKind kind) {
switch (kind) {
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
case DagKind::Output: return ImVec4(0.85f, 0.25f, 0.25f, 1.0f);
}
return ImVec4(1, 1, 1, 1);
}
static void draw_group(const char* header, DagKind kind) {
if (!ImGui::CollapsingHeader(header, ImGuiTreeNodeFlags_DefaultOpen)) return;
ImVec4 col = kind_tint(kind);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(col.x * 0.35f, col.y * 0.35f, col.z * 0.35f, 0.9f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(col.x * 0.55f, col.y * 0.55f, col.z * 0.55f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(col.x * 0.75f, col.y * 0.75f, col.z * 0.75f, 1.0f));
for (const auto& def : dag_catalog()) {
if (def.kind != kind) continue;
ImGui::PushID(def.name.c_str());
ImGui::Button(def.label.c_str(), ImVec2(-1, 0));
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
ImGui::SetDragDropPayload("DAG_NODE_TYPE", def.name.c_str(), def.name.size());
ImGui::Text("+ %s", def.label.c_str());
if (!def.desc.empty()) {
ImGui::TextDisabled("%s", def.desc.c_str());
}
ImGui::EndDragDropSource();
}
if (ImGui::IsItemHovered() && !def.desc.empty()) {
ImGui::SetTooltip("%s", def.desc.c_str());
}
ImGui::PopID();
}
ImGui::PopStyleColor(3);
}
void dag_palette() {
ImGui::TextDisabled("Drag into the DAG Pipeline canvas.");
ImGui::Spacing();
draw_group("Generators", DagKind::Gen);
draw_group("Operators", DagKind::Op);
draw_group("Blends", DagKind::Blend);
}
} // namespace fn::gfx
+11
View File
@@ -0,0 +1,11 @@
#pragma once
namespace fn::gfx {
// Renderiza una paleta del catalogo de nodos agrupada por kind (Gen / Op / Blend).
// Cada item es un drag source con payload "DAG_NODE_TYPE" (nombre del nodo).
// El Output no aparece en la paleta (es un sink fijo en el grafo).
// Llamar dentro de un ImGui::Begin/End.
void dag_palette();
} // namespace fn::gfx
+43
View File
@@ -0,0 +1,43 @@
---
name: dag_palette
kind: component
lang: cpp
domain: gfx
version: "1.0.0"
purity: impure
signature: "void dag_palette()"
description: "Paleta de nodos del catalogo, agrupada por kind (Gen/Op/Blend). Cada entrada es un drag source con payload DAG_NODE_TYPE que contiene el name del nodo. El node editor recibe el drop y anade un nuevo DagStep en la posicion del mouse."
tags: [imgui, dag, palette, drag-and-drop, gfx, component]
uses_functions: [dag_catalog_cpp_gfx]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [imgui, dag_catalog]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/gfx/dag_palette.cpp"
framework: imgui
params: []
output: "Dibuja los widgets de paleta dentro del ImGui::Begin/End del caller. No tiene estado propio."
---
# dag_palette
Paleta draggable de nodos del catalogo. Sustituye el boton "+ Add Node" del panel del DAG: en lugar de pulsar y navegar un submenu, el usuario arrastra el nodo desde la paleta al canvas del editor.
## Uso
```cpp
if (ImGui::Begin("Functions")) {
fn::gfx::dag_palette();
}
ImGui::End();
```
El node editor debe aceptar payloads con identificador `"DAG_NODE_TYPE"` y crear el `DagStep` correspondiente en la posicion del cursor.
## Drop zone del editor
El `dag_node_editor` usa un `ImGui::InvisibleButton` del tamano del canvas como drop target antes de `ed::Begin()`, y resetea el cursor para que el editor se dibuje encima. Al detectar drop, convierte la posicion screen a canvas con `ed::ScreenToCanvas()` y encola un add que se aplica tras abrir el contexto del editor.
+8 -6
View File
@@ -16,18 +16,20 @@ static std::string make_id() {
static ImVec4 kind_color(DagKind kind) {
switch (kind) {
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
case DagKind::Output: return ImVec4(0.85f, 0.25f, 0.25f, 1.0f);
}
return ImVec4(1, 1, 1, 1);
}
static const char* kind_label(DagKind kind) {
switch (kind) {
case DagKind::Gen: return "Gen";
case DagKind::Op: return "Op";
case DagKind::Blend: return "Blend";
case DagKind::Gen: return "Gen";
case DagKind::Op: return "Op";
case DagKind::Blend: return "Blend";
case DagKind::Output: return "Output";
}
return "?";
}
+1 -1
View File
@@ -6,7 +6,7 @@
namespace fn::gfx {
enum class DagKind { Gen, Op, Blend };
enum class DagKind { Gen, Op, Blend, Output };
struct DagControl {
enum class Kind { Slider, XY, Color };