19 KiB
Class Diagram Overview
Compact class diagram showing key classes and their relationships.
┌─────────────────────────────────────┐
│ Application │
│ (Base application lifecycle) │
├─────────────────────────────────────┤
│ +OnStart() │
│ +OnStop() │
│ +OnFrame(deltaTime) │
│ +TakeScreenshot() │
└──────────────┬──────────────────────┘
│ extends
▼
┌─────────────────────────────────────┐
│ App │
│ (Main application class) │
├─────────────────────────────────────┤
│ -m_Nodes: vector<Node> │
│ -m_Links: vector<Link> │
│ -m_RootContainers: vector<...> │
│ -m_ActiveRootContainer: RootContainer│
│ +GetNextId(): int │
│ +FindNode(id): Node* │
│ +FindLink(id): Link* │
│ +FindPin(id): Pin* │
│ +SpawnBlockNode(type): Node* │
│ +SpawnParameterNode(type): Node* │
│ +SaveGraph() │
│ +LoadGraph() │
│ +ExecuteRuntimeStep() │
│ +RenderNodes() │
│ +RenderLinks() │
└─┬────────────┬───────────────┬──────┘
│ │ │
│ manages │ owns │ uses
│ │ │
▼ ▼ ▼
┌──────────────────────┐ ┌─────────┐ ┌──────────────┐
│ RootContainer │ │ Node │ │ BlockRegistry│
│ (File-based graph) │ │ │ │ (Factory) │
├──────────────────────┤ └────┬────┘ ├──────────────┤
│ -m_Filename: string │ │ │ +CreateBlock()│
│ -m_IsDirty: bool │ │ has │ +Register() │
│ +GetFilename() │ ▼ └──────────────┘
│ +SetDirty() │ ┌─────────┐
└──────────┬───────────┘ │ Block │◄───┐
│ extends │ │ │
▼ ├─────────┤ │
┌──────────────────────┐ │ +Build() │ │ extends
│ Container │ │ +Render()│ │
│ (Hierarchical group) │ │ +Run() │ │
├──────────────────────┤ │ +Activate│ │
│ -m_NodeIds: vector │ │ Output()│ │
│ -m_LinkIds: vector │ │ +Activate│ │
│ -m_Children: vector │ │ Input() │ │
│ +GetNextId(): int │ └────┬────┘ │
│ +AddNode() │ │ │
│ +RemoveNode() │ │ │
│ +GetNodes(): vector │ │ │
│ +GetLinks(): vector │ │ │
│ +Run() │ │ │
│ +Render() │ │ │
└──────────────────────┘ │ │
│ │
│ │
┌────────────┴──┐ │
│ │ │
▼ ▼ │
┌──────────────────┐ ┌──────────────────┐
│ ParameterizedBlock│ │ ParameterNode │
│ (Block + params) │ │ (Value nodes) │
├──────────────────┤ ├──────────────────┤
│ -m_InputParams │ │ -m_ID: int │
│ -m_OutputParams │ │ -m_Name: string │
│ -m_Inputs │ │ -m_Type: PinType │
│ -m_Outputs │ │ -m_BoolValue │
│ +AddInputParam() │ │ -m_IntValue │
│ +AddOutputParam() │ │ -m_FloatValue │
│ +AddInput() │ │ +GetBool() │
│ +AddOutput() │ │ +SetBool() │
└──────────────────┘ │ +Run() │
│ +Render() │
│ +Build() │
└──────────────────┘
▲
│ creates
│
┌──────────────────┐
│ParameterRegistry │
│ (Factory) │
├──────────────────┤
│ +CreateParameter()│
└──────────────────┘
┌──────────────────────────────────────┐
│ EditorContext │
│ (Core editor & rendering) │
├──────────────────────────────────────┤
│ -m_Links: vector<Link*> │
│ -m_Nodes: vector<Node*> │
│ -m_GuidedLinks: map<LinkId, ...> │
│ +CreateLink(id): Link* │
│ +DrawNodes() │
│ +DrawLinks() │
│ +HandleControlPointDragging() │
│ +HandleGuidedLinkInteractions() │
│ +ExecuteRuntime() │
└───────┬────────────┬──────────────────┘
│ manages │ manages
│ │
▼ ▼
┌─────────┐ ┌──────────────────┐
│ Link │ │ GuidedLink │
│ │ │ (Waypoint routing)│
└─────────┘ ├──────────────────┤
│ +ID: LinkId │
│ +Mode: LinkMode │
│ +ControlPoints │
│ +AddControlPoint() │
│ +RemoveControlP()│
│ +GetCurveSegm() │
└────┬─────────────┘
│ contains
▼
┌──────────────────┐
│ ControlPoint │
│ (Waypoint) │
├──────────────────┤
│ +Position: ImVec2│
│ +IsManual: bool │
└──────────────────┘
▲
│ persists
│
┌──────────────────┐
│ LinkSettings │
│ (Persistence) │
├──────────────────┤
│ +m_ID: LinkId │
│ +m_Mode: LinkMode│
│ +m_ControlPoints │
│ +Serialize(): json│
└──────────────────┘
Relationship Legend:
───► = manages/owns (composition)
──..► = uses/creates (dependency)
──┬── = extends (inheritance)
──o── = has (aggregation)
Key Components
Application Layer
- App: Main application class managing nodes, links, containers, and runtime execution
- Extends Application base class for windowing and rendering
Block System
- Block: Abstract base class for executable nodes
- ParameterizedBlock: Block with input/output parameters and flow control
- BlockRegistry: Factory pattern for creating block instances
Parameter System
- ParameterNode: Standalone value nodes (Bool/Int/Float/String)
- ParameterRegistry: Factory for creating parameter nodes
Container System
- Container: Hierarchical node/link grouping with ID management
- RootContainer: Top-level container tied to a graph file
Editor System
- EditorContext: Core editor managing rendering and interactions
- GuidedLink: User-controllable waypoints for link routing
- ControlPoint: Individual waypoint on a guided link
- LinkSettings: Persistence for guided link configuration
Source Files Reference
| Class | Description | Header File | Implementation Files |
|---|---|---|---|
| Application | Base application class for windowing, rendering, and lifecycle management | examples/application/include/application.h |
- |
| App | Main application class managing nodes, links, containers, graph persistence, and runtime execution | examples/blueprints-example/app.h |
app-logic.cpp, app-render.cpp, app-runtime.cpp |
| Block | Abstract base class for executable nodes with Build/Render/Run interface and activation API | examples/blueprints-example/blocks/block.h |
blocks/*.cpp |
| ParameterizedBlock | Block with input/output parameters and flow control pins | examples/blueprints-example/blocks/block.h |
blocks/*.cpp |
| BlockRegistry | Factory pattern registry for creating block instances by type name | examples/blueprints-example/blocks/block.h |
- |
| ParameterNode | Standalone value nodes (Bool/Int/Float/String) with editable display modes | examples/blueprints-example/blocks/parameter_node.h |
blocks/parameter_node.cpp |
| ParameterRegistry | Factory for creating parameter nodes by type | examples/blueprints-example/blocks/parameter_node.h |
- |
| Container | Hierarchical container for grouping nodes/links with ID management and safe pointer resolution | examples/blueprints-example/containers/container.h |
containers/container.cpp |
| RootContainer | Top-level container tied to a graph file, manages root-level nodes and links | examples/blueprints-example/containers/root_container.h |
containers/root_container.cpp |
| EditorContext | Core editor managing rendering, interactions, link management, and guided link editing | imgui_node_editor_internal.h |
imgui_node_editor_render.cpp, imgui_node_editor_links.cpp |
| GuidedLink | User-controllable waypoints for custom link routing with control point management | links-guided.h |
imgui_node_editor_links.cpp |
| ControlPoint | Individual waypoint on a guided link with manual/auto placement flags | links-guided.h |
- |
| LinkSettings | Persistence structure for guided link configuration (mode, control points, snap settings) | links-guided.h |
- |
Key Methods
App Core Operations
SaveGraph()/LoadGraph(): Graph persistenceExecuteRuntimeStep(): Process activated blocksRenderNodes()/RenderLinks(): Visual renderingFindNode()/FindLink()/FindPin(): ID-based lookups
Block Execution
Block::Run(): Execute block logicBlock::ActivateOutput()/ActivateInput(): Flow controlBlock::Build(): Create node structure with pinsBlock::Render(): Custom rendering per block
Container Management
Container::GetNodes()/GetLinks(): Safe ID-to-pointer resolutionContainer::GetNextId(): Unique ID generationContainer::Run(): Execute container contents
Editor Rendering
EditorContext::DrawNodes()/DrawLinks(): Core renderingEditorContext::HandleGuidedLinkInteractions(): Waypoint editingEditorContext::ExecuteRuntime(): Runtime integration point
Simplification Suggestions
Core API Requirements
The imgui_node_editor.h API only requires:
- NodeId, LinkId, PinId: Type-safe IDs (already used correctly)
- BeginNode()/EndNode(): Node creation blocks
- BeginPin()/EndPin(): Pin creation blocks
- Link(): Create link between two PinIds
- QueryNewLink/QueryNewNode: Creation callbacks
- QueryDeletedNode/QueryDeletedLink: Deletion callbacks
- GetNodePosition/SetNodePosition: Position management
The editor does NOT require:
- ❌ Container/grouping hierarchy
- ❌ Complex inheritance chains
- ❌ Registry/factory patterns
- ❌ Duplicate storage systems
🗑️ Candidates for Removal/Simplification
1. Container System Redundancy ⚠️ HIGH IMPACT
Problem: App stores nodes/links in both m_Nodes/m_Links AND RootContainer::m_NodeIds/m_LinkIds
- Core API works with IDs directly - doesn't need containers
- Double storage = sync complexity + pointer invalidation risks
GetNodes()/GetLinks()converts IDs→pointers unnecessarily
Suggestion:
- Remove
Container/RootContainerentirely OR - Make Container just a grouping/visual feature (nodes still in App's flat list)
- Use
std::map<NodeId, Node*>for O(1) lookups instead of ID vectors
Files affected: containers/container.h, containers/root_container.h, app-logic.cpp (GetNodes/GetLinks calls)
2. Registry Pattern Overhead ⚠️ MEDIUM IMPACT
Problem: BlockRegistry and ParameterRegistry are singleton factories that just map strings to constructors
- Adds indirection for simple "create by type name" operations
- Could be simple function tables or even switch statements
Suggestion:
- Replace with
std::function<Block*(int)>map directly inApp - OR use simple
switch(typeName)if block types are finite - Keep factories only if you need dynamic plugin loading
Files affected: blocks/block.h (registry), app-logic.cpp (CreateBlock calls)
3. Block vs ParameterizedBlock Inheritance ⚠️ MEDIUM IMPACT
Problem: Two-level inheritance when most/all blocks need parameters anyway
- Base
Blockis abstract but has minimal functionality ParameterizedBlockadds params, but could just be the base class
Suggestion:
- Merge
BlockandParameterizedBlockinto singleBlockclass - OR make
ParameterizedBlockthe base, drop abstractBlockif unused - Check if any blocks actually use base
Blockwithout parameters
Files affected: blocks/block.h, all block implementations
4. LinkSettings vs GuidedLink Duplication ⚠️ LOW IMPACT
Problem: LinkSettings (for persistence) and GuidedLink (for runtime) store same data
- Two structures for same concept = sync complexity
Suggestion:
- Merge into single structure with
Serialize()method - OR make
GuidedLinkcontainLinkSettingsdirectly - Eliminates conversion between formats
Files affected: links-guided.h, save/load code
5. Node Instance Union Pattern ⚠️ LOW IMPACT
Problem: Node stores either BlockInstance* OR ParameterInstance* (mutually exclusive)
- Requires runtime type checking (
IsBlockBased(),GetBlockInstance(), etc.) - Could use
std::variant<Block*, ParameterNode*>or simpler approach
Suggestion:
- Use
std::variantfor type safety OR - Separate
BlockNodeandParameterNodecompletely (no shared base) - Current pattern works but adds complexity
Files affected: types.h (Node struct), rendering/runtime code
6. GetNodes()/GetLinks() ID→Pointer Conversion ⚠️ MEDIUM IMPACT
Problem: Container stores IDs in vectors, then converts to pointers via App::FindNode()
- Adds indirection layer, performance overhead (though minimal)
- Pointer invalidation risks when vectors resize
Suggestion:
- Store pointers directly if Container system is kept
- OR use
std::unordered_map<NodeId, Node*>for O(1) lookup - Current ID-based storage is safer but adds complexity
Files affected: containers/container.cpp (GetNodes/GetLinks implementations)
✅ What to Keep
- Block abstraction: Good separation of concerns (Build/Render/Run)
- Activation API: Useful for flow control in runtime
- Guided Links: Core feature, keep but simplify persistence
- ID-based lookups: Type safety is valuable, but consider maps vs vectors
📊 Impact Summary
| Simplification | Impact | Effort | Priority |
|---|---|---|---|
| Remove Container system | HIGH | HIGH | ⭐⭐⭐ |
| Simplify Registry | MEDIUM | LOW | ⭐⭐ |
| Merge Block inheritance | MEDIUM | MEDIUM | ⭐⭐ |
| Merge LinkSettings | LOW | LOW | ⭐ |
| Simplify Node instances | LOW | MEDIUM | ⭐ |
🔍 Recommended Investigation
Before removing anything, verify:
- Are containers used for anything beyond tracking node/link ownership? (groups? nesting?)
- Do any blocks actually inherit from
Blockdirectly without parameters? - Can we prove Container system adds value beyond App's flat storage?
- What percentage of code deals with Container vs direct Node/Link access?
Core Insight: The editor API works with IDs. Your abstractions should minimize conversion between IDs ↔ pointers ↔ containers.