# How Relay Nodes Get Flattened During Serialization in Ceres for Optimization

> Learn how Ceres optimizes relay nodes through serialization flattening, replacing indirect connections with direct edges for accurate graph reconstruction.

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

---

**Relay nodes in Ceres are flattened during serialization by replacing indirect connections with direct edges between source and destination ports, using an `isFlattened` flag to ensure accurate graph reconstruction on load.**

When saving complex flow graphs in the Ceres visual scripting system, relay nodes present a unique serialization challenge. These utility nodes exist purely to route data between distant ports, yet storing every intermediate edge would bloat the serialized file and slow down I/O operations. According to the Ceres source code, the framework solves this by flattening relay nodes during serialization—collapsing multi-hop connections into single direct edges while preserving enough metadata to reconstruct the original topology on deserialization.

## The Relay Node Flattening Pipeline

### Step 1: Collecting Relay Metadata Before Flattening

The process begins in `FlowGraphView.SerializeGraph` located in [`Editor/Flow/UIElements/FlowGraphView.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Flow/UIElements/FlowGraphView.cs). Before the standard node compilation flattens any connections, the serializer gathers every `RelayNodeView` in the visual graph and invokes `Compile()` on each one. This preemptive collection ensures that the relay's input and output connections are recorded before they are optimized away.

### Step 2: Compiling Raw Relay Connections

In [`Editor/Core/UIElements/Graph/Nodes/RelayNodeView.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Core/UIElements/Graph/Nodes/RelayNodeView.cs), the `Compile()` method (lines 95-155) constructs a `RelayNode` data object containing the relay's GUID, position, and port type. Crucially, it iterates through the `_inputPort.connections` and `_outputPort.connections` to populate `Data.inputs` and `Data.outputs` arrays with `RelayConnection` objects. This method performs no flattening itself—it merely snapshots the raw connectivity state.

### Step 3: Resolving Final Ports Through Relay Chains

The actual flattening logic resides in [`Editor/Core/UIElements/Graph/Ports/CeresPortView.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Core/UIElements/Graph/Ports/CeresPortView.cs). When `CeresPortView.Commit` processes outgoing connections, it calls `ResolveTargetPortsThroughRelayNodes` to determine the ultimate destination ports. This helper method traverses the graph recursively: if it encounters a port belonging to a `RelayNodeView` (where `currentPort.View == null`), it continues following the relay's output connections until reaching a concrete node port. During traversal, it sets a `hasTraversedRelay` boolean flag to `true` for all final ports reached via relay chains.

### Step 4: Marking Connections as Flattened

Still within `CeresPortView.Commit` (lines 79-89), the serializer creates `PortConnectionData` objects for each resolved final port. It assigns the `isFlattened` field based on the boolean flag collected during traversal. If the connection path included any relay nodes, `isFlattened` is set to `true`; otherwise, it remains `false`. This flag serves as the critical metadata for deserialization.

### Step 5: Skipping Flattened Connections on Restore

During deserialization, `CeresPortView.Connect` (lines 48-53) iterates through the stored `PortData.connections`. It explicitly checks the `isFlattened` flag and continues to the next iteration if the value is `true`. This prevents the creation of duplicate edges that would otherwise connect directly to the relay node's ports. Instead, the missing connections are restored later when the `RelayNodeView` itself is reconstructed and its `RestoreRelayConnections` method re-applies the saved input and output connection lists from the `RelayNode` metadata.

## Performance Benefits of Relay Flattening

Flattening relay nodes during serialization delivers measurable performance improvements for Ceres flow graphs. By replacing chains of intermediate edges with single direct connections, the system reduces the total number of `PortConnectionData` objects stored in the serialized file. This compression minimizes memory footprint and accelerates both save and load operations, particularly for complex graphs containing numerous relay nodes used for organizational layout. The `isFlattened` flag ensures this optimization remains lossless—when the graph reloads, the original visual topology is reconstructed exactly as the user designed it.

## Summary

- Relay nodes flatten during serialization by replacing multi-hop connections with direct edges between source and destination ports.
- The `FlowGraphView.SerializeGraph` method collects relay metadata before flattening occurs to preserve connection data.
- `RelayNodeView.Compile()` records raw input and output connections without performing flattening.
- `CeresPortView.Commit` resolves final destination ports by traversing relay chains and sets the `isFlattened` flag in `PortConnectionData`.
- During deserialization, `CeresPortView.Connect` skips flattened connections, allowing `RelayNodeView.RestoreRelayConnections` to rebuild the original topology.

## Frequently Asked Questions

### What is the purpose of the `isFlattened` flag in Ceres serialization?

The `isFlattened` flag in `PortConnectionData` indicates whether a connection was originally routed through one or more relay nodes before being flattened into a direct edge. During deserialization, this flag prevents the creation of duplicate connections to relay ports, ensuring that the relay node itself can restore its original input and output wiring without conflicts.

### How does Ceres ensure no data is lost when flattening relay connections?

Ceres preserves connection integrity by collecting relay node metadata before any flattening occurs. The `RelayNodeView.Compile()` method stores the original input and output connections in a `RelayNode` object. Even though the serialized graph contains flattened direct edges, the relay's metadata is saved separately, allowing `RestoreRelayConnections` to reconstruct the exact original topology when the graph loads.

### Does flattening relay nodes affect runtime execution of Ceres flow graphs?

No, flattening is purely a serialization optimization. At runtime, Ceres flow graphs execute using the concrete node connections that exist in memory. The relay nodes are restored with their original connections during deserialization, so the visual representation and logical data flow remain identical to what the user designed. The optimization only reduces file size and I/O time during save and load operations.

### Where can I find the implementation of relay node flattening in the Ceres source code?

The flattening logic is distributed across several files in the `akikurisu/ceres` repository. The entry point is [`Editor/Flow/UIElements/FlowGraphView.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Flow/UIElements/FlowGraphView.cs), which orchestrates serialization. Relay node compilation occurs in [`Editor/Core/UIElements/Graph/Nodes/RelayNodeView.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Core/UIElements/Graph/Nodes/RelayNodeView.cs). The actual flattening and `isFlattened` flag assignment happen in [`Editor/Core/UIElements/Graph/Ports/CeresPortView.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Core/UIElements/Graph/Ports/CeresPortView.cs). Runtime data structures are defined in [`Runtime/Core/Models/Graph/Ports/CeresPort.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Core/Models/Graph/Ports/CeresPort.cs) and [`Runtime/Core/Models/Graph/Nodes/RelayNode.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Core/Models/Graph/Nodes/RelayNode.cs).