# Understanding the Ceres Port System and NodePort References in Ceres Graphs

> Explore the Ceres Port system for typed data containers and learn how NodePort references enable runtime node resolution without garbage collection issues.

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

---

**The Ceres Port system provides typed data containers for visual scripting graphs, while NodePort references use weak references to `NodeReference` handles that resolve target nodes at runtime without preventing garbage collection.**

The Ceres Port system forms the backbone of data flow inside the [akikurisu/ceres](https://github.com/akikurisu/ceres) visual scripting framework. Every node field that should appear as a wired connection in the visual editor is implemented as a **port**—a typed container that either receives values as an input or provides values as an output. Understanding how the generic `CeresPort<T>` class functions, and specifically how `NodePort` references maintain graph connections via weak references, is essential for building robust node-based execution flows.

## Core Components of the Ceres Port System

### CeresPort<T> Generic Container

The foundation of the system is **`CeresPort<T>`**, a generic container defined in [`Runtime/Core/Models/Graph/Ports/CeresPort.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Core/Models/Graph/Ports/CeresPort.cs). This class holds the actual value of type `T`, a default fallback value, and the internal logic that resolves the final value from a linked source port. When a port has no incoming connection, it returns its default value; when linked, it reads from the source or an adapter that performs type conversion.

### Input and Output Attributes

Ports are declared declaratively using attributes from the `Ceres.Annotations` namespace. The **`InputPortAttribute`** marks a field as a receiving connector, while the **`OutputPortAttribute`** marks a field as a source connector. The visual editor scans for these attributes during node construction to generate the interactive connection points in the graph UI.

### Port Linking and Type Compatibility

Connections between ports are established through the **`CeresPort<T>.Link`** method. When two ports are wired together in the editor, the target port’s getter is redirected to read from the source port. The system supports **cross-type linking** via `CeresPort<T>.MakeCompatibleTo<TTarget>()`, which registers a conversion delegate. For example, an `int` port can feed a `float` port if a compatible conversion has been registered.

## How NodePort References Work

### NodePort as CeresPort<NodeReference>

**`NodePort`** is a specialized implementation where the generic argument is `NodeReference` rather than a primitive data type. Defined in [`Runtime/Core/Models/Graph/Ports/CeresPort.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Core/Models/Graph/Ports/CeresPort.cs) (lines 64‑77), `NodePort` inherits from `CeresPort<NodeReference>` and stores a **weak reference** to a target `CeresNode`. This design allows the graph to maintain a logical link to another node without preventing that node from being garbage collected if it is removed from the graph.

### Weak Reference Resolution

Internally, `NodePort` maintains a `WeakReference<CeresNode>` accessible via the `Node` property. When execution logic calls **`NodePort.Get()`**, the method attempts to resolve the target via `WeakReference.TryGetTarget`. If the target node has been destroyed or is no longer available, the method returns `null`, allowing the graph to fail gracefully rather than throwing exceptions or maintaining dangling pointers.

### Runtime Execution Flow

Flow nodes utilize `NodePort` fields to determine execution continuation. Consider the implementation in [`Runtime/Flow/Models/Nodes/Utilities/FlowNode_SwitchString.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Flow/Models/Nodes/Utilities/FlowNode_SwitchString.cs):

```csharp
public class FlowNode_SwitchString : ForwardNode, IPortArrayNode
{
    [InputPort, CeresLabel("Selection")]
    public CeresPort<string> sourceValue;

    [OutputPort(false)]
    public NodePort[] outputs;

    [OutputPort(false), CeresLabel("Default")]
    public NodePort defaultOutput;

    public override void Execute(ExecutionContext ctx)
    {
        var selected = sourceValue.Value;
        var targetPort = FindMatchingPort(selected) ?? defaultOutput;
        var nextNode = targetPort?.Get();
        nextNode?.Execute(ctx);
    }
}

```

In this pattern, the `Get()` call resolves the weak reference to obtain the next `CeresNode` instance, and execution continues only if the reference is valid. This mechanism applies to single `NodePort` fields as well as arrays like `NodePort[] outputs`.

## Practical Implementation Examples

### Defining a Typed Input Port

```csharp
public class MyNode : CeresNode
{
    [InputPort]
    public CeresPort<float> speed;
}

```

The `[InputPort]` attribute instructs the editor to render a connection point. At runtime, `speed.Value` returns either the linked input or the port’s configured default.

### Registering Cross-Type Compatibility

```csharp
// Allow linking int ports to float ports
CeresPort<int>.MakeCompatibleTo<float>(i => (float)i);

```

Once registered, a `CeresPort<float>` can accept connections from `CeresPort<int>` nodes, with the conversion applied automatically during value resolution.

### Manual Port Wiring

```csharp
CeresPort<float> source = nodeA.speed;
CeresPort<float> target = nodeB.targetSpeed;
source.Link(target);

```

This pattern is useful for custom editor scripts or programmatic graph construction, directly invoking the linking logic found in [`CeresPort.cs`](https://github.com/akikurisu/ceres/blob/main/CeresPort.cs).

### Using NodePort for Execution Branching

```csharp
public class JumpNode : CeresNode
{
    [OutputPort] 
    public NodePort next;

    public override void Execute(ExecutionContext ctx)
    {
        var target = next?.Get();
        target?.Execute(ctx);
    }
}

```

The `next.Get()` call demonstrates the weak reference resolution pattern, safely retrieving the target node if it exists.

## Summary

- **`CeresPort<T>`** in [`Runtime/Core/Models/Graph/Ports/CeresPort.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Core/Models/Graph/Ports/CeresPort.cs) provides the generic foundation for typed data flow, holding values and default fallbacks.
- **`NodePort`** specializes this system to store `NodeReference` values as weak references, enabling safe graph connections that do not prevent garbage collection.
- **Input and output attributes** drive the visual editor’s port rendering and connection capabilities.
- **`MakeCompatibleTo<T>()`** allows heterogeneous port types to link via registered conversion delegates.
- **Weak reference resolution** via `NodePort.Get()` ensures runtime safety when nodes are destroyed or disconnected.

## Frequently Asked Questions

### What is the difference between CeresPort<T> and NodePort?

**`CeresPort<T>`** is a generic container for any data type (floats, strings, custom objects), while **`NodePort`** is a concrete subclass where `T` is specifically `NodeReference`. `NodePort` adds weak reference semantics to track other nodes without strong references, whereas standard `CeresPort<T>` instances manage data values directly.

### How does the Ceres graph handle deleted nodes with active NodePort connections?

The graph handles deletions safely because `NodePort` stores a `WeakReference<CeresNode>` rather than a direct pointer. When `Get()` is called, it attempts `TryGetTarget`; if the node was destroyed, the call returns `null`. Execution logic can then branch or terminate gracefully without null reference exceptions.

### Can CeresPort types be linked if they have different generic arguments?

Yes, as long as a compatible conversion is registered. Call `CeresPort<TSource>.MakeCompatibleTo<TTarget>(converter)` to register a delegate that transforms the source type into the target type. The conversion runs automatically when the target port resolves its value.

### Where is the port linking logic implemented in the source code?

The core linking implementation resides in **[`Runtime/Core/Models/Graph/Ports/CeresPort.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Core/Models/Graph/Ports/CeresPort.cs)**, specifically within the `CeresPort<T>.Link` method and the compatibility resolution logic. The visual editor’s UI representation of ports is handled separately in [`Editor/Core/UIElements/Graph/Ports/CeresPortView.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Core/UIElements/Graph/Ports/CeresPortView.cs), which determines connection capacities (single vs. multi) based on field types.