# BehaviorGraph (Groups) Implementation Plan - Container Architecture ## Overview This document outlines the implementation plan for adding **BehaviorGraph** groups using a **container-based architecture**. Groups are implemented as nested containers within root containers. **Core Principle:** All nodes, links, blocks exist within **containers**. Containers manage, run, and render their contents. The app supports **multiple root containers** (one per loaded file), and each root can contain groups (nested containers). ## Architecture ### Container Hierarchy ``` App ├── RootContainer1 (loaded from file1.json) │ ├── Node A (top-level) │ ├── Node B (top-level) │ └── BehaviorGraph Container (Group 1) │ ├── Node C (moved here, owned by Group 1) │ ├── Node D (moved here, owned by Group 1) │ └── BehaviorGraph Container (Nested Group) │ └── Node E (owned by nested group) │ ├── RootContainer2 (loaded from file2.json) │ └── Node F │ └── RootContainer3 (loaded from file3.json) └── BehaviorGraph Container (Group 2) └── Node G ``` ### Key Concepts 1. **Container Base Class**: Base class for all containers (root and groups) 2. **Root Container**: One per file/graph, contains top-level nodes and groups 3. **BehaviorGraph Container**: Groups that inherit from Container + ParameterizedBlock 4. **Single Ownership**: Each node/link exists in exactly ONE container 5. **No Duplication**: Nodes moved between containers, not copied ## Data Structures ### BehaviorGraph Container **File:** `examples/blueprints-example/containers/behavior_graph.h` ```cpp class BehaviorGraph : public Container, public ParameterizedBlock { public: BehaviorGraph(int id, const char* name); // Container interface void AddNode(Node* node) override; void RemoveNode(Node* node) override; bool CanAddLink(Link* link) const override; // Validate group boundaries // Block interface (from ParameterizedBlock) void Build(Node& node, App* app) override; void Render(Node& node, App* app, Pin* newLinkPin) override; int Run(Node& node, App* app) override; // Propagate to inner blocks // Container execution (called from Run(Node&, App*)) void Run(App* app) override; // Only if active, sets Running flag during execution // Group-specific enum class GroupDisplayMode { Expanded, Collapsed }; GroupDisplayMode GetDisplayMode() const { return m_DisplayMode; } void SetDisplayMode(GroupDisplayMode mode) { m_DisplayMode = mode; } // Pin management void AddInputFlowPin(App* app); void AddOutputFlowPin(App* app); void AddInputParamPin(App* app, PinType type); void AddOutputParamPin(App* app, PinType type); void RemovePin(ed::PinId pinId, App* app); // Pin mapping (virtual connections) void MapGroupPinToInnerPin(ed::PinId groupPin, ed::PinId innerPin); void UnmapGroupPin(ed::PinId groupPin); private: GroupDisplayMode m_DisplayMode = GroupDisplayMode::Expanded; ImVec2 m_GroupSize; // For ed::Group() size }; ``` ### GroupPin Structure ```cpp struct GroupPin { ed::PinId ID; std::string Name; PinType Type; PinKind Kind; // Input or Output bool IsFlow; // true = flow pin, false = parameter pin }; ``` ## Implementation Phases ### Phase 2: BehaviorGraph Container (Groups) #### Task 2.1: Create BehaviorGraph Container **Files:** `containers/behavior_graph.h`, `containers/behavior_graph.cpp` - Define `BehaviorGraph` class inheriting from `Container` and `ParameterizedBlock` - Add `GroupDisplayMode` enum - Implement basic `Build()`, `Render()`, `Run()` stubs - Add pin management methods (AddInputFlowPin, etc.) - Store pins in node's Inputs/Outputs vectors (for ParameterizedBlock compatibility) **Dependencies:** Task 1.1, `block.h` #### Task 2.2: Implement Variable Pin System **Files:** `containers/behavior_graph.cpp` - Implement `AddInputFlowPin()`, `AddOutputFlowPin()` - Implement `AddInputParamPin()`, `AddOutputParamPin()` - Store pins in container's pin vectors AND node's Inputs/Outputs vectors - Pin editing similar to `parameter_node.h` (edit name, type, value) - Update `Build()` to register pins with node editor **Dependencies:** Task 2.1 #### Task 2.3: Implement Pin Removal **Files:** `containers/behavior_graph.cpp` - Implement `RemovePin()` method - Handle link cleanup when pin removed - Remove from both container vectors and node vectors - Update virtual connection mappings **Dependencies:** Task 2.2 #### Task 2.4: Context Menu for Pin Management **Files:** `containers/behavior_graph.cpp` - Extend `OnMenu()` (from ParameterizedBlock) to show: - "Add Input Flow Pin" - "Add Output Flow Pin" - "Add Input Parameter Pin" (with type selection) - "Add Output Parameter Pin" (with type selection) - "Remove Pin" submenu (list all pins) - Handle pin creation/removal from menu **Dependencies:** Task 2.2, 2.3 ### Phase 3: Display Modes #### Task 3.1: Implement Expanded Mode Rendering **Files:** `containers/behavior_graph.cpp` - Render group container with title bar - Use `ed::Group(m_GroupSize)` to define group bounds - Render flow pins on left/right edges (using `FlowPinRenderer`) - Render parameter pins on top/bottom edges (using `ParameterPinRenderer`) - Render inner nodes (from `m_Nodes`) inside group bounds - Allow user interaction inside group **Reference:** `ref/sample.cpp` lines 1397-1421 **Dependencies:** Task 2.1 #### Task 3.2: Implement Collapsed Mode Rendering **Files:** `containers/behavior_graph.cpp` - Render as compact node (similar to block nodes) - Show group name/title - Render pins only (no inner nodes visible) - Use `FlowPinRenderer` and `ParameterPinRenderer` for compact display - Inner nodes not visible but remain in container (positions preserved) **Dependencies:** Task 3.1 #### Task 3.3: Mode Toggle **Files:** `containers/behavior_graph.cpp` - Add mode toggle to context menu - Add keyboard shortcut (optional: 'T' key) - Store mode in container state - Persist in serialization **Dependencies:** Task 3.1, 3.2 #### Task 3.4: Implement Container Flags **Files:** `containers/container.cpp`, `containers/behavior_graph.cpp` - Implement flag management (hidden, active/disabled, running, error) - **Hidden**: Container not rendered (but still exists) - **Active/Disabled**: Disabled containers don't execute (skip Run()) - **Running**: Set during execution, cleared after (visual feedback) - **Error**: Set when container encounters error (visual feedback) - Update Render() to check IsHidden() - Update Run() to check IsActive() / IsDisabled() - Visual indicators for running/error states (e.g., red border for error, pulsing for running) **Dependencies:** Task 1.1 ### Phase 4: Group Creation #### Task 4.1: Implement 'g' Key Shortcut for Grouping **Files:** `app-render.cpp`, `app-logic.cpp` - In `HandleKeyboardShortcuts()`, detect 'g' key press - Get selected nodes from active root container - Calculate bounding box of selected nodes - Create new `BehaviorGraph` container - **Move selected nodes** from active root container → group container: - Remove nodes from root container's `m_Nodes` - Add nodes to group container's `m_Nodes` - **Break external links** (links from/to selected nodes to/from external nodes) - Move internal links (between selected nodes) to group container - Add group container to root container's `m_Children` - Set group size to encompass selected nodes (nodes keep absolute positions) **Dependencies:** Task 2.1, 1.3 #### Task 4.2: Link Breaking Logic **Files:** `app-logic.cpp` - When creating group, scan all links - For links connecting selected nodes to external nodes: - Remove link from root container - Store for potential group pin creation later - For links between selected nodes: - Move link to group container - Clean up link references **Dependencies:** Task 4.1 ### Phase 5: Pin Connections & Runtime Execution #### Task 5.0: Implement Container Flags (if not done in Phase 3) **Files:** `containers/container.cpp` - Implement flag getters/setters - Update Render() to respect IsHidden() - Update Run() to respect IsActive() / IsDisabled() - Visual feedback for Running and Error states **Dependencies:** Task 1.1 #### Task 5.1: Implement Virtual Pin Connections **Files:** `containers/behavior_graph.cpp`, `app-logic.cpp` - **Group pins act as proxies** - users connect external nodes to group pins - **Virtual connections** stored in maps (NOT actual Link objects): - `m_GroupToInnerPins`: Maps group input pin → inner node input pin - `m_InnerToGroupPins`: Maps inner node output pin → group output pin - **Link validation:** Prevent inner nodes from connecting directly to external nodes - Override/extend `CanCreateLink()` in App to reject: inner pin ↔ external pin - Only allow: group pin ↔ external pin, group pin ↔ inner pin (via mapping), inner pin ↔ inner pin - **Pin mapping UI:** When user connects group pin to inner pin (or vice versa), store in mapping **Dependencies:** Task 2.2, 4.1 #### Task 5.2: Implement Run() Method for Flow Execution **Files:** `containers/behavior_graph.cpp` - Implement `Run(Node&, App*)` method (required for ParameterizedBlock): **Step 1: Parameter propagation FIRST** - Copy parameter values from group input pins to connected inner pins - Use existing `GetInputParamValue*` helpers on group pins - Use existing `SetOutputParamValue*` helpers (but apply to inner node pins) - Use `m_GroupToInnerPins` mapping to find target inner pins **Step 2: Flow activation** - When group flow input is activated (via `IsInputActive()`): - Find inner node connected via `m_GroupToInnerPins` mapping - Activate the corresponding inner node's flow input (by index) - Multiple activated inputs propagate independently **Step 3: Output collection** - After inner blocks execute (may be in next iteration), check inner node output states - Iterate through `m_Nodes` (inner nodes) - For each inner node output that's active, find mapped group output pin (via `m_InnerToGroupPins`) - Activate corresponding group output pin - Call `Run(App*)` from `Run(Node&, App*)` to execute inner blocks **Execution Flow:** 1. Iteration N: Group input activated → `Run()` copies params, activates inner inputs → returns 2. Iteration N+1: Runtime detects inner nodes have activated inputs → executes inner blocks 3. Iteration N+2: Group `Run()` called again → collects inner outputs → activates group outputs **Dependencies:** Task 5.1 #### Task 5.3: Update Runtime to Support Containers **Files:** `app-runtime.cpp`, `containers/root_container.cpp` - Update `ExecuteRuntimeStep()` to call `m_ActiveRootContainer->Run(app)` (only if active) - Update `RootContainer::Run()` to: - Check `IsActive()` / `IsDisabled()` - skip if disabled - Set `SetRunning(true)` at start, `SetRunning(false)` at end - Iterate through its nodes, find blocks with activated inputs - Execute blocks - For BehaviorGraph containers (in `m_Children`), call their `Run()` recursively (only if active) - Container execution propagates through hierarchy naturally - Respect disabled flag - disabled containers don't execute **Dependencies:** Task 5.2 ### Phase 6: Serialization #### Task 6.1: Save Container Data **Files:** `app-logic.cpp`, `containers/container.cpp`, `containers/behavior_graph.cpp` - Extend `SaveGraph()` to save active root container: - Container saves its nodes - Container saves its links - Container saves its child containers (recursively) - BehaviorGraph saves pins, virtual mappings, display mode - Each root container saves to its own file **Dependencies:** Task 4.1, 5.1 #### Task 6.2: Load Container Data **Files:** `app-logic.cpp`, `containers/container.cpp`, `containers/behavior_graph.cpp` - **Loading order:** Nodes first, then links, then containers - Extend `LoadGraph()` to: 1. Load nodes first (into temporary storage) 2. Load links (reference nodes by ID) 3. Load containers (root container first, then nested): - Create container - Assign nodes to container (by ID lookup) - Assign links to container - Restore pins, mappings, display mode 4. Establish virtual connections **Dependencies:** Task 6.1 ### Phase 7: Edge Cases & Polish #### Task 7.1: Handle Edge Cases **Files:** `containers/behavior_graph.cpp`, `app-logic.cpp` - **Group nesting is allowed** - groups can contain other groups - **Node deletion when inside group:** - When node is deleted, remove from its container's `m_Nodes` - Clean up any virtual pin mappings involving this node - **Group deletion:** - Remove group container from parent's `m_Children` - Move inner nodes back to parent container (or delete) - Break all links connected to group pins - Clean up virtual pin mappings - **Link deletion:** - When link involving group pin is deleted, remove from virtual mappings - When link between inner nodes is deleted, no special handling needed - **Root container deletion:** - Remove from `m_RootContainers` - Delete all nodes/links/containers it owns **Dependencies:** All previous tasks #### Task 7.2: Visual Polish **Files:** `containers/behavior_graph.cpp` - Improve styling for collapsed vs expanded modes - Add hover effects - Add selection highlighting - Improve pin layout/spacing - Handle link rendering across group boundaries **Dependencies:** Task 3.1, 3.2 ## File Structure ``` examples/blueprints-example/ ├── containers/ │ ├── container.h (NEW - base Container class) │ ├── container.cpp (NEW) │ ├── root_container.h (NEW) │ ├── root_container.cpp (NEW) │ ├── behavior_graph.h (NEW - BehaviorGraph container) │ └── behavior_graph.cpp (NEW) ├── blocks/ │ └── (existing files...) └── (existing files - updated) ``` ## Key Implementation Details ### Node Movement (Not Duplication) When creating a group: ```cpp // Before: Nodes in root rootContainer->m_Nodes = [node1, node2, node3] // After grouping node1, node2: rootContainer->m_Nodes = [node3] rootContainer->m_Children = [behaviorGraph1] behaviorGraph1->m_Nodes = [node1, node2] // MOVED, not duplicated ``` ### Link Validation ```cpp bool App::CanCreateLink(Pin* a, Pin* b) { // ... existing validation ... // Check if pins are in different root containers (reject) Container* containerA = FindContainerForNode(a->Node->ID); Container* containerB = FindContainerForNode(b->Node->ID); if (GetRootContainer(containerA) != GetRootContainer(containerB)) return false; // Cross-root connections not allowed // Check group boundary violations if (IsInnerPin(a) && IsExternalPin(b)) return false; // Inner pins can't connect to external pins if (IsExternalPin(a) && IsInnerPin(b)) return false; // ... rest of validation ... } ``` ### Container Rendering ```cpp void RootContainer::Render(App* app, Pin* newLinkPin) { // Only render if not hidden if (IsHidden()) return; // Render this container's nodes for (auto* node : m_Nodes) { RenderNode(node, app, newLinkPin); } // Render links between nodes in this container for (auto* link : m_Links) { RenderLink(link, app); } // Render child containers (groups) - only if not hidden for (auto* child : m_Children) { if (child->IsHidden()) continue; if (auto* group = dynamic_cast(child)) { if (group->GetDisplayMode() == GroupDisplayMode::Expanded) { // Render expanded group with ed::Group() group->Render(app, newLinkPin); } else { // Render as collapsed node RenderCollapsedGroup(group, app, newLinkPin); } } } } ``` ### Container Execution ```cpp void RootContainer::Run(App* app) { // Only execute if active (not disabled) if (IsDisabled()) return; // Set running flag SetRunning(true); // Execute blocks in this container for (auto* node : m_Nodes) { if (node->IsBlockBased() && node->BlockInstance) { // Check if input activated, execute, etc. (existing logic) } } // Execute child containers (groups) - only if active for (auto* child : m_Children) { if (child->IsDisabled()) continue; if (auto* group = dynamic_cast(child)) { // Group's Run() is called by runtime system when group input is activated // But we can also call it here if needed for recursive execution } } // Clear running flag SetRunning(false); } ``` ## Final TODO List ### Phase 2: BehaviorGraph Container 4. **Create BehaviorGraph container** (`containers/behavior_graph.h/cpp`) - Inherit from Container and ParameterizedBlock - Add GroupDisplayMode - Implement Build(), Render(), Run() stubs 5. **Implement variable pin management** (`behavior_graph.cpp`) - AddInputFlowPin, AddOutputFlowPin - AddInputParamPin, AddOutputParamPin - RemovePin - Pin editing (name, type, value) 6. **Implement context menu for pins** (`behavior_graph.cpp`) - Add/remove pins via menu - Similar to parameter_node.h patterns ### Phase 3: Display Modes 7. **Implement expanded mode rendering** (`behavior_graph.cpp`) - Render with ed::Group() - Render pins and inner nodes 8. **Implement collapsed mode rendering** (`behavior_graph.cpp`) - Compact node view with pins only 9. **Implement mode toggle** (`behavior_graph.cpp`) - Context menu and keyboard shortcut 10. **Implement container flags** (`container.cpp`) - Hidden: Container not rendered - Active/Disabled: Disabled containers don't execute - Running: Visual feedback during execution - Error: Visual feedback for errors - Update Render() and Run() to respect flags ### Phase 4: Group Creation 10. **Implement 'g' key shortcut** (`app-render.cpp`) - Detect 'g' key press - Move selected nodes from root → group container - Break external links - Move internal links ### Phase 5: Pin Connections & Runtime 11. **Implement virtual pin connections** (`behavior_graph.cpp`) - Store mappings (group pin → inner pin) - Link validation (prevent inner ↔ external) - Pin mapping UI 12. **Implement Run() method** (`behavior_graph.cpp`) - Parameter propagation - Flow activation - Output collection 13. **Update runtime system** (`app-runtime.cpp`, `root_container.cpp`) - Container-based execution - Recursive container running ### Phase 6: Serialization 14. **Implement container serialization** (`container.cpp`, `behavior_graph.cpp`) - Save container hierarchy - Load in correct order (nodes → links → containers) ### Phase 7: Polish 16. **Handle edge cases** (`behavior_graph.cpp`, `app-logic.cpp`) - Node deletion - Group deletion - Link deletion - Root container deletion 17. **Visual polish** (`behavior_graph.cpp`) - Visual indicators for flags (running, error, disabled, hidden) - Styling, hover effects, selection ## Notes - **Container-based architecture** eliminates duplication - nodes owned by exactly one container - **Multiple root containers** - app supports multiple files, each is a root container - **BehaviorGraph is a container** - inherits Container + ParameterizedBlock - **No m_ParentGroup field** - container owns node, find via `FindContainerForNode()` - **Group pins act as proxies** - virtual connections stored in maps - **Link validation prevents** inner pins connecting directly to external pins - **Group nesting allowed** - containers within containers - **Serialization per file** - each root container saves to its own file - Reference `app-runtime.cpp` for execution patterns - Reference `parameter_node.h` for pin editing patterns - Reference `ref/sample.cpp` for group rendering - Reference `block.cpp` for pin rendering patterns ## Migration Strategy 1. **Start with container foundation** - Move existing code to use root container 2. **Then add BehaviorGraph** - Groups as nested containers 3. **Incremental migration** - Can maintain flattened view temporarily for compatibility This architecture provides clean separation, no duplication, and natural multi-file support!