# 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 ```cpp 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 m_Nodes; // Nodes in this container std::vector m_Links; // Links in this container (between nodes in this container) std::vector 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 m_InputFlowPins; std::vector m_OutputFlowPins; std::vector m_InputParamPins; std::vector m_OutputParamPins; // Virtual connections (group pin → inner node pin) std::map m_GroupToInnerPins; // Group input pin → Inner input pin std::map 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 ```cpp 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) ```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 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 ```cpp 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& 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 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 m_Nodes; // Flattened view of active root (if kept) std::vector m_Links; // Flattened view of active root (if kept) }; ``` ## Key Operations ### Create Group (Move Nodes) ```cpp void App::CreateGroupFromSelection(const std::vector& 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 ```cpp Container* App::FindContainerForNode(NodeId nodeId) { for (auto* root : m_RootContainers) { if (auto* container = root->FindContainer(nodeId)) return container; } return nullptr; } ``` ### Render Container Hierarchy ```cpp 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(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?)