BPVM Snack Pack #10 - The Function Factory: Where Graphs Become Functions
Your Event Graph isn't really a graph when it runs. It's transformed into a giant function called the Ubergraph. Here's how the function factory works its magic.
The content in this post is based on Unreal Engine 5.6.0
BPVM Snack Pack - Quick Blueprint knowledge drops! Part of the Blueprint to Bytecode series.
The Multiple Graph Problem
You’ve created multiple Event Graph pages for organization:
- “Player Input” page
- “Combat Logic” page
- “UI Updates” page
Clean and organized, right? But here’s the secret: They all become ONE function.
Meet the Ubergraph
The compiler takes ALL your event graphs and merges them:
1
2
3
4
5
6
7
8
9
10
11
12
void CreateAndProcessUbergraph()
{
// Create one mega-graph
ConsolidatedEventGraph = NewObject<UEdGraph>("Ubergraph");
// Copy ALL event graph pages into it
for (UEdGraph* EventGraph : Blueprint->EventGraphs) {
MergeIntoUbergraph(EventGraph, ConsolidatedEventGraph);
}
// This is now ONE giant function!
}
Think of it like taking multiple recipe cards and combining them into one cookbook!
Why Merge Everything?
The VM doesn’t understand “pages” - it only executes functions:
1
2
3
4
5
6
7
8
9
10
11
// What you see in editor:
EventGraph_Page1 → BeginPlay node
EventGraph_Page2 → Tick node
EventGraph_Page3 → OnDamaged node
// What the VM sees:
Ubergraph() {
BeginPlay_Implementation();
Tick_Implementation();
OnDamaged_Implementation();
}
Pages are for humans. The machine wants one function.
The Function Creation Pipeline
The factory processes four types of graphs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void CreateFunctionList()
{
// 1. The Ubergraph (all event graphs merged)
if (DoesSupportEventGraphs(Blueprint)) {
CreateAndProcessUbergraph();
}
// 2. Regular function graphs
for (UEdGraph* Graph : Blueprint->FunctionGraphs) {
ProcessOneFunctionGraph(Graph);
}
// 3. Generated function graphs (from macros, etc.)
for (UEdGraph* Graph : GeneratedFunctionGraphs) {
ProcessOneFunctionGraph(Graph);
}
// 4. Interface functions
for (auto& Interface : Blueprint->ImplementedInterfaces) {
for (UEdGraph* Graph : Interface.Graphs) {
ProcessOneFunctionGraph(Graph);
}
}
}
Processing Each Function
Each graph goes through the same factory process:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void ProcessOneFunctionGraph(UEdGraph* SourceGraph)
{
// Step 1: Clone to temporary graph
UEdGraph* TempGraph = DuplicateGraph(SourceGraph);
// Step 2: Expand nodes (macros become real nodes)
ExpandAllMacroNodes(TempGraph);
// Step 3: Create function context
FKismetFunctionContext* Context = CreateFunctionContext();
Context->SourceGraph = TempGraph;
// Step 4: Add to function list
FunctionList.Add(Context);
}
The Event Node Magic
Each event in your graph becomes a function stub:
1
2
3
4
5
6
7
8
// You have a BeginPlay event node
UK2Node_Event* BeginPlayNode;
// Compiler creates a function stub
void ReceiveBeginPlay() {
// Jump to the right spot in Ubergraph
Ubergraph(ENTRY_BeginPlay);
}
Events are just entry points into the mega-function!
Function Context: The Blueprint
Each function gets a FKismetFunctionContext:
1
2
3
4
5
6
7
8
struct FKismetFunctionContext
{
UEdGraph* SourceGraph; // The visual graph
TArray<FBPTerminal*> Parameters; // Input pins
TArray<FBPTerminal*> Locals; // Local variables
TArray<UEdGraphNode*> LinearExecutionList; // Node order
TArray<FBlueprintCompiledStatement*> AllGeneratedStatements; // The code!
};
This context is the blueprint (pun intended) for building the actual function!
Macro Expansion
Macros get inlined during processing:
1
2
3
4
5
// Before expansion
CallMacro("MyUtilityMacro")
// After expansion (nodes copied directly)
Node1 → Node2 → Node3 → Node4 // The macro's actual nodes
Macros disappear - their nodes are copied right into your function!
The Ubergraph Name
Ever see this in crash logs?
1
ExecuteUbergraph_BP_MyActor
Now you know what it means - it’s the mega-function containing all your events!
Function Types Explained
Regular Functions:
1
2
ProcessOneFunctionGraph(MyFunction)
→ Creates: MyFunction()
Event Graph Events:
1
2
3
CreateAndProcessUbergraph()
→ Creates: ExecuteUbergraph_BP_MyActor()
→ With stubs: ReceiveBeginPlay(), ReceiveTick(), etc.
Interface Functions:
1
2
ProcessOneFunctionGraph(InterfaceFunc)
→ Creates: InterfaceFunc_Implementation()
The Hidden Optimization
Why merge everything into Ubergraph?
Without Ubergraph (inefficient):
1
2
3
4
void BeginPlay() { /* bytecode */ }
void Tick() { /* bytecode */ }
void OnDamaged() { /* bytecode */ }
// Three separate function calls, three contexts
With Ubergraph (optimized):
1
2
3
4
5
6
7
8
void ExecuteUbergraph(int EntryPoint) {
switch(EntryPoint) {
case 0: /* BeginPlay bytecode */
case 1: /* Tick bytecode */
case 2: /* OnDamaged bytecode */
}
// One function, shared context!
}
Quick Takeaway
- All Event Graph pages become ONE function (the Ubergraph)
- Regular functions each get their own function
- Macros are expanded inline (they disappear)
- Each function gets a FKismetFunctionContext (its blueprint)
- Events are just entry points into the Ubergraph
- Interface functions get _Implementation suffix
The Factory Never Sleeps
Every time you compile:
- Event graphs merge into Ubergraph
- Functions are processed individually
- Macros expand and vanish
- Contexts are created for each function
- The factory produces executable functions!
Want More Details?
For the complete function creation process:
Next: How everything gets linked together!
🍿 BPVM Snack Pack Series
- ← #9: Variables Become Properties
- #10: The Function Factory ← You are here
- #11: Linking and Binding →