11 KiB
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
- Multiple Graphs: App can load multiple files, each is a root container
- No Duplication: Nodes exist in exactly one container
- Natural Nesting: Groups are containers within root containers
- Clean Ownership: Container owns its nodes/links/children
- File Association: Each root container maps to a file
- Independent Execution: Each root container executes independently (or can cross-link?)