# Understanding the Relationship Between CeresNode, CeresPort, and CeresGraph in Unity Workflows

> Uncover the connection between CeresNode, CeresPort, and CeresGraph in Unity workflows. Learn how CeresGraph orchestrates nodes and ports for efficient data-flow execution.

- Repository: [AkiKurisu/ceres](https://github.com/akikurisu/ceres)
- Tags: deep-dive
- Published: 2026-02-24

---

**The relationship between CeresNode, CeresPort, and CeresGraph follows a container-composite pattern where CeresGraph orchestrates CeresNode instances and manages their CeresPort connections to enable data-flow execution.**

The akikurisu/ceres repository provides a node-based graph execution framework for Unity. Understanding the relationship between CeresNode, CeresPort, and CeresGraph is essential for building robust visual workflows, as these three classes form the core architecture that handles initialization, data flow, and runtime execution.

## The Three Pillars of Ceres Graph Architecture

### CeresGraph – The Workflow Container

Located in [`Runtime/Core/Models/Graph/CeresGraph.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Core/Models/Graph/CeresGraph.cs), the graph class maintains two critical collections: a `List<CeresNode> nodes` containing all executable units, and a `HashSet<CeresPort> _internalPorts` tracking every port instance across the workflow. During initialization, the graph invokes `node.InitializeVariables()` and `node.InitializePorts()` on each node, then registers every port via `graph.LinkPort()` to establish the connection topology.

### CeresNode – The Executable Unit

Defined in [`Runtime/Core/Models/Graph/Nodes/CeresNode.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Core/Models/Graph/Nodes/CeresNode.cs), this abstract base class represents individual processing steps. Each node maintains a `Dictionary<string, CeresPort> Ports` for named data endpoints and a `Dictionary<string, IList> PortLists` for variable-length port collections. Nodes expose a unique `Guid` identifier and serialized `CeresNodeData`. During runtime, the node's ports are created, linked, and used to pass data between nodes.

### CeresPort – The Data Conduit

Implemented in [`Runtime/Core/Models/Graph/Ports/CeresPort.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Core/Models/Graph/Ports/CeresPort.cs), ports are the typed channels that transport values between nodes. The abstract `CeresPort` base defines `GetValueType()` for runtime type inspection and `Link(CeresPort targetPort)` for connection establishment. The generic subclass `CeresPort<TValue>` stores a `defaultValue` field and manages the `Getter` → `AdaptedGetter` chain for value resolution. When linked, ports register with the parent graph, enabling dependency path calculation and execution order determination.

## How CeresNode, CeresPort, and CeresGraph Interact at Runtime

The relationship between these components follows a strict lifecycle during graph compilation. First, `CeresGraph.Compile()` iterates through the `nodes` list, triggering `InitializeVariables()` to set up shared blackboard bindings. Next, `InitializePorts()` populates the `Ports` and `PortLists` dictionaries with concrete `CeresPort` instances. Finally, the graph calls `LinkPort()` for every port, adding them to `_internalPorts` and resolving forward connections to build the dependency-execution order used by the runtime compiler.

## Practical Implementation: Creating and Linking Nodes

### Instantiating a Graph and Adding Nodes

```csharp
using Ceres.Graph;
using UnityEngine;

// Create the container
var graph = new CeresGraph();

// Instantiate a concrete node (e.g., a debug print node)
var printNode = new PrintNode();  // derives from CeresNode
graph.nodes.Add(printNode);

// Compile to initialize ports and variables
graph.Compile(new CeresGraphCompiler());

```

### Accessing and Configuring Ports

```csharp
// Retrieve the port by name from the node's Ports dictionary
CeresPort<string> messagePort = (CeresPort<string>)printNode.Ports["message"];

// Set a default value for unconnected execution
messagePort.defaultValue = "Hello, Ceres Graph!";

```

### Linking Nodes for Data Flow

```csharp
// Create producer and consumer nodes
var producer = new IntProducerNode();  // exposes "output" port
var consumer = new IntConsumerNode();  // expects "input" port

graph.nodes.AddRange(new CeresNode[] { producer, consumer });
graph.Compile(new CeresGraphCompiler());

// Retrieve ports and establish connection
var outPort = (CeresPort<int>)producer.Ports["output"];
var inPort = (CeresPort<int>)consumer.Ports["input"];

// Link creates the data conduit; graph tracks this in _internalPorts
outPort.Link(inPort);

```

### Referencing Nodes by GUID

```csharp
// Store a reference using the node's unique identifier
NodeReference nodeRef = new NodeReference(producer.Guid);

// Later, resolve the reference against the graph instance
CeresNode resolvedNode = nodeRef.Get(graph);
Debug.Assert(resolvedNode == producer);

```

## Summary

- **CeresGraph** serves as the workflow container, managing the `nodes` list and `_internalPorts` collection while orchestrating initialization via `Compile()`.
- **CeresNode** represents individual executable units, exposing `Ports` and `PortLists` dictionaries that define data endpoints for inter-node communication.
- **CeresPort** acts as the typed data conduit, with `CeresPort<TValue>` storing `defaultValue` and supporting `Link()` operations that the graph tracks to calculate execution order.
- The initialization sequence—`InitializeVariables()`, `InitializePorts()`, then `LinkPort()`—establishes the runtime topology that drives the Ceres execution engine.

## Frequently Asked Questions

### What is the difference between CeresPort and CeresPort<TValue>?

`CeresPort` is the abstract base class that defines the common API for all ports, including `GetValueType()` and `Link()`. `CeresPort<TValue>` is the generic concrete implementation that stores a strongly-typed `defaultValue` field and manages type-specific value resolution through the `Getter` chain. You interact with the generic version when declaring typed data endpoints in custom nodes.

### How does CeresGraph maintain connections between nodes?

The graph maintains a `HashSet<CeresPort> _internalPorts` that tracks every port instance across all nodes. When `Compile()` is called, the graph invokes `LinkPort()` for each port, registering them in this internal set. This registration enables the graph to resolve forward links, calculate dependency paths, and determine the correct execution order for the runtime compiler.

### Can I access a CeresNode after compilation without a direct reference?

Yes, every `CeresNode` exposes a unique `Guid` identifier that persists across serialization. You can store this identifier in a `NodeReference` struct and later call `NodeReference.Get(graph)` to resolve the actual node instance from the graph's `nodes` list. This is essential for runtime debugging and serialized graph reconstruction.

### When should I use PortLists versus individual Ports in CeresNode?

Use the `Ports` dictionary (`Dictionary<string, CeresPort>`) for single, named data endpoints like "input" or "output". Use `PortLists` (`Dictionary<string, IList>`) when your node needs to accept a variable number of connections, such as an array of inputs or multiple execution flows. The graph initialization process handles both collections during `InitializePorts()`.