# How the Ceres Source Generator Uses Partial Classes and GenerateFlowAttribute

> Discover how the Ceres source generator uses partial classes and GenerateFlowAttribute to automatically implement IFlowGraphContainer and IFlowGraphRuntime interfaces, merging code seamlessly at compile time.

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

---

**The Ceres source generator scans compilation for partial classes marked with `[GenerateFlow]` and emits companion [`.gen.cs`](https://github.com/akikurisu/ceres/blob/main/.gen.cs) files that implement `IFlowGraphContainer` and/or `IFlowGraphRuntime` interfaces, merging seamlessly with user code at compile time.**

The Ceres library (akikurisu/ceres) eliminates boilerplate when building flow-graph systems by leveraging Roslyn source generators. Understanding how the Ceres Source Generator works with partial classes and the `GenerateFlowAttribute` is essential for implementing custom flow containers without writing repetitive interface implementations.

## Why Partial Classes Are Required for the Ceres Source Generator

The generator emits a second source file containing another part of the same class definition. C# merges multiple `partial` class declarations into a single type at compile time, allowing generated members to coexist with user-written members. If the class were not declared `partial`, the generator would create a duplicate type definition, causing a compilation error.

This requirement is enforced in `CeresSyntaxReceiver` (registered in `FlowGraphGenerator.Initialize`), which explicitly checks for the `partial` modifier before adding a class to the candidate list:

```csharp
if (!classNode.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) return;
Candidates.Add(classNode);

```

## How the GenerateFlowAttribute Controls Code Generation

The `GenerateFlowAttribute` (defined in [`Runtime/Flow/Annotations/GenerateFlowAttribute.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Flow/Annotations/GenerateFlowAttribute.cs)) acts as the generation trigger and provides configuration via two boolean properties: `GenerateImplementation` and `GenerateRuntime`.

### Syntax Collection and Candidate Detection

The pipeline begins with `CeresSyntaxReceiver` walking the syntax tree to collect every `class` declaration that has a base list and carries the `partial` modifier. This filtering ensures only valid candidates proceed to semantic analysis.

### Attribute Detection and Argument Parsing

For each candidate, the generator obtains the `INamedTypeSymbol` and searches for the attribute using its fully qualified name `Ceres.Graph.Flow.Annotations.GenerateFlowAttribute`:

```csharp
var generateAttribute = classSymbol.GetAttributes()
    .FirstOrDefault(x => x.AttributeClass?.ToDisplayString()
        == "Ceres.Graph.Flow.Annotations.GenerateFlowAttribute");

```

The generator then reads the named arguments to determine which interfaces to implement:

```csharp
foreach (var arg in generateAttribute.NamedArguments)
{
    if (arg.Key == nameof(FlowGraphGeneratorTemplate.GenerateRuntime))
        generateRuntime = (bool)arg.Value.Value;
    else if (arg.Key == nameof(FlowGraphGeneratorTemplate.GenerateImplementation))
        generateImplementation = (bool)arg.Value.Value;
}

```

### Template Selection Based on Generation Flags

`FlowGraphGeneratorTemplate` (located in `Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/FlowGraphGeneratorTemplate.cs`) selects from four string templates based on the flag values:

- **Implementation + Runtime**: `ImplementationRuntimeTemplate`
- **Implementation only**: `ImplementationNonRuntimeTemplate`
- **Runtime only**: `NonImplementationRuntimeTemplate`
- **Neither**: Minimal stub

The generator creates a `FlowGraphGeneratorTemplate` instance, sets `Namespace`, `ClassName`, and the flag values, then calls `GenerateCode()` to concatenate the selected templates into the final source.

## Source Generator Implementation Details

The core logic resides in `Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/FlowGraphGenerator.cs`. After template selection, the generator creates a file named `{ClassName}.gen.cs`, adds it to the `GeneratedFile` list, and writes it to the compilation context via `GenerateFiles(context, generatedFiles)`.

The generated code implements `IFlowGraphContainer` (when `GenerateImplementation` is true) and/or `IFlowGraphRuntime` (when `GenerateRuntime` is true), providing properties like `Graph`, methods like `GetFlowGraph()`, and backing fields such as `_graph` and `graphData`.

## Practical Usage Examples

### Container Implementation Without Runtime

To generate only the container interface without runtime execution logic:

```csharp
using Ceres.Graph.Flow;
using Ceres.Graph.Flow.Annotations;

namespace MyGame.Flow
{
    [GenerateFlow(GenerateRuntime = false, GenerateImplementation = true)]
    public partial class MyGraph : FlowGraphObjectBase
    {
        // Custom logic here
    }
}

```

The generator produces [`MyGraph.gen.cs`](https://github.com/akikurisu/ceres/blob/main/MyGraph.gen.cs) using `ImplementationNonRuntimeTemplate`, adding `IFlowGraphContainer` implementation while omitting runtime-specific members.

### Full Generation with Both Interfaces

For classes requiring both container and runtime capabilities:

```csharp
[GenerateFlow(GenerateRuntime = true, GenerateImplementation = true)]
public partial class EnemyAI : FlowGraphObjectBase { }

```

The generated code implements both interfaces with lazy initialization:

```csharp
public partial class EnemyAI : IFlowGraphContainer, IFlowGraphRuntime
{
    [NonSerialized] private FlowGraph _graph;
    [SerializeField] private FlowGraphData graphData;

    public UObject Object => this;

    public FlowGraph Graph
    {
        get
        {
            if (_graph == null)
            {
                _graph = GetFlowGraph();
                using var ctx = FlowGraphCompilationContext.GetPooled();
                using var comp = CeresGraphCompiler.GetPooled(_graph, ctx);
                _graph.Compile(comp);
            }
            return _graph;
        }
    }

    public FlowGraph GetFlowGraph() => graphData.CreateFlowGraphInstance();
    FlowGraphData IFlowGraphContainer.GetFlowGraphData() => graphData;
    public void SetGraphData(CeresGraphData graph) => graphData = (FlowGraphData)graph;
    protected FlowGraphData GetGraphData() => graphData;
}

```

### Runtime-Only Generation

For scenarios requiring only the runtime accessor:

```csharp
[GenerateFlow(GenerateRuntime = true, GenerateImplementation = false)]
public partial class UIFlow : MonoBehaviour { }

```

This emits only the `Graph` property and related runtime logic, omitting `IFlowGraphContainer` implementation.

## Summary

- The Ceres Source Generator requires **partial classes** to merge generated code with user code without type conflicts.
- The **`[GenerateFlow]`** attribute triggers generation and controls output via `GenerateImplementation` and `GenerateRuntime` properties.
- **`CeresSyntaxReceiver`** filters candidates by checking for the `partial` modifier and base type inheritance.
- **`FlowGraphGeneratorTemplate`** selects from four code templates based on attribute arguments to implement `IFlowGraphContainer` and/or `IFlowGraphRuntime`.
- Generated files follow the convention `{ClassName}.gen.cs` and are compiled alongside the original partial class.

## Frequently Asked Questions

### Why must Ceres flow-graph classes be declared as partial?

The generator emits a separate source file containing the second part of the class definition. C# merges multiple `partial` declarations into a single type at compile time, allowing generated members to coexist with user-written code. Without the `partial` modifier, the generator would produce a conflicting type definition and compilation would fail. The `CeresSyntaxReceiver` explicitly filters out non-partial classes by checking for `SyntaxKind.PartialKeyword`.

### What is the difference between GenerateImplementation and GenerateRuntime?

`GenerateImplementation` controls whether the generated code includes the `IFlowGraphContainer` interface and its members such as `GetFlowGraphData` and `SetGraphData`. `GenerateRuntime` controls whether the class implements `IFlowGraphRuntime` and includes the lazy-initialized `Graph` property with compilation logic. You can enable either flag independently, both together, or neither, allowing you to generate exactly the boilerplate your class requires.

### Where does the generated code get saved in the Ceres repository?

The generator creates in-memory source files named `{ClassName}.gen.cs` during the compilation process. These files are not written to disk in the repository source tree; instead, they are added to the compilation context via `GenerateFiles(context, generatedFiles)` and compiled alongside your partial class. You can view the generated code in your IDE's "Generated Files" or "Analyzers" node under the project dependencies.

### Can I use GenerateFlowAttribute on classes that don't inherit from FlowGraphObjectBase?

Yes, provided the class is declared as `partial` and inherits from a base type compatible with the Ceres runtime (such as `MonoBehaviour` or `ScriptableObject` in Unity environments). The `CeresSyntaxReceiver` checks for a base list to ensure the class has inheritance, but the specific base type is flexible. The generated code will implement the requested interfaces regardless of the concrete base class, as long as the type system allows the interface implementations.