312 lines
11 KiB
Markdown
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?)
|
|
|