deargui-vpl/docs/groups.md

606 lines
21 KiB
Markdown

# 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<BehaviorGraph*>(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<BehaviorGraph*>(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!