How to Implement Custom Events Using ImplementableEventAttribute in Ceres
Mark any C# method with [ImplementableEvent] to generate a graph-editable event node that routes EventBase<T> payloads through Ceres-Flow, enabling visual scripting integration for custom runtime events.
The ImplementableEventAttribute bridges compiled C# code with Ceres-Flow visual graphs through compile-time source generation. When applied to a method, this attribute triggers the Ceres source generator to create an ExecutableEvent node that can be placed in a Flow Graph, along with a matching CustomExecutionEvent type that routes runtime EventBase<T> instances to that node. This architecture enables developers to fire events from code and handle them visually in the graph, or override them programmatically at runtime.
Core Concepts and Architecture
Understanding how Ceres-Flow processes custom events requires familiarity with the components that connect attributed methods to graph execution.
| Component | Purpose | Source File |
|---|---|---|
| ImplementableEventAttribute | Marks methods for source-generation of graph-editable event nodes | Runtime/Flow/Annotations/ImplementableEventAttribute.cs |
| CustomExecutionEvent | Abstract base for generated event nodes; maintains ID-to-name mapping for event routing | Runtime/Flow/Models/Nodes/Core/CustomExecutionEvent.cs |
| ProcessEvent | Helper method that fires events through the graph's execution pipeline | Runtime/Flow/Models/FlowGraph.cs (lines 33-86) |
| OverrideEventImplementation | Runtime extension that replaces default graph logic with custom delegates | Runtime/Flow/Models/FlowGraph.cs (lines 606-608) |
The source generator creates concrete ExecutableEvent_{ClassName} nodes for every EventBase<T> type used with [ImplementableEvent], as implemented in the template at Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/CustomEventGeneratorTemplate.cs.
Step-by-Step Implementation Guide
Define a Custom Event Type
Create a class derived from EventBase<T> to serve as your event payload. This type identifies the event within the Ceres event system and carries data to graph nodes.
using Chris.Events;
public sealed class TestGlobalEvent : EventBase<TestGlobalEvent>
{
// Optional payload fields
public int Value;
}
When the project compiles, the Ceres.SourceGenerator emits a concrete ExecutableEvent_TestGlobalEvent node deriving from CustomExecutionEvent<TestGlobalEvent>.
Mark Methods with ImplementableEventAttribute
Add a method in a MonoBehaviour or any class implementing IFlowGraphRuntime and annotate it with [ImplementableEvent].
using Ceres.Graph.Flow;
using Ceres.Graph.Flow.Annotations;
using UnityEngine;
public class MyFlowBehaviour : MonoBehaviour, IFlowGraphRuntime
{
public GameObject gameObject => this.gameObject; // required by IFlowGraphRuntime
public FlowGraph Graph => /* obtain your compiled FlowGraph instance */;
[ImplementableEvent] // ← Attribute triggers source generation
private void OnTestGlobalEvent()
{
// This body executes when the custom event is raised
Debug.Log("Custom event received – custom logic runs here.");
}
}
The source generator detects the attribute, creates the corresponding ExecutableEvent_TestGlobalEvent node, and inserts a delegate port that calls OnTestGlobalEvent during graph execution.
Fire Events from Code
Invoke the generated event using ProcessEvent<T>() or SendEvent() from any C# method.
using Ceres.Graph.Flow;
public void Trigger()
{
// Create a pooled instance of the custom event, fill payload if needed
using var evt = TestGlobalEvent.GetPooled(42);
// Send it to the graph – this locates the generated ExecutableEvent node
this.SendEvent(evt); // alternative: this.ProcessEvent<TestGlobalEvent>();
}
SendEvent routes the EventBase to the CallbackEventHandler, which looks up the event name via CustomExecutionEvent.GetEventName(eventBase.EventTypeId) and executes the matching graph node.
Override Implementations at Runtime (Optional)
Replace the default graph node behavior with custom logic using OverrideEventImplementation<TEvent>().
using Ceres.Graph.Flow;
public void Init()
{
// Replace the generated node with a custom callback
this.OverrideEventImplementation<TestGlobalEvent>(evt =>
{
// Custom handling – no graph node runs
Debug.Log($"Override: received {evt.Value}");
// Prevent default graph execution if desired
evt.PreventDefault();
}).AddTo(this); // disposes automatically with the MonoBehaviour
}
This uses FlowGraphRuntimeExtensions.OverrideEventImplementation<TEvent> as implemented in Runtime/Flow/Models/FlowGraph.cs (lines 606-608). The override remains active only while the returned IDisposable is alive.
Complete Working Example
The following MonoBehaviour demonstrates the full workflow: event definition, method annotation, event firing, and runtime override.
// 1️⃣ Custom event definition
using Chris.Events;
public sealed class TestGlobalEvent : EventBase<TestGlobalEvent>
{
public int Value;
}
// 2️⃣ Behaviour exposing the event
using Ceres.Graph.Flow;
using Ceres.Graph.Flow.Annotations;
using UnityEngine;
public class TestFlowBehaviour : MonoBehaviour, IFlowGraphRuntime
{
// Provide the runtime graph (omitted here for brevity)
public FlowGraph Graph => /* your compiled FlowGraph instance */;
public GameObject gameObject => this.gameObject;
// Generated node will call this method when the event occurs
[ImplementableEvent]
private void OnTestGlobalEvent()
{
Debug.Log("Graph‑triggered custom event executed.");
}
// Fire the event from anywhere
public void Send()
{
using var evt = TestGlobalEvent.GetPooled(123);
this.SendEvent(evt); // or this.ProcessEvent<TestGlobalEvent>();
}
// Optional runtime override
private void Awake()
{
this.OverrideEventImplementation<TestGlobalEvent>(e =>
{
Debug.Log($"Override received value {e.Value}");
// Stop the default graph node from running
e.PreventDefault();
}).AddTo(this);
}
}
When Send() is called, the following occurs:
CallbackEventHandlerreceivesTestGlobalEvent.- It queries
CustomExecutionEvent.GetEventName(eventBase.EventTypeId)→"ExecutableEvent_TestGlobalEvent". - The matching
ExecutableEvent_TestGlobalEventnode executes, forwarding to the delegate port →OnTestGlobalEvent.
If an override was registered, the override runs instead, and the graph node is skipped because PreventDefault() is called.
Key Source Files and Implementation Details
Summary
-
ImplementableEventAttribute bridges C# methods and Ceres-Flow graph nodes through compile-time source generation.
-
Define event payloads by inheriting from
EventBase<T>; the generator creates matchingExecutableEventnodes automatically. -
Annotate methods with
[ImplementableEvent]to expose them as graph entry points that receive runtime events. -
Fire events via
SendEvent()orProcessEvent<T>()to trigger graph execution from code. -
Use
OverrideEventImplementation<T>()to replace generated graph logic with custom runtime delegates when needed.
Frequently Asked Questions
How does ImplementableEventAttribute differ from standard Unity events?
ImplementableEventAttribute generates a graph-editable node at compile time, whereas standard Unity events like UnityEvent are purely runtime constructs without visual representation. The attribute triggers the Ceres source generator to create a CustomExecutionEvent subclass that maps the event ID to a specific graph node name, allowing the event to be handled visually in the Flow Graph while maintaining type-safe C# integration through the CallbackEventHandler system.
Can I pass custom data with ImplementableEventAttribute?
Yes, by defining a payload class that inherits from EventBase<T>. For example, TestGlobalEvent : EventBase<TestGlobalEvent> can include public fields like int Value. When firing the event via SendEvent() or ProcessEvent<T>(), the payload travels through the CallbackEventHandler to the generated ExecutableEvent node, where it can be accessed by ports in the Flow Graph or by the annotated C# method.
Where is the code generated by ImplementableEventAttribute located?
The source generator creates concrete event node classes during compilation and injects them into the assembly. The generation logic resides in Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/CustomEventGeneratorTemplate.cs, while the runtime base classes are in Runtime/Flow/Models/Nodes/Core/CustomExecutionEvent.cs. The generated nodes follow the naming convention ExecutableEvent_{ClassName} and derive from CustomExecutionEvent<T>.
How do I prevent the default graph node from executing?
Call PreventDefault() on the event instance within an override callback registered via OverrideEventImplementation<TEvent>(). This method, located in Runtime/Flow/Models/FlowGraph.cs (lines 606-608), returns an IDisposable that maintains the override scope. When PreventDefault() is invoked, the event system skips the standard graph node execution, allowing your custom delegate to handle the event exclusively.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →