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

312 lines
11 KiB
Markdown

# 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<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
```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<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)
```cpp
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
```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<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?)