deargui-vpl/docs/groups-container-api.md

11 KiB

Container API Design

Overview

Containers manage nodes, links, and nested containers. The app supports multiple root containers (one per file), and each root can contain groups (nested containers).

Container State Flags:

  • Hidden: Container not visible (but still exists)
  • Active/Disabled: Container enabled or disabled (disabled containers don't execute)
  • Running: Container is currently executing
  • Error: Container has encountered an error

Container Base Class

class Container
{
public:
    // Identification
    ed::NodeId GetID() const { return m_ID; }
    const std::string& GetName() const { return m_Name; }
    void SetName(const std::string& name) { m_Name = name; }
    
    // Container flags/state
    bool IsHidden() const { return m_Flags & ContainerFlag_Hidden; }
    void SetHidden(bool hidden);
    bool IsActive() const { return !(m_Flags & ContainerFlag_Disabled); }
    void SetActive(bool active) { SetDisabled(!active); }
    bool IsDisabled() const { return m_Flags & ContainerFlag_Disabled; }
    void SetDisabled(bool disabled);
    bool IsRunning() const { return m_Flags & ContainerFlag_Running; }
    void SetRunning(bool running);
    bool HasError() const { return m_Flags & ContainerFlag_Error; }
    void SetError(bool error);
    
    // Ownership
    std::vector<Node*> m_Nodes;           // Nodes in this container
    std::vector<Link*> m_Links;           // Links in this container (between nodes in this container)
    std::vector<Container*> m_Children;   // Nested containers (groups)
    Container* m_Parent;                   // Parent container (nullptr for root containers)
    
    // Interface pins (for groups only - root containers have no interface)
    std::vector<GroupPin> m_InputFlowPins;
    std::vector<GroupPin> m_OutputFlowPins;
    std::vector<GroupPin> m_InputParamPins;
    std::vector<GroupPin> m_OutputParamPins;
    
    // Virtual connections (group pin → inner node pin)
    std::map<ed::PinId, ed::PinId> m_GroupToInnerPins;   // Group input pin → Inner input pin
    std::map<ed::PinId, ed::PinId> m_InnerToGroupPins;   // Inner output pin → Group output pin
    
    // Management
    virtual void AddNode(Node* node);
    virtual void RemoveNode(Node* node);
    virtual void AddLink(Link* link);
    virtual void RemoveLink(Link* link);
    virtual void AddChildContainer(Container* container);
    virtual void RemoveChildContainer(Container* container);
    
    // Query
    Node* FindNode(NodeId nodeId);
    Link* FindLink(LinkId linkId);
    Pin* FindPin(PinId pinId);
    Container* FindContainer(NodeId nodeId);  // Recursive search (this + children)
    bool ContainsNode(NodeId nodeId) const;
    bool ContainsLink(LinkId linkId) const;
    
    // Execution (for BehaviorGraph containers)
    virtual void Run(App* app);  // Execute container contents (only if active)
    
    // Rendering
    virtual void Render(App* app, Pin* newLinkPin);  // Only if not hidden
    
    // State management
    uint32_t GetFlags() const { return m_Flags; }
    void SetFlag(ContainerFlags flag, bool value);
    bool HasFlag(ContainerFlags flag) const { return (m_Flags & flag) != 0; }
    
    // Serialization
    virtual void Serialize(crude_json::value& json) const;
    virtual void Deserialize(const crude_json::value& json, App* app);
    
    // Validation
    virtual bool CanAddNode(Node* node) const { return true; }
    virtual bool CanAddLink(Link* link) const;  // Check if link is valid for this container
    
protected:
    ed::NodeId m_ID;
    std::string m_Name;
    GroupDisplayMode m_DisplayMode;  // For groups only
    ImVec2 m_Size;  // For groups only (ed::Group size)
    
    // Container flags
    enum ContainerFlags {
        ContainerFlag_Hidden   = 1 << 0,
        ContainerFlag_Disabled = 1 << 1,
        ContainerFlag_Running  = 1 << 2,
        ContainerFlag_Error    = 1 << 3
    };
    uint32_t m_Flags = 0;  // Bitmask of ContainerFlags
};

Root Container

class RootContainer : public Container
{
public:
    RootContainer(const std::string& filename, int id);
    
    // Root-specific
    const std::string& GetFilename() const { return m_Filename; }
    void SetFilename(const std::string& filename) { m_Filename = filename; }
    bool IsDirty() const { return m_IsDirty; }
    void SetDirty(bool dirty) { m_IsDirty = dirty; }
    
    // Root has no interface pins
    void Run(App* app) override;  // Execute all blocks in root
    void Render(App* app, Pin* newLinkPin) override;  // Render root + children
    
private:
    std::string m_Filename;
    bool m_IsDirty = false;
};

BehaviorGraph Container (Group)

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
    void Run(App* app) override;  // Execute inner blocks (called from Run(Node&, App*))
    
    // Group-specific
    GroupDisplayMode GetDisplayMode() const { return m_DisplayMode; }
    void SetDisplayMode(GroupDisplayMode mode) { m_DisplayMode = mode; }
    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
    void MapGroupPinToInnerPin(ed::PinId groupPin, ed::PinId innerPin);
    void UnmapGroupPin(ed::PinId groupPin);
    
private:
    // Inherits pin vectors and mappings from Container
    // Inherits execution logic from ParameterizedBlock
};

App Structure with Multiple Root Containers

class App
{
public:
    // Root container management
    RootContainer* GetActiveRootContainer() { return m_ActiveRootContainer; }
    RootContainer* AddRootContainer(const std::string& filename);
    void RemoveRootContainer(RootContainer* container);
    void SetActiveRootContainer(RootContainer* container);
    const std::vector<RootContainer*>& GetRootContainers() const { return m_RootContainers; }
    
    // Node/Link/Pin lookups (search active root, or all roots)
    Node* FindNode(NodeId nodeId);  // Search all root containers
    Link* FindLink(LinkId linkId);  // Search all root containers
    Pin* FindPin(PinId pinId);       // Search all root containers
    Container* FindContainerForNode(NodeId nodeId);  // Which container owns this node?
    
    // File operations
    void LoadGraph(const std::string& filename);  // Creates new root container
    void SaveGraph(RootContainer* container);     // Save specific root container
    void SaveAllGraphs();                         // Save all root containers
    
    // Node creation (operates on active root)
    Node* SpawnBlockNode(const char* blockType, int nodeId = -1);
    Node* SpawnParameterNode(PinType paramType, int nodeId = -1, ...);
    
    // Runtime execution
    void ExecuteRuntimeStep();  // Execute active root container
    
    // Rendering
    void RenderNodes(Pin* newLinkPin);  // Render active root container
    
private:
    std::vector<RootContainer*> m_RootContainers;
    RootContainer* m_ActiveRootContainer = nullptr;
    
    // Backward compatibility (optional - maintain flattened view of active root)
    // Or remove m_Nodes/m_Links entirely and force container API
    std::vector<Node> m_Nodes;  // Flattened view of active root (if kept)
    std::vector<Link> m_Links;   // Flattened view of active root (if kept)
};

Key Operations

Create Group (Move Nodes)

void App::CreateGroupFromSelection(const std::vector<NodeId>& selectedNodes)
{
    auto* activeRoot = GetActiveRootContainer();
    
    // Create BehaviorGraph container
    auto* group = new BehaviorGraph(GetNextId(), "Group");
    activeRoot->AddChildContainer(group);
    
    // Move selected nodes from root → group
    for (auto nodeId : selectedNodes)
    {
        Node* node = activeRoot->FindNode(nodeId);
        if (node)
        {
            // Break external links (links to/from nodes outside selection)
            BreakExternalLinks(node, selectedNodes);
            
            // Move node
            activeRoot->RemoveNode(node);
            group->AddNode(node);
        }
    }
    
    // Move internal links (between selected nodes)
    MoveInternalLinks(selectedNodes, activeRoot, group);
}

Find Container for Node

Container* App::FindContainerForNode(NodeId nodeId)
{
    for (auto* root : m_RootContainers)
    {
        if (auto* container = root->FindContainer(nodeId))
            return container;
    }
    return nullptr;
}

Render Container Hierarchy

void RootContainer::Render(App* app, Pin* newLinkPin)
{
    // Render this container's nodes
    for (auto* node : m_Nodes)
    {
        // Render node (existing logic)
        RenderNode(node, app, newLinkPin);
    }
    
    // Render child containers (groups)
    for (auto* child : m_Children)
    {
        if (auto* group = dynamic_cast<BehaviorGraph*>(child))
        {
            if (group->GetDisplayMode() == GroupDisplayMode::Expanded)
            {
                // Render group with ed::Group()
                group->Render(app, newLinkPin);
            }
            else
            {
                // Render as collapsed node
                RenderCollapsedGroup(group, app, newLinkPin);
            }
        }
    }
}

File Structure

examples/blueprints-example/
├── containers/
│   ├── container.h           (NEW - base Container class)
│   ├── container.cpp         (NEW)
│   ├── root_container.h      (NEW - RootContainer)
│   ├── root_container.cpp    (NEW)
│   └── behavior_graph.h      (NEW - BehaviorGraph container)
│   └── behavior_graph.cpp    (NEW)
└── (existing files...)

Benefits of This Structure

  1. Multiple Graphs: App can load multiple files, each is a root container
  2. No Duplication: Nodes exist in exactly one container
  3. Natural Nesting: Groups are containers within root containers
  4. Clean Ownership: Container owns its nodes/links/children
  5. File Association: Each root container maps to a file
  6. Independent Execution: Each root container executes independently (or can cross-link?)