A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
DialogueCompiler.cpp
Go to the documentation of this file.
1// Copyright Csaba Molnar, Daniel Butum. All Rights Reserved.
2#include "DialogueCompiler.h"
3
4#include "Algo/Reverse.h"
5
11#include "Nodes/DlgNode.h"
12#include "DlgDialogue.h"
13#include "DlgHelper.h"
14
16{
17 check(Dialogue);
18 UDialogueGraph* DialogueGraph = CastChecked<UDialogueGraph>(Dialogue->GetGraph());
20 if (DialogueGraphNodes.Num() == 0)
21 return;
22
23 ResultDialogueNodes.Empty();
24 VisitedNodes.Empty();
25 Queue.Empty();
26 IndicesHistory.Empty();
27 NodesPath.Empty();
29
30 // The code below tries to reconstruct the Dialogue Nodes from the Graph Nodes (aka compile).
31 // The tricky part of the reconstructing (the Dialogue Nodes) is the node indices, because it takes
32 // too much time to find the graph node with index X and add it in position X in the Dialogue.Nodes Array,
33 // we simply walk the graph with a breath first search and reassign the indices as they appear.
34 // This also has the advantage that close nodes will also have indices close to each other.
35
36 // TODO(vampy): Add checking and output errors for the nodes, like orphans
37 // Step 1. Find the root and set the start node
38 GraphNodeRoot = DialogueGraph->GetRootGraphNode();
39 check(GraphNodeRoot);
41
42 // Add initially the children as we do not add the StartNode to the array of DialogueNodes
43 NodeDepth = 0;
44 NodesNumberUntilDepthIncrease = 1; // root/start node
45 NodesNumberNextDepth = 0; // we do not know yet
47
48 // Step 2. Walk the graph and set the rest of the nodes.
50
51 // Step 3. Set nodes categorization, this ignores isolated nodes
53
54 // Step 4. Add orphan nodes (nodes / node group with no parents), not connected to the start node
56
57 // Step 5. Update the dialogue data.
61
62 // Step 6. Fix old indices and update GUID for the Conditions
64
65 Dialogue->PostEditChange();
66}
67
69{
70 // Sort connections/children so that they're organized the same as user can see in the editor.
73 GraphNode->SetNodeDepth(NodeDepth);
75}
76
78{
79 GraphNode->ApplyCompilerWarnings();
80
81 // Check symmetry, dialogue node <-> graph node
83
84 // Ensure the Node has a valid GUID
85 UDlgNode* DialogueNode = GraphNode->GetMutableDialogueNode();
86 if (!DialogueNode->HasGUID())
87 {
88 DialogueNode->RegenerateGUID();
89 }
90
91 // Update depth
92 // BFS has the property that unvisited nodes in the queue all have depths that never decrease,
93 // and increase by at most 1.
95
96 // Track NodeDepth + 1 number
99 {
100 // Next depth coming up!
101 NodeDepth++;
103 // Reset for the next dept aka NodeDepth + 1
105 }
106}
107
109{
110 PreCompileGraphNode(GraphNode);
111
112 // Get the data as mutable, so what we can modify inplace
113 const UDlgNode& DialogueNode = GraphNode->GetDialogueNode();
114 const TArray<FDlgEdge>& NodeEdges = DialogueNode.GetNodeChildren();;
115
116 // Walk over direct children
117 // NOTE: GraphNode.OutputPin.LinkedTo are kept in sync with the Dialogue.Children
118 const TArray<UDlgNode*>& DialogueNodes = Dialogue->GetNodes();
119 const TArray<UDialogueGraphNode*> ChildNodes = GraphNode->GetChildNodes();
120
121 for (int32 ChildIndex = 0, ChildrenNum = ChildNodes.Num(); ChildIndex < ChildrenNum; ChildIndex++)
122 {
123 // Fail on invalid edges
124 check(NodeEdges[ChildIndex].IsValid());
125 UDialogueGraphNode* ChildNode = ChildNodes[ChildIndex];
126
127 // Sanity check to assume that the child node will have the same edge data from the parent
128 // BEFORE TargetIndex reassignment. If this fails it means that the Dialogue Node Children are not in
129 // the right order (assumption below fails).
130 const int32 ChildNodeTargetIndex = NodeEdges[ChildIndex].TargetIndex;
131 check(ChildNode == DialogueNodes[ChildNodeTargetIndex]->GetGraphNode())
132
133 // Prevent double visiting nodes
134 if (VisitedNodes.Contains(ChildNode))
135 {
136 // Node already visited, this means it has already a set index (we found a cycle)
137 GraphNode->SetEdgeTargetIndexAt(ChildIndex, ChildNode->GetDialogueNodeIndex());
138 continue;
139 }
140
141 // Traverse the queue
142 verify(Queue.Enqueue(ChildNode));
143 VisitedNodes.Add(ChildNode);
144
145 // From This Node => Parent Node
146 NodesPath.Add(ChildNode, GraphNode);
148
149 // Assume they will be added in order.
150 // Parent (GraphNode) points to the new assigned ChildNode.NodeIndex
152 GraphNode->SetEdgeTargetIndexAt(ChildIndex, NextAvailableIndex);
154 }
155
156 // Update the graph node/dialogue node with the new Node data (indices of children)
157 PostCompileGraphNode(GraphNode);
158}
159
161{
162 // Complexity O(|V| + |E|)
163 // Reassign all the indices in the queue
164 while (!Queue.IsEmpty())
165 {
166 UDialogueGraphNode* GraphNode;
167 verify(Queue.Dequeue(GraphNode));
168
169 // See children
170 CompileGraphNode(GraphNode);
171
172 // Accumulate for the Dialogue.Nodes array
173 const int32 NodeAddedIndex = ResultDialogueNodes.Add(GraphNode->GetMutableDialogueNode());
174
175 // Expect prediction to be true
176 check(NodeAddedIndex == GraphNode->GetDialogueNodeIndex());
177 }
178}
179
181 const UDialogueGraphNode* SourceNode,
182 const UDialogueGraphNode* TargetNode,
183 TArray<const UDialogueGraphNode*>& OutPath
184)
185{
186 OutPath.Empty();
187 OutPath.Add(TargetNode);
188
189 // backtrack to the source node
190 const UDialogueGraphNode* CurrentNode = TargetNode;
191 while (CurrentNode != SourceNode)
192 {
193 // Find the parent of the current node in the path
194 const UDialogueGraphNode** ParentNodePtr = NodesPath.Find(CurrentNode);
195
196 // Something went wrong :(
197 if (ParentNodePtr == nullptr)
198 {
199 return false;
200 }
201
202 // continue searching
203 OutPath.Add(*ParentNodePtr);
204 CurrentNode = *ParentNodePtr;
205 }
206
207 Algo::Reverse(OutPath);
208 return true;
209}
210
212 const UDialogueGraphNode* SourceNode,
213 const UDialogueGraphNode* TargetNode,
214 TSet<const UDialogueGraphNode*>& OutNodesInPath
215)
216{
217 OutNodesInPath.Empty();
218 OutNodesInPath.Add(TargetNode);
219
220 const UDialogueGraphNode* CurrentNode = TargetNode;
221 while (CurrentNode != SourceNode)
222 {
223 const UDialogueGraphNode** ParentNodePtr = NodesPath.Find(CurrentNode);
224 if (ParentNodePtr == nullptr)
225 {
226 return false;
227 }
228
229 OutNodesInPath.Add(*ParentNodePtr);
230 CurrentNode = *ParentNodePtr;
231 }
232
233 return true;
234}
235
237{
238 // If there is an unique path from the root node to the child node (the node the edge points to) of this edge it means the
239 // edge is primary, otherwise it is secondary.
240 for (UDialogueGraphNode* GraphNode : VisitedNodes)
241 {
242 // Ignore the root node
243 if (GraphNode->IsRootNode())
244 {
245 continue;
246 }
247
248 TSet<const UDialogueGraphNode*> PathToThisNodeSet;
249 if (!GetPathToNodeAsSet(GraphNodeRoot, GraphNode, PathToThisNodeSet))
250 {
251 UE_LOG(LogDlgSystemEditor, Warning, TEXT("Can't find a path from the root node to the node with index = %d"), GraphNode->GetDialogueNodeIndex());
252 continue;
253 }
254
255 // (input pin) GraphNode (output pin) -> (input pin) ChildEdgeNode (output pin) -> (input pin) ChildNode (output pin)
256 for (UDialogueGraphNode_Edge* ChildEdgeNode : GraphNode->GetChildEdgeNodes())
257 {
258 // Unique path is determined by:
259 // If the path to the parent Node of this Edge (aka PathToThisNode) does not contain the ChildNode
260 // it means this path is unique (primary edge) to the ChildNode
261 ChildEdgeNode->SetIsPrimaryEdge(!PathToThisNodeSet.Contains(ChildEdgeNode->GetChildNode()));
262 }
263 }
264}
265
267{
268 // No orphans/isolated nodes
269 if (DialogueGraphNodes.Num() == VisitedNodes.Num())
270 {
271 return;
272 }
273
274 const TSet<UDialogueGraphNode*> NodesSet(DialogueGraphNodes);
275 // Get every orphan
276 while (NodesSet.Num() != VisitedNodes.Num())
277 {
278 // Nodes that are in the graph but not in the visited nodes set
279 const TSet<UDialogueGraphNode*> OrphanedNodes = NodesSet.Difference(VisitedNodes);
280
281 // Try to find the root orphan (the one with 0 inputs)
282 // This will fail if the orphan subgraph is cyclic.
283 UDialogueGraphNode* RootOrphan = nullptr;
284 for (UDialogueGraphNode* GraphNode : OrphanedNodes)
285 {
286 if (GraphNode->GetInputPin()->LinkedTo.Num() == 0)
287 {
288 RootOrphan = GraphNode;
289 break;
290 }
291 }
292 // Cyclic orphan subgraph found, choose first node
293 if (!IsValid(RootOrphan))
294 {
295 RootOrphan = CastChecked<UDialogueGraphNode>(*FDlgHelper::GetFirstSetElement(OrphanedNodes));
296 }
297
298 // Queue and assign node
299 SetNextAvailableIndexToNode(RootOrphan);
300 VisitedNodes.Add(RootOrphan);
301 Queue.Empty();
302 verify(Queue.Enqueue(RootOrphan));
304 CompileGraph();
305 }
306}
307
309{
310 // Check if we have any modified indices
311 // bool bHistoryModified = false;
312 // for (auto& Elem : IndicesHistory)
313 // {
314 // if (Elem.Key != Elem.Value)
315 // {
316 // bHistoryModified = true;
317 // break;
318 // }
319 // }
320 // if (!bHistoryModified)
321 // {
322 // return;
323 // }
324
326}
327
TSet< UDialogueGraphNode * > VisitedNodes
void PostCompileGraphNode(UDialogueGraphNode *GraphNode)
void CompileGraphNode(UDialogueGraphNode *GraphNode)
bool GetPathToNode(const UDialogueGraphNode *SourceNode, const UDialogueGraphNode *TargetNode, TArray< const UDialogueGraphNode * > &OutPath)
TQueue< UDialogueGraphNode * > Queue
TMap< const UDialogueGraphNode *, const UDialogueGraphNode * > NodesPath
void SetNextAvailableIndexToNode(UDialogueGraphNode *GraphNode)
UDialogueGraphNode_Root * GraphNodeRoot
TArray< UDlgNode * > ResultDialogueNodes
TMap< int32, int32 > IndicesHistory
bool GetPathToNodeAsSet(const UDialogueGraphNode *SourceNode, const UDialogueGraphNode *TargetNode, TSet< const UDialogueGraphNode * > &OutNodesInPath)
void PreCompileGraphNode(UDialogueGraphNode *GraphNode)
TArray< UDialogueGraphNode * > DialogueGraphNodes
static void RemapOldIndicesWithNewAndUpdateGUID(const TArray< UDialogueGraphNode * > &GraphNodes, const TMap< int32, int32 > &OldToNewIndexMap)
static TCopyQualifiersFromTo< SetType, typenameSetType::ElementType >::Type * GetFirstSetElement(SetType &Set)
Definition DlgHelper.h:373
UDialogueGraphNode_Root * GetRootGraphNode() const
TArray< UDialogueGraphNode * > GetAllDialogueGraphNodes() const
virtual int32 GetDialogueNodeIndex() const
void SetNodeDepth(int32 NewNodeDepth)
void CheckDialogueNodeSyncWithGraphNode(bool bStrictCheck=false) const
TArray< UDialogueGraphNode * > GetChildNodes() const
DlgNodeType * GetMutableDialogueNode()
virtual void SetDialogueNodeIndex(int32 InIndex)
const DlgNodeType & GetDialogueNode() const
void SetEdgeTargetIndexAt(int32 EdgeIndex, int32 NewTargetIndex)
const TArray< UDlgNode * > & GetNodes() const
UFUNCTION(BlueprintPure, Category = "Dialogue")
void SetNodes(const TArray< UDlgNode * > &InNodes)
void SetStartNode(UDlgNode *InStartNode)
void EmptyNodesGUIDToIndexMap()
UCLASS(BlueprintType, Abstract, EditInlineNew, ClassGroup = "Dialogue")
Definition DlgNode.h:40
void RegenerateGUID()
Definition DlgNode.h:113
virtual const TArray< FDlgEdge > & GetNodeChildren() const
Gets this nodes children (edges) as a const/mutable array.
Definition DlgNode.h:184
bool HasGUID() const
UFUNCTION(BlueprintPure, Category = "Dialogue|Node")
Definition DlgNode.h:111