Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f2d1c24
Add execution pin infrastructure to Node base class
Konstantysz Nov 21, 2025
3ac6b27
Implement execution flow orchestration in NodeEditor
Konstantysz Nov 21, 2025
dcf0fda
Add execution pin types and visual constants
Konstantysz Nov 21, 2025
6767131
Render execution pins as arrow shapes in horizontal row
Konstantysz Nov 21, 2025
e458f71
Integrate execution pins into all vision processing nodes
Konstantysz Nov 21, 2025
99e8050
Remove Unreal Engine and Blueprint references from comments
Konstantysz Nov 21, 2025
aed1476
Remove unused variable and add override keywords
Konstantysz Nov 21, 2025
d038740
Remove dead code
Konstantysz Nov 21, 2025
52ca67a
Fix execution flow validation and entry point detection
Konstantysz Nov 21, 2025
a3117ea
Enforce 1:1 execution pin connections at core layer
Konstantysz Nov 21, 2025
6670292
Add thread safety lock to BuildExecutionPlan
Konstantysz Nov 21, 2025
a18894b
Optimize execution pin lookup from O(n) to O(1)
Konstantysz Nov 21, 2025
6e014c9
Add defensive cycle detection to execution flow
Konstantysz Nov 21, 2025
83ff1cb
Add comprehensive test coverage for execution flow
Konstantysz Nov 21, 2025
8d52187
Add execution flow architecture documentation
Konstantysz Nov 21, 2025
ad88873
Refactor pin separation logic into helper function
Konstantysz Nov 21, 2025
35ded7a
Optimize execution plan memory and clarify lookahead
Konstantysz Nov 21, 2025
aab4ec0
Update CLAUDE.md with execution flow architecture details
Konstantysz Nov 21, 2025
d800c40
Fix github workflow
Konstantysz Nov 21, 2025
1c6b517
Fix tests
Konstantysz Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,24 @@ Thread safety: `NodeEditor` uses `std::recursive_mutex graphMutex` for all graph
- OpenCV `cv::Mat` uses reference counting (zero-copy in slots)

### Connection Rules

- Output pin → Input pin only (enforced by `ConnectionManager::IsConnectionValid()`)
- Input pins limited to ONE connection (enforced by `RemoveConnectionToInput()`)
- Output pins can have MULTIPLE connections
- Type checking: Pin data types must match (or be compatible)
- **Execution Flow Rules**:
- **1:1 Enforcement**: Execution pins (white arrows) strictly enforce 1:1 connections (one output to one input) at the core layer (`NodeEditor::AddConnection`).
- **Cycle Prevention**: Execution flow must be acyclic. `BuildExecutionPlan` performs cycle detection.

### Execution Flow Architecture
- **Hybrid Model**: Separates control flow (execution pins) from data flow (data pins).
- **Execution Pins**: White arrows (`PinType::Execution`). Define order of operations.
- **Data Pins**: Colored circles (`PinType::Data`). Define data dependencies.
- **Execution Plan**:
- `BuildExecutionPlan()` compiles the graph into a linear `std::vector<ExecutionStep>`.
- Uses topological sort on execution connections.
- Precomputes incoming data connection indices for O(1) access during execution.
- **Lookahead Advancement**: `ExecutionFrame` advances `nextInstructionIndex` *before* executing the current node.
- **Pin Separation**: Use `Widgets::SeparatePinsByType()` helper to separate execution/data pins in UI code.

### Serialization

Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,29 @@ VisionCraft is built using a domain-driven architecture with clear separation of
- **UI**: Presentation layer (rendering, canvas, widgets, application layers)
- **App**: Application composition and entry point

## ⚡ Execution Flow Architecture

VisionCraft uses a hybrid execution model that separates **control flow** from **data flow**:

### 1. Execution Flow (White Arrows)
- Defines the **order of operations** (what executes when).
- Represented by white arrow pins and connections.
- Enforces a strict **1:1 connection rule** (one output to one input) to prevent ambiguity.
- Supports branching and linear sequences.
- **Entry Points**: Nodes with execution outputs but no execution inputs (e.g., `Image Input`) start the flow.

### 2. Data Flow (Colored Wires)
- Defines **data dependencies** (what data goes where).
- Represented by colored pins (Green=Image, Cyan=Int, etc.).
- Allows **1:N connections** (one output can feed multiple inputs).
- Data is passed automatically before a node executes.

### 3. Execution Model
- **Compilation Phase**: The graph is compiled into a linear `ExecutionPlan` via topological sort of the execution flow.
- **Optimization**: Connection lookups are precomputed into indices for O(1) access during execution.
- **Lookahead Advancement**: The instruction pointer advances *before* node execution, enabling robust error handling and cancellation.
- **Thread Safety**: The execution engine is fully thread-safe, supporting async background execution with cancellation and progress reporting.

## 📋 Prerequisites

### 🌐 Common Requirements
Expand Down
30 changes: 30 additions & 0 deletions src/Nodes/Core/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,36 @@ namespace VisionCraft::Nodes
return outputSlots.find(slotName) != outputSlots.end();
}

void Node::CreateExecutionInputPin(const std::string &pinName)
{
executionInputPins.insert(pinName);
}

void Node::CreateExecutionOutputPin(const std::string &pinName)
{
executionOutputPins.insert(pinName);
}

bool Node::HasExecutionInputPin(const std::string &pinName) const
{
return executionInputPins.count(pinName) > 0;
}

bool Node::HasExecutionOutputPin(const std::string &pinName) const
{
return executionOutputPins.count(pinName) > 0;
}

std::vector<std::string> Node::GetExecutionInputPins() const
{
return std::vector<std::string>(executionInputPins.begin(), executionInputPins.end());
}

std::vector<std::string> Node::GetExecutionOutputPins() const
{
return std::vector<std::string>(executionOutputPins.begin(), executionOutputPins.end());
}

template<typename T> Slot &Node::CreateInputSlot(const std::string &slotName, T defaultValue)
{
NodeData nodeData = std::move(defaultValue);
Expand Down
59 changes: 55 additions & 4 deletions src/Nodes/Core/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "Nodes/Core/Slot.h"
Expand Down Expand Up @@ -171,11 +172,61 @@ namespace VisionCraft::Nodes
*/
[[nodiscard]] bool HasOutputSlot(const std::string &slotName) const;

/**
* @brief Creates execution input pin (white wire input).
*
* Execution pins control the flow of execution through the graph. An execution
* input pin means "this node can be executed from another node". Multiple nodes
* can have execution connections TO this pin, but typically only one is active.
*
* @param pinName Name of the execution pin (e.g., "Execute", "In")
*/
void CreateExecutionInputPin(const std::string &pinName);

/**
* @brief Creates execution output pin (white wire output).
*
* Execution pins control the flow of execution through the graph. An execution
* output pin means "this node can trigger execution of other nodes". This pin
* can connect to multiple downstream nodes' execution inputs.
*
* @param pinName Name of the execution pin (e.g., "Then", "Out", "True", "False")
*/
void CreateExecutionOutputPin(const std::string &pinName);

/**
* @brief Checks if node has execution input pin.
* @param pinName Pin name
* @return True if exists
*/
[[nodiscard]] bool HasExecutionInputPin(const std::string &pinName) const;

/**
* @brief Checks if node has execution output pin.
* @param pinName Pin name
* @return True if exists
*/
[[nodiscard]] bool HasExecutionOutputPin(const std::string &pinName) const;

/**
* @brief Returns all execution input pin names.
* @return Vector of pin names
*/
[[nodiscard]] std::vector<std::string> GetExecutionInputPins() const;

/**
* @brief Returns all execution output pin names.
* @return Vector of pin names
*/
[[nodiscard]] std::vector<std::string> GetExecutionOutputPins() const;

protected:
std::string name; ///< Name of the node
NodeId id; ///< Unique identifier of the node
std::unordered_map<std::string, Slot> inputSlots; ///< Input data slots
std::unordered_map<std::string, Slot> outputSlots; ///< Output data slots
std::string name; ///< Name of the node
NodeId id; ///< Unique identifier of the node
std::unordered_map<std::string, Slot> inputSlots; ///< Input data slots
std::unordered_map<std::string, Slot> outputSlots; ///< Output data slots
std::unordered_set<std::string> executionInputPins; ///< Execution input pins (O(1) lookup)
std::unordered_set<std::string> executionOutputPins; ///< Execution output pins (O(1) lookup)
};

/**
Expand Down
Loading
Loading